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(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
21 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
24 void ctf_CaptureRecord(entity flag, entity player)
26 float cap_record = ctf_captimerecord;
27 float cap_time = (time - flag.ctf_pickuptime);
28 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
31 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
32 else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
33 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)); }
34 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)); }
36 // write that shit in the database
37 if(!ctf_oneflag) // but not in 1-flag mode
38 if((!ctf_captimerecord) || (cap_time < cap_record))
40 ctf_captimerecord = cap_time;
41 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
42 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
43 write_recordmarker(player, (time - cap_time), cap_time);
47 void ctf_FlagcarrierWaypoints(entity player)
49 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
50 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
51 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
52 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
55 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
57 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
58 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
59 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
60 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
63 if(current_height) // make sure we can actually do this arcing path
65 targpos = (to + ('0 0 1' * current_height));
66 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
67 if(trace_fraction < 1)
69 //print("normal arc line failed, trying to find new pos...");
70 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
71 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
72 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
73 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
74 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
77 else { targpos = to; }
79 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
81 vector desired_direction = normalize(targpos - from);
82 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
83 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
86 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
88 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
90 // directional tracing only
92 makevectors(passer_angle);
94 // find the closest point on the enemy to the center of the attack
95 float ang; // angle between shotdir and h
96 float h; // hypotenuse, which is the distance between attacker to head
97 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
99 h = vlen(head_center - passer_center);
100 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
103 vector nearest_on_line = (passer_center + a * v_forward);
104 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
106 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
107 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
109 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
114 else { return TRUE; }
118 // =======================
119 // CaptureShield Functions
120 // =======================
122 float ctf_CaptureShield_CheckStatus(entity p)
124 float s, s2, s3, s4, se, se2, se3, se4, sr, ser;
126 float players_worseeq, players_total;
128 if(ctf_captureshield_max_ratio <= 0)
131 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
132 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
133 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
134 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
136 sr = ((s - s2) + (s3 + s4));
138 if(sr >= -ctf_captureshield_min_negscore)
141 players_total = players_worseeq = 0;
146 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
147 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
148 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
149 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
151 ser = ((se - se2) + (se3 + se4));
158 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
159 // use this rule here
161 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
167 void ctf_CaptureShield_Update(entity player, float wanted_status)
169 float updated_status = ctf_CaptureShield_CheckStatus(player);
170 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
172 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
173 player.ctf_captureshielded = updated_status;
177 float ctf_CaptureShield_Customize()
179 if(self.enemy.active != ACTIVE_ACTIVE) { return TRUE; }
180 if(!other.ctf_captureshielded) { return FALSE; }
181 if(CTF_SAMETEAM(self, other)) { return FALSE; }
186 void ctf_CaptureShield_Touch()
188 if(self.enemy.active != ACTIVE_ACTIVE)
190 vector mymid = (self.absmin + self.absmax) * 0.5;
191 vector othermid = (other.absmin + other.absmax) * 0.5;
193 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
194 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_INACTIVE); }
199 if(!other.ctf_captureshielded) { return; }
200 if(CTF_SAMETEAM(self, other)) { return; }
202 vector mymid = (self.absmin + self.absmax) * 0.5;
203 vector othermid = (other.absmin + other.absmax) * 0.5;
205 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
206 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
209 void ctf_CaptureShield_Spawn(entity flag)
211 entity shield = spawn();
214 shield.team = self.team;
215 shield.touch = ctf_CaptureShield_Touch;
216 shield.customizeentityforclient = ctf_CaptureShield_Customize;
217 shield.classname = "ctf_captureshield";
218 shield.effects = EF_ADDITIVE;
219 shield.movetype = MOVETYPE_NOCLIP;
220 shield.solid = SOLID_TRIGGER;
221 shield.avelocity = '7 0 11';
224 setorigin(shield, self.origin);
225 setmodel(shield, "models/ctf/shield.md3");
226 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
230 // ====================
231 // Drop/Pass/Throw Code
232 // ====================
234 void ctf_Handle_Drop(entity flag, entity player, float droptype)
237 player = (player ? player : flag.pass_sender);
240 flag.movetype = MOVETYPE_TOSS;
241 flag.takedamage = DAMAGE_YES;
242 flag.angles = '0 0 0';
243 flag.health = flag.max_flag_health;
244 flag.ctf_droptime = time;
245 flag.ctf_dropper = player;
246 flag.ctf_status = FLAG_DROPPED;
248 // messages and sounds
249 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
250 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
251 ctf_EventLog("dropped", player.team, player);
254 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
255 PlayerScore_Add(player, SP_CTF_DROPS, 1);
258 if(autocvar_g_ctf_flag_dropped_waypoint)
259 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));
261 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
263 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
264 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
267 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
269 if(droptype == DROP_PASS)
271 flag.pass_distance = 0;
272 flag.pass_sender = world;
273 flag.pass_target = world;
277 void ctf_Handle_Retrieve(entity flag, entity player)
279 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
280 entity sender = flag.pass_sender;
282 // transfer flag to player
284 flag.owner.flagcarried = flag;
289 setattachment(flag, player.vehicle, "");
290 setorigin(flag, VEHICLE_FLAG_OFFSET);
291 flag.scale = VEHICLE_FLAG_SCALE;
295 setattachment(flag, player, "");
296 setorigin(flag, FLAG_CARRY_OFFSET);
298 flag.movetype = MOVETYPE_NONE;
299 flag.takedamage = DAMAGE_NO;
300 flag.solid = SOLID_NOT;
301 flag.angles = '0 0 0';
302 flag.ctf_status = FLAG_CARRY;
304 // messages and sounds
305 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
306 ctf_EventLog("receive", flag.team, player);
308 FOR_EACH_REALPLAYER(tmp_player)
310 if(tmp_player == sender)
311 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
312 else if(tmp_player == player)
313 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
314 else if(SAME_TEAM(tmp_player, sender))
315 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
318 // create new waypoint
319 ctf_FlagcarrierWaypoints(player);
321 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
322 player.throw_antispam = sender.throw_antispam;
324 flag.pass_distance = 0;
325 flag.pass_sender = world;
326 flag.pass_target = world;
329 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
331 entity flag = player.flagcarried;
332 vector targ_origin, flag_velocity;
334 if(!flag) { return; }
335 if((droptype == DROP_PASS) && !receiver) { return; }
337 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
340 setattachment(flag, world, "");
341 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
342 flag.owner.flagcarried = world;
344 flag.solid = SOLID_TRIGGER;
345 flag.ctf_dropper = player;
346 flag.ctf_droptime = time;
348 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
355 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
356 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
357 WarpZone_RefSys_Copy(flag, receiver);
358 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
359 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
361 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
362 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
365 flag.movetype = MOVETYPE_FLY;
366 flag.takedamage = DAMAGE_NO;
367 flag.pass_sender = player;
368 flag.pass_target = receiver;
369 flag.ctf_status = FLAG_PASSING;
372 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
373 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
374 ctf_EventLog("pass", flag.team, player);
380 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'));
382 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)));
383 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
384 ctf_Handle_Drop(flag, player, droptype);
390 flag.velocity = '0 0 0'; // do nothing
397 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);
398 ctf_Handle_Drop(flag, player, droptype);
403 // kill old waypointsprite
404 WaypointSprite_Ping(player.wps_flagcarrier);
405 WaypointSprite_Kill(player.wps_flagcarrier);
407 if(player.wps_enemyflagcarrier)
408 WaypointSprite_Kill(player.wps_enemyflagcarrier);
411 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
419 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
421 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
422 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
423 entity player_team_flag = world, tmp_entity;
424 float old_time, new_time;
426 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
429 if(CTF_SAMETEAM(player, flag)) { return; }
431 else if(CTF_DIFFTEAM(player, flag)) { return; }
434 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
435 if(SAME_TEAM(tmp_entity, player))
437 player_team_flag = tmp_entity;
441 player.throw_prevtime = time;
442 player.throw_count = 0;
444 // messages and sounds
445 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
446 ctf_CaptureRecord(enemy_flag, player);
447 sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
451 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
452 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
457 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
458 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
460 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
461 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
462 if(!old_time || new_time < old_time)
463 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
466 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
467 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
470 if(capturetype == CAPTURE_NORMAL)
472 WaypointSprite_Kill(player.wps_flagcarrier);
473 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
475 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
476 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
480 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
481 ctf_RespawnFlag(enemy_flag);
484 void ctf_Handle_Return(entity flag, entity player)
486 // messages and sounds
487 if(player.flags & FL_MONSTER)
489 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
493 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
494 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
496 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
497 ctf_EventLog("return", flag.team, player);
500 if(IS_PLAYER(player))
502 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
503 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
506 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
510 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
511 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
512 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
516 if(player.flagcarried == flag)
517 WaypointSprite_Kill(player.wps_flagcarrier);
520 ctf_RespawnFlag(flag);
523 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
526 float pickup_dropped_score; // used to calculate dropped pickup score
527 entity tmp_entity; // temporary entity
529 // attach the flag to the player
531 player.flagcarried = flag;
534 setattachment(flag, player.vehicle, "");
535 setorigin(flag, VEHICLE_FLAG_OFFSET);
536 flag.scale = VEHICLE_FLAG_SCALE;
540 setattachment(flag, player, "");
541 setorigin(flag, FLAG_CARRY_OFFSET);
545 flag.movetype = MOVETYPE_NONE;
546 flag.takedamage = DAMAGE_NO;
547 flag.solid = SOLID_NOT;
548 flag.angles = '0 0 0';
549 flag.ctf_status = FLAG_CARRY;
553 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
554 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
558 // messages and sounds
559 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
560 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
561 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
562 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
563 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)); }
565 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
568 FOR_EACH_PLAYER(tmp_entity)
569 if(tmp_entity != player)
570 if(DIFF_TEAM(player, tmp_entity))
571 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
574 FOR_EACH_PLAYER(tmp_entity)
575 if(tmp_entity != player)
576 if(CTF_SAMETEAM(flag, tmp_entity))
577 if(SAME_TEAM(player, tmp_entity))
578 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
580 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);
582 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
585 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
590 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
591 ctf_EventLog("steal", flag.team, player);
597 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);
598 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);
599 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
600 PlayerTeamScore_AddScore(player, pickup_dropped_score);
601 ctf_EventLog("pickup", flag.team, player);
609 if(pickuptype == PICKUP_BASE)
611 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
612 if((player.speedrunning) && (ctf_captimerecord))
613 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
617 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
620 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
621 ctf_FlagcarrierWaypoints(player);
622 WaypointSprite_Ping(player.wps_flagcarrier);
626 // ===================
627 // Main Flag Functions
628 // ===================
630 void ctf_CheckFlagReturn(entity flag, float returntype)
632 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
634 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
636 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
640 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
641 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
642 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
643 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
647 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
649 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
650 ctf_EventLog("returned", flag.team, world);
651 ctf_RespawnFlag(flag);
656 float ctf_Stalemate_Customize()
658 // make spectators see what the player would see
660 e = WaypointSprite_getviewentity(other);
661 wp_owner = self.owner;
664 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return FALSE; }
665 if(SAME_TEAM(wp_owner, e)) { return FALSE; }
666 if(!IS_PLAYER(e)) { return FALSE; }
671 void ctf_CheckStalemate(void)
674 float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
677 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
679 // build list of stale flags
680 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
682 if(autocvar_g_ctf_stalemate)
683 if(tmp_entity.ctf_status != FLAG_BASE)
684 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
686 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
687 ctf_staleflaglist = tmp_entity;
689 switch(tmp_entity.team)
691 case NUM_TEAM_1: ++stale_red_flags; break;
692 case NUM_TEAM_2: ++stale_blue_flags; break;
693 case NUM_TEAM_3: ++stale_yellow_flags; break;
694 case NUM_TEAM_4: ++stale_pink_flags; break;
695 default: ++stale_neutral_flags; break;
701 stale_flags = (stale_neutral_flags >= 1);
703 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
705 if(ctf_oneflag && stale_flags == 1)
706 ctf_stalemate = TRUE;
707 else if(stale_flags == ctf_teams)
708 ctf_stalemate = TRUE;
709 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
710 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
711 else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1)
712 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
714 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
717 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
719 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
721 WaypointSprite_Spawn(((ctf_oneflag) ? "flagcarrier" : "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));
722 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
726 if (!wpforenemy_announced)
728 FOR_EACH_REALPLAYER(tmp_entity)
729 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
731 wpforenemy_announced = TRUE;
736 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
738 if(ITEM_DAMAGE_NEEDKILL(deathtype))
740 if(autocvar_g_ctf_flag_return_damage_delay)
742 self.ctf_flagdamaged = TRUE;
747 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
751 if(autocvar_g_ctf_flag_return_damage)
753 // reduce health and check if it should be returned
754 self.health = self.health - damage;
755 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
765 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
768 if(self == ctf_worldflaglist) // only for the first flag
769 FOR_EACH_CLIENT(tmp_entity)
770 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
773 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
774 dprint("wtf the flag got squashed?\n");
775 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
776 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
777 setsize(self, FLAG_MIN, FLAG_MAX); }
779 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
783 self.angles = '0 0 0';
791 switch(self.ctf_status)
795 if(autocvar_g_ctf_dropped_capture_radius)
797 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
798 if(tmp_entity.ctf_status == FLAG_DROPPED)
799 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
800 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
801 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
808 if(autocvar_g_ctf_flag_dropped_floatinwater)
810 vector midpoint = ((self.absmin + self.absmax) * 0.5);
811 if(pointcontents(midpoint) == CONTENT_WATER)
813 self.velocity = self.velocity * 0.5;
815 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
816 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
818 { self.movetype = MOVETYPE_FLY; }
820 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
822 if(autocvar_g_ctf_flag_return_dropped)
824 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
827 ctf_CheckFlagReturn(self, RETURN_DROPPED);
831 if(self.ctf_flagdamaged)
833 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
834 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
837 else if(autocvar_g_ctf_flag_return_time)
839 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
840 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
848 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
851 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
855 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
859 if(autocvar_g_ctf_stalemate)
861 if(time >= wpforenemy_nextthink)
863 ctf_CheckStalemate();
864 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
867 if(CTF_SAMETEAM(self, self.owner) && self.team)
869 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
870 ctf_Handle_Throw(self.owner, world, DROP_THROW);
871 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
872 ctf_Handle_Return(self, self.owner);
879 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
880 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
881 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
883 if((self.pass_target == world)
884 || (self.pass_target.deadflag != DEAD_NO)
885 || (self.pass_target.flagcarried)
886 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
887 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
888 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
890 // give up, pass failed
891 ctf_Handle_Drop(self, world, DROP_PASS);
895 // still a viable target, go for it
896 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
901 default: // this should never happen
903 dprint("ctf_FlagThink(): Flag exists with no status?\n");
911 if(gameover) { return; }
912 if(self.active != ACTIVE_ACTIVE) { return; }
914 entity toucher = other, tmp_entity;
915 float is_not_monster = (!(toucher.flags & FL_MONSTER)), num_perteam = 0;
917 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
918 if(ITEM_TOUCH_NEEDKILL())
920 if(!autocvar_g_ctf_flag_return_damage_delay)
923 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
928 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
930 // special touch behaviors
931 if(toucher.vehicle_flags & VHF_ISVEHICLE)
933 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
934 toucher = toucher.owner; // the player is actually the vehicle owner, not other
936 return; // do nothing
938 else if(toucher.flags & FL_MONSTER)
940 if(!autocvar_g_ctf_allow_monster_touch)
941 return; // do nothing
943 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
945 if(time > self.wait) // if we haven't in a while, play a sound/effect
947 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
948 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
949 self.wait = time + FLAG_TOUCHRATE;
953 else if(toucher.deadflag != DEAD_NO) { return; }
955 switch(self.ctf_status)
961 if(CTF_DIFFTEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
962 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
963 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
964 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
966 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
967 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
968 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
969 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
975 if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team
976 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
977 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
978 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
984 dprint("Someone touched a flag even though it was being carried?\n");
990 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
992 if(DIFF_TEAM(toucher, self.pass_sender))
993 ctf_Handle_Return(self, toucher);
995 ctf_Handle_Retrieve(self, toucher);
1002 .float last_respawn;
1003 void ctf_RespawnFlag(entity flag)
1005 // check for flag respawn being called twice in a row
1006 if(flag.last_respawn > time - 0.5)
1007 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1009 flag.last_respawn = time;
1011 // reset the player (if there is one)
1012 if((flag.owner) && (flag.owner.flagcarried == flag))
1014 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1015 WaypointSprite_Kill(flag.wps_flagcarrier);
1017 flag.owner.flagcarried = world;
1019 if(flag.speedrunning)
1020 ctf_FakeTimeLimit(flag.owner, -1);
1023 if((flag.owner) && (flag.owner.vehicle))
1024 flag.scale = FLAG_SCALE;
1026 if(flag.ctf_status == FLAG_DROPPED)
1027 { WaypointSprite_Kill(flag.wps_flagdropped); }
1030 setattachment(flag, world, "");
1031 setorigin(flag, flag.ctf_spawnorigin);
1033 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1034 flag.takedamage = DAMAGE_NO;
1035 flag.health = flag.max_flag_health;
1036 flag.solid = SOLID_TRIGGER;
1037 flag.velocity = '0 0 0';
1038 flag.angles = flag.mangle;
1039 flag.flags = FL_ITEM | FL_NOTARGET;
1041 flag.ctf_status = FLAG_BASE;
1043 flag.pass_distance = 0;
1044 flag.pass_sender = world;
1045 flag.pass_target = world;
1046 flag.ctf_dropper = world;
1047 flag.ctf_pickuptime = 0;
1048 flag.ctf_droptime = 0;
1049 flag.ctf_flagdamaged = 0;
1051 ctf_CheckStalemate();
1057 if(IS_PLAYER(self.owner))
1058 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1060 ctf_RespawnFlag(self);
1065 if(self.ctf_status != FLAG_BASE) { return; }
1070 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1073 if(SAME_TEAM(flag, self))
1074 if(flag.active == ACTIVE_ACTIVE)
1078 if(self.active == ACTIVE_ACTIVE)
1081 dprint("ctf_Use: Unable to deactivate flag (not enough active flags on the map)\n");
1085 self.active = ((self.active) ? ACTIVE_NOT : ACTIVE_ACTIVE);
1087 if(self.active == ACTIVE_ACTIVE)
1088 WaypointSprite_Ping(self.wps_flagbase);
1091 float ctf_FlagWaypoint_Customize()
1093 if(self.owner.active != ACTIVE_ACTIVE) { return FALSE; }
1097 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1100 waypoint_spawnforitem_force(self, self.origin);
1101 self.nearestwaypointtimeout = 0; // activate waypointing again
1102 self.bot_basewaypoint = self.nearestwaypoint;
1105 string basename = "base";
1109 case NUM_TEAM_1: basename = "redbase"; break;
1110 case NUM_TEAM_2: basename = "bluebase"; break;
1111 case NUM_TEAM_3: basename = "yellowbase"; break;
1112 case NUM_TEAM_4: basename = "pinkbase"; break;
1113 default: basename = "neutralbase"; break;
1116 WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'));
1117 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, FALSE) : '1 1 1'));
1118 self.wps_flagbase.customizeentityforclient = ctf_FlagWaypoint_Customize;
1120 // captureshield setup
1121 ctf_CaptureShield_Spawn(self);
1124 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1127 self = flag; // for later usage with droptofloor()
1130 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1131 ctf_worldflaglist = flag;
1133 setattachment(flag, world, "");
1135 flag.netname = sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber));
1136 flag.team = teamnumber;
1137 flag.classname = "item_flag_team";
1138 flag.target = "###item###"; // wut?
1139 flag.flags = FL_ITEM | FL_NOTARGET;
1140 flag.solid = SOLID_TRIGGER;
1141 flag.takedamage = DAMAGE_NO;
1142 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1143 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1144 flag.health = flag.max_flag_health;
1145 flag.event_damage = ctf_FlagDamage;
1146 flag.pushable = TRUE;
1147 flag.teleportable = TELEPORT_NORMAL;
1148 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1149 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1150 flag.velocity = '0 0 0';
1151 flag.mangle = flag.angles;
1152 flag.reset = ctf_Reset;
1154 flag.touch = ctf_FlagTouch;
1155 flag.think = ctf_FlagThink;
1156 flag.nextthink = time + FLAG_THINKRATE;
1157 flag.ctf_status = FLAG_BASE;
1160 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 : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_model : autocvar_g_ctf_flag_neutral_model)))); }
1161 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1162 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 : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_skin : autocvar_g_ctf_flag_neutral_skin)))); }
1163 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber == NUM_TEAM_1) ? "redflag_touch" : ((teamnumber == NUM_TEAM_2) ? "blueflag_touch" : ((teamnumber == NUM_TEAM_3) ? "yellowflag_touch" : ((teamnumber == NUM_TEAM_4) ? "pinkflag_touch" : "neutralflag_touch")))); }
1164 if(flag.passeffect == "") { flag.passeffect = ((teamnumber == NUM_TEAM_1) ? "red_pass" : ((teamnumber == NUM_TEAM_2) ? "blue_pass" : ((teamnumber == NUM_TEAM_3) ? "yellow_pass" : ((teamnumber == NUM_TEAM_4) ? "pink_pass" : "neutral_pass")))); }
1165 if(flag.capeffect == "") { flag.capeffect = ((teamnumber == NUM_TEAM_1) ? "red_cap" : ((teamnumber == NUM_TEAM_2) ? "blue_cap" : ((teamnumber == NUM_TEAM_3) ? "yellow_cap" : ((teamnumber == NUM_TEAM_4) ? "pink_cap" : "")))); } // neutral flag cant be capped itself
1166 if(!flag.active) { flag.active = ACTIVE_ACTIVE; }
1169 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" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_taken.wav" : "ctf/neutral_taken.wav")))); }
1170 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" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_returned.wav" : "")))); } // neutral flag can't be returned by players
1171 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" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_capture.wav" : "")))); } // again can't be captured
1172 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.
1173 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" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_dropped.wav" : "ctf/neutral_dropped.wav")))); }
1174 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1175 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1178 precache_sound(flag.snd_flag_taken);
1179 precache_sound(flag.snd_flag_returned);
1180 precache_sound(flag.snd_flag_capture);
1181 precache_sound(flag.snd_flag_respawn);
1182 precache_sound(flag.snd_flag_dropped);
1183 precache_sound(flag.snd_flag_touch);
1184 precache_sound(flag.snd_flag_pass);
1185 precache_model(flag.model);
1186 precache_model("models/ctf/shield.md3");
1187 precache_model("models/ctf/shockwavetransring.md3");
1190 setmodel(flag, flag.model); // precision set below
1191 setsize(flag, FLAG_MIN, FLAG_MAX);
1192 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1194 if(autocvar_g_ctf_flag_glowtrails)
1196 flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254))));
1197 flag.glow_size = 25;
1198 flag.glow_trail = 1;
1201 flag.effects |= EF_LOWPRECISION;
1202 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1203 if(autocvar_g_ctf_dynamiclights)
1207 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1208 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1209 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1210 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1211 default: flag.effects |= EF_DIMLIGHT; break;
1216 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1218 flag.dropped_origin = flag.origin;
1219 flag.noalign = TRUE;
1220 flag.movetype = MOVETYPE_NONE;
1222 else // drop to floor, automatically find a platform and set that as spawn origin
1224 flag.noalign = FALSE;
1227 flag.movetype = MOVETYPE_TOSS;
1230 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1238 // NOTE: LEGACY CODE, needs to be re-written!
1240 void havocbot_calculate_middlepoint()
1244 vector fo = '0 0 0';
1247 f = ctf_worldflaglist;
1252 f = f.ctf_worldflagnext;
1256 havocbot_ctf_middlepoint = s * (1.0 / n);
1257 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1261 entity havocbot_ctf_find_flag(entity bot)
1264 f = ctf_worldflaglist;
1267 if (CTF_SAMETEAM(bot, f))
1269 f = f.ctf_worldflagnext;
1274 entity havocbot_ctf_find_enemy_flag(entity bot)
1277 f = ctf_worldflaglist;
1282 if(CTF_DIFFTEAM(bot, f))
1289 else if(!bot.flagcarried)
1293 else if (CTF_DIFFTEAM(bot, f))
1295 f = f.ctf_worldflagnext;
1300 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1308 FOR_EACH_PLAYER(head)
1310 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1313 if(vlen(head.origin - org) < tc_radius)
1320 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1323 head = ctf_worldflaglist;
1326 if (CTF_SAMETEAM(self, head))
1328 head = head.ctf_worldflagnext;
1331 navigation_routerating(head, ratingscale, 10000);
1334 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1337 head = ctf_worldflaglist;
1340 if (CTF_SAMETEAM(self, head))
1342 head = head.ctf_worldflagnext;
1347 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1350 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1353 head = ctf_worldflaglist;
1358 if(CTF_DIFFTEAM(self, head))
1362 if(self.flagcarried)
1365 else if(!self.flagcarried)
1369 else if(CTF_DIFFTEAM(self, head))
1371 head = head.ctf_worldflagnext;
1374 navigation_routerating(head, ratingscale, 10000);
1377 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1379 if (!bot_waypoints_for_items)
1381 havocbot_goalrating_ctf_enemyflag(ratingscale);
1387 head = havocbot_ctf_find_enemy_flag(self);
1392 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1395 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1399 mf = havocbot_ctf_find_flag(self);
1401 if(mf.ctf_status == FLAG_BASE)
1405 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1408 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1411 head = ctf_worldflaglist;
1414 // flag is out in the field
1415 if(head.ctf_status != FLAG_BASE)
1416 if(head.tag_entity==world) // dropped
1420 if(vlen(org-head.origin)<df_radius)
1421 navigation_routerating(head, ratingscale, 10000);
1424 navigation_routerating(head, ratingscale, 10000);
1427 head = head.ctf_worldflagnext;
1431 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1435 head = findchainfloat(bot_pickup, TRUE);
1438 // gather health and armor only
1440 if (head.health || head.armorvalue)
1441 if (vlen(head.origin - org) < sradius)
1443 // get the value of the item
1444 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1446 navigation_routerating(head, t * ratingscale, 500);
1452 void havocbot_ctf_reset_role(entity bot)
1454 float cdefense, cmiddle, coffense;
1455 entity mf, ef, head;
1458 if(bot.deadflag != DEAD_NO)
1461 if(vlen(havocbot_ctf_middlepoint)==0)
1462 havocbot_calculate_middlepoint();
1465 if (bot.flagcarried)
1467 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1471 mf = havocbot_ctf_find_flag(bot);
1472 ef = havocbot_ctf_find_enemy_flag(bot);
1474 // Retrieve stolen flag
1475 if(mf.ctf_status!=FLAG_BASE)
1477 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1481 // If enemy flag is taken go to the middle to intercept pursuers
1482 if(ef.ctf_status!=FLAG_BASE)
1484 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1488 // if there is only me on the team switch to offense
1490 FOR_EACH_PLAYER(head)
1491 if(SAME_TEAM(head, bot))
1496 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1500 // Evaluate best position to take
1501 // Count mates on middle position
1502 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1504 // Count mates on defense position
1505 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1507 // Count mates on offense position
1508 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1510 if(cdefense<=coffense)
1511 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1512 else if(coffense<=cmiddle)
1513 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1515 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1518 void havocbot_role_ctf_carrier()
1520 if(self.deadflag != DEAD_NO)
1522 havocbot_ctf_reset_role(self);
1526 if (self.flagcarried == world)
1528 havocbot_ctf_reset_role(self);
1532 if (self.bot_strategytime < time)
1534 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1536 navigation_goalrating_start();
1538 havocbot_goalrating_ctf_enemybase(50000);
1540 havocbot_goalrating_ctf_ourbase(50000);
1543 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1545 navigation_goalrating_end();
1547 if (self.navigation_hasgoals)
1548 self.havocbot_cantfindflag = time + 10;
1549 else if (time > self.havocbot_cantfindflag)
1551 // Can't navigate to my own base, suicide!
1552 // TODO: drop it and wander around
1553 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1559 void havocbot_role_ctf_escort()
1563 if(self.deadflag != DEAD_NO)
1565 havocbot_ctf_reset_role(self);
1569 if (self.flagcarried)
1571 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1575 // If enemy flag is back on the base switch to previous role
1576 ef = havocbot_ctf_find_enemy_flag(self);
1577 if(ef.ctf_status==FLAG_BASE)
1579 self.havocbot_role = self.havocbot_previous_role;
1580 self.havocbot_role_timeout = 0;
1584 // If the flag carrier reached the base switch to defense
1585 mf = havocbot_ctf_find_flag(self);
1586 if(mf.ctf_status!=FLAG_BASE)
1587 if(vlen(ef.origin - mf.dropped_origin) < 300)
1589 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1593 // Set the role timeout if necessary
1594 if (!self.havocbot_role_timeout)
1596 self.havocbot_role_timeout = time + random() * 30 + 60;
1599 // If nothing happened just switch to previous role
1600 if (time > self.havocbot_role_timeout)
1602 self.havocbot_role = self.havocbot_previous_role;
1603 self.havocbot_role_timeout = 0;
1607 // Chase the flag carrier
1608 if (self.bot_strategytime < time)
1610 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1611 navigation_goalrating_start();
1612 havocbot_goalrating_ctf_enemyflag(30000);
1613 havocbot_goalrating_ctf_ourstolenflag(40000);
1614 havocbot_goalrating_items(10000, self.origin, 10000);
1615 navigation_goalrating_end();
1619 void havocbot_role_ctf_offense()
1624 if(self.deadflag != DEAD_NO)
1626 havocbot_ctf_reset_role(self);
1630 if (self.flagcarried)
1632 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1637 mf = havocbot_ctf_find_flag(self);
1638 ef = havocbot_ctf_find_enemy_flag(self);
1641 if(mf.ctf_status!=FLAG_BASE)
1644 pos = mf.tag_entity.origin;
1648 // Try to get it if closer than the enemy base
1649 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1651 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1656 // Escort flag carrier
1657 if(ef.ctf_status!=FLAG_BASE)
1660 pos = ef.tag_entity.origin;
1664 if(vlen(pos-mf.dropped_origin)>700)
1666 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1671 // About to fail, switch to middlefield
1674 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1678 // Set the role timeout if necessary
1679 if (!self.havocbot_role_timeout)
1680 self.havocbot_role_timeout = time + 120;
1682 if (time > self.havocbot_role_timeout)
1684 havocbot_ctf_reset_role(self);
1688 if (self.bot_strategytime < time)
1690 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1691 navigation_goalrating_start();
1692 havocbot_goalrating_ctf_ourstolenflag(50000);
1693 havocbot_goalrating_ctf_enemybase(20000);
1694 havocbot_goalrating_items(5000, self.origin, 1000);
1695 havocbot_goalrating_items(1000, self.origin, 10000);
1696 navigation_goalrating_end();
1700 // Retriever (temporary role):
1701 void havocbot_role_ctf_retriever()
1705 if(self.deadflag != DEAD_NO)
1707 havocbot_ctf_reset_role(self);
1711 if (self.flagcarried)
1713 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1717 // If flag is back on the base switch to previous role
1718 mf = havocbot_ctf_find_flag(self);
1719 if(mf.ctf_status==FLAG_BASE)
1721 havocbot_ctf_reset_role(self);
1725 if (!self.havocbot_role_timeout)
1726 self.havocbot_role_timeout = time + 20;
1728 if (time > self.havocbot_role_timeout)
1730 havocbot_ctf_reset_role(self);
1734 if (self.bot_strategytime < time)
1739 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1740 navigation_goalrating_start();
1741 havocbot_goalrating_ctf_ourstolenflag(50000);
1742 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1743 havocbot_goalrating_ctf_enemybase(30000);
1744 havocbot_goalrating_items(500, self.origin, rt_radius);
1745 navigation_goalrating_end();
1749 void havocbot_role_ctf_middle()
1753 if(self.deadflag != DEAD_NO)
1755 havocbot_ctf_reset_role(self);
1759 if (self.flagcarried)
1761 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1765 mf = havocbot_ctf_find_flag(self);
1766 if(mf.ctf_status!=FLAG_BASE)
1768 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1772 if (!self.havocbot_role_timeout)
1773 self.havocbot_role_timeout = time + 10;
1775 if (time > self.havocbot_role_timeout)
1777 havocbot_ctf_reset_role(self);
1781 if (self.bot_strategytime < time)
1785 org = havocbot_ctf_middlepoint;
1786 org_z = self.origin_z;
1788 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1789 navigation_goalrating_start();
1790 havocbot_goalrating_ctf_ourstolenflag(50000);
1791 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1792 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1793 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1794 havocbot_goalrating_items(2500, self.origin, 10000);
1795 havocbot_goalrating_ctf_enemybase(2500);
1796 navigation_goalrating_end();
1800 void havocbot_role_ctf_defense()
1804 if(self.deadflag != DEAD_NO)
1806 havocbot_ctf_reset_role(self);
1810 if (self.flagcarried)
1812 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1816 // If own flag was captured
1817 mf = havocbot_ctf_find_flag(self);
1818 if(mf.ctf_status!=FLAG_BASE)
1820 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1824 if (!self.havocbot_role_timeout)
1825 self.havocbot_role_timeout = time + 30;
1827 if (time > self.havocbot_role_timeout)
1829 havocbot_ctf_reset_role(self);
1832 if (self.bot_strategytime < time)
1837 org = mf.dropped_origin;
1838 mp_radius = havocbot_ctf_middlepoint_radius;
1840 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1841 navigation_goalrating_start();
1843 // if enemies are closer to our base, go there
1844 entity head, closestplayer = world;
1845 float distance, bestdistance = 10000;
1846 FOR_EACH_PLAYER(head)
1848 if(head.deadflag!=DEAD_NO)
1851 distance = vlen(org - head.origin);
1852 if(distance<bestdistance)
1854 closestplayer = head;
1855 bestdistance = distance;
1860 if(DIFF_TEAM(closestplayer, self))
1861 if(vlen(org - self.origin)>1000)
1862 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1863 havocbot_goalrating_ctf_ourbase(30000);
1865 havocbot_goalrating_ctf_ourstolenflag(20000);
1866 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1867 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1868 havocbot_goalrating_items(10000, org, mp_radius);
1869 havocbot_goalrating_items(5000, self.origin, 10000);
1870 navigation_goalrating_end();
1874 void havocbot_role_ctf_setrole(entity bot, float role)
1876 dprint(strcat(bot.netname," switched to "));
1879 case HAVOCBOT_CTF_ROLE_CARRIER:
1881 bot.havocbot_role = havocbot_role_ctf_carrier;
1882 bot.havocbot_role_timeout = 0;
1883 bot.havocbot_cantfindflag = time + 10;
1884 bot.bot_strategytime = 0;
1886 case HAVOCBOT_CTF_ROLE_DEFENSE:
1888 bot.havocbot_role = havocbot_role_ctf_defense;
1889 bot.havocbot_role_timeout = 0;
1891 case HAVOCBOT_CTF_ROLE_MIDDLE:
1893 bot.havocbot_role = havocbot_role_ctf_middle;
1894 bot.havocbot_role_timeout = 0;
1896 case HAVOCBOT_CTF_ROLE_OFFENSE:
1898 bot.havocbot_role = havocbot_role_ctf_offense;
1899 bot.havocbot_role_timeout = 0;
1901 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1902 dprint("retriever");
1903 bot.havocbot_previous_role = bot.havocbot_role;
1904 bot.havocbot_role = havocbot_role_ctf_retriever;
1905 bot.havocbot_role_timeout = time + 10;
1906 bot.bot_strategytime = 0;
1908 case HAVOCBOT_CTF_ROLE_ESCORT:
1910 bot.havocbot_previous_role = bot.havocbot_role;
1911 bot.havocbot_role = havocbot_role_ctf_escort;
1912 bot.havocbot_role_timeout = time + 30;
1913 bot.bot_strategytime = 0;
1924 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1928 float t = 0, t2 = 0, t3 = 0;
1930 // initially clear items so they can be set as necessary later.
1931 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1932 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1933 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1934 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1935 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1936 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1938 // scan through all the flags and notify the client about them
1939 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1941 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1942 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1943 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1944 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1945 if(flag.team == 0) { t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
1947 switch(flag.ctf_status)
1952 if((flag.owner == self) || (flag.pass_sender == self))
1953 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1955 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1960 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1966 // item for stopping players from capturing the flag too often
1967 if(self.ctf_captureshielded)
1968 self.ctf_flagstatus |= CTF_SHIELDED;
1970 // update the health of the flag carrier waypointsprite
1971 if(self.wps_flagcarrier)
1972 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1977 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1979 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1981 if(frag_target == frag_attacker) // damage done to yourself
1983 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1984 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1986 else // damage done to everyone else
1988 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1989 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1992 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1994 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)))
1995 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1997 frag_target.wps_helpme_time = time;
1998 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2000 // todo: add notification for when flag carrier needs help?
2005 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
2007 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2009 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2010 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2013 if(frag_target.flagcarried)
2015 entity tmp_entity = frag_target.flagcarried;
2016 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2017 tmp_entity.ctf_dropper = world;
2023 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
2026 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2029 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
2031 entity flag; // temporary entity for the search method
2033 if(self.flagcarried)
2034 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2036 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2038 if(flag.pass_sender == self) { flag.pass_sender = world; }
2039 if(flag.pass_target == self) { flag.pass_target = world; }
2040 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
2046 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2048 if(self.flagcarried)
2049 if(!autocvar_g_ctf_portalteleport)
2050 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2055 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2057 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
2059 entity player = self;
2061 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2063 // pass the flag to a team mate
2064 if(autocvar_g_ctf_pass)
2066 entity head, closest_target = world;
2067 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
2069 while(head) // find the closest acceptable target to pass to
2071 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2072 if(head != player && SAME_TEAM(head, player))
2073 if(!head.speedrunning && !head.vehicle)
2075 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2076 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2077 vector passer_center = CENTER_OR_VIEWOFS(player);
2079 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2081 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2083 if(IS_BOT_CLIENT(head))
2085 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2086 ctf_Handle_Throw(head, player, DROP_PASS);
2090 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2091 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2093 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2096 else if(player.flagcarried)
2100 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2101 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2102 { closest_target = head; }
2104 else { closest_target = head; }
2111 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
2114 // throw the flag in front of you
2115 if(autocvar_g_ctf_throw && player.flagcarried)
2117 if(player.throw_count == -1)
2119 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2121 player.throw_prevtime = time;
2122 player.throw_count = 1;
2123 ctf_Handle_Throw(player, world, DROP_THROW);
2128 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2134 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2135 else { player.throw_count += 1; }
2136 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2138 player.throw_prevtime = time;
2139 ctf_Handle_Throw(player, world, DROP_THROW);
2148 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2150 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2152 self.wps_helpme_time = time;
2153 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2155 else // create a normal help me waypointsprite
2157 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');
2158 WaypointSprite_Ping(self.wps_helpme);
2164 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2166 if(vh_player.flagcarried)
2168 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2170 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2174 setattachment(vh_player.flagcarried, vh_vehicle, "");
2175 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2176 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2177 //vh_player.flagcarried.angles = '0 0 0';
2185 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2187 if(vh_player.flagcarried)
2189 setattachment(vh_player.flagcarried, vh_player, "");
2190 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2191 vh_player.flagcarried.scale = FLAG_SCALE;
2192 vh_player.flagcarried.angles = '0 0 0';
2199 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2201 if(self.flagcarried)
2203 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2204 ctf_RespawnFlag(self.flagcarried);
2211 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2213 entity flag; // temporary entity for the search method
2215 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2217 switch(flag.ctf_status)
2222 // lock the flag, game is over
2223 flag.movetype = MOVETYPE_NONE;
2224 flag.takedamage = DAMAGE_NO;
2225 flag.solid = SOLID_NOT;
2226 flag.nextthink = FALSE; // stop thinking
2228 //dprint("stopping the ", flag.netname, " from moving.\n");
2236 // do nothing for these flags
2245 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2247 havocbot_ctf_reset_role(self);
2251 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2253 //ret_float = ctf_teams;
2254 ret_string = "ctf_team";
2258 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2260 self.ctf_flagstatus = other.ctf_flagstatus;
2269 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2270 CTF Starting point for a player in team one (Red).
2271 Keys: "angle" viewing angle when spawning. */
2272 void spawnfunc_info_player_team1()
2274 if(g_assault) { remove(self); return; }
2276 self.team = NUM_TEAM_1; // red
2277 spawnfunc_info_player_deathmatch();
2281 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2282 CTF Starting point for a player in team two (Blue).
2283 Keys: "angle" viewing angle when spawning. */
2284 void spawnfunc_info_player_team2()
2286 if(g_assault) { remove(self); return; }
2288 self.team = NUM_TEAM_2; // blue
2289 spawnfunc_info_player_deathmatch();
2292 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2293 CTF Starting point for a player in team three (Yellow).
2294 Keys: "angle" viewing angle when spawning. */
2295 void spawnfunc_info_player_team3()
2297 if(g_assault) { remove(self); return; }
2299 self.team = NUM_TEAM_3; // yellow
2300 spawnfunc_info_player_deathmatch();
2304 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2305 CTF Starting point for a player in team four (Purple).
2306 Keys: "angle" viewing angle when spawning. */
2307 void spawnfunc_info_player_team4()
2309 if(g_assault) { remove(self); return; }
2311 self.team = NUM_TEAM_4; // purple
2312 spawnfunc_info_player_deathmatch();
2315 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2316 CTF flag for team one (Red).
2318 "angle" Angle the flag will point (minus 90 degrees)...
2319 "model" model to use, note this needs red and blue as skins 0 and 1...
2320 "noise" sound played when flag is picked up...
2321 "noise1" sound played when flag is returned by a teammate...
2322 "noise2" sound played when flag is captured...
2323 "noise3" sound played when flag is lost in the field and respawns itself...
2324 "noise4" sound played when flag is dropped by a player...
2325 "noise5" sound played when flag touches the ground... */
2326 void spawnfunc_item_flag_team1()
2328 if(!g_ctf) { remove(self); return; }
2330 ctf_FlagSetup(NUM_TEAM_1, self);
2333 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2334 CTF flag for team two (Blue).
2336 "angle" Angle the flag will point (minus 90 degrees)...
2337 "model" model to use, note this needs red and blue as skins 0 and 1...
2338 "noise" sound played when flag is picked up...
2339 "noise1" sound played when flag is returned by a teammate...
2340 "noise2" sound played when flag is captured...
2341 "noise3" sound played when flag is lost in the field and respawns itself...
2342 "noise4" sound played when flag is dropped by a player...
2343 "noise5" sound played when flag touches the ground... */
2344 void spawnfunc_item_flag_team2()
2346 if(!g_ctf) { remove(self); return; }
2348 ctf_FlagSetup(NUM_TEAM_2, self);
2351 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2352 CTF flag for team three (Yellow).
2354 "angle" Angle the flag will point (minus 90 degrees)...
2355 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2356 "noise" sound played when flag is picked up...
2357 "noise1" sound played when flag is returned by a teammate...
2358 "noise2" sound played when flag is captured...
2359 "noise3" sound played when flag is lost in the field and respawns itself...
2360 "noise4" sound played when flag is dropped by a player...
2361 "noise5" sound played when flag touches the ground... */
2362 void spawnfunc_item_flag_team3()
2364 if(!g_ctf) { remove(self); return; }
2366 ctf_FlagSetup(NUM_TEAM_3, self);
2369 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2370 CTF flag for team four (Pink).
2372 "angle" Angle the flag will point (minus 90 degrees)...
2373 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2374 "noise" sound played when flag is picked up...
2375 "noise1" sound played when flag is returned by a teammate...
2376 "noise2" sound played when flag is captured...
2377 "noise3" sound played when flag is lost in the field and respawns itself...
2378 "noise4" sound played when flag is dropped by a player...
2379 "noise5" sound played when flag touches the ground... */
2380 void spawnfunc_item_flag_team4()
2382 if(!g_ctf) { remove(self); return; }
2384 ctf_FlagSetup(NUM_TEAM_4, self);
2387 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2390 "angle" Angle the flag will point (minus 90 degrees)...
2391 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2392 "noise" sound played when flag is picked up...
2393 "noise1" sound played when flag is returned by a teammate...
2394 "noise2" sound played when flag is captured...
2395 "noise3" sound played when flag is lost in the field and respawns itself...
2396 "noise4" sound played when flag is dropped by a player...
2397 "noise5" sound played when flag touches the ground... */
2398 void spawnfunc_item_flag_neutral()
2400 if(!g_ctf) { remove(self); return; }
2402 ctf_FlagSetup(0, self);
2405 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2406 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2407 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.
2409 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2410 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2411 void spawnfunc_ctf_team()
2413 if(!g_ctf) { remove(self); return; }
2415 self.classname = "ctf_team";
2416 self.team = self.cnt + 1;
2419 // compatibility for quake maps
2420 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2421 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2422 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2423 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2424 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2425 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2433 void ctf_ScoreRules(float teams)
2435 CheckAllowedTeams(world);
2436 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2437 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2438 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2439 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2440 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2441 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2442 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2443 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2444 ScoreRules_basics_end();
2447 // code from here on is just to support maps that don't have flag and team entities
2448 void ctf_SpawnTeam (string teamname, float teamcolor)
2453 self.classname = "ctf_team";
2454 self.netname = teamname;
2455 self.cnt = teamcolor;
2457 spawnfunc_ctf_team();
2462 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2467 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2469 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2470 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2471 if(tmp_entity.team == 0) { ctf_oneflag = TRUE; }
2474 ctf_teams = bound(2, ctf_teams, 4);
2476 // if no teams are found, spawn defaults
2477 if(find(world, classname, "ctf_team") == world)
2479 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2480 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2481 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2483 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2485 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2488 ctf_ScoreRules(ctf_teams);
2491 void ctf_Initialize()
2493 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2495 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2496 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2497 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2499 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2501 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2505 MUTATOR_DEFINITION(gamemode_ctf)
2507 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2508 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2509 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2510 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2511 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2512 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2513 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2514 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2515 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2516 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2517 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2518 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2519 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2520 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2521 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2522 MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2526 if(time > 1) // game loads at time 1
2527 error("This is a game type and it cannot be added at runtime.");
2531 MUTATOR_ONROLLBACK_OR_REMOVE
2533 // we actually cannot roll back ctf_Initialize here
2534 // BUT: we don't need to! If this gets called, adding always
2540 print("This is a game type and it cannot be removed at runtime.");