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 ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1082 self = flag; // for later usage with droptofloor()
1085 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1086 ctf_worldflaglist = flag;
1088 setattachment(flag, world, "");
1090 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1091 flag.team = teamnumber;
1092 flag.classname = "item_flag_team";
1093 flag.target = "###item###"; // wut?
1094 flag.flags = FL_ITEM | FL_NOTARGET;
1095 flag.solid = SOLID_TRIGGER;
1096 flag.takedamage = DAMAGE_NO;
1097 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1098 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1099 flag.health = flag.max_flag_health;
1100 flag.event_damage = ctf_FlagDamage;
1101 flag.pushable = true;
1102 flag.teleportable = TELEPORT_NORMAL;
1103 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1104 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1105 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1106 flag.velocity = '0 0 0';
1107 flag.mangle = flag.angles;
1108 flag.reset = ctf_Reset;
1109 flag.touch = ctf_FlagTouch;
1110 flag.think = ctf_FlagThink;
1111 flag.nextthink = time + FLAG_THINKRATE;
1112 flag.ctf_status = FLAG_BASE;
1115 if(flag.model == "") { flag.model = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_model : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_model : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_model : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_model : autocvar_g_ctf_flag_neutral_model)))); }
1116 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1117 if(!flag.skin) { flag.skin = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_skin : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_skin : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_skin : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_skin : autocvar_g_ctf_flag_neutral_skin)))); }
1118 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber == NUM_TEAM_1) ? "redflag_touch" : ((teamnumber == NUM_TEAM_2) ? "blueflag_touch" : ((teamnumber == NUM_TEAM_3) ? "yellowflag_touch" : ((teamnumber == NUM_TEAM_4) ? "pinkflag_touch" : "neutralflag_touch")))); }
1119 if(flag.passeffect == "") { flag.passeffect = ((teamnumber == NUM_TEAM_1) ? "red_pass" : ((teamnumber == NUM_TEAM_2) ? "blue_pass" : ((teamnumber == NUM_TEAM_3) ? "yellow_pass" : ((teamnumber == NUM_TEAM_4) ? "pink_pass" : "neutral_pass")))); }
1120 if(flag.capeffect == "") { flag.capeffect = ((teamnumber == NUM_TEAM_1) ? "red_cap" : ((teamnumber == NUM_TEAM_2) ? "blue_cap" : ((teamnumber == NUM_TEAM_3) ? "yellow_cap" : ((teamnumber == NUM_TEAM_4) ? "pink_cap" : "")))); } // neutral flag cant be capped itself
1123 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber == NUM_TEAM_1) ? "ctf/red_taken.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_taken.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_taken.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_taken.wav" : "ctf/neutral_taken.wav")))); }
1124 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber == NUM_TEAM_1) ? "ctf/red_returned.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_returned.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_returned.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_returned.wav" : "")))); } // neutral flag can't be returned by players
1125 if(flag.snd_flag_capture == "") { flag.snd_flag_capture = ((teamnumber == NUM_TEAM_1) ? "ctf/red_capture.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_capture.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_capture.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_capture.wav" : "")))); } // again can't be captured
1126 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.
1127 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber == NUM_TEAM_1) ? "ctf/red_dropped.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_dropped.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_dropped.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_dropped.wav" : "ctf/neutral_dropped.wav")))); }
1128 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1129 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1132 precache_sound(flag.snd_flag_taken);
1133 precache_sound(flag.snd_flag_returned);
1134 precache_sound(flag.snd_flag_capture);
1135 precache_sound(flag.snd_flag_respawn);
1136 precache_sound(flag.snd_flag_dropped);
1137 precache_sound(flag.snd_flag_touch);
1138 precache_sound(flag.snd_flag_pass);
1139 precache_model(flag.model);
1140 precache_model("models/ctf/shield.md3");
1141 precache_model("models/ctf/shockwavetransring.md3");
1144 setmodel(flag, flag.model); // precision set below
1145 setsize(flag, FLAG_MIN, FLAG_MAX);
1146 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1148 if(autocvar_g_ctf_flag_glowtrails)
1150 flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254))));
1151 flag.glow_size = 25;
1152 flag.glow_trail = 1;
1155 flag.effects |= EF_LOWPRECISION;
1156 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1157 if(autocvar_g_ctf_dynamiclights)
1161 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1162 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1163 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1164 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1165 default: flag.effects |= EF_DIMLIGHT; break;
1170 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1172 flag.dropped_origin = flag.origin;
1173 flag.noalign = true;
1174 flag.movetype = MOVETYPE_NONE;
1176 else // drop to floor, automatically find a platform and set that as spawn origin
1178 flag.noalign = false;
1181 flag.movetype = MOVETYPE_TOSS;
1184 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1192 // NOTE: LEGACY CODE, needs to be re-written!
1194 void havocbot_calculate_middlepoint()
1198 vector fo = '0 0 0';
1201 f = ctf_worldflaglist;
1206 f = f.ctf_worldflagnext;
1210 havocbot_ctf_middlepoint = s * (1.0 / n);
1211 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1215 entity havocbot_ctf_find_flag(entity bot)
1218 f = ctf_worldflaglist;
1221 if (CTF_SAMETEAM(bot, f))
1223 f = f.ctf_worldflagnext;
1228 entity havocbot_ctf_find_enemy_flag(entity bot)
1231 f = ctf_worldflaglist;
1236 if(CTF_DIFFTEAM(bot, f))
1243 else if(!bot.flagcarried)
1247 else if (CTF_DIFFTEAM(bot, f))
1249 f = f.ctf_worldflagnext;
1254 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1262 FOR_EACH_PLAYER(head)
1264 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1267 if(vlen(head.origin - org) < tc_radius)
1274 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1277 head = ctf_worldflaglist;
1280 if (CTF_SAMETEAM(self, head))
1282 head = head.ctf_worldflagnext;
1285 navigation_routerating(head, ratingscale, 10000);
1288 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1291 head = ctf_worldflaglist;
1294 if (CTF_SAMETEAM(self, head))
1296 head = head.ctf_worldflagnext;
1301 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1304 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1307 head = ctf_worldflaglist;
1312 if(CTF_DIFFTEAM(self, head))
1316 if(self.flagcarried)
1319 else if(!self.flagcarried)
1323 else if(CTF_DIFFTEAM(self, head))
1325 head = head.ctf_worldflagnext;
1328 navigation_routerating(head, ratingscale, 10000);
1331 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1333 if (!bot_waypoints_for_items)
1335 havocbot_goalrating_ctf_enemyflag(ratingscale);
1341 head = havocbot_ctf_find_enemy_flag(self);
1346 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1349 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1353 mf = havocbot_ctf_find_flag(self);
1355 if(mf.ctf_status == FLAG_BASE)
1359 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1362 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1365 head = ctf_worldflaglist;
1368 // flag is out in the field
1369 if(head.ctf_status != FLAG_BASE)
1370 if(head.tag_entity==world) // dropped
1374 if(vlen(org-head.origin)<df_radius)
1375 navigation_routerating(head, ratingscale, 10000);
1378 navigation_routerating(head, ratingscale, 10000);
1381 head = head.ctf_worldflagnext;
1385 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1389 head = findchainfloat(bot_pickup, true);
1392 // gather health and armor only
1394 if (head.health || head.armorvalue)
1395 if (vlen(head.origin - org) < sradius)
1397 // get the value of the item
1398 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1400 navigation_routerating(head, t * ratingscale, 500);
1406 void havocbot_ctf_reset_role(entity bot)
1408 float cdefense, cmiddle, coffense;
1409 entity mf, ef, head;
1412 if(bot.deadflag != DEAD_NO)
1415 if(vlen(havocbot_ctf_middlepoint)==0)
1416 havocbot_calculate_middlepoint();
1419 if (bot.flagcarried)
1421 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1425 mf = havocbot_ctf_find_flag(bot);
1426 ef = havocbot_ctf_find_enemy_flag(bot);
1428 // Retrieve stolen flag
1429 if(mf.ctf_status!=FLAG_BASE)
1431 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1435 // If enemy flag is taken go to the middle to intercept pursuers
1436 if(ef.ctf_status!=FLAG_BASE)
1438 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1442 // if there is only me on the team switch to offense
1444 FOR_EACH_PLAYER(head)
1445 if(SAME_TEAM(head, bot))
1450 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1454 // Evaluate best position to take
1455 // Count mates on middle position
1456 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1458 // Count mates on defense position
1459 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1461 // Count mates on offense position
1462 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1464 if(cdefense<=coffense)
1465 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1466 else if(coffense<=cmiddle)
1467 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1469 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1472 void havocbot_role_ctf_carrier()
1474 if(self.deadflag != DEAD_NO)
1476 havocbot_ctf_reset_role(self);
1480 if (self.flagcarried == world)
1482 havocbot_ctf_reset_role(self);
1486 if (self.bot_strategytime < time)
1488 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1490 navigation_goalrating_start();
1492 havocbot_goalrating_ctf_enemybase(50000);
1494 havocbot_goalrating_ctf_ourbase(50000);
1497 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1499 navigation_goalrating_end();
1501 if (self.navigation_hasgoals)
1502 self.havocbot_cantfindflag = time + 10;
1503 else if (time > self.havocbot_cantfindflag)
1505 // Can't navigate to my own base, suicide!
1506 // TODO: drop it and wander around
1507 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1513 void havocbot_role_ctf_escort()
1517 if(self.deadflag != DEAD_NO)
1519 havocbot_ctf_reset_role(self);
1523 if (self.flagcarried)
1525 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1529 // If enemy flag is back on the base switch to previous role
1530 ef = havocbot_ctf_find_enemy_flag(self);
1531 if(ef.ctf_status==FLAG_BASE)
1533 self.havocbot_role = self.havocbot_previous_role;
1534 self.havocbot_role_timeout = 0;
1538 // If the flag carrier reached the base switch to defense
1539 mf = havocbot_ctf_find_flag(self);
1540 if(mf.ctf_status!=FLAG_BASE)
1541 if(vlen(ef.origin - mf.dropped_origin) < 300)
1543 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1547 // Set the role timeout if necessary
1548 if (!self.havocbot_role_timeout)
1550 self.havocbot_role_timeout = time + random() * 30 + 60;
1553 // If nothing happened just switch to previous role
1554 if (time > self.havocbot_role_timeout)
1556 self.havocbot_role = self.havocbot_previous_role;
1557 self.havocbot_role_timeout = 0;
1561 // Chase the flag carrier
1562 if (self.bot_strategytime < time)
1564 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1565 navigation_goalrating_start();
1566 havocbot_goalrating_ctf_enemyflag(30000);
1567 havocbot_goalrating_ctf_ourstolenflag(40000);
1568 havocbot_goalrating_items(10000, self.origin, 10000);
1569 navigation_goalrating_end();
1573 void havocbot_role_ctf_offense()
1578 if(self.deadflag != DEAD_NO)
1580 havocbot_ctf_reset_role(self);
1584 if (self.flagcarried)
1586 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1591 mf = havocbot_ctf_find_flag(self);
1592 ef = havocbot_ctf_find_enemy_flag(self);
1595 if(mf.ctf_status!=FLAG_BASE)
1598 pos = mf.tag_entity.origin;
1602 // Try to get it if closer than the enemy base
1603 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1605 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1610 // Escort flag carrier
1611 if(ef.ctf_status!=FLAG_BASE)
1614 pos = ef.tag_entity.origin;
1618 if(vlen(pos-mf.dropped_origin)>700)
1620 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1625 // About to fail, switch to middlefield
1628 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1632 // Set the role timeout if necessary
1633 if (!self.havocbot_role_timeout)
1634 self.havocbot_role_timeout = time + 120;
1636 if (time > self.havocbot_role_timeout)
1638 havocbot_ctf_reset_role(self);
1642 if (self.bot_strategytime < time)
1644 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1645 navigation_goalrating_start();
1646 havocbot_goalrating_ctf_ourstolenflag(50000);
1647 havocbot_goalrating_ctf_enemybase(20000);
1648 havocbot_goalrating_items(5000, self.origin, 1000);
1649 havocbot_goalrating_items(1000, self.origin, 10000);
1650 navigation_goalrating_end();
1654 // Retriever (temporary role):
1655 void havocbot_role_ctf_retriever()
1659 if(self.deadflag != DEAD_NO)
1661 havocbot_ctf_reset_role(self);
1665 if (self.flagcarried)
1667 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1671 // If flag is back on the base switch to previous role
1672 mf = havocbot_ctf_find_flag(self);
1673 if(mf.ctf_status==FLAG_BASE)
1675 havocbot_ctf_reset_role(self);
1679 if (!self.havocbot_role_timeout)
1680 self.havocbot_role_timeout = time + 20;
1682 if (time > self.havocbot_role_timeout)
1684 havocbot_ctf_reset_role(self);
1688 if (self.bot_strategytime < time)
1693 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1694 navigation_goalrating_start();
1695 havocbot_goalrating_ctf_ourstolenflag(50000);
1696 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1697 havocbot_goalrating_ctf_enemybase(30000);
1698 havocbot_goalrating_items(500, self.origin, rt_radius);
1699 navigation_goalrating_end();
1703 void havocbot_role_ctf_middle()
1707 if(self.deadflag != DEAD_NO)
1709 havocbot_ctf_reset_role(self);
1713 if (self.flagcarried)
1715 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1719 mf = havocbot_ctf_find_flag(self);
1720 if(mf.ctf_status!=FLAG_BASE)
1722 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1726 if (!self.havocbot_role_timeout)
1727 self.havocbot_role_timeout = time + 10;
1729 if (time > self.havocbot_role_timeout)
1731 havocbot_ctf_reset_role(self);
1735 if (self.bot_strategytime < time)
1739 org = havocbot_ctf_middlepoint;
1740 org.z = self.origin.z;
1742 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1743 navigation_goalrating_start();
1744 havocbot_goalrating_ctf_ourstolenflag(50000);
1745 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1746 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1747 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1748 havocbot_goalrating_items(2500, self.origin, 10000);
1749 havocbot_goalrating_ctf_enemybase(2500);
1750 navigation_goalrating_end();
1754 void havocbot_role_ctf_defense()
1758 if(self.deadflag != DEAD_NO)
1760 havocbot_ctf_reset_role(self);
1764 if (self.flagcarried)
1766 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1770 // If own flag was captured
1771 mf = havocbot_ctf_find_flag(self);
1772 if(mf.ctf_status!=FLAG_BASE)
1774 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1778 if (!self.havocbot_role_timeout)
1779 self.havocbot_role_timeout = time + 30;
1781 if (time > self.havocbot_role_timeout)
1783 havocbot_ctf_reset_role(self);
1786 if (self.bot_strategytime < time)
1791 org = mf.dropped_origin;
1792 mp_radius = havocbot_ctf_middlepoint_radius;
1794 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1795 navigation_goalrating_start();
1797 // if enemies are closer to our base, go there
1798 entity head, closestplayer = world;
1799 float distance, bestdistance = 10000;
1800 FOR_EACH_PLAYER(head)
1802 if(head.deadflag!=DEAD_NO)
1805 distance = vlen(org - head.origin);
1806 if(distance<bestdistance)
1808 closestplayer = head;
1809 bestdistance = distance;
1814 if(DIFF_TEAM(closestplayer, self))
1815 if(vlen(org - self.origin)>1000)
1816 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1817 havocbot_goalrating_ctf_ourbase(30000);
1819 havocbot_goalrating_ctf_ourstolenflag(20000);
1820 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1821 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1822 havocbot_goalrating_items(10000, org, mp_radius);
1823 havocbot_goalrating_items(5000, self.origin, 10000);
1824 navigation_goalrating_end();
1828 void havocbot_role_ctf_setrole(entity bot, float role)
1830 dprint(strcat(bot.netname," switched to "));
1833 case HAVOCBOT_CTF_ROLE_CARRIER:
1835 bot.havocbot_role = havocbot_role_ctf_carrier;
1836 bot.havocbot_role_timeout = 0;
1837 bot.havocbot_cantfindflag = time + 10;
1838 bot.bot_strategytime = 0;
1840 case HAVOCBOT_CTF_ROLE_DEFENSE:
1842 bot.havocbot_role = havocbot_role_ctf_defense;
1843 bot.havocbot_role_timeout = 0;
1845 case HAVOCBOT_CTF_ROLE_MIDDLE:
1847 bot.havocbot_role = havocbot_role_ctf_middle;
1848 bot.havocbot_role_timeout = 0;
1850 case HAVOCBOT_CTF_ROLE_OFFENSE:
1852 bot.havocbot_role = havocbot_role_ctf_offense;
1853 bot.havocbot_role_timeout = 0;
1855 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1856 dprint("retriever");
1857 bot.havocbot_previous_role = bot.havocbot_role;
1858 bot.havocbot_role = havocbot_role_ctf_retriever;
1859 bot.havocbot_role_timeout = time + 10;
1860 bot.bot_strategytime = 0;
1862 case HAVOCBOT_CTF_ROLE_ESCORT:
1864 bot.havocbot_previous_role = bot.havocbot_role;
1865 bot.havocbot_role = havocbot_role_ctf_escort;
1866 bot.havocbot_role_timeout = time + 30;
1867 bot.bot_strategytime = 0;
1878 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1881 int t = 0, t2 = 0, t3 = 0;
1883 // initially clear items so they can be set as necessary later.
1884 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1885 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1886 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1887 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1888 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1889 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1891 // scan through all the flags and notify the client about them
1892 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1894 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1895 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1896 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1897 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1898 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; }
1900 switch(flag.ctf_status)
1905 if((flag.owner == self) || (flag.pass_sender == self))
1906 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1908 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1913 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1919 // item for stopping players from capturing the flag too often
1920 if(self.ctf_captureshielded)
1921 self.ctf_flagstatus |= CTF_SHIELDED;
1923 // update the health of the flag carrier waypointsprite
1924 if(self.wps_flagcarrier)
1925 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1930 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1932 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1934 if(frag_target == frag_attacker) // damage done to yourself
1936 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1937 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1939 else // damage done to everyone else
1941 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1942 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1945 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1947 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)))
1948 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1950 frag_target.wps_helpme_time = time;
1951 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1953 // todo: add notification for when flag carrier needs help?
1958 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1960 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1962 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1963 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1966 if(frag_target.flagcarried)
1968 entity tmp_entity = frag_target.flagcarried;
1969 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
1970 tmp_entity.ctf_dropper = world;
1976 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1979 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1982 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1984 entity flag; // temporary entity for the search method
1986 if(self.flagcarried)
1987 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1989 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1991 if(flag.pass_sender == self) { flag.pass_sender = world; }
1992 if(flag.pass_target == self) { flag.pass_target = world; }
1993 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1999 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2001 if(self.flagcarried)
2002 if(!autocvar_g_ctf_portalteleport)
2003 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2008 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2010 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2012 entity player = self;
2014 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2016 // pass the flag to a team mate
2017 if(autocvar_g_ctf_pass)
2019 entity head, closest_target = world;
2020 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2022 while(head) // find the closest acceptable target to pass to
2024 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2025 if(head != player && SAME_TEAM(head, player))
2026 if(!head.speedrunning && !head.vehicle)
2028 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2029 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2030 vector passer_center = CENTER_OR_VIEWOFS(player);
2032 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2034 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2036 if(IS_BOT_CLIENT(head))
2038 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2039 ctf_Handle_Throw(head, player, DROP_PASS);
2043 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2044 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2046 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2049 else if(player.flagcarried)
2053 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2054 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2055 { closest_target = head; }
2057 else { closest_target = head; }
2064 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2067 // throw the flag in front of you
2068 if(autocvar_g_ctf_throw && player.flagcarried)
2070 if(player.throw_count == -1)
2072 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2074 player.throw_prevtime = time;
2075 player.throw_count = 1;
2076 ctf_Handle_Throw(player, world, DROP_THROW);
2081 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2087 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2088 else { player.throw_count += 1; }
2089 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2091 player.throw_prevtime = time;
2092 ctf_Handle_Throw(player, world, DROP_THROW);
2101 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2103 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2105 self.wps_helpme_time = time;
2106 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2108 else // create a normal help me waypointsprite
2110 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');
2111 WaypointSprite_Ping(self.wps_helpme);
2117 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2119 if(vh_player.flagcarried)
2121 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2123 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2127 setattachment(vh_player.flagcarried, vh_vehicle, "");
2128 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2129 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2130 //vh_player.flagcarried.angles = '0 0 0';
2138 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2140 if(vh_player.flagcarried)
2142 setattachment(vh_player.flagcarried, vh_player, "");
2143 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2144 vh_player.flagcarried.scale = FLAG_SCALE;
2145 vh_player.flagcarried.angles = '0 0 0';
2152 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2154 if(self.flagcarried)
2156 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));
2157 ctf_RespawnFlag(self.flagcarried);
2164 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2166 entity flag; // temporary entity for the search method
2168 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2170 switch(flag.ctf_status)
2175 // lock the flag, game is over
2176 flag.movetype = MOVETYPE_NONE;
2177 flag.takedamage = DAMAGE_NO;
2178 flag.solid = SOLID_NOT;
2179 flag.nextthink = false; // stop thinking
2181 //dprint("stopping the ", flag.netname, " from moving.\n");
2189 // do nothing for these flags
2198 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2200 havocbot_ctf_reset_role(self);
2204 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2206 //ret_float = ctf_teams;
2207 ret_string = "ctf_team";
2211 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2213 self.ctf_flagstatus = other.ctf_flagstatus;
2222 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2223 CTF Starting point for a player in team one (Red).
2224 Keys: "angle" viewing angle when spawning. */
2225 void spawnfunc_info_player_team1()
2227 if(g_assault) { remove(self); return; }
2229 self.team = NUM_TEAM_1; // red
2230 spawnfunc_info_player_deathmatch();
2234 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2235 CTF Starting point for a player in team two (Blue).
2236 Keys: "angle" viewing angle when spawning. */
2237 void spawnfunc_info_player_team2()
2239 if(g_assault) { remove(self); return; }
2241 self.team = NUM_TEAM_2; // blue
2242 spawnfunc_info_player_deathmatch();
2245 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2246 CTF Starting point for a player in team three (Yellow).
2247 Keys: "angle" viewing angle when spawning. */
2248 void spawnfunc_info_player_team3()
2250 if(g_assault) { remove(self); return; }
2252 self.team = NUM_TEAM_3; // yellow
2253 spawnfunc_info_player_deathmatch();
2257 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2258 CTF Starting point for a player in team four (Purple).
2259 Keys: "angle" viewing angle when spawning. */
2260 void spawnfunc_info_player_team4()
2262 if(g_assault) { remove(self); return; }
2264 self.team = NUM_TEAM_4; // purple
2265 spawnfunc_info_player_deathmatch();
2268 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2269 CTF flag for team one (Red).
2271 "angle" Angle the flag will point (minus 90 degrees)...
2272 "model" model to use, note this needs red and blue as skins 0 and 1...
2273 "noise" sound played when flag is picked up...
2274 "noise1" sound played when flag is returned by a teammate...
2275 "noise2" sound played when flag is captured...
2276 "noise3" sound played when flag is lost in the field and respawns itself...
2277 "noise4" sound played when flag is dropped by a player...
2278 "noise5" sound played when flag touches the ground... */
2279 void spawnfunc_item_flag_team1()
2281 if(!g_ctf) { remove(self); return; }
2283 ctf_FlagSetup(NUM_TEAM_1, self);
2286 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2287 CTF flag for team two (Blue).
2289 "angle" Angle the flag will point (minus 90 degrees)...
2290 "model" model to use, note this needs red and blue as skins 0 and 1...
2291 "noise" sound played when flag is picked up...
2292 "noise1" sound played when flag is returned by a teammate...
2293 "noise2" sound played when flag is captured...
2294 "noise3" sound played when flag is lost in the field and respawns itself...
2295 "noise4" sound played when flag is dropped by a player...
2296 "noise5" sound played when flag touches the ground... */
2297 void spawnfunc_item_flag_team2()
2299 if(!g_ctf) { remove(self); return; }
2301 ctf_FlagSetup(NUM_TEAM_2, self);
2304 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2305 CTF flag for team three (Yellow).
2307 "angle" Angle the flag will point (minus 90 degrees)...
2308 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2309 "noise" sound played when flag is picked up...
2310 "noise1" sound played when flag is returned by a teammate...
2311 "noise2" sound played when flag is captured...
2312 "noise3" sound played when flag is lost in the field and respawns itself...
2313 "noise4" sound played when flag is dropped by a player...
2314 "noise5" sound played when flag touches the ground... */
2315 void spawnfunc_item_flag_team3()
2317 if(!g_ctf) { remove(self); return; }
2319 ctf_FlagSetup(NUM_TEAM_3, self);
2322 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2323 CTF flag for team four (Pink).
2325 "angle" Angle the flag will point (minus 90 degrees)...
2326 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2327 "noise" sound played when flag is picked up...
2328 "noise1" sound played when flag is returned by a teammate...
2329 "noise2" sound played when flag is captured...
2330 "noise3" sound played when flag is lost in the field and respawns itself...
2331 "noise4" sound played when flag is dropped by a player...
2332 "noise5" sound played when flag touches the ground... */
2333 void spawnfunc_item_flag_team4()
2335 if(!g_ctf) { remove(self); return; }
2337 ctf_FlagSetup(NUM_TEAM_4, self);
2340 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2343 "angle" Angle the flag will point (minus 90 degrees)...
2344 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2345 "noise" sound played when flag is picked up...
2346 "noise1" sound played when flag is returned by a teammate...
2347 "noise2" sound played when flag is captured...
2348 "noise3" sound played when flag is lost in the field and respawns itself...
2349 "noise4" sound played when flag is dropped by a player...
2350 "noise5" sound played when flag touches the ground... */
2351 void spawnfunc_item_flag_neutral()
2353 if(!g_ctf) { remove(self); return; }
2354 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2356 ctf_FlagSetup(0, self);
2359 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2360 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2361 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.
2363 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2364 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2365 void spawnfunc_ctf_team()
2367 if(!g_ctf) { remove(self); return; }
2369 self.classname = "ctf_team";
2370 self.team = self.cnt + 1;
2373 // compatibility for quake maps
2374 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2375 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2376 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2377 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2378 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2379 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2387 void ctf_ScoreRules(float teams)
2389 CheckAllowedTeams(world);
2390 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2391 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2392 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2393 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2394 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2395 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2396 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2397 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2398 ScoreRules_basics_end();
2401 // code from here on is just to support maps that don't have flag and team entities
2402 void ctf_SpawnTeam (string teamname, float teamcolor)
2407 self.classname = "ctf_team";
2408 self.netname = teamname;
2409 self.cnt = teamcolor;
2411 spawnfunc_ctf_team();
2416 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2421 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2423 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2424 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2425 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2428 ctf_teams = bound(2, ctf_teams, 4);
2430 // if no teams are found, spawn defaults
2431 if(find(world, classname, "ctf_team") == world)
2433 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2434 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2435 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2437 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2439 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2442 ctf_ScoreRules(ctf_teams);
2445 void ctf_Initialize()
2447 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2449 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2450 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2451 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2453 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2455 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2459 MUTATOR_DEFINITION(gamemode_ctf)
2461 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2462 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2463 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2464 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2465 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2466 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2467 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2468 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2469 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2470 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2471 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2472 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2473 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2474 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2475 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2476 MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2480 if(time > 1) // game loads at time 1
2481 error("This is a game type and it cannot be added at runtime.");
2485 MUTATOR_ONROLLBACK_OR_REMOVE
2487 // we actually cannot roll back ctf_Initialize here
2488 // BUT: we don't need to! If this gets called, adding always
2494 print("This is a game type and it cannot be removed at runtime.");