]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/ctf.qc
Replaced trunk's centerprint system by the simpler one in branch; this is to
[divverent/nexuiz.git] / data / qcsrc / server / ctf.qc
1
2 .float next_take_time;                  // the next time a player can pick up a flag (time + blah)
3                                                                 /// I used this, in part, to fix the looping score bug. - avirox
4
5 //float FLAGSCORE_PICKUP        =  1;
6 //float FLAGSCORE_RETURN        =  5; // returned by owner team
7 //float FLAGSCORE_RETURNROGUE   = 10; // returned by rogue team
8 //float FLAGSCORE_CAPTURE       =  5;
9 //float FLAGSCORE_CAPTURE_TEAM  = 20;
10
11 #define FLAG_CARRY_POS '-15 0 7'
12
13 void() FlagThink;
14 void() FlagTouch;
15
16 void() place_flag =
17 {
18         if(!self.t_width)
19                 self.t_width = 0.1; // frame animation rate
20         if(!self.t_length)
21                 self.t_length = 119; // maximum frame
22
23         setattachment(self, world, ""); 
24         self.mdl = self.model;
25         self.flags = FL_ITEM;
26         self.solid = SOLID_TRIGGER;
27         self.movetype = MOVETYPE_TOSS;
28         self.velocity = '0 0 0';
29         self.origin_z = self.origin_z + 6;
30         self.think = FlagThink;
31         self.touch = FlagTouch;
32         self.nextthink = time + 0.1;
33         self.cnt = FLAG_BASE;
34         self.mangle = self.angles;
35         //self.effects = self.effects | EF_DIMLIGHT;
36         if (!droptofloor(0, 0))
37         {
38                 dprint("Flag fell out of level at ", vtos(self.origin), "\n");
39                 remove(self);
40                 return;
41         }
42         self.oldorigin = self.origin;
43 };
44
45 void LogCTF(string mode, float flagteam, entity actor)
46 {
47         string s;
48         if(!cvar("sv_eventlog"))
49                 return;
50         s = strcat(":ctf:", mode);
51         s = strcat(s, ":", ftos(flagteam));
52         if(actor != world)
53                 s = strcat(s, ":", ftos(actor.playerid));
54         GameLogEcho(s, FALSE);
55 }
56
57 void(entity e) RegenFlag =
58 {
59         setattachment(e, world, "");    
60         e.movetype = MOVETYPE_TOSS;
61         e.solid = SOLID_TRIGGER;
62         // TODO: play a sound here
63         setorigin(e, e.oldorigin);
64         e.angles = e.mangle;
65         e.cnt = FLAG_BASE;
66         e.owner = world;
67         e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
68 };
69
70 void(entity e) ReturnFlag =
71 {
72         if (e.owner)
73         if (e.owner.flagcarried == e)
74                 e.owner.flagcarried = world;
75         e.owner = world;
76         RegenFlag(e);
77 };
78
79 void(entity e) DropFlag =
80 {
81         local entity p;
82
83         if (!e.owner)
84         {
85                 dprint("FLAG: drop - no owner?!?!\n");
86                 return;
87         }
88         p = e.owner;
89         if (p.flagcarried != e)
90         {
91                 dprint("FLAG: drop - owner is not carrying this flag??\n");
92                 return;
93         }
94         bprint(strcat(p.netname, "^7 lost the ", e.netname, "\n"));
95         LogCTF("dropped", p.team, p.flagcarried);
96
97         setattachment(e, world, "");
98
99         if (p.flagcarried == e)
100                 p.flagcarried = world;
101         e.owner = world;
102
103         e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
104         e.solid = SOLID_TRIGGER;
105         e.movetype = MOVETYPE_TOSS;
106         // setsize(e, '-16 -16 0', '16 16 74');
107         setorigin(e, p.origin - '0 0 24' + '0 0 37');
108         e.cnt = FLAG_DROPPED;
109         e.velocity = '0 0 300';
110         e.pain_finished = time + cvar("g_ctf_flag_returntime");//30;
111
112         trace_startsolid = FALSE;
113         tracebox(e.origin, e.mins, e.maxs, e.origin, TRUE, e);
114         if(trace_startsolid)
115                 dprint("FLAG FALLTHROUGH will happen SOON\n");
116 };
117
118 void AnimateFlag()
119 {
120         if(self.delay > time)
121                 return;
122         self.delay = time + self.t_width;
123         if(self.nextthink > self.delay)
124                 self.nextthink = self.delay;
125
126         self.frame = self.frame + 1;
127         if(self.frame > self.t_length)
128                 self.frame = 0;
129 }
130
131 void() FlagThink =
132 {
133         local entity e;
134
135         self.nextthink = time + 0.1;
136
137         AnimateFlag();
138
139         if (self.cnt == FLAG_BASE)
140                 return;
141
142         if (self.cnt == FLAG_DROPPED)
143         {
144                 // flag fallthrough? FIXME remove this if bug is really fixed now
145                 if(self.origin_z < -131072)
146                 {
147                         dprint("FLAG FALLTHROUGH just happened\n");
148                         self.pain_finished = 0;
149                 }
150                 setattachment(self, world, "");
151                 if (time > self.pain_finished)
152                 {
153                         bprint(strcat("The ", self.netname, " has returned to base\n"));
154                         sound (e, CHAN_AUTO, self.noise3, 1, ATTN_NONE);
155                         LogCTF("returned", self.team, world);
156                         ReturnFlag(self);
157                 }
158                 return;
159         }
160
161         e = self.owner;
162         if (e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
163         {
164                 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
165                 DropFlag(self);
166                 return;
167         }
168 };
169
170 float   flagcaptimerecord;
171 .float  flagpickuptime;
172
173 void() FlagTouch =
174 {
175         if(gameover) return;
176
177         local float t;
178         local entity head;
179         local entity player;
180         if (other.classname != "player")
181                 return;
182         if (other.health < 1) // ignore dead players
183                 return;
184
185         if (self.cnt == FLAG_CARRY)
186                 return;
187
188         if (self.cnt == FLAG_BASE)
189         if (other.team == self.team)
190         if (other.flagcarried) // he's got a flag
191         if (other.flagcarried.team != self.team) // capture
192         {
193                 if (other.flagcarried == world)
194                 {
195                         return;
196                 }
197                 t = time - other.flagcarried.flagpickuptime;
198                 if (flagcaptimerecord == 0)
199                 {
200                         bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", ftos(t), " seconds\n");
201                         flagcaptimerecord = t;
202                 }
203                 else if (t < flagcaptimerecord)
204                 {
205                         bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", ftos(t), ", breaking the previous record of ", ftos(flagcaptimerecord), " seconds\n");
206                         flagcaptimerecord = t;
207                 }
208                 else
209                 {
210                         bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", ftos(t), ", failing to break the previous record of ", ftos(flagcaptimerecord), " seconds\n");
211                 }
212
213                 LogCTF("capture", other.flagcarried.team, other);
214                 // give credit to the individual player
215                 other.frags = other.frags + cvar("g_ctf_flagscore_capture");//FLAGSCORE_CAPTURE;
216                 // give credit to all players of the team (rewards large teams)
217                 // NOTE: this defaults to 0
218                 head = find(head, classname, "player");
219                 while (head)
220                 {
221                         if (head.team == self.team)
222                                 head.frags = head.frags + cvar("g_ctf_flagscore_capture_team");//FLAGSCORE_CAPTURE_TEAM;
223                         head = find(head, classname, "player");
224                 }
225                 sound (self, CHAN_AUTO, self.noise2, 1, ATTN_NONE);
226                 RegenFlag (other.flagcarried);
227                 other.flagcarried = world;
228                 other.next_take_time = time + 1;
229         }
230         if (self.cnt == FLAG_BASE)
231         if (other.team == 5 || other.team == 14) // only red and blue team can steal flags
232         if (other.team != self.team)
233         if (!other.flagcarried)
234         {
235                 if (other.next_take_time > time)
236                         return;
237                 // pick up
238                 self.flagpickuptime = time; // used for timing runs
239                 self.solid = SOLID_NOT;
240                 setorigin(self, self.origin); // relink
241                 self.owner = other;
242                 other.flagcarried = self;
243                 self.cnt = FLAG_CARRY;
244                 bprint(other.netname, "^7 got the ", self.netname, "\n");
245                 other.frags = other.frags + cvar("g_ctf_flagscore_pickup");//FLAGSCORE_PICKUP;
246                 LogCTF("steal", self.team, other);
247                 sound (self, CHAN_AUTO, self.noise, 1, ATTN_NONE);
248
249                 player = find(world, classname, "player");
250                 while(player) {
251                         if(player.team == self.team) centerprint(player, "The enemy got your flag! Retrieve it!");
252                         player = find(player, classname, "player");
253                 }
254
255                 self.movetype = MOVETYPE_NONE;
256                 setorigin(self, FLAG_CARRY_POS);
257                 setattachment(self, other, "");
258
259                 return;
260         }
261
262         if (self.cnt == FLAG_DROPPED)
263         {
264                 self.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
265                 if (other.team == self.team || (other.team != 5 && other.team != 14))
266                 {
267                         // return flag
268                         bprint(other.netname, "^7 returned the ", self.netname, "\n");
269                         if (other.team == 5 || other.team == 14)
270                                 other.frags = other.frags + cvar("g_ctf_flagscore_return");//FLAGSCORE_RETURN;
271                         else
272                                 other.frags = other.frags + cvar("g_ctf_flagscore_return_rogue");//FLAGSCORE_RETURNROGUE;
273                         LogCTF("return", self.team, other);
274                         sound (self, CHAN_AUTO, self.noise1, 1, ATTN_NONE);
275                         ReturnFlag(self);
276                 }
277                 else if (!other.flagcarried)
278                 {
279                         // pick up
280                         self.solid = SOLID_NOT;
281                         setorigin(self, self.origin); // relink
282                         self.owner = other;
283                         other.flagcarried = self;
284                         self.cnt = FLAG_CARRY;
285                         bprint(other.netname, "^7 picked up the ", self.netname, "\n");
286                         other.frags = other.frags + cvar("g_ctf_flagscore_pickup");//FLAGSCORE_PICKUP;
287                         LogCTF("pickup", self.team, other);
288                         sound (self, CHAN_AUTO, self.noise, 1, ATTN_NONE);
289
290                         player = find(world, classname, "player");
291                         while(player) {
292                                 if(player.team == self.team) centerprint(player, "The enemy got your flag! Retrieve it!");
293                                 player = find(player, classname, "player");
294                         }
295                         self.movetype = MOVETYPE_NONE;  // flag must have MOVETYPE_NONE here, otherwise it will drop through the floor...
296                         setorigin(self, FLAG_CARRY_POS);
297                         setattachment(self, other, "");
298                 }
299         }
300 };
301
302 /*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
303 CTF Starting point for a player
304 in team one (Red).
305
306 Keys:
307 "angle"
308  viewing angle when spawning
309 */
310 void() info_player_team1 =
311 {
312         self.classname = "info_player_deathmatch";
313         self.team = 5; // red
314         relocate_spawnpoint();
315 };
316 //self.team = 4;self.classname = "info_player_start";info_player_start();};
317
318 /*QUAKED info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
319 CTF Starting point for a player in
320 team two (Blue).
321
322 Keys:
323 "angle"
324  viewing angle when spawning
325 */
326 void() info_player_team2 =
327 {
328         self.classname = "info_player_deathmatch";
329         self.team = 14; // blue
330         relocate_spawnpoint();
331 };
332 //self.team = 13;self.classname = "info_player_start";info_player_start();};
333
334 /*QUAKED info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
335 CTF Starting point for a player in
336 team three (Magenta).
337
338 Keys:
339 "angle"
340  viewing angle when spawning
341 */
342 void() info_player_team3 =
343 {
344         self.classname = "info_player_deathmatch";
345         self.team = 10; // purple
346         relocate_spawnpoint();
347 };
348
349
350 /*QUAKED info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
351 CTF Starting point for a player in
352 team four (Yellow).
353
354 Keys:
355 "angle"
356  viewing angle when spawning
357 */
358 void() info_player_team4 =
359 {
360         self.classname = "info_player_deathmatch";
361         self.team = 13; // yellow
362         relocate_spawnpoint();
363 };
364
365
366
367
368 /*QUAKED item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
369 CTF flag for team one (Red).
370 Multiple are allowed.
371
372 Keys:
373 "angle"
374  Angle the flag will point
375 (minus 90 degrees)
376 "model"
377  model to use, note this needs red and blue as skins 0 and 1
378  (default models/ctf/flag.md3)
379 "noise"
380  sound played when flag is picked up
381  (default ctf/take.wav)
382 "noise1"
383  sound played when flag is returned by a teammate
384  (default ctf/return.wav)
385 "noise2"
386  sound played when flag is captured
387  (default ctf/capture.wav)
388 "noise3"
389  sound played when flag is lost in the field and respawns itself
390  (default ctf/respawn.wav)
391 */
392
393 void() item_flag_team1 =
394 {
395         if (!cvar("g_ctf"))
396                 return;
397         //if(!cvar("teamplay"))
398         //      cvar_set("teamplay", "3");
399
400         self.classname = "item_flag_team1";
401         self.team = 5; // color 4 team (red)
402         self.items = IT_KEY2; // gold key (redish enough)
403         self.netname = "^1RED^7 flag";
404         self.target = "###item###";
405         self.skin = 0;
406         if (!self.model)
407                 self.model = "models/ctf/flag_red.md3";
408         if (!self.noise)
409                 self.noise = "ctf/take.wav";
410         if (!self.noise1)
411                 self.noise1 = "ctf/return.wav";
412         if (!self.noise2)
413                 self.noise2 = "ctf/capture.wav";
414         if (!self.noise3)
415                 self.noise3 = "ctf/respawn.wav";
416         precache_model (self.model);
417         setmodel (self, self.model);
418         precache_sound (self.noise);
419         precache_sound (self.noise1);
420         precache_sound (self.noise2);
421         precache_sound (self.noise3);
422         setsize(self, '-16 -16 -37', '16 16 37');
423         setorigin(self, self.origin + '0 0 37');
424         self.nextthink = time + 0.2; // start after doors etc
425         self.think = place_flag;
426
427         if(!self.scale)
428                 self.scale = 0.6;
429         //if(!self.glow_size)
430         //      self.glow_size = 50;
431
432         self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION;
433 };
434
435 /*QUAKED item_flag_team2 (0 0.5 0.8) (-48 -48 -24) (48 48 64)
436 CTF flag for team two (Blue).
437 Multiple are allowed.
438
439 Keys:
440 "angle"
441  Angle the flag will point
442 (minus 90 degrees)
443
444 */
445
446 void() item_flag_team2 =
447 {
448         if (!cvar("g_ctf"))
449                 return;
450         //if(!cvar("teamplay"))
451         //      cvar_set("teamplay", "3");
452
453         self.classname = "item_flag_team2";
454         self.team = 14; // color 13 team (blue)
455         self.items = IT_KEY1; // silver key (bluish enough)
456         self.netname = "^4BLUE^7 flag";
457         self.target = "###item###";
458         self.skin = 0;
459         if (!self.model)
460                 self.model = "models/ctf/flag_blue.md3";
461         if (!self.noise)
462                 self.noise = "ctf/take.wav";
463         if (!self.noise1)
464                 self.noise1 = "ctf/return.wav";
465         if (!self.noise2)
466                 self.noise2 = "ctf/capture.wav";
467         if (!self.noise3)
468                 self.noise3 = "ctf/respawn.wav";
469         precache_model (self.model);
470         setmodel (self, self.model);
471         precache_sound (self.noise);
472         precache_sound (self.noise1);
473         precache_sound (self.noise2);
474         precache_sound (self.noise3);
475         setsize(self, '-16 -16 -37', '16 16 37');
476         setorigin(self, self.origin + '0 0 37');
477         self.nextthink = time + 0.2; // start after doors etc
478         self.think = place_flag;
479
480         if(!self.scale)
481                 self.scale = 0.6;
482         //if(!self.glow_size)
483         //      self.glow_size = 50;
484
485         self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION;
486 };
487
488
489 // spawnfunctions for q3 flags
490 void team_CTF_redflag()
491 {
492         item_flag_team1();
493 }
494
495 void team_CTF_blueflag()
496 {
497         item_flag_team2();
498 }
499
500
501 /*QUAKED ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
502 Team declaration for CTF gameplay, this allows you to decide what team
503 names and control point models are used in your map.
504
505 Note: If you use ctf_team entities you must define at least 2!  However, unlike
506 domination, you don't need to make a blank one too.
507
508 Keys:
509 "netname"
510  Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)
511 "cnt"
512  Scoreboard color of the team (for example 4 is red and 13 is blue)
513
514 */
515
516 void() ctf_team =
517 {
518         self.classname = "ctf_team";
519         self.team = self.cnt + 1;
520 };
521
522 // code from here on is just to support maps that don't have control point and team entities
523 void ctf_spawnteam (string teamname, float teamcolor)
524 {
525         local entity oldself;
526         oldself = self;
527         self = spawn();
528         self.classname = "ctf_team";
529         self.netname = teamname;
530         self.cnt = teamcolor;
531
532         ctf_team();
533
534         self = oldself;
535 };
536
537 // spawn some default teams if the map is not set up for ctf
538 void() ctf_spawnteams =
539 {
540         float numteams;
541
542         numteams = 2;//cvar("g_ctf_default_teams");
543
544         ctf_spawnteam("Red", 4);
545         ctf_spawnteam("Blue", 13);
546 };
547
548 void() ctf_delayedinit =
549 {
550         self.think = SUB_Remove;
551         self.nextthink = time;
552         // if no teams are found, spawn defaults
553         if (find(world, classname, "ctf_team") == world)
554                 ctf_spawnteams();
555 };
556
557 void() ctf_init =
558 {
559         local entity e;
560         e = spawn();
561         e.think = ctf_delayedinit;
562         e.nextthink = time + 0.1;
563 };
564
565 void(entity flag) ctf_setstatus2 =
566 {
567         if (flag) {
568                 local float shift;
569                 if (flag.team == 5) shift = IT_RED_FLAG_TAKEN;
570                 else if (flag.team == 14) shift = IT_BLUE_FLAG_TAKEN;
571                 else shift = 0;
572
573                 local float status;
574                 if (flag.cnt == FLAG_CARRY)
575                         if (flag.owner == self) status = 3;
576                         else status = 1;
577                 else if (flag.cnt == FLAG_DROPPED) status = 2;
578                 else status = 0;
579
580                 self.items = self.items | (shift * status);
581         }
582 };
583
584 void() ctf_setstatus =
585 {
586         self.items = self.items - (self.items & IT_RED_FLAG_TAKEN);
587         self.items = self.items - (self.items & IT_RED_FLAG_LOST);
588         self.items = self.items - (self.items & IT_BLUE_FLAG_TAKEN);
589         self.items = self.items - (self.items & IT_BLUE_FLAG_LOST);
590
591         if (cvar("g_ctf")) {
592                 local entity flag;
593                 flag = find(world, classname, "item_flag_team1");
594                 ctf_setstatus2(flag);
595                 flag = find(world, classname, "item_flag_team2");
596                 ctf_setstatus2(flag);
597         }
598 };