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