1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 void ctf_CaptureRecord(entity flag, entity player)
26 float notification, success;
27 float cap_record = ctf_captimerecord;
28 float cap_time = (time - flag.ctf_pickuptime);
29 float f1, f2 = NO_FL_ARG;
30 string s1, s2 = NO_STR_ARG;
31 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
34 if(!ctf_captimerecord)
35 { notification = RED_OR_BLUE(flag, INFO_CTF_CAPTURE_TIME_); s1 = player.netname; f1 = (cap_time * 100); success = TRUE; }
36 else if(cap_time < cap_record)
37 { notification = RED_OR_BLUE(flag, INFO_CTF_CAPTURE_BROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = TRUE; }
39 { notification = RED_OR_BLUE(flag, INFO_CTF_CAPTURE_UNBROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = FALSE; }
42 FOR_EACH_REALCLIENT(tmp_entity)
44 if not(tmp_entity.CAPTURE_VERBOSE) { notification = RED_OR_BLUE(flag, INFO_CTF_CAPTURE_); s2 = NO_STR_ARG; f1 = f2 = NO_FL_ARG; }
45 Send_Notification(tmp_entity, MSG_INFO, notification, s1, s2, f1, f2, NO_FL_ARG);
48 // write that shit in the database
51 ctf_captimerecord = cap_time;
52 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
53 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
54 write_recordmarker(player, (time - cap_time), cap_time);
58 void ctf_FlagcarrierWaypoints(entity player)
60 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
61 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
62 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
63 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
66 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
68 float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis
69 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
70 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
71 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
74 if(current_height) // make sure we can actually do this arcing path
76 targpos = (to + ('0 0 1' * current_height));
77 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
78 if(trace_fraction < 1)
80 //print("normal arc line failed, trying to find new pos...");
81 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
82 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
83 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
84 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
85 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
88 else { targpos = to; }
90 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
92 vector desired_direction = normalize(targpos - from);
93 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
94 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
97 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
99 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
101 // directional tracing only
103 makevectors(passer_angle);
105 // find the closest point on the enemy to the center of the attack
106 float ang; // angle between shotdir and h
107 float h; // hypotenuse, which is the distance between attacker to head
108 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
110 h = vlen(head_center - passer_center);
111 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
114 vector nearest_on_line = (passer_center + a * v_forward);
115 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
117 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
118 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
120 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
125 else { return TRUE; }
129 // =======================
130 // CaptureShield Functions
131 // =======================
133 float ctf_CaptureShield_CheckStatus(entity p)
137 float players_worseeq, players_total;
139 if(ctf_captureshield_max_ratio <= 0)
142 s = PlayerScore_Add(p, SP_SCORE, 0);
143 if(s >= -ctf_captureshield_min_negscore)
146 players_total = players_worseeq = 0;
149 if(IsDifferentTeam(e, p))
151 se = PlayerScore_Add(e, SP_SCORE, 0);
157 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
158 // use this rule here
160 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
166 void ctf_CaptureShield_Update(entity player, float wanted_status)
168 float updated_status = ctf_CaptureShield_CheckStatus(player);
169 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
171 if(updated_status) // TODO csqc notifier for this // Samual: How?
172 Send_Notification(player, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
174 Send_Notification(player, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_FREE, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
176 player.ctf_captureshielded = updated_status;
180 float ctf_CaptureShield_Customize()
182 if(!other.ctf_captureshielded) { return FALSE; }
183 if(!IsDifferentTeam(self, other)) { return FALSE; }
188 void ctf_CaptureShield_Touch()
190 if(!other.ctf_captureshielded) { return; }
191 if(!IsDifferentTeam(self, other)) { return; }
193 vector mymid = (self.absmin + self.absmax) * 0.5;
194 vector othermid = (other.absmin + other.absmax) * 0.5;
196 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
197 Send_Notification(other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
200 void ctf_CaptureShield_Spawn(entity flag)
202 entity shield = spawn();
205 shield.team = self.team;
206 shield.touch = ctf_CaptureShield_Touch;
207 shield.customizeentityforclient = ctf_CaptureShield_Customize;
208 shield.classname = "ctf_captureshield";
209 shield.effects = EF_ADDITIVE;
210 shield.movetype = MOVETYPE_NOCLIP;
211 shield.solid = SOLID_TRIGGER;
212 shield.avelocity = '7 0 11';
215 setorigin(shield, self.origin);
216 setmodel(shield, "models/ctf/shield.md3");
217 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
221 // ====================
222 // Drop/Pass/Throw Code
223 // ====================
225 void ctf_Handle_Drop(entity flag, entity player, float droptype)
228 player = (player ? player : flag.pass_sender);
231 flag.movetype = MOVETYPE_TOSS;
232 flag.takedamage = DAMAGE_YES;
233 flag.angles = '0 0 0';
234 flag.health = flag.max_flag_health;
235 flag.ctf_droptime = time;
236 flag.ctf_dropper = player;
237 flag.ctf_status = FLAG_DROPPED;
239 // messages and sounds
240 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
241 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
242 ctf_EventLog("dropped", player.team, player);
245 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
246 PlayerScore_Add(player, SP_CTF_DROPS, 1);
249 if(autocvar_g_ctf_flag_dropped_waypoint)
250 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
252 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
254 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
255 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
258 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
260 if(droptype == DROP_PASS)
262 flag.pass_distance = 0;
263 flag.pass_sender = world;
264 flag.pass_target = world;
268 void ctf_Handle_Retrieve(entity flag, entity player)
270 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
271 entity sender = flag.pass_sender;
273 // transfer flag to player
275 flag.owner.flagcarried = flag;
278 setattachment(flag, player, "");
279 setorigin(flag, FLAG_CARRY_OFFSET);
280 flag.movetype = MOVETYPE_NONE;
281 flag.takedamage = DAMAGE_NO;
282 flag.solid = SOLID_NOT;
283 flag.angles = '0 0 0';
284 flag.ctf_status = FLAG_CARRY;
286 // messages and sounds
287 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
288 ctf_EventLog("receive", flag.team, player);
290 FOR_EACH_REALPLAYER(tmp_player)
292 if(tmp_player == sender)
293 Send_Notification(tmp_player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_PASS_SENT_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
294 else if(tmp_player == player)
295 Send_Notification(tmp_player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
296 else if(!IsDifferentTeam(tmp_player, sender))
297 Send_Notification(tmp_player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
300 // create new waypoint
301 ctf_FlagcarrierWaypoints(player);
303 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
304 player.throw_antispam = sender.throw_antispam;
306 flag.pass_distance = 0;
307 flag.pass_sender = world;
308 flag.pass_target = world;
311 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
313 entity flag = player.flagcarried;
314 vector targ_origin, flag_velocity;
316 if(!flag) { return; }
317 if((droptype == DROP_PASS) && !receiver) { return; }
319 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
322 setattachment(flag, world, "");
323 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
324 flag.owner.flagcarried = world;
326 flag.solid = SOLID_TRIGGER;
327 flag.ctf_dropper = player;
328 flag.ctf_droptime = time;
330 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
337 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
338 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
339 WarpZone_RefSys_Copy(flag, receiver);
340 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
341 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
343 flag.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' * player.origin_x) + ('0 1 0' * player.origin_y))); // for the sake of this check, exclude Z axis
344 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
347 flag.movetype = MOVETYPE_FLY;
348 flag.takedamage = DAMAGE_NO;
349 flag.pass_sender = player;
350 flag.pass_target = receiver;
351 flag.ctf_status = FLAG_PASSING;
354 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
355 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
356 ctf_EventLog("pass", flag.team, player);
362 makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
364 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
365 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
366 ctf_Handle_Drop(flag, player, droptype);
372 flag.velocity = '0 0 0'; // do nothing
379 flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
380 ctf_Handle_Drop(flag, player, droptype);
385 // kill old waypointsprite
386 WaypointSprite_Ping(player.wps_flagcarrier);
387 WaypointSprite_Kill(player.wps_flagcarrier);
389 if(player.wps_enemyflagcarrier)
390 WaypointSprite_Kill(player.wps_enemyflagcarrier);
393 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
401 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
403 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
404 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
405 float old_time, new_time;
407 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
409 // messages and sounds
410 ctf_CaptureRecord(enemy_flag, player);
411 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
415 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
416 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
421 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
422 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
424 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
425 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
426 if(!old_time || new_time < old_time)
427 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
430 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
431 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
434 if(capturetype == CAPTURE_NORMAL)
436 WaypointSprite_Kill(player.wps_flagcarrier);
437 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
439 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
440 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
444 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
445 ctf_RespawnFlag(enemy_flag);
448 void ctf_Handle_Return(entity flag, entity player)
450 // messages and sounds
451 Send_Notification(player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_RETURN_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
452 Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
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 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
478 float pickup_dropped_score; // used to calculate dropped pickup score
480 // attach the flag to the player
482 player.flagcarried = flag;
483 setattachment(flag, player, "");
484 setorigin(flag, FLAG_CARRY_OFFSET);
487 flag.movetype = MOVETYPE_NONE;
488 flag.takedamage = DAMAGE_NO;
489 flag.solid = SOLID_NOT;
490 flag.angles = '0 0 0';
491 flag.ctf_status = FLAG_CARRY;
495 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
496 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
500 // messages and sounds
501 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
502 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
504 FOR_EACH_REALPLAYER(tmp_player)
506 if(tmp_player == player)
508 Send_Notification(tmp_player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_PICKUP_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
509 if(ctf_stalemate) { Send_Notification(player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); }
511 else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
512 Send_Notification(tmp_player, MSG_CENTER, (tmp_player.PICKUP_TEAM_VERBOSE ? CENTER_CTF_PICKUP_TEAM_VERBOSE : CENTER_CTF_PICKUP_TEAM), Team_ColorCode(player.team), (tmp_player.PICKUP_TEAM_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
513 else if(IsDifferentTeam(tmp_player, player))
514 Send_Notification(tmp_player, MSG_CENTER, (tmp_player.PICKUP_ENEMY_VERBOSE ? CENTER_CTF_PICKUP_ENEMY_VERBOSE : CENTER_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), (tmp_player.PICKUP_ENEMY_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
518 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
523 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
524 ctf_EventLog("steal", flag.team, player);
530 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);
531 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);
532 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
533 PlayerTeamScore_AddScore(player, pickup_dropped_score);
534 ctf_EventLog("pickup", flag.team, player);
542 if(pickuptype == PICKUP_BASE)
544 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
545 if((player.speedrunning) && (ctf_captimerecord))
546 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
550 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
553 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
554 ctf_FlagcarrierWaypoints(player);
555 WaypointSprite_Ping(player.wps_flagcarrier);
559 // ===================
560 // Main Flag Functions
561 // ===================
563 void ctf_CheckFlagReturn(entity flag, float returntype)
565 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
567 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
569 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
573 case RETURN_DROPPED: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
574 case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
575 case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
576 case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
580 { bprint("The ", flag.netname, " has returned to base\n"); break; }
582 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
583 ctf_EventLog("returned", flag.team, world);
584 ctf_RespawnFlag(flag);
589 void ctf_CheckStalemate(void)
592 float stale_red_flags, stale_blue_flags;
595 entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
597 // build list of stale flags
598 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
600 if(autocvar_g_ctf_stalemate)
601 if(tmp_entity.ctf_status != FLAG_BASE)
602 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
604 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
605 ctf_staleflaglist = tmp_entity;
607 switch(tmp_entity.team)
609 case COLOR_TEAM1: ++stale_red_flags; break;
610 case COLOR_TEAM2: ++stale_blue_flags; break;
615 if(stale_red_flags && stale_blue_flags)
616 ctf_stalemate = TRUE;
617 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
618 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
619 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
620 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
622 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
625 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
627 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
628 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));
631 if not(wpforenemy_announced)
633 FOR_EACH_REALPLAYER(tmp_entity)
634 if(tmp_entity.flagcarried)
635 Send_Notification(tmp_entity, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
637 Send_Notification(tmp_entity, MSG_CENTER, CENTER_CTF_STALEMATE_OTHER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
639 wpforenemy_announced = TRUE;
644 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
646 if(ITEM_DAMAGE_NEEDKILL(deathtype))
648 // automatically kill the flag and return it
650 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
653 if(autocvar_g_ctf_flag_return_damage)
655 // reduce health and check if it should be returned
656 self.health = self.health - damage;
657 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
667 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
670 if(self == ctf_worldflaglist) // only for the first flag
671 FOR_EACH_CLIENT(tmp_entity)
672 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
675 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
676 dprint("wtf the flag got squashed?\n");
677 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
678 if(!trace_startsolid) // can we resize it without getting stuck?
679 setsize(self, FLAG_MIN, FLAG_MAX); }
681 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
685 self.angles = '0 0 0';
693 switch(self.ctf_status)
697 if(autocvar_g_ctf_dropped_capture_radius)
699 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
700 if(tmp_entity.ctf_status == FLAG_DROPPED)
701 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
702 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
703 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
710 if(autocvar_g_ctf_flag_dropped_floatinwater)
712 vector midpoint = ((self.absmin + self.absmax) * 0.5);
713 if(pointcontents(midpoint) == CONTENT_WATER)
715 self.velocity = self.velocity * 0.5;
717 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
718 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
720 { self.movetype = MOVETYPE_FLY; }
722 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
724 if(autocvar_g_ctf_flag_return_dropped)
726 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
729 ctf_CheckFlagReturn(self, RETURN_DROPPED);
733 if(autocvar_g_ctf_flag_return_time)
735 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
736 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
744 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
747 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
751 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
755 if(autocvar_g_ctf_stalemate)
757 if(time >= wpforenemy_nextthink)
759 ctf_CheckStalemate();
760 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
768 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
769 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
770 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
772 if((self.pass_target == world)
773 || (self.pass_target.deadflag != DEAD_NO)
774 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
775 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
776 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
778 // give up, pass failed
779 ctf_Handle_Drop(self, world, DROP_PASS);
783 // still a viable target, go for it
784 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
789 default: // this should never happen
791 dprint("ctf_FlagThink(): Flag exists with no status?\n");
799 if(gameover) { return; }
801 entity toucher = other;
803 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
804 if(ITEM_TOUCH_NEEDKILL())
807 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
811 // special touch behaviors
812 if(toucher.vehicle_flags & VHF_ISVEHICLE)
814 if(autocvar_g_ctf_allow_vehicle_touch)
815 toucher = toucher.owner; // the player is actually the vehicle owner, not other
817 return; // do nothing
819 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
821 if(time > self.wait) // if we haven't in a while, play a sound/effect
823 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
824 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
825 self.wait = time + FLAG_TOUCHRATE;
829 else if(toucher.deadflag != DEAD_NO) { return; }
831 switch(self.ctf_status)
835 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
836 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
837 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
838 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
844 if(!IsDifferentTeam(toucher, self))
845 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
846 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
847 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
853 dprint("Someone touched a flag even though it was being carried?\n");
859 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
861 if(IsDifferentTeam(toucher, self.pass_sender))
862 ctf_Handle_Return(self, toucher);
864 ctf_Handle_Retrieve(self, toucher);
872 void ctf_RespawnFlag(entity flag)
874 // check for flag respawn being called twice in a row
875 if(flag.last_respawn > time - 0.5)
876 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
878 flag.last_respawn = time;
880 // reset the player (if there is one)
881 if((flag.owner) && (flag.owner.flagcarried == flag))
883 if(flag.owner.wps_enemyflagcarrier)
884 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
886 WaypointSprite_Kill(flag.wps_flagcarrier);
888 flag.owner.flagcarried = world;
890 if(flag.speedrunning)
891 ctf_FakeTimeLimit(flag.owner, -1);
894 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
895 { WaypointSprite_Kill(flag.wps_flagdropped); }
898 setattachment(flag, world, "");
899 setorigin(flag, flag.ctf_spawnorigin);
901 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
902 flag.takedamage = DAMAGE_NO;
903 flag.health = flag.max_flag_health;
904 flag.solid = SOLID_TRIGGER;
905 flag.velocity = '0 0 0';
906 flag.angles = flag.mangle;
907 flag.flags = FL_ITEM | FL_NOTARGET;
909 flag.ctf_status = FLAG_BASE;
911 flag.pass_distance = 0;
912 flag.pass_sender = world;
913 flag.pass_target = world;
914 flag.ctf_dropper = world;
915 flag.ctf_pickuptime = 0;
916 flag.ctf_droptime = 0;
922 if(self.owner.classname == "player")
923 ctf_Handle_Throw(self.owner, world, DROP_RESET);
925 ctf_RespawnFlag(self);
928 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
931 waypoint_spawnforitem_force(self, self.origin);
932 self.nearestwaypointtimeout = 0; // activate waypointing again
933 self.bot_basewaypoint = self.nearestwaypoint;
936 WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
937 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
939 // captureshield setup
940 ctf_CaptureShield_Spawn(self);
943 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
946 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.
947 self = flag; // for later usage with droptofloor()
950 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
951 ctf_worldflaglist = flag;
953 setattachment(flag, world, "");
955 flag.netname = ((teamnumber) ? "^1REPLACETHIS^7" : "^4REPLACETHIS^7"); // ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
956 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
957 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
958 flag.classname = "item_flag_team";
959 flag.target = "###item###"; // wut?
960 flag.flags = FL_ITEM | FL_NOTARGET;
961 flag.solid = SOLID_TRIGGER;
962 flag.takedamage = DAMAGE_NO;
963 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
964 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
965 flag.health = flag.max_flag_health;
966 flag.event_damage = ctf_FlagDamage;
967 flag.pushable = TRUE;
968 flag.teleportable = TELEPORT_NORMAL;
969 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
970 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
971 flag.velocity = '0 0 0';
972 flag.mangle = flag.angles;
973 flag.reset = ctf_Reset;
974 flag.touch = ctf_FlagTouch;
975 flag.think = ctf_FlagThink;
976 flag.nextthink = time + FLAG_THINKRATE;
977 flag.ctf_status = FLAG_BASE;
979 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
980 if(!flag.scale) { flag.scale = FLAG_SCALE; }
981 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
982 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
983 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
984 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
987 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
988 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
989 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
990 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.
991 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
992 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
993 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
996 precache_sound(flag.snd_flag_taken);
997 precache_sound(flag.snd_flag_returned);
998 precache_sound(flag.snd_flag_capture);
999 precache_sound(flag.snd_flag_respawn);
1000 precache_sound(flag.snd_flag_dropped);
1001 precache_sound(flag.snd_flag_touch);
1002 precache_sound(flag.snd_flag_pass);
1003 precache_model(flag.model);
1004 precache_model("models/ctf/shield.md3");
1005 precache_model("models/ctf/shockwavetransring.md3");
1008 setmodel(flag, flag.model); // precision set below
1009 setsize(flag, FLAG_MIN, FLAG_MAX);
1010 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1012 if(autocvar_g_ctf_flag_glowtrails)
1014 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1015 flag.glow_size = 25;
1016 flag.glow_trail = 1;
1019 flag.effects |= EF_LOWPRECISION;
1020 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1021 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1024 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1026 flag.dropped_origin = flag.origin;
1027 flag.noalign = TRUE;
1028 flag.movetype = MOVETYPE_NONE;
1030 else // drop to floor, automatically find a platform and set that as spawn origin
1032 flag.noalign = FALSE;
1035 flag.movetype = MOVETYPE_TOSS;
1038 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1046 // NOTE: LEGACY CODE, needs to be re-written!
1048 void havocbot_calculate_middlepoint()
1052 vector fo = '0 0 0';
1055 f = ctf_worldflaglist;
1060 f = f.ctf_worldflagnext;
1064 havocbot_ctf_middlepoint = s * (1.0 / n);
1065 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1069 entity havocbot_ctf_find_flag(entity bot)
1072 f = ctf_worldflaglist;
1075 if (bot.team == f.team)
1077 f = f.ctf_worldflagnext;
1082 entity havocbot_ctf_find_enemy_flag(entity bot)
1085 f = ctf_worldflaglist;
1088 if (bot.team != f.team)
1090 f = f.ctf_worldflagnext;
1095 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1103 FOR_EACH_PLAYER(head)
1105 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1108 if(vlen(head.origin - org) < tc_radius)
1115 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1118 head = ctf_worldflaglist;
1121 if (self.team == head.team)
1123 head = head.ctf_worldflagnext;
1126 navigation_routerating(head, ratingscale, 10000);
1129 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1132 head = ctf_worldflaglist;
1135 if (self.team == head.team)
1137 head = head.ctf_worldflagnext;
1142 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1145 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1148 head = ctf_worldflaglist;
1151 if (self.team != head.team)
1153 head = head.ctf_worldflagnext;
1156 navigation_routerating(head, ratingscale, 10000);
1159 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1161 if not(bot_waypoints_for_items)
1163 havocbot_goalrating_ctf_enemyflag(ratingscale);
1169 head = havocbot_ctf_find_enemy_flag(self);
1174 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1177 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1181 mf = havocbot_ctf_find_flag(self);
1183 if(mf.ctf_status == FLAG_BASE)
1187 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1190 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1193 head = ctf_worldflaglist;
1196 // flag is out in the field
1197 if(head.ctf_status != FLAG_BASE)
1198 if(head.tag_entity==world) // dropped
1202 if(vlen(org-head.origin)<df_radius)
1203 navigation_routerating(head, ratingscale, 10000);
1206 navigation_routerating(head, ratingscale, 10000);
1209 head = head.ctf_worldflagnext;
1213 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1217 head = findchainfloat(bot_pickup, TRUE);
1220 // gather health and armor only
1222 if (head.health || head.armorvalue)
1223 if (vlen(head.origin - org) < sradius)
1225 // get the value of the item
1226 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1228 navigation_routerating(head, t * ratingscale, 500);
1234 void havocbot_ctf_reset_role(entity bot)
1236 float cdefense, cmiddle, coffense;
1237 entity mf, ef, head;
1240 if(bot.deadflag != DEAD_NO)
1243 if(vlen(havocbot_ctf_middlepoint)==0)
1244 havocbot_calculate_middlepoint();
1247 if (bot.flagcarried)
1249 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1253 mf = havocbot_ctf_find_flag(bot);
1254 ef = havocbot_ctf_find_enemy_flag(bot);
1256 // Retrieve stolen flag
1257 if(mf.ctf_status!=FLAG_BASE)
1259 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1263 // If enemy flag is taken go to the middle to intercept pursuers
1264 if(ef.ctf_status!=FLAG_BASE)
1266 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1270 // if there is only me on the team switch to offense
1272 FOR_EACH_PLAYER(head)
1273 if(head.team==bot.team)
1278 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1282 // Evaluate best position to take
1283 // Count mates on middle position
1284 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1286 // Count mates on defense position
1287 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1289 // Count mates on offense position
1290 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1292 if(cdefense<=coffense)
1293 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1294 else if(coffense<=cmiddle)
1295 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1297 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1300 void havocbot_role_ctf_carrier()
1302 if(self.deadflag != DEAD_NO)
1304 havocbot_ctf_reset_role(self);
1308 if (self.flagcarried == world)
1310 havocbot_ctf_reset_role(self);
1314 if (self.bot_strategytime < time)
1316 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1318 navigation_goalrating_start();
1319 havocbot_goalrating_ctf_ourbase(50000);
1322 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1324 navigation_goalrating_end();
1326 if (self.navigation_hasgoals)
1327 self.havocbot_cantfindflag = time + 10;
1328 else if (time > self.havocbot_cantfindflag)
1330 // Can't navigate to my own base, suicide!
1331 // TODO: drop it and wander around
1332 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1338 void havocbot_role_ctf_escort()
1342 if(self.deadflag != DEAD_NO)
1344 havocbot_ctf_reset_role(self);
1348 if (self.flagcarried)
1350 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1354 // If enemy flag is back on the base switch to previous role
1355 ef = havocbot_ctf_find_enemy_flag(self);
1356 if(ef.ctf_status==FLAG_BASE)
1358 self.havocbot_role = self.havocbot_previous_role;
1359 self.havocbot_role_timeout = 0;
1363 // If the flag carrier reached the base switch to defense
1364 mf = havocbot_ctf_find_flag(self);
1365 if(mf.ctf_status!=FLAG_BASE)
1366 if(vlen(ef.origin - mf.dropped_origin) < 300)
1368 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1372 // Set the role timeout if necessary
1373 if (!self.havocbot_role_timeout)
1375 self.havocbot_role_timeout = time + random() * 30 + 60;
1378 // If nothing happened just switch to previous role
1379 if (time > self.havocbot_role_timeout)
1381 self.havocbot_role = self.havocbot_previous_role;
1382 self.havocbot_role_timeout = 0;
1386 // Chase the flag carrier
1387 if (self.bot_strategytime < time)
1389 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1390 navigation_goalrating_start();
1391 havocbot_goalrating_ctf_enemyflag(30000);
1392 havocbot_goalrating_ctf_ourstolenflag(40000);
1393 havocbot_goalrating_items(10000, self.origin, 10000);
1394 navigation_goalrating_end();
1398 void havocbot_role_ctf_offense()
1403 if(self.deadflag != DEAD_NO)
1405 havocbot_ctf_reset_role(self);
1409 if (self.flagcarried)
1411 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1416 mf = havocbot_ctf_find_flag(self);
1417 ef = havocbot_ctf_find_enemy_flag(self);
1420 if(mf.ctf_status!=FLAG_BASE)
1423 pos = mf.tag_entity.origin;
1427 // Try to get it if closer than the enemy base
1428 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1430 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1435 // Escort flag carrier
1436 if(ef.ctf_status!=FLAG_BASE)
1439 pos = ef.tag_entity.origin;
1443 if(vlen(pos-mf.dropped_origin)>700)
1445 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1450 // About to fail, switch to middlefield
1453 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1457 // Set the role timeout if necessary
1458 if (!self.havocbot_role_timeout)
1459 self.havocbot_role_timeout = time + 120;
1461 if (time > self.havocbot_role_timeout)
1463 havocbot_ctf_reset_role(self);
1467 if (self.bot_strategytime < time)
1469 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1470 navigation_goalrating_start();
1471 havocbot_goalrating_ctf_ourstolenflag(50000);
1472 havocbot_goalrating_ctf_enemybase(20000);
1473 havocbot_goalrating_items(5000, self.origin, 1000);
1474 havocbot_goalrating_items(1000, self.origin, 10000);
1475 navigation_goalrating_end();
1479 // Retriever (temporary role):
1480 void havocbot_role_ctf_retriever()
1484 if(self.deadflag != DEAD_NO)
1486 havocbot_ctf_reset_role(self);
1490 if (self.flagcarried)
1492 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1496 // If flag is back on the base switch to previous role
1497 mf = havocbot_ctf_find_flag(self);
1498 if(mf.ctf_status==FLAG_BASE)
1500 havocbot_ctf_reset_role(self);
1504 if (!self.havocbot_role_timeout)
1505 self.havocbot_role_timeout = time + 20;
1507 if (time > self.havocbot_role_timeout)
1509 havocbot_ctf_reset_role(self);
1513 if (self.bot_strategytime < time)
1518 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1519 navigation_goalrating_start();
1520 havocbot_goalrating_ctf_ourstolenflag(50000);
1521 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1522 havocbot_goalrating_ctf_enemybase(30000);
1523 havocbot_goalrating_items(500, self.origin, rt_radius);
1524 navigation_goalrating_end();
1528 void havocbot_role_ctf_middle()
1532 if(self.deadflag != DEAD_NO)
1534 havocbot_ctf_reset_role(self);
1538 if (self.flagcarried)
1540 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1544 mf = havocbot_ctf_find_flag(self);
1545 if(mf.ctf_status!=FLAG_BASE)
1547 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1551 if (!self.havocbot_role_timeout)
1552 self.havocbot_role_timeout = time + 10;
1554 if (time > self.havocbot_role_timeout)
1556 havocbot_ctf_reset_role(self);
1560 if (self.bot_strategytime < time)
1564 org = havocbot_ctf_middlepoint;
1565 org_z = self.origin_z;
1567 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1568 navigation_goalrating_start();
1569 havocbot_goalrating_ctf_ourstolenflag(50000);
1570 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1571 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1572 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1573 havocbot_goalrating_items(2500, self.origin, 10000);
1574 havocbot_goalrating_ctf_enemybase(2500);
1575 navigation_goalrating_end();
1579 void havocbot_role_ctf_defense()
1583 if(self.deadflag != DEAD_NO)
1585 havocbot_ctf_reset_role(self);
1589 if (self.flagcarried)
1591 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1595 // If own flag was captured
1596 mf = havocbot_ctf_find_flag(self);
1597 if(mf.ctf_status!=FLAG_BASE)
1599 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1603 if (!self.havocbot_role_timeout)
1604 self.havocbot_role_timeout = time + 30;
1606 if (time > self.havocbot_role_timeout)
1608 havocbot_ctf_reset_role(self);
1611 if (self.bot_strategytime < time)
1616 org = mf.dropped_origin;
1617 mp_radius = havocbot_ctf_middlepoint_radius;
1619 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1620 navigation_goalrating_start();
1622 // if enemies are closer to our base, go there
1623 entity head, closestplayer = world;
1624 float distance, bestdistance = 10000;
1625 FOR_EACH_PLAYER(head)
1627 if(head.deadflag!=DEAD_NO)
1630 distance = vlen(org - head.origin);
1631 if(distance<bestdistance)
1633 closestplayer = head;
1634 bestdistance = distance;
1639 if(closestplayer.team!=self.team)
1640 if(vlen(org - self.origin)>1000)
1641 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1642 havocbot_goalrating_ctf_ourbase(30000);
1644 havocbot_goalrating_ctf_ourstolenflag(20000);
1645 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1646 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1647 havocbot_goalrating_items(10000, org, mp_radius);
1648 havocbot_goalrating_items(5000, self.origin, 10000);
1649 navigation_goalrating_end();
1653 void havocbot_role_ctf_setrole(entity bot, float role)
1655 dprint(strcat(bot.netname," switched to "));
1658 case HAVOCBOT_CTF_ROLE_CARRIER:
1660 bot.havocbot_role = havocbot_role_ctf_carrier;
1661 bot.havocbot_role_timeout = 0;
1662 bot.havocbot_cantfindflag = time + 10;
1663 bot.bot_strategytime = 0;
1665 case HAVOCBOT_CTF_ROLE_DEFENSE:
1667 bot.havocbot_role = havocbot_role_ctf_defense;
1668 bot.havocbot_role_timeout = 0;
1670 case HAVOCBOT_CTF_ROLE_MIDDLE:
1672 bot.havocbot_role = havocbot_role_ctf_middle;
1673 bot.havocbot_role_timeout = 0;
1675 case HAVOCBOT_CTF_ROLE_OFFENSE:
1677 bot.havocbot_role = havocbot_role_ctf_offense;
1678 bot.havocbot_role_timeout = 0;
1680 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1681 dprint("retriever");
1682 bot.havocbot_previous_role = bot.havocbot_role;
1683 bot.havocbot_role = havocbot_role_ctf_retriever;
1684 bot.havocbot_role_timeout = time + 10;
1685 bot.bot_strategytime = 0;
1687 case HAVOCBOT_CTF_ROLE_ESCORT:
1689 bot.havocbot_previous_role = bot.havocbot_role;
1690 bot.havocbot_role = havocbot_role_ctf_escort;
1691 bot.havocbot_role_timeout = time + 30;
1692 bot.bot_strategytime = 0;
1703 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1707 // initially clear items so they can be set as necessary later.
1708 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1709 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1711 // scan through all the flags and notify the client about them
1712 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1714 switch(flag.ctf_status)
1719 if((flag.owner == self) || (flag.pass_sender == self))
1720 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1722 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1727 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1733 // item for stopping players from capturing the flag too often
1734 if(self.ctf_captureshielded)
1735 self.items |= IT_CTF_SHIELDED;
1737 // update the health of the flag carrier waypointsprite
1738 if(self.wps_flagcarrier)
1739 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1744 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1746 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1748 if(frag_target == frag_attacker) // damage done to yourself
1750 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1751 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1753 else // damage done to everyone else
1755 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1756 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1759 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1761 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1762 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1764 frag_target.wps_helpme_time = time;
1765 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1771 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1773 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1775 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1776 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1779 if(frag_target.flagcarried)
1780 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1785 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1788 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1791 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1793 entity flag; // temporary entity for the search method
1795 if(self.flagcarried)
1796 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1798 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1800 if(flag.pass_sender == self) { flag.pass_sender = world; }
1801 if(flag.pass_target == self) { flag.pass_target = world; }
1802 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1808 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1810 if(self.flagcarried)
1811 if(!autocvar_g_ctf_portalteleport)
1812 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1817 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1819 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1821 entity player = self;
1823 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1825 // pass the flag to a team mate
1826 if(autocvar_g_ctf_pass)
1828 entity head, closest_target;
1829 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1831 while(head) // find the closest acceptable target to pass to
1833 if(head.classname == "player" && head.deadflag == DEAD_NO)
1834 if(head != player && !IsDifferentTeam(head, player))
1835 if(!head.speedrunning && !head.vehicle)
1837 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1838 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1839 vector passer_center = CENTER_OR_VIEWOFS(player);
1841 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1843 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1845 if(clienttype(head) == CLIENTTYPE_BOT)
1847 Send_Notification(player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1848 ctf_Handle_Throw(head, player, DROP_PASS);
1852 Send_Notification(head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1853 Send_Notification(player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1855 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1858 else if(player.flagcarried)
1862 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1863 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1864 { closest_target = head; }
1866 else { closest_target = head; }
1873 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1876 // throw the flag in front of you
1877 if(autocvar_g_ctf_throw && player.flagcarried)
1879 if(player.throw_count == -1)
1881 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1883 player.throw_prevtime = time;
1884 player.throw_count = 1;
1885 ctf_Handle_Throw(player, world, DROP_THROW);
1890 Send_Notification(player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, NO_STR_ARG, NO_STR_ARG, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time), NO_FL_ARG, NO_FL_ARG);
1896 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1897 else { player.throw_count += 1; }
1898 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1900 player.throw_prevtime = time;
1901 ctf_Handle_Throw(player, world, DROP_THROW);
1910 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1912 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1914 self.wps_helpme_time = time;
1915 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1917 else // create a normal help me waypointsprite
1919 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');
1920 WaypointSprite_Ping(self.wps_helpme);
1926 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1928 if(vh_player.flagcarried)
1930 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1932 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1936 setattachment(vh_player.flagcarried, vh_vehicle, "");
1937 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1938 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1939 //vh_player.flagcarried.angles = '0 0 0';
1947 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1949 if(vh_player.flagcarried)
1951 setattachment(vh_player.flagcarried, vh_player, "");
1952 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1953 vh_player.flagcarried.scale = FLAG_SCALE;
1954 vh_player.flagcarried.angles = '0 0 0';
1961 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1963 if(self.flagcarried)
1965 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1966 ctf_RespawnFlag(self);
1973 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1975 entity flag; // temporary entity for the search method
1977 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1979 switch(flag.ctf_status)
1984 // lock the flag, game is over
1985 flag.movetype = MOVETYPE_NONE;
1986 flag.takedamage = DAMAGE_NO;
1987 flag.solid = SOLID_NOT;
1988 flag.nextthink = FALSE; // stop thinking
1990 print("stopping the ", flag.netname, " from moving.\n");
1998 // do nothing for these flags
2007 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2009 havocbot_ctf_reset_role(self);
2013 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2015 GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2016 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
2017 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
2026 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2027 CTF Starting point for a player in team one (Red).
2028 Keys: "angle" viewing angle when spawning. */
2029 void spawnfunc_info_player_team1()
2031 if(g_assault) { remove(self); return; }
2033 self.team = COLOR_TEAM1; // red
2034 spawnfunc_info_player_deathmatch();
2038 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2039 CTF Starting point for a player in team two (Blue).
2040 Keys: "angle" viewing angle when spawning. */
2041 void spawnfunc_info_player_team2()
2043 if(g_assault) { remove(self); return; }
2045 self.team = COLOR_TEAM2; // blue
2046 spawnfunc_info_player_deathmatch();
2049 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2050 CTF Starting point for a player in team three (Yellow).
2051 Keys: "angle" viewing angle when spawning. */
2052 void spawnfunc_info_player_team3()
2054 if(g_assault) { remove(self); return; }
2056 self.team = COLOR_TEAM3; // yellow
2057 spawnfunc_info_player_deathmatch();
2061 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2062 CTF Starting point for a player in team four (Purple).
2063 Keys: "angle" viewing angle when spawning. */
2064 void spawnfunc_info_player_team4()
2066 if(g_assault) { remove(self); return; }
2068 self.team = COLOR_TEAM4; // purple
2069 spawnfunc_info_player_deathmatch();
2072 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2073 CTF flag for team one (Red).
2075 "angle" Angle the flag will point (minus 90 degrees)...
2076 "model" model to use, note this needs red and blue as skins 0 and 1...
2077 "noise" sound played when flag is picked up...
2078 "noise1" sound played when flag is returned by a teammate...
2079 "noise2" sound played when flag is captured...
2080 "noise3" sound played when flag is lost in the field and respawns itself...
2081 "noise4" sound played when flag is dropped by a player...
2082 "noise5" sound played when flag touches the ground... */
2083 void spawnfunc_item_flag_team1()
2085 if(!g_ctf) { remove(self); return; }
2087 ctf_FlagSetup(1, self); // 1 = red
2090 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2091 CTF flag for team two (Blue).
2093 "angle" Angle the flag will point (minus 90 degrees)...
2094 "model" model to use, note this needs red and blue as skins 0 and 1...
2095 "noise" sound played when flag is picked up...
2096 "noise1" sound played when flag is returned by a teammate...
2097 "noise2" sound played when flag is captured...
2098 "noise3" sound played when flag is lost in the field and respawns itself...
2099 "noise4" sound played when flag is dropped by a player...
2100 "noise5" sound played when flag touches the ground... */
2101 void spawnfunc_item_flag_team2()
2103 if(!g_ctf) { remove(self); return; }
2105 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2108 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2109 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2110 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.
2112 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2113 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2114 void spawnfunc_ctf_team()
2116 if(!g_ctf) { remove(self); return; }
2118 self.classname = "ctf_team";
2119 self.team = self.cnt + 1;
2122 // compatibility for quake maps
2123 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2124 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2125 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2126 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2127 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2128 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2136 void ctf_ScoreRules()
2138 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2139 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2140 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2141 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2142 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2143 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2144 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2145 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2146 ScoreRules_basics_end();
2149 // code from here on is just to support maps that don't have flag and team entities
2150 void ctf_SpawnTeam (string teamname, float teamcolor)
2155 self.classname = "ctf_team";
2156 self.netname = teamname;
2157 self.cnt = teamcolor;
2159 spawnfunc_ctf_team();
2164 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2166 // if no teams are found, spawn defaults
2167 if(find(world, classname, "ctf_team") == world)
2169 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2170 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
2171 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
2177 void ctf_Initialize()
2179 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2181 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2182 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2183 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2185 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2189 MUTATOR_DEFINITION(gamemode_ctf)
2191 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2192 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2193 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2194 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2195 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2196 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2197 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2198 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2199 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2200 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2201 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2202 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2203 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2204 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2205 MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2209 if(time > 1) // game loads at time 1
2210 error("This is a game type and it cannot be added at runtime.");
2218 error("This is a game type and it cannot be removed at runtime.");