1 #include "gamemode_ctf.qh"
7 #include "../vehicles/vehicle.qh"
10 #include "../../warpzonelib/common.qh"
11 #include "../../warpzonelib/mathlib.qh"
13 void ctf_FakeTimeLimit(entity e, float t)
16 WriteByte(MSG_ONE, 3); // svc_updatestat
17 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
19 WriteCoord(MSG_ONE, autocvar_timelimit);
21 WriteCoord(MSG_ONE, (t + 1) / 60);
24 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
26 if(autocvar_sv_eventlog)
27 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
30 void ctf_CaptureRecord(entity flag, entity player)
32 float cap_record = ctf_captimerecord;
33 float cap_time = (time - flag.ctf_pickuptime);
34 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
37 if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
38 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)); }
39 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)); }
41 // write that shit in the database
42 if((!ctf_captimerecord) || (cap_time < cap_record))
44 ctf_captimerecord = cap_time;
45 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
46 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
47 write_recordmarker(player, (time - cap_time), cap_time);
51 void ctf_FlagcarrierWaypoints(entity player)
53 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
54 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
55 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
56 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
59 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
61 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
62 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
63 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
64 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
67 if(current_height) // make sure we can actually do this arcing path
69 targpos = (to + ('0 0 1' * current_height));
70 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
71 if(trace_fraction < 1)
73 //print("normal arc line failed, trying to find new pos...");
74 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
75 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
76 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
77 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
78 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
81 else { targpos = to; }
83 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
85 vector desired_direction = normalize(targpos - from);
86 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
87 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
90 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
92 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
94 // directional tracing only
96 makevectors(passer_angle);
98 // find the closest point on the enemy to the center of the attack
99 float h; // hypotenuse, which is the distance between attacker to head
100 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
102 h = vlen(head_center - passer_center);
103 a = h * (normalize(head_center - passer_center) * v_forward);
105 vector nearest_on_line = (passer_center + a * v_forward);
106 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
108 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
109 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
111 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
116 else { return true; }
120 // =======================
121 // CaptureShield Functions
122 // =======================
124 bool ctf_CaptureShield_CheckStatus(entity p)
128 float players_worseeq, players_total;
130 if(ctf_captureshield_max_ratio <= 0)
133 s = PlayerScore_Add(p, SP_SCORE, 0);
134 if(s >= -ctf_captureshield_min_negscore)
137 players_total = players_worseeq = 0;
142 se = PlayerScore_Add(e, SP_SCORE, 0);
148 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
149 // use this rule here
151 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
157 void ctf_CaptureShield_Update(entity player, float wanted_status)
159 float updated_status = ctf_CaptureShield_CheckStatus(player);
160 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
162 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
163 player.ctf_captureshielded = updated_status;
167 float ctf_CaptureShield_Customize()
169 if(!other.ctf_captureshielded) { return false; }
170 if(SAME_TEAM(self, other)) { return false; }
175 void ctf_CaptureShield_Touch()
177 if(!other.ctf_captureshielded) { return; }
178 if(SAME_TEAM(self, other)) { return; }
180 vector mymid = (self.absmin + self.absmax) * 0.5;
181 vector othermid = (other.absmin + other.absmax) * 0.5;
183 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
184 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
187 void ctf_CaptureShield_Spawn(entity flag)
189 entity shield = spawn();
192 shield.team = self.team;
193 shield.touch = ctf_CaptureShield_Touch;
194 shield.customizeentityforclient = ctf_CaptureShield_Customize;
195 shield.classname = "ctf_captureshield";
196 shield.effects = EF_ADDITIVE;
197 shield.movetype = MOVETYPE_NOCLIP;
198 shield.solid = SOLID_TRIGGER;
199 shield.avelocity = '7 0 11';
202 setorigin(shield, self.origin);
203 setmodel(shield, "models/ctf/shield.md3");
204 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
208 // ====================
209 // Drop/Pass/Throw Code
210 // ====================
212 void ctf_Handle_Drop(entity flag, entity player, float droptype)
215 player = (player ? player : flag.pass_sender);
218 flag.movetype = MOVETYPE_TOSS;
219 flag.takedamage = DAMAGE_YES;
220 flag.angles = '0 0 0';
221 flag.health = flag.max_flag_health;
222 flag.ctf_droptime = time;
223 flag.ctf_dropper = player;
224 flag.ctf_status = FLAG_DROPPED;
226 // messages and sounds
227 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
228 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
229 ctf_EventLog("dropped", player.team, player);
232 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
233 PlayerScore_Add(player, SP_CTF_DROPS, 1);
236 if(autocvar_g_ctf_flag_dropped_waypoint)
237 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));
239 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
241 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
242 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
245 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
247 if(droptype == DROP_PASS)
249 flag.pass_distance = 0;
250 flag.pass_sender = world;
251 flag.pass_target = world;
255 void ctf_Handle_Retrieve(entity flag, entity player)
257 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
258 entity sender = flag.pass_sender;
260 // transfer flag to player
262 flag.owner.flagcarried = flag;
267 setattachment(flag, player.vehicle, "");
268 setorigin(flag, VEHICLE_FLAG_OFFSET);
269 flag.scale = VEHICLE_FLAG_SCALE;
273 setattachment(flag, player, "");
274 setorigin(flag, FLAG_CARRY_OFFSET);
276 flag.movetype = MOVETYPE_NONE;
277 flag.takedamage = DAMAGE_NO;
278 flag.solid = SOLID_NOT;
279 flag.angles = '0 0 0';
280 flag.ctf_status = FLAG_CARRY;
282 // messages and sounds
283 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
284 ctf_EventLog("receive", flag.team, player);
286 FOR_EACH_REALPLAYER(tmp_player)
288 if(tmp_player == sender)
289 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
290 else if(tmp_player == player)
291 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
292 else if(SAME_TEAM(tmp_player, sender))
293 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
296 // create new waypoint
297 ctf_FlagcarrierWaypoints(player);
299 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
300 player.throw_antispam = sender.throw_antispam;
302 flag.pass_distance = 0;
303 flag.pass_sender = world;
304 flag.pass_target = world;
307 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
309 entity flag = player.flagcarried;
310 vector targ_origin, flag_velocity;
312 if(!flag) { return; }
313 if((droptype == DROP_PASS) && !receiver) { return; }
315 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
318 setattachment(flag, world, "");
319 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
320 flag.owner.flagcarried = world;
322 flag.solid = SOLID_TRIGGER;
323 flag.ctf_dropper = player;
324 flag.ctf_droptime = time;
326 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
333 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
334 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
335 WarpZone_RefSys_Copy(flag, receiver);
336 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
337 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
339 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
340 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
343 flag.movetype = MOVETYPE_FLY;
344 flag.takedamage = DAMAGE_NO;
345 flag.pass_sender = player;
346 flag.pass_target = receiver;
347 flag.ctf_status = FLAG_PASSING;
350 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
351 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
352 ctf_EventLog("pass", flag.team, player);
358 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'));
360 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)));
361 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
362 ctf_Handle_Drop(flag, player, droptype);
368 flag.velocity = '0 0 0'; // do nothing
375 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);
376 ctf_Handle_Drop(flag, player, droptype);
381 // kill old waypointsprite
382 WaypointSprite_Ping(player.wps_flagcarrier);
383 WaypointSprite_Kill(player.wps_flagcarrier);
385 if(player.wps_enemyflagcarrier)
386 WaypointSprite_Kill(player.wps_enemyflagcarrier);
389 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
397 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
399 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
400 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
401 float old_time, new_time;
403 if (!player) { return; } // without someone to give the reward to, we can't possibly cap
405 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
407 // messages and sounds
408 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
409 ctf_CaptureRecord(enemy_flag, player);
410 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
414 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
415 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
420 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
421 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
423 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
424 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
425 if(!old_time || new_time < old_time)
426 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
429 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
430 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
433 if(capturetype == CAPTURE_NORMAL)
435 WaypointSprite_Kill(player.wps_flagcarrier);
436 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
438 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
439 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
443 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
444 ctf_RespawnFlag(enemy_flag);
447 void ctf_Handle_Return(entity flag, entity player)
449 // messages and sounds
450 if(player.flags & FL_MONSTER)
452 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
456 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
457 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
459 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
460 ctf_EventLog("return", flag.team, player);
463 if(IS_PLAYER(player))
465 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
466 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
468 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
471 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
475 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
476 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
477 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
481 ctf_RespawnFlag(flag);
484 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
487 float pickup_dropped_score; // used to calculate dropped pickup score
489 // attach the flag to the player
491 player.flagcarried = flag;
494 setattachment(flag, player.vehicle, "");
495 setorigin(flag, VEHICLE_FLAG_OFFSET);
496 flag.scale = VEHICLE_FLAG_SCALE;
500 setattachment(flag, player, "");
501 setorigin(flag, FLAG_CARRY_OFFSET);
505 flag.movetype = MOVETYPE_NONE;
506 flag.takedamage = DAMAGE_NO;
507 flag.solid = SOLID_NOT;
508 flag.angles = '0 0 0';
509 flag.ctf_status = FLAG_CARRY;
513 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
514 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
518 // messages and sounds
519 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
520 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
521 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
523 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
524 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
526 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
529 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
530 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
535 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
536 ctf_EventLog("steal", flag.team, player);
542 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);
543 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);
544 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
545 PlayerTeamScore_AddScore(player, pickup_dropped_score);
546 ctf_EventLog("pickup", flag.team, player);
554 if(pickuptype == PICKUP_BASE)
556 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
557 if((player.speedrunning) && (ctf_captimerecord))
558 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
562 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
565 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
566 ctf_FlagcarrierWaypoints(player);
567 WaypointSprite_Ping(player.wps_flagcarrier);
571 // ===================
572 // Main Flag Functions
573 // ===================
575 void ctf_CheckFlagReturn(entity flag, float returntype)
577 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
579 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
581 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
585 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
586 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
587 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
588 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
592 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
594 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
595 ctf_EventLog("returned", flag.team, world);
596 ctf_RespawnFlag(flag);
601 void ctf_CheckStalemate(void)
604 float stale_red_flags = 0, stale_blue_flags = 0;
607 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
609 // build list of stale flags
610 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
612 if(autocvar_g_ctf_stalemate)
613 if(tmp_entity.ctf_status != FLAG_BASE)
614 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
616 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
617 ctf_staleflaglist = tmp_entity;
619 switch(tmp_entity.team)
621 case NUM_TEAM_1: ++stale_red_flags; break;
622 case NUM_TEAM_2: ++stale_blue_flags; break;
627 if(stale_red_flags && stale_blue_flags)
628 ctf_stalemate = true;
629 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
630 { ctf_stalemate = false; wpforenemy_announced = false; }
631 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
632 { ctf_stalemate = false; wpforenemy_announced = false; }
634 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
637 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
639 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
640 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));
643 if (!wpforenemy_announced)
645 FOR_EACH_REALPLAYER(tmp_entity)
646 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
648 wpforenemy_announced = true;
653 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
655 if(ITEM_DAMAGE_NEEDKILL(deathtype))
657 // automatically kill the flag and return it
659 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
662 if(autocvar_g_ctf_flag_return_damage)
664 // reduce health and check if it should be returned
665 self.health = self.health - damage;
666 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
676 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
679 if(self == ctf_worldflaglist) // only for the first flag
680 FOR_EACH_CLIENT(tmp_entity)
681 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
684 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
685 dprint("wtf the flag got squashed?\n");
686 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
687 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
688 setsize(self, FLAG_MIN, FLAG_MAX); }
690 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
694 self.angles = '0 0 0';
702 switch(self.ctf_status)
706 if(autocvar_g_ctf_dropped_capture_radius)
708 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
709 if(tmp_entity.ctf_status == FLAG_DROPPED)
710 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
711 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
712 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
719 if(autocvar_g_ctf_flag_dropped_floatinwater)
721 vector midpoint = ((self.absmin + self.absmax) * 0.5);
722 if(pointcontents(midpoint) == CONTENT_WATER)
724 self.velocity = self.velocity * 0.5;
726 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
727 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
729 { self.movetype = MOVETYPE_FLY; }
731 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
733 if(autocvar_g_ctf_flag_return_dropped)
735 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
738 ctf_CheckFlagReturn(self, RETURN_DROPPED);
742 if(autocvar_g_ctf_flag_return_time)
744 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
745 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
753 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
756 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
760 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
764 if(autocvar_g_ctf_stalemate)
766 if(time >= wpforenemy_nextthink)
768 ctf_CheckStalemate();
769 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
777 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
778 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
779 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
781 if((self.pass_target == world)
782 || (self.pass_target.deadflag != DEAD_NO)
783 || (self.pass_target.flagcarried)
784 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
785 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
786 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
788 // give up, pass failed
789 ctf_Handle_Drop(self, world, DROP_PASS);
793 // still a viable target, go for it
794 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
799 default: // this should never happen
801 dprint("ctf_FlagThink(): Flag exists with no status?\n");
809 if(gameover) { return; }
810 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
812 entity toucher = other;
813 float is_not_monster = (!(toucher.flags & FL_MONSTER));
815 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
816 if(ITEM_TOUCH_NEEDKILL())
819 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
823 // special touch behaviors
824 if(toucher.frozen) { return; }
825 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
827 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
828 toucher = toucher.owner; // the player is actually the vehicle owner, not other
830 return; // do nothing
832 else if(toucher.flags & FL_MONSTER)
834 if(!autocvar_g_ctf_allow_monster_touch)
835 return; // do nothing
837 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
839 if(time > self.wait) // if we haven't in a while, play a sound/effect
841 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
842 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
843 self.wait = time + FLAG_TOUCHRATE;
847 else if(toucher.deadflag != DEAD_NO) { return; }
849 switch(self.ctf_status)
853 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
854 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
855 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
856 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
862 if(SAME_TEAM(toucher, self))
863 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
864 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
865 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
871 dprint("Someone touched a flag even though it was being carried?\n");
877 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
879 if(DIFF_TEAM(toucher, self.pass_sender))
880 ctf_Handle_Return(self, toucher);
882 ctf_Handle_Retrieve(self, toucher);
890 void ctf_RespawnFlag(entity flag)
892 // check for flag respawn being called twice in a row
893 if(flag.last_respawn > time - 0.5)
894 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
896 flag.last_respawn = time;
898 // reset the player (if there is one)
899 if((flag.owner) && (flag.owner.flagcarried == flag))
901 if(flag.owner.wps_enemyflagcarrier)
902 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
904 WaypointSprite_Kill(flag.wps_flagcarrier);
906 flag.owner.flagcarried = world;
908 if(flag.speedrunning)
909 ctf_FakeTimeLimit(flag.owner, -1);
912 if((flag.owner) && (flag.owner.vehicle))
913 flag.scale = FLAG_SCALE;
915 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
916 { WaypointSprite_Kill(flag.wps_flagdropped); }
919 setattachment(flag, world, "");
920 setorigin(flag, flag.ctf_spawnorigin);
922 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
923 flag.takedamage = DAMAGE_NO;
924 flag.health = flag.max_flag_health;
925 flag.solid = SOLID_TRIGGER;
926 flag.velocity = '0 0 0';
927 flag.angles = flag.mangle;
928 flag.flags = FL_ITEM | FL_NOTARGET;
930 flag.ctf_status = FLAG_BASE;
932 flag.pass_distance = 0;
933 flag.pass_sender = world;
934 flag.pass_target = world;
935 flag.ctf_dropper = world;
936 flag.ctf_pickuptime = 0;
937 flag.ctf_droptime = 0;
939 ctf_CheckStalemate();
945 if(IS_PLAYER(self.owner))
946 ctf_Handle_Throw(self.owner, world, DROP_RESET);
948 ctf_RespawnFlag(self);
951 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
954 waypoint_spawnforitem_force(self, self.origin);
955 self.nearestwaypointtimeout = 0; // activate waypointing again
956 self.bot_basewaypoint = self.nearestwaypoint;
959 // move_origin isnt accessible just yet
960 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
961 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
963 // captureshield setup
964 ctf_CaptureShield_Spawn(self);
967 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
970 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.
971 self = flag; // for later usage with droptofloor()
974 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
975 ctf_worldflaglist = flag;
977 setattachment(flag, world, "");
979 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
980 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
981 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
982 flag.classname = "item_flag_team";
983 flag.target = "###item###"; // wut?
984 flag.flags = FL_ITEM | FL_NOTARGET;
985 flag.solid = SOLID_TRIGGER;
986 flag.takedamage = DAMAGE_NO;
987 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
988 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
989 flag.health = flag.max_flag_health;
990 flag.event_damage = ctf_FlagDamage;
991 flag.pushable = true;
992 flag.teleportable = TELEPORT_NORMAL;
993 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
994 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
995 flag.velocity = '0 0 0';
996 flag.mangle = flag.angles;
997 flag.reset = ctf_Reset;
998 flag.touch = ctf_FlagTouch;
999 flag.think = ctf_FlagThink;
1000 flag.nextthink = time + FLAG_THINKRATE;
1001 flag.ctf_status = FLAG_BASE;
1004 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
1005 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1006 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
1007 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
1008 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
1009 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
1012 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
1013 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
1014 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
1015 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.
1016 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1017 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1018 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1021 precache_sound(flag.snd_flag_taken);
1022 precache_sound(flag.snd_flag_returned);
1023 precache_sound(flag.snd_flag_capture);
1024 precache_sound(flag.snd_flag_respawn);
1025 precache_sound(flag.snd_flag_dropped);
1026 precache_sound(flag.snd_flag_touch);
1027 precache_sound(flag.snd_flag_pass);
1028 precache_model(flag.model);
1029 precache_model("models/ctf/shield.md3");
1030 precache_model("models/ctf/shockwavetransring.md3");
1033 setmodel(flag, flag.model); // precision set below
1034 setsize(flag, FLAG_MIN, FLAG_MAX);
1035 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1037 if(autocvar_g_ctf_flag_glowtrails)
1039 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1040 flag.glow_size = 25;
1041 flag.glow_trail = 1;
1044 flag.effects |= EF_LOWPRECISION;
1045 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1046 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1049 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1051 flag.dropped_origin = flag.origin;
1052 flag.noalign = true;
1053 flag.movetype = MOVETYPE_NONE;
1055 else // drop to floor, automatically find a platform and set that as spawn origin
1057 flag.noalign = false;
1060 flag.movetype = MOVETYPE_TOSS;
1063 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1071 // NOTE: LEGACY CODE, needs to be re-written!
1073 void havocbot_calculate_middlepoint()
1077 vector fo = '0 0 0';
1080 f = ctf_worldflaglist;
1085 f = f.ctf_worldflagnext;
1089 havocbot_ctf_middlepoint = s * (1.0 / n);
1090 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1094 entity havocbot_ctf_find_flag(entity bot)
1097 f = ctf_worldflaglist;
1100 if (bot.team == f.team)
1102 f = f.ctf_worldflagnext;
1107 entity havocbot_ctf_find_enemy_flag(entity bot)
1110 f = ctf_worldflaglist;
1113 if (bot.team != f.team)
1115 f = f.ctf_worldflagnext;
1120 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1128 FOR_EACH_PLAYER(head)
1130 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1133 if(vlen(head.origin - org) < tc_radius)
1140 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1143 head = ctf_worldflaglist;
1146 if (self.team == head.team)
1148 head = head.ctf_worldflagnext;
1151 navigation_routerating(head, ratingscale, 10000);
1154 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1157 head = ctf_worldflaglist;
1160 if (self.team == head.team)
1162 head = head.ctf_worldflagnext;
1167 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1170 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1173 head = ctf_worldflaglist;
1176 if (self.team != head.team)
1178 head = head.ctf_worldflagnext;
1181 navigation_routerating(head, ratingscale, 10000);
1184 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1186 if (!bot_waypoints_for_items)
1188 havocbot_goalrating_ctf_enemyflag(ratingscale);
1194 head = havocbot_ctf_find_enemy_flag(self);
1199 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1202 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1206 mf = havocbot_ctf_find_flag(self);
1208 if(mf.ctf_status == FLAG_BASE)
1212 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1215 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1218 head = ctf_worldflaglist;
1221 // flag is out in the field
1222 if(head.ctf_status != FLAG_BASE)
1223 if(head.tag_entity==world) // dropped
1227 if(vlen(org-head.origin)<df_radius)
1228 navigation_routerating(head, ratingscale, 10000);
1231 navigation_routerating(head, ratingscale, 10000);
1234 head = head.ctf_worldflagnext;
1238 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1242 head = findchainfloat(bot_pickup, true);
1245 // gather health and armor only
1247 if (head.health || head.armorvalue)
1248 if (vlen(head.origin - org) < sradius)
1250 // get the value of the item
1251 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1253 navigation_routerating(head, t * ratingscale, 500);
1259 void havocbot_ctf_reset_role(entity bot)
1261 float cdefense, cmiddle, coffense;
1262 entity mf, ef, head;
1265 if(bot.deadflag != DEAD_NO)
1268 if(vlen(havocbot_ctf_middlepoint)==0)
1269 havocbot_calculate_middlepoint();
1272 if (bot.flagcarried)
1274 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1278 mf = havocbot_ctf_find_flag(bot);
1279 ef = havocbot_ctf_find_enemy_flag(bot);
1281 // Retrieve stolen flag
1282 if(mf.ctf_status!=FLAG_BASE)
1284 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1288 // If enemy flag is taken go to the middle to intercept pursuers
1289 if(ef.ctf_status!=FLAG_BASE)
1291 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1295 // if there is only me on the team switch to offense
1297 FOR_EACH_PLAYER(head)
1298 if(SAME_TEAM(head, bot))
1303 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1307 // Evaluate best position to take
1308 // Count mates on middle position
1309 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1311 // Count mates on defense position
1312 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1314 // Count mates on offense position
1315 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1317 if(cdefense<=coffense)
1318 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1319 else if(coffense<=cmiddle)
1320 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1322 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1325 void havocbot_role_ctf_carrier()
1327 if(self.deadflag != DEAD_NO)
1329 havocbot_ctf_reset_role(self);
1333 if (self.flagcarried == world)
1335 havocbot_ctf_reset_role(self);
1339 if (self.bot_strategytime < time)
1341 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1343 navigation_goalrating_start();
1344 havocbot_goalrating_ctf_ourbase(50000);
1347 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1349 navigation_goalrating_end();
1351 if (self.navigation_hasgoals)
1352 self.havocbot_cantfindflag = time + 10;
1353 else if (time > self.havocbot_cantfindflag)
1355 // Can't navigate to my own base, suicide!
1356 // TODO: drop it and wander around
1357 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1363 void havocbot_role_ctf_escort()
1367 if(self.deadflag != DEAD_NO)
1369 havocbot_ctf_reset_role(self);
1373 if (self.flagcarried)
1375 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1379 // If enemy flag is back on the base switch to previous role
1380 ef = havocbot_ctf_find_enemy_flag(self);
1381 if(ef.ctf_status==FLAG_BASE)
1383 self.havocbot_role = self.havocbot_previous_role;
1384 self.havocbot_role_timeout = 0;
1388 // If the flag carrier reached the base switch to defense
1389 mf = havocbot_ctf_find_flag(self);
1390 if(mf.ctf_status!=FLAG_BASE)
1391 if(vlen(ef.origin - mf.dropped_origin) < 300)
1393 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1397 // Set the role timeout if necessary
1398 if (!self.havocbot_role_timeout)
1400 self.havocbot_role_timeout = time + random() * 30 + 60;
1403 // If nothing happened just switch to previous role
1404 if (time > self.havocbot_role_timeout)
1406 self.havocbot_role = self.havocbot_previous_role;
1407 self.havocbot_role_timeout = 0;
1411 // Chase the flag carrier
1412 if (self.bot_strategytime < time)
1414 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1415 navigation_goalrating_start();
1416 havocbot_goalrating_ctf_enemyflag(30000);
1417 havocbot_goalrating_ctf_ourstolenflag(40000);
1418 havocbot_goalrating_items(10000, self.origin, 10000);
1419 navigation_goalrating_end();
1423 void havocbot_role_ctf_offense()
1428 if(self.deadflag != DEAD_NO)
1430 havocbot_ctf_reset_role(self);
1434 if (self.flagcarried)
1436 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1441 mf = havocbot_ctf_find_flag(self);
1442 ef = havocbot_ctf_find_enemy_flag(self);
1445 if(mf.ctf_status!=FLAG_BASE)
1448 pos = mf.tag_entity.origin;
1452 // Try to get it if closer than the enemy base
1453 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1455 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1460 // Escort flag carrier
1461 if(ef.ctf_status!=FLAG_BASE)
1464 pos = ef.tag_entity.origin;
1468 if(vlen(pos-mf.dropped_origin)>700)
1470 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1475 // About to fail, switch to middlefield
1478 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1482 // Set the role timeout if necessary
1483 if (!self.havocbot_role_timeout)
1484 self.havocbot_role_timeout = time + 120;
1486 if (time > self.havocbot_role_timeout)
1488 havocbot_ctf_reset_role(self);
1492 if (self.bot_strategytime < time)
1494 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1495 navigation_goalrating_start();
1496 havocbot_goalrating_ctf_ourstolenflag(50000);
1497 havocbot_goalrating_ctf_enemybase(20000);
1498 havocbot_goalrating_items(5000, self.origin, 1000);
1499 havocbot_goalrating_items(1000, self.origin, 10000);
1500 navigation_goalrating_end();
1504 // Retriever (temporary role):
1505 void havocbot_role_ctf_retriever()
1509 if(self.deadflag != DEAD_NO)
1511 havocbot_ctf_reset_role(self);
1515 if (self.flagcarried)
1517 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1521 // If flag is back on the base switch to previous role
1522 mf = havocbot_ctf_find_flag(self);
1523 if(mf.ctf_status==FLAG_BASE)
1525 havocbot_ctf_reset_role(self);
1529 if (!self.havocbot_role_timeout)
1530 self.havocbot_role_timeout = time + 20;
1532 if (time > self.havocbot_role_timeout)
1534 havocbot_ctf_reset_role(self);
1538 if (self.bot_strategytime < time)
1543 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1544 navigation_goalrating_start();
1545 havocbot_goalrating_ctf_ourstolenflag(50000);
1546 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1547 havocbot_goalrating_ctf_enemybase(30000);
1548 havocbot_goalrating_items(500, self.origin, rt_radius);
1549 navigation_goalrating_end();
1553 void havocbot_role_ctf_middle()
1557 if(self.deadflag != DEAD_NO)
1559 havocbot_ctf_reset_role(self);
1563 if (self.flagcarried)
1565 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1569 mf = havocbot_ctf_find_flag(self);
1570 if(mf.ctf_status!=FLAG_BASE)
1572 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1576 if (!self.havocbot_role_timeout)
1577 self.havocbot_role_timeout = time + 10;
1579 if (time > self.havocbot_role_timeout)
1581 havocbot_ctf_reset_role(self);
1585 if (self.bot_strategytime < time)
1589 org = havocbot_ctf_middlepoint;
1590 org.z = self.origin.z;
1592 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1593 navigation_goalrating_start();
1594 havocbot_goalrating_ctf_ourstolenflag(50000);
1595 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1596 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1597 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1598 havocbot_goalrating_items(2500, self.origin, 10000);
1599 havocbot_goalrating_ctf_enemybase(2500);
1600 navigation_goalrating_end();
1604 void havocbot_role_ctf_defense()
1608 if(self.deadflag != DEAD_NO)
1610 havocbot_ctf_reset_role(self);
1614 if (self.flagcarried)
1616 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1620 // If own flag was captured
1621 mf = havocbot_ctf_find_flag(self);
1622 if(mf.ctf_status!=FLAG_BASE)
1624 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1628 if (!self.havocbot_role_timeout)
1629 self.havocbot_role_timeout = time + 30;
1631 if (time > self.havocbot_role_timeout)
1633 havocbot_ctf_reset_role(self);
1636 if (self.bot_strategytime < time)
1641 org = mf.dropped_origin;
1642 mp_radius = havocbot_ctf_middlepoint_radius;
1644 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1645 navigation_goalrating_start();
1647 // if enemies are closer to our base, go there
1648 entity head, closestplayer = world;
1649 float distance, bestdistance = 10000;
1650 FOR_EACH_PLAYER(head)
1652 if(head.deadflag!=DEAD_NO)
1655 distance = vlen(org - head.origin);
1656 if(distance<bestdistance)
1658 closestplayer = head;
1659 bestdistance = distance;
1664 if(DIFF_TEAM(closestplayer, self))
1665 if(vlen(org - self.origin)>1000)
1666 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1667 havocbot_goalrating_ctf_ourbase(30000);
1669 havocbot_goalrating_ctf_ourstolenflag(20000);
1670 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1671 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1672 havocbot_goalrating_items(10000, org, mp_radius);
1673 havocbot_goalrating_items(5000, self.origin, 10000);
1674 navigation_goalrating_end();
1678 void havocbot_role_ctf_setrole(entity bot, float role)
1680 dprint(strcat(bot.netname," switched to "));
1683 case HAVOCBOT_CTF_ROLE_CARRIER:
1685 bot.havocbot_role = havocbot_role_ctf_carrier;
1686 bot.havocbot_role_timeout = 0;
1687 bot.havocbot_cantfindflag = time + 10;
1688 bot.bot_strategytime = 0;
1690 case HAVOCBOT_CTF_ROLE_DEFENSE:
1692 bot.havocbot_role = havocbot_role_ctf_defense;
1693 bot.havocbot_role_timeout = 0;
1695 case HAVOCBOT_CTF_ROLE_MIDDLE:
1697 bot.havocbot_role = havocbot_role_ctf_middle;
1698 bot.havocbot_role_timeout = 0;
1700 case HAVOCBOT_CTF_ROLE_OFFENSE:
1702 bot.havocbot_role = havocbot_role_ctf_offense;
1703 bot.havocbot_role_timeout = 0;
1705 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1706 dprint("retriever");
1707 bot.havocbot_previous_role = bot.havocbot_role;
1708 bot.havocbot_role = havocbot_role_ctf_retriever;
1709 bot.havocbot_role_timeout = time + 10;
1710 bot.bot_strategytime = 0;
1712 case HAVOCBOT_CTF_ROLE_ESCORT:
1714 bot.havocbot_previous_role = bot.havocbot_role;
1715 bot.havocbot_role = havocbot_role_ctf_escort;
1716 bot.havocbot_role_timeout = time + 30;
1717 bot.bot_strategytime = 0;
1728 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1732 // initially clear items so they can be set as necessary later.
1733 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1734 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1736 // scan through all the flags and notify the client about them
1737 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1739 switch(flag.ctf_status)
1744 if((flag.owner == self) || (flag.pass_sender == self))
1745 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1747 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1752 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1758 // item for stopping players from capturing the flag too often
1759 if(self.ctf_captureshielded)
1760 self.items |= IT_CTF_SHIELDED;
1762 // update the health of the flag carrier waypointsprite
1763 if(self.wps_flagcarrier)
1764 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1769 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1771 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1773 if(frag_target == frag_attacker) // damage done to yourself
1775 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1776 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1778 else // damage done to everyone else
1780 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1781 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1784 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1786 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)))
1787 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1789 frag_target.wps_helpme_time = time;
1790 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1792 // todo: add notification for when flag carrier needs help?
1797 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1799 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1801 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1802 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1805 if(frag_target.flagcarried)
1806 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1811 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1814 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1817 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1819 entity flag; // temporary entity for the search method
1821 if(self.flagcarried)
1822 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1824 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1826 if(flag.pass_sender == self) { flag.pass_sender = world; }
1827 if(flag.pass_target == self) { flag.pass_target = world; }
1828 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1834 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1836 if(self.flagcarried)
1837 if(!autocvar_g_ctf_portalteleport)
1838 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1843 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1845 if(MUTATOR_RETURNVALUE || gameover) { return false; }
1847 entity player = self;
1849 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1851 // pass the flag to a team mate
1852 if(autocvar_g_ctf_pass)
1854 entity head, closest_target = world;
1855 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
1857 while(head) // find the closest acceptable target to pass to
1859 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1860 if(head != player && SAME_TEAM(head, player))
1861 if(!head.speedrunning && !head.vehicle)
1863 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1864 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1865 vector passer_center = CENTER_OR_VIEWOFS(player);
1867 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1869 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1871 if(IS_BOT_CLIENT(head))
1873 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1874 ctf_Handle_Throw(head, player, DROP_PASS);
1878 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1879 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1881 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1884 else if(player.flagcarried)
1888 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1889 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1890 { closest_target = head; }
1892 else { closest_target = head; }
1899 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
1902 // throw the flag in front of you
1903 if(autocvar_g_ctf_throw && player.flagcarried)
1905 if(player.throw_count == -1)
1907 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1909 player.throw_prevtime = time;
1910 player.throw_count = 1;
1911 ctf_Handle_Throw(player, world, DROP_THROW);
1916 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1922 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1923 else { player.throw_count += 1; }
1924 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1926 player.throw_prevtime = time;
1927 ctf_Handle_Throw(player, world, DROP_THROW);
1936 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1938 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1940 self.wps_helpme_time = time;
1941 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1943 else // create a normal help me waypointsprite
1945 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');
1946 WaypointSprite_Ping(self.wps_helpme);
1952 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1954 if(vh_player.flagcarried)
1956 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1958 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1962 setattachment(vh_player.flagcarried, vh_vehicle, "");
1963 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1964 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1965 //vh_player.flagcarried.angles = '0 0 0';
1973 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1975 if(vh_player.flagcarried)
1977 setattachment(vh_player.flagcarried, vh_player, "");
1978 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1979 vh_player.flagcarried.scale = FLAG_SCALE;
1980 vh_player.flagcarried.angles = '0 0 0';
1987 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1989 if(self.flagcarried)
1991 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1992 ctf_RespawnFlag(self.flagcarried);
1999 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2001 entity flag; // temporary entity for the search method
2003 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2005 switch(flag.ctf_status)
2010 // lock the flag, game is over
2011 flag.movetype = MOVETYPE_NONE;
2012 flag.takedamage = DAMAGE_NO;
2013 flag.solid = SOLID_NOT;
2014 flag.nextthink = false; // stop thinking
2016 //dprint("stopping the ", flag.netname, " from moving.\n");
2024 // do nothing for these flags
2033 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2035 havocbot_ctf_reset_role(self);
2044 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2045 CTF Starting point for a player in team one (Red).
2046 Keys: "angle" viewing angle when spawning. */
2047 void spawnfunc_info_player_team1()
2049 if(g_assault) { remove(self); return; }
2051 self.team = NUM_TEAM_1; // red
2052 spawnfunc_info_player_deathmatch();
2056 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2057 CTF Starting point for a player in team two (Blue).
2058 Keys: "angle" viewing angle when spawning. */
2059 void spawnfunc_info_player_team2()
2061 if(g_assault) { remove(self); return; }
2063 self.team = NUM_TEAM_2; // blue
2064 spawnfunc_info_player_deathmatch();
2067 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2068 CTF Starting point for a player in team three (Yellow).
2069 Keys: "angle" viewing angle when spawning. */
2070 void spawnfunc_info_player_team3()
2072 if(g_assault) { remove(self); return; }
2074 self.team = NUM_TEAM_3; // yellow
2075 spawnfunc_info_player_deathmatch();
2079 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2080 CTF Starting point for a player in team four (Purple).
2081 Keys: "angle" viewing angle when spawning. */
2082 void spawnfunc_info_player_team4()
2084 if(g_assault) { remove(self); return; }
2086 self.team = NUM_TEAM_4; // purple
2087 spawnfunc_info_player_deathmatch();
2090 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2091 CTF flag for team one (Red).
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_team1()
2103 if(!g_ctf) { remove(self); return; }
2105 ctf_FlagSetup(1, self); // 1 = red
2108 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2109 CTF flag for team two (Blue).
2111 "angle" Angle the flag will point (minus 90 degrees)...
2112 "model" model to use, note this needs red and blue as skins 0 and 1...
2113 "noise" sound played when flag is picked up...
2114 "noise1" sound played when flag is returned by a teammate...
2115 "noise2" sound played when flag is captured...
2116 "noise3" sound played when flag is lost in the field and respawns itself...
2117 "noise4" sound played when flag is dropped by a player...
2118 "noise5" sound played when flag touches the ground... */
2119 void spawnfunc_item_flag_team2()
2121 if(!g_ctf) { remove(self); return; }
2123 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2126 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2127 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2128 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.
2130 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2131 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2132 void spawnfunc_ctf_team()
2134 if(!g_ctf) { remove(self); return; }
2136 self.classname = "ctf_team";
2137 self.team = self.cnt + 1;
2140 // compatibility for quake maps
2141 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2142 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2143 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2144 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2145 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2146 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2154 void ctf_ScoreRules()
2156 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, true);
2157 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2158 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2159 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2160 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2161 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2162 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2163 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2164 ScoreRules_basics_end();
2167 // code from here on is just to support maps that don't have flag and team entities
2168 void ctf_SpawnTeam (string teamname, float teamcolor)
2173 self.classname = "ctf_team";
2174 self.netname = teamname;
2175 self.cnt = teamcolor;
2177 spawnfunc_ctf_team();
2182 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2184 // if no teams are found, spawn defaults
2185 if(find(world, classname, "ctf_team") == world)
2187 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2188 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2189 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2195 void ctf_Initialize()
2197 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2199 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2200 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2201 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2203 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2207 MUTATOR_DEFINITION(gamemode_ctf)
2209 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2210 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2211 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2212 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2213 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2214 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2215 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2216 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2217 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2218 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2219 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2220 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2221 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2222 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2226 if(time > 1) // game loads at time 1
2227 error("This is a game type and it cannot be added at runtime.");
2231 MUTATOR_ONROLLBACK_OR_REMOVE
2233 // we actually cannot roll back ctf_Initialize here
2234 // BUT: we don't need to! If this gets called, adding always
2240 print("This is a game type and it cannot be removed at runtime.");