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