2 entity ctf_worldflaglist; // CTF flags in the map
3 .entity ctf_worldflagnext;
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 //float FLAGSCORE_PICKUP = 1;
8 //float FLAGSCORE_RETURN = 5; // returned by owner team
9 //float FLAGSCORE_RETURNROGUE = 10; // returned by rogue team
10 //float FLAGSCORE_CAPTURE = 5;
11 //float FLAGSCORE_CAPTURE_TEAM = 20;
13 #define FLAG_CARRY_POS '-15 0 7'
15 void FakeTimeLimit(entity e, float t)
18 WriteByte(MSG_ONE, 3); // svc_updatestat
19 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
21 WriteCoord(MSG_ONE, cvar("timelimit"));
23 WriteCoord(MSG_ONE, (t + 1) / 60);
26 float flagcaptimerecord;
27 .float flagpickuptime;
37 self.t_width = 0.1; // frame animation rate
39 self.t_length = 119; // maximum frame
41 setattachment(self, world, "");
42 self.mdl = self.model;
44 self.solid = SOLID_TRIGGER;
45 self.movetype = MOVETYPE_NONE;
46 self.velocity = '0 0 0';
47 self.origin_z = self.origin_z + 6;
48 self.think = FlagThink;
49 self.touch = FlagTouch;
50 self.nextthink = time + 0.1;
52 self.mangle = self.angles;
53 self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
54 //self.effects = self.effects | EF_DIMLIGHT;
57 self.movetype = MOVETYPE_TOSS;
60 dprint("Flag fell out of level at ", vtos(self.origin), "\n");
65 self.oldorigin = self.origin;
68 void LogCTF(string mode, float flagteam, entity actor)
71 if(!cvar("sv_eventlog"))
73 s = strcat(":ctf:", mode);
74 s = strcat(s, ":", ftos(flagteam));
76 s = strcat(s, ":", ftos(actor.playerid));
80 void RegenFlag(entity e)
82 setattachment(e, world, "");
83 e.movetype = MOVETYPE_NONE;
85 e.movetype = MOVETYPE_TOSS;
86 e.solid = SOLID_TRIGGER;
87 // TODO: play a sound here
88 setorigin(e, e.oldorigin);
92 e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
95 void ReturnFlag(entity e)
98 if (e.owner.flagcarried == e)
100 WaypointSprite_DetachCarrier(e.owner);
101 e.owner.flagcarried = world;
104 FakeTimeLimit(e.owner, -1);
110 void DropFlag(entity e, float penalty)
122 dprint("FLAG: drop - no owner?!?!\n");
126 if (p.flagcarried != e)
128 dprint("FLAG: drop - owner is not carrying this flag??\n");
131 bprint(p.netname, "^7 lost the ", e.netname, "\n");
135 UpdateFrags(p, -cvar("g_ctf_flagscore_pickup"));
136 PlayerScore_Add(p, SP_CTF_PICKUPS, -1);
139 if(cvar("g_ctf_flagpenalty_drop"))
140 UpdateFrags(p, -cvar("g_ctf_flagpenalty_drop"));
141 PlayerScore_Add(p, SP_CTF_DROPS, +1);
143 //if(e.enemy && e.enemy != e)
144 //UpdateFrags(e.enemy, cvar("g_ctf_flagscore_kill"));
145 WaypointSprite_DetachCarrier(p);
146 LogCTF("dropped", p.team, p.flagcarried);
148 setattachment(e, world, "");
150 if (p.flagcarried == e)
151 p.flagcarried = world;
154 e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
155 e.solid = SOLID_TRIGGER;
156 e.movetype = MOVETYPE_TOSS;
157 // setsize(e, '-16 -16 0', '16 16 74');
158 setorigin(e, p.origin - '0 0 24' + '0 0 37');
159 e.cnt = FLAG_DROPPED;
160 e.velocity = '0 0 300';
161 e.pain_finished = time + cvar("g_ctf_flag_returntime");//30;
163 trace_startsolid = FALSE;
164 tracebox(e.origin, e.mins, e.maxs, e.origin, TRUE, e);
166 dprint("FLAG FALLTHROUGH will happen SOON\n");
171 if(self.delay > time)
173 self.delay = time + self.t_width;
174 if(self.nextthink > self.delay)
175 self.nextthink = self.delay;
177 self.frame = self.frame + 1;
178 if(self.frame > self.t_length)
186 self.nextthink = time + 0.1;
190 if(self.speedrunning)
191 if(self.cnt == FLAG_CARRY)
194 if(flagcaptimerecord)
195 if(time >= self.flagpickuptime + flagcaptimerecord)
197 bprint("The ", self.netname, " became impatient after ", ftos_decimals(flagcaptimerecord, 2), " seconds and returned itself\n");
199 self.owner.impulse = 77; // returning!
209 if (self.cnt == FLAG_BASE)
212 if (self.cnt == FLAG_DROPPED)
214 // flag fallthrough? FIXME remove this if bug is really fixed now
215 if(self.origin_z < -131072)
217 dprint("FLAG FALLTHROUGH just happened\n");
218 self.pain_finished = 0;
220 setattachment(self, world, "");
221 if (time > self.pain_finished)
223 bprint("The ", self.netname, " has returned to base\n");
224 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
225 LogCTF("returned", self.team, world);
232 if (e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
234 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
247 local string s, s0, h0, h1;
248 if (other.classname != "player")
250 if (other.health < 1) // ignore dead players
253 if (self.cnt == FLAG_CARRY)
256 if (self.cnt == FLAG_BASE)
257 if (other.team == self.team)
258 if (other.flagcarried) // he's got a flag
259 if (other.flagcarried.team != self.team) // capture
261 if (other.flagcarried == world)
265 t = time - other.flagcarried.flagpickuptime;
266 s = ftos_decimals(t, 2);
267 s0 = ftos_decimals(flagcaptimerecord, 2);
268 h0 = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
273 h0 = strcat(h0, "^7's"); // h0: display text for previous netname
274 if (flagcaptimerecord == 0)
276 bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, " seconds\n");
277 flagcaptimerecord = t;
278 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
279 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
280 GameLogEcho(strcat(":recordset:", ftos(other.playerid), ":", ftos(t)));
282 else if (t < flagcaptimerecord)
284 bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, ", breaking ", strcat(h0, " previous record of ", s0, " seconds\n"));
285 flagcaptimerecord = t;
286 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
287 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
288 GameLogEcho(strcat(":recordset:", ftos(other.playerid), ":", ftos(t)));
292 bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, ", failing to break ", strcat(h0, " record of ", s0, " seconds\n"));
295 PlayerTeamScore_Add(other, SP_CTF_CAPS, ST_CTF_CAPS, 1);
296 LogCTF("capture", other.flagcarried.team, other);
297 // give credit to the individual player
298 UpdateFrags(other, cvar("g_ctf_flagscore_capture"));
300 // give credit to all players of the team (rewards large teams)
301 // NOTE: this defaults to 0
302 FOR_EACH_PLAYER(head)
303 if (head.team == self.team)
304 UpdateFrags(head, cvar("g_ctf_flagscore_capture_team"));
306 sound (other, CHAN_AUTO, self.noise2, VOL_BASE, ATTN_NONE);
307 WaypointSprite_DetachCarrier(other);
308 if(self.speedrunning)
309 FakeTimeLimit(other, -1);
310 RegenFlag (other.flagcarried);
311 other.flagcarried = world;
312 other.next_take_time = time + 1;
314 if (self.cnt == FLAG_BASE)
315 if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) // only red and blue team can steal flags
316 if (other.team != self.team)
317 if (!other.flagcarried)
319 if (other.next_take_time > time)
322 self.flagpickuptime = time; // used for timing runs
323 self.speedrunning = other.speedrunning; // if speedrunning, flag will self-return and teleport the owner back after the record
324 if(other.speedrunning)
325 if(flagcaptimerecord)
326 FakeTimeLimit(other, time + flagcaptimerecord);
327 self.solid = SOLID_NOT;
328 setorigin(self, self.origin); // relink
330 other.flagcarried = self;
331 self.cnt = FLAG_CARRY;
332 self.angles = '0 0 0';
333 bprint(other.netname, "^7 got the ", self.netname, "\n");
334 UpdateFrags(other, cvar("g_ctf_flagscore_pickup"));
335 PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
336 LogCTF("steal", self.team, other);
337 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NONE);
339 FOR_EACH_PLAYER(player)
340 if(player.team == self.team)
341 centerprint(player, "The enemy got your flag! Retrieve it!");
343 self.movetype = MOVETYPE_NONE;
344 setorigin(self, FLAG_CARRY_POS);
345 setattachment(self, other, "");
346 WaypointSprite_AttachCarrier("flagcarrier", other);
351 if (self.cnt == FLAG_DROPPED)
353 self.flags = FL_ITEM; // clear FL_ONGROUND and any other junk
354 if (other.team == self.team || (other.team != COLOR_TEAM1 && other.team != COLOR_TEAM2))
357 bprint(other.netname, "^7 returned the ", self.netname, "\n");
358 if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
359 UpdateFrags(other, cvar("g_ctf_flagscore_return"));
361 UpdateFrags(other, cvar("g_ctf_flagscore_return_rogue"));
362 PlayerScore_Add(other, SP_CTF_RETURNS, 1);
363 LogCTF("return", self.team, other);
364 sound (other, CHAN_AUTO, self.noise1, VOL_BASE, ATTN_NONE);
367 else if (!other.flagcarried)
370 self.solid = SOLID_NOT;
371 setorigin(self, self.origin); // relink
373 other.flagcarried = self;
374 self.cnt = FLAG_CARRY;
375 bprint(other.netname, "^7 picked up the ", self.netname, "\n");
376 UpdateFrags(other, cvar("g_ctf_flagscore_pickup"));
377 PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
378 LogCTF("pickup", self.team, other);
379 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NONE);
381 FOR_EACH_PLAYER(player)
382 if(player.team == self.team)
383 centerprint(player, "The enemy got your flag! Retrieve it!");
385 self.movetype = MOVETYPE_NONE; // flag must have MOVETYPE_NONE here, otherwise it will drop through the floor...
386 setorigin(self, FLAG_CARRY_POS);
387 setattachment(self, other, "");
388 WaypointSprite_AttachCarrier("flagcarrier", other);
393 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
394 CTF Starting point for a player
399 viewing angle when spawning
401 void spawnfunc_info_player_team1()
403 self.team = COLOR_TEAM1; // red
404 spawnfunc_info_player_deathmatch();
406 //self.team = 4;self.classname = "info_player_start";spawnfunc_info_player_start();};
408 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
409 CTF Starting point for a player in
414 viewing angle when spawning
416 void spawnfunc_info_player_team2()
418 self.team = COLOR_TEAM2; // blue
419 spawnfunc_info_player_deathmatch();
421 //self.team = 13;self.classname = "info_player_start";spawnfunc_info_player_start();};
423 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
424 CTF Starting point for a player in
425 team three (Magenta).
429 viewing angle when spawning
431 void spawnfunc_info_player_team3()
433 self.team = COLOR_TEAM3; // purple
434 spawnfunc_info_player_deathmatch();
438 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
439 CTF Starting point for a player in
444 viewing angle when spawning
446 void spawnfunc_info_player_team4()
448 self.team = COLOR_TEAM4; // yellow
449 spawnfunc_info_player_deathmatch();
455 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
456 CTF flag for team one (Red).
457 Multiple are allowed.
461 Angle the flag will point
464 model to use, note this needs red and blue as skins 0 and 1
465 (default models/ctf/flag.md3)
467 sound played when flag is picked up
468 (default ctf/take.wav)
470 sound played when flag is returned by a teammate
471 (default ctf/return.wav)
473 sound played when flag is captured
474 (default ctf/redcapture.wav)
476 sound played when flag is lost in the field and respawns itself
477 (default ctf/respawn.wav)
480 void spawnfunc_item_flag_team1()
488 //if(!cvar("teamplay"))
489 // cvar_set("teamplay", "3");
491 // link flag into ctf_worldflaglist
492 self.ctf_worldflagnext = ctf_worldflaglist;
493 ctf_worldflaglist = self;
495 self.classname = "item_flag_team";
496 self.team = COLOR_TEAM1; // color 4 team (red)
497 self.items = IT_KEY2; // gold key (redish enough)
498 self.netname = "^1RED^7 flag";
499 self.target = "###item###";
501 if(self.spawnflags & 1)
504 self.model = "models/ctf/flag_red.md3";
506 self.noise = "ctf/take.wav";
508 self.noise1 = "ctf/return.wav";
510 self.noise2 = "ctf/redcapture.wav"; // blue team scores by capturing the red flag
512 self.noise3 = "ctf/respawn.wav";
513 precache_model (self.model);
514 setmodel (self, self.model); // precision set below
515 precache_sound (self.noise);
516 precache_sound (self.noise1);
517 precache_sound (self.noise2);
518 precache_sound (self.noise3);
519 setsize(self, '-16 -16 -37', '16 16 37');
520 setorigin(self, self.origin + '0 0 37');
521 self.nextthink = time + 0.2; // start after doors etc
522 self.think = place_flag;
526 //if(!self.glow_size)
527 // self.glow_size = 50;
529 self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION;
533 waypoint_spawnforitem(self);
535 WaypointSprite_SpawnFixed("redbase", self.origin + '0 0 37', self, sprite);
538 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -24) (48 48 64)
539 CTF flag for team two (Blue).
540 Multiple are allowed.
544 Angle the flag will point
547 model to use, note this needs red and blue as skins 0 and 1
548 (default models/ctf/flag.md3)
550 sound played when flag is picked up
551 (default ctf/take.wav)
553 sound played when flag is returned by a teammate
554 (default ctf/return.wav)
556 sound played when flag is captured
557 (default ctf/bluecapture.wav)
559 sound played when flag is lost in the field and respawns itself
560 (default ctf/respawn.wav)
563 void spawnfunc_item_flag_team2()
570 //if(!cvar("teamplay"))
571 // cvar_set("teamplay", "3");
573 // link flag into ctf_worldflaglist
574 self.ctf_worldflagnext = ctf_worldflaglist;
575 ctf_worldflaglist = self;
577 self.classname = "item_flag_team";
578 self.team = COLOR_TEAM2; // color 13 team (blue)
579 self.items = IT_KEY1; // silver key (bluish enough)
580 self.netname = "^4BLUE^7 flag";
581 self.target = "###item###";
583 if(self.spawnflags & 1)
586 self.model = "models/ctf/flag_blue.md3";
588 self.noise = "ctf/take.wav";
590 self.noise1 = "ctf/return.wav";
592 self.noise2 = "ctf/bluecapture.wav"; // red team scores by capturing the blue flag
594 self.noise3 = "ctf/respawn.wav";
595 precache_model (self.model);
596 setmodel (self, self.model); // precision set below
597 precache_sound (self.noise);
598 precache_sound (self.noise1);
599 precache_sound (self.noise2);
600 precache_sound (self.noise3);
601 setsize(self, '-16 -16 -37', '16 16 37');
602 setorigin(self, self.origin + '0 0 37');
603 self.nextthink = time + 0.2; // start after doors etc
604 self.think = place_flag;
608 //if(!self.glow_size)
609 // self.glow_size = 50;
611 self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION;
615 waypoint_spawnforitem(self);
617 WaypointSprite_SpawnFixed("bluebase", self.origin + '0 0 37', self, sprite);
621 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
622 Team declaration for CTF gameplay, this allows you to decide what team
623 names and control point models are used in your map.
625 Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike
626 domination, you don't need to make a blank one too.
630 Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)
632 Scoreboard color of the team (for example 4 is red and 13 is blue)
636 void spawnfunc_ctf_team()
643 self.classname = "ctf_team";
644 self.team = self.cnt + 1;
647 // code from here on is just to support maps that don't have control point and team entities
648 void ctf_spawnteam (string teamname, float teamcolor)
650 local entity oldself;
653 self.classname = "ctf_team";
654 self.netname = teamname;
655 self.cnt = teamcolor;
657 spawnfunc_ctf_team();
662 // spawn some default teams if the map is not set up for ctf
663 void ctf_spawnteams()
667 numteams = 2;//cvar("g_ctf_default_teams");
669 ctf_spawnteam("Red", COLOR_TEAM1 - 1);
670 ctf_spawnteam("Blue", COLOR_TEAM2 - 1);
673 void ctf_delayedinit()
675 // if no teams are found, spawn defaults
676 if (find(world, classname, "ctf_team") == world)
684 InitializeEntity(world, ctf_delayedinit, INITPRIO_GAMETYPE);
685 flagcaptimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
688 void ctf_setstatus2(entity flag, float shift)
690 if (flag.cnt == FLAG_CARRY)
691 if (flag.owner == self)
692 self.items |= shift * 3;
694 self.items |= shift * 1;
695 else if (flag.cnt == FLAG_DROPPED)
696 self.items |= shift * 2;
705 self.items = self.items - (self.items & IT_RED_FLAG_TAKEN);
706 self.items = self.items - (self.items & IT_RED_FLAG_LOST);
707 self.items = self.items - (self.items & IT_BLUE_FLAG_TAKEN);
708 self.items = self.items - (self.items & IT_BLUE_FLAG_LOST);
712 float redflags, blueflags;
717 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
719 if(flag.team == COLOR_TEAM1)
721 else if(flag.team == COLOR_TEAM2)
725 // blinking magic: if there is more than one flag, show one of these in a clever way
727 redflags = mod(floor(time * redflags * 0.75), redflags);
729 blueflags = mod(floor(time * blueflags * 0.75), blueflags);
731 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
733 if(flag.team == COLOR_TEAM1)
735 if(--redflags == -1) // happens exactly once (redflags is in 0..count-1, and will --'ed count times)
736 ctf_setstatus2(flag, IT_RED_FLAG_TAKEN);
738 else if(flag.team == COLOR_TEAM2)
740 if(--blueflags == -1) // happens exactly once
741 ctf_setstatus2(flag, IT_BLUE_FLAG_TAKEN);
747 entity(float cteam) ctf_team_has_commander =
750 if(cteam != COLOR_TEAM1 || cteam != COLOR_TEAM2)
753 FOR_EACH_REALPLAYER(pl) {
754 if(pl.team == cteam && pl.iscommander) {
761 void(entity e, float st) ctf_setstate =
767 void(float cteam) ctf_new_commander =
772 FOR_EACH_REALPLAYER(pl) {
773 if(pl.team == cteam) {
774 if(pl.iscommander) { // don't reassign if alreay there
777 if(plmax == world || plmax.frags < pl.frags) <<<<<<<<<<<<<<<<< BROKEN in new scoring system
782 bprint(strcat(ColoredTeamName(cteam), " Team has no Commander!\n"));
786 plmax.iscommander = TRUE;
787 ctf_setstate(plmax, 3);
788 sprint(plmax, "^3You're the commander now!\n");
789 centerprint(plmax, "^3You're the commander now!\n");
792 void() ctf_clientconnect =
794 self.iscommander = FALSE;
796 if(!self.team || self.classname != "player") {
797 ctf_setstate(self, -1);
799 ctf_setstate(self, 0);
801 self.team_saved = self.team;
803 if(self.team != 0 && self.classname == "player" && !ctf_team_has_commander(self.team)) {
804 ctf_new_commander(self.team);
808 void() ctf_playerchanged =
810 if(!self.team || self.classname != "player") {
811 ctf_setstate(self, -1);
812 } else if(self.ctf_state < 0 && self.classname == "player") {
813 ctf_setstate(self, 0);
816 if(self.iscommander &&
817 (self.classname != "player" || self.team != self.team_saved)
820 self.iscommander = FALSE;
821 if(self.classname == "player")
822 ctf_setstate(self, 0);
824 ctf_setstate(self, -1);
825 ctf_new_commander(self.team_saved);
828 self.team_saved = self.team;
830 ctf_new_commander(self.team);
833 void() ctf_clientdisconnect =
837 ctf_new_commander(self.team);
841 entity GetPlayer(string);
842 float() ctf_clientcommand =
845 if(argv(0) == "order") {
847 sprint(self, "This command is not supported in this gamemode.\n");
850 if(!self.iscommander) {
851 sprint(self, "^1You are not the commander!\n");
855 sprint(self, "Usage: order #player status - (playernumber as in status)\n");
858 e = GetPlayer(argv(1));
860 sprint(self, "Invalid player.\nUsage: order #player status - (playernumber as in status)\n");
863 if(e.team != self.team) {
864 sprint(self, "^3You can only give orders to your own team!\n");
867 if(argv(2) == "attack") {
868 sprint(self, strcat("Ordering ", e.netname, " to attack!\n"));
869 sprint(e, "^1Attack!\n");
870 centerprint(e, "^7You've been ordered to^9\n^1Attack!\n");
872 } else if(argv(2) == "defend") {
873 sprint(self, strcat("Ordering ", e.netname, " to defend!\n"));
874 sprint(e, "^Defend!\n");
875 centerprint(e, "^7You've been ordered to^9\n^2Defend!\n");
878 sprint(self, "^7Invalid command, use ^3attack^7, or ^3defend^7.\n");