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