1 #include "gamemode_ctf.qh"
7 #include "../../common/vehicles/all.qh"
10 #include "../../warpzonelib/common.qh"
12 void ctf_FakeTimeLimit(entity e, float t)
15 WriteByte(MSG_ONE, 3); // svc_updatestat
16 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
18 WriteCoord(MSG_ONE, autocvar_timelimit);
20 WriteCoord(MSG_ONE, (t + 1) / 60);
23 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
25 if(autocvar_sv_eventlog)
26 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
27 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
30 void ctf_CaptureRecord(entity flag, entity player)
32 float cap_record = ctf_captimerecord;
33 float cap_time = (time - flag.ctf_pickuptime);
34 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
37 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
38 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)); }
39 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)); }
40 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)); }
42 // write that shit in the database
43 if(!ctf_oneflag) // but not in 1-flag mode
44 if((!ctf_captimerecord) || (cap_time < cap_record))
46 ctf_captimerecord = cap_time;
47 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
48 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
49 write_recordmarker(player, (time - cap_time), cap_time);
53 void ctf_FlagcarrierWaypoints(entity player)
55 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
56 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
57 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
58 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
61 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
63 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
64 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
65 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
66 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
69 if(current_height) // make sure we can actually do this arcing path
71 targpos = (to + ('0 0 1' * current_height));
72 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
73 if(trace_fraction < 1)
75 //print("normal arc line failed, trying to find new pos...");
76 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
77 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
78 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
79 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
80 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
83 else { targpos = to; }
85 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
87 vector desired_direction = normalize(targpos - from);
88 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
89 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
92 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
94 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
96 // directional tracing only
98 makevectors(passer_angle);
100 // find the closest point on the enemy to the center of the attack
101 float h; // hypotenuse, which is the distance between attacker to head
102 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
104 h = vlen(head_center - passer_center);
105 a = h * (normalize(head_center - passer_center) * v_forward);
107 vector nearest_on_line = (passer_center + a * v_forward);
108 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
110 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
111 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
113 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
118 else { return true; }
122 // =======================
123 // CaptureShield Functions
124 // =======================
126 bool ctf_CaptureShield_CheckStatus(entity p)
128 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
130 int players_worseeq, players_total;
132 if(ctf_captureshield_max_ratio <= 0)
135 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
136 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
137 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
138 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
140 sr = ((s - s2) + (s3 + s4));
142 if(sr >= -ctf_captureshield_min_negscore)
145 players_total = players_worseeq = 0;
150 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
151 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
152 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
153 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
155 ser = ((se - se2) + (se3 + se4));
162 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
163 // use this rule here
165 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
171 void ctf_CaptureShield_Update(entity player, bool wanted_status)
173 bool updated_status = ctf_CaptureShield_CheckStatus(player);
174 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
176 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
177 player.ctf_captureshielded = updated_status;
181 bool ctf_CaptureShield_Customize()
183 if(!other.ctf_captureshielded) { return false; }
184 if(CTF_SAMETEAM(self, other)) { return false; }
189 void ctf_CaptureShield_Touch()
191 if(!other.ctf_captureshielded) { return; }
192 if(CTF_SAMETEAM(self, other)) { return; }
194 vector mymid = (self.absmin + self.absmax) * 0.5;
195 vector othermid = (other.absmin + other.absmax) * 0.5;
197 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
198 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
201 void ctf_CaptureShield_Spawn(entity flag)
203 entity shield = spawn();
206 shield.team = self.team;
207 shield.touch = ctf_CaptureShield_Touch;
208 shield.customizeentityforclient = ctf_CaptureShield_Customize;
209 shield.classname = "ctf_captureshield";
210 shield.effects = EF_ADDITIVE;
211 shield.movetype = MOVETYPE_NOCLIP;
212 shield.solid = SOLID_TRIGGER;
213 shield.avelocity = '7 0 11';
216 setorigin(shield, self.origin);
217 setmodel(shield, MDL_CTF_SHIELD);
218 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
222 // ====================
223 // Drop/Pass/Throw Code
224 // ====================
226 void ctf_Handle_Drop(entity flag, entity player, int droptype)
229 player = (player ? player : flag.pass_sender);
232 flag.movetype = MOVETYPE_TOSS;
233 flag.takedamage = DAMAGE_YES;
234 flag.angles = '0 0 0';
235 flag.health = flag.max_flag_health;
236 flag.ctf_droptime = time;
237 flag.ctf_dropper = player;
238 flag.ctf_status = FLAG_DROPPED;
240 // messages and sounds
241 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
242 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
243 ctf_EventLog("dropped", player.team, player);
246 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
247 PlayerScore_Add(player, SP_CTF_DROPS, 1);
250 if(autocvar_g_ctf_flag_dropped_waypoint) {
251 entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
252 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
255 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
257 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
258 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
261 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
263 if(droptype == DROP_PASS)
265 flag.pass_distance = 0;
266 flag.pass_sender = world;
267 flag.pass_target = world;
271 void ctf_Handle_Retrieve(entity flag, entity player)
273 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
274 entity sender = flag.pass_sender;
276 // transfer flag to player
278 flag.owner.flagcarried = flag;
283 setattachment(flag, player.vehicle, "");
284 setorigin(flag, VEHICLE_FLAG_OFFSET);
285 flag.scale = VEHICLE_FLAG_SCALE;
289 setattachment(flag, player, "");
290 setorigin(flag, FLAG_CARRY_OFFSET);
292 flag.movetype = MOVETYPE_NONE;
293 flag.takedamage = DAMAGE_NO;
294 flag.solid = SOLID_NOT;
295 flag.angles = '0 0 0';
296 flag.ctf_status = FLAG_CARRY;
298 // messages and sounds
299 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
300 ctf_EventLog("receive", flag.team, player);
302 FOR_EACH_REALPLAYER(tmp_player)
304 if(tmp_player == sender)
305 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);
306 else if(tmp_player == player)
307 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);
308 else if(SAME_TEAM(tmp_player, sender))
309 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);
312 // create new waypoint
313 ctf_FlagcarrierWaypoints(player);
315 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
316 player.throw_antispam = sender.throw_antispam;
318 flag.pass_distance = 0;
319 flag.pass_sender = world;
320 flag.pass_target = world;
323 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
325 entity flag = player.flagcarried;
326 vector targ_origin, flag_velocity;
328 if(!flag) { return; }
329 if((droptype == DROP_PASS) && !receiver) { return; }
331 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
334 setattachment(flag, world, "");
335 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
336 flag.owner.flagcarried = world;
338 flag.solid = SOLID_TRIGGER;
339 flag.ctf_dropper = player;
340 flag.ctf_droptime = time;
342 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
349 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
350 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
351 WarpZone_RefSys_Copy(flag, receiver);
352 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
353 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
355 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
356 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
359 flag.movetype = MOVETYPE_FLY;
360 flag.takedamage = DAMAGE_NO;
361 flag.pass_sender = player;
362 flag.pass_target = receiver;
363 flag.ctf_status = FLAG_PASSING;
366 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
367 WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
368 ctf_EventLog("pass", flag.team, player);
374 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'));
376 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
377 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
378 ctf_Handle_Drop(flag, player, droptype);
384 flag.velocity = '0 0 0'; // do nothing
391 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);
392 ctf_Handle_Drop(flag, player, droptype);
397 // kill old waypointsprite
398 WaypointSprite_Ping(player.wps_flagcarrier);
399 WaypointSprite_Kill(player.wps_flagcarrier);
401 if(player.wps_enemyflagcarrier)
402 WaypointSprite_Kill(player.wps_enemyflagcarrier);
405 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
413 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
415 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
416 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
417 entity player_team_flag = world, tmp_entity;
418 float old_time, new_time;
420 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
421 if(CTF_DIFFTEAM(player, flag)) { return; }
424 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
425 if(SAME_TEAM(tmp_entity, player))
427 player_team_flag = tmp_entity;
431 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
433 player.throw_prevtime = time;
434 player.throw_count = 0;
436 // messages and sounds
437 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
438 ctf_CaptureRecord(enemy_flag, player);
439 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);
443 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
444 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
449 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
450 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
452 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
453 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
454 if(!old_time || new_time < old_time)
455 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
458 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
459 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
462 if(capturetype == CAPTURE_NORMAL)
464 WaypointSprite_Kill(player.wps_flagcarrier);
465 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
467 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
468 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
472 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
473 ctf_RespawnFlag(enemy_flag);
476 void ctf_Handle_Return(entity flag, entity player)
478 // messages and sounds
479 if(IS_MONSTER(player))
481 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
485 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
486 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
488 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
489 ctf_EventLog("return", flag.team, player);
492 if(IS_PLAYER(player))
494 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
495 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
497 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
500 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
504 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
505 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
506 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
510 if(player.flagcarried == flag)
511 WaypointSprite_Kill(player.wps_flagcarrier);
514 ctf_RespawnFlag(flag);
517 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
520 float pickup_dropped_score; // used to calculate dropped pickup score
521 entity tmp_entity; // temporary entity
523 // attach the flag to the player
525 player.flagcarried = flag;
528 setattachment(flag, player.vehicle, "");
529 setorigin(flag, VEHICLE_FLAG_OFFSET);
530 flag.scale = VEHICLE_FLAG_SCALE;
534 setattachment(flag, player, "");
535 setorigin(flag, FLAG_CARRY_OFFSET);
539 flag.movetype = MOVETYPE_NONE;
540 flag.takedamage = DAMAGE_NO;
541 flag.solid = SOLID_NOT;
542 flag.angles = '0 0 0';
543 flag.ctf_status = FLAG_CARRY;
547 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
548 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
552 // messages and sounds
553 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
554 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
555 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
556 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
557 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)); }
559 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);
562 FOR_EACH_PLAYER(tmp_entity)
563 if(tmp_entity != player)
564 if(DIFF_TEAM(player, tmp_entity))
565 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
568 FOR_EACH_PLAYER(tmp_entity)
569 if(tmp_entity != player)
570 if(CTF_SAMETEAM(flag, tmp_entity))
571 if(SAME_TEAM(player, tmp_entity))
572 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
574 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);
576 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
579 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
580 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
585 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
586 ctf_EventLog("steal", flag.team, player);
592 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);
593 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);
594 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
595 PlayerTeamScore_AddScore(player, pickup_dropped_score);
596 ctf_EventLog("pickup", flag.team, player);
604 if(pickuptype == PICKUP_BASE)
606 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
607 if((player.speedrunning) && (ctf_captimerecord))
608 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
612 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
615 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
616 ctf_FlagcarrierWaypoints(player);
617 WaypointSprite_Ping(player.wps_flagcarrier);
621 // ===================
622 // Main Flag Functions
623 // ===================
625 void ctf_CheckFlagReturn(entity flag, int returntype)
627 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
629 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
631 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
635 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;
636 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;
637 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;
638 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;
642 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
644 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
645 ctf_EventLog("returned", flag.team, world);
646 ctf_RespawnFlag(flag);
651 bool ctf_Stalemate_Customize()
653 // make spectators see what the player would see
655 e = WaypointSprite_getviewentity(other);
656 wp_owner = self.owner;
659 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
660 if(SAME_TEAM(wp_owner, e)) { return false; }
661 if(!IS_PLAYER(e)) { return false; }
666 void ctf_CheckStalemate(void)
669 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
672 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
674 // build list of stale flags
675 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
677 if(autocvar_g_ctf_stalemate)
678 if(tmp_entity.ctf_status != FLAG_BASE)
679 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
681 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
682 ctf_staleflaglist = tmp_entity;
684 switch(tmp_entity.team)
686 case NUM_TEAM_1: ++stale_red_flags; break;
687 case NUM_TEAM_2: ++stale_blue_flags; break;
688 case NUM_TEAM_3: ++stale_yellow_flags; break;
689 case NUM_TEAM_4: ++stale_pink_flags; break;
690 default: ++stale_neutral_flags; break;
696 stale_flags = (stale_neutral_flags >= 1);
698 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
700 if(ctf_oneflag && stale_flags == 1)
701 ctf_stalemate = true;
702 else if(stale_flags >= 2)
703 ctf_stalemate = true;
704 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
705 { ctf_stalemate = false; wpforenemy_announced = false; }
706 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
707 { ctf_stalemate = false; wpforenemy_announced = false; }
709 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
712 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
714 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
716 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
717 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
718 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
722 if (!wpforenemy_announced)
724 FOR_EACH_REALPLAYER(tmp_entity)
725 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
727 wpforenemy_announced = true;
732 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
734 if(ITEM_DAMAGE_NEEDKILL(deathtype))
736 if(autocvar_g_ctf_flag_return_damage_delay)
738 self.ctf_flagdamaged = true;
743 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
747 if(autocvar_g_ctf_flag_return_damage)
749 // reduce health and check if it should be returned
750 self.health = self.health - damage;
751 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
761 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
764 if(self == ctf_worldflaglist) // only for the first flag
765 FOR_EACH_CLIENT(tmp_entity)
766 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
769 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
770 LOG_TRACE("wtf the flag got squashed?\n");
771 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
772 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
773 setsize(self, FLAG_MIN, FLAG_MAX); }
775 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
779 self.angles = '0 0 0';
787 switch(self.ctf_status)
791 if(autocvar_g_ctf_dropped_capture_radius)
793 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
794 if(tmp_entity.ctf_status == FLAG_DROPPED)
795 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
796 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
797 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
804 if(autocvar_g_ctf_flag_dropped_floatinwater)
806 vector midpoint = ((self.absmin + self.absmax) * 0.5);
807 if(pointcontents(midpoint) == CONTENT_WATER)
809 self.velocity = self.velocity * 0.5;
811 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
812 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
814 { self.movetype = MOVETYPE_FLY; }
816 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
818 if(autocvar_g_ctf_flag_return_dropped)
820 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
823 ctf_CheckFlagReturn(self, RETURN_DROPPED);
827 if(self.ctf_flagdamaged)
829 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
830 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
833 else if(autocvar_g_ctf_flag_return_time)
835 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
836 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
844 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
847 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
850 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
854 if(autocvar_g_ctf_stalemate)
856 if(time >= wpforenemy_nextthink)
858 ctf_CheckStalemate();
859 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
862 if(CTF_SAMETEAM(self, self.owner) && self.team)
864 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
865 ctf_Handle_Throw(self.owner, world, DROP_THROW);
866 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
867 ctf_Handle_Return(self, self.owner);
874 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
875 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
876 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
878 if((self.pass_target == world)
879 || (self.pass_target.deadflag != DEAD_NO)
880 || (self.pass_target.flagcarried)
881 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
882 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
883 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
885 // give up, pass failed
886 ctf_Handle_Drop(self, world, DROP_PASS);
890 // still a viable target, go for it
891 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
896 default: // this should never happen
898 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
906 if(gameover) { return; }
907 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
909 entity toucher = other, tmp_entity;
910 bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
912 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
913 if(ITEM_TOUCH_NEEDKILL())
915 if(!autocvar_g_ctf_flag_return_damage_delay)
918 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
920 if(!self.ctf_flagdamaged) { return; }
923 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
925 // special touch behaviors
926 if(toucher.frozen) { return; }
927 else if(IS_VEHICLE(toucher))
929 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
930 toucher = toucher.owner; // the player is actually the vehicle owner, not other
932 return; // do nothing
934 else if(IS_MONSTER(toucher))
936 if(!autocvar_g_ctf_allow_monster_touch)
937 return; // do nothing
939 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
941 if(time > self.wait) // if we haven't in a while, play a sound/effect
943 Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1);
944 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
945 self.wait = time + FLAG_TOUCHRATE;
949 else if(toucher.deadflag != DEAD_NO) { return; }
951 switch(self.ctf_status)
957 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
958 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
959 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
960 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
962 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
963 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
964 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
965 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
971 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
972 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
973 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
974 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
980 LOG_TRACE("Someone touched a flag even though it was being carried?\n");
986 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
988 if(DIFF_TEAM(toucher, self.pass_sender))
989 ctf_Handle_Return(self, toucher);
991 ctf_Handle_Retrieve(self, toucher);
999 void ctf_RespawnFlag(entity flag)
1001 // check for flag respawn being called twice in a row
1002 if(flag.last_respawn > time - 0.5)
1003 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1005 flag.last_respawn = time;
1007 // reset the player (if there is one)
1008 if((flag.owner) && (flag.owner.flagcarried == flag))
1010 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1011 WaypointSprite_Kill(flag.wps_flagcarrier);
1013 flag.owner.flagcarried = world;
1015 if(flag.speedrunning)
1016 ctf_FakeTimeLimit(flag.owner, -1);
1019 if((flag.owner) && (flag.owner.vehicle))
1020 flag.scale = FLAG_SCALE;
1022 if(flag.ctf_status == FLAG_DROPPED)
1023 { WaypointSprite_Kill(flag.wps_flagdropped); }
1026 setattachment(flag, world, "");
1027 setorigin(flag, flag.ctf_spawnorigin);
1029 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1030 flag.takedamage = DAMAGE_NO;
1031 flag.health = flag.max_flag_health;
1032 flag.solid = SOLID_TRIGGER;
1033 flag.velocity = '0 0 0';
1034 flag.angles = flag.mangle;
1035 flag.flags = FL_ITEM | FL_NOTARGET;
1037 flag.ctf_status = FLAG_BASE;
1039 flag.pass_distance = 0;
1040 flag.pass_sender = world;
1041 flag.pass_target = world;
1042 flag.ctf_dropper = world;
1043 flag.ctf_pickuptime = 0;
1044 flag.ctf_droptime = 0;
1045 flag.ctf_flagdamaged = 0;
1047 ctf_CheckStalemate();
1053 if(IS_PLAYER(self.owner))
1054 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1056 ctf_RespawnFlag(self);
1059 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1062 waypoint_spawnforitem_force(self, self.origin);
1063 self.nearestwaypointtimeout = 0; // activate waypointing again
1064 self.bot_basewaypoint = self.nearestwaypoint;
1070 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1071 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1072 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1073 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1074 default: basename = WP_FlagBaseNeutral; break;
1077 entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
1078 wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
1079 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1081 // captureshield setup
1082 ctf_CaptureShield_Spawn(self);
1085 void set_flag_string(entity flag, .string field, string value, string teamname)
1087 if(flag.(field) == "")
1088 flag.(field) = strzone(sprintf(value,teamname));
1091 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1094 string teamname = Static_Team_ColorName_Lower(teamnumber);
1095 setself(flag); // for later usage with droptofloor()
1098 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1099 ctf_worldflaglist = flag;
1101 setattachment(flag, world, "");
1103 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1104 flag.team = teamnumber;
1105 flag.classname = "item_flag_team";
1106 flag.target = "###item###"; // wut?
1107 flag.flags = FL_ITEM | FL_NOTARGET;
1108 flag.solid = SOLID_TRIGGER;
1109 flag.takedamage = DAMAGE_NO;
1110 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1111 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1112 flag.health = flag.max_flag_health;
1113 flag.event_damage = ctf_FlagDamage;
1114 flag.pushable = true;
1115 flag.teleportable = TELEPORT_NORMAL;
1116 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1117 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1118 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1119 flag.velocity = '0 0 0';
1120 flag.mangle = flag.angles;
1121 flag.reset = ctf_Reset;
1122 flag.touch = ctf_FlagTouch;
1123 flag.think = ctf_FlagThink;
1124 flag.nextthink = time + FLAG_THINKRATE;
1125 flag.ctf_status = FLAG_BASE;
1128 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1129 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1130 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1131 set_flag_string(flag, toucheffect, "%sflag_touch", teamname);
1132 set_flag_string(flag, passeffect, "%s_pass", teamname);
1133 set_flag_string(flag, capeffect, "%s_cap", teamname);
1136 set_flag_string(flag, snd_flag_taken, "ctf/%s_taken.wav", teamname);
1137 set_flag_string(flag, snd_flag_returned, "ctf/%s_returned.wav", teamname);
1138 set_flag_string(flag, snd_flag_capture, "ctf/%s_capture.wav", teamname);
1139 set_flag_string(flag, snd_flag_dropped, "ctf/%s_dropped.wav", teamname);
1140 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.
1141 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1142 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1145 precache_sound(flag.snd_flag_taken);
1146 precache_sound(flag.snd_flag_returned);
1147 precache_sound(flag.snd_flag_capture);
1148 precache_sound(flag.snd_flag_respawn);
1149 precache_sound(flag.snd_flag_dropped);
1150 precache_sound(flag.snd_flag_touch);
1151 precache_sound(flag.snd_flag_pass);
1152 precache_model(flag.model);
1155 _setmodel(flag, flag.model); // precision set below
1156 setsize(flag, FLAG_MIN, FLAG_MAX);
1157 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1159 if(autocvar_g_ctf_flag_glowtrails)
1163 case NUM_TEAM_1: flag.glow_color = 251; break;
1164 case NUM_TEAM_2: flag.glow_color = 210; break;
1165 case NUM_TEAM_3: flag.glow_color = 110; break;
1166 case NUM_TEAM_4: flag.glow_color = 145; break;
1167 default: flag.glow_color = 254; break;
1169 flag.glow_size = 25;
1170 flag.glow_trail = 1;
1173 flag.effects |= EF_LOWPRECISION;
1174 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1175 if(autocvar_g_ctf_dynamiclights)
1179 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1180 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1181 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1182 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1183 default: flag.effects |= EF_DIMLIGHT; break;
1188 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1190 flag.dropped_origin = flag.origin;
1191 flag.noalign = true;
1192 flag.movetype = MOVETYPE_NONE;
1194 else // drop to floor, automatically find a platform and set that as spawn origin
1196 flag.noalign = false;
1199 flag.movetype = MOVETYPE_TOSS;
1202 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1210 // NOTE: LEGACY CODE, needs to be re-written!
1212 void havocbot_calculate_middlepoint()
1216 vector fo = '0 0 0';
1219 f = ctf_worldflaglist;
1224 f = f.ctf_worldflagnext;
1228 havocbot_ctf_middlepoint = s * (1.0 / n);
1229 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1233 entity havocbot_ctf_find_flag(entity bot)
1236 f = ctf_worldflaglist;
1239 if (CTF_SAMETEAM(bot, f))
1241 f = f.ctf_worldflagnext;
1246 entity havocbot_ctf_find_enemy_flag(entity bot)
1249 f = ctf_worldflaglist;
1254 if(CTF_DIFFTEAM(bot, f))
1261 else if(!bot.flagcarried)
1265 else if (CTF_DIFFTEAM(bot, f))
1267 f = f.ctf_worldflagnext;
1272 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1280 FOR_EACH_PLAYER(head)
1282 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1285 if(vlen(head.origin - org) < tc_radius)
1292 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1295 head = ctf_worldflaglist;
1298 if (CTF_SAMETEAM(self, head))
1300 head = head.ctf_worldflagnext;
1303 navigation_routerating(head, ratingscale, 10000);
1306 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1309 head = ctf_worldflaglist;
1312 if (CTF_SAMETEAM(self, head))
1314 head = head.ctf_worldflagnext;
1319 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1322 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1325 head = ctf_worldflaglist;
1330 if(CTF_DIFFTEAM(self, head))
1334 if(self.flagcarried)
1337 else if(!self.flagcarried)
1341 else if(CTF_DIFFTEAM(self, head))
1343 head = head.ctf_worldflagnext;
1346 navigation_routerating(head, ratingscale, 10000);
1349 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1351 if (!bot_waypoints_for_items)
1353 havocbot_goalrating_ctf_enemyflag(ratingscale);
1359 head = havocbot_ctf_find_enemy_flag(self);
1364 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1367 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1371 mf = havocbot_ctf_find_flag(self);
1373 if(mf.ctf_status == FLAG_BASE)
1377 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1380 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1383 head = ctf_worldflaglist;
1386 // flag is out in the field
1387 if(head.ctf_status != FLAG_BASE)
1388 if(head.tag_entity==world) // dropped
1392 if(vlen(org-head.origin)<df_radius)
1393 navigation_routerating(head, ratingscale, 10000);
1396 navigation_routerating(head, ratingscale, 10000);
1399 head = head.ctf_worldflagnext;
1403 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1407 head = findchainfloat(bot_pickup, true);
1410 // gather health and armor only
1412 if (head.health || head.armorvalue)
1413 if (vlen(head.origin - org) < sradius)
1415 // get the value of the item
1416 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1418 navigation_routerating(head, t * ratingscale, 500);
1424 void havocbot_ctf_reset_role(entity bot)
1426 float cdefense, cmiddle, coffense;
1427 entity mf, ef, head;
1430 if(bot.deadflag != DEAD_NO)
1433 if(vlen(havocbot_ctf_middlepoint)==0)
1434 havocbot_calculate_middlepoint();
1437 if (bot.flagcarried)
1439 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1443 mf = havocbot_ctf_find_flag(bot);
1444 ef = havocbot_ctf_find_enemy_flag(bot);
1446 // Retrieve stolen flag
1447 if(mf.ctf_status!=FLAG_BASE)
1449 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1453 // If enemy flag is taken go to the middle to intercept pursuers
1454 if(ef.ctf_status!=FLAG_BASE)
1456 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1460 // if there is only me on the team switch to offense
1462 FOR_EACH_PLAYER(head)
1463 if(SAME_TEAM(head, bot))
1468 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1472 // Evaluate best position to take
1473 // Count mates on middle position
1474 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1476 // Count mates on defense position
1477 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1479 // Count mates on offense position
1480 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1482 if(cdefense<=coffense)
1483 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1484 else if(coffense<=cmiddle)
1485 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1487 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1490 void havocbot_role_ctf_carrier()
1492 if(self.deadflag != DEAD_NO)
1494 havocbot_ctf_reset_role(self);
1498 if (self.flagcarried == world)
1500 havocbot_ctf_reset_role(self);
1504 if (self.bot_strategytime < time)
1506 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1508 navigation_goalrating_start();
1510 havocbot_goalrating_ctf_enemybase(50000);
1512 havocbot_goalrating_ctf_ourbase(50000);
1515 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1517 navigation_goalrating_end();
1519 if (self.navigation_hasgoals)
1520 self.havocbot_cantfindflag = time + 10;
1521 else if (time > self.havocbot_cantfindflag)
1523 // Can't navigate to my own base, suicide!
1524 // TODO: drop it and wander around
1525 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1531 void havocbot_role_ctf_escort()
1535 if(self.deadflag != DEAD_NO)
1537 havocbot_ctf_reset_role(self);
1541 if (self.flagcarried)
1543 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1547 // If enemy flag is back on the base switch to previous role
1548 ef = havocbot_ctf_find_enemy_flag(self);
1549 if(ef.ctf_status==FLAG_BASE)
1551 self.havocbot_role = self.havocbot_previous_role;
1552 self.havocbot_role_timeout = 0;
1556 // If the flag carrier reached the base switch to defense
1557 mf = havocbot_ctf_find_flag(self);
1558 if(mf.ctf_status!=FLAG_BASE)
1559 if(vlen(ef.origin - mf.dropped_origin) < 300)
1561 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1565 // Set the role timeout if necessary
1566 if (!self.havocbot_role_timeout)
1568 self.havocbot_role_timeout = time + random() * 30 + 60;
1571 // If nothing happened just switch to previous role
1572 if (time > self.havocbot_role_timeout)
1574 self.havocbot_role = self.havocbot_previous_role;
1575 self.havocbot_role_timeout = 0;
1579 // Chase the flag carrier
1580 if (self.bot_strategytime < time)
1582 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1583 navigation_goalrating_start();
1584 havocbot_goalrating_ctf_enemyflag(30000);
1585 havocbot_goalrating_ctf_ourstolenflag(40000);
1586 havocbot_goalrating_items(10000, self.origin, 10000);
1587 navigation_goalrating_end();
1591 void havocbot_role_ctf_offense()
1596 if(self.deadflag != DEAD_NO)
1598 havocbot_ctf_reset_role(self);
1602 if (self.flagcarried)
1604 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1609 mf = havocbot_ctf_find_flag(self);
1610 ef = havocbot_ctf_find_enemy_flag(self);
1613 if(mf.ctf_status!=FLAG_BASE)
1616 pos = mf.tag_entity.origin;
1620 // Try to get it if closer than the enemy base
1621 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1623 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1628 // Escort flag carrier
1629 if(ef.ctf_status!=FLAG_BASE)
1632 pos = ef.tag_entity.origin;
1636 if(vlen(pos-mf.dropped_origin)>700)
1638 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1643 // About to fail, switch to middlefield
1646 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1650 // Set the role timeout if necessary
1651 if (!self.havocbot_role_timeout)
1652 self.havocbot_role_timeout = time + 120;
1654 if (time > self.havocbot_role_timeout)
1656 havocbot_ctf_reset_role(self);
1660 if (self.bot_strategytime < time)
1662 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1663 navigation_goalrating_start();
1664 havocbot_goalrating_ctf_ourstolenflag(50000);
1665 havocbot_goalrating_ctf_enemybase(20000);
1666 havocbot_goalrating_items(5000, self.origin, 1000);
1667 havocbot_goalrating_items(1000, self.origin, 10000);
1668 navigation_goalrating_end();
1672 // Retriever (temporary role):
1673 void havocbot_role_ctf_retriever()
1677 if(self.deadflag != DEAD_NO)
1679 havocbot_ctf_reset_role(self);
1683 if (self.flagcarried)
1685 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1689 // If flag is back on the base switch to previous role
1690 mf = havocbot_ctf_find_flag(self);
1691 if(mf.ctf_status==FLAG_BASE)
1693 havocbot_ctf_reset_role(self);
1697 if (!self.havocbot_role_timeout)
1698 self.havocbot_role_timeout = time + 20;
1700 if (time > self.havocbot_role_timeout)
1702 havocbot_ctf_reset_role(self);
1706 if (self.bot_strategytime < time)
1711 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1712 navigation_goalrating_start();
1713 havocbot_goalrating_ctf_ourstolenflag(50000);
1714 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1715 havocbot_goalrating_ctf_enemybase(30000);
1716 havocbot_goalrating_items(500, self.origin, rt_radius);
1717 navigation_goalrating_end();
1721 void havocbot_role_ctf_middle()
1725 if(self.deadflag != DEAD_NO)
1727 havocbot_ctf_reset_role(self);
1731 if (self.flagcarried)
1733 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1737 mf = havocbot_ctf_find_flag(self);
1738 if(mf.ctf_status!=FLAG_BASE)
1740 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1744 if (!self.havocbot_role_timeout)
1745 self.havocbot_role_timeout = time + 10;
1747 if (time > self.havocbot_role_timeout)
1749 havocbot_ctf_reset_role(self);
1753 if (self.bot_strategytime < time)
1757 org = havocbot_ctf_middlepoint;
1758 org.z = self.origin.z;
1760 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1761 navigation_goalrating_start();
1762 havocbot_goalrating_ctf_ourstolenflag(50000);
1763 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1764 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1765 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1766 havocbot_goalrating_items(2500, self.origin, 10000);
1767 havocbot_goalrating_ctf_enemybase(2500);
1768 navigation_goalrating_end();
1772 void havocbot_role_ctf_defense()
1776 if(self.deadflag != DEAD_NO)
1778 havocbot_ctf_reset_role(self);
1782 if (self.flagcarried)
1784 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1788 // If own flag was captured
1789 mf = havocbot_ctf_find_flag(self);
1790 if(mf.ctf_status!=FLAG_BASE)
1792 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1796 if (!self.havocbot_role_timeout)
1797 self.havocbot_role_timeout = time + 30;
1799 if (time > self.havocbot_role_timeout)
1801 havocbot_ctf_reset_role(self);
1804 if (self.bot_strategytime < time)
1809 org = mf.dropped_origin;
1810 mp_radius = havocbot_ctf_middlepoint_radius;
1812 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1813 navigation_goalrating_start();
1815 // if enemies are closer to our base, go there
1816 entity head, closestplayer = world;
1817 float distance, bestdistance = 10000;
1818 FOR_EACH_PLAYER(head)
1820 if(head.deadflag!=DEAD_NO)
1823 distance = vlen(org - head.origin);
1824 if(distance<bestdistance)
1826 closestplayer = head;
1827 bestdistance = distance;
1832 if(DIFF_TEAM(closestplayer, self))
1833 if(vlen(org - self.origin)>1000)
1834 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1835 havocbot_goalrating_ctf_ourbase(30000);
1837 havocbot_goalrating_ctf_ourstolenflag(20000);
1838 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1839 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1840 havocbot_goalrating_items(10000, org, mp_radius);
1841 havocbot_goalrating_items(5000, self.origin, 10000);
1842 navigation_goalrating_end();
1846 void havocbot_role_ctf_setrole(entity bot, int role)
1848 LOG_TRACE(strcat(bot.netname," switched to "));
1851 case HAVOCBOT_CTF_ROLE_CARRIER:
1852 LOG_TRACE("carrier");
1853 bot.havocbot_role = havocbot_role_ctf_carrier;
1854 bot.havocbot_role_timeout = 0;
1855 bot.havocbot_cantfindflag = time + 10;
1856 bot.bot_strategytime = 0;
1858 case HAVOCBOT_CTF_ROLE_DEFENSE:
1859 LOG_TRACE("defense");
1860 bot.havocbot_role = havocbot_role_ctf_defense;
1861 bot.havocbot_role_timeout = 0;
1863 case HAVOCBOT_CTF_ROLE_MIDDLE:
1864 LOG_TRACE("middle");
1865 bot.havocbot_role = havocbot_role_ctf_middle;
1866 bot.havocbot_role_timeout = 0;
1868 case HAVOCBOT_CTF_ROLE_OFFENSE:
1869 LOG_TRACE("offense");
1870 bot.havocbot_role = havocbot_role_ctf_offense;
1871 bot.havocbot_role_timeout = 0;
1873 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1874 LOG_TRACE("retriever");
1875 bot.havocbot_previous_role = bot.havocbot_role;
1876 bot.havocbot_role = havocbot_role_ctf_retriever;
1877 bot.havocbot_role_timeout = time + 10;
1878 bot.bot_strategytime = 0;
1880 case HAVOCBOT_CTF_ROLE_ESCORT:
1881 LOG_TRACE("escort");
1882 bot.havocbot_previous_role = bot.havocbot_role;
1883 bot.havocbot_role = havocbot_role_ctf_escort;
1884 bot.havocbot_role_timeout = time + 30;
1885 bot.bot_strategytime = 0;
1896 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1899 int t = 0, t2 = 0, t3 = 0;
1901 // initially clear items so they can be set as necessary later.
1902 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1903 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1904 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1905 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1906 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1907 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1909 // scan through all the flags and notify the client about them
1910 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1912 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1913 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1914 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1915 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1916 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; }
1918 switch(flag.ctf_status)
1923 if((flag.owner == self) || (flag.pass_sender == self))
1924 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1926 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1931 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1937 // item for stopping players from capturing the flag too often
1938 if(self.ctf_captureshielded)
1939 self.ctf_flagstatus |= CTF_SHIELDED;
1941 // update the health of the flag carrier waypointsprite
1942 if(self.wps_flagcarrier)
1943 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1948 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1950 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1952 if(frag_target == frag_attacker) // damage done to yourself
1954 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1955 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1957 else // damage done to everyone else
1959 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1960 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1963 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1965 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)))
1966 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1968 frag_target.wps_helpme_time = time;
1969 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1971 // todo: add notification for when flag carrier needs help?
1976 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1978 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1980 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1981 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1984 if(frag_target.flagcarried)
1986 entity tmp_entity = frag_target.flagcarried;
1987 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
1988 tmp_entity.ctf_dropper = world;
1994 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1997 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2000 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
2002 entity flag; // temporary entity for the search method
2004 if(self.flagcarried)
2005 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2007 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2009 if(flag.pass_sender == self) { flag.pass_sender = world; }
2010 if(flag.pass_target == self) { flag.pass_target = world; }
2011 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
2017 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2019 if(self.flagcarried)
2020 if(!autocvar_g_ctf_portalteleport)
2021 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2026 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2028 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2030 entity player = self;
2032 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2034 // pass the flag to a team mate
2035 if(autocvar_g_ctf_pass)
2037 entity head, closest_target = world;
2038 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2040 while(head) // find the closest acceptable target to pass to
2042 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2043 if(head != player && SAME_TEAM(head, player))
2044 if(!head.speedrunning && !head.vehicle)
2046 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2047 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2048 vector passer_center = CENTER_OR_VIEWOFS(player);
2050 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2052 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2054 if(IS_BOT_CLIENT(head))
2056 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2057 ctf_Handle_Throw(head, player, DROP_PASS);
2061 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2062 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2064 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2067 else if(player.flagcarried)
2071 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2072 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2073 { closest_target = head; }
2075 else { closest_target = head; }
2082 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2085 // throw the flag in front of you
2086 if(autocvar_g_ctf_throw && player.flagcarried)
2088 if(player.throw_count == -1)
2090 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2092 player.throw_prevtime = time;
2093 player.throw_count = 1;
2094 ctf_Handle_Throw(player, world, DROP_THROW);
2099 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2105 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2106 else { player.throw_count += 1; }
2107 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2109 player.throw_prevtime = time;
2110 ctf_Handle_Throw(player, world, DROP_THROW);
2119 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2121 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2123 self.wps_helpme_time = time;
2124 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2126 else // create a normal help me waypointsprite
2128 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
2129 WaypointSprite_Ping(self.wps_helpme);
2135 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2137 if(vh_player.flagcarried)
2139 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2141 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2143 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2147 setattachment(vh_player.flagcarried, vh_vehicle, "");
2148 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2149 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2150 //vh_player.flagcarried.angles = '0 0 0';
2158 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2160 if(vh_player.flagcarried)
2162 setattachment(vh_player.flagcarried, vh_player, "");
2163 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2164 vh_player.flagcarried.scale = FLAG_SCALE;
2165 vh_player.flagcarried.angles = '0 0 0';
2166 vh_player.flagcarried.nodrawtoclient = world;
2173 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2175 if(self.flagcarried)
2177 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));
2178 ctf_RespawnFlag(self.flagcarried);
2185 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2187 entity flag; // temporary entity for the search method
2189 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2191 switch(flag.ctf_status)
2196 // lock the flag, game is over
2197 flag.movetype = MOVETYPE_NONE;
2198 flag.takedamage = DAMAGE_NO;
2199 flag.solid = SOLID_NOT;
2200 flag.nextthink = false; // stop thinking
2202 //dprint("stopping the ", flag.netname, " from moving.\n");
2210 // do nothing for these flags
2219 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2221 havocbot_ctf_reset_role(self);
2225 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2227 //ret_float = ctf_teams;
2228 ret_string = "ctf_team";
2232 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2234 self.ctf_flagstatus = other.ctf_flagstatus;
2243 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2244 CTF flag for team one (Red).
2246 "angle" Angle the flag will point (minus 90 degrees)...
2247 "model" model to use, note this needs red and blue as skins 0 and 1...
2248 "noise" sound played when flag is picked up...
2249 "noise1" sound played when flag is returned by a teammate...
2250 "noise2" sound played when flag is captured...
2251 "noise3" sound played when flag is lost in the field and respawns itself...
2252 "noise4" sound played when flag is dropped by a player...
2253 "noise5" sound played when flag touches the ground... */
2254 void spawnfunc_item_flag_team1()
2256 if(!g_ctf) { remove(self); return; }
2258 ctf_FlagSetup(NUM_TEAM_1, self);
2261 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2262 CTF flag for team two (Blue).
2264 "angle" Angle the flag will point (minus 90 degrees)...
2265 "model" model to use, note this needs red and blue as skins 0 and 1...
2266 "noise" sound played when flag is picked up...
2267 "noise1" sound played when flag is returned by a teammate...
2268 "noise2" sound played when flag is captured...
2269 "noise3" sound played when flag is lost in the field and respawns itself...
2270 "noise4" sound played when flag is dropped by a player...
2271 "noise5" sound played when flag touches the ground... */
2272 void spawnfunc_item_flag_team2()
2274 if(!g_ctf) { remove(self); return; }
2276 ctf_FlagSetup(NUM_TEAM_2, self);
2279 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2280 CTF flag for team three (Yellow).
2282 "angle" Angle the flag will point (minus 90 degrees)...
2283 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2284 "noise" sound played when flag is picked up...
2285 "noise1" sound played when flag is returned by a teammate...
2286 "noise2" sound played when flag is captured...
2287 "noise3" sound played when flag is lost in the field and respawns itself...
2288 "noise4" sound played when flag is dropped by a player...
2289 "noise5" sound played when flag touches the ground... */
2290 void spawnfunc_item_flag_team3()
2292 if(!g_ctf) { remove(self); return; }
2294 ctf_FlagSetup(NUM_TEAM_3, self);
2297 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2298 CTF flag for team four (Pink).
2300 "angle" Angle the flag will point (minus 90 degrees)...
2301 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2302 "noise" sound played when flag is picked up...
2303 "noise1" sound played when flag is returned by a teammate...
2304 "noise2" sound played when flag is captured...
2305 "noise3" sound played when flag is lost in the field and respawns itself...
2306 "noise4" sound played when flag is dropped by a player...
2307 "noise5" sound played when flag touches the ground... */
2308 void spawnfunc_item_flag_team4()
2310 if(!g_ctf) { remove(self); return; }
2312 ctf_FlagSetup(NUM_TEAM_4, self);
2315 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2318 "angle" Angle the flag will point (minus 90 degrees)...
2319 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
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_neutral()
2328 if(!g_ctf) { remove(self); return; }
2329 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2331 ctf_FlagSetup(0, self);
2334 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2335 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2336 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.
2338 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2339 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2340 void spawnfunc_ctf_team()
2342 if(!g_ctf) { remove(self); return; }
2344 self.classname = "ctf_team";
2345 self.team = self.cnt + 1;
2348 // compatibility for quake maps
2349 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2350 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2351 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2352 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2353 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2354 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2356 void team_CTF_neutralflag() { spawnfunc_item_flag_neutral(); }
2357 void team_neutralobelisk() { spawnfunc_item_flag_neutral(); }
2365 void ctf_ScoreRules(int teams)
2367 CheckAllowedTeams(world);
2368 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2369 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2370 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2371 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2372 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2373 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2374 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2375 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2376 ScoreRules_basics_end();
2379 // code from here on is just to support maps that don't have flag and team entities
2380 void ctf_SpawnTeam (string teamname, int teamcolor)
2383 self.classname = "ctf_team";
2384 self.netname = teamname;
2385 self.cnt = teamcolor;
2387 spawnfunc_ctf_team();
2392 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2397 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2399 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2400 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2401 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2404 ctf_teams = bound(2, ctf_teams, 4);
2406 // if no teams are found, spawn defaults
2407 if(find(world, classname, "ctf_team") == world)
2409 LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2410 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2411 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2413 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2415 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2418 ctf_ScoreRules(ctf_teams);
2421 void ctf_Initialize()
2423 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2425 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2426 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2427 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2429 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2431 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2435 MUTATOR_DEFINITION(gamemode_ctf)
2437 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2438 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2439 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2440 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2441 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2442 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2443 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2444 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2445 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2446 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2447 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2448 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2449 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2450 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2451 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2452 MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2456 if(time > 1) // game loads at time 1
2457 error("This is a game type and it cannot be added at runtime.");
2461 MUTATOR_ONROLLBACK_OR_REMOVE
2463 // we actually cannot roll back ctf_Initialize here
2464 // BUT: we don't need to! If this gets called, adding always
2470 LOG_INFO("This is a game type and it cannot be removed at runtime.");