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, int 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 bool 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 bool 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, bool wanted_status)
169 bool 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 bool ctf_CaptureShield_Customize()
179 if(!other.ctf_captureshielded) { return false; }
180 if(CTF_SAMETEAM(self, other)) { return false; }
185 void ctf_CaptureShield_Touch()
187 if(!other.ctf_captureshielded) { return; }
188 if(CTF_SAMETEAM(self, other)) { return; }
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_SHIELDED); }
197 void ctf_CaptureShield_Spawn(entity flag)
199 entity shield = spawn();
202 shield.team = self.team;
203 shield.touch = ctf_CaptureShield_Touch;
204 shield.customizeentityforclient = ctf_CaptureShield_Customize;
205 shield.classname = "ctf_captureshield";
206 shield.effects = EF_ADDITIVE;
207 shield.movetype = MOVETYPE_NOCLIP;
208 shield.solid = SOLID_TRIGGER;
209 shield.avelocity = '7 0 11';
212 setorigin(shield, self.origin);
213 setmodel(shield, "models/ctf/shield.md3");
214 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
218 // ====================
219 // Drop/Pass/Throw Code
220 // ====================
222 void ctf_Handle_Drop(entity flag, entity player, int droptype)
225 player = (player ? player : flag.pass_sender);
228 flag.movetype = MOVETYPE_TOSS;
229 flag.takedamage = DAMAGE_YES;
230 flag.angles = '0 0 0';
231 flag.health = flag.max_flag_health;
232 flag.ctf_droptime = time;
233 flag.ctf_dropper = player;
234 flag.ctf_status = FLAG_DROPPED;
236 // messages and sounds
237 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
238 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
239 ctf_EventLog("dropped", player.team, player);
242 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
243 PlayerScore_Add(player, SP_CTF_DROPS, 1);
246 if(autocvar_g_ctf_flag_dropped_waypoint)
247 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));
249 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
251 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
252 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
255 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
257 if(droptype == DROP_PASS)
259 flag.pass_distance = 0;
260 flag.pass_sender = world;
261 flag.pass_target = world;
265 void ctf_Handle_Retrieve(entity flag, entity player)
267 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
268 entity sender = flag.pass_sender;
270 // transfer flag to player
272 flag.owner.flagcarried = flag;
277 setattachment(flag, player.vehicle, "");
278 setorigin(flag, VEHICLE_FLAG_OFFSET);
279 flag.scale = VEHICLE_FLAG_SCALE;
283 setattachment(flag, player, "");
284 setorigin(flag, FLAG_CARRY_OFFSET);
286 flag.movetype = MOVETYPE_NONE;
287 flag.takedamage = DAMAGE_NO;
288 flag.solid = SOLID_NOT;
289 flag.angles = '0 0 0';
290 flag.ctf_status = FLAG_CARRY;
292 // messages and sounds
293 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
294 ctf_EventLog("receive", flag.team, player);
296 FOR_EACH_REALPLAYER(tmp_player)
298 if(tmp_player == sender)
299 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);
300 else if(tmp_player == player)
301 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);
302 else if(SAME_TEAM(tmp_player, sender))
303 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);
306 // create new waypoint
307 ctf_FlagcarrierWaypoints(player);
309 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
310 player.throw_antispam = sender.throw_antispam;
312 flag.pass_distance = 0;
313 flag.pass_sender = world;
314 flag.pass_target = world;
317 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
319 entity flag = player.flagcarried;
320 vector targ_origin, flag_velocity;
322 if(!flag) { return; }
323 if((droptype == DROP_PASS) && !receiver) { return; }
325 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
328 setattachment(flag, world, "");
329 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
330 flag.owner.flagcarried = world;
332 flag.solid = SOLID_TRIGGER;
333 flag.ctf_dropper = player;
334 flag.ctf_droptime = time;
336 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
343 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
344 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
345 WarpZone_RefSys_Copy(flag, receiver);
346 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
347 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
349 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
350 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
353 flag.movetype = MOVETYPE_FLY;
354 flag.takedamage = DAMAGE_NO;
355 flag.pass_sender = player;
356 flag.pass_target = receiver;
357 flag.ctf_status = FLAG_PASSING;
360 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
361 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
362 ctf_EventLog("pass", flag.team, player);
368 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'));
370 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)));
371 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
372 ctf_Handle_Drop(flag, player, droptype);
378 flag.velocity = '0 0 0'; // do nothing
385 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);
386 ctf_Handle_Drop(flag, player, droptype);
391 // kill old waypointsprite
392 WaypointSprite_Ping(player.wps_flagcarrier);
393 WaypointSprite_Kill(player.wps_flagcarrier);
395 if(player.wps_enemyflagcarrier)
396 WaypointSprite_Kill(player.wps_enemyflagcarrier);
399 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
407 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
409 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
410 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
411 entity player_team_flag = world, tmp_entity;
412 float old_time, new_time;
414 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
415 if(CTF_DIFFTEAM(player, flag)) { return; }
418 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
419 if(SAME_TEAM(tmp_entity, player))
421 player_team_flag = tmp_entity;
425 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
427 player.throw_prevtime = time;
428 player.throw_count = 0;
430 // messages and sounds
431 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
432 ctf_CaptureRecord(enemy_flag, player);
433 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);
437 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
438 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
443 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
444 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
446 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
447 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
448 if(!old_time || new_time < old_time)
449 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
452 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
453 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
456 if(capturetype == CAPTURE_NORMAL)
458 WaypointSprite_Kill(player.wps_flagcarrier);
459 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
461 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
462 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
466 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
467 ctf_RespawnFlag(enemy_flag);
470 void ctf_Handle_Return(entity flag, entity player)
472 // messages and sounds
473 if(player.flags & FL_MONSTER)
475 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
479 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
480 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
482 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
483 ctf_EventLog("return", flag.team, player);
486 if(IS_PLAYER(player))
488 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
489 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
491 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
494 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
498 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
499 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
500 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
504 if(player.flagcarried == flag)
505 WaypointSprite_Kill(player.wps_flagcarrier);
508 ctf_RespawnFlag(flag);
511 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
514 float pickup_dropped_score; // used to calculate dropped pickup score
515 entity tmp_entity; // temporary entity
517 // attach the flag to the player
519 player.flagcarried = flag;
522 setattachment(flag, player.vehicle, "");
523 setorigin(flag, VEHICLE_FLAG_OFFSET);
524 flag.scale = VEHICLE_FLAG_SCALE;
528 setattachment(flag, player, "");
529 setorigin(flag, FLAG_CARRY_OFFSET);
533 flag.movetype = MOVETYPE_NONE;
534 flag.takedamage = DAMAGE_NO;
535 flag.solid = SOLID_NOT;
536 flag.angles = '0 0 0';
537 flag.ctf_status = FLAG_CARRY;
541 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
542 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
546 // messages and sounds
547 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
548 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
549 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
550 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
551 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)); }
553 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);
556 FOR_EACH_PLAYER(tmp_entity)
557 if(tmp_entity != player)
558 if(DIFF_TEAM(player, tmp_entity))
559 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
562 FOR_EACH_PLAYER(tmp_entity)
563 if(tmp_entity != player)
564 if(CTF_SAMETEAM(flag, tmp_entity))
565 if(SAME_TEAM(player, tmp_entity))
566 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
568 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);
570 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
573 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
574 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
579 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
580 ctf_EventLog("steal", flag.team, player);
586 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);
587 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);
588 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
589 PlayerTeamScore_AddScore(player, pickup_dropped_score);
590 ctf_EventLog("pickup", flag.team, player);
598 if(pickuptype == PICKUP_BASE)
600 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
601 if((player.speedrunning) && (ctf_captimerecord))
602 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
606 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
609 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
610 ctf_FlagcarrierWaypoints(player);
611 WaypointSprite_Ping(player.wps_flagcarrier);
615 // ===================
616 // Main Flag Functions
617 // ===================
619 void ctf_CheckFlagReturn(entity flag, int returntype)
621 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
623 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
625 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
629 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;
630 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;
631 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;
632 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;
636 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
638 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
639 ctf_EventLog("returned", flag.team, world);
640 ctf_RespawnFlag(flag);
645 bool ctf_Stalemate_Customize()
647 // make spectators see what the player would see
649 e = WaypointSprite_getviewentity(other);
650 wp_owner = self.owner;
653 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
654 if(SAME_TEAM(wp_owner, e)) { return false; }
655 if(!IS_PLAYER(e)) { return false; }
660 void ctf_CheckStalemate(void)
663 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
666 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
668 // build list of stale flags
669 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
671 if(autocvar_g_ctf_stalemate)
672 if(tmp_entity.ctf_status != FLAG_BASE)
673 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
675 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
676 ctf_staleflaglist = tmp_entity;
678 switch(tmp_entity.team)
680 case NUM_TEAM_1: ++stale_red_flags; break;
681 case NUM_TEAM_2: ++stale_blue_flags; break;
682 case NUM_TEAM_3: ++stale_yellow_flags; break;
683 case NUM_TEAM_4: ++stale_pink_flags; break;
684 default: ++stale_neutral_flags; break;
690 stale_flags = (stale_neutral_flags >= 1);
692 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
694 if(ctf_oneflag && stale_flags == 1)
695 ctf_stalemate = true;
696 else if(stale_flags == ctf_teams)
697 ctf_stalemate = true;
698 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
699 { ctf_stalemate = false; wpforenemy_announced = false; }
700 else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1)
701 { ctf_stalemate = false; wpforenemy_announced = false; }
703 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
706 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
708 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
710 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));
711 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
715 if (!wpforenemy_announced)
717 FOR_EACH_REALPLAYER(tmp_entity)
718 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
720 wpforenemy_announced = true;
725 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
727 if(ITEM_DAMAGE_NEEDKILL(deathtype))
729 if(autocvar_g_ctf_flag_return_damage_delay)
731 self.ctf_flagdamaged = true;
736 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
740 if(autocvar_g_ctf_flag_return_damage)
742 // reduce health and check if it should be returned
743 self.health = self.health - damage;
744 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
754 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
757 if(self == ctf_worldflaglist) // only for the first flag
758 FOR_EACH_CLIENT(tmp_entity)
759 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
762 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
763 dprint("wtf the flag got squashed?\n");
764 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
765 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
766 setsize(self, FLAG_MIN, FLAG_MAX); }
768 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
772 self.angles = '0 0 0';
780 switch(self.ctf_status)
784 if(autocvar_g_ctf_dropped_capture_radius)
786 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
787 if(tmp_entity.ctf_status == FLAG_DROPPED)
788 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
789 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
790 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
797 if(autocvar_g_ctf_flag_dropped_floatinwater)
799 vector midpoint = ((self.absmin + self.absmax) * 0.5);
800 if(pointcontents(midpoint) == CONTENT_WATER)
802 self.velocity = self.velocity * 0.5;
804 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
805 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
807 { self.movetype = MOVETYPE_FLY; }
809 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
811 if(autocvar_g_ctf_flag_return_dropped)
813 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
816 ctf_CheckFlagReturn(self, RETURN_DROPPED);
820 if(self.ctf_flagdamaged)
822 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
823 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
826 else if(autocvar_g_ctf_flag_return_time)
828 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
829 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
837 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
840 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
844 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
848 if(autocvar_g_ctf_stalemate)
850 if(time >= wpforenemy_nextthink)
852 ctf_CheckStalemate();
853 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
856 if(CTF_SAMETEAM(self, self.owner) && self.team)
858 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
859 ctf_Handle_Throw(self.owner, world, DROP_THROW);
860 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
861 ctf_Handle_Return(self, self.owner);
868 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
869 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
870 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
872 if((self.pass_target == world)
873 || (self.pass_target.deadflag != DEAD_NO)
874 || (self.pass_target.flagcarried)
875 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
876 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
877 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
879 // give up, pass failed
880 ctf_Handle_Drop(self, world, DROP_PASS);
884 // still a viable target, go for it
885 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
890 default: // this should never happen
892 dprint("ctf_FlagThink(): Flag exists with no status?\n");
900 if(gameover) { return; }
901 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
903 entity toucher = other, tmp_entity;
904 bool is_not_monster = (!(toucher.flags & FL_MONSTER)), num_perteam = 0;
906 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
907 if(ITEM_TOUCH_NEEDKILL())
909 if(!autocvar_g_ctf_flag_return_damage_delay)
912 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
914 if(!self.ctf_flagdamaged) { return; }
917 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
919 // special touch behaviors
920 if(toucher.frozen) { return; }
921 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
923 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
924 toucher = toucher.owner; // the player is actually the vehicle owner, not other
926 return; // do nothing
928 else if(toucher.flags & FL_MONSTER)
930 if(!autocvar_g_ctf_allow_monster_touch)
931 return; // do nothing
933 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
935 if(time > self.wait) // if we haven't in a while, play a sound/effect
937 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
938 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
939 self.wait = time + FLAG_TOUCHRATE;
943 else if(toucher.deadflag != DEAD_NO) { return; }
945 switch(self.ctf_status)
951 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
952 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
953 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
954 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
956 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
957 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
958 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
959 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
965 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
966 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
967 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
968 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
974 dprint("Someone touched a flag even though it was being carried?\n");
980 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
982 if(DIFF_TEAM(toucher, self.pass_sender))
983 ctf_Handle_Return(self, toucher);
985 ctf_Handle_Retrieve(self, toucher);
993 void ctf_RespawnFlag(entity flag)
995 // check for flag respawn being called twice in a row
996 if(flag.last_respawn > time - 0.5)
997 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
999 flag.last_respawn = time;
1001 // reset the player (if there is one)
1002 if((flag.owner) && (flag.owner.flagcarried == flag))
1004 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1005 WaypointSprite_Kill(flag.wps_flagcarrier);
1007 flag.owner.flagcarried = world;
1009 if(flag.speedrunning)
1010 ctf_FakeTimeLimit(flag.owner, -1);
1013 if((flag.owner) && (flag.owner.vehicle))
1014 flag.scale = FLAG_SCALE;
1016 if(flag.ctf_status == FLAG_DROPPED)
1017 { WaypointSprite_Kill(flag.wps_flagdropped); }
1020 setattachment(flag, world, "");
1021 setorigin(flag, flag.ctf_spawnorigin);
1023 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1024 flag.takedamage = DAMAGE_NO;
1025 flag.health = flag.max_flag_health;
1026 flag.solid = SOLID_TRIGGER;
1027 flag.velocity = '0 0 0';
1028 flag.angles = flag.mangle;
1029 flag.flags = FL_ITEM | FL_NOTARGET;
1031 flag.ctf_status = FLAG_BASE;
1033 flag.pass_distance = 0;
1034 flag.pass_sender = world;
1035 flag.pass_target = world;
1036 flag.ctf_dropper = world;
1037 flag.ctf_pickuptime = 0;
1038 flag.ctf_droptime = 0;
1039 flag.ctf_flagdamaged = 0;
1041 ctf_CheckStalemate();
1047 if(IS_PLAYER(self.owner))
1048 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1050 ctf_RespawnFlag(self);
1053 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1056 waypoint_spawnforitem_force(self, self.origin);
1057 self.nearestwaypointtimeout = 0; // activate waypointing again
1058 self.bot_basewaypoint = self.nearestwaypoint;
1061 string basename = "base";
1065 case NUM_TEAM_1: basename = "redbase"; break;
1066 case NUM_TEAM_2: basename = "bluebase"; break;
1067 case NUM_TEAM_3: basename = "yellowbase"; break;
1068 case NUM_TEAM_4: basename = "pinkbase"; break;
1069 default: basename = "neutralbase"; break;
1072 WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'));
1073 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1075 // captureshield setup
1076 ctf_CaptureShield_Spawn(self);
1079 void set_flag_string(entity flag, .string field, string value, string teamname)
1081 if(flag.field == "")
1082 flag.field = strzone(sprintf(value,teamname));
1085 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1088 string teamname = Static_Team_ColorName_Lower(teamnumber);
1089 self = flag; // for later usage with droptofloor()
1092 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1093 ctf_worldflaglist = flag;
1095 setattachment(flag, world, "");
1097 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1098 flag.team = teamnumber;
1099 flag.classname = "item_flag_team";
1100 flag.target = "###item###"; // wut?
1101 flag.flags = FL_ITEM | FL_NOTARGET;
1102 flag.solid = SOLID_TRIGGER;
1103 flag.takedamage = DAMAGE_NO;
1104 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1105 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1106 flag.health = flag.max_flag_health;
1107 flag.event_damage = ctf_FlagDamage;
1108 flag.pushable = true;
1109 flag.teleportable = TELEPORT_NORMAL;
1110 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1111 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1112 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1113 flag.velocity = '0 0 0';
1114 flag.mangle = flag.angles;
1115 flag.reset = ctf_Reset;
1116 flag.touch = ctf_FlagTouch;
1117 flag.think = ctf_FlagThink;
1118 flag.nextthink = time + FLAG_THINKRATE;
1119 flag.ctf_status = FLAG_BASE;
1122 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1123 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1124 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1125 set_flag_string(flag, toucheffect, "%sflag_touch", teamname);
1126 set_flag_string(flag, passeffect, "%sflag_pass", teamname);
1127 set_flag_string(flag, capeffect, "%sflag_cap", teamname);
1130 set_flag_string(flag, snd_flag_taken, "ctf/%s_taken.wav", teamname);
1131 set_flag_string(flag, snd_flag_returned, "ctf/%s_returned.wav", teamname);
1132 set_flag_string(flag, snd_flag_capture, "ctf/%s_capture.wav", teamname);
1133 set_flag_string(flag, snd_flag_dropped, "ctf/%s_dropped.wav", teamname);
1134 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.
1135 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1136 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1139 precache_sound(flag.snd_flag_taken);
1140 precache_sound(flag.snd_flag_returned);
1141 precache_sound(flag.snd_flag_capture);
1142 precache_sound(flag.snd_flag_respawn);
1143 precache_sound(flag.snd_flag_dropped);
1144 precache_sound(flag.snd_flag_touch);
1145 precache_sound(flag.snd_flag_pass);
1146 precache_model(flag.model);
1147 precache_model("models/ctf/shield.md3");
1148 precache_model("models/ctf/shockwavetransring.md3");
1151 setmodel(flag, flag.model); // precision set below
1152 setsize(flag, FLAG_MIN, FLAG_MAX);
1153 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1155 if(autocvar_g_ctf_flag_glowtrails)
1159 case NUM_TEAM_1: flag.glow_color = 251; break;
1160 case NUM_TEAM_2: flag.glow_color = 210; break;
1161 case NUM_TEAM_3: flag.glow_color = 110; break;
1162 case NUM_TEAM_4: flag.glow_color = 145; break;
1163 default: flag.glow_color = 254; break;
1165 flag.glow_size = 25;
1166 flag.glow_trail = 1;
1169 flag.effects |= EF_LOWPRECISION;
1170 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1171 if(autocvar_g_ctf_dynamiclights)
1175 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1176 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1177 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1178 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1179 default: flag.effects |= EF_DIMLIGHT; break;
1184 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1186 flag.dropped_origin = flag.origin;
1187 flag.noalign = true;
1188 flag.movetype = MOVETYPE_NONE;
1190 else // drop to floor, automatically find a platform and set that as spawn origin
1192 flag.noalign = false;
1195 flag.movetype = MOVETYPE_TOSS;
1198 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1206 // NOTE: LEGACY CODE, needs to be re-written!
1208 void havocbot_calculate_middlepoint()
1212 vector fo = '0 0 0';
1215 f = ctf_worldflaglist;
1220 f = f.ctf_worldflagnext;
1224 havocbot_ctf_middlepoint = s * (1.0 / n);
1225 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1229 entity havocbot_ctf_find_flag(entity bot)
1232 f = ctf_worldflaglist;
1235 if (CTF_SAMETEAM(bot, f))
1237 f = f.ctf_worldflagnext;
1242 entity havocbot_ctf_find_enemy_flag(entity bot)
1245 f = ctf_worldflaglist;
1250 if(CTF_DIFFTEAM(bot, f))
1257 else if(!bot.flagcarried)
1261 else if (CTF_DIFFTEAM(bot, f))
1263 f = f.ctf_worldflagnext;
1268 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1276 FOR_EACH_PLAYER(head)
1278 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1281 if(vlen(head.origin - org) < tc_radius)
1288 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1291 head = ctf_worldflaglist;
1294 if (CTF_SAMETEAM(self, head))
1296 head = head.ctf_worldflagnext;
1299 navigation_routerating(head, ratingscale, 10000);
1302 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1305 head = ctf_worldflaglist;
1308 if (CTF_SAMETEAM(self, head))
1310 head = head.ctf_worldflagnext;
1315 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1318 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1321 head = ctf_worldflaglist;
1326 if(CTF_DIFFTEAM(self, head))
1330 if(self.flagcarried)
1333 else if(!self.flagcarried)
1337 else if(CTF_DIFFTEAM(self, head))
1339 head = head.ctf_worldflagnext;
1342 navigation_routerating(head, ratingscale, 10000);
1345 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1347 if (!bot_waypoints_for_items)
1349 havocbot_goalrating_ctf_enemyflag(ratingscale);
1355 head = havocbot_ctf_find_enemy_flag(self);
1360 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1363 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1367 mf = havocbot_ctf_find_flag(self);
1369 if(mf.ctf_status == FLAG_BASE)
1373 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1376 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1379 head = ctf_worldflaglist;
1382 // flag is out in the field
1383 if(head.ctf_status != FLAG_BASE)
1384 if(head.tag_entity==world) // dropped
1388 if(vlen(org-head.origin)<df_radius)
1389 navigation_routerating(head, ratingscale, 10000);
1392 navigation_routerating(head, ratingscale, 10000);
1395 head = head.ctf_worldflagnext;
1399 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1403 head = findchainfloat(bot_pickup, true);
1406 // gather health and armor only
1408 if (head.health || head.armorvalue)
1409 if (vlen(head.origin - org) < sradius)
1411 // get the value of the item
1412 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1414 navigation_routerating(head, t * ratingscale, 500);
1420 void havocbot_ctf_reset_role(entity bot)
1422 float cdefense, cmiddle, coffense;
1423 entity mf, ef, head;
1426 if(bot.deadflag != DEAD_NO)
1429 if(vlen(havocbot_ctf_middlepoint)==0)
1430 havocbot_calculate_middlepoint();
1433 if (bot.flagcarried)
1435 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1439 mf = havocbot_ctf_find_flag(bot);
1440 ef = havocbot_ctf_find_enemy_flag(bot);
1442 // Retrieve stolen flag
1443 if(mf.ctf_status!=FLAG_BASE)
1445 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1449 // If enemy flag is taken go to the middle to intercept pursuers
1450 if(ef.ctf_status!=FLAG_BASE)
1452 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1456 // if there is only me on the team switch to offense
1458 FOR_EACH_PLAYER(head)
1459 if(SAME_TEAM(head, bot))
1464 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1468 // Evaluate best position to take
1469 // Count mates on middle position
1470 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1472 // Count mates on defense position
1473 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1475 // Count mates on offense position
1476 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1478 if(cdefense<=coffense)
1479 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1480 else if(coffense<=cmiddle)
1481 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1483 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1486 void havocbot_role_ctf_carrier()
1488 if(self.deadflag != DEAD_NO)
1490 havocbot_ctf_reset_role(self);
1494 if (self.flagcarried == world)
1496 havocbot_ctf_reset_role(self);
1500 if (self.bot_strategytime < time)
1502 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1504 navigation_goalrating_start();
1506 havocbot_goalrating_ctf_enemybase(50000);
1508 havocbot_goalrating_ctf_ourbase(50000);
1511 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1513 navigation_goalrating_end();
1515 if (self.navigation_hasgoals)
1516 self.havocbot_cantfindflag = time + 10;
1517 else if (time > self.havocbot_cantfindflag)
1519 // Can't navigate to my own base, suicide!
1520 // TODO: drop it and wander around
1521 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1527 void havocbot_role_ctf_escort()
1531 if(self.deadflag != DEAD_NO)
1533 havocbot_ctf_reset_role(self);
1537 if (self.flagcarried)
1539 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1543 // If enemy flag is back on the base switch to previous role
1544 ef = havocbot_ctf_find_enemy_flag(self);
1545 if(ef.ctf_status==FLAG_BASE)
1547 self.havocbot_role = self.havocbot_previous_role;
1548 self.havocbot_role_timeout = 0;
1552 // If the flag carrier reached the base switch to defense
1553 mf = havocbot_ctf_find_flag(self);
1554 if(mf.ctf_status!=FLAG_BASE)
1555 if(vlen(ef.origin - mf.dropped_origin) < 300)
1557 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1561 // Set the role timeout if necessary
1562 if (!self.havocbot_role_timeout)
1564 self.havocbot_role_timeout = time + random() * 30 + 60;
1567 // If nothing happened just switch to previous role
1568 if (time > self.havocbot_role_timeout)
1570 self.havocbot_role = self.havocbot_previous_role;
1571 self.havocbot_role_timeout = 0;
1575 // Chase the flag carrier
1576 if (self.bot_strategytime < time)
1578 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1579 navigation_goalrating_start();
1580 havocbot_goalrating_ctf_enemyflag(30000);
1581 havocbot_goalrating_ctf_ourstolenflag(40000);
1582 havocbot_goalrating_items(10000, self.origin, 10000);
1583 navigation_goalrating_end();
1587 void havocbot_role_ctf_offense()
1592 if(self.deadflag != DEAD_NO)
1594 havocbot_ctf_reset_role(self);
1598 if (self.flagcarried)
1600 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1605 mf = havocbot_ctf_find_flag(self);
1606 ef = havocbot_ctf_find_enemy_flag(self);
1609 if(mf.ctf_status!=FLAG_BASE)
1612 pos = mf.tag_entity.origin;
1616 // Try to get it if closer than the enemy base
1617 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1619 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1624 // Escort flag carrier
1625 if(ef.ctf_status!=FLAG_BASE)
1628 pos = ef.tag_entity.origin;
1632 if(vlen(pos-mf.dropped_origin)>700)
1634 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1639 // About to fail, switch to middlefield
1642 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1646 // Set the role timeout if necessary
1647 if (!self.havocbot_role_timeout)
1648 self.havocbot_role_timeout = time + 120;
1650 if (time > self.havocbot_role_timeout)
1652 havocbot_ctf_reset_role(self);
1656 if (self.bot_strategytime < time)
1658 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1659 navigation_goalrating_start();
1660 havocbot_goalrating_ctf_ourstolenflag(50000);
1661 havocbot_goalrating_ctf_enemybase(20000);
1662 havocbot_goalrating_items(5000, self.origin, 1000);
1663 havocbot_goalrating_items(1000, self.origin, 10000);
1664 navigation_goalrating_end();
1668 // Retriever (temporary role):
1669 void havocbot_role_ctf_retriever()
1673 if(self.deadflag != DEAD_NO)
1675 havocbot_ctf_reset_role(self);
1679 if (self.flagcarried)
1681 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1685 // If flag is back on the base switch to previous role
1686 mf = havocbot_ctf_find_flag(self);
1687 if(mf.ctf_status==FLAG_BASE)
1689 havocbot_ctf_reset_role(self);
1693 if (!self.havocbot_role_timeout)
1694 self.havocbot_role_timeout = time + 20;
1696 if (time > self.havocbot_role_timeout)
1698 havocbot_ctf_reset_role(self);
1702 if (self.bot_strategytime < time)
1707 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1708 navigation_goalrating_start();
1709 havocbot_goalrating_ctf_ourstolenflag(50000);
1710 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1711 havocbot_goalrating_ctf_enemybase(30000);
1712 havocbot_goalrating_items(500, self.origin, rt_radius);
1713 navigation_goalrating_end();
1717 void havocbot_role_ctf_middle()
1721 if(self.deadflag != DEAD_NO)
1723 havocbot_ctf_reset_role(self);
1727 if (self.flagcarried)
1729 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1733 mf = havocbot_ctf_find_flag(self);
1734 if(mf.ctf_status!=FLAG_BASE)
1736 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1740 if (!self.havocbot_role_timeout)
1741 self.havocbot_role_timeout = time + 10;
1743 if (time > self.havocbot_role_timeout)
1745 havocbot_ctf_reset_role(self);
1749 if (self.bot_strategytime < time)
1753 org = havocbot_ctf_middlepoint;
1754 org.z = self.origin.z;
1756 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1757 navigation_goalrating_start();
1758 havocbot_goalrating_ctf_ourstolenflag(50000);
1759 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1760 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1761 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1762 havocbot_goalrating_items(2500, self.origin, 10000);
1763 havocbot_goalrating_ctf_enemybase(2500);
1764 navigation_goalrating_end();
1768 void havocbot_role_ctf_defense()
1772 if(self.deadflag != DEAD_NO)
1774 havocbot_ctf_reset_role(self);
1778 if (self.flagcarried)
1780 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1784 // If own flag was captured
1785 mf = havocbot_ctf_find_flag(self);
1786 if(mf.ctf_status!=FLAG_BASE)
1788 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1792 if (!self.havocbot_role_timeout)
1793 self.havocbot_role_timeout = time + 30;
1795 if (time > self.havocbot_role_timeout)
1797 havocbot_ctf_reset_role(self);
1800 if (self.bot_strategytime < time)
1805 org = mf.dropped_origin;
1806 mp_radius = havocbot_ctf_middlepoint_radius;
1808 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1809 navigation_goalrating_start();
1811 // if enemies are closer to our base, go there
1812 entity head, closestplayer = world;
1813 float distance, bestdistance = 10000;
1814 FOR_EACH_PLAYER(head)
1816 if(head.deadflag!=DEAD_NO)
1819 distance = vlen(org - head.origin);
1820 if(distance<bestdistance)
1822 closestplayer = head;
1823 bestdistance = distance;
1828 if(DIFF_TEAM(closestplayer, self))
1829 if(vlen(org - self.origin)>1000)
1830 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1831 havocbot_goalrating_ctf_ourbase(30000);
1833 havocbot_goalrating_ctf_ourstolenflag(20000);
1834 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1835 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1836 havocbot_goalrating_items(10000, org, mp_radius);
1837 havocbot_goalrating_items(5000, self.origin, 10000);
1838 navigation_goalrating_end();
1842 void havocbot_role_ctf_setrole(entity bot, float role)
1844 dprint(strcat(bot.netname," switched to "));
1847 case HAVOCBOT_CTF_ROLE_CARRIER:
1849 bot.havocbot_role = havocbot_role_ctf_carrier;
1850 bot.havocbot_role_timeout = 0;
1851 bot.havocbot_cantfindflag = time + 10;
1852 bot.bot_strategytime = 0;
1854 case HAVOCBOT_CTF_ROLE_DEFENSE:
1856 bot.havocbot_role = havocbot_role_ctf_defense;
1857 bot.havocbot_role_timeout = 0;
1859 case HAVOCBOT_CTF_ROLE_MIDDLE:
1861 bot.havocbot_role = havocbot_role_ctf_middle;
1862 bot.havocbot_role_timeout = 0;
1864 case HAVOCBOT_CTF_ROLE_OFFENSE:
1866 bot.havocbot_role = havocbot_role_ctf_offense;
1867 bot.havocbot_role_timeout = 0;
1869 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1870 dprint("retriever");
1871 bot.havocbot_previous_role = bot.havocbot_role;
1872 bot.havocbot_role = havocbot_role_ctf_retriever;
1873 bot.havocbot_role_timeout = time + 10;
1874 bot.bot_strategytime = 0;
1876 case HAVOCBOT_CTF_ROLE_ESCORT:
1878 bot.havocbot_previous_role = bot.havocbot_role;
1879 bot.havocbot_role = havocbot_role_ctf_escort;
1880 bot.havocbot_role_timeout = time + 30;
1881 bot.bot_strategytime = 0;
1892 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1895 int t = 0, t2 = 0, t3 = 0;
1897 // initially clear items so they can be set as necessary later.
1898 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1899 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1900 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1901 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1902 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1903 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1905 // scan through all the flags and notify the client about them
1906 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1908 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1909 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1910 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1911 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1912 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; }
1914 switch(flag.ctf_status)
1919 if((flag.owner == self) || (flag.pass_sender == self))
1920 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1922 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1927 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1933 // item for stopping players from capturing the flag too often
1934 if(self.ctf_captureshielded)
1935 self.ctf_flagstatus |= CTF_SHIELDED;
1937 // update the health of the flag carrier waypointsprite
1938 if(self.wps_flagcarrier)
1939 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1944 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1946 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1948 if(frag_target == frag_attacker) // damage done to yourself
1950 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1951 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1953 else // damage done to everyone else
1955 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1956 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1959 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1961 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)))
1962 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1964 frag_target.wps_helpme_time = time;
1965 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1967 // todo: add notification for when flag carrier needs help?
1972 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1974 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1976 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1977 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1980 if(frag_target.flagcarried)
1982 entity tmp_entity = frag_target.flagcarried;
1983 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
1984 tmp_entity.ctf_dropper = world;
1990 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1993 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1996 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1998 entity flag; // temporary entity for the search method
2000 if(self.flagcarried)
2001 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2003 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2005 if(flag.pass_sender == self) { flag.pass_sender = world; }
2006 if(flag.pass_target == self) { flag.pass_target = world; }
2007 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
2013 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2015 if(self.flagcarried)
2016 if(!autocvar_g_ctf_portalteleport)
2017 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2022 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2024 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2026 entity player = self;
2028 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2030 // pass the flag to a team mate
2031 if(autocvar_g_ctf_pass)
2033 entity head, closest_target = world;
2034 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2036 while(head) // find the closest acceptable target to pass to
2038 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2039 if(head != player && SAME_TEAM(head, player))
2040 if(!head.speedrunning && !head.vehicle)
2042 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2043 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2044 vector passer_center = CENTER_OR_VIEWOFS(player);
2046 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2048 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2050 if(IS_BOT_CLIENT(head))
2052 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2053 ctf_Handle_Throw(head, player, DROP_PASS);
2057 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2058 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2060 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2063 else if(player.flagcarried)
2067 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2068 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2069 { closest_target = head; }
2071 else { closest_target = head; }
2078 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2081 // throw the flag in front of you
2082 if(autocvar_g_ctf_throw && player.flagcarried)
2084 if(player.throw_count == -1)
2086 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2088 player.throw_prevtime = time;
2089 player.throw_count = 1;
2090 ctf_Handle_Throw(player, world, DROP_THROW);
2095 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2101 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2102 else { player.throw_count += 1; }
2103 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2105 player.throw_prevtime = time;
2106 ctf_Handle_Throw(player, world, DROP_THROW);
2115 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2117 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2119 self.wps_helpme_time = time;
2120 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2122 else // create a normal help me waypointsprite
2124 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');
2125 WaypointSprite_Ping(self.wps_helpme);
2131 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2133 if(vh_player.flagcarried)
2135 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2137 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2141 setattachment(vh_player.flagcarried, vh_vehicle, "");
2142 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2143 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2144 //vh_player.flagcarried.angles = '0 0 0';
2152 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2154 if(vh_player.flagcarried)
2156 setattachment(vh_player.flagcarried, vh_player, "");
2157 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2158 vh_player.flagcarried.scale = FLAG_SCALE;
2159 vh_player.flagcarried.angles = '0 0 0';
2166 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2168 if(self.flagcarried)
2170 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));
2171 ctf_RespawnFlag(self.flagcarried);
2178 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2180 entity flag; // temporary entity for the search method
2182 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2184 switch(flag.ctf_status)
2189 // lock the flag, game is over
2190 flag.movetype = MOVETYPE_NONE;
2191 flag.takedamage = DAMAGE_NO;
2192 flag.solid = SOLID_NOT;
2193 flag.nextthink = false; // stop thinking
2195 //dprint("stopping the ", flag.netname, " from moving.\n");
2203 // do nothing for these flags
2212 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2214 havocbot_ctf_reset_role(self);
2218 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2220 //ret_float = ctf_teams;
2221 ret_string = "ctf_team";
2225 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2227 self.ctf_flagstatus = other.ctf_flagstatus;
2236 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2237 CTF Starting point for a player in team one (Red).
2238 Keys: "angle" viewing angle when spawning. */
2239 void spawnfunc_info_player_team1()
2241 if(g_assault) { remove(self); return; }
2243 self.team = NUM_TEAM_1; // red
2244 spawnfunc_info_player_deathmatch();
2248 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2249 CTF Starting point for a player in team two (Blue).
2250 Keys: "angle" viewing angle when spawning. */
2251 void spawnfunc_info_player_team2()
2253 if(g_assault) { remove(self); return; }
2255 self.team = NUM_TEAM_2; // blue
2256 spawnfunc_info_player_deathmatch();
2259 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2260 CTF Starting point for a player in team three (Yellow).
2261 Keys: "angle" viewing angle when spawning. */
2262 void spawnfunc_info_player_team3()
2264 if(g_assault) { remove(self); return; }
2266 self.team = NUM_TEAM_3; // yellow
2267 spawnfunc_info_player_deathmatch();
2271 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2272 CTF Starting point for a player in team four (Purple).
2273 Keys: "angle" viewing angle when spawning. */
2274 void spawnfunc_info_player_team4()
2276 if(g_assault) { remove(self); return; }
2278 self.team = NUM_TEAM_4; // purple
2279 spawnfunc_info_player_deathmatch();
2282 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2283 CTF flag for team one (Red).
2285 "angle" Angle the flag will point (minus 90 degrees)...
2286 "model" model to use, note this needs red and blue as skins 0 and 1...
2287 "noise" sound played when flag is picked up...
2288 "noise1" sound played when flag is returned by a teammate...
2289 "noise2" sound played when flag is captured...
2290 "noise3" sound played when flag is lost in the field and respawns itself...
2291 "noise4" sound played when flag is dropped by a player...
2292 "noise5" sound played when flag touches the ground... */
2293 void spawnfunc_item_flag_team1()
2295 if(!g_ctf) { remove(self); return; }
2297 ctf_FlagSetup(NUM_TEAM_1, self);
2300 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2301 CTF flag for team two (Blue).
2303 "angle" Angle the flag will point (minus 90 degrees)...
2304 "model" model to use, note this needs red and blue as skins 0 and 1...
2305 "noise" sound played when flag is picked up...
2306 "noise1" sound played when flag is returned by a teammate...
2307 "noise2" sound played when flag is captured...
2308 "noise3" sound played when flag is lost in the field and respawns itself...
2309 "noise4" sound played when flag is dropped by a player...
2310 "noise5" sound played when flag touches the ground... */
2311 void spawnfunc_item_flag_team2()
2313 if(!g_ctf) { remove(self); return; }
2315 ctf_FlagSetup(NUM_TEAM_2, self);
2318 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2319 CTF flag for team three (Yellow).
2321 "angle" Angle the flag will point (minus 90 degrees)...
2322 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2323 "noise" sound played when flag is picked up...
2324 "noise1" sound played when flag is returned by a teammate...
2325 "noise2" sound played when flag is captured...
2326 "noise3" sound played when flag is lost in the field and respawns itself...
2327 "noise4" sound played when flag is dropped by a player...
2328 "noise5" sound played when flag touches the ground... */
2329 void spawnfunc_item_flag_team3()
2331 if(!g_ctf) { remove(self); return; }
2333 ctf_FlagSetup(NUM_TEAM_3, self);
2336 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2337 CTF flag for team four (Pink).
2339 "angle" Angle the flag will point (minus 90 degrees)...
2340 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2341 "noise" sound played when flag is picked up...
2342 "noise1" sound played when flag is returned by a teammate...
2343 "noise2" sound played when flag is captured...
2344 "noise3" sound played when flag is lost in the field and respawns itself...
2345 "noise4" sound played when flag is dropped by a player...
2346 "noise5" sound played when flag touches the ground... */
2347 void spawnfunc_item_flag_team4()
2349 if(!g_ctf) { remove(self); return; }
2351 ctf_FlagSetup(NUM_TEAM_4, self);
2354 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2357 "angle" Angle the flag will point (minus 90 degrees)...
2358 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2359 "noise" sound played when flag is picked up...
2360 "noise1" sound played when flag is returned by a teammate...
2361 "noise2" sound played when flag is captured...
2362 "noise3" sound played when flag is lost in the field and respawns itself...
2363 "noise4" sound played when flag is dropped by a player...
2364 "noise5" sound played when flag touches the ground... */
2365 void spawnfunc_item_flag_neutral()
2367 if(!g_ctf) { remove(self); return; }
2368 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2370 ctf_FlagSetup(0, self);
2373 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2374 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2375 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.
2377 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2378 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2379 void spawnfunc_ctf_team()
2381 if(!g_ctf) { remove(self); return; }
2383 self.classname = "ctf_team";
2384 self.team = self.cnt + 1;
2387 // compatibility for quake maps
2388 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2389 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2390 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2391 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2392 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2393 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2401 void ctf_ScoreRules(float teams)
2403 CheckAllowedTeams(world);
2404 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2405 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2406 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2407 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2408 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2409 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2410 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2411 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2412 ScoreRules_basics_end();
2415 // code from here on is just to support maps that don't have flag and team entities
2416 void ctf_SpawnTeam (string teamname, float teamcolor)
2421 self.classname = "ctf_team";
2422 self.netname = teamname;
2423 self.cnt = teamcolor;
2425 spawnfunc_ctf_team();
2430 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2435 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2437 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2438 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2439 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2442 ctf_teams = bound(2, ctf_teams, 4);
2444 // if no teams are found, spawn defaults
2445 if(find(world, classname, "ctf_team") == world)
2447 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2448 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2449 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2451 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2453 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2456 ctf_ScoreRules(ctf_teams);
2459 void ctf_Initialize()
2461 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2463 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2464 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2465 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2467 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2469 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2473 MUTATOR_DEFINITION(gamemode_ctf)
2475 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2476 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2477 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2478 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2479 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2480 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2481 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2482 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2483 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2484 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2485 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2486 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2487 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2488 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2489 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2490 MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2494 if(time > 1) // game loads at time 1
2495 error("This is a game type and it cannot be added at runtime.");
2499 MUTATOR_ONROLLBACK_OR_REMOVE
2501 // we actually cannot roll back ctf_Initialize here
2502 // BUT: we don't need to! If this gets called, adding always
2508 print("This is a game type and it cannot be removed at runtime.");