]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_ctf.qc
Add player's team to the log print
[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, float 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 float 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 float 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, float wanted_status)
168 {
169         float updated_status = ctf_CaptureShield_CheckStatus(player);
170         if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
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 float ctf_CaptureShield_Customize()
178 {
179         if(self.enemy.active != ACTIVE_ACTIVE) { return TRUE; }
180         if(!other.ctf_captureshielded) { return FALSE; }
181         if(CTF_SAMETEAM(self, other)) { return FALSE; }
182
183         return TRUE;
184 }
185
186 void ctf_CaptureShield_Touch()
187 {
188         if(self.enemy.active != ACTIVE_ACTIVE)
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_INACTIVE); }
195
196                 return;
197         }
198
199         if(!other.ctf_captureshielded) { return; }
200         if(CTF_SAMETEAM(self, other)) { return; }
201
202         vector mymid = (self.absmin + self.absmax) * 0.5;
203         vector othermid = (other.absmin + other.absmax) * 0.5;
204
205         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
206         if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
207 }
208
209 void ctf_CaptureShield_Spawn(entity flag)
210 {
211         entity shield = spawn();
212
213         shield.enemy = self;
214         shield.team = self.team;
215         shield.touch = ctf_CaptureShield_Touch;
216         shield.customizeentityforclient = ctf_CaptureShield_Customize;
217         shield.classname = "ctf_captureshield";
218         shield.effects = EF_ADDITIVE;
219         shield.movetype = MOVETYPE_NOCLIP;
220         shield.solid = SOLID_TRIGGER;
221         shield.avelocity = '7 0 11';
222         shield.scale = 0.5;
223
224         setorigin(shield, self.origin);
225         setmodel(shield, "models/ctf/shield.md3");
226         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
227 }
228
229
230 // ====================
231 // Drop/Pass/Throw Code
232 // ====================
233
234 void ctf_Handle_Drop(entity flag, entity player, float droptype)
235 {
236         // declarations
237         player = (player ? player : flag.pass_sender);
238
239         // main
240         flag.movetype = MOVETYPE_TOSS;
241         flag.takedamage = DAMAGE_YES;
242         flag.angles = '0 0 0';
243         flag.health = flag.max_flag_health;
244         flag.ctf_droptime = time;
245         flag.ctf_dropper = player;
246         flag.ctf_status = FLAG_DROPPED;
247
248         // messages and sounds
249         Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
250         sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
251         ctf_EventLog("dropped", player.team, player);
252
253         // scoring
254         PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
255         PlayerScore_Add(player, SP_CTF_DROPS, 1);
256
257         // waypoints
258         if(autocvar_g_ctf_flag_dropped_waypoint)
259                 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
260
261         if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
262         {
263                 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
264                 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
265         }
266
267         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
268
269         if(droptype == DROP_PASS)
270         {
271                 flag.pass_distance = 0;
272                 flag.pass_sender = world;
273                 flag.pass_target = world;
274         }
275 }
276
277 void ctf_Handle_Retrieve(entity flag, entity player)
278 {
279         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
280         entity sender = flag.pass_sender;
281
282         // transfer flag to player
283         flag.owner = player;
284         flag.owner.flagcarried = flag;
285
286         // reset flag
287         if(player.vehicle)
288         {
289                 setattachment(flag, player.vehicle, "");
290                 setorigin(flag, VEHICLE_FLAG_OFFSET);
291                 flag.scale = VEHICLE_FLAG_SCALE;
292         }
293         else
294         {
295                 setattachment(flag, player, "");
296                 setorigin(flag, FLAG_CARRY_OFFSET);
297         }
298         flag.movetype = MOVETYPE_NONE;
299         flag.takedamage = DAMAGE_NO;
300         flag.solid = SOLID_NOT;
301         flag.angles = '0 0 0';
302         flag.ctf_status = FLAG_CARRY;
303
304         // messages and sounds
305         sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
306         ctf_EventLog("receive", flag.team, player);
307
308         FOR_EACH_REALPLAYER(tmp_player)
309         {
310                 if(tmp_player == sender)
311                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
312                 else if(tmp_player == player)
313                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
314                 else if(SAME_TEAM(tmp_player, sender))
315                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
316         }
317
318         // create new waypoint
319         ctf_FlagcarrierWaypoints(player);
320
321         sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
322         player.throw_antispam = sender.throw_antispam;
323
324         flag.pass_distance = 0;
325         flag.pass_sender = world;
326         flag.pass_target = world;
327 }
328
329 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
330 {
331         entity flag = player.flagcarried;
332         vector targ_origin, flag_velocity;
333
334         if(!flag) { return; }
335         if((droptype == DROP_PASS) && !receiver) { return; }
336
337         if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
338
339         // reset the flag
340         setattachment(flag, world, "");
341         setorigin(flag, player.origin + FLAG_DROP_OFFSET);
342         flag.owner.flagcarried = world;
343         flag.owner = world;
344         flag.solid = SOLID_TRIGGER;
345         flag.ctf_dropper = player;
346         flag.ctf_droptime = time;
347
348         flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
349
350         switch(droptype)
351         {
352                 case DROP_PASS:
353                 {
354                         // warpzone support:
355                         // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
356                         // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
357                         WarpZone_RefSys_Copy(flag, receiver);
358                         WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
359                         targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
360
361                         flag.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' *  player.origin_x) + ('0 1 0' *  player.origin_y))); // for the sake of this check, exclude Z axis
362                         ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
363
364                         // main
365                         flag.movetype = MOVETYPE_FLY;
366                         flag.takedamage = DAMAGE_NO;
367                         flag.pass_sender = player;
368                         flag.pass_target = receiver;
369                         flag.ctf_status = FLAG_PASSING;
370
371                         // other
372                         sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
373                         WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
374                         ctf_EventLog("pass", flag.team, player);
375                         break;
376                 }
377
378                 case DROP_THROW:
379                 {
380                         makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
381
382                         flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
383                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
384                         ctf_Handle_Drop(flag, player, droptype);
385                         break;
386                 }
387
388                 case DROP_RESET:
389                 {
390                         flag.velocity = '0 0 0'; // do nothing
391                         break;
392                 }
393
394                 default:
395                 case DROP_NORMAL:
396                 {
397                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
398                         ctf_Handle_Drop(flag, player, droptype);
399                         break;
400                 }
401         }
402
403         // kill old waypointsprite
404         WaypointSprite_Ping(player.wps_flagcarrier);
405         WaypointSprite_Kill(player.wps_flagcarrier);
406
407         if(player.wps_enemyflagcarrier)
408                 WaypointSprite_Kill(player.wps_enemyflagcarrier);
409
410         // captureshield
411         ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
412 }
413
414
415 // ==============
416 // Event Handlers
417 // ==============
418
419 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
420 {
421         entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
422         entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
423         entity player_team_flag = world, tmp_entity;
424         float old_time, new_time;
425
426         if(!player) { return; } // without someone to give the reward to, we can't possibly cap
427         if(ctf_oneflag) 
428         {
429                 if(CTF_SAMETEAM(player, flag)) { return; }
430         }
431         else if(CTF_DIFFTEAM(player, flag)) { return; }
432         
433         if(ctf_oneflag)
434         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
435         if(SAME_TEAM(tmp_entity, player))
436         {
437                 player_team_flag = tmp_entity;
438                 break;
439         }
440
441         player.throw_prevtime = time;
442         player.throw_count = 0;
443
444         // messages and sounds
445         Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
446         ctf_CaptureRecord(enemy_flag, player);
447         sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
448
449         switch(capturetype)
450         {
451                 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
452                 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
453                 default: break;
454         }
455
456         // scoring
457         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
458         PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
459
460         old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
461         new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
462         if(!old_time || new_time < old_time)
463                 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
464
465         // effects
466         pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
467         //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
468
469         // other
470         if(capturetype == CAPTURE_NORMAL)
471         {
472                 WaypointSprite_Kill(player.wps_flagcarrier);
473                 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
474
475                 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
476                         { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
477         }
478
479         // reset the flag
480         player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
481         ctf_RespawnFlag(enemy_flag);
482 }
483
484 void ctf_Handle_Return(entity flag, entity player)
485 {
486         // messages and sounds
487         if(player.flags & FL_MONSTER)
488         {
489                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
490         }
491         else if(flag.team)
492         {
493                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
494                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
495         }
496         sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
497         ctf_EventLog("return", flag.team, player);
498
499         // scoring
500         if(IS_PLAYER(player))
501         {
502                 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
503                 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
504         }
505
506         TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
507
508         if(flag.ctf_dropper)
509         {
510                 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
511                 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
512                 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
513         }
514
515         // other
516         if(player.flagcarried == flag)
517                 WaypointSprite_Kill(player.wps_flagcarrier);
518
519         // reset the flag
520         ctf_RespawnFlag(flag);
521 }
522
523 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
524 {
525         // declarations
526         float pickup_dropped_score; // used to calculate dropped pickup score
527         entity tmp_entity; // temporary entity
528
529         // attach the flag to the player
530         flag.owner = player;
531         player.flagcarried = flag;
532         if(player.vehicle)
533         {
534                 setattachment(flag, player.vehicle, "");
535                 setorigin(flag, VEHICLE_FLAG_OFFSET);
536                 flag.scale = VEHICLE_FLAG_SCALE;
537         }
538         else
539         {
540                 setattachment(flag, player, "");
541                 setorigin(flag, FLAG_CARRY_OFFSET);
542         }
543
544         // flag setup
545         flag.movetype = MOVETYPE_NONE;
546         flag.takedamage = DAMAGE_NO;
547         flag.solid = SOLID_NOT;
548         flag.angles = '0 0 0';
549         flag.ctf_status = FLAG_CARRY;
550
551         switch(pickuptype)
552         {
553                 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
554                 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
555                 default: break;
556         }
557
558         // messages and sounds
559         Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
560         if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
561         if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
562         else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
563         else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); }
564
565         Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
566         
567         if(!flag.team)
568         FOR_EACH_PLAYER(tmp_entity)
569         if(tmp_entity != player)
570         if(DIFF_TEAM(player, tmp_entity))
571                 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
572         
573         if(flag.team)
574         FOR_EACH_PLAYER(tmp_entity)
575         if(tmp_entity != player)
576         if(CTF_SAMETEAM(flag, tmp_entity))
577         if(SAME_TEAM(player, tmp_entity))
578                 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
579         else
580                 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
581
582         sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
583
584         // scoring
585         PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
586         switch(pickuptype)
587         {
588                 case PICKUP_BASE:
589                 {
590                         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
591                         ctf_EventLog("steal", flag.team, player);
592                         break;
593                 }
594
595                 case PICKUP_DROPPED:
596                 {
597                         pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
598                         pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
599                         dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
600                         PlayerTeamScore_AddScore(player, pickup_dropped_score);
601                         ctf_EventLog("pickup", flag.team, player);
602                         break;
603                 }
604
605                 default: break;
606         }
607
608         // speedrunning
609         if(pickuptype == PICKUP_BASE)
610         {
611                 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
612                 if((player.speedrunning) && (ctf_captimerecord))
613                         ctf_FakeTimeLimit(player, time + ctf_captimerecord);
614         }
615
616         // effects
617         pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
618
619         // waypoints
620         if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
621         ctf_FlagcarrierWaypoints(player);
622         WaypointSprite_Ping(player.wps_flagcarrier);
623 }
624
625
626 // ===================
627 // Main Flag Functions
628 // ===================
629
630 void ctf_CheckFlagReturn(entity flag, float returntype)
631 {
632         if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
633         {
634                 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
635
636                 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
637                 {
638                         switch(returntype)
639                         {
640                                 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
641                                 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
642                                 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
643                                 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
644
645                                 default:
646                                 case RETURN_TIMEOUT:
647                                         { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
648                         }
649                         sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
650                         ctf_EventLog("returned", flag.team, world);
651                         ctf_RespawnFlag(flag);
652                 }
653         }
654 }
655
656 float ctf_Stalemate_Customize()
657 {
658         // make spectators see what the player would see
659         entity e, wp_owner;
660         e = WaypointSprite_getviewentity(other);
661         wp_owner = self.owner;
662
663         // team waypoints
664         if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return FALSE; }
665         if(SAME_TEAM(wp_owner, e)) { return FALSE; }
666         if(!IS_PLAYER(e)) { return FALSE; }
667
668         return TRUE;
669 }
670
671 void ctf_CheckStalemate(void)
672 {
673         // declarations
674         float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
675         entity tmp_entity;
676
677         entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
678
679         // build list of stale flags
680         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
681         {
682                 if(autocvar_g_ctf_stalemate)
683                 if(tmp_entity.ctf_status != FLAG_BASE)
684                 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
685                 {
686                         tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
687                         ctf_staleflaglist = tmp_entity;
688
689                         switch(tmp_entity.team)
690                         {
691                                 case NUM_TEAM_1: ++stale_red_flags; break;
692                                 case NUM_TEAM_2: ++stale_blue_flags; break;
693                                 case NUM_TEAM_3: ++stale_yellow_flags; break;
694                                 case NUM_TEAM_4: ++stale_pink_flags; break;
695                                 default: ++stale_neutral_flags; break;
696                         }
697                 }
698         }
699
700         if(ctf_oneflag)
701                 stale_flags = (stale_neutral_flags >= 1);
702         else
703                 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
704
705         if(ctf_oneflag && stale_flags == 1)
706                 ctf_stalemate = TRUE;
707         else if(stale_flags == ctf_teams)
708                 ctf_stalemate = TRUE;
709         else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
710                 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
711         else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1)
712                 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
713
714         // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
715         if(ctf_stalemate)
716         {
717                 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
718                 {
719                         if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
720                         {
721                                 WaypointSprite_Spawn(((ctf_oneflag) ? "flagcarrier" : "enemyflagcarrier"), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
722                                 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
723                         }
724                 }
725
726                 if (!wpforenemy_announced)
727                 {
728                         FOR_EACH_REALPLAYER(tmp_entity)
729                                 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
730
731                         wpforenemy_announced = TRUE;
732                 }
733         }
734 }
735
736 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
737 {
738         if(ITEM_DAMAGE_NEEDKILL(deathtype))
739         {
740                 if(autocvar_g_ctf_flag_return_damage_delay)
741                 {
742                         self.ctf_flagdamaged = TRUE;
743                 }
744                 else
745                 {
746                         self.health = 0;
747                         ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
748                 }
749                 return;
750         }
751         if(autocvar_g_ctf_flag_return_damage)
752         {
753                 // reduce health and check if it should be returned
754                 self.health = self.health - damage;
755                 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
756                 return;
757         }
758 }
759
760 void ctf_FlagThink()
761 {
762         // declarations
763         entity tmp_entity;
764
765         self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
766
767         // captureshield
768         if(self == ctf_worldflaglist) // only for the first flag
769                 FOR_EACH_CLIENT(tmp_entity)
770                         ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
771
772         // sanity checks
773         if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
774                 dprint("wtf the flag got squashed?\n");
775                 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
776                 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
777                         setsize(self, FLAG_MIN, FLAG_MAX); }
778
779         switch(self.ctf_status) // reset flag angles in case warpzones adjust it
780         {
781                 case FLAG_DROPPED:
782                 {
783                         self.angles = '0 0 0';
784                         break;
785                 }
786
787                 default: break;
788         }
789
790         // main think method
791         switch(self.ctf_status)
792         {
793                 case FLAG_BASE:
794                 {
795                         if(autocvar_g_ctf_dropped_capture_radius)
796                         {
797                                 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
798                                         if(tmp_entity.ctf_status == FLAG_DROPPED)
799                                         if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
800                                         if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
801                                                 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
802                         }
803                         return;
804                 }
805
806                 case FLAG_DROPPED:
807                 {
808                         if(autocvar_g_ctf_flag_dropped_floatinwater)
809                         {
810                                 vector midpoint = ((self.absmin + self.absmax) * 0.5);
811                                 if(pointcontents(midpoint) == CONTENT_WATER)
812                                 {
813                                         self.velocity = self.velocity * 0.5;
814
815                                         if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
816                                                 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
817                                         else
818                                                 { self.movetype = MOVETYPE_FLY; }
819                                 }
820                                 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
821                         }
822                         if(autocvar_g_ctf_flag_return_dropped)
823                         {
824                                 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
825                                 {
826                                         self.health = 0;
827                                         ctf_CheckFlagReturn(self, RETURN_DROPPED);
828                                         return;
829                                 }
830                         }
831                         if(self.ctf_flagdamaged)
832                         {
833                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
834                                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
835                                 return;
836                         }
837                         else if(autocvar_g_ctf_flag_return_time)
838                         {
839                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
840                                 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
841                                 return;
842                         }
843                         return;
844                 }
845
846                 case FLAG_CARRY:
847                 {
848                         if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
849                         {
850                                 self.health = 0;
851                                 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
852
853                                 tmp_entity = self;
854                                 self = self.owner;
855                                 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
856                                 ImpulseCommands();
857                                 self = tmp_entity;
858                         }
859                         if(autocvar_g_ctf_stalemate)
860                         {
861                                 if(time >= wpforenemy_nextthink)
862                                 {
863                                         ctf_CheckStalemate();
864                                         wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
865                                 }
866                         }
867                         if(CTF_SAMETEAM(self, self.owner) && self.team)
868                         {
869                                 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
870                                         ctf_Handle_Throw(self.owner, world, DROP_THROW);
871                                 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
872                                         ctf_Handle_Return(self, self.owner);
873                         }
874                         return;
875                 }
876
877                 case FLAG_PASSING:
878                 {
879                         vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
880                         targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
881                         WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
882
883                         if((self.pass_target == world)
884                                 || (self.pass_target.deadflag != DEAD_NO)
885                                 || (self.pass_target.flagcarried)
886                                 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
887                                 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
888                                 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
889                         {
890                                 // give up, pass failed
891                                 ctf_Handle_Drop(self, world, DROP_PASS);
892                         }
893                         else
894                         {
895                                 // still a viable target, go for it
896                                 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
897                         }
898                         return;
899                 }
900
901                 default: // this should never happen
902                 {
903                         dprint("ctf_FlagThink(): Flag exists with no status?\n");
904                         return;
905                 }
906         }
907 }
908
909 void ctf_FlagTouch()
910 {
911         if(gameover) { return; }
912         if(self.active != ACTIVE_ACTIVE) { return; }
913
914         entity toucher = other, tmp_entity;
915         float is_not_monster = (!(toucher.flags & FL_MONSTER)), num_perteam = 0;
916
917         // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
918         if(ITEM_TOUCH_NEEDKILL())
919         {
920                 if(!autocvar_g_ctf_flag_return_damage_delay)
921                 {
922                         self.health = 0;
923                         ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
924                 }
925                 return;
926         }
927
928         FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
929
930         // special touch behaviors
931         if(toucher.vehicle_flags & VHF_ISVEHICLE)
932         {
933                 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
934                         toucher = toucher.owner; // the player is actually the vehicle owner, not other
935                 else
936                         return; // do nothing
937         }
938         else if(toucher.flags & FL_MONSTER)
939         {
940                 if(!autocvar_g_ctf_allow_monster_touch)
941                         return; // do nothing
942         }
943         else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
944         {
945                 if(time > self.wait) // if we haven't in a while, play a sound/effect
946                 {
947                         pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
948                         sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
949                         self.wait = time + FLAG_TOUCHRATE;
950                 }
951                 return;
952         }
953         else if(toucher.deadflag != DEAD_NO) { return; }
954
955         switch(self.ctf_status)
956         {
957                 case FLAG_BASE:
958                 {
959                         if(ctf_oneflag)
960                         {
961                                 if(CTF_DIFFTEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
962                                         ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
963                                 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
964                                         ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
965                         }
966                         else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
967                                 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
968                         else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
969                                 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
970                         break;
971                 }
972
973                 case FLAG_DROPPED:
974                 {
975                         if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team
976                                 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
977                         else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
978                                 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
979                         break;
980                 }
981
982                 case FLAG_CARRY:
983                 {
984                         dprint("Someone touched a flag even though it was being carried?\n");
985                         break;
986                 }
987
988                 case FLAG_PASSING:
989                 {
990                         if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
991                         {
992                                 if(DIFF_TEAM(toucher, self.pass_sender))
993                                         ctf_Handle_Return(self, toucher);
994                                 else
995                                         ctf_Handle_Retrieve(self, toucher);
996                         }
997                         break;
998                 }
999         }
1000 }
1001
1002 .float last_respawn;
1003 void ctf_RespawnFlag(entity flag)
1004 {
1005         // check for flag respawn being called twice in a row
1006         if(flag.last_respawn > time - 0.5)
1007                 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1008
1009         flag.last_respawn = time;
1010
1011         // reset the player (if there is one)
1012         if((flag.owner) && (flag.owner.flagcarried == flag))
1013         {
1014                 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1015                 WaypointSprite_Kill(flag.wps_flagcarrier);
1016
1017                 flag.owner.flagcarried = world;
1018
1019                 if(flag.speedrunning)
1020                         ctf_FakeTimeLimit(flag.owner, -1);
1021         }
1022
1023         if((flag.owner) && (flag.owner.vehicle))
1024                 flag.scale = FLAG_SCALE;
1025
1026         if(flag.ctf_status == FLAG_DROPPED)
1027                 { WaypointSprite_Kill(flag.wps_flagdropped); }
1028
1029         // reset the flag
1030         setattachment(flag, world, "");
1031         setorigin(flag, flag.ctf_spawnorigin);
1032
1033         flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1034         flag.takedamage = DAMAGE_NO;
1035         flag.health = flag.max_flag_health;
1036         flag.solid = SOLID_TRIGGER;
1037         flag.velocity = '0 0 0';
1038         flag.angles = flag.mangle;
1039         flag.flags = FL_ITEM | FL_NOTARGET;
1040
1041         flag.ctf_status = FLAG_BASE;
1042         flag.owner = world;
1043         flag.pass_distance = 0;
1044         flag.pass_sender = world;
1045         flag.pass_target = world;
1046         flag.ctf_dropper = world;
1047         flag.ctf_pickuptime = 0;
1048         flag.ctf_droptime = 0;
1049         flag.ctf_flagdamaged = 0;
1050         
1051         ctf_CheckStalemate();
1052 }
1053
1054 void ctf_Reset()
1055 {
1056         if(self.owner)
1057                 if(IS_PLAYER(self.owner))
1058                         ctf_Handle_Throw(self.owner, world, DROP_RESET);
1059
1060         ctf_RespawnFlag(self);
1061 }
1062
1063 void ctf_Use()
1064 {
1065         if(self.ctf_status != FLAG_BASE) { return; }
1066         
1067         entity flag;
1068         float flag_cnt = 0;
1069         
1070         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1071         {
1072                 if(flag != self)
1073                 if(SAME_TEAM(flag, self))
1074                 if(flag.active == ACTIVE_ACTIVE)
1075                         ++flag_cnt;
1076         }
1077
1078         if(self.active == ACTIVE_ACTIVE)
1079         if(flag_cnt < 1)
1080         {
1081                 dprint("ctf_Use: Unable to deactivate flag (not enough active flags on the map)\n");
1082                 return;
1083         }
1084
1085         self.active = ((self.active) ? ACTIVE_NOT : ACTIVE_ACTIVE);
1086         
1087         if(self.active == ACTIVE_ACTIVE)
1088                 WaypointSprite_Ping(self.wps_flagbase);
1089 }
1090
1091 float ctf_FlagWaypoint_Customize()
1092 {
1093         if(self.owner.active != ACTIVE_ACTIVE) { return FALSE; }
1094         return TRUE;
1095 }
1096
1097 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1098 {
1099         // bot waypoints
1100         waypoint_spawnforitem_force(self, self.origin);
1101         self.nearestwaypointtimeout = 0; // activate waypointing again
1102         self.bot_basewaypoint = self.nearestwaypoint;
1103
1104         // waypointsprites
1105         string basename = "base";
1106
1107         switch(self.team)
1108         {
1109                 case NUM_TEAM_1: basename = "redbase"; break;
1110                 case NUM_TEAM_2: basename = "bluebase"; break;
1111                 case NUM_TEAM_3: basename = "yellowbase"; break;
1112                 case NUM_TEAM_4: basename = "pinkbase"; break;
1113                 default: basename = "neutralbase"; break;
1114         }
1115
1116         WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'));
1117         WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, FALSE) : '1 1 1'));
1118         self.wps_flagbase.customizeentityforclient = ctf_FlagWaypoint_Customize;
1119
1120         // captureshield setup
1121         ctf_CaptureShield_Spawn(self);
1122 }
1123
1124 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc 
1125 {
1126         // declarations
1127         self = flag; // for later usage with droptofloor()
1128
1129         // main setup
1130         flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1131         ctf_worldflaglist = flag;
1132
1133         setattachment(flag, world, "");
1134
1135         flag.netname = sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber));
1136         flag.team = teamnumber;
1137         flag.classname = "item_flag_team";
1138         flag.target = "###item###"; // wut?
1139         flag.flags = FL_ITEM | FL_NOTARGET;
1140         flag.solid = SOLID_TRIGGER;
1141         flag.takedamage = DAMAGE_NO;
1142         flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1143         flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1144         flag.health = flag.max_flag_health;
1145         flag.event_damage = ctf_FlagDamage;
1146         flag.pushable = TRUE;
1147         flag.teleportable = TELEPORT_NORMAL;
1148         flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1149         flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1150         flag.velocity = '0 0 0';
1151         flag.mangle = flag.angles;
1152         flag.reset = ctf_Reset;
1153         flag.use = ctf_Use;
1154         flag.touch = ctf_FlagTouch;
1155         flag.think = ctf_FlagThink;
1156         flag.nextthink = time + FLAG_THINKRATE;
1157         flag.ctf_status = FLAG_BASE;
1158
1159         // appearence
1160         if(flag.model == "")       { flag.model = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_model : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_model : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_model : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_model : autocvar_g_ctf_flag_neutral_model)))); }
1161         if(!flag.scale)            { flag.scale = FLAG_SCALE; }
1162         if(!flag.skin)             { flag.skin = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_skin : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_skin : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_skin : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_skin : autocvar_g_ctf_flag_neutral_skin)))); }
1163         if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber == NUM_TEAM_1) ? "redflag_touch" : ((teamnumber == NUM_TEAM_2) ? "blueflag_touch" : ((teamnumber == NUM_TEAM_3) ? "yellowflag_touch" : ((teamnumber == NUM_TEAM_4) ? "pinkflag_touch" : "neutralflag_touch")))); }
1164         if(flag.passeffect == "")  { flag.passeffect = ((teamnumber == NUM_TEAM_1) ? "red_pass" : ((teamnumber == NUM_TEAM_2) ? "blue_pass" : ((teamnumber == NUM_TEAM_3) ? "yellow_pass" : ((teamnumber == NUM_TEAM_4) ? "pink_pass" : "neutral_pass")))); }
1165         if(flag.capeffect == "")   { flag.capeffect = ((teamnumber == NUM_TEAM_1) ? "red_cap" : ((teamnumber == NUM_TEAM_2) ? "blue_cap" : ((teamnumber == NUM_TEAM_3) ? "yellow_cap" : ((teamnumber == NUM_TEAM_4) ? "pink_cap" : "")))); } // neutral flag cant be capped itself
1166         if(!flag.active)           { flag.active = ACTIVE_ACTIVE; }
1167
1168         // sound
1169         if(flag.snd_flag_taken == "")    { flag.snd_flag_taken = ((teamnumber == NUM_TEAM_1) ? "ctf/red_taken.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_taken.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_taken.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_taken.wav" : "ctf/neutral_taken.wav")))); }
1170         if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber == NUM_TEAM_1) ? "ctf/red_returned.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_returned.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_returned.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_returned.wav" : "")))); } // neutral flag can't be returned by players
1171         if(flag.snd_flag_capture == "")  { flag.snd_flag_capture = ((teamnumber == NUM_TEAM_1) ? "ctf/red_capture.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_capture.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_capture.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_capture.wav" : "")))); } // again can't be captured
1172         if(flag.snd_flag_respawn == "")  { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
1173         if(flag.snd_flag_dropped == "")  { flag.snd_flag_dropped = ((teamnumber == NUM_TEAM_1) ? "ctf/red_dropped.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_dropped.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_dropped.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_dropped.wav" : "ctf/neutral_dropped.wav")))); }
1174         if(flag.snd_flag_touch == "")    { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1175         if(flag.snd_flag_pass == "")     { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1176
1177         // precache
1178         precache_sound(flag.snd_flag_taken);
1179         precache_sound(flag.snd_flag_returned);
1180         precache_sound(flag.snd_flag_capture);
1181         precache_sound(flag.snd_flag_respawn);
1182         precache_sound(flag.snd_flag_dropped);
1183         precache_sound(flag.snd_flag_touch);
1184         precache_sound(flag.snd_flag_pass);
1185         precache_model(flag.model);
1186         precache_model("models/ctf/shield.md3");
1187         precache_model("models/ctf/shockwavetransring.md3");
1188
1189         // appearence
1190         setmodel(flag, flag.model); // precision set below
1191         setsize(flag, FLAG_MIN, FLAG_MAX);
1192         setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1193
1194         if(autocvar_g_ctf_flag_glowtrails)
1195         {
1196                 flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254))));
1197                 flag.glow_size = 25;
1198                 flag.glow_trail = 1;
1199         }
1200
1201         flag.effects |= EF_LOWPRECISION;
1202         if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1203         if(autocvar_g_ctf_dynamiclights)
1204         {
1205                 switch(teamnumber)
1206                 {
1207                         case NUM_TEAM_1: flag.effects |= EF_RED; break;
1208                         case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1209                         case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1210                         case NUM_TEAM_4: flag.effects |= EF_RED; break;
1211                         default: flag.effects |= EF_DIMLIGHT; break;
1212                 }
1213         }
1214         
1215         // flag placement
1216         if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1217         {
1218                 flag.dropped_origin = flag.origin;
1219                 flag.noalign = TRUE;
1220                 flag.movetype = MOVETYPE_NONE;
1221         }
1222         else // drop to floor, automatically find a platform and set that as spawn origin
1223         {
1224                 flag.noalign = FALSE;
1225                 self = flag;
1226                 droptofloor();
1227                 flag.movetype = MOVETYPE_TOSS;
1228         }
1229
1230         InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1231 }
1232
1233
1234 // ================
1235 // Bot player logic
1236 // ================
1237
1238 // NOTE: LEGACY CODE, needs to be re-written!
1239
1240 void havocbot_calculate_middlepoint()
1241 {
1242         entity f;
1243         vector s = '0 0 0';
1244         vector fo = '0 0 0';
1245         float n = 0;
1246
1247         f = ctf_worldflaglist;
1248         while (f)
1249         {
1250                 fo = f.origin;
1251                 s = s + fo;
1252                 f = f.ctf_worldflagnext;
1253         }
1254         if(!n)
1255                 return;
1256         havocbot_ctf_middlepoint = s * (1.0 / n);
1257         havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
1258 }
1259
1260
1261 entity havocbot_ctf_find_flag(entity bot)
1262 {
1263         entity f;
1264         f = ctf_worldflaglist;
1265         while (f)
1266         {
1267                 if (CTF_SAMETEAM(bot, f))
1268                         return f;
1269                 f = f.ctf_worldflagnext;
1270         }
1271         return world;
1272 }
1273
1274 entity havocbot_ctf_find_enemy_flag(entity bot)
1275 {
1276         entity f;
1277         f = ctf_worldflaglist;
1278         while (f)
1279         {
1280                 if(ctf_oneflag)
1281                 {
1282                         if(CTF_DIFFTEAM(bot, f))
1283                         {
1284                                 if(f.team)
1285                                 {
1286                                         if(bot.flagcarried)
1287                                                 return f;
1288                                 }
1289                                 else if(!bot.flagcarried)
1290                                         return f;
1291                         }
1292                 }
1293                 else if (CTF_DIFFTEAM(bot, f))
1294                         return f;
1295                 f = f.ctf_worldflagnext;
1296         }
1297         return world;
1298 }
1299
1300 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1301 {
1302         if (!teamplay)
1303                 return 0;
1304
1305         float c = 0;
1306         entity head;
1307
1308         FOR_EACH_PLAYER(head)
1309         {
1310                 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1311                         continue;
1312
1313                 if(vlen(head.origin - org) < tc_radius)
1314                         ++c;
1315         }
1316
1317         return c;
1318 }
1319
1320 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1321 {
1322         entity head;
1323         head = ctf_worldflaglist;
1324         while (head)
1325         {
1326                 if (CTF_SAMETEAM(self, head))
1327                         break;
1328                 head = head.ctf_worldflagnext;
1329         }
1330         if (head)
1331                 navigation_routerating(head, ratingscale, 10000);
1332 }
1333
1334 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1335 {
1336         entity head;
1337         head = ctf_worldflaglist;
1338         while (head)
1339         {
1340                 if (CTF_SAMETEAM(self, head))
1341                         break;
1342                 head = head.ctf_worldflagnext;
1343         }
1344         if (!head)
1345                 return;
1346
1347         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1348 }
1349
1350 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1351 {
1352         entity head;
1353         head = ctf_worldflaglist;
1354         while (head)
1355         {
1356                 if(ctf_oneflag)
1357                 {
1358                         if(CTF_DIFFTEAM(self, head))
1359                         {
1360                                 if(head.team)
1361                                 {
1362                                         if(self.flagcarried)
1363                                                 break;
1364                                 }
1365                                 else if(!self.flagcarried)
1366                                         break;
1367                         }
1368                 }
1369                 else if(CTF_DIFFTEAM(self, head))
1370                         break;
1371                 head = head.ctf_worldflagnext;
1372         }
1373         if (head)
1374                 navigation_routerating(head, ratingscale, 10000);
1375 }
1376
1377 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1378 {
1379         if (!bot_waypoints_for_items)
1380         {
1381                 havocbot_goalrating_ctf_enemyflag(ratingscale);
1382                 return;
1383         }
1384
1385         entity head;
1386
1387         head = havocbot_ctf_find_enemy_flag(self);
1388
1389         if (!head)
1390                 return;
1391
1392         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1393 }
1394
1395 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1396 {
1397         entity mf;
1398
1399         mf = havocbot_ctf_find_flag(self);
1400
1401         if(mf.ctf_status == FLAG_BASE)
1402                 return;
1403
1404         if(mf.tag_entity)
1405                 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1406 }
1407
1408 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1409 {
1410         entity head;
1411         head = ctf_worldflaglist;
1412         while (head)
1413         {
1414                 // flag is out in the field
1415                 if(head.ctf_status != FLAG_BASE)
1416                 if(head.tag_entity==world)      // dropped
1417                 {
1418                         if(df_radius)
1419                         {
1420                                 if(vlen(org-head.origin)<df_radius)
1421                                         navigation_routerating(head, ratingscale, 10000);
1422                         }
1423                         else
1424                                 navigation_routerating(head, ratingscale, 10000);
1425                 }
1426
1427                 head = head.ctf_worldflagnext;
1428         }
1429 }
1430
1431 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1432 {
1433         entity head;
1434         float t;
1435         head = findchainfloat(bot_pickup, TRUE);
1436         while (head)
1437         {
1438                 // gather health and armor only
1439                 if (head.solid)
1440                 if (head.health || head.armorvalue)
1441                 if (vlen(head.origin - org) < sradius)
1442                 {
1443                         // get the value of the item
1444                         t = head.bot_pickupevalfunc(self, head) * 0.0001;
1445                         if (t > 0)
1446                                 navigation_routerating(head, t * ratingscale, 500);
1447                 }
1448                 head = head.chain;
1449         }
1450 }
1451
1452 void havocbot_ctf_reset_role(entity bot)
1453 {
1454         float cdefense, cmiddle, coffense;
1455         entity mf, ef, head;
1456         float c;
1457
1458         if(bot.deadflag != DEAD_NO)
1459                 return;
1460
1461         if(vlen(havocbot_ctf_middlepoint)==0)
1462                 havocbot_calculate_middlepoint();
1463
1464         // Check ctf flags
1465         if (bot.flagcarried)
1466         {
1467                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1468                 return;
1469         }
1470
1471         mf = havocbot_ctf_find_flag(bot);
1472         ef = havocbot_ctf_find_enemy_flag(bot);
1473
1474         // Retrieve stolen flag
1475         if(mf.ctf_status!=FLAG_BASE)
1476         {
1477                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1478                 return;
1479         }
1480
1481         // If enemy flag is taken go to the middle to intercept pursuers
1482         if(ef.ctf_status!=FLAG_BASE)
1483         {
1484                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1485                 return;
1486         }
1487
1488         // if there is only me on the team switch to offense
1489         c = 0;
1490         FOR_EACH_PLAYER(head)
1491         if(SAME_TEAM(head, bot))
1492                 ++c;
1493
1494         if(c==1)
1495         {
1496                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1497                 return;
1498         }
1499
1500         // Evaluate best position to take
1501         // Count mates on middle position
1502         cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1503
1504         // Count mates on defense position
1505         cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1506
1507         // Count mates on offense position
1508         coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1509
1510         if(cdefense<=coffense)
1511                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1512         else if(coffense<=cmiddle)
1513                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1514         else
1515                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1516 }
1517
1518 void havocbot_role_ctf_carrier()
1519 {
1520         if(self.deadflag != DEAD_NO)
1521         {
1522                 havocbot_ctf_reset_role(self);
1523                 return;
1524         }
1525
1526         if (self.flagcarried == world)
1527         {
1528                 havocbot_ctf_reset_role(self);
1529                 return;
1530         }
1531
1532         if (self.bot_strategytime < time)
1533         {
1534                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1535
1536                 navigation_goalrating_start();
1537                 if(ctf_oneflag)
1538                         havocbot_goalrating_ctf_enemybase(50000);
1539                 else
1540                         havocbot_goalrating_ctf_ourbase(50000);
1541
1542                 if(self.health<100)
1543                         havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1544
1545                 navigation_goalrating_end();
1546
1547                 if (self.navigation_hasgoals)
1548                         self.havocbot_cantfindflag = time + 10;
1549                 else if (time > self.havocbot_cantfindflag)
1550                 {
1551                         // Can't navigate to my own base, suicide!
1552                         // TODO: drop it and wander around
1553                         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1554                         return;
1555                 }
1556         }
1557 }
1558
1559 void havocbot_role_ctf_escort()
1560 {
1561         entity mf, ef;
1562
1563         if(self.deadflag != DEAD_NO)
1564         {
1565                 havocbot_ctf_reset_role(self);
1566                 return;
1567         }
1568
1569         if (self.flagcarried)
1570         {
1571                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1572                 return;
1573         }
1574
1575         // If enemy flag is back on the base switch to previous role
1576         ef = havocbot_ctf_find_enemy_flag(self);
1577         if(ef.ctf_status==FLAG_BASE)
1578         {
1579                 self.havocbot_role = self.havocbot_previous_role;
1580                 self.havocbot_role_timeout = 0;
1581                 return;
1582         }
1583
1584         // If the flag carrier reached the base switch to defense
1585         mf = havocbot_ctf_find_flag(self);
1586         if(mf.ctf_status!=FLAG_BASE)
1587         if(vlen(ef.origin - mf.dropped_origin) < 300)
1588         {
1589                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1590                 return;
1591         }
1592
1593         // Set the role timeout if necessary
1594         if (!self.havocbot_role_timeout)
1595         {
1596                 self.havocbot_role_timeout = time + random() * 30 + 60;
1597         }
1598
1599         // If nothing happened just switch to previous role
1600         if (time > self.havocbot_role_timeout)
1601         {
1602                 self.havocbot_role = self.havocbot_previous_role;
1603                 self.havocbot_role_timeout = 0;
1604                 return;
1605         }
1606
1607         // Chase the flag carrier
1608         if (self.bot_strategytime < time)
1609         {
1610                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1611                 navigation_goalrating_start();
1612                 havocbot_goalrating_ctf_enemyflag(30000);
1613                 havocbot_goalrating_ctf_ourstolenflag(40000);
1614                 havocbot_goalrating_items(10000, self.origin, 10000);
1615                 navigation_goalrating_end();
1616         }
1617 }
1618
1619 void havocbot_role_ctf_offense()
1620 {
1621         entity mf, ef;
1622         vector pos;
1623
1624         if(self.deadflag != DEAD_NO)
1625         {
1626                 havocbot_ctf_reset_role(self);
1627                 return;
1628         }
1629
1630         if (self.flagcarried)
1631         {
1632                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1633                 return;
1634         }
1635
1636         // Check flags
1637         mf = havocbot_ctf_find_flag(self);
1638         ef = havocbot_ctf_find_enemy_flag(self);
1639
1640         // Own flag stolen
1641         if(mf.ctf_status!=FLAG_BASE)
1642         {
1643                 if(mf.tag_entity)
1644                         pos = mf.tag_entity.origin;
1645                 else
1646                         pos = mf.origin;
1647
1648                 // Try to get it if closer than the enemy base
1649                 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1650                 {
1651                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1652                         return;
1653                 }
1654         }
1655
1656         // Escort flag carrier
1657         if(ef.ctf_status!=FLAG_BASE)
1658         {
1659                 if(ef.tag_entity)
1660                         pos = ef.tag_entity.origin;
1661                 else
1662                         pos = ef.origin;
1663
1664                 if(vlen(pos-mf.dropped_origin)>700)
1665                 {
1666                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1667                         return;
1668                 }
1669         }
1670
1671         // About to fail, switch to middlefield
1672         if(self.health<50)
1673         {
1674                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1675                 return;
1676         }
1677
1678         // Set the role timeout if necessary
1679         if (!self.havocbot_role_timeout)
1680                 self.havocbot_role_timeout = time + 120;
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                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1691                 navigation_goalrating_start();
1692                 havocbot_goalrating_ctf_ourstolenflag(50000);
1693                 havocbot_goalrating_ctf_enemybase(20000);
1694                 havocbot_goalrating_items(5000, self.origin, 1000);
1695                 havocbot_goalrating_items(1000, self.origin, 10000);
1696                 navigation_goalrating_end();
1697         }
1698 }
1699
1700 // Retriever (temporary role):
1701 void havocbot_role_ctf_retriever()
1702 {
1703         entity mf;
1704
1705         if(self.deadflag != DEAD_NO)
1706         {
1707                 havocbot_ctf_reset_role(self);
1708                 return;
1709         }
1710
1711         if (self.flagcarried)
1712         {
1713                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1714                 return;
1715         }
1716
1717         // If flag is back on the base switch to previous role
1718         mf = havocbot_ctf_find_flag(self);
1719         if(mf.ctf_status==FLAG_BASE)
1720         {
1721                 havocbot_ctf_reset_role(self);
1722                 return;
1723         }
1724
1725         if (!self.havocbot_role_timeout)
1726                 self.havocbot_role_timeout = time + 20;
1727
1728         if (time > self.havocbot_role_timeout)
1729         {
1730                 havocbot_ctf_reset_role(self);
1731                 return;
1732         }
1733
1734         if (self.bot_strategytime < time)
1735         {
1736                 float rt_radius;
1737                 rt_radius = 10000;
1738
1739                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1740                 navigation_goalrating_start();
1741                 havocbot_goalrating_ctf_ourstolenflag(50000);
1742                 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1743                 havocbot_goalrating_ctf_enemybase(30000);
1744                 havocbot_goalrating_items(500, self.origin, rt_radius);
1745                 navigation_goalrating_end();
1746         }
1747 }
1748
1749 void havocbot_role_ctf_middle()
1750 {
1751         entity mf;
1752
1753         if(self.deadflag != DEAD_NO)
1754         {
1755                 havocbot_ctf_reset_role(self);
1756                 return;
1757         }
1758
1759         if (self.flagcarried)
1760         {
1761                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1762                 return;
1763         }
1764
1765         mf = havocbot_ctf_find_flag(self);
1766         if(mf.ctf_status!=FLAG_BASE)
1767         {
1768                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1769                 return;
1770         }
1771
1772         if (!self.havocbot_role_timeout)
1773                 self.havocbot_role_timeout = time + 10;
1774
1775         if (time > self.havocbot_role_timeout)
1776         {
1777                 havocbot_ctf_reset_role(self);
1778                 return;
1779         }
1780
1781         if (self.bot_strategytime < time)
1782         {
1783                 vector org;
1784
1785                 org = havocbot_ctf_middlepoint;
1786                 org_z = self.origin_z;
1787
1788                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1789                 navigation_goalrating_start();
1790                 havocbot_goalrating_ctf_ourstolenflag(50000);
1791                 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1792                 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1793                 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1794                 havocbot_goalrating_items(2500, self.origin, 10000);
1795                 havocbot_goalrating_ctf_enemybase(2500);
1796                 navigation_goalrating_end();
1797         }
1798 }
1799
1800 void havocbot_role_ctf_defense()
1801 {
1802         entity mf;
1803
1804         if(self.deadflag != DEAD_NO)
1805         {
1806                 havocbot_ctf_reset_role(self);
1807                 return;
1808         }
1809
1810         if (self.flagcarried)
1811         {
1812                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1813                 return;
1814         }
1815
1816         // If own flag was captured
1817         mf = havocbot_ctf_find_flag(self);
1818         if(mf.ctf_status!=FLAG_BASE)
1819         {
1820                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1821                 return;
1822         }
1823
1824         if (!self.havocbot_role_timeout)
1825                 self.havocbot_role_timeout = time + 30;
1826
1827         if (time > self.havocbot_role_timeout)
1828         {
1829                 havocbot_ctf_reset_role(self);
1830                 return;
1831         }
1832         if (self.bot_strategytime < time)
1833         {
1834                 float mp_radius;
1835                 vector org;
1836
1837                 org = mf.dropped_origin;
1838                 mp_radius = havocbot_ctf_middlepoint_radius;
1839
1840                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1841                 navigation_goalrating_start();
1842
1843                 // if enemies are closer to our base, go there
1844                 entity head, closestplayer = world;
1845                 float distance, bestdistance = 10000;
1846                 FOR_EACH_PLAYER(head)
1847                 {
1848                         if(head.deadflag!=DEAD_NO)
1849                                 continue;
1850
1851                         distance = vlen(org - head.origin);
1852                         if(distance<bestdistance)
1853                         {
1854                                 closestplayer = head;
1855                                 bestdistance = distance;
1856                         }
1857                 }
1858
1859                 if(closestplayer)
1860                 if(DIFF_TEAM(closestplayer, self))
1861                 if(vlen(org - self.origin)>1000)
1862                 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1863                         havocbot_goalrating_ctf_ourbase(30000);
1864
1865                 havocbot_goalrating_ctf_ourstolenflag(20000);
1866                 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1867                 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1868                 havocbot_goalrating_items(10000, org, mp_radius);
1869                 havocbot_goalrating_items(5000, self.origin, 10000);
1870                 navigation_goalrating_end();
1871         }
1872 }
1873
1874 void havocbot_role_ctf_setrole(entity bot, float role)
1875 {
1876         dprint(strcat(bot.netname," switched to "));
1877         switch(role)
1878         {
1879                 case HAVOCBOT_CTF_ROLE_CARRIER:
1880                         dprint("carrier");
1881                         bot.havocbot_role = havocbot_role_ctf_carrier;
1882                         bot.havocbot_role_timeout = 0;
1883                         bot.havocbot_cantfindflag = time + 10;
1884                         bot.bot_strategytime = 0;
1885                         break;
1886                 case HAVOCBOT_CTF_ROLE_DEFENSE:
1887                         dprint("defense");
1888                         bot.havocbot_role = havocbot_role_ctf_defense;
1889                         bot.havocbot_role_timeout = 0;
1890                         break;
1891                 case HAVOCBOT_CTF_ROLE_MIDDLE:
1892                         dprint("middle");
1893                         bot.havocbot_role = havocbot_role_ctf_middle;
1894                         bot.havocbot_role_timeout = 0;
1895                         break;
1896                 case HAVOCBOT_CTF_ROLE_OFFENSE:
1897                         dprint("offense");
1898                         bot.havocbot_role = havocbot_role_ctf_offense;
1899                         bot.havocbot_role_timeout = 0;
1900                         break;
1901                 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1902                         dprint("retriever");
1903                         bot.havocbot_previous_role = bot.havocbot_role;
1904                         bot.havocbot_role = havocbot_role_ctf_retriever;
1905                         bot.havocbot_role_timeout = time + 10;
1906                         bot.bot_strategytime = 0;
1907                         break;
1908                 case HAVOCBOT_CTF_ROLE_ESCORT:
1909                         dprint("escort");
1910                         bot.havocbot_previous_role = bot.havocbot_role;
1911                         bot.havocbot_role = havocbot_role_ctf_escort;
1912                         bot.havocbot_role_timeout = time + 30;
1913                         bot.bot_strategytime = 0;
1914                         break;
1915         }
1916         dprint("\n");
1917 }
1918
1919
1920 // ==============
1921 // Hook Functions
1922 // ==============
1923
1924 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1925 {
1926         entity flag;
1927
1928         float t = 0, t2 = 0, t3 = 0;
1929
1930         // initially clear items so they can be set as necessary later.
1931         self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING          | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST 
1932                                                    | CTF_BLUE_FLAG_CARRYING             | CTF_BLUE_FLAG_TAKEN           | CTF_BLUE_FLAG_LOST
1933                                                    | CTF_YELLOW_FLAG_CARRYING   | CTF_YELLOW_FLAG_TAKEN         | CTF_YELLOW_FLAG_LOST
1934                                                    | CTF_PINK_FLAG_CARRYING     | CTF_PINK_FLAG_TAKEN           | CTF_PINK_FLAG_LOST
1935                                                    | CTF_NEUTRAL_FLAG_CARRYING  | CTF_NEUTRAL_FLAG_TAKEN        | CTF_NEUTRAL_FLAG_LOST
1936                                                    | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1937
1938         // scan through all the flags and notify the client about them
1939         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1940         {
1941                 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING;                t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
1942                 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING;               t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
1943                 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING;     t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
1944                 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING;               t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
1945                 if(flag.team == 0)                      { t = CTF_NEUTRAL_FLAG_CARRYING;        t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
1946
1947                 switch(flag.ctf_status)
1948                 {
1949                         case FLAG_PASSING:
1950                         case FLAG_CARRY:
1951                         {
1952                                 if((flag.owner == self) || (flag.pass_sender == self))
1953                                         self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1954                                 else 
1955                                         self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1956                                 break;
1957                         }
1958                         case FLAG_DROPPED:
1959                         {
1960                                 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1961                                 break;
1962                         }
1963                 }
1964         }
1965
1966         // item for stopping players from capturing the flag too often
1967         if(self.ctf_captureshielded)
1968                 self.ctf_flagstatus |= CTF_SHIELDED;
1969
1970         // update the health of the flag carrier waypointsprite
1971         if(self.wps_flagcarrier)
1972                 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1973
1974         return FALSE;
1975 }
1976
1977 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1978 {
1979         if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1980         {
1981                 if(frag_target == frag_attacker) // damage done to yourself
1982                 {
1983                         frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1984                         frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1985                 }
1986                 else // damage done to everyone else
1987                 {
1988                         frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1989                         frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1990                 }
1991         }
1992         else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1993         {
1994                 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)))
1995                 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1996                 {
1997                         frag_target.wps_helpme_time = time;
1998                         WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1999                 }
2000                 // todo: add notification for when flag carrier needs help?
2001         }
2002         return FALSE;
2003 }
2004
2005 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
2006 {
2007         if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2008         {
2009                 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2010                 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2011         }
2012
2013         if(frag_target.flagcarried)
2014         {
2015                 entity tmp_entity = frag_target.flagcarried;
2016                 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2017                 tmp_entity.ctf_dropper = world;
2018         }
2019
2020         return FALSE;
2021 }
2022
2023 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
2024 {
2025         frag_score = 0;
2026         return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2027 }
2028
2029 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
2030 {
2031         entity flag; // temporary entity for the search method
2032
2033         if(self.flagcarried)
2034                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2035
2036         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2037         {
2038                 if(flag.pass_sender == self) { flag.pass_sender = world; }
2039                 if(flag.pass_target == self) { flag.pass_target = world; }
2040                 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
2041         }
2042
2043         return FALSE;
2044 }
2045
2046 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2047 {
2048         if(self.flagcarried)
2049         if(!autocvar_g_ctf_portalteleport)
2050                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2051
2052         return FALSE;
2053 }
2054
2055 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2056 {
2057         if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
2058
2059         entity player = self;
2060
2061         if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2062         {
2063                 // pass the flag to a team mate
2064                 if(autocvar_g_ctf_pass)
2065                 {
2066                         entity head, closest_target = world;
2067                         head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
2068
2069                         while(head) // find the closest acceptable target to pass to
2070                         {
2071                                 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2072                                 if(head != player && SAME_TEAM(head, player))
2073                                 if(!head.speedrunning && !head.vehicle)
2074                                 {
2075                                         // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2076                                         vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2077                                         vector passer_center = CENTER_OR_VIEWOFS(player);
2078
2079                                         if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2080                                         {
2081                                                 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2082                                                 {
2083                                                         if(IS_BOT_CLIENT(head))
2084                                                         {
2085                                                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2086                                                                 ctf_Handle_Throw(head, player, DROP_PASS);
2087                                                         }
2088                                                         else
2089                                                         {
2090                                                                 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2091                                                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2092                                                         }
2093                                                         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2094                                                         return TRUE;
2095                                                 }
2096                                                 else if(player.flagcarried)
2097                                                 {
2098                                                         if(closest_target)
2099                                                         {
2100                                                                 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2101                                                                 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2102                                                                         { closest_target = head; }
2103                                                         }
2104                                                         else { closest_target = head; }
2105                                                 }
2106                                         }
2107                                 }
2108                                 head = head.chain;
2109                         }
2110
2111                         if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
2112                 }
2113
2114                 // throw the flag in front of you
2115                 if(autocvar_g_ctf_throw && player.flagcarried)
2116                 {
2117                         if(player.throw_count == -1)
2118                         {
2119                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2120                                 {
2121                                         player.throw_prevtime = time;
2122                                         player.throw_count = 1;
2123                                         ctf_Handle_Throw(player, world, DROP_THROW);
2124                                         return TRUE;
2125                                 }
2126                                 else
2127                                 {
2128                                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2129                                         return FALSE;
2130                                 }
2131                         }
2132                         else
2133                         {
2134                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2135                                 else { player.throw_count += 1; }
2136                                 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2137
2138                                 player.throw_prevtime = time;
2139                                 ctf_Handle_Throw(player, world, DROP_THROW);
2140                                 return TRUE;
2141                         }
2142                 }
2143         }
2144
2145         return FALSE;
2146 }
2147
2148 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2149 {
2150         if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2151         {
2152                 self.wps_helpme_time = time;
2153                 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2154         }
2155         else // create a normal help me waypointsprite
2156         {
2157                 WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0');
2158                 WaypointSprite_Ping(self.wps_helpme);
2159         }
2160
2161         return TRUE;
2162 }
2163
2164 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2165 {
2166         if(vh_player.flagcarried)
2167         {
2168                 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2169                 {
2170                         ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2171                 }
2172                 else
2173                 {
2174                         setattachment(vh_player.flagcarried, vh_vehicle, "");
2175                         setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2176                         vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2177                         //vh_player.flagcarried.angles = '0 0 0';
2178                 }
2179                 return TRUE;
2180         }
2181
2182         return FALSE;
2183 }
2184
2185 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2186 {
2187         if(vh_player.flagcarried)
2188         {
2189                 setattachment(vh_player.flagcarried, vh_player, "");
2190                 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2191                 vh_player.flagcarried.scale = FLAG_SCALE;
2192                 vh_player.flagcarried.angles = '0 0 0';
2193                 return TRUE;
2194         }
2195
2196         return FALSE;
2197 }
2198
2199 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2200 {
2201         if(self.flagcarried)
2202         {
2203                 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2204                 ctf_RespawnFlag(self.flagcarried);
2205                 return TRUE;
2206         }
2207
2208         return FALSE;
2209 }
2210
2211 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2212 {
2213         entity flag; // temporary entity for the search method
2214
2215         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2216         {
2217                 switch(flag.ctf_status)
2218                 {
2219                         case FLAG_DROPPED:
2220                         case FLAG_PASSING:
2221                         {
2222                                 // lock the flag, game is over
2223                                 flag.movetype = MOVETYPE_NONE;
2224                                 flag.takedamage = DAMAGE_NO;
2225                                 flag.solid = SOLID_NOT;
2226                                 flag.nextthink = FALSE; // stop thinking
2227
2228                                 //dprint("stopping the ", flag.netname, " from moving.\n");
2229                                 break;
2230                         }
2231
2232                         default:
2233                         case FLAG_BASE:
2234                         case FLAG_CARRY:
2235                         {
2236                                 // do nothing for these flags
2237                                 break;
2238                         }
2239                 }
2240         }
2241
2242         return FALSE;
2243 }
2244
2245 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2246 {
2247         havocbot_ctf_reset_role(self);
2248         return TRUE;
2249 }
2250
2251 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2252 {
2253         //ret_float = ctf_teams;
2254         ret_string = "ctf_team";
2255         return TRUE;
2256 }
2257
2258 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2259 {
2260         self.ctf_flagstatus = other.ctf_flagstatus;
2261         return FALSE;
2262 }
2263
2264
2265 // ==========
2266 // Spawnfuncs
2267 // ==========
2268
2269 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2270 CTF Starting point for a player in team one (Red).
2271 Keys: "angle" viewing angle when spawning. */
2272 void spawnfunc_info_player_team1()
2273 {
2274         if(g_assault) { remove(self); return; }
2275
2276         self.team = NUM_TEAM_1; // red
2277         spawnfunc_info_player_deathmatch();
2278 }
2279
2280
2281 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2282 CTF Starting point for a player in team two (Blue).
2283 Keys: "angle" viewing angle when spawning. */
2284 void spawnfunc_info_player_team2()
2285 {
2286         if(g_assault) { remove(self); return; }
2287
2288         self.team = NUM_TEAM_2; // blue
2289         spawnfunc_info_player_deathmatch();
2290 }
2291
2292 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2293 CTF Starting point for a player in team three (Yellow).
2294 Keys: "angle" viewing angle when spawning. */
2295 void spawnfunc_info_player_team3()
2296 {
2297         if(g_assault) { remove(self); return; }
2298
2299         self.team = NUM_TEAM_3; // yellow
2300         spawnfunc_info_player_deathmatch();
2301 }
2302
2303
2304 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2305 CTF Starting point for a player in team four (Purple).
2306 Keys: "angle" viewing angle when spawning. */
2307 void spawnfunc_info_player_team4()
2308 {
2309         if(g_assault) { remove(self); return; }
2310
2311         self.team = NUM_TEAM_4; // purple
2312         spawnfunc_info_player_deathmatch();
2313 }
2314
2315 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2316 CTF flag for team one (Red).
2317 Keys:
2318 "angle" Angle the flag will point (minus 90 degrees)...
2319 "model" model to use, note this needs red and blue as skins 0 and 1...
2320 "noise" sound played when flag is picked up...
2321 "noise1" sound played when flag is returned by a teammate...
2322 "noise2" sound played when flag is captured...
2323 "noise3" sound played when flag is lost in the field and respawns itself...
2324 "noise4" sound played when flag is dropped by a player...
2325 "noise5" sound played when flag touches the ground... */
2326 void spawnfunc_item_flag_team1()
2327 {
2328         if(!g_ctf) { remove(self); return; }
2329
2330         ctf_FlagSetup(NUM_TEAM_1, self);
2331 }
2332
2333 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2334 CTF flag for team two (Blue).
2335 Keys:
2336 "angle" Angle the flag will point (minus 90 degrees)...
2337 "model" model to use, note this needs red and blue as skins 0 and 1...
2338 "noise" sound played when flag is picked up...
2339 "noise1" sound played when flag is returned by a teammate...
2340 "noise2" sound played when flag is captured...
2341 "noise3" sound played when flag is lost in the field and respawns itself...
2342 "noise4" sound played when flag is dropped by a player...
2343 "noise5" sound played when flag touches the ground... */
2344 void spawnfunc_item_flag_team2()
2345 {
2346         if(!g_ctf) { remove(self); return; }
2347
2348         ctf_FlagSetup(NUM_TEAM_2, self);
2349 }
2350
2351 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2352 CTF flag for team three (Yellow).
2353 Keys: 
2354 "angle" Angle the flag will point (minus 90 degrees)... 
2355 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2356 "noise" sound played when flag is picked up...
2357 "noise1" sound played when flag is returned by a teammate...
2358 "noise2" sound played when flag is captured...
2359 "noise3" sound played when flag is lost in the field and respawns itself... 
2360 "noise4" sound played when flag is dropped by a player...
2361 "noise5" sound played when flag touches the ground... */
2362 void spawnfunc_item_flag_team3()
2363 {
2364         if(!g_ctf) { remove(self); return; }
2365
2366         ctf_FlagSetup(NUM_TEAM_3, self);
2367 }
2368
2369 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2370 CTF flag for team four (Pink).
2371 Keys: 
2372 "angle" Angle the flag will point (minus 90 degrees)... 
2373 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2374 "noise" sound played when flag is picked up...
2375 "noise1" sound played when flag is returned by a teammate...
2376 "noise2" sound played when flag is captured...
2377 "noise3" sound played when flag is lost in the field and respawns itself... 
2378 "noise4" sound played when flag is dropped by a player...
2379 "noise5" sound played when flag touches the ground... */
2380 void spawnfunc_item_flag_team4()
2381 {
2382         if(!g_ctf) { remove(self); return; }
2383
2384         ctf_FlagSetup(NUM_TEAM_4, self);
2385 }
2386
2387 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2388 CTF flag (Neutral).
2389 Keys: 
2390 "angle" Angle the flag will point (minus 90 degrees)... 
2391 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2392 "noise" sound played when flag is picked up...
2393 "noise1" sound played when flag is returned by a teammate...
2394 "noise2" sound played when flag is captured...
2395 "noise3" sound played when flag is lost in the field and respawns itself... 
2396 "noise4" sound played when flag is dropped by a player...
2397 "noise5" sound played when flag touches the ground... */
2398 void spawnfunc_item_flag_neutral()
2399 {
2400         if(!g_ctf) { remove(self); return; }
2401
2402         ctf_FlagSetup(0, self);
2403 }
2404
2405 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2406 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2407 Note: If you use spawnfunc_ctf_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
2408 Keys:
2409 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2410 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2411 void spawnfunc_ctf_team()
2412 {
2413         if(!g_ctf) { remove(self); return; }
2414
2415         self.classname = "ctf_team";
2416         self.team = self.cnt + 1;
2417 }
2418
2419 // compatibility for quake maps
2420 void spawnfunc_team_CTF_redflag()    { spawnfunc_item_flag_team1();    }
2421 void spawnfunc_team_CTF_blueflag()   { spawnfunc_item_flag_team2();    }
2422 void spawnfunc_team_CTF_redplayer()  { spawnfunc_info_player_team1();  }
2423 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2();  }
2424 void spawnfunc_team_CTF_redspawn()   { spawnfunc_info_player_team1();  }
2425 void spawnfunc_team_CTF_bluespawn()  { spawnfunc_info_player_team2();  }
2426
2427
2428 // ==============
2429 // Initialization
2430 // ==============
2431
2432 // scoreboard setup
2433 void ctf_ScoreRules(float teams)
2434 {
2435         CheckAllowedTeams(world);
2436         ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2437         ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
2438         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
2439         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
2440         ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS,  "pickups",   0);
2441         ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
2442         ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
2443         ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
2444         ScoreRules_basics_end();
2445 }
2446
2447 // code from here on is just to support maps that don't have flag and team entities
2448 void ctf_SpawnTeam (string teamname, float teamcolor)
2449 {
2450         entity oldself;
2451         oldself = self;
2452         self = spawn();
2453         self.classname = "ctf_team";
2454         self.netname = teamname;
2455         self.cnt = teamcolor;
2456
2457         spawnfunc_ctf_team();
2458
2459         self = oldself;
2460 }
2461
2462 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2463 {
2464         ctf_teams = 2;
2465
2466         entity tmp_entity;
2467         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2468         {
2469                 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2470                 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2471                 if(tmp_entity.team == 0) { ctf_oneflag = TRUE; }
2472         }
2473
2474         ctf_teams = bound(2, ctf_teams, 4);
2475
2476         // if no teams are found, spawn defaults
2477         if(find(world, classname, "ctf_team") == world)
2478         {
2479                 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2480                 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2481                 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2482                 if(ctf_teams >= 3)
2483                         ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2484                 if(ctf_teams >= 4)
2485                         ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2486         }
2487
2488         ctf_ScoreRules(ctf_teams);
2489 }
2490
2491 void ctf_Initialize()
2492 {
2493         ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2494
2495         ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2496         ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2497         ctf_captureshield_force = autocvar_g_ctf_shield_force;
2498         
2499         addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2500
2501         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2502 }
2503
2504
2505 MUTATOR_DEFINITION(gamemode_ctf)
2506 {
2507         MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2508         MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2509         MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2510         MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2511         MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2512         MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2513         MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2514         MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2515         MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2516         MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2517         MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2518         MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2519         MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2520         MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2521         MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2522         MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2523
2524         MUTATOR_ONADD
2525         {
2526                 if(time > 1) // game loads at time 1
2527                         error("This is a game type and it cannot be added at runtime.");
2528                 ctf_Initialize();
2529         }
2530
2531         MUTATOR_ONROLLBACK_OR_REMOVE
2532         {
2533                 // we actually cannot roll back ctf_Initialize here
2534                 // BUT: we don't need to! If this gets called, adding always
2535                 // succeeds.
2536         }
2537
2538         MUTATOR_ONREMOVE
2539         {
2540                 print("This is a game type and it cannot be removed at runtime.");
2541                 return -1;
2542         }
2543
2544         return 0;
2545 }