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