1 #include "gamemode_ctf.qh"
7 #include "../vehicles/vehicle.qh"
10 #include "../../warpzonelib/common.qh"
11 #include "../../warpzonelib/mathlib.qh"
13 // ================================================================
14 // Official capture the flag game mode coding, reworked by Samual
15 // Last updated: September, 2012
16 // ================================================================
18 void ctf_FakeTimeLimit(entity e, float t)
21 WriteByte(MSG_ONE, 3); // svc_updatestat
22 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
24 WriteCoord(MSG_ONE, autocvar_timelimit);
26 WriteCoord(MSG_ONE, (t + 1) / 60);
29 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
31 if(autocvar_sv_eventlog)
32 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
35 void ctf_CaptureRecord(entity flag, entity player)
37 float cap_record = ctf_captimerecord;
38 float cap_time = (time - flag.ctf_pickuptime);
39 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
42 if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
43 else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
44 else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
46 // write that shit in the database
47 if((!ctf_captimerecord) || (cap_time < cap_record))
49 ctf_captimerecord = cap_time;
50 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
51 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
52 write_recordmarker(player, (time - cap_time), cap_time);
56 void ctf_FlagcarrierWaypoints(entity player)
58 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
59 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
60 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
61 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
64 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
66 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
67 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
68 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
69 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
72 if(current_height) // make sure we can actually do this arcing path
74 targpos = (to + ('0 0 1' * current_height));
75 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
76 if(trace_fraction < 1)
78 //print("normal arc line failed, trying to find new pos...");
79 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
80 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
81 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
82 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
83 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
86 else { targpos = to; }
88 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
90 vector desired_direction = normalize(targpos - from);
91 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
92 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
95 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
97 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
99 // directional tracing only
101 makevectors(passer_angle);
103 // find the closest point on the enemy to the center of the attack
104 float h; // hypotenuse, which is the distance between attacker to head
105 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
107 h = vlen(head_center - passer_center);
108 a = h * (normalize(head_center - passer_center) * v_forward);
110 vector nearest_on_line = (passer_center + a * v_forward);
111 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
113 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
114 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
116 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
121 else { return true; }
125 // =======================
126 // CaptureShield Functions
127 // =======================
129 float ctf_CaptureShield_CheckStatus(entity p)
133 float players_worseeq, players_total;
135 if(ctf_captureshield_max_ratio <= 0)
138 s = PlayerScore_Add(p, SP_SCORE, 0);
139 if(s >= -ctf_captureshield_min_negscore)
142 players_total = players_worseeq = 0;
147 se = PlayerScore_Add(e, SP_SCORE, 0);
153 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
154 // use this rule here
156 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
162 void ctf_CaptureShield_Update(entity player, float wanted_status)
164 float updated_status = ctf_CaptureShield_CheckStatus(player);
165 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
167 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
168 player.ctf_captureshielded = updated_status;
172 float ctf_CaptureShield_Customize()
174 if(!other.ctf_captureshielded) { return false; }
175 if(SAME_TEAM(self, other)) { return false; }
180 void ctf_CaptureShield_Touch()
182 if(!other.ctf_captureshielded) { return; }
183 if(SAME_TEAM(self, other)) { return; }
185 vector mymid = (self.absmin + self.absmax) * 0.5;
186 vector othermid = (other.absmin + other.absmax) * 0.5;
188 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
189 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
192 void ctf_CaptureShield_Spawn(entity flag)
194 entity shield = spawn();
197 shield.team = self.team;
198 shield.touch = ctf_CaptureShield_Touch;
199 shield.customizeentityforclient = ctf_CaptureShield_Customize;
200 shield.classname = "ctf_captureshield";
201 shield.effects = EF_ADDITIVE;
202 shield.movetype = MOVETYPE_NOCLIP;
203 shield.solid = SOLID_TRIGGER;
204 shield.avelocity = '7 0 11';
207 setorigin(shield, self.origin);
208 setmodel(shield, "models/ctf/shield.md3");
209 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
213 // ====================
214 // Drop/Pass/Throw Code
215 // ====================
217 void ctf_Handle_Drop(entity flag, entity player, float droptype)
220 player = (player ? player : flag.pass_sender);
223 flag.movetype = MOVETYPE_TOSS;
224 flag.takedamage = DAMAGE_YES;
225 flag.angles = '0 0 0';
226 flag.health = flag.max_flag_health;
227 flag.ctf_droptime = time;
228 flag.ctf_dropper = player;
229 flag.ctf_status = FLAG_DROPPED;
231 // messages and sounds
232 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
233 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
234 ctf_EventLog("dropped", player.team, player);
237 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
238 PlayerScore_Add(player, SP_CTF_DROPS, 1);
241 if(autocvar_g_ctf_flag_dropped_waypoint)
242 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));
244 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
246 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
247 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
250 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
252 if(droptype == DROP_PASS)
254 flag.pass_distance = 0;
255 flag.pass_sender = world;
256 flag.pass_target = world;
260 void ctf_Handle_Retrieve(entity flag, entity player)
262 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
263 entity sender = flag.pass_sender;
265 // transfer flag to player
267 flag.owner.flagcarried = flag;
272 setattachment(flag, player.vehicle, "");
273 setorigin(flag, VEHICLE_FLAG_OFFSET);
274 flag.scale = VEHICLE_FLAG_SCALE;
278 setattachment(flag, player, "");
279 setorigin(flag, FLAG_CARRY_OFFSET);
281 flag.movetype = MOVETYPE_NONE;
282 flag.takedamage = DAMAGE_NO;
283 flag.solid = SOLID_NOT;
284 flag.angles = '0 0 0';
285 flag.ctf_status = FLAG_CARRY;
287 // messages and sounds
288 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
289 ctf_EventLog("receive", flag.team, player);
291 FOR_EACH_REALPLAYER(tmp_player)
293 if(tmp_player == sender)
294 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
295 else if(tmp_player == player)
296 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
297 else if(SAME_TEAM(tmp_player, sender))
298 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
301 // create new waypoint
302 ctf_FlagcarrierWaypoints(player);
304 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
305 player.throw_antispam = sender.throw_antispam;
307 flag.pass_distance = 0;
308 flag.pass_sender = world;
309 flag.pass_target = world;
312 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
314 entity flag = player.flagcarried;
315 vector targ_origin, flag_velocity;
317 if(!flag) { return; }
318 if((droptype == DROP_PASS) && !receiver) { return; }
320 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
323 setattachment(flag, world, "");
324 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
325 flag.owner.flagcarried = world;
327 flag.solid = SOLID_TRIGGER;
328 flag.ctf_dropper = player;
329 flag.ctf_droptime = time;
331 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
338 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
339 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
340 WarpZone_RefSys_Copy(flag, receiver);
341 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
342 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
344 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
345 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
348 flag.movetype = MOVETYPE_FLY;
349 flag.takedamage = DAMAGE_NO;
350 flag.pass_sender = player;
351 flag.pass_target = receiver;
352 flag.ctf_status = FLAG_PASSING;
355 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
356 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
357 ctf_EventLog("pass", flag.team, player);
363 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'));
365 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)));
366 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
367 ctf_Handle_Drop(flag, player, droptype);
373 flag.velocity = '0 0 0'; // do nothing
380 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);
381 ctf_Handle_Drop(flag, player, droptype);
386 // kill old waypointsprite
387 WaypointSprite_Ping(player.wps_flagcarrier);
388 WaypointSprite_Kill(player.wps_flagcarrier);
390 if(player.wps_enemyflagcarrier)
391 WaypointSprite_Kill(player.wps_enemyflagcarrier);
394 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
402 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
404 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
405 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
406 float old_time, new_time;
408 if (!player) { return; } // without someone to give the reward to, we can't possibly cap
410 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
412 // messages and sounds
413 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
414 ctf_CaptureRecord(enemy_flag, player);
415 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
419 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
420 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
425 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
426 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
428 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
429 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
430 if(!old_time || new_time < old_time)
431 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
434 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
435 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
438 if(capturetype == CAPTURE_NORMAL)
440 WaypointSprite_Kill(player.wps_flagcarrier);
441 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
443 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
444 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
448 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
449 ctf_RespawnFlag(enemy_flag);
452 void ctf_Handle_Return(entity flag, entity player)
454 // messages and sounds
455 if(player.flags & FL_MONSTER)
457 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
461 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
462 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
464 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
465 ctf_EventLog("return", flag.team, player);
468 if(IS_PLAYER(player))
470 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
471 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
473 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
476 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
480 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
481 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
482 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
486 ctf_RespawnFlag(flag);
489 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
492 float pickup_dropped_score; // used to calculate dropped pickup score
494 // attach the flag to the player
496 player.flagcarried = flag;
499 setattachment(flag, player.vehicle, "");
500 setorigin(flag, VEHICLE_FLAG_OFFSET);
501 flag.scale = VEHICLE_FLAG_SCALE;
505 setattachment(flag, player, "");
506 setorigin(flag, FLAG_CARRY_OFFSET);
510 flag.movetype = MOVETYPE_NONE;
511 flag.takedamage = DAMAGE_NO;
512 flag.solid = SOLID_NOT;
513 flag.angles = '0 0 0';
514 flag.ctf_status = FLAG_CARRY;
518 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
519 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
523 // messages and sounds
524 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
525 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
526 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
528 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
529 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
531 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
534 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
535 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
540 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
541 ctf_EventLog("steal", flag.team, player);
547 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);
548 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);
549 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
550 PlayerTeamScore_AddScore(player, pickup_dropped_score);
551 ctf_EventLog("pickup", flag.team, player);
559 if(pickuptype == PICKUP_BASE)
561 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
562 if((player.speedrunning) && (ctf_captimerecord))
563 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
567 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
570 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
571 ctf_FlagcarrierWaypoints(player);
572 WaypointSprite_Ping(player.wps_flagcarrier);
576 // ===================
577 // Main Flag Functions
578 // ===================
580 void ctf_CheckFlagReturn(entity flag, float returntype)
582 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
584 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
586 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
590 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
591 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
592 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
593 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
597 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
599 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
600 ctf_EventLog("returned", flag.team, world);
601 ctf_RespawnFlag(flag);
606 void ctf_CheckStalemate(void)
609 float stale_red_flags = 0, stale_blue_flags = 0;
612 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
614 // build list of stale flags
615 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
617 if(autocvar_g_ctf_stalemate)
618 if(tmp_entity.ctf_status != FLAG_BASE)
619 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
621 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
622 ctf_staleflaglist = tmp_entity;
624 switch(tmp_entity.team)
626 case NUM_TEAM_1: ++stale_red_flags; break;
627 case NUM_TEAM_2: ++stale_blue_flags; break;
632 if(stale_red_flags && stale_blue_flags)
633 ctf_stalemate = true;
634 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
635 { ctf_stalemate = false; wpforenemy_announced = false; }
636 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
637 { ctf_stalemate = false; wpforenemy_announced = false; }
639 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
642 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
644 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
645 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));
648 if (!wpforenemy_announced)
650 FOR_EACH_REALPLAYER(tmp_entity)
651 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
653 wpforenemy_announced = true;
658 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
660 if(ITEM_DAMAGE_NEEDKILL(deathtype))
662 // automatically kill the flag and return it
664 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
667 if(autocvar_g_ctf_flag_return_damage)
669 // reduce health and check if it should be returned
670 self.health = self.health - damage;
671 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
681 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
684 if(self == ctf_worldflaglist) // only for the first flag
685 FOR_EACH_CLIENT(tmp_entity)
686 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
689 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
690 dprint("wtf the flag got squashed?\n");
691 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
692 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
693 setsize(self, FLAG_MIN, FLAG_MAX); }
695 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
699 self.angles = '0 0 0';
707 switch(self.ctf_status)
711 if(autocvar_g_ctf_dropped_capture_radius)
713 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
714 if(tmp_entity.ctf_status == FLAG_DROPPED)
715 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
716 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
717 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
724 if(autocvar_g_ctf_flag_dropped_floatinwater)
726 vector midpoint = ((self.absmin + self.absmax) * 0.5);
727 if(pointcontents(midpoint) == CONTENT_WATER)
729 self.velocity = self.velocity * 0.5;
731 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
732 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
734 { self.movetype = MOVETYPE_FLY; }
736 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
738 if(autocvar_g_ctf_flag_return_dropped)
740 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
743 ctf_CheckFlagReturn(self, RETURN_DROPPED);
747 if(autocvar_g_ctf_flag_return_time)
749 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
750 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
758 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
761 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
765 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
769 if(autocvar_g_ctf_stalemate)
771 if(time >= wpforenemy_nextthink)
773 ctf_CheckStalemate();
774 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
782 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
783 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
784 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
786 if((self.pass_target == world)
787 || (self.pass_target.deadflag != DEAD_NO)
788 || (self.pass_target.flagcarried)
789 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
790 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
791 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
793 // give up, pass failed
794 ctf_Handle_Drop(self, world, DROP_PASS);
798 // still a viable target, go for it
799 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
804 default: // this should never happen
806 dprint("ctf_FlagThink(): Flag exists with no status?\n");
814 if(gameover) { return; }
815 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
817 entity toucher = other;
818 float is_not_monster = (!(toucher.flags & FL_MONSTER));
820 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
821 if(ITEM_TOUCH_NEEDKILL())
824 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
828 // special touch behaviors
829 if(toucher.frozen) { return; }
830 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
832 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
833 toucher = toucher.owner; // the player is actually the vehicle owner, not other
835 return; // do nothing
837 else if(toucher.flags & FL_MONSTER)
839 if(!autocvar_g_ctf_allow_monster_touch)
840 return; // do nothing
842 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
844 if(time > self.wait) // if we haven't in a while, play a sound/effect
846 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
847 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
848 self.wait = time + FLAG_TOUCHRATE;
852 else if(toucher.deadflag != DEAD_NO) { return; }
854 switch(self.ctf_status)
858 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
859 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
860 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
861 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
867 if(SAME_TEAM(toucher, self))
868 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
869 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
870 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
876 dprint("Someone touched a flag even though it was being carried?\n");
882 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
884 if(DIFF_TEAM(toucher, self.pass_sender))
885 ctf_Handle_Return(self, toucher);
887 ctf_Handle_Retrieve(self, toucher);
895 void ctf_RespawnFlag(entity flag)
897 // check for flag respawn being called twice in a row
898 if(flag.last_respawn > time - 0.5)
899 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
901 flag.last_respawn = time;
903 // reset the player (if there is one)
904 if((flag.owner) && (flag.owner.flagcarried == flag))
906 if(flag.owner.wps_enemyflagcarrier)
907 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
909 WaypointSprite_Kill(flag.wps_flagcarrier);
911 flag.owner.flagcarried = world;
913 if(flag.speedrunning)
914 ctf_FakeTimeLimit(flag.owner, -1);
917 if((flag.owner) && (flag.owner.vehicle))
918 flag.scale = FLAG_SCALE;
920 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
921 { WaypointSprite_Kill(flag.wps_flagdropped); }
924 setattachment(flag, world, "");
925 setorigin(flag, flag.ctf_spawnorigin);
927 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
928 flag.takedamage = DAMAGE_NO;
929 flag.health = flag.max_flag_health;
930 flag.solid = SOLID_TRIGGER;
931 flag.velocity = '0 0 0';
932 flag.angles = flag.mangle;
933 flag.flags = FL_ITEM | FL_NOTARGET;
935 flag.ctf_status = FLAG_BASE;
937 flag.pass_distance = 0;
938 flag.pass_sender = world;
939 flag.pass_target = world;
940 flag.ctf_dropper = world;
941 flag.ctf_pickuptime = 0;
942 flag.ctf_droptime = 0;
944 ctf_CheckStalemate();
950 if(IS_PLAYER(self.owner))
951 ctf_Handle_Throw(self.owner, world, DROP_RESET);
953 ctf_RespawnFlag(self);
956 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
959 waypoint_spawnforitem_force(self, self.origin);
960 self.nearestwaypointtimeout = 0; // activate waypointing again
961 self.bot_basewaypoint = self.nearestwaypoint;
964 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
965 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
967 // captureshield setup
968 ctf_CaptureShield_Spawn(self);
971 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
974 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.
975 self = flag; // for later usage with droptofloor()
978 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
979 ctf_worldflaglist = flag;
981 setattachment(flag, world, "");
983 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
984 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
985 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
986 flag.classname = "item_flag_team";
987 flag.target = "###item###"; // wut?
988 flag.flags = FL_ITEM | FL_NOTARGET;
989 flag.solid = SOLID_TRIGGER;
990 flag.takedamage = DAMAGE_NO;
991 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
992 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
993 flag.health = flag.max_flag_health;
994 flag.event_damage = ctf_FlagDamage;
995 flag.pushable = true;
996 flag.teleportable = TELEPORT_NORMAL;
997 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
998 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
999 flag.velocity = '0 0 0';
1000 flag.mangle = flag.angles;
1001 flag.reset = ctf_Reset;
1002 flag.touch = ctf_FlagTouch;
1003 flag.think = ctf_FlagThink;
1004 flag.nextthink = time + FLAG_THINKRATE;
1005 flag.ctf_status = FLAG_BASE;
1008 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
1009 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1010 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
1011 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
1012 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
1013 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
1016 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
1017 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
1018 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
1019 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.
1020 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1021 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1022 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1025 precache_sound(flag.snd_flag_taken);
1026 precache_sound(flag.snd_flag_returned);
1027 precache_sound(flag.snd_flag_capture);
1028 precache_sound(flag.snd_flag_respawn);
1029 precache_sound(flag.snd_flag_dropped);
1030 precache_sound(flag.snd_flag_touch);
1031 precache_sound(flag.snd_flag_pass);
1032 precache_model(flag.model);
1033 precache_model("models/ctf/shield.md3");
1034 precache_model("models/ctf/shockwavetransring.md3");
1037 setmodel(flag, flag.model); // precision set below
1038 setsize(flag, FLAG_MIN, FLAG_MAX);
1039 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1041 if(autocvar_g_ctf_flag_glowtrails)
1043 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1044 flag.glow_size = 25;
1045 flag.glow_trail = 1;
1048 flag.effects |= EF_LOWPRECISION;
1049 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1050 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1053 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1055 flag.dropped_origin = flag.origin;
1056 flag.noalign = true;
1057 flag.movetype = MOVETYPE_NONE;
1059 else // drop to floor, automatically find a platform and set that as spawn origin
1061 flag.noalign = false;
1064 flag.movetype = MOVETYPE_TOSS;
1067 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1075 // NOTE: LEGACY CODE, needs to be re-written!
1077 void havocbot_calculate_middlepoint()
1081 vector fo = '0 0 0';
1084 f = ctf_worldflaglist;
1089 f = f.ctf_worldflagnext;
1093 havocbot_ctf_middlepoint = s * (1.0 / n);
1094 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1098 entity havocbot_ctf_find_flag(entity bot)
1101 f = ctf_worldflaglist;
1104 if (bot.team == f.team)
1106 f = f.ctf_worldflagnext;
1111 entity havocbot_ctf_find_enemy_flag(entity bot)
1114 f = ctf_worldflaglist;
1117 if (bot.team != f.team)
1119 f = f.ctf_worldflagnext;
1124 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1132 FOR_EACH_PLAYER(head)
1134 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1137 if(vlen(head.origin - org) < tc_radius)
1144 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1147 head = ctf_worldflaglist;
1150 if (self.team == head.team)
1152 head = head.ctf_worldflagnext;
1155 navigation_routerating(head, ratingscale, 10000);
1158 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1161 head = ctf_worldflaglist;
1164 if (self.team == head.team)
1166 head = head.ctf_worldflagnext;
1171 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1174 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1177 head = ctf_worldflaglist;
1180 if (self.team != head.team)
1182 head = head.ctf_worldflagnext;
1185 navigation_routerating(head, ratingscale, 10000);
1188 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1190 if (!bot_waypoints_for_items)
1192 havocbot_goalrating_ctf_enemyflag(ratingscale);
1198 head = havocbot_ctf_find_enemy_flag(self);
1203 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1206 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1210 mf = havocbot_ctf_find_flag(self);
1212 if(mf.ctf_status == FLAG_BASE)
1216 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1219 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1222 head = ctf_worldflaglist;
1225 // flag is out in the field
1226 if(head.ctf_status != FLAG_BASE)
1227 if(head.tag_entity==world) // dropped
1231 if(vlen(org-head.origin)<df_radius)
1232 navigation_routerating(head, ratingscale, 10000);
1235 navigation_routerating(head, ratingscale, 10000);
1238 head = head.ctf_worldflagnext;
1242 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1246 head = findchainfloat(bot_pickup, true);
1249 // gather health and armor only
1251 if (head.health || head.armorvalue)
1252 if (vlen(head.origin - org) < sradius)
1254 // get the value of the item
1255 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1257 navigation_routerating(head, t * ratingscale, 500);
1263 void havocbot_ctf_reset_role(entity bot)
1265 float cdefense, cmiddle, coffense;
1266 entity mf, ef, head;
1269 if(bot.deadflag != DEAD_NO)
1272 if(vlen(havocbot_ctf_middlepoint)==0)
1273 havocbot_calculate_middlepoint();
1276 if (bot.flagcarried)
1278 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1282 mf = havocbot_ctf_find_flag(bot);
1283 ef = havocbot_ctf_find_enemy_flag(bot);
1285 // Retrieve stolen flag
1286 if(mf.ctf_status!=FLAG_BASE)
1288 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1292 // If enemy flag is taken go to the middle to intercept pursuers
1293 if(ef.ctf_status!=FLAG_BASE)
1295 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1299 // if there is only me on the team switch to offense
1301 FOR_EACH_PLAYER(head)
1302 if(SAME_TEAM(head, bot))
1307 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1311 // Evaluate best position to take
1312 // Count mates on middle position
1313 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1315 // Count mates on defense position
1316 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1318 // Count mates on offense position
1319 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1321 if(cdefense<=coffense)
1322 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1323 else if(coffense<=cmiddle)
1324 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1326 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1329 void havocbot_role_ctf_carrier()
1331 if(self.deadflag != DEAD_NO)
1333 havocbot_ctf_reset_role(self);
1337 if (self.flagcarried == world)
1339 havocbot_ctf_reset_role(self);
1343 if (self.bot_strategytime < time)
1345 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1347 navigation_goalrating_start();
1348 havocbot_goalrating_ctf_ourbase(50000);
1351 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1353 navigation_goalrating_end();
1355 if (self.navigation_hasgoals)
1356 self.havocbot_cantfindflag = time + 10;
1357 else if (time > self.havocbot_cantfindflag)
1359 // Can't navigate to my own base, suicide!
1360 // TODO: drop it and wander around
1361 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1367 void havocbot_role_ctf_escort()
1371 if(self.deadflag != DEAD_NO)
1373 havocbot_ctf_reset_role(self);
1377 if (self.flagcarried)
1379 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1383 // If enemy flag is back on the base switch to previous role
1384 ef = havocbot_ctf_find_enemy_flag(self);
1385 if(ef.ctf_status==FLAG_BASE)
1387 self.havocbot_role = self.havocbot_previous_role;
1388 self.havocbot_role_timeout = 0;
1392 // If the flag carrier reached the base switch to defense
1393 mf = havocbot_ctf_find_flag(self);
1394 if(mf.ctf_status!=FLAG_BASE)
1395 if(vlen(ef.origin - mf.dropped_origin) < 300)
1397 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1401 // Set the role timeout if necessary
1402 if (!self.havocbot_role_timeout)
1404 self.havocbot_role_timeout = time + random() * 30 + 60;
1407 // If nothing happened just switch to previous role
1408 if (time > self.havocbot_role_timeout)
1410 self.havocbot_role = self.havocbot_previous_role;
1411 self.havocbot_role_timeout = 0;
1415 // Chase the flag carrier
1416 if (self.bot_strategytime < time)
1418 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1419 navigation_goalrating_start();
1420 havocbot_goalrating_ctf_enemyflag(30000);
1421 havocbot_goalrating_ctf_ourstolenflag(40000);
1422 havocbot_goalrating_items(10000, self.origin, 10000);
1423 navigation_goalrating_end();
1427 void havocbot_role_ctf_offense()
1432 if(self.deadflag != DEAD_NO)
1434 havocbot_ctf_reset_role(self);
1438 if (self.flagcarried)
1440 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1445 mf = havocbot_ctf_find_flag(self);
1446 ef = havocbot_ctf_find_enemy_flag(self);
1449 if(mf.ctf_status!=FLAG_BASE)
1452 pos = mf.tag_entity.origin;
1456 // Try to get it if closer than the enemy base
1457 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1459 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1464 // Escort flag carrier
1465 if(ef.ctf_status!=FLAG_BASE)
1468 pos = ef.tag_entity.origin;
1472 if(vlen(pos-mf.dropped_origin)>700)
1474 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1479 // About to fail, switch to middlefield
1482 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1486 // Set the role timeout if necessary
1487 if (!self.havocbot_role_timeout)
1488 self.havocbot_role_timeout = time + 120;
1490 if (time > self.havocbot_role_timeout)
1492 havocbot_ctf_reset_role(self);
1496 if (self.bot_strategytime < time)
1498 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1499 navigation_goalrating_start();
1500 havocbot_goalrating_ctf_ourstolenflag(50000);
1501 havocbot_goalrating_ctf_enemybase(20000);
1502 havocbot_goalrating_items(5000, self.origin, 1000);
1503 havocbot_goalrating_items(1000, self.origin, 10000);
1504 navigation_goalrating_end();
1508 // Retriever (temporary role):
1509 void havocbot_role_ctf_retriever()
1513 if(self.deadflag != DEAD_NO)
1515 havocbot_ctf_reset_role(self);
1519 if (self.flagcarried)
1521 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1525 // If flag is back on the base switch to previous role
1526 mf = havocbot_ctf_find_flag(self);
1527 if(mf.ctf_status==FLAG_BASE)
1529 havocbot_ctf_reset_role(self);
1533 if (!self.havocbot_role_timeout)
1534 self.havocbot_role_timeout = time + 20;
1536 if (time > self.havocbot_role_timeout)
1538 havocbot_ctf_reset_role(self);
1542 if (self.bot_strategytime < time)
1547 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1548 navigation_goalrating_start();
1549 havocbot_goalrating_ctf_ourstolenflag(50000);
1550 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1551 havocbot_goalrating_ctf_enemybase(30000);
1552 havocbot_goalrating_items(500, self.origin, rt_radius);
1553 navigation_goalrating_end();
1557 void havocbot_role_ctf_middle()
1561 if(self.deadflag != DEAD_NO)
1563 havocbot_ctf_reset_role(self);
1567 if (self.flagcarried)
1569 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1573 mf = havocbot_ctf_find_flag(self);
1574 if(mf.ctf_status!=FLAG_BASE)
1576 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1580 if (!self.havocbot_role_timeout)
1581 self.havocbot_role_timeout = time + 10;
1583 if (time > self.havocbot_role_timeout)
1585 havocbot_ctf_reset_role(self);
1589 if (self.bot_strategytime < time)
1593 org = havocbot_ctf_middlepoint;
1594 org.z = self.origin.z;
1596 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1597 navigation_goalrating_start();
1598 havocbot_goalrating_ctf_ourstolenflag(50000);
1599 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1600 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1601 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1602 havocbot_goalrating_items(2500, self.origin, 10000);
1603 havocbot_goalrating_ctf_enemybase(2500);
1604 navigation_goalrating_end();
1608 void havocbot_role_ctf_defense()
1612 if(self.deadflag != DEAD_NO)
1614 havocbot_ctf_reset_role(self);
1618 if (self.flagcarried)
1620 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1624 // If own flag was captured
1625 mf = havocbot_ctf_find_flag(self);
1626 if(mf.ctf_status!=FLAG_BASE)
1628 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1632 if (!self.havocbot_role_timeout)
1633 self.havocbot_role_timeout = time + 30;
1635 if (time > self.havocbot_role_timeout)
1637 havocbot_ctf_reset_role(self);
1640 if (self.bot_strategytime < time)
1645 org = mf.dropped_origin;
1646 mp_radius = havocbot_ctf_middlepoint_radius;
1648 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1649 navigation_goalrating_start();
1651 // if enemies are closer to our base, go there
1652 entity head, closestplayer = world;
1653 float distance, bestdistance = 10000;
1654 FOR_EACH_PLAYER(head)
1656 if(head.deadflag!=DEAD_NO)
1659 distance = vlen(org - head.origin);
1660 if(distance<bestdistance)
1662 closestplayer = head;
1663 bestdistance = distance;
1668 if(DIFF_TEAM(closestplayer, self))
1669 if(vlen(org - self.origin)>1000)
1670 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1671 havocbot_goalrating_ctf_ourbase(30000);
1673 havocbot_goalrating_ctf_ourstolenflag(20000);
1674 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1675 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1676 havocbot_goalrating_items(10000, org, mp_radius);
1677 havocbot_goalrating_items(5000, self.origin, 10000);
1678 navigation_goalrating_end();
1682 void havocbot_role_ctf_setrole(entity bot, float role)
1684 dprint(strcat(bot.netname," switched to "));
1687 case HAVOCBOT_CTF_ROLE_CARRIER:
1689 bot.havocbot_role = havocbot_role_ctf_carrier;
1690 bot.havocbot_role_timeout = 0;
1691 bot.havocbot_cantfindflag = time + 10;
1692 bot.bot_strategytime = 0;
1694 case HAVOCBOT_CTF_ROLE_DEFENSE:
1696 bot.havocbot_role = havocbot_role_ctf_defense;
1697 bot.havocbot_role_timeout = 0;
1699 case HAVOCBOT_CTF_ROLE_MIDDLE:
1701 bot.havocbot_role = havocbot_role_ctf_middle;
1702 bot.havocbot_role_timeout = 0;
1704 case HAVOCBOT_CTF_ROLE_OFFENSE:
1706 bot.havocbot_role = havocbot_role_ctf_offense;
1707 bot.havocbot_role_timeout = 0;
1709 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1710 dprint("retriever");
1711 bot.havocbot_previous_role = bot.havocbot_role;
1712 bot.havocbot_role = havocbot_role_ctf_retriever;
1713 bot.havocbot_role_timeout = time + 10;
1714 bot.bot_strategytime = 0;
1716 case HAVOCBOT_CTF_ROLE_ESCORT:
1718 bot.havocbot_previous_role = bot.havocbot_role;
1719 bot.havocbot_role = havocbot_role_ctf_escort;
1720 bot.havocbot_role_timeout = time + 30;
1721 bot.bot_strategytime = 0;
1732 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1736 // initially clear items so they can be set as necessary later.
1737 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1738 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1740 // scan through all the flags and notify the client about them
1741 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1743 switch(flag.ctf_status)
1748 if((flag.owner == self) || (flag.pass_sender == self))
1749 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1751 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1756 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1762 // item for stopping players from capturing the flag too often
1763 if(self.ctf_captureshielded)
1764 self.items |= IT_CTF_SHIELDED;
1766 // update the health of the flag carrier waypointsprite
1767 if(self.wps_flagcarrier)
1768 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1773 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1775 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1777 if(frag_target == frag_attacker) // damage done to yourself
1779 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1780 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1782 else // damage done to everyone else
1784 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1785 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1788 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1790 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)))
1791 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1793 frag_target.wps_helpme_time = time;
1794 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1796 // todo: add notification for when flag carrier needs help?
1801 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1803 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1805 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1806 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1809 if(frag_target.flagcarried)
1810 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1815 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1818 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1821 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1823 entity flag; // temporary entity for the search method
1825 if(self.flagcarried)
1826 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1828 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1830 if(flag.pass_sender == self) { flag.pass_sender = world; }
1831 if(flag.pass_target == self) { flag.pass_target = world; }
1832 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1838 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1840 if(self.flagcarried)
1841 if(!autocvar_g_ctf_portalteleport)
1842 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1847 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1849 if(MUTATOR_RETURNVALUE || gameover) { return false; }
1851 entity player = self;
1853 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1855 // pass the flag to a team mate
1856 if(autocvar_g_ctf_pass)
1858 entity head, closest_target = world;
1859 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
1861 while(head) // find the closest acceptable target to pass to
1863 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1864 if(head != player && SAME_TEAM(head, player))
1865 if(!head.speedrunning && !head.vehicle)
1867 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1868 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1869 vector passer_center = CENTER_OR_VIEWOFS(player);
1871 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1873 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1875 if(IS_BOT_CLIENT(head))
1877 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1878 ctf_Handle_Throw(head, player, DROP_PASS);
1882 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1883 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1885 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1888 else if(player.flagcarried)
1892 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1893 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1894 { closest_target = head; }
1896 else { closest_target = head; }
1903 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
1906 // throw the flag in front of you
1907 if(autocvar_g_ctf_throw && player.flagcarried)
1909 if(player.throw_count == -1)
1911 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1913 player.throw_prevtime = time;
1914 player.throw_count = 1;
1915 ctf_Handle_Throw(player, world, DROP_THROW);
1920 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1926 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1927 else { player.throw_count += 1; }
1928 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1930 player.throw_prevtime = time;
1931 ctf_Handle_Throw(player, world, DROP_THROW);
1940 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1942 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1944 self.wps_helpme_time = time;
1945 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1947 else // create a normal help me waypointsprite
1949 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');
1950 WaypointSprite_Ping(self.wps_helpme);
1956 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1958 if(vh_player.flagcarried)
1960 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1962 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1966 setattachment(vh_player.flagcarried, vh_vehicle, "");
1967 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1968 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1969 //vh_player.flagcarried.angles = '0 0 0';
1977 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1979 if(vh_player.flagcarried)
1981 setattachment(vh_player.flagcarried, vh_player, "");
1982 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1983 vh_player.flagcarried.scale = FLAG_SCALE;
1984 vh_player.flagcarried.angles = '0 0 0';
1991 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1993 if(self.flagcarried)
1995 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1996 ctf_RespawnFlag(self.flagcarried);
2003 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2005 entity flag; // temporary entity for the search method
2007 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2009 switch(flag.ctf_status)
2014 // lock the flag, game is over
2015 flag.movetype = MOVETYPE_NONE;
2016 flag.takedamage = DAMAGE_NO;
2017 flag.solid = SOLID_NOT;
2018 flag.nextthink = false; // stop thinking
2020 //dprint("stopping the ", flag.netname, " from moving.\n");
2028 // do nothing for these flags
2037 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2039 havocbot_ctf_reset_role(self);
2048 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2049 CTF Starting point for a player in team one (Red).
2050 Keys: "angle" viewing angle when spawning. */
2051 void spawnfunc_info_player_team1()
2053 if(g_assault) { remove(self); return; }
2055 self.team = NUM_TEAM_1; // red
2056 spawnfunc_info_player_deathmatch();
2060 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2061 CTF Starting point for a player in team two (Blue).
2062 Keys: "angle" viewing angle when spawning. */
2063 void spawnfunc_info_player_team2()
2065 if(g_assault) { remove(self); return; }
2067 self.team = NUM_TEAM_2; // blue
2068 spawnfunc_info_player_deathmatch();
2071 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2072 CTF Starting point for a player in team three (Yellow).
2073 Keys: "angle" viewing angle when spawning. */
2074 void spawnfunc_info_player_team3()
2076 if(g_assault) { remove(self); return; }
2078 self.team = NUM_TEAM_3; // yellow
2079 spawnfunc_info_player_deathmatch();
2083 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2084 CTF Starting point for a player in team four (Purple).
2085 Keys: "angle" viewing angle when spawning. */
2086 void spawnfunc_info_player_team4()
2088 if(g_assault) { remove(self); return; }
2090 self.team = NUM_TEAM_4; // purple
2091 spawnfunc_info_player_deathmatch();
2094 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2095 CTF flag for team one (Red).
2097 "angle" Angle the flag will point (minus 90 degrees)...
2098 "model" model to use, note this needs red and blue as skins 0 and 1...
2099 "noise" sound played when flag is picked up...
2100 "noise1" sound played when flag is returned by a teammate...
2101 "noise2" sound played when flag is captured...
2102 "noise3" sound played when flag is lost in the field and respawns itself...
2103 "noise4" sound played when flag is dropped by a player...
2104 "noise5" sound played when flag touches the ground... */
2105 void spawnfunc_item_flag_team1()
2107 if(!g_ctf) { remove(self); return; }
2109 ctf_FlagSetup(1, self); // 1 = red
2112 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2113 CTF flag for team two (Blue).
2115 "angle" Angle the flag will point (minus 90 degrees)...
2116 "model" model to use, note this needs red and blue as skins 0 and 1...
2117 "noise" sound played when flag is picked up...
2118 "noise1" sound played when flag is returned by a teammate...
2119 "noise2" sound played when flag is captured...
2120 "noise3" sound played when flag is lost in the field and respawns itself...
2121 "noise4" sound played when flag is dropped by a player...
2122 "noise5" sound played when flag touches the ground... */
2123 void spawnfunc_item_flag_team2()
2125 if(!g_ctf) { remove(self); return; }
2127 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2130 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2131 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2132 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.
2134 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2135 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2136 void spawnfunc_ctf_team()
2138 if(!g_ctf) { remove(self); return; }
2140 self.classname = "ctf_team";
2141 self.team = self.cnt + 1;
2144 // compatibility for quake maps
2145 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2146 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2147 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2148 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2149 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2150 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2158 void ctf_ScoreRules()
2160 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, true);
2161 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2162 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2163 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2164 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2165 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2166 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2167 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2168 ScoreRules_basics_end();
2171 // code from here on is just to support maps that don't have flag and team entities
2172 void ctf_SpawnTeam (string teamname, float teamcolor)
2177 self.classname = "ctf_team";
2178 self.netname = teamname;
2179 self.cnt = teamcolor;
2181 spawnfunc_ctf_team();
2186 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2188 // if no teams are found, spawn defaults
2189 if(find(world, classname, "ctf_team") == world)
2191 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2192 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2193 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2199 void ctf_Initialize()
2201 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2203 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2204 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2205 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2207 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2211 MUTATOR_DEFINITION(gamemode_ctf)
2213 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2214 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2215 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2216 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2217 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2218 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2219 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2220 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2221 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2222 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2223 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2224 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2225 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2226 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2230 if(time > 1) // game loads at time 1
2231 error("This is a game type and it cannot be added at runtime.");
2235 MUTATOR_ONROLLBACK_OR_REMOVE
2237 // we actually cannot roll back ctf_Initialize here
2238 // BUT: we don't need to! If this gets called, adding always
2244 print("This is a game type and it cannot be removed at runtime.");