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;
22 .float ctf_dropperid; // don't allow spam of dropping the flag
24 .float ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
26 .float next_take_time; // Delay between when the person can pick up a flag // is this obsolete from the stuff above?
28 // CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
29 .float ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
30 float ctf_captureshield_min_negscore; // punish at -20 points
31 float ctf_captureshield_max_ratio; // punish at most 30% of each team
32 float ctf_captureshield_force; // push force of the shield
34 // declare functions so they can be used in any order in the file
35 void ctf_FlagTouch(void);
36 void ctf_FlagThink(void);
37 void ctf_SetupFlag(float, entity);
38 void ctf_RespawnFlag(entity);
39 float ctf_CaptureShield_CheckStatus(entity);
40 void ctf_CaptureShield_Update(entity, float);
41 float ctf_CaptureShield_Customize(void);
42 void ctf_CaptureShield_Touch(void);
43 void ctf_CaptureShield_Spawn(entity);
50 float ctf_ReadScore(string parameter) // make this obsolete
52 if(g_ctf_win_mode != 2)
53 return cvar(strcat("g_ctf_personal", parameter));
55 return cvar(strcat("g_ctf_flag", parameter));
58 void ctf_FakeTimeLimit(entity e, float t)
61 WriteByte(MSG_ONE, 3); // svc_updatestat
62 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
64 WriteCoord(MSG_ONE, autocvar_timelimit);
66 WriteCoord(MSG_ONE, (t + 1) / 60);
69 void ctf_EventLog(string mode, float flagteam, entity actor)
72 if(!autocvar_sv_eventlog)
74 s = strcat(":ctf:", mode);
75 s = strcat(s, ":", ftos(flagteam));
77 s = strcat(s, ":", ftos(actor.playerid));
81 void ctf_CreateBaseWaypoints(entity flag, float teamnumber)
84 waypoint_spawnforitem_force(flag, flag.origin);
85 flag.nearestwaypointtimeout = 0; // activate waypointing again
86 flag.bot_basewaypoint = flag.nearestwaypoint;
89 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 64', flag, wps_flagbase);
90 WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
93 void ctf_SetStatus_ForType(entity flag, float type)
95 if(flag.cnt == FLAG_CARRY)
97 if(flag.owner == self)
98 self.items |= type * 3; // carrying: self is currently carrying the flag
100 self.items |= type * 1; // taken: someone on self's team is carrying the flag
102 else if(flag.cnt == FLAG_DROPPED)
103 self.items |= type * 2; // lost: the flag is dropped somewhere on the map
106 void ctf_SetStatus() // re-write this in some less shitty way
109 float redflags, blueflags;
112 // initially clear items so they can be set as necessary later.
113 self.items &~= (IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
115 // item for stopping players from capturing the flag too often
116 if(self.ctf_captureshielded)
117 self.items |= IT_CTF_SHIELDED;
119 // figure out what flags we already own
120 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
122 if(flag.items & IT_KEY2) // blue
124 else if(flag.items & IT_KEY1) // red
128 // blinking magic: if there is more than one flag, show one of these in a clever way // wtf?
130 redflags = mod(floor(time * redflags * 0.75), redflags);
133 blueflags = mod(floor(time * blueflags * 0.75), blueflags);
135 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
137 if(flag.items & IT_KEY2) // blue
139 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.
140 ctf_SetStatus_ForType(flag, IT_RED_FLAG_TAKEN);
142 else if(flag.items & IT_KEY1) // red
144 if(--blueflags == -1) // happens exactly once
145 ctf_SetStatus_ForType(flag, IT_BLUE_FLAG_TAKEN);
153 if(self.owner.classname == "player")
154 ctf_Handle_Drop(self.owner);
156 ctf_RespawnFlag(self);
164 void ctf_Handle_Drop(entity player) // make sure this works
166 entity flag = player.flagcarried;
168 if(!flag) { return; }
169 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
172 setattachment(flag, world, "");
173 setorigin(flag, player.origin - '0 0 24' + '0 0 37');
174 flag.owner.flagcarried = world;
176 flag.movetype = MOVETYPE_TOSS;
177 flag.solid = SOLID_TRIGGER;
178 flag.takedamage = DAMAGE_YES;
179 //flag.flags = FL_ITEM; // does this need set? same as above. // 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;
185 flag.ctf_status = FLAG_DROPPED;
187 // messages and sounds
188 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
189 sound(flag, CHAN_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE);
190 ctf_EventLog("dropped", player.team, player);
193 PlayerScore_Add(player, SP_CTF_DROPS, 1);
194 UpdateFrags(player, -ctf_ReadScore("penalty_drop"));
197 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, player.team, flag, wps_flagdropped, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
198 WaypointSprite_Ping(player.wps_flagcarrier);
199 WaypointSprite_Kill(player.wps_flagcarrier);
202 ctf_CaptureShield_Update(player, 0); // shield only
205 trace_startsolid = FALSE;
206 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
208 dprint("FLAG FALLTHROUGH will happen SOON\n");
211 void ctf_Handle_Capture(entity flag, entity player) // make sure this works
214 float cap_time, cap_record, success;
215 string cap_message, refername;
218 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
219 cap_record = ctf_captimerecord;
220 cap_time = (time - player.flagcarried.ctf_pickuptime);
222 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
223 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
225 if(!ctf_captimerecord)
226 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
227 else if(cap_time < cap_record)
228 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
230 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
233 ctf_captimerecord = cap_time;
234 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
235 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
236 write_recordmarker(player, (time - cap_time), cap_time); } }
238 // messages and sounds
239 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
240 sound(player, CHAN_AUTO, flag.noise2, VOL_BASE, ATTN_NONE);
243 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
244 ctf_EventLog("capture", player.flagcarried.team, player);
245 UpdateFrags(player, ctf_ReadScore("score_capture"));
248 if (autocvar_g_ctf_flag_capture_effects)
250 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
251 shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
254 // waypointsprites // todo: improve this vvvv
255 // WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, player.team, flag, wps_flagdropped, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
256 //WaypointSprite_Ping(player.wps_flagcarrier);
257 WaypointSprite_Kill(player.wps_flagcarrier);
258 //WaypointSprite_DetachCarrier(player);
261 if(flag.speedrunning)
262 ctf_FakeTimeLimit(player, -1);
264 ctf_RespawnFlag(player.flagcarried);
265 //player.flagcarried = world;
266 //player.next_take_time = time + 1;
269 void ctf_Handle_Return(entity flag, entity player) // todo: re-write this
273 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
274 //bprint(player.netname, "^7 returned the ", flag.netname, "\n");
276 // punish the player who last had it
277 FOR_EACH_PLAYER(player)
278 if(player.playerid == flag.ctf_dropperid)
280 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
281 ctf_captureshield_update(player, 0); // shield only
284 // punish the team who was last carrying it
285 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned"));
287 // reward the player who returned it
288 if(player.playerid == flag.playerid) // is this the guy who killed the FC last?
290 if (player.team == COLOR_TEAM1 || player.team == COLOR_TEAM2)
291 UpdateFrags(player, ctf_ReadScore("score_return_by_killer"));
293 UpdateFrags(player, ctf_ReadScore("score_return_rogue_by_killer"));
297 if (player.team == COLOR_TEAM1 || player.team == COLOR_TEAM2)
298 UpdateFrags(player, ctf_ReadScore("score_return"));
300 UpdateFrags(player, ctf_ReadScore("score_return_rogue"));
302 PlayerScore_Add(player, SP_CTF_RETURNS, 1);
303 ctf_EventLog("return", flag.team, player);
304 sound(player, CHAN_AUTO, flag.noise1, VOL_BASE, ATTN_NONE);
309 void ctf_Handle_Pickup_Base(entity flag, entity player) // todo: re-write this
312 if (player.next_take_time > time)
315 if (autocvar_g_ctf_flag_pickup_effects) // pickup effect
316 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
318 // attach the flag to the player
320 player.flagcarried = flag;
321 setattachment(flag, player, "");
322 setorigin(flag, FLAG_CARRY_POS);
325 flag.movetype = MOVETYPE_NONE;
326 flag.solid = SOLID_NOT;
327 flag.angles = '0 0 0';
328 flag.ctf_pickuptime = time; // used for timing runs
329 flag.ctf_pickupid = player.playerid;
330 flag.ctf_status = FLAG_CARRY;
332 // messages and sounds
333 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
334 sound(player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
335 FOR_EACH_PLAYER(tmp_player)
336 if(tmp_player.team == flag.team)
337 centerprint(tmp_player, "The enemy got your flag! Retrieve it!");
340 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
341 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
342 ctf_EventLog("steal", flag.team, player);
345 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
346 if((player.speedrunning) && (ctf_captimerecord))
347 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
350 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
351 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
352 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
353 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
354 WaypointSprite_Ping(player.wps_flagcarrier);
358 void ctf_Handle_Pickup_Dropped(entity flag, entity player) // todo: re-write this
361 if(flag.waypointsprite_attachedforcarrier)
362 WaypointSprite_DetachCarrier(flag);
364 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
365 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
368 flag.solid = SOLID_NOT;
369 setorigin(flag, flag.origin); // relink
371 player.flagcarried = flag;
372 flag.cnt = FLAG_CARRY;
373 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
374 //bprint(player.netname, "^7 picked up the ", flag.netname, "\n");
377 f = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1);
378 //print("factor is ", ftos(f), "\n");
379 f = ctf_ReadScore("score_pickup_dropped_late") * (1-f)
380 + ctf_ReadScore("score_pickup_dropped_early") * f;
382 flag.dropperid = player.playerid;
383 //print("score is ", ftos(f), "\n");
385 UpdateFrags(player, f);
386 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
387 LogCTF("pickup", flag.team, player);
388 sound (player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
390 FOR_EACH_PLAYER(player)
391 if(player.team == flag.team)
392 centerprint(player, "The enemy got your flag! Retrieve it!");
394 flag.movetype = MOVETYPE_NONE; // flag must have MOVETYPE_NONE here, playerwise it will drop through the floor...
395 setorigin(flag, FLAG_CARRY_POS);
396 setattachment(flag, player, "");
397 flag.damageforcescale = 0;
398 flag.takedamage = DAMAGE_NO;
399 WaypointSprite_AttachCarrier("flagcarrier", player);
400 WaypointSprite_UpdateTeamRadar(player.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 1 0');
405 // ===================
406 // Main Flag Functions
407 // ===================
409 void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
412 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.
415 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified
416 ctf_worldflaglist = flag;
418 setattachment(flag, world, "");
420 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
421 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
422 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
423 flag.classname = "item_flag_team";
424 flag.target = "###item###"; // wut?
425 flag.flags = FL_ITEM;
426 flag.solid = SOLID_TRIGGER;
427 flag.velocity = '0 0 0';
428 flag.ctf_status = FLAG_BASE;
429 flag.mangle = flag.angles;
430 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
432 if(flag.spawnflags & 1) // I don't understand what all this is about.
435 flag.dropped_origin = flag.origin;
436 flag.movetype = MOVETYPE_NONE;
440 flag.noalign = FALSE;
442 flag.movetype = MOVETYPE_TOSS;
445 flag.reset = ctf_Reset;
446 flag.touch = ctf_FlagTouch;
447 //flag.think = ctf_RespawnFlag;
448 //flag.nextthink = time + 0.2; // start after doors etc // Samual: 0.2 though? Why?
451 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
452 setmodel (flag, flag.model); // precision set below
453 setsize(flag, FLAG_MIN, FLAG_MAX);
454 setorigin(flag, flag.origin);// + '0 0 37');
455 //flag.origin_z = flag.origin_z + 6; // why 6?
456 if(!flag.scale) { flag.scale = 0.6; }
458 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
460 if(autocvar_g_ctf_flag_glowtrails)
462 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
467 flag.effects |= EF_LOWPRECISION;
468 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
469 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
472 if(!flag.noise) { flag.noise = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
473 if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
474 if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
475 if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
476 if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
477 //if(!flag.noise5) { flag.noise5 = "ctf/flag_touch.wav"; }
480 precache_sound(flag.noise);
481 precache_sound(flag.noise1);
482 precache_sound(flag.noise2);
483 precache_sound(flag.noise3);
484 precache_sound(flag.noise4);
485 //precache_sound(flag.noise5);
486 precache_model(flag.model);
487 precache_model("models/ctf/shield.md3");
488 precache_model("models/ctf/shockwavetransring.md3");
490 // other initialization stuff
491 ctf_CreateBaseWaypoints(flag, teamnumber);
492 ctf_CaptureShield_Spawn(flag);
493 //InitializeEntity(flag, ctf_RespawnFlag, INITPRIO_SETLOCATION);
494 //InitializeEntity(self, ctf_CaptureShield_Spawn, INITPRIO_SETLOCATION);
497 void ctf_RespawnFlag(entity flag) // todo: re-write this
499 //if((self) && (!flag) { flag = self }
500 if(flag.classname != "item_flag_team") { backtrace("ctf_RespawnFlag was called incorrectly."); return; }
503 if(flag.owner.flagcarried == flag)
505 WaypointSprite_DetachCarrier(flag.owner);
506 flag.owner.flagcarried = world;
508 if(flag.speedrunning)
509 ctf_FakeTimeLimit(flag.owner, -1);
513 if(flag.waypointsprite_attachedforcarrier)
514 WaypointSprite_DetachCarrier(flag);
516 setattachment(flag, world, "");
517 flag.damageforcescale = 0;
518 flag.takedamage = DAMAGE_NO;
519 flag.movetype = MOVETYPE_NONE;
521 flag.movetype = MOVETYPE_TOSS;
522 flag.velocity = '0 0 0';
523 flag.solid = SOLID_TRIGGER;
524 // TODO: play a sound here
525 setorigin(flag, flag.dropped_origin);
526 flag.angles = flag.mangle;
527 flag.ctf_status = FLAG_BASE;
529 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.
532 void ctf_FlagThink() // todo: re-write this
536 self.nextthink = time + 0.1;
538 // sorry, we have to reset the flag size if it got squished by something
539 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX)
541 // if we can grow back, grow back
542 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
543 if(!trace_startsolid)
544 setsize(self, FLAG_MIN, FLAG_MAX);
547 if(self == ctf_worldflaglist) // only for the first flag
550 ctf_CaptureShield_Update(e, 1); // release shield only
553 if(self.speedrunning)
554 if(self.cnt == FLAG_CARRY)
557 if(ctf_captimerecord)
558 if(time >= self.ctf_pickuptime + ctf_captimerecord)
560 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
562 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
563 self.owner.impulse = 141; // returning!
574 if(self.cnt == FLAG_BASE)
577 if(self.cnt == FLAG_DROPPED)
579 // flag fallthrough? FIXME remove this if bug is really fixed now
580 if(self.origin_z < -131072)
582 dprint("FLAG FALLTHROUGH just happened\n");
583 self.pain_finished = 0;
585 setattachment(self, world, "");
586 if(time > self.pain_finished)
588 bprint("The ", self.netname, " has returned to base\n");
589 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
590 ctf_EventLog("returned", self.team, world);
591 ctf_RespawnFlag(self);
597 if(e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
599 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
604 if(autocvar_g_ctf_allow_drop)
606 ctf_Handle_Drop(self);
611 if(gameover) { return; }
612 if(!self) { return; }
613 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
614 { // The flag fell off the map, respawn it since players can't get to it
615 ctf_RespawnFlag(self);
618 if(other.deadflag != DEAD_NO) { return; }
619 if(other.classname != "player")
620 { // The flag just touched an object, most likely the world
621 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
622 sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
625 else if(self.wait > time) { return; }
627 switch(self.ctf_status)
630 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
631 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
632 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
633 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
637 if(other.team == self.team)
638 ctf_Handle_Return(self, other); // other just returned his own flag
639 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect)))
640 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
645 dprint("Someone touched a flag even though it was being carried? wtf?\n");
646 break; // this should never happen
651 // =======================
652 // CaptureShield Functions
653 // =======================
655 float ctf_CaptureShield_CheckStatus(entity p) // check to see
659 float players_worseeq, players_total;
661 if(ctf_captureshield_max_ratio <= 0)
664 s = PlayerScore_Add(p, SP_SCORE, 0);
665 if(s >= -ctf_captureshield_min_negscore)
668 players_total = players_worseeq = 0;
673 se = PlayerScore_Add(e, SP_SCORE, 0);
679 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
680 // use this rule here
682 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
688 void ctf_CaptureShield_Update(entity p, float dir)
691 if(dir == p.ctf_captureshielded) // 0: shield only, 1: unshield only
693 should = ctf_CaptureShield_CheckStatus(p);
696 if(should) // TODO csqc notifier for this
697 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.");
699 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.");
701 p.ctf_captureshielded = should;
706 float ctf_CaptureShield_Customize()
708 if not(other.ctf_captureshielded)
710 if(self.team == other.team)
715 void ctf_CaptureShield_Touch()
717 if not(other.ctf_captureshielded)
719 if(self.team == other.team)
723 mymid = (self.absmin + self.absmax) * 0.5;
724 othermid = (other.absmin + other.absmax) * 0.5;
725 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
726 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.");
729 void ctf_CaptureShield_Spawn(entity flag)
735 e.touch = ctf_CaptureShield_Touch;
736 e.customizeentityforclient = ctf_CaptureShield_Customize;
737 e.classname = "ctf_captureshield";
738 e.effects = EF_ADDITIVE;
739 e.movetype = MOVETYPE_NOCLIP;
740 e.solid = SOLID_TRIGGER;
741 e.avelocity = '7 0 11';
742 setorigin(e, self.origin);
743 setmodel(e, "models/ctf/shield.md3");
745 setsize(e, e.scale * e.mins, e.scale * e.maxs);
753 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
755 if(self.flagcarried) { ctf_Handle_Drop(self); } // figure this out
765 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
766 CTF Starting point for a player in team one (Red).
767 Keys: "angle" viewing angle when spawning. */
768 void spawnfunc_info_player_team1()
770 if(g_assault) { remove(self); return; }
772 self.team = COLOR_TEAM1; // red
773 spawnfunc_info_player_deathmatch();
777 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
778 CTF Starting point for a player in team two (Blue).
779 Keys: "angle" viewing angle when spawning. */
780 void spawnfunc_info_player_team2()
782 if(g_assault) { remove(self); return; }
784 self.team = COLOR_TEAM2; // blue
785 spawnfunc_info_player_deathmatch();
788 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
789 CTF Starting point for a player in team three (Yellow).
790 Keys: "angle" viewing angle when spawning. */
791 void spawnfunc_info_player_team3()
793 if(g_assault) { remove(self); return; }
795 self.team = COLOR_TEAM3; // yellow
796 spawnfunc_info_player_deathmatch();
800 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
801 CTF Starting point for a player in team four (Purple).
802 Keys: "angle" viewing angle when spawning. */
803 void spawnfunc_info_player_team4()
805 if(g_assault) { remove(self); return; }
807 self.team = COLOR_TEAM4; // purple
808 spawnfunc_info_player_deathmatch();
811 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
812 CTF flag for team one (Red). Multiple flags are allowed.
814 "angle" Angle the flag will point (minus 90 degrees)...
815 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
816 "noise" sound played when flag is picked up (default ctf/take.wav)...
817 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
818 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
819 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
820 void spawnfunc_item_flag_team1()
822 if(!g_ctf) { remove(self); return; }
824 ctf_SetupFlag(1, self);
827 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
828 CTF flag for team two (Blue). Multiple flags are allowed.
830 "angle" Angle the flag will point (minus 90 degrees)...
831 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
832 "noise" sound played when flag is picked up (default ctf/take.wav)...
833 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
834 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
835 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
836 void spawnfunc_item_flag_team2()
838 if(!g_ctf) { remove(self); return; }
840 ctf_SetupFlag(0, self);
843 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
844 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
845 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.
847 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
848 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
849 void spawnfunc_ctf_team()
851 if(!g_ctf) { remove(self); return; }
853 self.classname = "ctf_team";
854 self.team = self.cnt + 1;
862 // code from here on is just to support maps that don't have flag and team entities
863 void ctf_SpawnTeam (string teamname, float teamcolor)
865 local entity oldself;
868 self.classname = "ctf_team";
869 self.netname = teamname;
870 self.cnt = teamcolor;
872 spawnfunc_ctf_team();
877 void ctf_DelayedInit()
879 // if no teams are found, spawn defaults
880 if(find(world, classname, "ctf_team") == world)
882 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
883 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
887 void ctf_Initialize()
889 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
891 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
892 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
893 ctf_captureshield_force = autocvar_g_ctf_shield_force;
895 g_ctf_win_mode = cvar("g_ctf_win_mode");
899 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
903 MUTATOR_DEFINITION(gamemode_ctf)
905 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
906 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
907 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
908 //MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
909 //MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
910 //MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
911 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
915 if(time > 1) // game loads at time 1
916 error("This is a game type and it cannot be added at runtime.");
924 error("This is a game type and it cannot be removed at runtime.");