]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/ctf.qc
Committed my CSQC stuff, let's see:
[divverent/nexuiz.git] / data / qcsrc / server / ctf.qc
1 .entity sprite;
2 entity ctf_worldflaglist; // CTF flags in the map
3 .entity ctf_worldflagnext;
4
5 .float next_take_time;                  // the next time a player can pick up a flag (time + blah)
6                                                                 /// I used this, in part, to fix the looping score bug. - avirox
7
8 //float FLAGSCORE_PICKUP        =  1;
9 //float FLAGSCORE_RETURN        =  5; // returned by owner team
10 //float FLAGSCORE_RETURNROGUE   = 10; // returned by rogue team
11 //float FLAGSCORE_CAPTURE       =  5;
12 //float FLAGSCORE_CAPTURE_TEAM  = 20;
13
14 #define FLAG_CARRY_POS '-15 0 7'
15
16 void FakeTimeLimit(entity e, float t)
17 {
18         msg_entity = e;
19         WriteByte(MSG_ONE, 3); // svc_updatestat
20         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
21         if(t < 0)
22                 WriteCoord(MSG_ONE, cvar("timelimit"));
23         else
24                 WriteCoord(MSG_ONE, (t + 1) / 60);
25 }
26
27 float   flagcaptimerecord;
28 .float  flagpickuptime;
29 //.float  iscommander;
30 //.float  ctf_state;
31
32 void() FlagThink;
33 void() FlagTouch;
34
35 void place_flag()
36 {
37         if(!self.t_width)
38                 self.t_width = 0.1; // frame animation rate
39         if(!self.t_length)
40                 self.t_length = 119; // maximum frame
41
42         setattachment(self, world, "");
43         self.mdl = self.model;
44         self.flags = FL_ITEM;
45         self.solid = SOLID_TRIGGER;
46         self.movetype = MOVETYPE_NONE;
47         self.velocity = '0 0 0';
48         self.origin_z = self.origin_z + 6;
49         self.think = FlagThink;
50         self.touch = FlagTouch;
51         self.nextthink = time + 0.1;
52         self.cnt = FLAG_BASE;
53         self.mangle = self.angles;
54         self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
55         //self.effects = self.effects | EF_DIMLIGHT;
56         if(!self.noalign)
57         {
58                 self.movetype = MOVETYPE_TOSS;
59                 if (!droptofloor())
60                 {
61                         dprint("Flag fell out of level at ", vtos(self.origin), "\n");
62                         remove(self);
63                         return;
64                 }
65         }
66         self.oldorigin = self.origin;
67 };
68
69 void LogCTF(string mode, float flagteam, entity actor)
70 {
71         string s;
72         if(!cvar("sv_eventlog"))
73                 return;
74         s = strcat(":ctf:", mode);
75         s = strcat(s, ":", ftos(flagteam));
76         if(actor != world)
77                 s = strcat(s, ":", ftos(actor.playerid));
78         GameLogEcho(s, FALSE);
79 }
80
81 void RegenFlag(entity e)
82 {
83         setattachment(e, world, "");
84         e.movetype = MOVETYPE_NONE;
85         if(!self.noalign)
86                 e.movetype = MOVETYPE_TOSS;
87         e.solid = SOLID_TRIGGER;
88         // TODO: play a sound here
89         setorigin(e, e.oldorigin);
90         e.angles = e.mangle;
91         e.cnt = FLAG_BASE;
92         e.owner = world;
93         e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
94 };
95
96 void ReturnFlag(entity e)
97 {
98         if (e.owner)
99         if (e.owner.flagcarried == e)
100         {
101                 WaypointSprite_DetachCarrier(e.owner);
102                 e.owner.flagcarried = world;
103
104                 if(e.speedrunning)
105                         FakeTimeLimit(e.owner, -1);
106         }
107         e.owner = world;
108         RegenFlag(e);
109 };
110
111 void DropFlag(entity e)
112 {
113         local entity p;
114
115         if(e.speedrunning)
116         {
117                 ReturnFlag(e);
118                 return;
119         }
120
121         if (!e.owner)
122         {
123                 dprint("FLAG: drop - no owner?!?!\n");
124                 return;
125         }
126         p = e.owner;
127         if (p.flagcarried != e)
128         {
129                 dprint("FLAG: drop - owner is not carrying this flag??\n");
130                 return;
131         }
132         bprint(p.netname, "^7 lost the ", e.netname, "\n");
133         WaypointSprite_DetachCarrier(p);
134         LogCTF("dropped", p.team, p.flagcarried);
135
136         setattachment(e, world, "");
137
138         if (p.flagcarried == e)
139                 p.flagcarried = world;
140         e.owner = world;
141
142         e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
143         e.solid = SOLID_TRIGGER;
144         e.movetype = MOVETYPE_TOSS;
145         // setsize(e, '-16 -16 0', '16 16 74');
146         setorigin(e, p.origin - '0 0 24' + '0 0 37');
147         e.cnt = FLAG_DROPPED;
148         e.velocity = '0 0 300';
149         e.pain_finished = time + cvar("g_ctf_flag_returntime");//30;
150
151         trace_startsolid = FALSE;
152         tracebox(e.origin, e.mins, e.maxs, e.origin, TRUE, e);
153         if(trace_startsolid)
154                 dprint("FLAG FALLTHROUGH will happen SOON\n");
155 };
156
157 void AnimateFlag()
158 {
159         if(self.delay > time)
160                 return;
161         self.delay = time + self.t_width;
162         if(self.nextthink > self.delay)
163                 self.nextthink = self.delay;
164
165         self.frame = self.frame + 1;
166         if(self.frame > self.t_length)
167                 self.frame = 0;
168 }
169
170 void FlagThink()
171 {
172         local entity e;
173
174         self.nextthink = time + 0.1;
175
176         AnimateFlag();
177
178         if(self.speedrunning)
179         if(self.cnt == FLAG_CARRY)
180         {
181                 if(self.owner)
182                 if(flagcaptimerecord)
183                 if(time >= self.flagpickuptime + flagcaptimerecord)
184                 {
185                         bprint("The ", self.netname, " became impatient after ", ftos_decimals(flagcaptimerecord, 2), " seconds and returned itself\n");
186
187                         self.owner.impulse = 77; // returning!
188                         e = self;
189                         self = self.owner;
190                         ReturnFlag(e);
191                         ImpulseCommands();
192                         self = e;
193                         return;
194                 }
195         }
196
197         if (self.cnt == FLAG_BASE)
198                 return;
199
200         if (self.cnt == FLAG_DROPPED)
201         {
202                 // flag fallthrough? FIXME remove this if bug is really fixed now
203                 if(self.origin_z < -131072)
204                 {
205                         dprint("FLAG FALLTHROUGH just happened\n");
206                         self.pain_finished = 0;
207                 }
208                 setattachment(self, world, "");
209                 if (time > self.pain_finished)
210                 {
211                         bprint("The ", self.netname, " has returned to base\n");
212                         sound (e, CHAN_AUTO, self.noise3, 1, ATTN_NONE);
213                         LogCTF("returned", self.team, world);
214                         ReturnFlag(self);
215                 }
216                 return;
217         }
218
219         e = self.owner;
220         if (e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
221         {
222                 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
223                 DropFlag(self);
224                 return;
225         }
226 };
227
228 void FlagTouch()
229 {
230         if(gameover) return;
231
232         local float t;
233         local entity head;
234         local entity player;
235         local string s, s0, h0, h1;
236         if (other.classname != "player")
237                 return;
238         if (other.health < 1) // ignore dead players
239                 return;
240
241         if (self.cnt == FLAG_CARRY)
242                 return;
243
244         if (self.cnt == FLAG_BASE)
245         if (other.team == self.team)
246         if (other.flagcarried) // he's got a flag
247         if (other.flagcarried.team != self.team) // capture
248         {
249                 if (other.flagcarried == world)
250                 {
251                         return;
252                 }
253                 t = time - other.flagcarried.flagpickuptime;
254                 s = ftos_decimals(t, 2);
255                 s0 = ftos_decimals(flagcaptimerecord, 2);
256                 h0 = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
257                 h1 = other.netname;
258                 if(h0 == h1)
259                         h0 = "his";
260                 else
261                         h0 = strcat(h0, "^7's"); // h0: display text for previous netname
262                 if (flagcaptimerecord == 0)
263                 {
264                         bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, " seconds\n");
265                         flagcaptimerecord = t;
266                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
267                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
268                 }
269                 else if (t < flagcaptimerecord)
270                 {
271                         bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, ", breaking ", strcat(h0, " previous record of ", s0, " seconds\n"));
272                         flagcaptimerecord = t;
273                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
274                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
275                 }
276                 else
277                 {
278                         bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, ", failing to break ", strcat(h0, " record of ", s0, " seconds\n"));
279                 }
280
281                 LogCTF("capture", other.flagcarried.team, other);
282                 // give credit to the individual player
283                 UpdateFrags(other, cvar("g_ctf_flagscore_capture"));
284
285                 // give credit to all players of the team (rewards large teams)
286                 // NOTE: this defaults to 0
287                 FOR_EACH_PLAYER(head)
288                         if (head.team == self.team)
289                                 UpdateFrags(head, cvar("g_ctf_flagscore_capture_team"));
290
291                 sound (self, CHAN_AUTO, self.noise2, 1, ATTN_NONE);
292                 WaypointSprite_DetachCarrier(other);
293                 if(self.speedrunning)
294                         FakeTimeLimit(other, -1);
295                 RegenFlag (other.flagcarried);
296                 other.flagcarried = world;
297                 other.next_take_time = time + 1;
298         }
299         if (self.cnt == FLAG_BASE)
300         if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) // only red and blue team can steal flags
301         if (other.team != self.team)
302         if (!other.flagcarried)
303         {
304                 if (other.next_take_time > time)
305                         return;
306                 // pick up
307                 self.flagpickuptime = time; // used for timing runs
308                 self.speedrunning = other.speedrunning; // if speedrunning, flag will self-return and teleport the owner back after the record
309                 if(other.speedrunning)
310                 if(flagcaptimerecord)
311                         FakeTimeLimit(other, time + flagcaptimerecord);
312                 self.solid = SOLID_NOT;
313                 setorigin(self, self.origin); // relink
314                 self.owner = other;
315                 other.flagcarried = self;
316                 self.cnt = FLAG_CARRY;
317                 self.angles = '0 0 0';
318                 bprint(other.netname, "^7 got the ", self.netname, "\n");
319                 UpdateFrags(other, cvar("g_ctf_flagscore_pickup"));
320                 LogCTF("steal", self.team, other);
321                 sound (self, CHAN_AUTO, self.noise, 1, ATTN_NONE);
322
323                 FOR_EACH_PLAYER(player)
324                         if(player.team == self.team)
325                                 centerprint(player, "The enemy got your flag! Retrieve it!");
326
327                 self.movetype = MOVETYPE_NONE;
328                 setorigin(self, FLAG_CARRY_POS);
329                 setattachment(self, other, "");
330                 WaypointSprite_AttachCarrier("flagcarrier", other);
331
332                 return;
333         }
334
335         if (self.cnt == FLAG_DROPPED)
336         {
337                 self.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
338                 if (other.team == self.team || (other.team != COLOR_TEAM1 && other.team != COLOR_TEAM2))
339                 {
340                         // return flag
341                         bprint(other.netname, "^7 returned the ", self.netname, "\n");
342                         if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
343                                 UpdateFrags(other, cvar("g_ctf_flagscore_return"));
344                         else
345                                 UpdateFrags(other, cvar("g_ctf_flagscore_return_rogue"));
346                         LogCTF("return", self.team, other);
347                         sound (self, CHAN_AUTO, self.noise1, 1, ATTN_NONE);
348                         ReturnFlag(self);
349                 }
350                 else if (!other.flagcarried)
351                 {
352                         // pick up
353                         self.solid = SOLID_NOT;
354                         setorigin(self, self.origin); // relink
355                         self.owner = other;
356                         other.flagcarried = self;
357                         self.cnt = FLAG_CARRY;
358                         bprint(other.netname, "^7 picked up the ", self.netname, "\n");
359                         UpdateFrags(other, cvar("g_ctf_flagscore_pickup"));
360                         LogCTF("pickup", self.team, other);
361                         sound (self, CHAN_AUTO, self.noise, 1, ATTN_NONE);
362
363                         FOR_EACH_PLAYER(player)
364                                 if(player.team == self.team)
365                                         centerprint(player, "The enemy got your flag! Retrieve it!");
366
367                         self.movetype = MOVETYPE_NONE;  // flag must have MOVETYPE_NONE here, otherwise it will drop through the floor...
368                         setorigin(self, FLAG_CARRY_POS);
369                         setattachment(self, other, "");
370                         WaypointSprite_AttachCarrier("flagcarrier", other);
371                 }
372         }
373 };
374
375 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
376 CTF Starting point for a player
377 in team one (Red).
378
379 Keys:
380 "angle"
381  viewing angle when spawning
382 */
383 void spawnfunc_info_player_team1()
384 {
385         self.team = COLOR_TEAM1; // red
386         spawnfunc_info_player_deathmatch();
387 };
388 //self.team = 4;self.classname = "info_player_start";spawnfunc_info_player_start();};
389
390 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
391 CTF Starting point for a player in
392 team two (Blue).
393
394 Keys:
395 "angle"
396  viewing angle when spawning
397 */
398 void spawnfunc_info_player_team2()
399 {
400         self.team = COLOR_TEAM2; // blue
401         spawnfunc_info_player_deathmatch();
402 };
403 //self.team = 13;self.classname = "info_player_start";spawnfunc_info_player_start();};
404
405 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
406 CTF Starting point for a player in
407 team three (Magenta).
408
409 Keys:
410 "angle"
411  viewing angle when spawning
412 */
413 void spawnfunc_info_player_team3()
414 {
415         self.team = COLOR_TEAM3; // purple
416         spawnfunc_info_player_deathmatch();
417 };
418
419
420 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
421 CTF Starting point for a player in
422 team four (Yellow).
423
424 Keys:
425 "angle"
426  viewing angle when spawning
427 */
428 void spawnfunc_info_player_team4()
429 {
430         self.team = COLOR_TEAM4; // yellow
431         spawnfunc_info_player_deathmatch();
432 };
433
434
435
436
437 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
438 CTF flag for team one (Red).
439 Multiple are allowed.
440
441 Keys:
442 "angle"
443  Angle the flag will point
444 (minus 90 degrees)
445 "model"
446  model to use, note this needs red and blue as skins 0 and 1
447  (default models/ctf/flag.md3)
448 "noise"
449  sound played when flag is picked up
450  (default ctf/take.wav)
451 "noise1"
452  sound played when flag is returned by a teammate
453  (default ctf/return.wav)
454 "noise2"
455  sound played when flag is captured
456  (default ctf/redcapture.wav)
457 "noise3"
458  sound played when flag is lost in the field and respawns itself
459  (default ctf/respawn.wav)
460 */
461
462 void spawnfunc_item_flag_team1()
463 {
464         if (!g_ctf)
465         {
466                 remove(self);
467                 return;
468         }
469
470         //if(!cvar("teamplay"))
471         //      cvar_set("teamplay", "3");
472
473         // link flag into ctf_worldflaglist
474         self.ctf_worldflagnext = ctf_worldflaglist;
475         ctf_worldflaglist = self;
476
477         self.classname = "item_flag_team";
478         self.team = COLOR_TEAM1; // color 4 team (red)
479         self.items = IT_KEY2; // gold key (redish enough)
480         self.netname = "^1RED^7 flag";
481         self.target = "###item###";
482         self.skin = 0;
483         if(self.spawnflags & 1)
484                 self.noalign = 1;
485         if (!self.model)
486                 self.model = "models/ctf/flag_red.md3";
487         if (!self.noise)
488                 self.noise = "ctf/take.wav";
489         if (!self.noise1)
490                 self.noise1 = "ctf/return.wav";
491         if (!self.noise2)
492                 self.noise2 = "ctf/redcapture.wav"; // blue team scores by capturing the red flag
493         if (!self.noise3)
494                 self.noise3 = "ctf/respawn.wav";
495         precache_model (self.model);
496         setmodel (self, self.model); // precision set below
497         precache_sound (self.noise);
498         precache_sound (self.noise1);
499         precache_sound (self.noise2);
500         precache_sound (self.noise3);
501         setsize(self, '-16 -16 -37', '16 16 37');
502         setorigin(self, self.origin + '0 0 37');
503         self.nextthink = time + 0.2; // start after doors etc
504         self.think = place_flag;
505
506         if(!self.scale)
507                 self.scale = 0.6;
508         //if(!self.glow_size)
509         //      self.glow_size = 50;
510
511         self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION;
512         if(!self.noalign)
513                 droptofloor();
514
515         waypoint_spawnforitem(self);
516
517         WaypointSprite_SpawnFixed("redbase", self.origin + '0 0 37', self, sprite);
518 };
519
520 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -24) (48 48 64)
521 CTF flag for team two (Blue).
522 Multiple are allowed.
523
524 Keys:
525 "angle"
526  Angle the flag will point
527 (minus 90 degrees)
528 "model"
529  model to use, note this needs red and blue as skins 0 and 1
530  (default models/ctf/flag.md3)
531 "noise"
532  sound played when flag is picked up
533  (default ctf/take.wav)
534 "noise1"
535  sound played when flag is returned by a teammate
536  (default ctf/return.wav)
537 "noise2"
538  sound played when flag is captured
539  (default ctf/bluecapture.wav)
540 "noise3"
541  sound played when flag is lost in the field and respawns itself
542  (default ctf/respawn.wav)
543 */
544
545 void spawnfunc_item_flag_team2()
546 {
547         if (!g_ctf)
548         {
549                 remove(self);
550                 return;
551         }
552         //if(!cvar("teamplay"))
553         //      cvar_set("teamplay", "3");
554
555         // link flag into ctf_worldflaglist
556         self.ctf_worldflagnext = ctf_worldflaglist;
557         ctf_worldflaglist = self;
558
559         self.classname = "item_flag_team";
560         self.team = COLOR_TEAM2; // color 13 team (blue)
561         self.items = IT_KEY1; // silver key (bluish enough)
562         self.netname = "^4BLUE^7 flag";
563         self.target = "###item###";
564         self.skin = 0;
565         if(self.spawnflags & 1)
566                 self.noalign = 1;
567         if (!self.model)
568                 self.model = "models/ctf/flag_blue.md3";
569         if (!self.noise)
570                 self.noise = "ctf/take.wav";
571         if (!self.noise1)
572                 self.noise1 = "ctf/return.wav";
573         if (!self.noise2)
574                 self.noise2 = "ctf/bluecapture.wav"; // red team scores by capturing the blue flag
575         if (!self.noise3)
576                 self.noise3 = "ctf/respawn.wav";
577         precache_model (self.model);
578         setmodel (self, self.model); // precision set below
579         precache_sound (self.noise);
580         precache_sound (self.noise1);
581         precache_sound (self.noise2);
582         precache_sound (self.noise3);
583         setsize(self, '-16 -16 -37', '16 16 37');
584         setorigin(self, self.origin + '0 0 37');
585         self.nextthink = time + 0.2; // start after doors etc
586         self.think = place_flag;
587
588         if(!self.scale)
589                 self.scale = 0.6;
590         //if(!self.glow_size)
591         //      self.glow_size = 50;
592
593         self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION;
594         if(!self.noalign)
595                 droptofloor();
596
597         waypoint_spawnforitem(self);
598
599         WaypointSprite_SpawnFixed("bluebase", self.origin + '0 0 37', self, sprite);
600 };
601
602
603 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
604 Team declaration for CTF gameplay, this allows you to decide what team
605 names and control point models are used in your map.
606
607 Note: If you use spawnfunc_ctf_team entities you must define at least 2!  However, unlike
608 domination, you don't need to make a blank one too.
609
610 Keys:
611 "netname"
612  Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)
613 "cnt"
614  Scoreboard color of the team (for example 4 is red and 13 is blue)
615
616 */
617
618 void spawnfunc_ctf_team()
619 {
620         if (!g_ctf)
621         {
622                 remove(self);
623                 return;
624         }
625         self.classname = "ctf_team";
626         self.team = self.cnt + 1;
627 };
628
629 // code from here on is just to support maps that don't have control point and team entities
630 void ctf_spawnteam (string teamname, float teamcolor)
631 {
632         local entity oldself;
633         oldself = self;
634         self = spawn();
635         self.classname = "ctf_team";
636         self.netname = teamname;
637         self.cnt = teamcolor;
638
639         spawnfunc_ctf_team();
640
641         self = oldself;
642 };
643
644 // spawn some default teams if the map is not set up for ctf
645 void ctf_spawnteams()
646 {
647         float numteams;
648
649         numteams = 2;//cvar("g_ctf_default_teams");
650
651         ctf_spawnteam("Red", COLOR_TEAM1 - 1);
652         ctf_spawnteam("Blue", COLOR_TEAM2 - 1);
653 };
654
655 void ctf_delayedinit()
656 {
657         self.think = SUB_Remove;
658         self.nextthink = time;
659         // if no teams are found, spawn defaults
660         if (find(world, classname, "ctf_team") == world)
661                 ctf_spawnteams();
662 };
663
664 void ctf_init()
665 {
666         local entity e;
667
668         //addstat(STAT_CTF_STATE, AS_FLOAT_TRUNCATED, ctf_state);
669         e = spawn();
670         e.think = ctf_delayedinit;
671         e.nextthink = time + 0.1;
672         flagcaptimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
673 };
674
675 void ctf_setstatus2(entity flag, float shift)
676 {
677         if (flag.cnt == FLAG_CARRY)
678                 if (flag.owner == self)
679                         self.items |= shift * 3;
680                 else
681                         self.items |= shift * 1;
682         else if (flag.cnt == FLAG_DROPPED)
683                 self.items |= shift * 2;
684         else
685         {
686                 // no status bits
687         }
688 };
689
690 void ctf_setstatus()
691 {
692         self.items = self.items - (self.items & IT_RED_FLAG_TAKEN);
693         self.items = self.items - (self.items & IT_RED_FLAG_LOST);
694         self.items = self.items - (self.items & IT_BLUE_FLAG_TAKEN);
695         self.items = self.items - (self.items & IT_BLUE_FLAG_LOST);
696
697         if (g_ctf) {
698                 local entity flag;
699                 float redflags, blueflags;
700
701                 redflags = 0;
702                 blueflags = 0;
703
704                 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
705                 {
706                         if(flag.team == COLOR_TEAM1)
707                                 ++redflags;
708                         else if(flag.team == COLOR_TEAM2)
709                                 ++blueflags;
710                 }
711
712                 // blinking magic: if there is more than one flag, show one of these in a clever way
713                 if(redflags)
714                         redflags = mod(floor(time * redflags * 0.75), redflags);
715                 if(blueflags)
716                         blueflags = mod(floor(time * blueflags * 0.75), blueflags);
717
718                 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
719                 {
720                         if(flag.team == COLOR_TEAM1)
721                         {
722                                 if(redflags-- == 0) // happens exactly once
723                                         ctf_setstatus2(flag, IT_RED_FLAG_TAKEN);
724                         }
725                         else if(flag.team == COLOR_TEAM2)
726                         {
727                                 if(blueflags-- == 0) // happens exactly once
728                                         ctf_setstatus2(flag, IT_BLUE_FLAG_TAKEN);
729                         }
730                 }
731         }
732 };
733 /*
734 entity(float cteam) ctf_team_has_commander =
735 {
736         entity pl;
737         if(cteam != COLOR_TEAM1 || cteam != COLOR_TEAM2)
738                 return world;
739         
740         FOR_EACH_REALPLAYER(pl) {
741                 if(pl.team == cteam && pl.iscommander) {
742                         return pl;
743                 }
744         }
745         return world;
746 };
747
748 void(entity e, float st) ctf_setstate =
749 {
750         e.ctf_state = st;
751         e.version ++;
752 };
753
754 void(float cteam) ctf_new_commander =
755 {
756         entity pl, plmax;
757         
758         plmax = world;
759         FOR_EACH_REALPLAYER(pl) {
760                 if(pl.team == cteam) {
761                         if(pl.iscommander) { // don't reassign if alreay there
762                                 return;
763                         }
764                         if(plmax == world || plmax.frags < pl.frags)
765                                 plmax = pl;
766                 }
767         }
768         if(plmax == world) {
769                 bprint(strcat(ColoredTeamName(cteam), " Team has no Commander!\n"));
770                 return;
771         }
772
773         plmax.iscommander = TRUE;
774         ctf_setstate(plmax, 3);
775         sprint(plmax, "^3You're the commander now!\n");
776         centerprint(plmax, "^3You're the commander now!\n");
777 };
778
779 void() ctf_clientconnect =
780 {
781         self.iscommander = FALSE;
782         
783         if(!self.team || self.classname != "player") {
784                 ctf_setstate(self, -1);
785         } else
786                 ctf_setstate(self, 0);
787
788         self.team_saved = self.team;
789         
790         if(self.team != 0 && self.classname == "player" && !ctf_team_has_commander(self.team)) {
791                 ctf_new_commander(self.team);
792         }
793 };
794
795 void() ctf_playerchanged =
796 {
797         if(!self.team || self.classname != "player") {
798                 ctf_setstate(self, -1);
799         } else if(self.ctf_state < 0 && self.classname == "player") {
800                 ctf_setstate(self, 0);
801         }
802
803         if(self.iscommander &&
804            (self.classname != "player" || self.team != self.team_saved)
805                 )
806         {
807                 self.iscommander = FALSE;
808                 if(self.classname == "player")
809                         ctf_setstate(self, 0);
810                 else
811                         ctf_setstate(self, -1);
812                 ctf_new_commander(self.team_saved);
813         }
814         
815         self.team_saved = self.team;
816         
817         ctf_new_commander(self.team);
818 };
819
820 void() ctf_clientdisconnect =
821 {
822         if(self.iscommander)
823         {
824                 ctf_new_commander(self.team);
825         }
826 };
827
828 entity GetPlayer(string);
829 float() ctf_clientcommand =
830 {
831         entity e;
832         if(argv(0) == "order") {
833                 if(!g_ctf) {
834                         sprint(self, "This command is not supported in this gamemode.\n");
835                         return TRUE;
836                 }
837                 if(!self.iscommander) {
838                         sprint(self, "^1You are not the commander!\n");
839                         return TRUE;
840                 }
841                 if(argv(2) == "") {
842                         sprint(self, "Usage: order #player status   - (playernumber as in status)\n");
843                         return TRUE;
844                 }
845                 e = GetPlayer(argv(1));
846                 if(e == world) {
847                         sprint(self, "Invalid player.\nUsage: order #player status   - (playernumber as in status)\n");
848                         return TRUE;
849                 }
850                 if(e.team != self.team) {
851                         sprint(self, "^3You can only give orders to your own team!\n");
852                         return TRUE;
853                 }
854                 if(argv(2) == "attack") {
855                         sprint(self, strcat("Ordering ", e.netname, " to attack!\n"));
856                         sprint(e, "^1Attack!\n");
857                         centerprint(e, "^7You've been ordered to^9\n^1Attack!\n");
858                         ctf_setstate(e, 1);
859                 } else if(argv(2) == "defend") {
860                         sprint(self, strcat("Ordering ", e.netname, " to defend!\n"));
861                         sprint(e, "^Defend!\n");
862                         centerprint(e, "^7You've been ordered to^9\n^2Defend!\n");
863                         ctf_setstate(e, 2);
864                 } else {
865                         sprint(self, "^7Invalid command, use ^3attack^7, or ^3defend^7.\n");
866                 }
867                 return TRUE;
868         }
869         return FALSE;
870 };
871 */