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; // replace this later
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
307 if (player.next_take_time > time)
310 if (autocvar_g_ctf_flag_pickup_effects) // pickup effect
311 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
314 flag.ctf_pickuptime = time; // used for timing runs
315 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
316 if(player.speedrunning)
317 if(ctf_captimerecord)
318 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
319 flag.solid = SOLID_NOT;
320 setorigin(flag, flag.origin); // relink
322 player.flagcarried = flag;
323 flag.ctf_status = FLAG_CARRY;
324 flag.angles = '0 0 0';
325 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
326 UpdateFrags(player, ctf_ReadScore("score_pickup_base"));
327 flag.ctf_dropperid = player.playerid;
328 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
329 ctf_EventLog("steal", flag.team, player);
330 sound (player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
332 FOR_EACH_PLAYER(player)
333 if(player.team == flag.team)
334 centerprint(player, "The enemy got your flag! Retrieve it!");
336 flag.movetype = MOVETYPE_NONE;
337 setorigin(flag, FLAG_CARRY_POS);
338 setattachment(flag, player, "");
339 WaypointSprite_AttachCarrier("flagcarrier", player);
340 WaypointSprite_UpdateTeamRadar(player.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 1 0');
341 WaypointSprite_Ping(flag.sprite);
345 void ctf_Handle_Pickup_Dropped(entity flag, entity player) // todo: re-write this
348 if(flag.waypointsprite_attachedforcarrier)
349 WaypointSprite_DetachCarrier(flag);
351 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
352 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
355 flag.solid = SOLID_NOT;
356 setorigin(flag, flag.origin); // relink
358 player.flagcarried = flag;
359 flag.cnt = FLAG_CARRY;
360 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
361 //bprint(player.netname, "^7 picked up the ", flag.netname, "\n");
364 f = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1);
365 //print("factor is ", ftos(f), "\n");
366 f = ctf_ReadScore("score_pickup_dropped_late") * (1-f)
367 + ctf_ReadScore("score_pickup_dropped_early") * f;
369 flag.dropperid = player.playerid;
370 //print("score is ", ftos(f), "\n");
372 UpdateFrags(player, f);
373 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
374 LogCTF("pickup", flag.team, player);
375 sound (player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
377 FOR_EACH_PLAYER(player)
378 if(player.team == flag.team)
379 centerprint(player, "The enemy got your flag! Retrieve it!");
381 flag.movetype = MOVETYPE_NONE; // flag must have MOVETYPE_NONE here, playerwise it will drop through the floor...
382 setorigin(flag, FLAG_CARRY_POS);
383 setattachment(flag, player, "");
384 flag.damageforcescale = 0;
385 flag.takedamage = DAMAGE_NO;
386 WaypointSprite_AttachCarrier("flagcarrier", player);
387 WaypointSprite_UpdateTeamRadar(player.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 1 0');
392 // ===================
393 // Main Flag Functions
394 // ===================
396 void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
399 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.
402 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified
403 ctf_worldflaglist = flag;
405 setattachment(flag, world, "");
407 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
408 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
409 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
410 flag.classname = "item_flag_team";
411 flag.target = "###item###"; // wut?
412 flag.flags = FL_ITEM;
413 flag.solid = SOLID_TRIGGER;
414 flag.velocity = '0 0 0';
415 flag.ctf_status = FLAG_BASE;
416 flag.mangle = flag.angles;
417 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
419 if(flag.spawnflags & 1) // I don't understand what all this is about.
422 flag.dropped_origin = flag.origin;
423 flag.movetype = MOVETYPE_NONE;
427 flag.noalign = FALSE;
429 flag.movetype = MOVETYPE_TOSS;
432 flag.reset = ctf_Reset;
433 flag.touch = ctf_FlagTouch;
434 //flag.think = ctf_RespawnFlag;
435 //flag.nextthink = time + 0.2; // start after doors etc // Samual: 0.2 though? Why?
438 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
439 setmodel (flag, flag.model); // precision set below
440 setsize(flag, FLAG_MIN, FLAG_MAX);
441 setorigin(flag, flag.origin);// + '0 0 37');
442 //flag.origin_z = flag.origin_z + 6; // why 6?
443 if(!flag.scale) { flag.scale = 0.6; }
445 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
447 if(autocvar_g_ctf_flag_glowtrails)
449 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
454 flag.effects |= EF_LOWPRECISION;
455 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
456 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
459 if(!flag.noise) { flag.noise = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
460 if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
461 if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
462 if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
463 if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
466 precache_sound(flag.noise);
467 precache_sound(flag.noise1);
468 precache_sound(flag.noise2);
469 precache_sound(flag.noise3);
470 precache_sound(flag.noise4);
471 precache_model(flag.model);
472 precache_model("models/ctf/shield.md3");
473 precache_model("models/ctf/shockwavetransring.md3");
475 // other initialization stuff
476 ctf_CreateBaseWaypoints(flag, teamnumber);
477 ctf_CaptureShield_Spawn(flag);
478 //InitializeEntity(flag, ctf_RespawnFlag, INITPRIO_SETLOCATION);
479 //InitializeEntity(self, ctf_CaptureShield_Spawn, INITPRIO_SETLOCATION);
482 void ctf_RespawnFlag(entity flag) // todo: re-write this
484 //if((self) && (!flag) { flag = self }
485 if(flag.classname != "item_flag_team") { backtrace("ctf_RespawnFlag was called incorrectly."); return; }
488 if(flag.owner.flagcarried == flag)
490 WaypointSprite_DetachCarrier(flag.owner);
491 flag.owner.flagcarried = world;
493 if(flag.speedrunning)
494 ctf_FakeTimeLimit(flag.owner, -1);
498 if(flag.waypointsprite_attachedforcarrier)
499 WaypointSprite_DetachCarrier(flag);
501 setattachment(flag, world, "");
502 flag.damageforcescale = 0;
503 flag.takedamage = DAMAGE_NO;
504 flag.movetype = MOVETYPE_NONE;
506 flag.movetype = MOVETYPE_TOSS;
507 flag.velocity = '0 0 0';
508 flag.solid = SOLID_TRIGGER;
509 // TODO: play a sound here
510 setorigin(flag, flag.dropped_origin);
511 flag.angles = flag.mangle;
512 flag.ctf_status = FLAG_BASE;
514 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.
517 void ctf_FlagThink() // todo: re-write this
521 self.nextthink = time + 0.1;
523 // sorry, we have to reset the flag size if it got squished by something
524 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX)
526 // if we can grow back, grow back
527 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
528 if(!trace_startsolid)
529 setsize(self, FLAG_MIN, FLAG_MAX);
532 if(self == ctf_worldflaglist) // only for the first flag
535 ctf_CaptureShield_Update(e, 1); // release shield only
538 if(self.speedrunning)
539 if(self.cnt == FLAG_CARRY)
542 if(ctf_captimerecord)
543 if(time >= self.ctf_pickuptime + ctf_captimerecord)
545 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
547 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
548 self.owner.impulse = 141; // returning!
559 if(self.cnt == FLAG_BASE)
562 if(self.cnt == FLAG_DROPPED)
564 // flag fallthrough? FIXME remove this if bug is really fixed now
565 if(self.origin_z < -131072)
567 dprint("FLAG FALLTHROUGH just happened\n");
568 self.pain_finished = 0;
570 setattachment(self, world, "");
571 if(time > self.pain_finished)
573 bprint("The ", self.netname, " has returned to base\n");
574 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
575 ctf_EventLog("returned", self.team, world);
576 ctf_RespawnFlag(self);
582 if(e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
584 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
589 if(autocvar_g_ctf_allow_drop)
591 ctf_Handle_Drop(self);
596 if(gameover) { return; }
597 if(!self) { return; }
598 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
599 { // The flag fell off the map, respawn it since players can't get to it
600 ctf_RespawnFlag(self);
603 if(other.deadflag != DEAD_NO) { return; }
604 if(other.classname != "player")
605 { // The flag just touched an object, most likely the world
606 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
607 sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
610 else if(self.wait > time) { return; }
612 switch(self.ctf_status)
615 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
616 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
617 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
618 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
622 if(other.team == self.team)
623 ctf_Handle_Return(self, other); // other just returned his own flag
624 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect)))
625 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
630 dprint("Someone touched a flag even though it was being carried? wtf?\n");
631 break; // this should never happen
636 // =======================
637 // CaptureShield Functions
638 // =======================
640 float ctf_CaptureShield_CheckStatus(entity p) // check to see
644 float players_worseeq, players_total;
646 if(ctf_captureshield_max_ratio <= 0)
649 s = PlayerScore_Add(p, SP_SCORE, 0);
650 if(s >= -ctf_captureshield_min_negscore)
653 players_total = players_worseeq = 0;
658 se = PlayerScore_Add(e, SP_SCORE, 0);
664 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
665 // use this rule here
667 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
673 void ctf_CaptureShield_Update(entity p, float dir)
676 if(dir == p.ctf_captureshielded) // 0: shield only, 1: unshield only
678 should = ctf_CaptureShield_CheckStatus(p);
681 if(should) // TODO csqc notifier for this
682 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.");
684 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.");
686 p.ctf_captureshielded = should;
691 float ctf_CaptureShield_Customize()
693 if not(other.ctf_captureshielded)
695 if(self.team == other.team)
700 void ctf_CaptureShield_Touch()
702 if not(other.ctf_captureshielded)
704 if(self.team == other.team)
708 mymid = (self.absmin + self.absmax) * 0.5;
709 othermid = (other.absmin + other.absmax) * 0.5;
710 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
711 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.");
714 void ctf_CaptureShield_Spawn(entity flag)
720 e.touch = ctf_CaptureShield_Touch;
721 e.customizeentityforclient = ctf_CaptureShield_Customize;
722 e.classname = "ctf_captureshield";
723 e.effects = EF_ADDITIVE;
724 e.movetype = MOVETYPE_NOCLIP;
725 e.solid = SOLID_TRIGGER;
726 e.avelocity = '7 0 11';
727 setorigin(e, self.origin);
728 setmodel(e, "models/ctf/shield.md3");
730 setsize(e, e.scale * e.mins, e.scale * e.maxs);
738 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
740 if(self.flagcarried) { ctf_Handle_Drop(self); } // figure this out
750 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
751 CTF Starting point for a player in team one (Red).
752 Keys: "angle" viewing angle when spawning. */
753 void spawnfunc_info_player_team1()
755 if(g_assault) { remove(self); return; }
757 self.team = COLOR_TEAM1; // red
758 spawnfunc_info_player_deathmatch();
762 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
763 CTF Starting point for a player in team two (Blue).
764 Keys: "angle" viewing angle when spawning. */
765 void spawnfunc_info_player_team2()
767 if(g_assault) { remove(self); return; }
769 self.team = COLOR_TEAM2; // blue
770 spawnfunc_info_player_deathmatch();
773 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
774 CTF Starting point for a player in team three (Yellow).
775 Keys: "angle" viewing angle when spawning. */
776 void spawnfunc_info_player_team3()
778 if(g_assault) { remove(self); return; }
780 self.team = COLOR_TEAM3; // yellow
781 spawnfunc_info_player_deathmatch();
785 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
786 CTF Starting point for a player in team four (Purple).
787 Keys: "angle" viewing angle when spawning. */
788 void spawnfunc_info_player_team4()
790 if(g_assault) { remove(self); return; }
792 self.team = COLOR_TEAM4; // purple
793 spawnfunc_info_player_deathmatch();
796 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
797 CTF flag for team one (Red). Multiple flags are allowed.
799 "angle" Angle the flag will point (minus 90 degrees)...
800 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
801 "noise" sound played when flag is picked up (default ctf/take.wav)...
802 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
803 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
804 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
805 void spawnfunc_item_flag_team1()
807 if(!g_ctf) { remove(self); return; }
809 ctf_SetupFlag(1, self);
812 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
813 CTF flag for team two (Blue). Multiple flags are allowed.
815 "angle" Angle the flag will point (minus 90 degrees)...
816 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
817 "noise" sound played when flag is picked up (default ctf/take.wav)...
818 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
819 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
820 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
821 void spawnfunc_item_flag_team2()
823 if(!g_ctf) { remove(self); return; }
825 ctf_SetupFlag(0, self);
828 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
829 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
830 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.
832 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
833 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
834 void spawnfunc_ctf_team()
836 if(!g_ctf) { remove(self); return; }
838 self.classname = "ctf_team";
839 self.team = self.cnt + 1;
847 // code from here on is just to support maps that don't have flag and team entities
848 void ctf_SpawnTeam (string teamname, float teamcolor)
850 local entity oldself;
853 self.classname = "ctf_team";
854 self.netname = teamname;
855 self.cnt = teamcolor;
857 spawnfunc_ctf_team();
862 void ctf_DelayedInit()
864 // if no teams are found, spawn defaults
865 if(find(world, classname, "ctf_team") == world)
867 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
868 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
872 void ctf_Initialize()
874 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
876 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
877 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
878 ctf_captureshield_force = autocvar_g_ctf_shield_force;
880 g_ctf_win_mode = cvar("g_ctf_win_mode");
884 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
888 MUTATOR_DEFINITION(gamemode_ctf)
890 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
891 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
892 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
893 //MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
894 //MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
895 //MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
896 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
900 if(time > 1) // game loads at time 1
901 error("This is a game type and it cannot be added at runtime.");
909 error("This is a game type and it cannot be removed at runtime.");