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