]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_ctf.qc
4b0f0546e5ef488b01fe2fd9dd0c50bb312f4a30
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_ctf.qc
1 // ================================================================
2 //  Official capture the flag game mode coding, reworked by Samual
3 //  Last updated: September, 2012
4 // ================================================================
5
6 void ctf_FakeTimeLimit(entity e, float t)
7 {
8         msg_entity = e;
9         WriteByte(MSG_ONE, 3); // svc_updatestat
10         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
11         if(t < 0)
12                 WriteCoord(MSG_ONE, autocvar_timelimit);
13         else
14                 WriteCoord(MSG_ONE, (t + 1) / 60);
15 }
16
17 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
18 {
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))) : "")));
22 }
23
24 void ctf_CaptureRecord(entity flag, entity player)
25 {
26         float cap_record = ctf_captimerecord;
27         float cap_time = (time - flag.ctf_pickuptime);
28         string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
29
30         // notify about shit
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)); }
35
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))
39         {
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);
44         }
45 }
46
47 void ctf_FlagcarrierWaypoints(entity player)
48 {
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));
53 }
54
55 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
56 {
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");
61
62         vector targpos;
63         if(current_height) // make sure we can actually do this arcing path
64         {
65                 targpos = (to + ('0 0 1' * current_height));
66                 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
67                 if(trace_fraction < 1)
68                 {
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"); } */
75                 }
76         }
77         else { targpos = to; }
78
79         //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
80
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); }
84 }
85
86 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
87 {
88         if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
89         {
90                 // directional tracing only
91                 float spreadlimit;
92                 makevectors(passer_angle);
93
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
98
99                 h = vlen(head_center - passer_center);
100                 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
101                 a = h * cos(ang);
102
103                 vector nearest_on_line = (passer_center + a * v_forward);
104                 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
105
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);
108
109                 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
110                         { return true; }
111                 else
112                         { return false; }
113         }
114         else { return true; }
115 }
116
117
118 // =======================
119 // CaptureShield Functions
120 // =======================
121
122 bool ctf_CaptureShield_CheckStatus(entity p)
123 {
124         float s, s2, s3, s4, se, se2, se3, se4, sr, ser;
125         entity e;
126         float players_worseeq, players_total;
127
128         if(ctf_captureshield_max_ratio <= 0)
129                 return false;
130
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);
135
136         sr = ((s - s2) + (s3 + s4));
137
138         if(sr >= -ctf_captureshield_min_negscore)
139                 return false;
140
141         players_total = players_worseeq = 0;
142         FOR_EACH_PLAYER(e)
143         {
144                 if(DIFF_TEAM(e, p))
145                         continue;
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);
150
151                 ser = ((se - se2) + (se3 + se4));
152
153                 if(ser <= sr)
154                         ++players_worseeq;
155                 ++players_total;
156         }
157
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
160
161         if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
162                 return false;
163
164         return true;
165 }
166
167 void ctf_CaptureShield_Update(entity player, bool wanted_status)
168 {
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
171         {
172                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
173                 player.ctf_captureshielded = updated_status;
174         }
175 }
176
177 bool ctf_CaptureShield_Customize()
178 {
179         if(!other.ctf_captureshielded) { return false; }
180         if(CTF_SAMETEAM(self, other)) { return false; }
181
182         return true;
183 }
184
185 void ctf_CaptureShield_Touch()
186 {
187         if(!other.ctf_captureshielded) { return; }
188         if(CTF_SAMETEAM(self, other)) { return; }
189
190         vector mymid = (self.absmin + self.absmax) * 0.5;
191         vector othermid = (other.absmin + other.absmax) * 0.5;
192
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); }
195 }
196
197 void ctf_CaptureShield_Spawn(entity flag)
198 {
199         entity shield = spawn();
200
201         shield.enemy = self;
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';
210         shield.scale = 0.5;
211
212         setorigin(shield, self.origin);
213         setmodel(shield, "models/ctf/shield.md3");
214         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
215 }
216
217
218 // ====================
219 // Drop/Pass/Throw Code
220 // ====================
221
222 void ctf_Handle_Drop(entity flag, entity player, int droptype)
223 {
224         // declarations
225         player = (player ? player : flag.pass_sender);
226
227         // main
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;
235
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);
240
241         // scoring
242         PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
243         PlayerScore_Add(player, SP_CTF_DROPS, 1);
244
245         // waypoints
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));
248
249         if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
250         {
251                 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
252                 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
253         }
254
255         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
256
257         if(droptype == DROP_PASS)
258         {
259                 flag.pass_distance = 0;
260                 flag.pass_sender = world;
261                 flag.pass_target = world;
262         }
263 }
264
265 void ctf_Handle_Retrieve(entity flag, entity player)
266 {
267         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
268         entity sender = flag.pass_sender;
269
270         // transfer flag to player
271         flag.owner = player;
272         flag.owner.flagcarried = flag;
273
274         // reset flag
275         if(player.vehicle)
276         {
277                 setattachment(flag, player.vehicle, "");
278                 setorigin(flag, VEHICLE_FLAG_OFFSET);
279                 flag.scale = VEHICLE_FLAG_SCALE;
280         }
281         else
282         {
283                 setattachment(flag, player, "");
284                 setorigin(flag, FLAG_CARRY_OFFSET);
285         }
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;
291
292         // messages and sounds
293         sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
294         ctf_EventLog("receive", flag.team, player);
295
296         FOR_EACH_REALPLAYER(tmp_player)
297         {
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);
304         }
305
306         // create new waypoint
307         ctf_FlagcarrierWaypoints(player);
308
309         sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
310         player.throw_antispam = sender.throw_antispam;
311
312         flag.pass_distance = 0;
313         flag.pass_sender = world;
314         flag.pass_target = world;
315 }
316
317 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
318 {
319         entity flag = player.flagcarried;
320         vector targ_origin, flag_velocity;
321
322         if(!flag) { return; }
323         if((droptype == DROP_PASS) && !receiver) { return; }
324
325         if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
326
327         // reset the flag
328         setattachment(flag, world, "");
329         setorigin(flag, player.origin + FLAG_DROP_OFFSET);
330         flag.owner.flagcarried = world;
331         flag.owner = world;
332         flag.solid = SOLID_TRIGGER;
333         flag.ctf_dropper = player;
334         flag.ctf_droptime = time;
335
336         flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
337
338         switch(droptype)
339         {
340                 case DROP_PASS:
341                 {
342                         // warpzone support:
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
348
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);
351
352                         // main
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;
358
359                         // other
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);
363                         break;
364                 }
365
366                 case DROP_THROW:
367                 {
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'));
369
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);
373                         break;
374                 }
375
376                 case DROP_RESET:
377                 {
378                         flag.velocity = '0 0 0'; // do nothing
379                         break;
380                 }
381
382                 default:
383                 case DROP_NORMAL:
384                 {
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);
387                         break;
388                 }
389         }
390
391         // kill old waypointsprite
392         WaypointSprite_Ping(player.wps_flagcarrier);
393         WaypointSprite_Kill(player.wps_flagcarrier);
394
395         if(player.wps_enemyflagcarrier)
396                 WaypointSprite_Kill(player.wps_enemyflagcarrier);
397
398         // captureshield
399         ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
400 }
401
402
403 // ==============
404 // Event Handlers
405 // ==============
406
407 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
408 {
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;
413
414         if(!player) { return; } // without someone to give the reward to, we can't possibly cap
415         if(CTF_DIFFTEAM(player, flag)) { return; }
416         
417         if(ctf_oneflag)
418         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
419         if(SAME_TEAM(tmp_entity, player))
420         {
421                 player_team_flag = tmp_entity;
422                 break;
423         }
424
425         nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
426
427         player.throw_prevtime = time;
428         player.throw_count = 0;
429
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);
434
435         switch(capturetype)
436         {
437                 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
438                 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
439                 default: break;
440         }
441
442         // scoring
443         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
444         PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
445
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);
450
451         // effects
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);
454
455         // other
456         if(capturetype == CAPTURE_NORMAL)
457         {
458                 WaypointSprite_Kill(player.wps_flagcarrier);
459                 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
460
461                 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
462                         { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
463         }
464
465         // reset the flag
466         player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
467         ctf_RespawnFlag(enemy_flag);
468 }
469
470 void ctf_Handle_Return(entity flag, entity player)
471 {
472         // messages and sounds
473         if(player.flags & FL_MONSTER)
474         {
475                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
476         }
477         else if(flag.team)
478         {
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);
481         }
482         sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
483         ctf_EventLog("return", flag.team, player);
484
485         // scoring
486         if(IS_PLAYER(player))
487         {
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
490
491                 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
492         }
493
494         TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
495
496         if(flag.ctf_dropper)
497         {
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
501         }
502
503         // other
504         if(player.flagcarried == flag)
505                 WaypointSprite_Kill(player.wps_flagcarrier);
506
507         // reset the flag
508         ctf_RespawnFlag(flag);
509 }
510
511 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
512 {
513         // declarations
514         float pickup_dropped_score; // used to calculate dropped pickup score
515         entity tmp_entity; // temporary entity
516
517         // attach the flag to the player
518         flag.owner = player;
519         player.flagcarried = flag;
520         if(player.vehicle)
521         {
522                 setattachment(flag, player.vehicle, "");
523                 setorigin(flag, VEHICLE_FLAG_OFFSET);
524                 flag.scale = VEHICLE_FLAG_SCALE;
525         }
526         else
527         {
528                 setattachment(flag, player, "");
529                 setorigin(flag, FLAG_CARRY_OFFSET);
530         }
531
532         // flag setup
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;
538
539         switch(pickuptype)
540         {
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
543                 default: break;
544         }
545
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)); }
552
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);
554         
555         if(!flag.team)
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);
560         
561         if(flag.team)
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);
567         else
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);
569
570         sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
571
572         // scoring
573         PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
574         nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
575         switch(pickuptype)
576         {
577                 case PICKUP_BASE:
578                 {
579                         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
580                         ctf_EventLog("steal", flag.team, player);
581                         break;
582                 }
583
584                 case PICKUP_DROPPED:
585                 {
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);
591                         break;
592                 }
593
594                 default: break;
595         }
596
597         // speedrunning
598         if(pickuptype == PICKUP_BASE)
599         {
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);
603         }
604
605         // effects
606         pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
607
608         // waypoints
609         if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
610         ctf_FlagcarrierWaypoints(player);
611         WaypointSprite_Ping(player.wps_flagcarrier);
612 }
613
614
615 // ===================
616 // Main Flag Functions
617 // ===================
618
619 void ctf_CheckFlagReturn(entity flag, int returntype)
620 {
621         if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
622         {
623                 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
624
625                 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
626                 {
627                         switch(returntype)
628                         {
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;
633
634                                 default:
635                                 case RETURN_TIMEOUT:
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; }
637                         }
638                         sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
639                         ctf_EventLog("returned", flag.team, world);
640                         ctf_RespawnFlag(flag);
641                 }
642         }
643 }
644
645 bool ctf_Stalemate_Customize()
646 {
647         // make spectators see what the player would see
648         entity e, wp_owner;
649         e = WaypointSprite_getviewentity(other);
650         wp_owner = self.owner;
651
652         // team waypoints
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; }
656
657         return true;
658 }
659
660 void ctf_CheckStalemate(void)
661 {
662         // declarations
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;
664         entity tmp_entity;
665
666         entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
667
668         // build list of stale flags
669         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
670         {
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
674                 {
675                         tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
676                         ctf_staleflaglist = tmp_entity;
677
678                         switch(tmp_entity.team)
679                         {
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;
685                         }
686                 }
687         }
688
689         if(ctf_oneflag)
690                 stale_flags = (stale_neutral_flags >= 1);
691         else
692                 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
693
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; }
702
703         // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
704         if(ctf_stalemate)
705         {
706                 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
707                 {
708                         if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
709                         {
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;
712                         }
713                 }
714
715                 if (!wpforenemy_announced)
716                 {
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));
719
720                         wpforenemy_announced = true;
721                 }
722         }
723 }
724
725 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
726 {
727         if(ITEM_DAMAGE_NEEDKILL(deathtype))
728         {
729                 if(autocvar_g_ctf_flag_return_damage_delay)
730                 {
731                         self.ctf_flagdamaged = true;
732                 }
733                 else
734                 {
735                         self.health = 0;
736                         ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
737                 }
738                 return;
739         }
740         if(autocvar_g_ctf_flag_return_damage)
741         {
742                 // reduce health and check if it should be returned
743                 self.health = self.health - damage;
744                 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
745                 return;
746         }
747 }
748
749 void ctf_FlagThink()
750 {
751         // declarations
752         entity tmp_entity;
753
754         self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
755
756         // captureshield
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
760
761         // sanity checks
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); }
767
768         switch(self.ctf_status) // reset flag angles in case warpzones adjust it
769         {
770                 case FLAG_DROPPED:
771                 {
772                         self.angles = '0 0 0';
773                         break;
774                 }
775
776                 default: break;
777         }
778
779         // main think method
780         switch(self.ctf_status)
781         {
782                 case FLAG_BASE:
783                 {
784                         if(autocvar_g_ctf_dropped_capture_radius)
785                         {
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);
791                         }
792                         return;
793                 }
794
795                 case FLAG_DROPPED:
796                 {
797                         if(autocvar_g_ctf_flag_dropped_floatinwater)
798                         {
799                                 vector midpoint = ((self.absmin + self.absmax) * 0.5);
800                                 if(pointcontents(midpoint) == CONTENT_WATER)
801                                 {
802                                         self.velocity = self.velocity * 0.5;
803
804                                         if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
805                                                 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
806                                         else
807                                                 { self.movetype = MOVETYPE_FLY; }
808                                 }
809                                 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
810                         }
811                         if(autocvar_g_ctf_flag_return_dropped)
812                         {
813                                 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
814                                 {
815                                         self.health = 0;
816                                         ctf_CheckFlagReturn(self, RETURN_DROPPED);
817                                         return;
818                                 }
819                         }
820                         if(self.ctf_flagdamaged)
821                         {
822                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
823                                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
824                                 return;
825                         }
826                         else if(autocvar_g_ctf_flag_return_time)
827                         {
828                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
829                                 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
830                                 return;
831                         }
832                         return;
833                 }
834
835                 case FLAG_CARRY:
836                 {
837                         if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
838                         {
839                                 self.health = 0;
840                                 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
841
842                                 tmp_entity = self;
843                                 self = self.owner;
844                                 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
845                                 ImpulseCommands();
846                                 self = tmp_entity;
847                         }
848                         if(autocvar_g_ctf_stalemate)
849                         {
850                                 if(time >= wpforenemy_nextthink)
851                                 {
852                                         ctf_CheckStalemate();
853                                         wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
854                                 }
855                         }
856                         if(CTF_SAMETEAM(self, self.owner) && self.team)
857                         {
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);
862                         }
863                         return;
864                 }
865
866                 case FLAG_PASSING:
867                 {
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);
871
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))
878                         {
879                                 // give up, pass failed
880                                 ctf_Handle_Drop(self, world, DROP_PASS);
881                         }
882                         else
883                         {
884                                 // still a viable target, go for it
885                                 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
886                         }
887                         return;
888                 }
889
890                 default: // this should never happen
891                 {
892                         dprint("ctf_FlagThink(): Flag exists with no status?\n");
893                         return;
894                 }
895         }
896 }
897
898 void ctf_FlagTouch()
899 {
900         if(gameover) { return; }
901         if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
902
903         entity toucher = other, tmp_entity;
904         bool is_not_monster = (!(toucher.flags & FL_MONSTER)), num_perteam = 0;
905
906         // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
907         if(ITEM_TOUCH_NEEDKILL())
908         {
909                 if(!autocvar_g_ctf_flag_return_damage_delay)
910                 {
911                         self.health = 0;
912                         ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
913                 }
914                 if(!self.ctf_flagdamaged) { return; }
915         }
916
917         FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
918
919         // special touch behaviors
920         if(toucher.frozen) { return; }
921         else if(toucher.vehicle_flags & VHF_ISVEHICLE)
922         {
923                 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
924                         toucher = toucher.owner; // the player is actually the vehicle owner, not other
925                 else
926                         return; // do nothing
927         }
928         else if(toucher.flags & FL_MONSTER)
929         {
930                 if(!autocvar_g_ctf_allow_monster_touch)
931                         return; // do nothing
932         }
933         else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
934         {
935                 if(time > self.wait) // if we haven't in a while, play a sound/effect
936                 {
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;
940                 }
941                 return;
942         }
943         else if(toucher.deadflag != DEAD_NO) { return; }
944
945         switch(self.ctf_status)
946         {
947                 case FLAG_BASE:
948                 {
949                         if(ctf_oneflag)
950                         {
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
955                         }
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
960                         break;
961                 }
962
963                 case FLAG_DROPPED:
964                 {
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
969                         break;
970                 }
971
972                 case FLAG_CARRY:
973                 {
974                         dprint("Someone touched a flag even though it was being carried?\n");
975                         break;
976                 }
977
978                 case FLAG_PASSING:
979                 {
980                         if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
981                         {
982                                 if(DIFF_TEAM(toucher, self.pass_sender))
983                                         ctf_Handle_Return(self, toucher);
984                                 else
985                                         ctf_Handle_Retrieve(self, toucher);
986                         }
987                         break;
988                 }
989         }
990 }
991
992 .float last_respawn;
993 void ctf_RespawnFlag(entity flag)
994 {
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..."); }
998
999         flag.last_respawn = time;
1000
1001         // reset the player (if there is one)
1002         if((flag.owner) && (flag.owner.flagcarried == flag))
1003         {
1004                 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1005                 WaypointSprite_Kill(flag.wps_flagcarrier);
1006
1007                 flag.owner.flagcarried = world;
1008
1009                 if(flag.speedrunning)
1010                         ctf_FakeTimeLimit(flag.owner, -1);
1011         }
1012
1013         if((flag.owner) && (flag.owner.vehicle))
1014                 flag.scale = FLAG_SCALE;
1015
1016         if(flag.ctf_status == FLAG_DROPPED)
1017                 { WaypointSprite_Kill(flag.wps_flagdropped); }
1018
1019         // reset the flag
1020         setattachment(flag, world, "");
1021         setorigin(flag, flag.ctf_spawnorigin);
1022
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;
1030
1031         flag.ctf_status = FLAG_BASE;
1032         flag.owner = world;
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;
1040
1041         ctf_CheckStalemate();
1042 }
1043
1044 void ctf_Reset()
1045 {
1046         if(self.owner)
1047                 if(IS_PLAYER(self.owner))
1048                         ctf_Handle_Throw(self.owner, world, DROP_RESET);
1049
1050         ctf_RespawnFlag(self);
1051 }
1052
1053 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1054 {
1055         // bot waypoints
1056         waypoint_spawnforitem_force(self, self.origin);
1057         self.nearestwaypointtimeout = 0; // activate waypointing again
1058         self.bot_basewaypoint = self.nearestwaypoint;
1059
1060         // waypointsprites
1061         string basename = "base";
1062
1063         switch(self.team)
1064         {
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;
1070         }
1071
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'));
1074
1075         // captureshield setup
1076         ctf_CaptureShield_Spawn(self);
1077 }
1078
1079 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1080 {
1081         // declarations
1082         self = flag; // for later usage with droptofloor()
1083
1084         // main setup
1085         flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1086         ctf_worldflaglist = flag;
1087
1088         setattachment(flag, world, "");
1089
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;
1113
1114         // appearence
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
1121
1122         // sound
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
1130
1131         // precache
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");
1142
1143         // appearence
1144         setmodel(flag, flag.model); // precision set below
1145         setsize(flag, FLAG_MIN, FLAG_MAX);
1146         setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1147
1148         if(autocvar_g_ctf_flag_glowtrails)
1149         {
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;
1153         }
1154
1155         flag.effects |= EF_LOWPRECISION;
1156         if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1157         if(autocvar_g_ctf_dynamiclights)
1158         {
1159                 switch(teamnumber)
1160                 {
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;
1166                 }
1167         }
1168         
1169         // flag placement
1170         if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1171         {
1172                 flag.dropped_origin = flag.origin;
1173                 flag.noalign = true;
1174                 flag.movetype = MOVETYPE_NONE;
1175         }
1176         else // drop to floor, automatically find a platform and set that as spawn origin
1177         {
1178                 flag.noalign = false;
1179                 self = flag;
1180                 droptofloor();
1181                 flag.movetype = MOVETYPE_TOSS;
1182         }
1183
1184         InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1185 }
1186
1187
1188 // ================
1189 // Bot player logic
1190 // ================
1191
1192 // NOTE: LEGACY CODE, needs to be re-written!
1193
1194 void havocbot_calculate_middlepoint()
1195 {
1196         entity f;
1197         vector s = '0 0 0';
1198         vector fo = '0 0 0';
1199         float n = 0;
1200
1201         f = ctf_worldflaglist;
1202         while (f)
1203         {
1204                 fo = f.origin;
1205                 s = s + fo;
1206                 f = f.ctf_worldflagnext;
1207         }
1208         if(!n)
1209                 return;
1210         havocbot_ctf_middlepoint = s * (1.0 / n);
1211         havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
1212 }
1213
1214
1215 entity havocbot_ctf_find_flag(entity bot)
1216 {
1217         entity f;
1218         f = ctf_worldflaglist;
1219         while (f)
1220         {
1221                 if (CTF_SAMETEAM(bot, f))
1222                         return f;
1223                 f = f.ctf_worldflagnext;
1224         }
1225         return world;
1226 }
1227
1228 entity havocbot_ctf_find_enemy_flag(entity bot)
1229 {
1230         entity f;
1231         f = ctf_worldflaglist;
1232         while (f)
1233         {
1234                 if(ctf_oneflag)
1235                 {
1236                         if(CTF_DIFFTEAM(bot, f))
1237                         {
1238                                 if(f.team)
1239                                 {
1240                                         if(bot.flagcarried)
1241                                                 return f;
1242                                 }
1243                                 else if(!bot.flagcarried)
1244                                         return f;
1245                         }
1246                 }
1247                 else if (CTF_DIFFTEAM(bot, f))
1248                         return f;
1249                 f = f.ctf_worldflagnext;
1250         }
1251         return world;
1252 }
1253
1254 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1255 {
1256         if (!teamplay)
1257                 return 0;
1258
1259         int c = 0;
1260         entity head;
1261
1262         FOR_EACH_PLAYER(head)
1263         {
1264                 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1265                         continue;
1266
1267                 if(vlen(head.origin - org) < tc_radius)
1268                         ++c;
1269         }
1270
1271         return c;
1272 }
1273
1274 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1275 {
1276         entity head;
1277         head = ctf_worldflaglist;
1278         while (head)
1279         {
1280                 if (CTF_SAMETEAM(self, head))
1281                         break;
1282                 head = head.ctf_worldflagnext;
1283         }
1284         if (head)
1285                 navigation_routerating(head, ratingscale, 10000);
1286 }
1287
1288 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1289 {
1290         entity head;
1291         head = ctf_worldflaglist;
1292         while (head)
1293         {
1294                 if (CTF_SAMETEAM(self, head))
1295                         break;
1296                 head = head.ctf_worldflagnext;
1297         }
1298         if (!head)
1299                 return;
1300
1301         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1302 }
1303
1304 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1305 {
1306         entity head;
1307         head = ctf_worldflaglist;
1308         while (head)
1309         {
1310                 if(ctf_oneflag)
1311                 {
1312                         if(CTF_DIFFTEAM(self, head))
1313                         {
1314                                 if(head.team)
1315                                 {
1316                                         if(self.flagcarried)
1317                                                 break;
1318                                 }
1319                                 else if(!self.flagcarried)
1320                                         break;
1321                         }
1322                 }
1323                 else if(CTF_DIFFTEAM(self, head))
1324                         break;
1325                 head = head.ctf_worldflagnext;
1326         }
1327         if (head)
1328                 navigation_routerating(head, ratingscale, 10000);
1329 }
1330
1331 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1332 {
1333         if (!bot_waypoints_for_items)
1334         {
1335                 havocbot_goalrating_ctf_enemyflag(ratingscale);
1336                 return;
1337         }
1338
1339         entity head;
1340
1341         head = havocbot_ctf_find_enemy_flag(self);
1342
1343         if (!head)
1344                 return;
1345
1346         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1347 }
1348
1349 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1350 {
1351         entity mf;
1352
1353         mf = havocbot_ctf_find_flag(self);
1354
1355         if(mf.ctf_status == FLAG_BASE)
1356                 return;
1357
1358         if(mf.tag_entity)
1359                 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1360 }
1361
1362 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1363 {
1364         entity head;
1365         head = ctf_worldflaglist;
1366         while (head)
1367         {
1368                 // flag is out in the field
1369                 if(head.ctf_status != FLAG_BASE)
1370                 if(head.tag_entity==world)      // dropped
1371                 {
1372                         if(df_radius)
1373                         {
1374                                 if(vlen(org-head.origin)<df_radius)
1375                                         navigation_routerating(head, ratingscale, 10000);
1376                         }
1377                         else
1378                                 navigation_routerating(head, ratingscale, 10000);
1379                 }
1380
1381                 head = head.ctf_worldflagnext;
1382         }
1383 }
1384
1385 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1386 {
1387         entity head;
1388         float t;
1389         head = findchainfloat(bot_pickup, true);
1390         while (head)
1391         {
1392                 // gather health and armor only
1393                 if (head.solid)
1394                 if (head.health || head.armorvalue)
1395                 if (vlen(head.origin - org) < sradius)
1396                 {
1397                         // get the value of the item
1398                         t = head.bot_pickupevalfunc(self, head) * 0.0001;
1399                         if (t > 0)
1400                                 navigation_routerating(head, t * ratingscale, 500);
1401                 }
1402                 head = head.chain;
1403         }
1404 }
1405
1406 void havocbot_ctf_reset_role(entity bot)
1407 {
1408         float cdefense, cmiddle, coffense;
1409         entity mf, ef, head;
1410         float c;
1411
1412         if(bot.deadflag != DEAD_NO)
1413                 return;
1414
1415         if(vlen(havocbot_ctf_middlepoint)==0)
1416                 havocbot_calculate_middlepoint();
1417
1418         // Check ctf flags
1419         if (bot.flagcarried)
1420         {
1421                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1422                 return;
1423         }
1424
1425         mf = havocbot_ctf_find_flag(bot);
1426         ef = havocbot_ctf_find_enemy_flag(bot);
1427
1428         // Retrieve stolen flag
1429         if(mf.ctf_status!=FLAG_BASE)
1430         {
1431                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1432                 return;
1433         }
1434
1435         // If enemy flag is taken go to the middle to intercept pursuers
1436         if(ef.ctf_status!=FLAG_BASE)
1437         {
1438                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1439                 return;
1440         }
1441
1442         // if there is only me on the team switch to offense
1443         c = 0;
1444         FOR_EACH_PLAYER(head)
1445         if(SAME_TEAM(head, bot))
1446                 ++c;
1447
1448         if(c==1)
1449         {
1450                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1451                 return;
1452         }
1453
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);
1457
1458         // Count mates on defense position
1459         cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1460
1461         // Count mates on offense position
1462         coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1463
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);
1468         else
1469                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1470 }
1471
1472 void havocbot_role_ctf_carrier()
1473 {
1474         if(self.deadflag != DEAD_NO)
1475         {
1476                 havocbot_ctf_reset_role(self);
1477                 return;
1478         }
1479
1480         if (self.flagcarried == world)
1481         {
1482                 havocbot_ctf_reset_role(self);
1483                 return;
1484         }
1485
1486         if (self.bot_strategytime < time)
1487         {
1488                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1489
1490                 navigation_goalrating_start();
1491                 if(ctf_oneflag)
1492                         havocbot_goalrating_ctf_enemybase(50000);
1493                 else
1494                         havocbot_goalrating_ctf_ourbase(50000);
1495
1496                 if(self.health<100)
1497                         havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1498
1499                 navigation_goalrating_end();
1500
1501                 if (self.navigation_hasgoals)
1502                         self.havocbot_cantfindflag = time + 10;
1503                 else if (time > self.havocbot_cantfindflag)
1504                 {
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');
1508                         return;
1509                 }
1510         }
1511 }
1512
1513 void havocbot_role_ctf_escort()
1514 {
1515         entity mf, ef;
1516
1517         if(self.deadflag != DEAD_NO)
1518         {
1519                 havocbot_ctf_reset_role(self);
1520                 return;
1521         }
1522
1523         if (self.flagcarried)
1524         {
1525                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1526                 return;
1527         }
1528
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)
1532         {
1533                 self.havocbot_role = self.havocbot_previous_role;
1534                 self.havocbot_role_timeout = 0;
1535                 return;
1536         }
1537
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)
1542         {
1543                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1544                 return;
1545         }
1546
1547         // Set the role timeout if necessary
1548         if (!self.havocbot_role_timeout)
1549         {
1550                 self.havocbot_role_timeout = time + random() * 30 + 60;
1551         }
1552
1553         // If nothing happened just switch to previous role
1554         if (time > self.havocbot_role_timeout)
1555         {
1556                 self.havocbot_role = self.havocbot_previous_role;
1557                 self.havocbot_role_timeout = 0;
1558                 return;
1559         }
1560
1561         // Chase the flag carrier
1562         if (self.bot_strategytime < time)
1563         {
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();
1570         }
1571 }
1572
1573 void havocbot_role_ctf_offense()
1574 {
1575         entity mf, ef;
1576         vector pos;
1577
1578         if(self.deadflag != DEAD_NO)
1579         {
1580                 havocbot_ctf_reset_role(self);
1581                 return;
1582         }
1583
1584         if (self.flagcarried)
1585         {
1586                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1587                 return;
1588         }
1589
1590         // Check flags
1591         mf = havocbot_ctf_find_flag(self);
1592         ef = havocbot_ctf_find_enemy_flag(self);
1593
1594         // Own flag stolen
1595         if(mf.ctf_status!=FLAG_BASE)
1596         {
1597                 if(mf.tag_entity)
1598                         pos = mf.tag_entity.origin;
1599                 else
1600                         pos = mf.origin;
1601
1602                 // Try to get it if closer than the enemy base
1603                 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1604                 {
1605                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1606                         return;
1607                 }
1608         }
1609
1610         // Escort flag carrier
1611         if(ef.ctf_status!=FLAG_BASE)
1612         {
1613                 if(ef.tag_entity)
1614                         pos = ef.tag_entity.origin;
1615                 else
1616                         pos = ef.origin;
1617
1618                 if(vlen(pos-mf.dropped_origin)>700)
1619                 {
1620                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1621                         return;
1622                 }
1623         }
1624
1625         // About to fail, switch to middlefield
1626         if(self.health<50)
1627         {
1628                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1629                 return;
1630         }
1631
1632         // Set the role timeout if necessary
1633         if (!self.havocbot_role_timeout)
1634                 self.havocbot_role_timeout = time + 120;
1635
1636         if (time > self.havocbot_role_timeout)
1637         {
1638                 havocbot_ctf_reset_role(self);
1639                 return;
1640         }
1641
1642         if (self.bot_strategytime < time)
1643         {
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();
1651         }
1652 }
1653
1654 // Retriever (temporary role):
1655 void havocbot_role_ctf_retriever()
1656 {
1657         entity mf;
1658
1659         if(self.deadflag != DEAD_NO)
1660         {
1661                 havocbot_ctf_reset_role(self);
1662                 return;
1663         }
1664
1665         if (self.flagcarried)
1666         {
1667                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1668                 return;
1669         }
1670
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)
1674         {
1675                 havocbot_ctf_reset_role(self);
1676                 return;
1677         }
1678
1679         if (!self.havocbot_role_timeout)
1680                 self.havocbot_role_timeout = time + 20;
1681
1682         if (time > self.havocbot_role_timeout)
1683         {
1684                 havocbot_ctf_reset_role(self);
1685                 return;
1686         }
1687
1688         if (self.bot_strategytime < time)
1689         {
1690                 float rt_radius;
1691                 rt_radius = 10000;
1692
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();
1700         }
1701 }
1702
1703 void havocbot_role_ctf_middle()
1704 {
1705         entity mf;
1706
1707         if(self.deadflag != DEAD_NO)
1708         {
1709                 havocbot_ctf_reset_role(self);
1710                 return;
1711         }
1712
1713         if (self.flagcarried)
1714         {
1715                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1716                 return;
1717         }
1718
1719         mf = havocbot_ctf_find_flag(self);
1720         if(mf.ctf_status!=FLAG_BASE)
1721         {
1722                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1723                 return;
1724         }
1725
1726         if (!self.havocbot_role_timeout)
1727                 self.havocbot_role_timeout = time + 10;
1728
1729         if (time > self.havocbot_role_timeout)
1730         {
1731                 havocbot_ctf_reset_role(self);
1732                 return;
1733         }
1734
1735         if (self.bot_strategytime < time)
1736         {
1737                 vector org;
1738
1739                 org = havocbot_ctf_middlepoint;
1740                 org.z = self.origin.z;
1741
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();
1751         }
1752 }
1753
1754 void havocbot_role_ctf_defense()
1755 {
1756         entity mf;
1757
1758         if(self.deadflag != DEAD_NO)
1759         {
1760                 havocbot_ctf_reset_role(self);
1761                 return;
1762         }
1763
1764         if (self.flagcarried)
1765         {
1766                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1767                 return;
1768         }
1769
1770         // If own flag was captured
1771         mf = havocbot_ctf_find_flag(self);
1772         if(mf.ctf_status!=FLAG_BASE)
1773         {
1774                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1775                 return;
1776         }
1777
1778         if (!self.havocbot_role_timeout)
1779                 self.havocbot_role_timeout = time + 30;
1780
1781         if (time > self.havocbot_role_timeout)
1782         {
1783                 havocbot_ctf_reset_role(self);
1784                 return;
1785         }
1786         if (self.bot_strategytime < time)
1787         {
1788                 float mp_radius;
1789                 vector org;
1790
1791                 org = mf.dropped_origin;
1792                 mp_radius = havocbot_ctf_middlepoint_radius;
1793
1794                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1795                 navigation_goalrating_start();
1796
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)
1801                 {
1802                         if(head.deadflag!=DEAD_NO)
1803                                 continue;
1804
1805                         distance = vlen(org - head.origin);
1806                         if(distance<bestdistance)
1807                         {
1808                                 closestplayer = head;
1809                                 bestdistance = distance;
1810                         }
1811                 }
1812
1813                 if(closestplayer)
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);
1818
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();
1825         }
1826 }
1827
1828 void havocbot_role_ctf_setrole(entity bot, float role)
1829 {
1830         dprint(strcat(bot.netname," switched to "));
1831         switch(role)
1832         {
1833                 case HAVOCBOT_CTF_ROLE_CARRIER:
1834                         dprint("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;
1839                         break;
1840                 case HAVOCBOT_CTF_ROLE_DEFENSE:
1841                         dprint("defense");
1842                         bot.havocbot_role = havocbot_role_ctf_defense;
1843                         bot.havocbot_role_timeout = 0;
1844                         break;
1845                 case HAVOCBOT_CTF_ROLE_MIDDLE:
1846                         dprint("middle");
1847                         bot.havocbot_role = havocbot_role_ctf_middle;
1848                         bot.havocbot_role_timeout = 0;
1849                         break;
1850                 case HAVOCBOT_CTF_ROLE_OFFENSE:
1851                         dprint("offense");
1852                         bot.havocbot_role = havocbot_role_ctf_offense;
1853                         bot.havocbot_role_timeout = 0;
1854                         break;
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;
1861                         break;
1862                 case HAVOCBOT_CTF_ROLE_ESCORT:
1863                         dprint("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;
1868                         break;
1869         }
1870         dprint("\n");
1871 }
1872
1873
1874 // ==============
1875 // Hook Functions
1876 // ==============
1877
1878 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1879 {
1880         entity flag;
1881         int t = 0, t2 = 0, t3 = 0;
1882
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);
1890
1891         // scan through all the flags and notify the client about them
1892         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1893         {
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; }
1899
1900                 switch(flag.ctf_status)
1901                 {
1902                         case FLAG_PASSING:
1903                         case FLAG_CARRY:
1904                         {
1905                                 if((flag.owner == self) || (flag.pass_sender == self))
1906                                         self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1907                                 else 
1908                                         self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1909                                 break;
1910                         }
1911                         case FLAG_DROPPED:
1912                         {
1913                                 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1914                                 break;
1915                         }
1916                 }
1917         }
1918
1919         // item for stopping players from capturing the flag too often
1920         if(self.ctf_captureshielded)
1921                 self.ctf_flagstatus |= CTF_SHIELDED;
1922
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));
1926
1927         return false;
1928 }
1929
1930 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1931 {
1932         if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1933         {
1934                 if(frag_target == frag_attacker) // damage done to yourself
1935                 {
1936                         frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1937                         frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1938                 }
1939                 else // damage done to everyone else
1940                 {
1941                         frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1942                         frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1943                 }
1944         }
1945         else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1946         {
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)
1949                 {
1950                         frag_target.wps_helpme_time = time;
1951                         WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1952                 }
1953                 // todo: add notification for when flag carrier needs help?
1954         }
1955         return false;
1956 }
1957
1958 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1959 {
1960         if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1961         {
1962                 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1963                 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1964         }
1965
1966         if(frag_target.flagcarried)
1967         {
1968                 entity tmp_entity = frag_target.flagcarried;
1969                 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
1970                 tmp_entity.ctf_dropper = world;
1971         }
1972
1973         return false;
1974 }
1975
1976 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1977 {
1978         frag_score = 0;
1979         return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1980 }
1981
1982 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1983 {
1984         entity flag; // temporary entity for the search method
1985
1986         if(self.flagcarried)
1987                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1988
1989         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1990         {
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; }
1994         }
1995
1996         return false;
1997 }
1998
1999 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2000 {
2001         if(self.flagcarried)
2002         if(!autocvar_g_ctf_portalteleport)
2003                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2004
2005         return false;
2006 }
2007
2008 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2009 {
2010         if(MUTATOR_RETURNVALUE || gameover) { return false; }
2011
2012         entity player = self;
2013
2014         if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2015         {
2016                 // pass the flag to a team mate
2017                 if(autocvar_g_ctf_pass)
2018                 {
2019                         entity head, closest_target = world;
2020                         head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2021
2022                         while(head) // find the closest acceptable target to pass to
2023                         {
2024                                 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2025                                 if(head != player && SAME_TEAM(head, player))
2026                                 if(!head.speedrunning && !head.vehicle)
2027                                 {
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);
2031
2032                                         if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2033                                         {
2034                                                 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2035                                                 {
2036                                                         if(IS_BOT_CLIENT(head))
2037                                                         {
2038                                                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2039                                                                 ctf_Handle_Throw(head, player, DROP_PASS);
2040                                                         }
2041                                                         else
2042                                                         {
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);
2045                                                         }
2046                                                         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2047                                                         return true;
2048                                                 }
2049                                                 else if(player.flagcarried)
2050                                                 {
2051                                                         if(closest_target)
2052                                                         {
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; }
2056                                                         }
2057                                                         else { closest_target = head; }
2058                                                 }
2059                                         }
2060                                 }
2061                                 head = head.chain;
2062                         }
2063
2064                         if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2065                 }
2066
2067                 // throw the flag in front of you
2068                 if(autocvar_g_ctf_throw && player.flagcarried)
2069                 {
2070                         if(player.throw_count == -1)
2071                         {
2072                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2073                                 {
2074                                         player.throw_prevtime = time;
2075                                         player.throw_count = 1;
2076                                         ctf_Handle_Throw(player, world, DROP_THROW);
2077                                         return true;
2078                                 }
2079                                 else
2080                                 {
2081                                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2082                                         return false;
2083                                 }
2084                         }
2085                         else
2086                         {
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; }
2090
2091                                 player.throw_prevtime = time;
2092                                 ctf_Handle_Throw(player, world, DROP_THROW);
2093                                 return true;
2094                         }
2095                 }
2096         }
2097
2098         return false;
2099 }
2100
2101 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2102 {
2103         if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2104         {
2105                 self.wps_helpme_time = time;
2106                 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2107         }
2108         else // create a normal help me waypointsprite
2109         {
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);
2112         }
2113
2114         return true;
2115 }
2116
2117 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2118 {
2119         if(vh_player.flagcarried)
2120         {
2121                 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2122                 {
2123                         ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2124                 }
2125                 else
2126                 {
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';
2131                 }
2132                 return true;
2133         }
2134
2135         return false;
2136 }
2137
2138 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2139 {
2140         if(vh_player.flagcarried)
2141         {
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';
2146                 return true;
2147         }
2148
2149         return false;
2150 }
2151
2152 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2153 {
2154         if(self.flagcarried)
2155         {
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);
2158                 return true;
2159         }
2160
2161         return false;
2162 }
2163
2164 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2165 {
2166         entity flag; // temporary entity for the search method
2167
2168         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2169         {
2170                 switch(flag.ctf_status)
2171                 {
2172                         case FLAG_DROPPED:
2173                         case FLAG_PASSING:
2174                         {
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
2180
2181                                 //dprint("stopping the ", flag.netname, " from moving.\n");
2182                                 break;
2183                         }
2184
2185                         default:
2186                         case FLAG_BASE:
2187                         case FLAG_CARRY:
2188                         {
2189                                 // do nothing for these flags
2190                                 break;
2191                         }
2192                 }
2193         }
2194
2195         return false;
2196 }
2197
2198 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2199 {
2200         havocbot_ctf_reset_role(self);
2201         return true;
2202 }
2203
2204 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2205 {
2206         //ret_float = ctf_teams;
2207         ret_string = "ctf_team";
2208         return true;
2209 }
2210
2211 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2212 {
2213         self.ctf_flagstatus = other.ctf_flagstatus;
2214         return false;
2215 }
2216
2217
2218 // ==========
2219 // Spawnfuncs
2220 // ==========
2221
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()
2226 {
2227         if(g_assault) { remove(self); return; }
2228
2229         self.team = NUM_TEAM_1; // red
2230         spawnfunc_info_player_deathmatch();
2231 }
2232
2233
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()
2238 {
2239         if(g_assault) { remove(self); return; }
2240
2241         self.team = NUM_TEAM_2; // blue
2242         spawnfunc_info_player_deathmatch();
2243 }
2244
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()
2249 {
2250         if(g_assault) { remove(self); return; }
2251
2252         self.team = NUM_TEAM_3; // yellow
2253         spawnfunc_info_player_deathmatch();
2254 }
2255
2256
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()
2261 {
2262         if(g_assault) { remove(self); return; }
2263
2264         self.team = NUM_TEAM_4; // purple
2265         spawnfunc_info_player_deathmatch();
2266 }
2267
2268 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2269 CTF flag for team one (Red).
2270 Keys:
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()
2280 {
2281         if(!g_ctf) { remove(self); return; }
2282
2283         ctf_FlagSetup(NUM_TEAM_1, self);
2284 }
2285
2286 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2287 CTF flag for team two (Blue).
2288 Keys:
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()
2298 {
2299         if(!g_ctf) { remove(self); return; }
2300
2301         ctf_FlagSetup(NUM_TEAM_2, self);
2302 }
2303
2304 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2305 CTF flag for team three (Yellow).
2306 Keys: 
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()
2316 {
2317         if(!g_ctf) { remove(self); return; }
2318
2319         ctf_FlagSetup(NUM_TEAM_3, self);
2320 }
2321
2322 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2323 CTF flag for team four (Pink).
2324 Keys: 
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()
2334 {
2335         if(!g_ctf) { remove(self); return; }
2336
2337         ctf_FlagSetup(NUM_TEAM_4, self);
2338 }
2339
2340 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2341 CTF flag (Neutral).
2342 Keys: 
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()
2352 {
2353         if(!g_ctf) { remove(self); return; }
2354         if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2355
2356         ctf_FlagSetup(0, self);
2357 }
2358
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.
2362 Keys:
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()
2366 {
2367         if(!g_ctf) { remove(self); return; }
2368
2369         self.classname = "ctf_team";
2370         self.team = self.cnt + 1;
2371 }
2372
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();  }
2380
2381
2382 // ==============
2383 // Initialization
2384 // ==============
2385
2386 // scoreboard setup
2387 void ctf_ScoreRules(float teams)
2388 {
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();
2399 }
2400
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)
2403 {
2404         entity oldself;
2405         oldself = self;
2406         self = spawn();
2407         self.classname = "ctf_team";
2408         self.netname = teamname;
2409         self.cnt = teamcolor;
2410
2411         spawnfunc_ctf_team();
2412
2413         self = oldself;
2414 }
2415
2416 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2417 {
2418         ctf_teams = 2;
2419
2420         entity tmp_entity;
2421         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2422         {
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; }
2426         }
2427
2428         ctf_teams = bound(2, ctf_teams, 4);
2429
2430         // if no teams are found, spawn defaults
2431         if(find(world, classname, "ctf_team") == world)
2432         {
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);
2436                 if(ctf_teams >= 3)
2437                         ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2438                 if(ctf_teams >= 4)
2439                         ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2440         }
2441
2442         ctf_ScoreRules(ctf_teams);
2443 }
2444
2445 void ctf_Initialize()
2446 {
2447         ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2448
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;
2452
2453         addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2454
2455         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2456 }
2457
2458
2459 MUTATOR_DEFINITION(gamemode_ctf)
2460 {
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);
2477
2478         MUTATOR_ONADD
2479         {
2480                 if(time > 1) // game loads at time 1
2481                         error("This is a game type and it cannot be added at runtime.");
2482                 ctf_Initialize();
2483         }
2484
2485         MUTATOR_ONROLLBACK_OR_REMOVE
2486         {
2487                 // we actually cannot roll back ctf_Initialize here
2488                 // BUT: we don't need to! If this gets called, adding always
2489                 // succeeds.
2490         }
2491
2492         MUTATOR_ONREMOVE
2493         {
2494                 print("This is a game type and it cannot be removed at runtime.");
2495                 return -1;
2496         }
2497
2498         return 0;
2499 }