1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 void ctf_CaptureRecord(entity flag, entity player)
25 float cap_record = ctf_captimerecord;
26 float cap_time = (time - flag.ctf_pickuptime);
27 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
30 if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
31 else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
32 else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
34 // write that shit in the database
35 if((!ctf_captimerecord) || (cap_time < cap_record))
37 ctf_captimerecord = cap_time;
38 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
39 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
40 write_recordmarker(player, (time - cap_time), cap_time);
44 void ctf_FlagcarrierWaypoints(entity player)
46 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
47 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
48 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
49 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
52 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
54 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
55 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
56 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
57 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
60 if(current_height) // make sure we can actually do this arcing path
62 targpos = (to + ('0 0 1' * current_height));
63 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
64 if(trace_fraction < 1)
66 //print("normal arc line failed, trying to find new pos...");
67 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
68 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
69 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
70 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
71 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
74 else { targpos = to; }
76 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
78 vector desired_direction = normalize(targpos - from);
79 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
80 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
83 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
85 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
87 // directional tracing only
89 makevectors(passer_angle);
91 // find the closest point on the enemy to the center of the attack
92 float ang; // angle between shotdir and h
93 float h; // hypotenuse, which is the distance between attacker to head
94 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
96 h = vlen(head_center - passer_center);
97 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
100 vector nearest_on_line = (passer_center + a * v_forward);
101 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
103 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
104 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
106 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
111 else { return TRUE; }
114 float ctf_Stalemate_customizeentityforclient()
116 // make spectators see what the player would see
118 e = WaypointSprite_getviewentity(other);
119 wp_owner = self.owner;
122 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner))
125 if(SAME_TEAM(wp_owner, e))
135 // =======================
136 // CaptureShield Functions
137 // =======================
139 float ctf_CaptureShield_CheckStatus(entity p)
141 float s, s2, s3, s4, se, se2, se3, se4, sr, ser;
143 float players_worseeq, players_total;
145 if(ctf_captureshield_max_ratio <= 0)
148 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
149 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
150 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
151 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
153 sr = ((s - s2) + (s3 + s4));
155 if(sr >= -ctf_captureshield_min_negscore)
158 players_total = players_worseeq = 0;
163 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
164 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
165 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
166 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
168 ser = ((se - se2) + (se3 + se4));
175 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
176 // use this rule here
178 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
184 void ctf_CaptureShield_Update(entity player, float wanted_status)
186 float updated_status = ctf_CaptureShield_CheckStatus(player);
187 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
189 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
190 player.ctf_captureshielded = updated_status;
194 float ctf_CaptureShield_Customize()
196 if(!other.ctf_captureshielded) { return FALSE; }
197 if(CTF_SAMETEAM(self, other)) { return FALSE; }
202 void ctf_CaptureShield_Touch()
204 if(!other.ctf_captureshielded) { return; }
205 if(CTF_SAMETEAM(self, other)) { return; }
207 vector mymid = (self.absmin + self.absmax) * 0.5;
208 vector othermid = (other.absmin + other.absmax) * 0.5;
210 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
211 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
214 void ctf_CaptureShield_Spawn(entity flag)
216 entity shield = spawn();
219 shield.team = self.team;
220 shield.touch = ctf_CaptureShield_Touch;
221 shield.customizeentityforclient = ctf_CaptureShield_Customize;
222 shield.classname = "ctf_captureshield";
223 shield.effects = EF_ADDITIVE;
224 shield.movetype = MOVETYPE_NOCLIP;
225 shield.solid = SOLID_TRIGGER;
226 shield.avelocity = '7 0 11';
229 setorigin(shield, self.origin);
230 setmodel(shield, "models/ctf/shield.md3");
231 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
235 // ====================
236 // Drop/Pass/Throw Code
237 // ====================
239 void ctf_Handle_Drop(entity flag, entity player, float droptype)
242 player = (player ? player : flag.pass_sender);
245 flag.movetype = MOVETYPE_TOSS;
246 flag.takedamage = DAMAGE_YES;
247 flag.angles = '0 0 0';
248 flag.health = flag.max_flag_health;
249 flag.ctf_droptime = time;
250 flag.ctf_dropper = player;
251 flag.ctf_status = FLAG_DROPPED;
253 // messages and sounds
254 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_LOST_), player.netname);
255 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
256 ctf_EventLog("dropped", player.team, player);
259 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
260 PlayerScore_Add(player, SP_CTF_DROPS, 1);
263 if(autocvar_g_ctf_flag_dropped_waypoint)
264 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));
266 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
268 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
269 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
272 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
274 if(droptype == DROP_PASS)
276 flag.pass_distance = 0;
277 flag.pass_sender = world;
278 flag.pass_target = world;
282 void ctf_Handle_Retrieve(entity flag, entity player)
284 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
285 entity sender = flag.pass_sender;
287 // transfer flag to player
289 flag.owner.flagcarried = flag;
292 setattachment(flag, player, "");
293 setorigin(flag, FLAG_CARRY_OFFSET);
294 flag.movetype = MOVETYPE_NONE;
295 flag.takedamage = DAMAGE_NO;
296 flag.solid = SOLID_NOT;
297 flag.angles = '0 0 0';
298 flag.ctf_status = FLAG_CARRY;
300 // messages and sounds
301 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
302 ctf_EventLog("receive", flag.team, player);
304 FOR_EACH_REALPLAYER(tmp_player)
306 if(tmp_player == sender)
307 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_), player.netname);
308 else if(tmp_player == player)
309 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
310 else if(SAME_TEAM(tmp_player, sender))
311 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
314 // create new waypoint
315 ctf_FlagcarrierWaypoints(player);
317 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
318 player.throw_antispam = sender.throw_antispam;
320 flag.pass_distance = 0;
321 flag.pass_sender = world;
322 flag.pass_target = world;
325 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
327 entity flag = player.flagcarried;
328 vector targ_origin, flag_velocity;
330 if(!flag) { return; }
331 if((droptype == DROP_PASS) && !receiver) { return; }
333 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
336 setattachment(flag, world, "");
337 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
338 flag.owner.flagcarried = world;
340 flag.solid = SOLID_TRIGGER;
341 flag.ctf_dropper = player;
342 flag.ctf_droptime = time;
344 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
351 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
352 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
353 WarpZone_RefSys_Copy(flag, receiver);
354 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
355 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
357 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
358 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
361 flag.movetype = MOVETYPE_FLY;
362 flag.takedamage = DAMAGE_NO;
363 flag.pass_sender = player;
364 flag.pass_target = receiver;
365 flag.ctf_status = FLAG_PASSING;
368 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
369 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
370 ctf_EventLog("pass", flag.team, player);
376 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'));
378 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)));
379 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
380 ctf_Handle_Drop(flag, player, droptype);
386 flag.velocity = '0 0 0'; // do nothing
393 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);
394 ctf_Handle_Drop(flag, player, droptype);
399 // kill old waypointsprite
400 WaypointSprite_Ping(player.wps_flagcarrier);
401 WaypointSprite_Kill(player.wps_flagcarrier);
403 if(player.wps_enemyflagcarrier)
404 WaypointSprite_Kill(player.wps_enemyflagcarrier);
407 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
415 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
417 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
418 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
419 float old_time, new_time;
421 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
422 if(CTF_DIFFTEAM(player, flag)) { return; }
424 // messages and sounds
425 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_));
426 ctf_CaptureRecord(enemy_flag, player);
427 sound(player, CH_TRIGGER, ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture), VOL_BASE, ATTEN_NONE);
431 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
432 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
437 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
438 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
440 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
441 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
442 if(!old_time || new_time < old_time)
443 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
446 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
447 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
450 if(capturetype == CAPTURE_NORMAL)
452 WaypointSprite_Kill(player.wps_flagcarrier);
453 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
455 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
456 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
460 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
461 ctf_RespawnFlag(enemy_flag);
464 void ctf_Handle_Return(entity flag, entity player)
466 // messages and sounds
467 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
468 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
469 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
470 ctf_EventLog("return", flag.team, player);
473 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
474 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
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 if(player.flagcarried == flag)
487 WaypointSprite_Kill(player.wps_flagcarrier);
490 ctf_RespawnFlag(flag);
493 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
496 float pickup_dropped_score; // used to calculate dropped pickup score
497 entity tmp_entity; // temporary entity
499 // attach the flag to the player
501 player.flagcarried = flag;
502 setattachment(flag, player, "");
503 setorigin(flag, FLAG_CARRY_OFFSET);
506 flag.movetype = MOVETYPE_NONE;
507 flag.takedamage = DAMAGE_NO;
508 flag.solid = SOLID_NOT;
509 flag.angles = '0 0 0';
510 flag.ctf_status = FLAG_CARRY;
514 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
515 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
519 // messages and sounds
520 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_), player.netname);
521 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
522 if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
523 else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); }
525 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
527 FOR_EACH_PLAYER(tmp_entity)
528 if(tmp_entity != player)
529 if(CTF_SAMETEAM(flag, tmp_entity))
530 if(SAME_TEAM(player, tmp_entity))
531 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
533 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
535 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
538 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
543 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
544 ctf_EventLog("steal", flag.team, player);
550 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);
551 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);
552 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
553 PlayerTeamScore_AddScore(player, pickup_dropped_score);
554 ctf_EventLog("pickup", flag.team, player);
562 if(pickuptype == PICKUP_BASE)
564 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
565 if((player.speedrunning) && (ctf_captimerecord))
566 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
570 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
573 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
574 ctf_FlagcarrierWaypoints(player);
575 WaypointSprite_Ping(player.wps_flagcarrier);
579 // ===================
580 // Main Flag Functions
581 // ===================
583 void ctf_CheckFlagReturn(entity flag, float returntype)
585 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
587 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
589 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
593 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
594 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
595 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
596 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
600 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
602 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
603 ctf_EventLog("returned", flag.team, world);
604 ctf_RespawnFlag(flag);
609 void ctf_CheckStalemate(void)
612 float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0;
615 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
617 // build list of stale flags
618 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
620 if(autocvar_g_ctf_stalemate)
621 if(tmp_entity.ctf_status != FLAG_BASE)
622 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
624 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
625 ctf_staleflaglist = tmp_entity;
627 switch(tmp_entity.team)
629 case NUM_TEAM_1: ++stale_red_flags; break;
630 case NUM_TEAM_2: ++stale_blue_flags; break;
631 case NUM_TEAM_3: ++stale_yellow_flags; break;
632 case NUM_TEAM_4: ++stale_pink_flags; break;
637 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
639 if(stale_flags == ctf_teams)
640 ctf_stalemate = TRUE;
641 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
642 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
643 else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1)
644 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
646 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
649 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
651 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
653 WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
654 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_customizeentityforclient;
658 if (!wpforenemy_announced)
660 FOR_EACH_REALPLAYER(tmp_entity)
661 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
663 wpforenemy_announced = TRUE;
668 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
670 if(ITEM_DAMAGE_NEEDKILL(deathtype))
672 // automatically kill the flag and return it
674 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
677 if(autocvar_g_ctf_flag_return_damage)
679 // reduce health and check if it should be returned
680 self.health = self.health - damage;
681 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
691 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
694 if(self == ctf_worldflaglist) // only for the first flag
695 FOR_EACH_CLIENT(tmp_entity)
696 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
699 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
700 dprint("wtf the flag got squashed?\n");
701 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
702 if(!trace_startsolid) // can we resize it without getting stuck?
703 setsize(self, FLAG_MIN, FLAG_MAX); }
705 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
709 self.angles = '0 0 0';
717 switch(self.ctf_status)
721 if(autocvar_g_ctf_dropped_capture_radius)
723 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
724 if(tmp_entity.ctf_status == FLAG_DROPPED)
725 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
726 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
727 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
734 if(autocvar_g_ctf_flag_dropped_floatinwater)
736 vector midpoint = ((self.absmin + self.absmax) * 0.5);
737 if(pointcontents(midpoint) == CONTENT_WATER)
739 self.velocity = self.velocity * 0.5;
741 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
742 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
744 { self.movetype = MOVETYPE_FLY; }
746 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
748 if(autocvar_g_ctf_flag_return_dropped)
750 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
753 ctf_CheckFlagReturn(self, RETURN_DROPPED);
757 if(autocvar_g_ctf_flag_return_time)
759 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
760 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
768 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
771 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
775 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
779 if(autocvar_g_ctf_stalemate)
781 if(time >= wpforenemy_nextthink)
783 ctf_CheckStalemate();
784 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
787 if(CTF_SAMETEAM(self, self.owner))
789 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
790 ctf_Handle_Throw(self.owner, world, DROP_THROW);
791 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
792 ctf_Handle_Return(self, self.owner);
799 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
800 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
801 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
803 if((self.pass_target == world)
804 || (self.pass_target.deadflag != DEAD_NO)
805 || (self.pass_target.flagcarried)
806 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
807 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
808 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
810 // give up, pass failed
811 ctf_Handle_Drop(self, world, DROP_PASS);
815 // still a viable target, go for it
816 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
821 default: // this should never happen
823 dprint("ctf_FlagThink(): Flag exists with no status?\n");
831 if(gameover) { return; }
833 entity toucher = other;
835 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
836 if(ITEM_TOUCH_NEEDKILL())
839 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
843 // special touch behaviors
844 if(toucher.vehicle_flags & VHF_ISVEHICLE)
846 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
847 toucher = toucher.owner; // the player is actually the vehicle owner, not other
849 return; // do nothing
851 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
853 if(time > self.wait) // if we haven't in a while, play a sound/effect
855 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
856 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
857 self.wait = time + FLAG_TOUCHRATE;
861 else if(toucher.deadflag != DEAD_NO) { return; }
863 switch(self.ctf_status)
867 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self))
868 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
869 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
870 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
876 if(CTF_SAMETEAM(toucher, self) && autocvar_g_ctf_flag_return)
877 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
878 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
879 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
885 dprint("Someone touched a flag even though it was being carried?\n");
891 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
893 if(DIFF_TEAM(toucher, self.pass_sender))
894 ctf_Handle_Return(self, toucher);
896 ctf_Handle_Retrieve(self, toucher);
904 void ctf_RespawnFlag(entity flag)
906 // check for flag respawn being called twice in a row
907 if(flag.last_respawn > time - 0.5)
908 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
910 flag.last_respawn = time;
912 // reset the player (if there is one)
913 if((flag.owner) && (flag.owner.flagcarried == flag))
915 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
916 WaypointSprite_Kill(flag.wps_flagcarrier);
918 flag.owner.flagcarried = world;
920 if(flag.speedrunning)
921 ctf_FakeTimeLimit(flag.owner, -1);
924 if(flag.ctf_status == FLAG_DROPPED)
925 { WaypointSprite_Kill(flag.wps_flagdropped); }
928 setattachment(flag, world, "");
929 setorigin(flag, flag.ctf_spawnorigin);
931 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
932 flag.takedamage = DAMAGE_NO;
933 flag.health = flag.max_flag_health;
934 flag.solid = SOLID_TRIGGER;
935 flag.velocity = '0 0 0';
936 flag.angles = flag.mangle;
937 flag.flags = FL_ITEM | FL_NOTARGET;
939 flag.ctf_status = FLAG_BASE;
941 flag.pass_distance = 0;
942 flag.pass_sender = world;
943 flag.pass_target = world;
944 flag.ctf_dropper = world;
945 flag.ctf_pickuptime = 0;
946 flag.ctf_droptime = 0;
952 if(IS_PLAYER(self.owner))
953 ctf_Handle_Throw(self.owner, world, DROP_RESET);
955 ctf_RespawnFlag(self);
958 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
961 waypoint_spawnforitem_force(self, self.origin);
962 self.nearestwaypointtimeout = 0; // activate waypointing again
963 self.bot_basewaypoint = self.nearestwaypoint;
966 string basename = "base";
970 case NUM_TEAM_1: basename = "redbase"; break;
971 case NUM_TEAM_2: basename = "bluebase"; break;
972 case NUM_TEAM_3: basename = "yellowbase"; break;
973 case NUM_TEAM_4: basename = "pinkbase"; break;
976 WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
977 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
979 // captureshield setup
980 ctf_CaptureShield_Spawn(self);
983 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
986 self = flag; // for later usage with droptofloor()
989 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
990 ctf_worldflaglist = flag;
992 setattachment(flag, world, "");
994 flag.netname = ((teamnumber == NUM_TEAM_1) ? "^1RED^7 flag" : ((teamnumber == NUM_TEAM_2) ? "^2BLUE^7 flag" : ((teamnumber == NUM_TEAM_3) ? "^3YELLOW^7 flag" : "^6PINK^7 flag")));
995 flag.team = teamnumber;
996 flag.classname = "item_flag_team";
997 flag.target = "###item###"; // wut?
998 flag.flags = FL_ITEM | FL_NOTARGET;
999 flag.solid = SOLID_TRIGGER;
1000 flag.takedamage = DAMAGE_NO;
1001 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1002 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1003 flag.health = flag.max_flag_health;
1004 flag.event_damage = ctf_FlagDamage;
1005 flag.pushable = TRUE;
1006 flag.teleportable = TELEPORT_NORMAL;
1007 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1008 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1009 flag.velocity = '0 0 0';
1010 flag.mangle = flag.angles;
1011 flag.reset = ctf_Reset;
1012 flag.touch = ctf_FlagTouch;
1013 flag.think = ctf_FlagThink;
1014 flag.nextthink = time + FLAG_THINKRATE;
1015 flag.ctf_status = FLAG_BASE;
1018 if(flag.model == "") { flag.model = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_model : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_model : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_model : autocvar_g_ctf_flag_pink_model))); }
1019 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1020 if(!flag.skin) { flag.skin = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_skin : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_skin : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_skin : autocvar_g_ctf_flag_pink_skin))); }
1021 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber == NUM_TEAM_1) ? "redflag_touch" : ((teamnumber == NUM_TEAM_2) ? "blueflag_touch" : ((teamnumber == NUM_TEAM_3) ? "yellowflag_touch" : "pinkflag_touch"))); }
1022 if(flag.passeffect == "") { flag.passeffect = ((teamnumber == NUM_TEAM_1) ? "red_pass" : ((teamnumber == NUM_TEAM_2) ? "blue_pass" : ((teamnumber == NUM_TEAM_3) ? "yellow_pass" : "pink_pass"))); }
1023 if(flag.capeffect == "") { flag.capeffect = ((teamnumber == NUM_TEAM_1) ? "red_cap" : ((teamnumber == NUM_TEAM_2) ? "blue_cap" : ((teamnumber == NUM_TEAM_3) ? "yellow_cap" : "pink_cap"))); }
1026 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber == NUM_TEAM_1) ? "ctf/red_taken.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_taken.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_taken.wav" : "ctf/pink_taken.wav"))); }
1027 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber == NUM_TEAM_1) ? "ctf/red_returned.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_returned.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_returned.wav" : "ctf/pink_returned.wav"))); }
1028 if(flag.snd_flag_capture == "") { flag.snd_flag_capture = ((teamnumber == NUM_TEAM_1) ? "ctf/red_capture.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_capture.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_capture.wav" : "ctf/pink_capture.wav"))); }
1029 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber == NUM_TEAM_1) ? "ctf/red_dropped.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_dropped.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_dropped.wav" : "ctf/pink_dropped.wav"))); }
1030 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.
1031 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1032 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1035 precache_sound(flag.snd_flag_taken);
1036 precache_sound(flag.snd_flag_returned);
1037 precache_sound(flag.snd_flag_capture);
1038 precache_sound(flag.snd_flag_respawn);
1039 precache_sound(flag.snd_flag_dropped);
1040 precache_sound(flag.snd_flag_touch);
1041 precache_sound(flag.snd_flag_pass);
1042 precache_model(flag.model);
1043 precache_model("models/ctf/shield.md3");
1044 precache_model("models/ctf/shockwavetransring.md3");
1047 setmodel(flag, flag.model); // precision set below
1048 setsize(flag, FLAG_MIN, FLAG_MAX);
1049 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1051 if(autocvar_g_ctf_flag_glowtrails)
1053 flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : 145)));
1054 flag.glow_size = 25;
1055 flag.glow_trail = 1;
1058 flag.effects |= EF_LOWPRECISION;
1059 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1060 if(autocvar_g_ctf_dynamiclights)
1064 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1065 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1066 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1067 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1072 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1074 flag.dropped_origin = flag.origin;
1075 flag.noalign = TRUE;
1076 flag.movetype = MOVETYPE_NONE;
1078 else // drop to floor, automatically find a platform and set that as spawn origin
1080 flag.noalign = FALSE;
1083 flag.movetype = MOVETYPE_TOSS;
1086 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1094 // NOTE: LEGACY CODE, needs to be re-written!
1096 void havocbot_calculate_middlepoint()
1100 vector fo = '0 0 0';
1103 f = ctf_worldflaglist;
1108 f = f.ctf_worldflagnext;
1112 havocbot_ctf_middlepoint = s * (1.0 / n);
1113 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1117 entity havocbot_ctf_find_flag(entity bot)
1120 f = ctf_worldflaglist;
1123 if (CTF_SAMETEAM(bot, f))
1125 f = f.ctf_worldflagnext;
1130 entity havocbot_ctf_find_enemy_flag(entity bot)
1133 f = ctf_worldflaglist;
1136 if (CTF_DIFFTEAM(bot, f))
1138 f = f.ctf_worldflagnext;
1143 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1151 FOR_EACH_PLAYER(head)
1153 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1156 if(vlen(head.origin - org) < tc_radius)
1163 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1166 head = ctf_worldflaglist;
1169 if (CTF_SAMETEAM(self, head))
1171 head = head.ctf_worldflagnext;
1174 navigation_routerating(head, ratingscale, 10000);
1177 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1180 head = ctf_worldflaglist;
1183 if (CTF_SAMETEAM(self, head))
1185 head = head.ctf_worldflagnext;
1190 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1193 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1196 head = ctf_worldflaglist;
1199 if(CTF_DIFFTEAM(self, head))
1201 head = head.ctf_worldflagnext;
1204 navigation_routerating(head, ratingscale, 10000);
1207 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1209 if (!bot_waypoints_for_items)
1211 havocbot_goalrating_ctf_enemyflag(ratingscale);
1217 head = havocbot_ctf_find_enemy_flag(self);
1222 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1225 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1229 mf = havocbot_ctf_find_flag(self);
1231 if(mf.ctf_status == FLAG_BASE)
1235 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1238 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1241 head = ctf_worldflaglist;
1244 // flag is out in the field
1245 if(head.ctf_status != FLAG_BASE)
1246 if(head.tag_entity==world) // dropped
1250 if(vlen(org-head.origin)<df_radius)
1251 navigation_routerating(head, ratingscale, 10000);
1254 navigation_routerating(head, ratingscale, 10000);
1257 head = head.ctf_worldflagnext;
1261 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1265 head = findchainfloat(bot_pickup, TRUE);
1268 // gather health and armor only
1270 if (head.health || head.armorvalue)
1271 if (vlen(head.origin - org) < sradius)
1273 // get the value of the item
1274 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1276 navigation_routerating(head, t * ratingscale, 500);
1282 void havocbot_ctf_reset_role(entity bot)
1284 float cdefense, cmiddle, coffense;
1285 entity mf, ef, head;
1288 if(bot.deadflag != DEAD_NO)
1291 if(vlen(havocbot_ctf_middlepoint)==0)
1292 havocbot_calculate_middlepoint();
1295 if (bot.flagcarried)
1297 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1301 mf = havocbot_ctf_find_flag(bot);
1302 ef = havocbot_ctf_find_enemy_flag(bot);
1304 // Retrieve stolen flag
1305 if(mf.ctf_status!=FLAG_BASE)
1307 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1311 // If enemy flag is taken go to the middle to intercept pursuers
1312 if(ef.ctf_status!=FLAG_BASE)
1314 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1318 // if there is only me on the team switch to offense
1320 FOR_EACH_PLAYER(head)
1321 if(head.team==bot.team)
1326 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1330 // Evaluate best position to take
1331 // Count mates on middle position
1332 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1334 // Count mates on defense position
1335 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1337 // Count mates on offense position
1338 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1340 if(cdefense<=coffense)
1341 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1342 else if(coffense<=cmiddle)
1343 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1345 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1348 void havocbot_role_ctf_carrier()
1350 if(self.deadflag != DEAD_NO)
1352 havocbot_ctf_reset_role(self);
1356 if (self.flagcarried == world)
1358 havocbot_ctf_reset_role(self);
1362 if (self.bot_strategytime < time)
1364 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1366 navigation_goalrating_start();
1367 havocbot_goalrating_ctf_ourbase(50000);
1370 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1372 navigation_goalrating_end();
1374 if (self.navigation_hasgoals)
1375 self.havocbot_cantfindflag = time + 10;
1376 else if (time > self.havocbot_cantfindflag)
1378 // Can't navigate to my own base, suicide!
1379 // TODO: drop it and wander around
1380 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1386 void havocbot_role_ctf_escort()
1390 if(self.deadflag != DEAD_NO)
1392 havocbot_ctf_reset_role(self);
1396 if (self.flagcarried)
1398 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1402 // If enemy flag is back on the base switch to previous role
1403 ef = havocbot_ctf_find_enemy_flag(self);
1404 if(ef.ctf_status==FLAG_BASE)
1406 self.havocbot_role = self.havocbot_previous_role;
1407 self.havocbot_role_timeout = 0;
1411 // If the flag carrier reached the base switch to defense
1412 mf = havocbot_ctf_find_flag(self);
1413 if(mf.ctf_status!=FLAG_BASE)
1414 if(vlen(ef.origin - mf.dropped_origin) < 300)
1416 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1420 // Set the role timeout if necessary
1421 if (!self.havocbot_role_timeout)
1423 self.havocbot_role_timeout = time + random() * 30 + 60;
1426 // If nothing happened just switch to previous role
1427 if (time > self.havocbot_role_timeout)
1429 self.havocbot_role = self.havocbot_previous_role;
1430 self.havocbot_role_timeout = 0;
1434 // Chase the flag carrier
1435 if (self.bot_strategytime < time)
1437 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1438 navigation_goalrating_start();
1439 havocbot_goalrating_ctf_enemyflag(30000);
1440 havocbot_goalrating_ctf_ourstolenflag(40000);
1441 havocbot_goalrating_items(10000, self.origin, 10000);
1442 navigation_goalrating_end();
1446 void havocbot_role_ctf_offense()
1451 if(self.deadflag != DEAD_NO)
1453 havocbot_ctf_reset_role(self);
1457 if (self.flagcarried)
1459 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1464 mf = havocbot_ctf_find_flag(self);
1465 ef = havocbot_ctf_find_enemy_flag(self);
1468 if(mf.ctf_status!=FLAG_BASE)
1471 pos = mf.tag_entity.origin;
1475 // Try to get it if closer than the enemy base
1476 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1478 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1483 // Escort flag carrier
1484 if(ef.ctf_status!=FLAG_BASE)
1487 pos = ef.tag_entity.origin;
1491 if(vlen(pos-mf.dropped_origin)>700)
1493 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1498 // About to fail, switch to middlefield
1501 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1505 // Set the role timeout if necessary
1506 if (!self.havocbot_role_timeout)
1507 self.havocbot_role_timeout = time + 120;
1509 if (time > self.havocbot_role_timeout)
1511 havocbot_ctf_reset_role(self);
1515 if (self.bot_strategytime < time)
1517 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1518 navigation_goalrating_start();
1519 havocbot_goalrating_ctf_ourstolenflag(50000);
1520 havocbot_goalrating_ctf_enemybase(20000);
1521 havocbot_goalrating_items(5000, self.origin, 1000);
1522 havocbot_goalrating_items(1000, self.origin, 10000);
1523 navigation_goalrating_end();
1527 // Retriever (temporary role):
1528 void havocbot_role_ctf_retriever()
1532 if(self.deadflag != DEAD_NO)
1534 havocbot_ctf_reset_role(self);
1538 if (self.flagcarried)
1540 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1544 // If flag is back on the base switch to previous role
1545 mf = havocbot_ctf_find_flag(self);
1546 if(mf.ctf_status==FLAG_BASE)
1548 havocbot_ctf_reset_role(self);
1552 if (!self.havocbot_role_timeout)
1553 self.havocbot_role_timeout = time + 20;
1555 if (time > self.havocbot_role_timeout)
1557 havocbot_ctf_reset_role(self);
1561 if (self.bot_strategytime < time)
1566 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1567 navigation_goalrating_start();
1568 havocbot_goalrating_ctf_ourstolenflag(50000);
1569 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1570 havocbot_goalrating_ctf_enemybase(30000);
1571 havocbot_goalrating_items(500, self.origin, rt_radius);
1572 navigation_goalrating_end();
1576 void havocbot_role_ctf_middle()
1580 if(self.deadflag != DEAD_NO)
1582 havocbot_ctf_reset_role(self);
1586 if (self.flagcarried)
1588 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1592 mf = havocbot_ctf_find_flag(self);
1593 if(mf.ctf_status!=FLAG_BASE)
1595 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1599 if (!self.havocbot_role_timeout)
1600 self.havocbot_role_timeout = time + 10;
1602 if (time > self.havocbot_role_timeout)
1604 havocbot_ctf_reset_role(self);
1608 if (self.bot_strategytime < time)
1612 org = havocbot_ctf_middlepoint;
1613 org_z = self.origin_z;
1615 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1616 navigation_goalrating_start();
1617 havocbot_goalrating_ctf_ourstolenflag(50000);
1618 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1619 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1620 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1621 havocbot_goalrating_items(2500, self.origin, 10000);
1622 havocbot_goalrating_ctf_enemybase(2500);
1623 navigation_goalrating_end();
1627 void havocbot_role_ctf_defense()
1631 if(self.deadflag != DEAD_NO)
1633 havocbot_ctf_reset_role(self);
1637 if (self.flagcarried)
1639 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1643 // If own flag was captured
1644 mf = havocbot_ctf_find_flag(self);
1645 if(mf.ctf_status!=FLAG_BASE)
1647 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1651 if (!self.havocbot_role_timeout)
1652 self.havocbot_role_timeout = time + 30;
1654 if (time > self.havocbot_role_timeout)
1656 havocbot_ctf_reset_role(self);
1659 if (self.bot_strategytime < time)
1664 org = mf.dropped_origin;
1665 mp_radius = havocbot_ctf_middlepoint_radius;
1667 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1668 navigation_goalrating_start();
1670 // if enemies are closer to our base, go there
1671 entity head, closestplayer = world;
1672 float distance, bestdistance = 10000;
1673 FOR_EACH_PLAYER(head)
1675 if(head.deadflag!=DEAD_NO)
1678 distance = vlen(org - head.origin);
1679 if(distance<bestdistance)
1681 closestplayer = head;
1682 bestdistance = distance;
1687 if(closestplayer.team!=self.team)
1688 if(vlen(org - self.origin)>1000)
1689 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1690 havocbot_goalrating_ctf_ourbase(30000);
1692 havocbot_goalrating_ctf_ourstolenflag(20000);
1693 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1694 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1695 havocbot_goalrating_items(10000, org, mp_radius);
1696 havocbot_goalrating_items(5000, self.origin, 10000);
1697 navigation_goalrating_end();
1701 void havocbot_role_ctf_setrole(entity bot, float role)
1703 dprint(strcat(bot.netname," switched to "));
1706 case HAVOCBOT_CTF_ROLE_CARRIER:
1708 bot.havocbot_role = havocbot_role_ctf_carrier;
1709 bot.havocbot_role_timeout = 0;
1710 bot.havocbot_cantfindflag = time + 10;
1711 bot.bot_strategytime = 0;
1713 case HAVOCBOT_CTF_ROLE_DEFENSE:
1715 bot.havocbot_role = havocbot_role_ctf_defense;
1716 bot.havocbot_role_timeout = 0;
1718 case HAVOCBOT_CTF_ROLE_MIDDLE:
1720 bot.havocbot_role = havocbot_role_ctf_middle;
1721 bot.havocbot_role_timeout = 0;
1723 case HAVOCBOT_CTF_ROLE_OFFENSE:
1725 bot.havocbot_role = havocbot_role_ctf_offense;
1726 bot.havocbot_role_timeout = 0;
1728 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1729 dprint("retriever");
1730 bot.havocbot_previous_role = bot.havocbot_role;
1731 bot.havocbot_role = havocbot_role_ctf_retriever;
1732 bot.havocbot_role_timeout = time + 10;
1733 bot.bot_strategytime = 0;
1735 case HAVOCBOT_CTF_ROLE_ESCORT:
1737 bot.havocbot_previous_role = bot.havocbot_role;
1738 bot.havocbot_role = havocbot_role_ctf_escort;
1739 bot.havocbot_role_timeout = time + 30;
1740 bot.bot_strategytime = 0;
1751 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1754 float t = 0, t2 = 0, t3 = 0;
1756 // initially clear items so they can be set as necessary later.
1757 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1758 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST
1759 | IT_YELLOW_FLAG_CARRYING | IT_YELLOW_FLAG_TAKEN | IT_YELLOW_FLAG_LOST
1760 | IT_PINK_FLAG_CARRYING | IT_PINK_FLAG_TAKEN | IT_PINK_FLAG_LOST
1763 // scan through all the flags and notify the client about them
1764 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1766 if(flag.team == NUM_TEAM_1) { t = IT_RED_FLAG_CARRYING; t2 = IT_RED_FLAG_TAKEN; t3 = IT_RED_FLAG_LOST; }
1767 if(flag.team == NUM_TEAM_2) { t = IT_BLUE_FLAG_CARRYING; t2 = IT_BLUE_FLAG_TAKEN; t3 = IT_BLUE_FLAG_LOST; }
1768 if(flag.team == NUM_TEAM_3) { t = IT_YELLOW_FLAG_CARRYING; t2 = IT_YELLOW_FLAG_TAKEN; t3 = IT_YELLOW_FLAG_LOST; }
1769 if(flag.team == NUM_TEAM_4) { t = IT_PINK_FLAG_CARRYING; t2 = IT_PINK_FLAG_TAKEN; t3 = IT_PINK_FLAG_LOST; }
1771 switch(flag.ctf_status)
1776 if((flag.owner == self) || (flag.pass_sender == self))
1777 self.items |= t; // carrying: self is currently carrying the flag
1779 self.items |= t2; // taken: someone else is carrying the flag
1784 self.items |= t3; // lost: the flag is dropped somewhere on the map
1790 // item for stopping players from capturing the flag too often
1791 if(self.ctf_captureshielded)
1792 self.items |= IT_CTF_SHIELDED;
1794 // update the health of the flag carrier waypointsprite
1795 if(self.wps_flagcarrier)
1796 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1801 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1803 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1805 if(frag_target == frag_attacker) // damage done to yourself
1807 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1808 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1810 else // damage done to everyone else
1812 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1813 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1816 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1818 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)))
1819 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1821 frag_target.wps_helpme_time = time;
1822 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1824 // todo: add notification for when flag carrier needs help?
1829 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1831 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1833 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1834 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1837 if(frag_target.flagcarried)
1838 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1843 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1846 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1849 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1851 entity flag; // temporary entity for the search method
1853 if(self.flagcarried)
1854 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1856 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1858 if(flag.pass_sender == self) { flag.pass_sender = world; }
1859 if(flag.pass_target == self) { flag.pass_target = world; }
1860 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1866 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1868 if(self.flagcarried)
1869 if(!autocvar_g_ctf_portalteleport)
1870 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1875 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1877 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1879 entity player = self;
1881 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1883 // pass the flag to a team mate
1884 if(autocvar_g_ctf_pass)
1886 entity head, closest_target = world;
1887 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1889 while(head) // find the closest acceptable target to pass to
1891 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1892 if(head != player && SAME_TEAM(head, player))
1893 if(!head.speedrunning && !head.vehicle)
1895 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1896 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1897 vector passer_center = CENTER_OR_VIEWOFS(player);
1899 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1901 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1903 if(IS_BOT_CLIENT(head))
1905 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1906 ctf_Handle_Throw(head, player, DROP_PASS);
1910 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1911 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1913 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1916 else if(player.flagcarried)
1920 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1921 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1922 { closest_target = head; }
1924 else { closest_target = head; }
1931 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1934 // throw the flag in front of you
1935 if(autocvar_g_ctf_throw && player.flagcarried)
1937 if(player.throw_count == -1)
1939 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1941 player.throw_prevtime = time;
1942 player.throw_count = 1;
1943 ctf_Handle_Throw(player, world, DROP_THROW);
1948 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1954 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1955 else { player.throw_count += 1; }
1956 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1958 player.throw_prevtime = time;
1959 ctf_Handle_Throw(player, world, DROP_THROW);
1968 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1970 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1972 self.wps_helpme_time = time;
1973 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1975 else // create a normal help me waypointsprite
1977 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');
1978 WaypointSprite_Ping(self.wps_helpme);
1984 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1986 if(vh_player.flagcarried)
1988 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1990 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1994 setattachment(vh_player.flagcarried, vh_vehicle, "");
1995 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1996 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1997 //vh_player.flagcarried.angles = '0 0 0';
2005 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2007 if(vh_player.flagcarried)
2009 setattachment(vh_player.flagcarried, vh_player, "");
2010 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2011 vh_player.flagcarried.scale = FLAG_SCALE;
2012 vh_player.flagcarried.angles = '0 0 0';
2019 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2021 if(self.flagcarried)
2023 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
2024 ctf_RespawnFlag(self.flagcarried);
2031 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2033 entity flag; // temporary entity for the search method
2035 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2037 switch(flag.ctf_status)
2042 // lock the flag, game is over
2043 flag.movetype = MOVETYPE_NONE;
2044 flag.takedamage = DAMAGE_NO;
2045 flag.solid = SOLID_NOT;
2046 flag.nextthink = FALSE; // stop thinking
2048 //dprint("stopping the ", flag.netname, " from moving.\n");
2056 // do nothing for these flags
2065 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2067 havocbot_ctf_reset_role(self);
2071 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2073 ret_float = ctf_teams;
2081 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2082 CTF Starting point for a player in team one (Red).
2083 Keys: "angle" viewing angle when spawning. */
2084 void spawnfunc_info_player_team1()
2086 if(g_assault) { remove(self); return; }
2088 self.team = NUM_TEAM_1; // red
2089 spawnfunc_info_player_deathmatch();
2093 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2094 CTF Starting point for a player in team two (Blue).
2095 Keys: "angle" viewing angle when spawning. */
2096 void spawnfunc_info_player_team2()
2098 if(g_assault) { remove(self); return; }
2100 self.team = NUM_TEAM_2; // blue
2101 spawnfunc_info_player_deathmatch();
2104 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2105 CTF Starting point for a player in team three (Yellow).
2106 Keys: "angle" viewing angle when spawning. */
2107 void spawnfunc_info_player_team3()
2109 if(g_assault) { remove(self); return; }
2111 self.team = NUM_TEAM_3; // yellow
2112 spawnfunc_info_player_deathmatch();
2116 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2117 CTF Starting point for a player in team four (Purple).
2118 Keys: "angle" viewing angle when spawning. */
2119 void spawnfunc_info_player_team4()
2121 if(g_assault) { remove(self); return; }
2123 self.team = NUM_TEAM_4; // purple
2124 spawnfunc_info_player_deathmatch();
2127 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2128 CTF flag for team one (Red).
2130 "angle" Angle the flag will point (minus 90 degrees)...
2131 "model" model to use, note this needs red and blue as skins 0 and 1...
2132 "noise" sound played when flag is picked up...
2133 "noise1" sound played when flag is returned by a teammate...
2134 "noise2" sound played when flag is captured...
2135 "noise3" sound played when flag is lost in the field and respawns itself...
2136 "noise4" sound played when flag is dropped by a player...
2137 "noise5" sound played when flag touches the ground... */
2138 void spawnfunc_item_flag_team1()
2140 if(!g_ctf) { remove(self); return; }
2142 ctf_FlagSetup(NUM_TEAM_1, self);
2145 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2146 CTF flag for team two (Blue).
2148 "angle" Angle the flag will point (minus 90 degrees)...
2149 "model" model to use, note this needs red and blue as skins 0 and 1...
2150 "noise" sound played when flag is picked up...
2151 "noise1" sound played when flag is returned by a teammate...
2152 "noise2" sound played when flag is captured...
2153 "noise3" sound played when flag is lost in the field and respawns itself...
2154 "noise4" sound played when flag is dropped by a player...
2155 "noise5" sound played when flag touches the ground... */
2156 void spawnfunc_item_flag_team2()
2158 if(!g_ctf) { remove(self); return; }
2160 ctf_FlagSetup(NUM_TEAM_2, self);
2163 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2164 CTF flag for team three (Yellow).
2166 "angle" Angle the flag will point (minus 90 degrees)...
2167 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2168 "noise" sound played when flag is picked up...
2169 "noise1" sound played when flag is returned by a teammate...
2170 "noise2" sound played when flag is captured...
2171 "noise3" sound played when flag is lost in the field and respawns itself...
2172 "noise4" sound played when flag is dropped by a player...
2173 "noise5" sound played when flag touches the ground... */
2174 void spawnfunc_item_flag_team3()
2176 if(!g_ctf) { remove(self); return; }
2178 ctf_FlagSetup(NUM_TEAM_3, self);
2181 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2182 CTF flag for team two (Pink).
2184 "angle" Angle the flag will point (minus 90 degrees)...
2185 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2186 "noise" sound played when flag is picked up...
2187 "noise1" sound played when flag is returned by a teammate...
2188 "noise2" sound played when flag is captured...
2189 "noise3" sound played when flag is lost in the field and respawns itself...
2190 "noise4" sound played when flag is dropped by a player...
2191 "noise5" sound played when flag touches the ground... */
2192 void spawnfunc_item_flag_team4()
2194 if(!g_ctf) { remove(self); return; }
2196 ctf_FlagSetup(NUM_TEAM_4, self);
2199 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2200 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2201 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.
2203 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2204 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2205 void spawnfunc_ctf_team()
2207 if(!g_ctf) { remove(self); return; }
2209 self.classname = "ctf_team";
2210 self.team = self.cnt + 1;
2213 // compatibility for quake maps
2214 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2215 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2216 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2217 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2218 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2219 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2227 void ctf_ScoreRules(float teams)
2229 CheckAllowedTeams(world);
2230 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2231 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2232 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2233 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2234 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2235 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2236 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2237 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2238 ScoreRules_basics_end();
2241 // code from here on is just to support maps that don't have flag and team entities
2242 void ctf_SpawnTeam (string teamname, float teamcolor)
2247 self.classname = "ctf_team";
2248 self.netname = teamname;
2249 self.cnt = teamcolor;
2251 spawnfunc_ctf_team();
2256 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2261 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2263 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2264 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2267 ctf_teams = bound(2, ctf_teams, 4);
2269 // if no teams are found, spawn defaults
2270 if(find(world, classname, "ctf_team") == world)
2272 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2273 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2274 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2276 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2278 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2281 ctf_ScoreRules(ctf_teams);
2284 void ctf_Initialize()
2286 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2288 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2289 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2290 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2292 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2296 MUTATOR_DEFINITION(gamemode_ctf)
2298 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2299 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2300 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2301 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2302 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2303 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2304 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2305 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2306 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2307 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2308 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2309 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2310 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2311 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2312 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2316 if(time > 1) // game loads at time 1
2317 error("This is a game type and it cannot be added at runtime.");
2321 MUTATOR_ONROLLBACK_OR_REMOVE
2323 // we actually cannot roll back ctf_Initialize here
2324 // BUT: we don't need to! If this gets called, adding always
2330 print("This is a game type and it cannot be removed at runtime.");