1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: March 28th, 2011
4 // ================================================================
7 #define FLAG_MIN (PL_MIN + '0 0 -13')
8 #define FLAG_MAX (PL_MAX + '0 0 -13')
9 #define FLAG_CARRY_POS '-15 0 7'
11 .entity bot_basewaypoint; // flag waypointsprite
13 .entity wps_flagcarrier;
14 .entity wps_flagdropped;
16 entity ctf_worldflaglist; // CTF flags in the map
17 .entity ctf_worldflagnext;
19 float ctf_captimerecord; // record time for capturing the flag
20 .float ctf_pickuptime;
21 .float ctf_dropperid; // don't allow spam of dropping the flag
23 .float ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
25 .float next_take_time; // Delay between when the person can pick up a flag // is this obsolete from the stuff above?
27 // CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
28 .float ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
29 float ctf_captureshield_min_negscore; // punish at -20 points
30 float ctf_captureshield_max_ratio; // punish at most 30% of each team
31 float ctf_captureshield_force; // push force of the shield
33 // declare functions so they can be used in any order in the file
34 void ctf_FlagTouch(void);
35 void ctf_FlagThink(void);
36 void ctf_SetupFlag(float, entity);
37 void ctf_RespawnFlag(entity);
38 float ctf_CaptureShield_CheckStatus(entity);
39 void ctf_CaptureShield_Update(entity, float);
40 float ctf_CaptureShield_Customize(void);
41 void ctf_CaptureShield_Touch(void);
42 void ctf_CaptureShield_Spawn(entity);
49 float ctf_ReadScore(string parameter) // make this obsolete
51 if(g_ctf_win_mode != 2)
52 return cvar(strcat("g_ctf_personal", parameter));
54 return cvar(strcat("g_ctf_flag", parameter));
57 void ctf_FakeTimeLimit(entity e, float t)
60 WriteByte(MSG_ONE, 3); // svc_updatestat
61 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
63 WriteCoord(MSG_ONE, autocvar_timelimit);
65 WriteCoord(MSG_ONE, (t + 1) / 60);
68 void ctf_EventLog(string mode, float flagteam, entity actor)
71 if(!autocvar_sv_eventlog)
73 s = strcat(":ctf:", mode);
74 s = strcat(s, ":", ftos(flagteam));
76 s = strcat(s, ":", ftos(actor.playerid));
80 void ctf_CreateBaseWaypoints(entity flag, float teamnumber)
83 waypoint_spawnforitem_force(flag, flag.origin);
84 flag.nearestwaypointtimeout = 0; // activate waypointing again
85 flag.bot_basewaypoint = flag.nearestwaypoint;
88 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 61', flag, wps_flagbase);
89 WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
92 void ctf_SetStatus_ForType(entity flag, float type)
94 if(flag.cnt == FLAG_CARRY)
96 if(flag.owner == self)
97 self.items |= type * 3; // carrying: self is currently carrying the flag
99 self.items |= type * 1; // taken: someone on self's team is carrying the flag
101 else if(flag.cnt == FLAG_DROPPED)
102 self.items |= type * 2; // lost: the flag is dropped somewhere on the map
105 void ctf_SetStatus() // re-write this in some less shitty way
108 float redflags, blueflags;
111 // initially clear items so they can be set as necessary later.
112 self.items &~= (IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
114 // item for stopping players from capturing the flag too often
115 if(self.ctf_captureshielded)
116 self.items |= IT_CTF_SHIELDED;
118 // figure out what flags we already own
119 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
121 if(flag.items & IT_KEY2) // blue
123 else if(flag.items & IT_KEY1) // red
127 // blinking magic: if there is more than one flag, show one of these in a clever way // wtf?
129 redflags = mod(floor(time * redflags * 0.75), redflags);
132 blueflags = mod(floor(time * blueflags * 0.75), blueflags);
134 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
136 if(flag.items & IT_KEY2) // blue
138 if(--redflags == -1) // happens exactly once (redflags is in 0..count-1, and will --'ed count times) // WHAT THE FUCK DOES THIS MEAN? whoever wrote this is shitty at explaining things.
139 ctf_SetStatus_ForType(flag, IT_RED_FLAG_TAKEN);
141 else if(flag.items & IT_KEY1) // red
143 if(--blueflags == -1) // happens exactly once
144 ctf_SetStatus_ForType(flag, IT_BLUE_FLAG_TAKEN);
152 if(self.owner.classname == "player")
153 ctf_Handle_Drop(self.owner);
155 ctf_RespawnFlag(self);
163 void ctf_Handle_Drop(entity player) // make sure this works
165 entity flag = player.flagcarried;
167 if(!flag) { return; }
168 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
171 setattachment(flag, world, "");
172 flag.owner.flagcarried = world;
174 flag.ctf_status = FLAG_DROPPED;
175 flag.movetype = MOVETYPE_TOSS;
176 flag.solid = SOLID_TRIGGER;
177 flag.takedamage = DAMAGE_YES;
178 flag.flags = FL_ITEM; // does this need set? same as above.
179 setorigin(flag, player.origin - '0 0 24' + '0 0 37'); // eh wtf is with these weird values?
180 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
181 flag.pain_finished = time + autocvar_g_ctf_flag_returntime;
183 flag.ctf_droptime = time;
184 flag.ctf_dropperid = player.playerid;
186 // messages and sounds
187 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
188 sound(flag, CHAN_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE);
189 ctf_EventLog("dropped", player.team, player);
192 PlayerScore_Add(player, SP_CTF_DROPS, 1);
193 UpdateFrags(player, -ctf_ReadScore("penalty_drop"));
196 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, (COLOR_TEAM1 + COLOR_TEAM2 - flag.team), flag, wps_flagcarrier, FALSE);
197 WaypointSprite_Ping(player.wps_flagcarrier);
198 WaypointSprite_Kill(player.wps_flagcarrier);
201 ctf_CaptureShield_Update(player, 0); // shield only
204 trace_startsolid = FALSE;
205 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
207 dprint("FLAG FALLTHROUGH will happen SOON\n");
210 void ctf_Handle_Capture(entity flag, entity player) // this too
213 float cap_time, cap_record, success;
214 string cap_message, refername;
217 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
218 cap_record = ctf_captimerecord;
219 cap_time = (time - flag.ctf_pickuptime);
221 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
222 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
224 if(ctf_captimerecord)
225 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
226 else if(cap_time < cap_record)
227 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
229 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
232 ctf_captimerecord = cap_time;
233 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
234 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
235 write_recordmarker(player, (time - cap_time), cap_time); } }
237 // messages and sounds
238 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
239 sound(player, CHAN_AUTO, flag.noise2, VOL_BASE, ATTN_NONE);
242 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
243 ctf_EventLog("capture", player.flagcarried.team, player);
244 UpdateFrags(player, ctf_ReadScore("score_capture"));
247 if (autocvar_g_ctf_flag_capture_effects)
249 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
250 shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
253 // waypointsprites // todo: improve this vvvv
254 WaypointSprite_DetachCarrier(player);
257 if(flag.speedrunning)
258 ctf_FakeTimeLimit(player, -1);
260 ctf_RespawnFlag(player.flagcarried);
261 //player.flagcarried = world;
262 //player.next_take_time = time + 1;
265 void ctf_Handle_Return(entity flag, entity player) // todo: re-write this
269 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
270 //bprint(player.netname, "^7 returned the ", flag.netname, "\n");
272 // punish the player who last had it
273 FOR_EACH_PLAYER(player)
274 if(player.playerid == flag.ctf_dropperid)
276 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
277 ctf_captureshield_update(player, 0); // shield only
280 // punish the team who was last carrying it
281 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned"));
283 // reward the player who returned it
284 if(player.playerid == flag.playerid) // is this the guy who killed the FC last?
286 if (player.team == COLOR_TEAM1 || player.team == COLOR_TEAM2)
287 UpdateFrags(player, ctf_ReadScore("score_return_by_killer"));
289 UpdateFrags(player, ctf_ReadScore("score_return_rogue_by_killer"));
293 if (player.team == COLOR_TEAM1 || player.team == COLOR_TEAM2)
294 UpdateFrags(player, ctf_ReadScore("score_return"));
296 UpdateFrags(player, ctf_ReadScore("score_return_rogue"));
298 PlayerScore_Add(player, SP_CTF_RETURNS, 1);
299 ctf_EventLog("return", flag.team, player);
300 sound(player, CHAN_AUTO, flag.noise1, VOL_BASE, ATTN_NONE);
305 void ctf_Handle_Pickup_Base(entity flag, entity player) // todo: re-write this
308 if (player.next_take_time > time)
311 if (autocvar_g_ctf_flag_pickup_effects) // pickup effect
312 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
315 flag.ctf_pickuptime = time; // used for timing runs
316 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
317 if(player.speedrunning)
318 if(ctf_captimerecord)
319 FakeTimeLimit(player, time + ctf_captimerecord);
320 flag.solid = SOLID_NOT;
321 setorigin(flag, flag.origin); // relink
323 player.flagcarried = flag;
324 flag.cnt = FLAG_CARRY;
325 flag.angles = '0 0 0';
326 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
327 UpdateFrags(player, ctf_ReadScore("score_pickup_base"));
328 flag.dropperid = player.playerid;
329 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
330 LogCTF("steal", flag.team, player);
331 sound (player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
333 FOR_EACH_PLAYER(player)
334 if(player.team == flag.team)
335 centerprint(player, "The enemy got your flag! Retrieve it!");
337 flag.movetype = MOVETYPE_NONE;
338 setorigin(flag, FLAG_CARRY_POS);
339 setattachment(flag, player, "");
340 WaypointSprite_AttachCarrier("flagcarrier", player);
341 WaypointSprite_UpdateTeamRadar(player.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 1 0');
342 WaypointSprite_Ping(flag.sprite);
346 void ctf_Handle_Pickup_Dropped(entity flag, entity player) // todo: re-write this
349 if(flag.waypointsprite_attachedforcarrier)
350 WaypointSprite_DetachCarrier(flag);
352 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
353 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
356 flag.solid = SOLID_NOT;
357 setorigin(flag, flag.origin); // relink
359 player.flagcarried = flag;
360 flag.cnt = FLAG_CARRY;
361 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
362 //bprint(player.netname, "^7 picked up the ", flag.netname, "\n");
365 f = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1);
366 //print("factor is ", ftos(f), "\n");
367 f = ctf_ReadScore("score_pickup_dropped_late") * (1-f)
368 + ctf_ReadScore("score_pickup_dropped_early") * f;
370 flag.dropperid = player.playerid;
371 //print("score is ", ftos(f), "\n");
373 UpdateFrags(player, f);
374 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
375 LogCTF("pickup", flag.team, player);
376 sound (player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
378 FOR_EACH_PLAYER(player)
379 if(player.team == flag.team)
380 centerprint(player, "The enemy got your flag! Retrieve it!");
382 flag.movetype = MOVETYPE_NONE; // flag must have MOVETYPE_NONE here, playerwise it will drop through the floor...
383 setorigin(flag, FLAG_CARRY_POS);
384 setattachment(flag, player, "");
385 flag.damageforcescale = 0;
386 flag.takedamage = DAMAGE_NO;
387 WaypointSprite_AttachCarrier("flagcarrier", player);
388 WaypointSprite_UpdateTeamRadar(player.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 1 0');
393 // ===================
394 // Main Flag Functions
395 // ===================
397 void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
400 teamnumber = fabs(teamnumber - bound(0, g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1.
403 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified
404 ctf_worldflaglist = flag;
406 setattachment(flag, world, "");
408 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
409 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
410 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
411 flag.classname = "item_flag_team";
412 flag.target = "###item###"; // wut?
413 flag.flags = FL_ITEM;
414 flag.solid = SOLID_TRIGGER;
415 flag.velocity = '0 0 0';
416 flag.ctf_status = FLAG_BASE;
417 flag.mangle = flag.angles;
418 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
420 if(flag.spawnflags & 1) // I don't understand what all this is about.
423 flag.dropped_origin = flag.origin;
424 flag.movetype = MOVETYPE_NONE;
428 flag.noalign = FALSE;
430 flag.movetype = MOVETYPE_TOSS;
433 flag.reset = ctf_Reset;
434 flag.touch = ctf_FlagTouch;
435 //flag.think = ctf_RespawnFlag;
436 //flag.nextthink = time + 0.2; // start after doors etc // Samual: 0.2 though? Why?
439 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
440 setmodel (flag, flag.model); // precision set below
441 setsize(flag, FLAG_MIN, FLAG_MAX);
442 setorigin(flag, flag.origin + '0 0 37');
443 flag.origin_z = flag.origin_z + 6; // why 6?
444 if(!flag.scale) { flag.scale = 0.6; }
446 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
448 if(autocvar_g_ctf_flag_glowtrails)
450 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
455 flag.effects |= EF_LOWPRECISION;
456 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
457 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
460 if(!flag.noise) { flag.noise = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
461 if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
462 if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
463 if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
464 if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
467 precache_sound(flag.noise);
468 precache_sound(flag.noise1);
469 precache_sound(flag.noise2);
470 precache_sound(flag.noise3);
471 precache_sound(flag.noise4);
472 precache_model(flag.model);
473 precache_model("models/ctf/shield.md3");
474 precache_model("models/ctf/shockwavetransring.md3");
476 // other initialization stuff
477 ctf_CreateBaseWaypoints(flag, teamnumber);
478 ctf_CaptureShield_Spawn(flag);
479 //InitializeEntity(flag, ctf_RespawnFlag, INITPRIO_SETLOCATION);
480 //InitializeEntity(self, ctf_CaptureShield_Spawn, INITPRIO_SETLOCATION);
483 void ctf_RespawnFlag(entity flag) // todo: re-write this
485 //if((self) && (!flag) { flag = self }
486 if(flag.classname != "item_flag_team") { backtrace("ctf_RespawnFlag was called incorrectly."); return; }
489 if(flag.owner.flagcarried == flag)
491 WaypointSprite_DetachCarrier(flag.owner);
492 flag.owner.flagcarried = world;
494 if(flag.speedrunning)
495 ctf_FakeTimeLimit(flag.owner, -1);
499 if(flag.waypointsprite_attachedforcarrier)
500 WaypointSprite_DetachCarrier(flag);
502 setattachment(flag, world, "");
503 flag.damageforcescale = 0;
504 flag.takedamage = DAMAGE_NO;
505 flag.movetype = MOVETYPE_NONE;
507 flag.movetype = MOVETYPE_TOSS;
508 flag.velocity = '0 0 0';
509 flag.solid = SOLID_TRIGGER;
510 // TODO: play a sound here
511 setorigin(flag, flag.dropped_origin);
512 flag.angles = flag.mangle;
513 flag.ctf_status = FLAG_BASE;
515 flag.flags = FL_ITEM; // clear FL_ONGROUND and any other junk // there shouldn't be any "junk" set on this... look into it and make sure it's kept clean.
518 void ctf_FlagThink() // todo: re-write this
522 self.nextthink = time + 0.1;
524 // sorry, we have to reset the flag size if it got squished by something
525 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX)
527 // if we can grow back, grow back
528 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
529 if(!trace_startsolid)
530 setsize(self, FLAG_MIN, FLAG_MAX);
533 if(self == ctf_worldflaglist) // only for the first flag
536 ctf_CaptureShield_Update(e, 1); // release shield only
539 if(self.speedrunning)
540 if(self.cnt == FLAG_CARRY)
543 if(ctf_captimerecord)
544 if(time >= self.ctf_pickuptime + ctf_captimerecord)
546 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
548 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
549 self.owner.impulse = 141; // returning!
560 if(self.cnt == FLAG_BASE)
563 if(self.cnt == FLAG_DROPPED)
565 // flag fallthrough? FIXME remove this if bug is really fixed now
566 if(self.origin_z < -131072)
568 dprint("FLAG FALLTHROUGH just happened\n");
569 self.pain_finished = 0;
571 setattachment(self, world, "");
572 if(time > self.pain_finished)
574 bprint("The ", self.netname, " has returned to base\n");
575 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
576 ctf_EventLog("returned", self.team, world);
577 ctf_RespawnFlag(self);
583 if(e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
585 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
590 if(autocvar_g_ctf_allow_drop)
592 ctf_Handle_Drop(self);
597 if(gameover) { return; }
598 if(!self) { return; }
599 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
600 { // The flag fell off the map, respawn it since players can't get to it
601 ctf_RespawnFlag(self);
604 if(other.deadflag != DEAD_NO) { return; }
605 if(other.classname != "player")
606 { // The flag just touched an object, most likely the world
607 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
608 sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
611 else if(self.wait > time) { return; }
613 switch(self.ctf_status)
616 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
617 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
618 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
619 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
623 if(other.team == self.team)
624 ctf_Handle_Return(self, other); // other just returned his own flag
625 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect)))
626 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
631 dprint("Someone touched a flag even though it was being carried? wtf?\n");
632 break; // this should never happen
637 // =======================
638 // CaptureShield Functions
639 // =======================
641 float ctf_CaptureShield_CheckStatus(entity p) // check to see
645 float players_worseeq, players_total;
647 if(ctf_captureshield_max_ratio <= 0)
650 s = PlayerScore_Add(p, SP_SCORE, 0);
651 if(s >= -ctf_captureshield_min_negscore)
654 players_total = players_worseeq = 0;
659 se = PlayerScore_Add(e, SP_SCORE, 0);
665 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
666 // use this rule here
668 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
674 void ctf_CaptureShield_Update(entity p, float dir)
677 if(dir == p.ctf_captureshielded) // 0: shield only, 1: unshield only
679 should = ctf_CaptureShield_CheckStatus(p);
682 if(should) // TODO csqc notifier for this
683 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.");
685 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.");
687 p.ctf_captureshielded = should;
692 float ctf_CaptureShield_Customize()
694 if not(other.ctf_captureshielded)
696 if(self.team == other.team)
701 void ctf_CaptureShield_Touch()
703 if not(other.ctf_captureshielded)
705 if(self.team == other.team)
709 mymid = (self.absmin + self.absmax) * 0.5;
710 othermid = (other.absmin + other.absmax) * 0.5;
711 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
712 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.");
715 void ctf_CaptureShield_Spawn(entity flag)
721 e.touch = ctf_CaptureShield_Touch;
722 e.customizeentityforclient = ctf_CaptureShield_Customize;
723 e.classname = "ctf_captureshield";
724 e.effects = EF_ADDITIVE;
725 e.movetype = MOVETYPE_NOCLIP;
726 e.solid = SOLID_TRIGGER;
727 e.avelocity = '7 0 11';
728 setorigin(e, self.origin);
729 setmodel(e, "models/ctf/shield.md3");
731 setsize(e, e.scale * e.mins, e.scale * e.maxs);
739 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
741 if(self.flagcarried) { ctf_Handle_Drop(self); } // figure this out
751 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
752 CTF Starting point for a player in team one (Red).
753 Keys: "angle" viewing angle when spawning. */
754 void spawnfunc_info_player_team1()
756 if(g_assault) { remove(self); return; }
758 self.team = COLOR_TEAM1; // red
759 spawnfunc_info_player_deathmatch();
763 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
764 CTF Starting point for a player in team two (Blue).
765 Keys: "angle" viewing angle when spawning. */
766 void spawnfunc_info_player_team2()
768 if(g_assault) { remove(self); return; }
770 self.team = COLOR_TEAM2; // blue
771 spawnfunc_info_player_deathmatch();
774 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
775 CTF Starting point for a player in team three (Yellow).
776 Keys: "angle" viewing angle when spawning. */
777 void spawnfunc_info_player_team3()
779 if(g_assault) { remove(self); return; }
781 self.team = COLOR_TEAM3; // yellow
782 spawnfunc_info_player_deathmatch();
786 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
787 CTF Starting point for a player in team four (Purple).
788 Keys: "angle" viewing angle when spawning. */
789 void spawnfunc_info_player_team4()
791 if(g_assault) { remove(self); return; }
793 self.team = COLOR_TEAM4; // purple
794 spawnfunc_info_player_deathmatch();
797 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
798 CTF flag for team one (Red). Multiple flags are allowed.
800 "angle" Angle the flag will point (minus 90 degrees)...
801 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
802 "noise" sound played when flag is picked up (default ctf/take.wav)...
803 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
804 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
805 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
806 void spawnfunc_item_flag_team1()
808 if(!g_ctf) { remove(self); return; }
810 ctf_SetupFlag(1, self);
813 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
814 CTF flag for team two (Blue). Multiple flags are allowed.
816 "angle" Angle the flag will point (minus 90 degrees)...
817 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
818 "noise" sound played when flag is picked up (default ctf/take.wav)...
819 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
820 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
821 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
822 void spawnfunc_item_flag_team2()
824 if(!g_ctf) { remove(self); return; }
826 ctf_SetupFlag(0, self);
829 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
830 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
831 Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too.
833 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
834 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
835 void spawnfunc_ctf_team()
837 if(!g_ctf) { remove(self); return; }
839 self.classname = "ctf_team";
840 self.team = self.cnt + 1;
848 // code from here on is just to support maps that don't have flag and team entities
849 void ctf_SpawnTeam (string teamname, float teamcolor)
851 local entity oldself;
854 self.classname = "ctf_team";
855 self.netname = teamname;
856 self.cnt = teamcolor;
858 spawnfunc_ctf_team();
863 void ctf_DelayedInit()
865 // if no teams are found, spawn defaults
866 if(find(world, classname, "ctf_team") == world)
868 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
869 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
873 void ctf_Initialize()
875 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
877 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
878 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
879 ctf_captureshield_force = autocvar_g_ctf_shield_force;
881 g_ctf_win_mode = cvar("g_ctf_win_mode");
885 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
889 MUTATOR_DEFINITION(gamemode_ctf)
891 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
892 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
893 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
894 //MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
895 //MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
896 //MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
897 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
901 if(time > 1) // game loads at time 1
902 error("This is a game type and it cannot be added at runtime.");
910 error("This is a game type and it cannot be removed at runtime.");