]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/ctf.qc
git-svn-id: svn://svn.icculus.org/nexuiz/trunk@5009 f962a42d-fe04-0410-a3ab-8c8b0445ebaa
[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 .float dropperid;
5 .float ctf_droptime;
6
7 .float next_take_time;                  // the next time a player can pick up a flag (time + blah)
8                                                                 /// I used this, in part, to fix the looping score bug. - avirox
9 //float FLAGSCORE_PICKUP        =  1;
10 //float FLAGSCORE_RETURN        =  5; // returned by owner team
11 //float FLAGSCORE_RETURNROGUE   = 10; // returned by rogue team
12 //float FLAGSCORE_CAPTURE       =  5;
13
14 #define FLAG_CARRY_POS '-15 0 7'
15
16 .float ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
17
18 float captureshield_min_negscore; // punish at -20 points
19 float captureshield_max_ratio; // punish at most 30% of each team
20 float captureshield_force; // push force of the shield
21
22 float ctf_captureshield_shielded(entity p)
23 {
24         float s, se;
25         entity e;
26         float players_worseeq, players_total;
27
28         if(captureshield_max_ratio <= 0)
29                 return FALSE;
30
31         s = PlayerScore_Add(p, SP_SCORE, 0);
32         if(s >= -captureshield_min_negscore)
33                 return FALSE;
34
35         players_total = players_worseeq = 0;
36         FOR_EACH_PLAYER(e)
37         {
38                 if(e.team != p.team)
39                         continue;
40                 se = PlayerScore_Add(e, SP_SCORE, 0);
41                 if(se <= s)
42                         ++players_worseeq;
43                 ++players_total;
44         }
45
46         // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
47         // use this rule here
48         
49         if(players_worseeq >= players_total * captureshield_max_ratio)
50                 return FALSE;
51
52         return TRUE;
53 }
54
55 void ctf_captureshield_update(entity p, float dir)
56 {
57         float should;
58         if(dir == p.ctf_captureshielded) // 0: shield only, 1: unshield only
59         {
60                 should = ctf_captureshield_shielded(p);
61                 if(should != dir)
62                 {
63                         if(should)
64                         {
65                                 centerprint_atprio(p, CENTERPRIO_SHIELDING, "^3You are now ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Make some defensive scores before trying again.");
66                                 // TODO csqc notifier for this
67                         }
68                         else
69                         {
70                                 centerprint_atprio(p, CENTERPRIO_SHIELDING, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.");
71                                 // TODO csqc notifier for this
72                         }
73                         p.ctf_captureshielded = should;
74                 }
75         }
76 }
77
78 float ctf_captureshield_customize()
79 {
80         if not(other.ctf_captureshielded)
81                 return FALSE;
82         if(self.team == other.team)
83                 return FALSE;
84         return TRUE;
85 }
86
87 void ctf_captureshield_touch()
88 {
89         if not(other.ctf_captureshielded)
90                 return;
91         if(self.team == other.team)
92                 return;
93         vector mymid;
94         vector othermid;
95         mymid = (self.absmin + self.absmax) * 0.5;
96         othermid = (other.absmin + other.absmax) * 0.5;
97         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * captureshield_force);
98         centerprint_atprio(other, CENTERPRIO_SHIELDING, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.");
99 }
100
101 void ctf_captureshield_spawn()
102 {
103         entity e;
104         e = spawn();
105         e.enemy = self;
106         e.team = self.team;
107         e.touch = ctf_captureshield_touch;
108         e.customizeentityforclient = ctf_captureshield_customize;
109         e.classname = "ctf_captureshield";
110         e.effects = EF_ADDITIVE;
111         e.movetype = MOVETYPE_NOCLIP;
112         e.solid = SOLID_TRIGGER;
113         e.avelocity = '7 0 11';
114         setorigin(e, self.origin);
115         setmodel(e, "models/onslaught/generator_shield.md3");
116         e.scale = 0.5;
117         setsize(e, e.scale * e.mins, e.scale * e.maxs);
118         print(etos(e), "\n");
119 }
120
121 float ctf_score_value(string parameter)
122 {
123         if(g_ctf_win_mode != 2)
124                 return cvar(strcat("g_ctf_personal", parameter));
125         else
126                 return cvar(strcat("g_ctf_flag", parameter));
127 }
128
129 void FakeTimeLimit(entity e, float t)
130 {
131         msg_entity = e;
132         WriteByte(MSG_ONE, 3); // svc_updatestat
133         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
134         if(t < 0)
135                 WriteCoord(MSG_ONE, cvar("timelimit"));
136         else
137                 WriteCoord(MSG_ONE, (t + 1) / 60);
138 }
139
140 float   flagcaptimerecord;
141 .float  flagpickuptime;
142 //.float  iscommander;
143 //.float  ctf_state;
144
145 void() FlagThink;
146 void() FlagTouch;
147
148 void place_flag()
149 {
150         if(!self.t_width)
151                 self.t_width = 0.1; // frame animation rate
152         if(!self.t_length)
153                 self.t_length = 119; // maximum frame
154
155         setattachment(self, world, "");
156         self.mdl = self.model;
157         self.flags = FL_ITEM;
158         self.solid = SOLID_TRIGGER;
159         self.movetype = MOVETYPE_NONE;
160         self.velocity = '0 0 0';
161         self.origin_z = self.origin_z + 6;
162         self.think = FlagThink;
163         self.touch = FlagTouch;
164         self.nextthink = time + 0.1;
165         self.cnt = FLAG_BASE;
166         self.mangle = self.angles;
167         self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
168         //self.effects = self.effects | EF_DIMLIGHT;
169         if(self.noalign)
170         {
171                 self.dropped_origin = self.origin;
172         }
173         else
174         {
175                 droptofloor();
176                 self.movetype = MOVETYPE_TOSS;
177         }
178         InitializeEntity(self, ctf_captureshield_spawn, INITPRIO_SETLOCATION);
179 };
180
181 void LogCTF(string mode, float flagteam, entity actor)
182 {
183         string s;
184         if(!cvar("sv_eventlog"))
185                 return;
186         s = strcat(":ctf:", mode);
187         s = strcat(s, ":", ftos(flagteam));
188         if(actor != world)
189                 s = strcat(s, ":", ftos(actor.playerid));
190         GameLogEcho(s);
191 }
192
193 void RegenFlag(entity e)
194 {
195         setattachment(e, world, "");
196         e.damageforcescale = 0;
197         e.movetype = MOVETYPE_NONE;
198         if(!self.noalign)
199                 e.movetype = MOVETYPE_TOSS;
200         e.solid = SOLID_TRIGGER;
201         // TODO: play a sound here
202         setorigin(e, e.dropped_origin);
203         e.angles = e.mangle;
204         e.cnt = FLAG_BASE;
205         e.owner = world;
206         e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
207 };
208
209 void ReturnFlag(entity e)
210 {
211         if (e.owner)
212         if (e.owner.flagcarried == e)
213         {
214                 WaypointSprite_DetachCarrier(e.owner);
215                 e.owner.flagcarried = world;
216
217                 if(e.speedrunning)
218                         FakeTimeLimit(e.owner, -1);
219         }
220         e.owner = world;
221         RegenFlag(e);
222 };
223
224 void DropFlag(entity e, entity penalty_receiver, entity attacker)
225 {
226         local entity p;
227
228         if(e.speedrunning)
229         {
230                 ReturnFlag(e);
231                 return;
232         }
233
234         if (!e.owner)
235         {
236                 dprint("FLAG: drop - no owner?!?!\n");
237                 return;
238         }
239         p = e.owner;
240         if (p.flagcarried != e)
241         {
242                 dprint("FLAG: drop - owner is not carrying this flag??\n");
243                 return;
244         }
245         bprint(p.netname, "^7 lost the ", e.netname, "\n");
246
247         if(penalty_receiver)
248                 UpdateFrags(penalty_receiver, -ctf_score_value("penalty_suicidedrop"));
249         else
250                 UpdateFrags(p, -ctf_score_value("penalty_drop"));
251         PlayerScore_Add(p, SP_CTF_DROPS, +1);
252         ctf_captureshield_update(p, 0); // shield only
253         e.playerid = attacker.playerid;
254         e.ctf_droptime = time;
255         
256         WaypointSprite_Ping(p.waypointsprite_attachedforcarrier);
257         WaypointSprite_DetachCarrier(p);
258         LogCTF("dropped", p.team, p);
259
260         setattachment(e, world, "");
261         e.damageforcescale = cvar("g_balance_ctf_damageforcescale");
262
263         if (p.flagcarried == e)
264                 p.flagcarried = world;
265         e.owner = world;
266
267         e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
268         e.solid = SOLID_TRIGGER;
269         e.movetype = MOVETYPE_TOSS;
270         // setsize(e, '-16 -16 0', '16 16 74');
271         setorigin(e, p.origin - '0 0 24' + '0 0 37');
272         e.cnt = FLAG_DROPPED;
273         e.velocity = '0 0 300';
274         e.pain_finished = time + cvar("g_ctf_flag_returntime");//30;
275
276         trace_startsolid = FALSE;
277         tracebox(e.origin, e.mins, e.maxs, e.origin, TRUE, e);
278         if(trace_startsolid)
279                 dprint("FLAG FALLTHROUGH will happen SOON\n");
280 };
281
282 void AnimateFlag()
283 {
284         if(self.delay > time)
285                 return;
286         self.delay = time + self.t_width;
287         if(self.nextthink > self.delay)
288                 self.nextthink = self.delay;
289
290         self.frame = self.frame + 1;
291         if(self.frame > self.t_length)
292                 self.frame = 0;
293 }
294
295 void FlagThink()
296 {
297         local entity e;
298
299         self.nextthink = time + 0.1;
300
301         if(self == ctf_worldflaglist) // only for the first flag
302         {
303                 FOR_EACH_CLIENT(e)
304                         ctf_captureshield_update(e, 1); // release shield only
305         }
306
307         AnimateFlag();
308
309         if(self.speedrunning)
310         if(self.cnt == FLAG_CARRY)
311         {
312                 if(self.owner)
313                 if(flagcaptimerecord)
314                 if(time >= self.flagpickuptime + flagcaptimerecord)
315                 {
316                         bprint("The ", self.netname, " became impatient after ", ftos_decimals(flagcaptimerecord, 2), " seconds and returned itself\n");
317
318                         self.owner.impulse = 77; // returning!
319                         e = self;
320                         self = self.owner;
321                         ReturnFlag(e);
322                         ImpulseCommands();
323                         self = e;
324                         return;
325                 }
326         }
327
328         if (self.cnt == FLAG_BASE)
329                 return;
330
331         if (self.cnt == FLAG_DROPPED)
332         {
333                 // flag fallthrough? FIXME remove this if bug is really fixed now
334                 if(self.origin_z < -131072)
335                 {
336                         dprint("FLAG FALLTHROUGH just happened\n");
337                         self.pain_finished = 0;
338                 }
339                 setattachment(self, world, "");
340                 if (time > self.pain_finished)
341                 {
342                         bprint("The ", self.netname, " has returned to base\n");
343                         sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
344                         LogCTF("returned", self.team, world);
345                         ReturnFlag(self);
346                 }
347                 return;
348         }
349
350         e = self.owner;
351         if (e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
352         {
353                 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
354                 DropFlag(self, world, world);
355                 return;
356         }
357
358         if(cvar("g_ctf_allow_drop"))
359         if(e.BUTTON_USE)
360                 DropFlag(self, e, world);
361 };
362
363 void FlagTouch()
364 {
365         if(gameover) return;
366
367         local float t;
368         local entity player;
369         local string s, s0, h0, h1;
370         if (other.classname != "player")
371                 return;
372         if (other.health < 1) // ignore dead players
373                 return;
374
375         if (self.cnt == FLAG_CARRY)
376                 return;
377
378         if (self.cnt == FLAG_BASE)
379         if (other.team == self.team)
380         if (other.flagcarried) // he's got a flag
381         if (other.flagcarried.team != self.team) // capture
382         {
383                 if (other.flagcarried == world)
384                 {
385                         return;
386                 }
387                 t = time - other.flagcarried.flagpickuptime;
388                 s = ftos_decimals(t, 2);
389                 s0 = ftos_decimals(flagcaptimerecord, 2);
390                 h0 = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
391                 h1 = other.netname;
392                 if(h0 == h1)
393                         h0 = "his";
394                 else
395                         h0 = strcat(h0, "^7's"); // h0: display text for previous netname
396                 if (flagcaptimerecord == 0)
397                 {
398                         bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, " seconds\n");
399                         flagcaptimerecord = t;
400                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
401                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
402                         GameLogEcho(strcat(":recordset:", ftos(other.playerid), ":", ftos(t)));
403                 }
404                 else if (t < flagcaptimerecord)
405                 {
406                         bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, ", breaking ", strcat(h0, " previous record of ", s0, " seconds\n"));
407                         flagcaptimerecord = t;
408                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
409                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
410                         GameLogEcho(strcat(":recordset:", ftos(other.playerid), ":", ftos(t)));
411                 }
412                 else
413                 {
414                         bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, ", failing to break ", strcat(h0, " record of ", s0, " seconds\n"));
415                 }
416
417                 PlayerTeamScore_Add(other, SP_CTF_CAPS, ST_CTF_CAPS, 1);
418                 LogCTF("capture", other.flagcarried.team, other);
419                 // give credit to the individual player
420                 UpdateFrags(other, ctf_score_value("score_capture"));
421
422                 sound (other, CHAN_AUTO, self.noise2, VOL_BASE, ATTN_NONE);
423                 WaypointSprite_DetachCarrier(other);
424                 if(self.speedrunning)
425                         FakeTimeLimit(other, -1);
426                 RegenFlag (other.flagcarried);
427                 other.flagcarried = world;
428                 other.next_take_time = time + 1;
429         }
430         if (self.cnt == FLAG_BASE)
431         if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) // only red and blue team can steal flags
432         if (other.team != self.team)
433         if (!other.flagcarried)
434         if (!other.ctf_captureshielded)
435         {
436                 if (other.next_take_time > time)
437                         return;
438                 // pick up
439                 self.flagpickuptime = time; // used for timing runs
440                 self.speedrunning = other.speedrunning; // if speedrunning, flag will self-return and teleport the owner back after the record
441                 if(other.speedrunning)
442                 if(flagcaptimerecord)
443                         FakeTimeLimit(other, time + flagcaptimerecord);
444                 self.solid = SOLID_NOT;
445                 setorigin(self, self.origin); // relink
446                 self.owner = other;
447                 other.flagcarried = self;
448                 self.cnt = FLAG_CARRY;
449                 self.angles = '0 0 0';
450                 bprint(other.netname, "^7 got the ", self.netname, "\n");
451                 UpdateFrags(other, ctf_score_value("score_pickup_base"));
452                 self.dropperid = other.playerid;
453                 PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
454                 LogCTF("steal", self.team, other);
455                 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NONE);
456
457                 FOR_EACH_PLAYER(player)
458                         if(player.team == self.team)
459                                 centerprint(player, "The enemy got your flag! Retrieve it!");
460
461                 self.movetype = MOVETYPE_NONE;
462                 setorigin(self, FLAG_CARRY_POS);
463                 setattachment(self, other, "");
464                 WaypointSprite_AttachCarrier("flagcarrier", other);
465                 WaypointSprite_UpdateTeamRadar(other.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 1 0');
466                 WaypointSprite_Ping(self.sprite);
467
468                 return;
469         }
470
471         if (self.cnt == FLAG_DROPPED)
472         {
473                 self.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
474                 if (other.team == self.team || (other.team != COLOR_TEAM1 && other.team != COLOR_TEAM2))
475                 {
476                         // return flag
477                         bprint(other.netname, "^7 returned the ", self.netname, "\n");
478
479                         // punish the player who last had it
480                         FOR_EACH_PLAYER(player)
481                                 if(player.playerid == self.dropperid)
482                                 {
483                                         PlayerScore_Add(player, SP_SCORE, -ctf_score_value("penalty_returned"));
484                                         ctf_captureshield_update(player, 0); // shield only
485                                 }
486
487                         // punish the team who was last carrying it
488                         if(self.team == COLOR_TEAM1)
489                                 TeamScore_AddToTeam(COLOR_TEAM2, ST_SCORE, -ctf_score_value("penalty_returned"));
490                         else
491                                 TeamScore_AddToTeam(COLOR_TEAM1, ST_SCORE, -ctf_score_value("penalty_returned"));
492
493                         // reward the player who returned it
494                         if(other.playerid == self.playerid) // is this the guy who killed the FC last?
495                         {
496                                 if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
497                                         UpdateFrags(other, ctf_score_value("score_return_by_killer"));
498                                 else
499                                         UpdateFrags(other, ctf_score_value("score_return_rogue_by_killer"));
500                         }
501                         else
502                         {
503                                 if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
504                                         UpdateFrags(other, ctf_score_value("score_return"));
505                                 else
506                                         UpdateFrags(other, ctf_score_value("score_return_rogue"));
507                         }
508                         PlayerScore_Add(other, SP_CTF_RETURNS, 1);
509                         LogCTF("return", self.team, other);
510                         sound (other, CHAN_AUTO, self.noise1, VOL_BASE, ATTN_NONE);
511                         ReturnFlag(self);
512                 }
513                 else if (!other.flagcarried && (other.playerid != self.dropperid || time > self.ctf_droptime + cvar("g_balance_ctf_delay_collect")))
514                 {
515                         // pick up
516                         self.solid = SOLID_NOT;
517                         setorigin(self, self.origin); // relink
518                         self.owner = other;
519                         other.flagcarried = self;
520                         self.cnt = FLAG_CARRY;
521                         bprint(other.netname, "^7 picked up the ", self.netname, "\n");
522
523                         float f;
524                         f = bound(0, (self.pain_finished - time) / cvar("g_ctf_flag_returntime"), 1);
525                         //print("factor is ", ftos(f), "\n");
526                         f = ctf_score_value("score_pickup_dropped_late") * (1-f)
527                           + ctf_score_value("score_pickup_dropped_early") * f;
528                         f = floor(f + 0.5);
529                         self.dropperid = other.playerid;
530                         //print("score is ", ftos(f), "\n");
531
532                         UpdateFrags(other, f);
533                         PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
534                         LogCTF("pickup", self.team, other);
535                         sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NONE);
536
537                         FOR_EACH_PLAYER(player)
538                                 if(player.team == self.team)
539                                         centerprint(player, "The enemy got your flag! Retrieve it!");
540
541                         self.movetype = MOVETYPE_NONE;  // flag must have MOVETYPE_NONE here, otherwise it will drop through the floor...
542                         setorigin(self, FLAG_CARRY_POS);
543                         setattachment(self, other, "");
544                         self.damageforcescale = 0;
545                         WaypointSprite_AttachCarrier("flagcarrier", other);
546                         WaypointSprite_UpdateTeamRadar(other.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 1 0');
547                 }
548         }
549 };
550
551 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
552 CTF Starting point for a player
553 in team one (Red).
554
555 Keys:
556 "angle"
557  viewing angle when spawning
558 */
559 void spawnfunc_info_player_team1()
560 {
561         self.team = COLOR_TEAM1; // red
562         spawnfunc_info_player_deathmatch();
563 };
564 //self.team = 4;self.classname = "info_player_start";spawnfunc_info_player_start();};
565
566 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
567 CTF Starting point for a player in
568 team two (Blue).
569
570 Keys:
571 "angle"
572  viewing angle when spawning
573 */
574 void spawnfunc_info_player_team2()
575 {
576         self.team = COLOR_TEAM2; // blue
577         spawnfunc_info_player_deathmatch();
578 };
579 //self.team = 13;self.classname = "info_player_start";spawnfunc_info_player_start();};
580
581 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
582 CTF Starting point for a player in
583 team three (Magenta).
584
585 Keys:
586 "angle"
587  viewing angle when spawning
588 */
589 void spawnfunc_info_player_team3()
590 {
591         self.team = COLOR_TEAM3; // purple
592         spawnfunc_info_player_deathmatch();
593 };
594
595
596 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
597 CTF Starting point for a player in
598 team four (Yellow).
599
600 Keys:
601 "angle"
602  viewing angle when spawning
603 */
604 void spawnfunc_info_player_team4()
605 {
606         self.team = COLOR_TEAM4; // yellow
607         spawnfunc_info_player_deathmatch();
608 };
609
610
611
612
613 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
614 CTF flag for team one (Red).
615 Multiple are allowed.
616
617 Keys:
618 "angle"
619  Angle the flag will point
620 (minus 90 degrees)
621 "model"
622  model to use, note this needs red and blue as skins 0 and 1
623  (default models/ctf/flag.md3)
624 "noise"
625  sound played when flag is picked up
626  (default ctf/take.wav)
627 "noise1"
628  sound played when flag is returned by a teammate
629  (default ctf/return.wav)
630 "noise2"
631  sound played when flag is captured
632  (default ctf/redcapture.wav)
633 "noise3"
634  sound played when flag is lost in the field and respawns itself
635  (default ctf/respawn.wav)
636 */
637
638 void spawnfunc_item_flag_team1()
639 {
640         if (!g_ctf)
641         {
642                 remove(self);
643                 return;
644         }
645
646         //if(!cvar("teamplay"))
647         //      cvar_set("teamplay", "3");
648
649         // link flag into ctf_worldflaglist
650         self.ctf_worldflagnext = ctf_worldflaglist;
651         ctf_worldflaglist = self;
652
653         self.classname = "item_flag_team";
654         if(g_ctf_reverse)
655         {
656                 self.team = COLOR_TEAM2; // color 13 team (blue)
657                 self.items = IT_KEY1; // silver key (bluish enough)
658         }
659         else
660         {
661                 self.team = COLOR_TEAM1; // color 4 team (red)
662                 self.items = IT_KEY2; // gold key (redish enough)
663         }
664         self.netname = "^1RED^7 flag";
665         self.target = "###item###";
666         self.skin = 0;
667         if(self.spawnflags & 1)
668                 self.noalign = 1;
669         if (!self.model)
670                 self.model = "models/ctf/flag_red.md3";
671         if (!self.noise)
672                 self.noise = "ctf/take.wav";
673         if (!self.noise1)
674                 self.noise1 = "ctf/return.wav";
675         if (!self.noise2)
676                 self.noise2 = "ctf/redcapture.wav"; // blue team scores by capturing the red flag
677         if (!self.noise3)
678                 self.noise3 = "ctf/respawn.wav";
679         precache_model (self.model);
680         setmodel (self, self.model); // precision set below
681         precache_sound (self.noise);
682         precache_sound (self.noise1);
683         precache_sound (self.noise2);
684         precache_sound (self.noise3);
685         //setsize(self, '-16 -16 -37', '16 16 37');
686         setsize(self, PL_MIN + '0 0 -13', PL_MAX + '0 0 -13');
687         setorigin(self, self.origin + '0 0 37');
688         self.nextthink = time + 0.2; // start after doors etc
689         self.think = place_flag;
690
691         if(!self.scale)
692                 self.scale = 0.6;
693         //if(!self.glow_size)
694         //      self.glow_size = 50;
695
696         self.effects = self.effects | EF_LOWPRECISION;
697         if(cvar("g_ctf_fullbrightflags"))
698                 self.effects |= EF_FULLBRIGHT;
699
700         waypoint_spawnforitem(self);
701
702         WaypointSprite_SpawnFixed("redbase", self.origin + '0 0 37', self, sprite);
703         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_FLAG, '1 0 0');
704
705         precache_model("models/onslaught/generator_shield.md3");
706 };
707
708 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -24) (48 48 64)
709 CTF flag for team two (Blue).
710 Multiple are allowed.
711
712 Keys:
713 "angle"
714  Angle the flag will point
715 (minus 90 degrees)
716 "model"
717  model to use, note this needs red and blue as skins 0 and 1
718  (default models/ctf/flag.md3)
719 "noise"
720  sound played when flag is picked up
721  (default ctf/take.wav)
722 "noise1"
723  sound played when flag is returned by a teammate
724  (default ctf/return.wav)
725 "noise2"
726  sound played when flag is captured
727  (default ctf/bluecapture.wav)
728 "noise3"
729  sound played when flag is lost in the field and respawns itself
730  (default ctf/respawn.wav)
731 */
732
733 void spawnfunc_item_flag_team2()
734 {
735         if (!g_ctf)
736         {
737                 remove(self);
738                 return;
739         }
740         //if(!cvar("teamplay"))
741         //      cvar_set("teamplay", "3");
742
743         // link flag into ctf_worldflaglist
744         self.ctf_worldflagnext = ctf_worldflaglist;
745         ctf_worldflaglist = self;
746
747         self.classname = "item_flag_team";
748         if(g_ctf_reverse)
749         {
750                 self.team = COLOR_TEAM1; // color 4 team (red)
751                 self.items = IT_KEY2; // gold key (redish enough)
752         }
753         else
754         {
755                 self.team = COLOR_TEAM2; // color 13 team (blue)
756                 self.items = IT_KEY1; // silver key (bluish enough)
757         }
758         self.netname = "^4BLUE^7 flag";
759         self.target = "###item###";
760         self.skin = 0;
761         if(self.spawnflags & 1)
762                 self.noalign = 1;
763         if (!self.model)
764                 self.model = "models/ctf/flag_blue.md3";
765         if (!self.noise)
766                 self.noise = "ctf/take.wav";
767         if (!self.noise1)
768                 self.noise1 = "ctf/return.wav";
769         if (!self.noise2)
770                 self.noise2 = "ctf/bluecapture.wav"; // red team scores by capturing the blue flag
771         if (!self.noise3)
772                 self.noise3 = "ctf/respawn.wav";
773         precache_model (self.model);
774         setmodel (self, self.model); // precision set below
775         precache_sound (self.noise);
776         precache_sound (self.noise1);
777         precache_sound (self.noise2);
778         precache_sound (self.noise3);
779         //setsize(self, '-16 -16 -37', '16 16 37');
780         setsize(self, PL_MIN + '0 0 -13', PL_MAX + '0 0 -13');
781         setorigin(self, self.origin + '0 0 37');
782         self.nextthink = time + 0.2; // start after doors etc
783         self.think = place_flag;
784
785         if(!self.scale)
786                 self.scale = 0.6;
787         //if(!self.glow_size)
788         //      self.glow_size = 50;
789
790         self.effects = self.effects | EF_LOWPRECISION;
791         if(cvar("g_ctf_fullbrightflags"))
792                 self.effects |= EF_FULLBRIGHT;
793
794         waypoint_spawnforitem(self);
795
796         WaypointSprite_SpawnFixed("bluebase", self.origin + '0 0 37', self, sprite);
797         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_FLAG, '0 0 1');
798
799         precache_model("models/onslaught/generator_shield.md3");
800 };
801
802
803 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
804 Team declaration for CTF gameplay, this allows you to decide what team
805 names and control point models are used in your map.
806
807 Note: If you use spawnfunc_ctf_team entities you must define at least 2!  However, unlike
808 domination, you don't need to make a blank one too.
809
810 Keys:
811 "netname"
812  Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)
813 "cnt"
814  Scoreboard color of the team (for example 4 is red and 13 is blue)
815
816 */
817
818 void spawnfunc_ctf_team()
819 {
820         if (!g_ctf)
821         {
822                 remove(self);
823                 return;
824         }
825         self.classname = "ctf_team";
826         self.team = self.cnt + 1;
827 };
828
829 // code from here on is just to support maps that don't have control point and team entities
830 void ctf_spawnteam (string teamname, float teamcolor)
831 {
832         local entity oldself;
833         oldself = self;
834         self = spawn();
835         self.classname = "ctf_team";
836         self.netname = teamname;
837         self.cnt = teamcolor;
838
839         spawnfunc_ctf_team();
840
841         self = oldself;
842 };
843
844 // spawn some default teams if the map is not set up for ctf
845 void ctf_spawnteams()
846 {
847         float numteams;
848
849         numteams = 2;//cvar("g_ctf_default_teams");
850
851         ctf_spawnteam("Red", COLOR_TEAM1 - 1);
852         ctf_spawnteam("Blue", COLOR_TEAM2 - 1);
853 };
854
855 void ctf_delayedinit()
856 {
857         // if no teams are found, spawn defaults
858         if (find(world, classname, "ctf_team") == world)
859                 ctf_spawnteams();
860
861         ScoreRules_ctf();
862 };
863
864 void ctf_init()
865 {
866         InitializeEntity(world, ctf_delayedinit, INITPRIO_GAMETYPE);
867         flagcaptimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
868
869         captureshield_min_negscore = cvar("g_ctf_shield_min_negscore");
870         captureshield_max_ratio = cvar("g_ctf_shield_max_ratio");
871         captureshield_force = cvar("g_ctf_shield_force");
872 };
873
874 void ctf_setstatus2(entity flag, float shift)
875 {
876         if (flag.cnt == FLAG_CARRY)
877                 if (flag.owner == self)
878                         self.items |= shift * 3;
879                 else
880                         self.items |= shift * 1;
881         else if (flag.cnt == FLAG_DROPPED)
882                 self.items |= shift * 2;
883         else
884         {
885                 // no status bits
886         }
887 };
888
889 void ctf_setstatus()
890 {
891         self.items (-) IT_RED_FLAG_TAKEN;
892         self.items (-) IT_RED_FLAG_LOST;
893         self.items (-) IT_BLUE_FLAG_TAKEN;
894         self.items (-) IT_BLUE_FLAG_LOST;
895         self.items (-) IT_CTF_SHIELDED;
896
897         if (g_ctf) {
898                 local entity flag;
899                 float redflags, blueflags;
900
901                 if(self.ctf_captureshielded)
902                         self.items |= IT_CTF_SHIELDED;
903
904                 redflags = 0;
905                 blueflags = 0;
906
907                 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
908                 {
909                         if(flag.items & IT_KEY2) // blue
910                                 ++redflags;
911                         else if(flag.items & IT_KEY1) // red
912                                 ++blueflags;
913                 }
914
915                 // blinking magic: if there is more than one flag, show one of these in a clever way
916                 if(redflags)
917                         redflags = mod(floor(time * redflags * 0.75), redflags);
918                 if(blueflags)
919                         blueflags = mod(floor(time * blueflags * 0.75), blueflags);
920
921                 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
922                 {
923                         if(flag.items & IT_KEY2) // blue
924                         {
925                                 if(--redflags == -1) // happens exactly once (redflags is in 0..count-1, and will --'ed count times)
926                                         ctf_setstatus2(flag, IT_RED_FLAG_TAKEN);
927                         }
928                         else if(flag.items & IT_KEY1) // red
929                         {
930                                 if(--blueflags == -1) // happens exactly once
931                                         ctf_setstatus2(flag, IT_BLUE_FLAG_TAKEN);
932                         }
933                 }
934         }
935 };
936 /*
937 entity(float cteam) ctf_team_has_commander =
938 {
939         entity pl;
940         if(cteam != COLOR_TEAM1 || cteam != COLOR_TEAM2)
941                 return world;
942         
943         FOR_EACH_REALPLAYER(pl) {
944                 if(pl.team == cteam && pl.iscommander) {
945                         return pl;
946                 }
947         }
948         return world;
949 };
950
951 void(entity e, float st) ctf_setstate =
952 {
953         e.ctf_state = st;
954         ++e.version;
955 };
956
957 void(float cteam) ctf_new_commander =
958 {
959         entity pl, plmax;
960         
961         plmax = world;
962         FOR_EACH_REALPLAYER(pl) {
963                 if(pl.team == cteam) {
964                         if(pl.iscommander) { // don't reassign if alreay there
965                                 return;
966                         }
967                         if(plmax == world || plmax.frags < pl.frags) <<<<<<<<<<<<<<<<< BROKEN in new scoring system
968                                 plmax = pl;
969                 }
970         }
971         if(plmax == world) {
972                 bprint(strcat(ColoredTeamName(cteam), " Team has no Commander!\n"));
973                 return;
974         }
975
976         plmax.iscommander = TRUE;
977         ctf_setstate(plmax, 3);
978         sprint(plmax, "^3You're the commander now!\n");
979         centerprint(plmax, "^3You're the commander now!\n");
980 };
981
982 void() ctf_clientconnect =
983 {
984         self.iscommander = FALSE;
985         
986         if(!self.team || self.classname != "player") {
987                 ctf_setstate(self, -1);
988         } else
989                 ctf_setstate(self, 0);
990
991         self.team_saved = self.team;
992         
993         if(self.team != 0 && self.classname == "player" && !ctf_team_has_commander(self.team)) {
994                 ctf_new_commander(self.team);
995         }
996 };
997
998 void() ctf_playerchanged =
999 {
1000         if(!self.team || self.classname != "player") {
1001                 ctf_setstate(self, -1);
1002         } else if(self.ctf_state < 0 && self.classname == "player") {
1003                 ctf_setstate(self, 0);
1004         }
1005
1006         if(self.iscommander &&
1007            (self.classname != "player" || self.team != self.team_saved)
1008                 )
1009         {
1010                 self.iscommander = FALSE;
1011                 if(self.classname == "player")
1012                         ctf_setstate(self, 0);
1013                 else
1014                         ctf_setstate(self, -1);
1015                 ctf_new_commander(self.team_saved);
1016         }
1017         
1018         self.team_saved = self.team;
1019         
1020         ctf_new_commander(self.team);
1021 };
1022
1023 void() ctf_clientdisconnect =
1024 {
1025         if(self.iscommander)
1026         {
1027                 ctf_new_commander(self.team);
1028         }
1029 };
1030
1031 entity GetPlayer(string);
1032 float() ctf_clientcommand =
1033 {
1034         entity e;
1035         if(argv(0) == "order") {
1036                 if(!g_ctf) {
1037                         sprint(self, "This command is not supported in this gamemode.\n");
1038                         return TRUE;
1039                 }
1040                 if(!self.iscommander) {
1041                         sprint(self, "^1You are not the commander!\n");
1042                         return TRUE;
1043                 }
1044                 if(argv(2) == "") {
1045                         sprint(self, "Usage: order #player status   - (playernumber as in status)\n");
1046                         return TRUE;
1047                 }
1048                 e = GetPlayer(argv(1));
1049                 if(e == world) {
1050                         sprint(self, "Invalid player.\nUsage: order #player status   - (playernumber as in status)\n");
1051                         return TRUE;
1052                 }
1053                 if(e.team != self.team) {
1054                         sprint(self, "^3You can only give orders to your own team!\n");
1055                         return TRUE;
1056                 }
1057                 if(argv(2) == "attack") {
1058                         sprint(self, strcat("Ordering ", e.netname, " to attack!\n"));
1059                         sprint(e, "^1Attack!\n");
1060                         centerprint(e, "^7You've been ordered to^9\n^1Attack!\n");
1061                         ctf_setstate(e, 1);
1062                 } else if(argv(2) == "defend") {
1063                         sprint(self, strcat("Ordering ", e.netname, " to defend!\n"));
1064                         sprint(e, "^Defend!\n");
1065                         centerprint(e, "^7You've been ordered to^9\n^2Defend!\n");
1066                         ctf_setstate(e, 2);
1067                 } else {
1068                         sprint(self, "^7Invalid command, use ^3attack^7, or ^3defend^7.\n");
1069                 }
1070                 return TRUE;
1071         }
1072         return FALSE;
1073 };
1074 */