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