]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_ctf.qc
705469d6efd3a694d76a2e95024066d79873e24e
[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(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
21 }
22
23 string ctf_CaptureRecord(entity flag, entity player)
24 {
25         float cap_time, cap_record, success;
26         string cap_message, refername;
27         
28         if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) 
29         {
30                 cap_record = ctf_captimerecord;
31                 cap_time = (time - flag.ctf_pickuptime);
32
33                 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
34                 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
35
36                 if(!ctf_captimerecord) 
37                         { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
38                 else if(cap_time < cap_record) 
39                         { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
40                 else
41                         { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
42
43                 if(success) 
44                 {
45                         ctf_captimerecord = cap_time;
46                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
47                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
48                         write_recordmarker(player, (time - cap_time), cap_time); 
49                 } 
50         }
51         
52         return cap_message;
53 }
54
55 void ctf_FlagcarrierWaypoints(entity player)
56 {
57         WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
58         WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
59         WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
60         WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
61 }
62
63 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
64 {
65         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
66         float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
67         float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
68         //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
69
70         vector targpos;
71         if(current_height) // make sure we can actually do this arcing path
72         {
73                 targpos = (to + ('0 0 1' * current_height));
74                 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
75                 if(trace_fraction < 1)
76                 {
77                         //print("normal arc line failed, trying to find new pos...");
78                         WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
79                         targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
80                         WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
81                         if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
82                         /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
83                 }
84         }
85         else { targpos = to; }
86
87         //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
88
89         vector desired_direction = normalize(targpos - from);
90         if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
91         else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
92 }
93
94 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
95 {
96         if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
97         {
98                 // directional tracing only
99                 float spreadlimit;
100                 makevectors(passer_angle);
101
102                 // find the closest point on the enemy to the center of the attack
103                 float ang; // angle between shotdir and h
104                 float h; // hypotenuse, which is the distance between attacker to head
105                 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
106                 
107                 h = vlen(head_center - passer_center);
108                 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
109                 a = h * cos(ang);
110
111                 vector nearest_on_line = (passer_center + a * v_forward);
112                 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
113
114                 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
115                 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
116
117                 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
118                         { return TRUE; }
119                 else
120                         { return FALSE; }
121         }
122         else { return TRUE; }
123 }
124
125
126 // =======================
127 // CaptureShield Functions 
128 // =======================
129
130 float ctf_CaptureShield_CheckStatus(entity p) 
131 {
132         float s, se;
133         entity e;
134         float players_worseeq, players_total;
135
136         if(ctf_captureshield_max_ratio <= 0)
137                 return FALSE;
138
139         s = PlayerScore_Add(p, SP_SCORE, 0);
140         if(s >= -ctf_captureshield_min_negscore)
141                 return FALSE;
142
143         players_total = players_worseeq = 0;
144         FOR_EACH_PLAYER(e)
145         {
146                 if(IsDifferentTeam(e, p))
147                         continue;
148                 se = PlayerScore_Add(e, SP_SCORE, 0);
149                 if(se <= s)
150                         ++players_worseeq;
151                 ++players_total;
152         }
153
154         // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
155         // use this rule here
156         
157         if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
158                 return FALSE;
159
160         return TRUE;
161 }
162
163 void ctf_CaptureShield_Update(entity player, float wanted_status)
164 {
165         float updated_status = ctf_CaptureShield_CheckStatus(player);
166         if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
167         {
168                 if(updated_status) // TODO csqc notifier for this // Samual: How?
169                         Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Make some defensive scores before trying again.", 5, 0);
170                 else
171                         Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
172                         
173                 player.ctf_captureshielded = updated_status;
174         }
175 }
176
177 float ctf_CaptureShield_Customize()
178 {
179         if(!other.ctf_captureshielded) { return FALSE; }
180         if(!IsDifferentTeam(self, other)) { return FALSE; }
181         
182         return TRUE;
183 }
184
185 void ctf_CaptureShield_Touch()
186 {
187         if(!other.ctf_captureshielded) { return; }
188         if(!IsDifferentTeam(self, other)) { return; }
189         
190         vector mymid = (self.absmin + self.absmax) * 0.5;
191         vector othermid = (other.absmin + other.absmax) * 0.5;
192
193         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
194         Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
195 }
196
197 void ctf_CaptureShield_Spawn(entity flag)
198 {
199         entity shield = spawn();
200         
201         shield.enemy = self;
202         shield.team = self.team;
203         shield.touch = ctf_CaptureShield_Touch;
204         shield.customizeentityforclient = ctf_CaptureShield_Customize;
205         shield.classname = "ctf_captureshield";
206         shield.effects = EF_ADDITIVE;
207         shield.movetype = MOVETYPE_NOCLIP;
208         shield.solid = SOLID_TRIGGER;
209         shield.avelocity = '7 0 11';
210         shield.scale = 0.5;
211         
212         setorigin(shield, self.origin);
213         setmodel(shield, "models/ctf/shield.md3");
214         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
215 }
216
217
218 // ====================
219 // Drop/Pass/Throw Code
220 // ====================
221
222 void ctf_Handle_Drop(entity flag, entity player, float droptype)
223 {
224         // declarations
225         player = (player ? player : flag.pass_sender);
226
227         // main
228         flag.movetype = MOVETYPE_TOSS;
229         flag.takedamage = DAMAGE_YES;
230         flag.angles = '0 0 0';
231         flag.health = flag.max_flag_health;
232         flag.ctf_droptime = time;
233         flag.ctf_dropper = player;
234         flag.ctf_status = FLAG_DROPPED;
235         
236         // messages and sounds
237         Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
238         sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
239         ctf_EventLog("dropped", player.team, player);
240
241         // scoring
242         PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);   
243         PlayerScore_Add(player, SP_CTF_DROPS, 1);
244         
245         // waypoints
246         if(autocvar_g_ctf_flag_dropped_waypoint)
247                 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
248
249         if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
250         {
251                 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
252                 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
253         }
254         
255         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
256         
257         if(droptype == DROP_PASS)
258         {
259                 flag.pass_distance = 0;
260                 flag.pass_sender = world;
261                 flag.pass_target = world;
262         }
263 }
264
265 void ctf_Handle_Retrieve(entity flag, entity player)
266 {
267         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
268         entity sender = flag.pass_sender;
269         
270         // transfer flag to player
271         flag.owner = player;
272         flag.owner.flagcarried = flag;
273         
274         // reset flag
275         setattachment(flag, player, "");
276         setorigin(flag, FLAG_CARRY_OFFSET);
277         flag.movetype = MOVETYPE_NONE;
278         flag.takedamage = DAMAGE_NO;
279         flag.solid = SOLID_NOT;
280         flag.angles = '0 0 0';
281         flag.ctf_status = FLAG_CARRY;
282
283         // messages and sounds
284         sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
285         ctf_EventLog("receive", flag.team, player);
286         
287         FOR_EACH_REALPLAYER(tmp_player)
288         {
289                 if(tmp_player == sender)
290                         centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
291                 else if(tmp_player == player)
292                         centerprint(tmp_player, strcat("You received the ", flag.netname, " from ", sender.netname));
293                 else if(!IsDifferentTeam(tmp_player, sender))
294                         centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
295         }
296         
297         // create new waypoint
298         ctf_FlagcarrierWaypoints(player);
299         
300         sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
301         player.throw_antispam = sender.throw_antispam;
302
303         flag.pass_distance = 0;
304         flag.pass_sender = world;
305         flag.pass_target = world;
306 }
307
308 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
309 {
310         entity flag = player.flagcarried;
311         vector targ_origin, flag_velocity;
312         
313         if(!flag) { return; }
314         if((droptype == DROP_PASS) && !receiver) { return; }
315         
316         if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
317         
318         // reset the flag
319         setattachment(flag, world, "");
320         setorigin(flag, player.origin + FLAG_DROP_OFFSET);
321         flag.owner.flagcarried = world;
322         flag.owner = world;
323         flag.solid = SOLID_TRIGGER;
324         flag.ctf_dropper = player;
325         flag.ctf_droptime = time;
326         
327         flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
328         
329         switch(droptype)
330         {
331                 case DROP_PASS:
332                 {
333                         // warpzone support:
334                         // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
335                         // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
336                         WarpZone_RefSys_Copy(flag, receiver);
337                         WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
338                         targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
339
340                         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
341                         ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
342
343                         // main
344                         flag.movetype = MOVETYPE_FLY;
345                         flag.takedamage = DAMAGE_NO;
346                         flag.pass_sender = player;
347                         flag.pass_target = receiver;
348                         flag.ctf_status = FLAG_PASSING;
349                         
350                         // other
351                         sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
352                         WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
353                         ctf_EventLog("pass", flag.team, player);
354                         break;
355                 }
356                 
357                 case DROP_THROW:
358                 {
359                         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'));
360                                 
361                         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)));
362                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
363                         ctf_Handle_Drop(flag, player, droptype);
364                         break;
365                 }
366                 
367                 case DROP_RESET:
368                 {
369                         flag.velocity = '0 0 0'; // do nothing
370                         break;
371                 }
372                 
373                 default:
374                 case DROP_NORMAL:
375                 {
376                         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);
377                         ctf_Handle_Drop(flag, player, droptype);
378                         break;
379                 }
380         }
381
382         // kill old waypointsprite
383         WaypointSprite_Ping(player.wps_flagcarrier);
384         WaypointSprite_Kill(player.wps_flagcarrier);
385         
386         if(player.wps_enemyflagcarrier)
387                 WaypointSprite_Kill(player.wps_enemyflagcarrier);
388         
389         // captureshield
390         ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
391 }
392
393
394 // ==============
395 // Event Handlers
396 // ==============
397
398 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
399 {
400         entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
401         entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
402         float old_time, new_time; 
403         
404         if not(player) { return; } // without someone to give the reward to, we can't possibly cap
405         
406         // messages and sounds
407         Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
408         sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
409         
410         switch(capturetype)
411         {
412                 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
413                 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
414                 default: break;
415         }
416         
417         // scoring
418         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
419         PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
420
421         old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
422         new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
423         if(!old_time || new_time < old_time)
424                 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
425
426         // effects
427         pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
428         //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
429
430         // other
431         if(capturetype == CAPTURE_NORMAL)
432         {
433                 WaypointSprite_Kill(player.wps_flagcarrier);
434                 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
435                 
436                 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
437                         { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
438         }
439         
440         // reset the flag
441         player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
442         ctf_RespawnFlag(enemy_flag);
443 }
444
445 void ctf_Handle_Return(entity flag, entity player)
446 {
447         // messages and sounds
448         //centerprint(player, strcat("You returned the ", flag.netname));
449         Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
450         sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
451         ctf_EventLog("return", flag.team, player);
452
453         // scoring
454         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
455         PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
456
457         TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
458         
459         if(flag.ctf_dropper) 
460         {
461                 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
462                 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag 
463                 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
464         }
465         
466         // reset the flag
467         ctf_RespawnFlag(flag);
468 }
469
470 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
471 {
472         // declarations
473         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
474         string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
475         float pickup_dropped_score; // used to calculate dropped pickup score
476         
477         // attach the flag to the player
478         flag.owner = player;
479         player.flagcarried = flag;
480         setattachment(flag, player, "");
481         setorigin(flag, FLAG_CARRY_OFFSET);
482         
483         // flag setup
484         flag.movetype = MOVETYPE_NONE;
485         flag.takedamage = DAMAGE_NO;
486         flag.solid = SOLID_NOT;
487         flag.angles = '0 0 0';
488         flag.ctf_status = FLAG_CARRY;
489         
490         switch(pickuptype)
491         {
492                 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
493                 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
494                 default: break;
495         }
496
497         // messages and sounds
498         Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
499         sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
500         verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
501         
502         FOR_EACH_REALPLAYER(tmp_player)
503         {
504                 if(tmp_player == player)
505                         centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
506                 //else if(!IsDifferentTeam(tmp_player, player))
507                 //      centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
508                 else if(!IsDifferentTeam(tmp_player, flag))
509                         centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
510         }
511         
512         // scoring
513         PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
514         switch(pickuptype)
515         {               
516                 case PICKUP_BASE:
517                 {
518                         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
519                         ctf_EventLog("steal", flag.team, player);
520                         break;
521                 }
522                 
523                 case PICKUP_DROPPED:
524                 {
525                         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);
526                         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);
527                         dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
528                         PlayerTeamScore_AddScore(player, pickup_dropped_score);
529                         ctf_EventLog("pickup", flag.team, player);
530                         break;
531                 }
532                 
533                 default: break;
534         }
535         
536         // speedrunning
537         if(pickuptype == PICKUP_BASE)
538         {
539                 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
540                 if((player.speedrunning) && (ctf_captimerecord))
541                         ctf_FakeTimeLimit(player, time + ctf_captimerecord);
542         }
543                 
544         // effects
545         pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
546         
547         // waypoints 
548         if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
549         ctf_FlagcarrierWaypoints(player);
550         WaypointSprite_Ping(player.wps_flagcarrier);
551 }
552
553
554 // ===================
555 // Main Flag Functions
556 // ===================
557
558 void ctf_CheckFlagReturn(entity flag, float returntype)
559 {
560         if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
561         {
562                 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
563                 
564                 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
565                 {
566                         switch(returntype)
567                         {
568                                 case RETURN_DROPPED: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
569                                 case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
570                                 case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
571                                 case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
572                                 
573                                 default:
574                                 case RETURN_TIMEOUT:
575                                         { bprint("The ", flag.netname, " has returned to base\n"); break; }
576                         }
577                         sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
578                         ctf_EventLog("returned", flag.team, world);
579                         ctf_RespawnFlag(flag);
580                 }
581         }
582 }
583
584 void ctf_CheckStalemate(void)
585 {
586         // declarations
587         float stale_red_flags, stale_blue_flags;
588         entity tmp_entity;
589
590         entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
591
592         // build list of stale flags
593         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
594         {
595                 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
596                 if(tmp_entity.ctf_status != FLAG_BASE)
597                 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
598                 {
599                         tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
600                         ctf_staleflaglist = tmp_entity;
601                         
602                         switch(tmp_entity.team)
603                         {
604                                 case COLOR_TEAM1: ++stale_red_flags; break;
605                                 case COLOR_TEAM2: ++stale_blue_flags; break;
606                         }
607                 }
608         }
609
610         if(stale_red_flags && stale_blue_flags)
611                 ctf_stalemate = TRUE;
612         else if(!stale_red_flags && !stale_blue_flags)
613                 ctf_stalemate = FALSE;
614         
615         // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
616         if(ctf_stalemate)
617         {
618                 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
619                 {
620                         if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
621                                 WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
622                 }
623                 
624                 if not(wpforenemy_announced)
625                 {
626                         FOR_EACH_REALPLAYER(tmp_entity)
627                                 if(tmp_entity.flagcarried)
628                                         centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
629                                 else
630                                         centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
631                         
632                         wpforenemy_announced = TRUE;
633                 }
634         }
635 }
636
637 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
638 {
639         if(ITEM_DAMAGE_NEEDKILL(deathtype))
640         {
641                 // automatically kill the flag and return it
642                 self.health = 0;
643                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
644                 return;
645         }
646         if(autocvar_g_ctf_flag_return_damage) 
647         {
648                 // reduce health and check if it should be returned
649                 self.health = self.health - damage;
650                 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
651                 return;
652         }
653 }
654
655 void ctf_FlagThink()
656 {
657         // declarations
658         entity tmp_entity;
659
660         self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
661
662         // captureshield
663         if(self == ctf_worldflaglist) // only for the first flag
664                 FOR_EACH_CLIENT(tmp_entity)
665                         ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
666
667         // sanity checks
668         if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
669                 dprint("wtf the flag got squashed?\n");
670                 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
671                 if(!trace_startsolid) // can we resize it without getting stuck?
672                         setsize(self, FLAG_MIN, FLAG_MAX); }
673                         
674         switch(self.ctf_status) // reset flag angles in case warpzones adjust it
675         {
676                 case FLAG_DROPPED:
677                 {
678                         self.angles = '0 0 0';
679                         break;
680                 }
681                 
682                 default: break;
683         }
684
685         // main think method
686         switch(self.ctf_status)
687         {       
688                 case FLAG_BASE:
689                 {
690                         if(autocvar_g_ctf_dropped_capture_radius)
691                         {
692                                 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
693                                         if(tmp_entity.ctf_status == FLAG_DROPPED)
694                                         if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
695                                         if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
696                                                 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
697                         }
698                         return;
699                 }
700                 
701                 case FLAG_DROPPED:
702                 {
703                         if(autocvar_g_ctf_flag_dropped_floatinwater)
704                         {
705                                 vector midpoint = ((self.absmin + self.absmax) * 0.5);
706                                 if(pointcontents(midpoint) == CONTENT_WATER)
707                                 {
708                                         self.velocity = self.velocity * 0.5;
709                                         
710                                         if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
711                                                 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
712                                         else
713                                                 { self.movetype = MOVETYPE_FLY; }
714                                 }
715                                 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
716                         }
717                         if(autocvar_g_ctf_flag_return_dropped)
718                         {
719                                 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
720                                 {
721                                         self.health = 0;
722                                         ctf_CheckFlagReturn(self, RETURN_DROPPED);
723                                         return;
724                                 }
725                         }
726                         if(autocvar_g_ctf_flag_return_time)
727                         {
728                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
729                                 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
730                                 return;
731                         } 
732                         return;
733                 }
734                         
735                 case FLAG_CARRY:
736                 {
737                         if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord)) 
738                         {
739                                 self.health = 0;
740                                 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
741
742                                 tmp_entity = self;
743                                 self = self.owner;
744                                 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
745                                 ImpulseCommands();
746                                 self = tmp_entity;
747                         }
748                         if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
749                         {
750                                 if(time >= wpforenemy_nextthink)
751                                 {
752                                         ctf_CheckStalemate();
753                                         wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
754                                 }
755                         }
756                         return;
757                 }
758                 
759                 case FLAG_PASSING:
760                 {
761                         vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
762                         targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
763                         WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
764                         
765                         if((self.pass_target == world)
766                                 || (self.pass_target.deadflag != DEAD_NO)
767                                 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
768                                 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
769                                 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
770                         {
771                                 // give up, pass failed
772                                 ctf_Handle_Drop(self, world, DROP_PASS);
773                         }
774                         else
775                         {
776                                 // still a viable target, go for it
777                                 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
778                         }
779                         return;
780                 }
781
782                 default: // this should never happen
783                 {
784                         dprint("ctf_FlagThink(): Flag exists with no status?\n");
785                         return;
786                 }
787         }
788 }
789
790 void ctf_FlagTouch()
791 {
792         if(gameover) { return; }
793         
794         entity toucher = other;
795         
796         // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
797         if(ITEM_TOUCH_NEEDKILL())
798         {
799                 self.health = 0;
800                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
801                 return;
802         }
803         
804         // special touch behaviors
805         if(toucher.vehicle_flags & VHF_ISVEHICLE)
806         {
807                 if(autocvar_g_ctf_allow_vehicle_touch)
808                         toucher = toucher.owner; // the player is actually the vehicle owner, not other
809                 else
810                         return; // do nothing
811         }
812         else if(toucher.classname != "player") // The flag just touched an object, most likely the world
813         {
814                 if(time > self.wait) // if we haven't in a while, play a sound/effect
815                 {
816                         pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
817                         sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
818                         self.wait = time + FLAG_TOUCHRATE;
819                 }
820                 return;
821         }
822         else if(toucher.deadflag != DEAD_NO) { return; }
823
824         switch(self.ctf_status) 
825         {       
826                 case FLAG_BASE:
827                 {
828                         if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
829                                 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
830                         else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
831                                 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
832                         break;
833                 }
834                 
835                 case FLAG_DROPPED:
836                 {
837                         if(!IsDifferentTeam(toucher, self))
838                                 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
839                         else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
840                                 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
841                         break;
842                 }
843                         
844                 case FLAG_CARRY:
845                 {
846                         dprint("Someone touched a flag even though it was being carried?\n");
847                         break;
848                 }
849                 
850                 case FLAG_PASSING:
851                 {
852                         if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
853                         {
854                                 if(IsDifferentTeam(toucher, self.pass_sender))
855                                         ctf_Handle_Return(self, toucher);
856                                 else
857                                         ctf_Handle_Retrieve(self, toucher);
858                         }
859                         break;
860                 }
861         }
862 }
863
864 .float last_respawn;
865 void ctf_RespawnFlag(entity flag)
866 {
867         // check for flag respawn being called twice in a row
868         if(flag.last_respawn > time - 0.5)
869                 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
870
871         flag.last_respawn = time;
872         
873         // reset the player (if there is one)
874         if((flag.owner) && (flag.owner.flagcarried == flag))
875         {
876                 if(flag.owner.wps_enemyflagcarrier)
877                         WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
878                         
879                 WaypointSprite_Kill(flag.wps_flagcarrier);
880                 
881                 flag.owner.flagcarried = world;
882
883                 if(flag.speedrunning)
884                         ctf_FakeTimeLimit(flag.owner, -1);
885         }
886
887         if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
888                 { WaypointSprite_Kill(flag.wps_flagdropped); }
889
890         // reset the flag
891         setattachment(flag, world, "");
892         setorigin(flag, flag.ctf_spawnorigin);
893         
894         flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
895         flag.takedamage = DAMAGE_NO;
896         flag.health = flag.max_flag_health;
897         flag.solid = SOLID_TRIGGER;
898         flag.velocity = '0 0 0';
899         flag.angles = flag.mangle;
900         flag.flags = FL_ITEM | FL_NOTARGET;
901         
902         flag.ctf_status = FLAG_BASE;
903         flag.owner = world;
904         flag.pass_distance = 0;
905         flag.pass_sender = world;
906         flag.pass_target = world;
907         flag.ctf_dropper = world;
908         flag.ctf_pickuptime = 0;
909         flag.ctf_droptime = 0;
910
911         wpforenemy_announced = FALSE;
912 }
913
914 void ctf_Reset()
915 {
916         if(self.owner)
917                 if(self.owner.classname == "player")
918                         ctf_Handle_Throw(self.owner, world, DROP_RESET);
919                         
920         ctf_RespawnFlag(self);
921 }
922
923 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
924 {
925         // bot waypoints
926         waypoint_spawnforitem_force(self, self.origin);
927         self.nearestwaypointtimeout = 0; // activate waypointing again
928         self.bot_basewaypoint = self.nearestwaypoint;
929
930         // waypointsprites
931         WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
932         WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
933
934         // captureshield setup
935         ctf_CaptureShield_Spawn(self);
936 }
937
938 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc 
939 {       
940         // declarations
941         teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1. 
942         self = flag; // for later usage with droptofloor()
943         
944         // main setup
945         flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
946         ctf_worldflaglist = flag;
947
948         setattachment(flag, world, ""); 
949
950         flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
951         flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
952         flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
953         flag.classname = "item_flag_team";
954         flag.target = "###item###"; // wut?
955         flag.flags = FL_ITEM | FL_NOTARGET;
956         flag.solid = SOLID_TRIGGER;
957         flag.takedamage = DAMAGE_NO;
958         flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;   
959         flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
960         flag.health = flag.max_flag_health;
961         flag.event_damage = ctf_FlagDamage;
962         flag.pushable = TRUE;
963         flag.teleportable = TELEPORT_NORMAL;
964         flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
965         flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
966         flag.velocity = '0 0 0';
967         flag.mangle = flag.angles;
968         flag.reset = ctf_Reset;
969         flag.touch = ctf_FlagTouch;
970         flag.think = ctf_FlagThink;
971         flag.nextthink = time + FLAG_THINKRATE;
972         flag.ctf_status = FLAG_BASE;
973         
974         if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
975         if(!flag.scale) { flag.scale = FLAG_SCALE; }
976         if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
977         if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
978         if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
979         if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
980         
981         // sound 
982         if(!flag.snd_flag_taken) { flag.snd_flag_taken  = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
983         if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
984         if(!flag.snd_flag_capture) { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
985         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.
986         if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
987         if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
988         if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
989         
990         // precache
991         precache_sound(flag.snd_flag_taken);
992         precache_sound(flag.snd_flag_returned);
993         precache_sound(flag.snd_flag_capture);
994         precache_sound(flag.snd_flag_respawn);
995         precache_sound(flag.snd_flag_dropped);
996         precache_sound(flag.snd_flag_touch);
997         precache_sound(flag.snd_flag_pass);
998         precache_model(flag.model);
999         precache_model("models/ctf/shield.md3");
1000         precache_model("models/ctf/shockwavetransring.md3");
1001
1002         // appearence
1003         setmodel(flag, flag.model); // precision set below
1004         setsize(flag, FLAG_MIN, FLAG_MAX);
1005         setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1006         
1007         if(autocvar_g_ctf_flag_glowtrails)
1008         {
1009                 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1010                 flag.glow_size = 25;
1011                 flag.glow_trail = 1;
1012         }
1013         
1014         flag.effects |= EF_LOWPRECISION;
1015         if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1016         if(autocvar_g_ctf_dynamiclights)   { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1017         
1018         // flag placement
1019         if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1020         {       
1021                 flag.dropped_origin = flag.origin; 
1022                 flag.noalign = TRUE;
1023                 flag.movetype = MOVETYPE_NONE;
1024         }
1025         else // drop to floor, automatically find a platform and set that as spawn origin
1026         { 
1027                 flag.noalign = FALSE;
1028                 self = flag;
1029                 droptofloor();
1030                 flag.movetype = MOVETYPE_TOSS; 
1031         }       
1032         
1033         InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1034 }
1035
1036
1037 // ================
1038 // Bot player logic
1039 // ================
1040
1041 // NOTE: LEGACY CODE, needs to be re-written!
1042
1043 void havocbot_calculate_middlepoint()
1044 {
1045         entity f;
1046         vector s = '0 0 0';
1047         vector fo = '0 0 0';
1048         float n = 0;
1049
1050         f = ctf_worldflaglist;
1051         while (f)
1052         {
1053                 fo = f.origin;
1054                 s = s + fo;
1055                 f = f.ctf_worldflagnext;
1056         }
1057         if(!n)
1058                 return;
1059         havocbot_ctf_middlepoint = s * (1.0 / n);
1060         havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
1061 }
1062
1063
1064 entity havocbot_ctf_find_flag(entity bot)
1065 {
1066         entity f;
1067         f = ctf_worldflaglist;
1068         while (f)
1069         {
1070                 if (bot.team == f.team)
1071                         return f;
1072                 f = f.ctf_worldflagnext;
1073         }
1074         return world;
1075 }
1076
1077 entity havocbot_ctf_find_enemy_flag(entity bot)
1078 {
1079         entity f;
1080         f = ctf_worldflaglist;
1081         while (f)
1082         {
1083                 if (bot.team != f.team)
1084                         return f;
1085                 f = f.ctf_worldflagnext;
1086         }
1087         return world;
1088 }
1089
1090 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1091 {
1092         if not(teamplay)
1093                 return 0;
1094
1095         float c = 0;
1096         entity head;
1097
1098         FOR_EACH_PLAYER(head)
1099         {
1100                 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1101                         continue;
1102
1103                 if(vlen(head.origin - org) < tc_radius)
1104                         ++c;
1105         }
1106
1107         return c;
1108 }
1109
1110 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1111 {
1112         entity head;
1113         head = ctf_worldflaglist;
1114         while (head)
1115         {
1116                 if (self.team == head.team)
1117                         break;
1118                 head = head.ctf_worldflagnext;
1119         }
1120         if (head)
1121                 navigation_routerating(head, ratingscale, 10000);
1122 }
1123
1124 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1125 {
1126         entity head;
1127         head = ctf_worldflaglist;
1128         while (head)
1129         {
1130                 if (self.team == head.team)
1131                         break;
1132                 head = head.ctf_worldflagnext;
1133         }
1134         if not(head)
1135                 return;
1136
1137         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1138 }
1139
1140 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1141 {
1142         entity head;
1143         head = ctf_worldflaglist;
1144         while (head)
1145         {
1146                 if (self.team != head.team)
1147                         break;
1148                 head = head.ctf_worldflagnext;
1149         }
1150         if (head)
1151                 navigation_routerating(head, ratingscale, 10000);
1152 }
1153
1154 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1155 {
1156         if not(bot_waypoints_for_items)
1157         {
1158                 havocbot_goalrating_ctf_enemyflag(ratingscale);
1159                 return;
1160         }
1161
1162         entity head;
1163
1164         head = havocbot_ctf_find_enemy_flag(self);
1165
1166         if not(head)
1167                 return;
1168
1169         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1170 }
1171
1172 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1173 {
1174         entity mf;
1175
1176         mf = havocbot_ctf_find_flag(self);
1177
1178         if(mf.ctf_status == FLAG_BASE)
1179                 return;
1180
1181         if(mf.tag_entity)
1182                 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1183 }
1184
1185 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1186 {
1187         entity head;
1188         head = ctf_worldflaglist;
1189         while (head)
1190         {
1191                 // flag is out in the field
1192                 if(head.ctf_status != FLAG_BASE)
1193                 if(head.tag_entity==world)      // dropped
1194                 {
1195                         if(df_radius)
1196                         {
1197                                 if(vlen(org-head.origin)<df_radius)
1198                                         navigation_routerating(head, ratingscale, 10000);
1199                         }
1200                         else
1201                                 navigation_routerating(head, ratingscale, 10000);
1202                 }
1203
1204                 head = head.ctf_worldflagnext;
1205         }
1206 }
1207
1208 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1209 {
1210         entity head;
1211         float t;
1212         head = findchainfloat(bot_pickup, TRUE);
1213         while (head)
1214         {
1215                 // gather health and armor only
1216                 if (head.solid)
1217                 if (head.health || head.armorvalue)
1218                 if (vlen(head.origin - org) < sradius)
1219                 {
1220                         // get the value of the item
1221                         t = head.bot_pickupevalfunc(self, head) * 0.0001;
1222                         if (t > 0)
1223                                 navigation_routerating(head, t * ratingscale, 500);
1224                 }
1225                 head = head.chain;
1226         }
1227 }
1228
1229 void havocbot_ctf_reset_role(entity bot)
1230 {
1231         float cdefense, cmiddle, coffense;
1232         entity mf, ef, head;
1233         float c;
1234
1235         if(bot.deadflag != DEAD_NO)
1236                 return;
1237
1238         if(vlen(havocbot_ctf_middlepoint)==0)
1239                 havocbot_calculate_middlepoint();
1240
1241         // Check ctf flags
1242         if (bot.flagcarried)
1243         {
1244                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1245                 return;
1246         }
1247
1248         mf = havocbot_ctf_find_flag(bot);
1249         ef = havocbot_ctf_find_enemy_flag(bot);
1250
1251         // Retrieve stolen flag
1252         if(mf.ctf_status!=FLAG_BASE)
1253         {
1254                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1255                 return;
1256         }
1257
1258         // If enemy flag is taken go to the middle to intercept pursuers
1259         if(ef.ctf_status!=FLAG_BASE)
1260         {
1261                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1262                 return;
1263         }
1264
1265         // if there is only me on the team switch to offense
1266         c = 0;
1267         FOR_EACH_PLAYER(head)
1268         if(head.team==bot.team)
1269                 ++c;
1270
1271         if(c==1)
1272         {
1273                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1274                 return;
1275         }
1276
1277         // Evaluate best position to take
1278         // Count mates on middle position
1279         cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1280
1281         // Count mates on defense position
1282         cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1283
1284         // Count mates on offense position
1285         coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1286
1287         if(cdefense<=coffense)
1288                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1289         else if(coffense<=cmiddle)
1290                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1291         else
1292                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1293 }
1294
1295 void havocbot_role_ctf_carrier()
1296 {
1297         if(self.deadflag != DEAD_NO)
1298         {
1299                 havocbot_ctf_reset_role(self);
1300                 return;
1301         }
1302
1303         if (self.flagcarried == world)
1304         {
1305                 havocbot_ctf_reset_role(self);
1306                 return;
1307         }
1308
1309         if (self.bot_strategytime < time)
1310         {
1311                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1312
1313                 navigation_goalrating_start();
1314                 havocbot_goalrating_ctf_ourbase(50000);
1315
1316                 if(self.health<100)
1317                         havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1318
1319                 navigation_goalrating_end();
1320
1321                 if (self.navigation_hasgoals)
1322                         self.havocbot_cantfindflag = time + 10;
1323                 else if (time > self.havocbot_cantfindflag)
1324                 {
1325                         // Can't navigate to my own base, suicide!
1326                         // TODO: drop it and wander around
1327                         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1328                         return;
1329                 }
1330         }
1331 }
1332
1333 void havocbot_role_ctf_escort()
1334 {
1335         entity mf, ef;
1336
1337         if(self.deadflag != DEAD_NO)
1338         {
1339                 havocbot_ctf_reset_role(self);
1340                 return;
1341         }
1342
1343         if (self.flagcarried)
1344         {
1345                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1346                 return;
1347         }
1348
1349         // If enemy flag is back on the base switch to previous role
1350         ef = havocbot_ctf_find_enemy_flag(self);
1351         if(ef.ctf_status==FLAG_BASE)
1352         {
1353                 self.havocbot_role = self.havocbot_previous_role;
1354                 self.havocbot_role_timeout = 0;
1355                 return;
1356         }
1357
1358         // If the flag carrier reached the base switch to defense
1359         mf = havocbot_ctf_find_flag(self);
1360         if(mf.ctf_status!=FLAG_BASE)
1361         if(vlen(ef.origin - mf.dropped_origin) < 300)
1362         {
1363                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1364                 return;
1365         }
1366
1367         // Set the role timeout if necessary
1368         if (!self.havocbot_role_timeout)
1369         {
1370                 self.havocbot_role_timeout = time + random() * 30 + 60;
1371         }
1372
1373         // If nothing happened just switch to previous role
1374         if (time > self.havocbot_role_timeout)
1375         {
1376                 self.havocbot_role = self.havocbot_previous_role;
1377                 self.havocbot_role_timeout = 0;
1378                 return;
1379         }
1380
1381         // Chase the flag carrier
1382         if (self.bot_strategytime < time)
1383         {
1384                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1385                 navigation_goalrating_start();
1386                 havocbot_goalrating_ctf_enemyflag(30000);
1387                 havocbot_goalrating_ctf_ourstolenflag(40000);
1388                 havocbot_goalrating_items(10000, self.origin, 10000);
1389                 navigation_goalrating_end();
1390         }
1391 }
1392
1393 void havocbot_role_ctf_offense()
1394 {
1395         entity mf, ef;
1396         vector pos;
1397
1398         if(self.deadflag != DEAD_NO)
1399         {
1400                 havocbot_ctf_reset_role(self);
1401                 return;
1402         }
1403
1404         if (self.flagcarried)
1405         {
1406                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1407                 return;
1408         }
1409
1410         // Check flags
1411         mf = havocbot_ctf_find_flag(self);
1412         ef = havocbot_ctf_find_enemy_flag(self);
1413
1414         // Own flag stolen
1415         if(mf.ctf_status!=FLAG_BASE)
1416         {
1417                 if(mf.tag_entity)
1418                         pos = mf.tag_entity.origin;
1419                 else
1420                         pos = mf.origin;
1421
1422                 // Try to get it if closer than the enemy base
1423                 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1424                 {
1425                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1426                         return;
1427                 }
1428         }
1429
1430         // Escort flag carrier
1431         if(ef.ctf_status!=FLAG_BASE)
1432         {
1433                 if(ef.tag_entity)
1434                         pos = ef.tag_entity.origin;
1435                 else
1436                         pos = ef.origin;
1437
1438                 if(vlen(pos-mf.dropped_origin)>700)
1439                 {
1440                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1441                         return;
1442                 }
1443         }
1444
1445         // About to fail, switch to middlefield
1446         if(self.health<50)
1447         {
1448                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1449                 return;
1450         }
1451
1452         // Set the role timeout if necessary
1453         if (!self.havocbot_role_timeout)
1454                 self.havocbot_role_timeout = time + 120;
1455
1456         if (time > self.havocbot_role_timeout)
1457         {
1458                 havocbot_ctf_reset_role(self);
1459                 return;
1460         }
1461
1462         if (self.bot_strategytime < time)
1463         {
1464                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1465                 navigation_goalrating_start();
1466                 havocbot_goalrating_ctf_ourstolenflag(50000);
1467                 havocbot_goalrating_ctf_enemybase(20000);
1468                 havocbot_goalrating_items(5000, self.origin, 1000);
1469                 havocbot_goalrating_items(1000, self.origin, 10000);
1470                 navigation_goalrating_end();
1471         }
1472 }
1473
1474 // Retriever (temporary role):
1475 void havocbot_role_ctf_retriever()
1476 {
1477         entity mf;
1478
1479         if(self.deadflag != DEAD_NO)
1480         {
1481                 havocbot_ctf_reset_role(self);
1482                 return;
1483         }
1484
1485         if (self.flagcarried)
1486         {
1487                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1488                 return;
1489         }
1490
1491         // If flag is back on the base switch to previous role
1492         mf = havocbot_ctf_find_flag(self);
1493         if(mf.ctf_status==FLAG_BASE)
1494         {
1495                 havocbot_ctf_reset_role(self);
1496                 return;
1497         }
1498
1499         if (!self.havocbot_role_timeout)
1500                 self.havocbot_role_timeout = time + 20;
1501
1502         if (time > self.havocbot_role_timeout)
1503         {
1504                 havocbot_ctf_reset_role(self);
1505                 return;
1506         }
1507
1508         if (self.bot_strategytime < time)
1509         {
1510                 float rt_radius;
1511                 rt_radius = 10000;
1512
1513                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1514                 navigation_goalrating_start();
1515                 havocbot_goalrating_ctf_ourstolenflag(50000);
1516                 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1517                 havocbot_goalrating_ctf_enemybase(30000);
1518                 havocbot_goalrating_items(500, self.origin, rt_radius);
1519                 navigation_goalrating_end();
1520         }
1521 }
1522
1523 void havocbot_role_ctf_middle()
1524 {
1525         entity mf;
1526
1527         if(self.deadflag != DEAD_NO)
1528         {
1529                 havocbot_ctf_reset_role(self);
1530                 return;
1531         }
1532
1533         if (self.flagcarried)
1534         {
1535                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1536                 return;
1537         }
1538
1539         mf = havocbot_ctf_find_flag(self);
1540         if(mf.ctf_status!=FLAG_BASE)
1541         {
1542                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1543                 return;
1544         }
1545
1546         if (!self.havocbot_role_timeout)
1547                 self.havocbot_role_timeout = time + 10;
1548
1549         if (time > self.havocbot_role_timeout)
1550         {
1551                 havocbot_ctf_reset_role(self);
1552                 return;
1553         }
1554
1555         if (self.bot_strategytime < time)
1556         {
1557                 vector org;
1558
1559                 org = havocbot_ctf_middlepoint;
1560                 org_z = self.origin_z;
1561
1562                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1563                 navigation_goalrating_start();
1564                 havocbot_goalrating_ctf_ourstolenflag(50000);
1565                 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1566                 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1567                 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1568                 havocbot_goalrating_items(2500, self.origin, 10000);
1569                 havocbot_goalrating_ctf_enemybase(2500);
1570                 navigation_goalrating_end();
1571         }
1572 }
1573
1574 void havocbot_role_ctf_defense()
1575 {
1576         entity mf;
1577
1578         if(self.deadflag != DEAD_NO)
1579         {
1580                 havocbot_ctf_reset_role(self);
1581                 return;
1582         }
1583
1584         if (self.flagcarried)
1585         {
1586                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1587                 return;
1588         }
1589
1590         // If own flag was captured
1591         mf = havocbot_ctf_find_flag(self);
1592         if(mf.ctf_status!=FLAG_BASE)
1593         {
1594                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1595                 return;
1596         }
1597
1598         if (!self.havocbot_role_timeout)
1599                 self.havocbot_role_timeout = time + 30;
1600
1601         if (time > self.havocbot_role_timeout)
1602         {
1603                 havocbot_ctf_reset_role(self);
1604                 return;
1605         }
1606         if (self.bot_strategytime < time)
1607         {
1608                 float mp_radius;
1609                 vector org;
1610
1611                 org = mf.dropped_origin;
1612                 mp_radius = havocbot_ctf_middlepoint_radius;
1613
1614                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1615                 navigation_goalrating_start();
1616
1617                 // if enemies are closer to our base, go there
1618                 entity head, closestplayer = world;
1619                 float distance, bestdistance = 10000;
1620                 FOR_EACH_PLAYER(head)
1621                 {
1622                         if(head.deadflag!=DEAD_NO)
1623                                 continue;
1624
1625                         distance = vlen(org - head.origin);
1626                         if(distance<bestdistance)
1627                         {
1628                                 closestplayer = head;
1629                                 bestdistance = distance;
1630                         }
1631                 }
1632
1633                 if(closestplayer)
1634                 if(closestplayer.team!=self.team)
1635                 if(vlen(org - self.origin)>1000)
1636                 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1637                         havocbot_goalrating_ctf_ourbase(30000);
1638
1639                 havocbot_goalrating_ctf_ourstolenflag(20000);
1640                 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1641                 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1642                 havocbot_goalrating_items(10000, org, mp_radius);
1643                 havocbot_goalrating_items(5000, self.origin, 10000);
1644                 navigation_goalrating_end();
1645         }
1646 }
1647
1648 void havocbot_role_ctf_setrole(entity bot, float role)
1649 {
1650         dprint(strcat(bot.netname," switched to "));
1651         switch(role)
1652         {
1653                 case HAVOCBOT_CTF_ROLE_CARRIER:
1654                         dprint("carrier");
1655                         bot.havocbot_role = havocbot_role_ctf_carrier;
1656                         bot.havocbot_role_timeout = 0;
1657                         bot.havocbot_cantfindflag = time + 10;
1658                         bot.bot_strategytime = 0;
1659                         break;
1660                 case HAVOCBOT_CTF_ROLE_DEFENSE:
1661                         dprint("defense");
1662                         bot.havocbot_role = havocbot_role_ctf_defense;
1663                         bot.havocbot_role_timeout = 0;
1664                         break;
1665                 case HAVOCBOT_CTF_ROLE_MIDDLE:
1666                         dprint("middle");
1667                         bot.havocbot_role = havocbot_role_ctf_middle;
1668                         bot.havocbot_role_timeout = 0;
1669                         break;
1670                 case HAVOCBOT_CTF_ROLE_OFFENSE:
1671                         dprint("offense");
1672                         bot.havocbot_role = havocbot_role_ctf_offense;
1673                         bot.havocbot_role_timeout = 0;
1674                         break;
1675                 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1676                         dprint("retriever");
1677                         bot.havocbot_previous_role = bot.havocbot_role;
1678                         bot.havocbot_role = havocbot_role_ctf_retriever;
1679                         bot.havocbot_role_timeout = time + 10;
1680                         bot.bot_strategytime = 0;
1681                         break;
1682                 case HAVOCBOT_CTF_ROLE_ESCORT:
1683                         dprint("escort");
1684                         bot.havocbot_previous_role = bot.havocbot_role;
1685                         bot.havocbot_role = havocbot_role_ctf_escort;
1686                         bot.havocbot_role_timeout = time + 30;
1687                         bot.bot_strategytime = 0;
1688                         break;
1689         }
1690         dprint("\n");
1691 }
1692
1693
1694 // ==============
1695 // Hook Functions
1696 // ==============
1697
1698 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1699 {
1700         entity flag;
1701         
1702         // initially clear items so they can be set as necessary later.
1703         self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST 
1704                 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1705
1706         // scan through all the flags and notify the client about them 
1707         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1708         {
1709                 switch(flag.ctf_status)
1710                 {
1711                         case FLAG_PASSING:
1712                         case FLAG_CARRY:
1713                         {
1714                                 if((flag.owner == self) || (flag.pass_sender == self))
1715                                         self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1716                                 else 
1717                                         self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1718                                 break;
1719                         }
1720                         case FLAG_DROPPED:
1721                         {
1722                                 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1723                                 break;
1724                         }
1725                 }
1726         }
1727         
1728         // item for stopping players from capturing the flag too often
1729         if(self.ctf_captureshielded)
1730                 self.items |= IT_CTF_SHIELDED;
1731         
1732         // update the health of the flag carrier waypointsprite
1733         if(self.wps_flagcarrier) 
1734                 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1735         
1736         return FALSE;
1737 }
1738
1739 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1740 {
1741         if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1742         {
1743                 if(frag_target == frag_attacker) // damage done to yourself
1744                 {
1745                         frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1746                         frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1747                 }
1748                 else // damage done to everyone else
1749                 {
1750                         frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1751                         frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1752                 }
1753         }
1754         else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1755         {
1756                 if(autocvar_g_ctf_flagcarrier_auto_helpme_when_damaged > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1757                         WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1758         }
1759         return FALSE;
1760 }
1761
1762 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1763 {
1764         if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1765         {
1766                 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1767                 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1768         }
1769                                 
1770         if(frag_target.flagcarried)
1771                 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1772                 
1773         return FALSE;
1774 }
1775
1776 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1777 {
1778         frag_score = 0;
1779         return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1780 }
1781
1782 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1783 {
1784         entity flag; // temporary entity for the search method
1785         
1786         if(self.flagcarried)
1787                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1788         
1789         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1790         {
1791                 if(flag.pass_sender == self) { flag.pass_sender = world; }
1792                 if(flag.pass_target == self) { flag.pass_target = world; }
1793                 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1794         }
1795                 
1796         return FALSE;
1797 }
1798
1799 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1800 {
1801         if(self.flagcarried) 
1802         if(!autocvar_g_ctf_portalteleport)
1803                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1804
1805         return FALSE;
1806 }
1807
1808 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1809 {
1810         if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1811         
1812         entity player = self;
1813
1814         if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1815         {
1816                 // pass the flag to a team mate
1817                 if(autocvar_g_ctf_pass)
1818                 {
1819                         entity head, closest_target;
1820                         head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1821                         
1822                         while(head) // find the closest acceptable target to pass to
1823                         {
1824                                 if(head.classname == "player" && head.deadflag == DEAD_NO)
1825                                 if(head != player && !IsDifferentTeam(head, player))
1826                                 if(!head.speedrunning && !head.vehicle)
1827                                 {
1828                                         // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) 
1829                                         vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1830                                         vector passer_center = CENTER_OR_VIEWOFS(player);
1831                                         
1832                                         if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1833                                         {
1834                                                 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) 
1835                                                 { 
1836                                                         if(clienttype(head) == CLIENTTYPE_BOT)
1837                                                         {
1838                                                                 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
1839                                                                 ctf_Handle_Throw(head, player, DROP_PASS);
1840                                                         }
1841                                                         else
1842                                                         {
1843                                                                 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname)); 
1844                                                                 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
1845                                                         }
1846                                                         player.throw_antispam = time + autocvar_g_ctf_pass_wait; 
1847                                                         return TRUE; 
1848                                                 }
1849                                                 else if(player.flagcarried)
1850                                                 {
1851                                                         if(closest_target)
1852                                                         {
1853                                                                 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1854                                                                 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1855                                                                         { closest_target = head; }
1856                                                         }
1857                                                         else { closest_target = head; }
1858                                                 }
1859                                         }
1860                                 }
1861                                 head = head.chain;
1862                         }
1863                         
1864                         if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1865                 }
1866                 
1867                 // throw the flag in front of you
1868                 if(autocvar_g_ctf_throw && player.flagcarried)
1869                 {
1870                         if(player.throw_count == -1)
1871                         {
1872                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1873                                 {
1874                                         player.throw_prevtime = time;
1875                                         player.throw_count = 1;
1876                                         ctf_Handle_Throw(player, world, DROP_THROW);
1877                                         return TRUE;
1878                                 }
1879                                 else
1880                                 {
1881                                         centerprint(player, strcat("Too many flag throws, throwing disabled for ", ftos(rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time)), " seconds."));
1882                                         return FALSE;
1883                                 }
1884                         }
1885                         else
1886                         {
1887                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1888                                 else { player.throw_count += 1; }
1889                                 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1890                                         
1891                                 player.throw_prevtime = time;
1892                                 ctf_Handle_Throw(player, world, DROP_THROW);
1893                                 return TRUE;
1894                         }
1895                 }
1896         }
1897                 
1898         return FALSE;
1899 }
1900
1901 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1902 {
1903         if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1904         {
1905                 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1906         } 
1907         else // create a normal help me waypointsprite
1908         {
1909                 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');
1910                 WaypointSprite_Ping(self.wps_helpme);
1911         }
1912
1913         return TRUE;
1914 }
1915
1916 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1917 {
1918         if(vh_player.flagcarried)
1919         {
1920                 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1921                 {
1922                         ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1923                 }
1924                 else
1925                 {            
1926                         setattachment(vh_player.flagcarried, vh_vehicle, ""); 
1927                         setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1928                         vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1929                         //vh_player.flagcarried.angles = '0 0 0';       
1930                 }
1931                 return TRUE;
1932         }
1933                 
1934         return FALSE;
1935 }
1936
1937 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1938 {
1939         if(vh_player.flagcarried)
1940         {
1941                 setattachment(vh_player.flagcarried, vh_player, ""); 
1942                 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1943                 vh_player.flagcarried.scale = FLAG_SCALE;
1944                 vh_player.flagcarried.angles = '0 0 0';
1945                 return TRUE;
1946         }
1947
1948         return FALSE;
1949 }
1950
1951 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1952 {
1953         if(self.flagcarried)
1954         {
1955                 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1956                 ctf_RespawnFlag(self);
1957                 return TRUE;
1958         }
1959         
1960         return FALSE;
1961 }
1962
1963 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1964 {
1965         entity flag; // temporary entity for the search method
1966         
1967         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1968         {
1969                 switch(flag.ctf_status)
1970                 {
1971                         case FLAG_DROPPED:
1972                         case FLAG_PASSING:
1973                         {
1974                                 // lock the flag, game is over
1975                                 flag.movetype = MOVETYPE_NONE;
1976                                 flag.takedamage = DAMAGE_NO;
1977                                 flag.solid = SOLID_NOT;
1978                                 flag.nextthink = FALSE; // stop thinking
1979                                 
1980                                 print("stopping the ", flag.netname, " from moving.\n");
1981                                 break;
1982                         }
1983                         
1984                         default:
1985                         case FLAG_BASE:
1986                         case FLAG_CARRY:
1987                         {
1988                                 // do nothing for these flags
1989                                 break;
1990                         }
1991                 }
1992         }
1993         
1994         return FALSE;
1995 }
1996
1997 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1998 {
1999         havocbot_ctf_reset_role(self);
2000         return TRUE;
2001 }
2002
2003
2004 // ==========
2005 // Spawnfuncs
2006 // ==========
2007
2008 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2009 CTF Starting point for a player in team one (Red).
2010 Keys: "angle" viewing angle when spawning. */
2011 void spawnfunc_info_player_team1()
2012 {
2013         if(g_assault) { remove(self); return; }
2014         
2015         self.team = COLOR_TEAM1; // red
2016         spawnfunc_info_player_deathmatch();
2017 }
2018
2019
2020 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2021 CTF Starting point for a player in team two (Blue).
2022 Keys: "angle" viewing angle when spawning. */
2023 void spawnfunc_info_player_team2()
2024 {
2025         if(g_assault) { remove(self); return; }
2026         
2027         self.team = COLOR_TEAM2; // blue
2028         spawnfunc_info_player_deathmatch();
2029 }
2030
2031 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2032 CTF Starting point for a player in team three (Yellow).
2033 Keys: "angle" viewing angle when spawning. */
2034 void spawnfunc_info_player_team3()
2035 {
2036         if(g_assault) { remove(self); return; }
2037         
2038         self.team = COLOR_TEAM3; // yellow
2039         spawnfunc_info_player_deathmatch();
2040 }
2041
2042
2043 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2044 CTF Starting point for a player in team four (Purple).
2045 Keys: "angle" viewing angle when spawning. */
2046 void spawnfunc_info_player_team4()
2047 {
2048         if(g_assault) { remove(self); return; }
2049         
2050         self.team = COLOR_TEAM4; // purple
2051         spawnfunc_info_player_deathmatch();
2052 }
2053
2054 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2055 CTF flag for team one (Red).
2056 Keys: 
2057 "angle" Angle the flag will point (minus 90 degrees)... 
2058 "model" model to use, note this needs red and blue as skins 0 and 1...
2059 "noise" sound played when flag is picked up...
2060 "noise1" sound played when flag is returned by a teammate...
2061 "noise2" sound played when flag is captured...
2062 "noise3" sound played when flag is lost in the field and respawns itself... 
2063 "noise4" sound played when flag is dropped by a player...
2064 "noise5" sound played when flag touches the ground... */
2065 void spawnfunc_item_flag_team1()
2066 {
2067         if(!g_ctf) { remove(self); return; }
2068
2069         ctf_FlagSetup(1, self); // 1 = red
2070 }
2071
2072 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2073 CTF flag for team two (Blue).
2074 Keys: 
2075 "angle" Angle the flag will point (minus 90 degrees)... 
2076 "model" model to use, note this needs red and blue as skins 0 and 1...
2077 "noise" sound played when flag is picked up...
2078 "noise1" sound played when flag is returned by a teammate...
2079 "noise2" sound played when flag is captured...
2080 "noise3" sound played when flag is lost in the field and respawns itself... 
2081 "noise4" sound played when flag is dropped by a player...
2082 "noise5" sound played when flag touches the ground... */
2083 void spawnfunc_item_flag_team2()
2084 {
2085         if(!g_ctf) { remove(self); return; }
2086
2087         ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2088 }
2089
2090 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2091 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2092 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.
2093 Keys:
2094 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2095 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2096 void spawnfunc_ctf_team()
2097 {
2098         if(!g_ctf) { remove(self); return; }
2099         
2100         self.classname = "ctf_team";
2101         self.team = self.cnt + 1;
2102 }
2103
2104 // compatibility for quake maps
2105 void spawnfunc_team_CTF_redflag()    { spawnfunc_item_flag_team1();    }
2106 void spawnfunc_team_CTF_blueflag()   { spawnfunc_item_flag_team2();    }
2107 void spawnfunc_team_CTF_redplayer()  { spawnfunc_info_player_team1();  }
2108 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2();  }
2109 void spawnfunc_team_CTF_redspawn()   { spawnfunc_info_player_team1();  }
2110 void spawnfunc_team_CTF_bluespawn()  { spawnfunc_info_player_team2();  }
2111
2112
2113 // ==============
2114 // Initialization
2115 // ==============
2116
2117 // scoreboard setup
2118 void ctf_ScoreRules()
2119 {
2120         ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2121         ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
2122         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
2123         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
2124         ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS,  "pickups",   0);
2125         ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
2126         ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
2127         ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
2128         ScoreRules_basics_end();
2129 }
2130
2131 // code from here on is just to support maps that don't have flag and team entities
2132 void ctf_SpawnTeam (string teamname, float teamcolor)
2133 {
2134         entity oldself;
2135         oldself = self;
2136         self = spawn();
2137         self.classname = "ctf_team";
2138         self.netname = teamname;
2139         self.cnt = teamcolor;
2140
2141         spawnfunc_ctf_team();
2142
2143         self = oldself;
2144 }
2145
2146 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2147 {
2148         // if no teams are found, spawn defaults
2149         if(find(world, classname, "ctf_team") == world)
2150         {
2151                 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2152                 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
2153                 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
2154         }
2155         
2156         ctf_ScoreRules();
2157 }
2158
2159 void ctf_Initialize()
2160 {
2161         ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2162
2163         ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2164         ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2165         ctf_captureshield_force = autocvar_g_ctf_shield_force;
2166         
2167         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2168 }
2169
2170
2171 MUTATOR_DEFINITION(gamemode_ctf)
2172 {
2173         MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2174         MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2175         MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2176         MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2177         MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2178         MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2179         MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2180         MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2181         MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2182         MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2183         MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2184         MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2185         MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2186         MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2187         
2188         MUTATOR_ONADD
2189         {
2190                 if(time > 1) // game loads at time 1
2191                         error("This is a game type and it cannot be added at runtime.");
2192                 g_ctf = 1;
2193                 ctf_Initialize();
2194         }
2195
2196         MUTATOR_ONREMOVE
2197         {
2198                 g_ctf = 0;
2199                 error("This is a game type and it cannot be removed at runtime.");
2200         }
2201
2202         return 0;
2203 }