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) // use an alias for easy changing and quick editing later
71 if(autocvar_sv_eventlog)
72 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (":", ftos(actor.playerid)) : "")));
75 void ctf_CreateBaseWaypoints(entity flag, float teamnumber)
78 waypoint_spawnforitem_force(flag, flag.origin);
79 flag.nearestwaypointtimeout = 0; // activate waypointing again
80 flag.bot_basewaypoint = flag.nearestwaypoint;
83 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 64', flag, wps_flagbase);
84 WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
92 void ctf_Handle_Drop(entity player) // make sure this works
94 entity flag = player.flagcarried;
97 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
100 setattachment(flag, world, "");
101 setorigin(flag, player.origin - '0 0 24' + '0 0 37');
102 flag.owner.flagcarried = world;
104 flag.movetype = MOVETYPE_TOSS;
105 flag.solid = SOLID_TRIGGER;
106 flag.takedamage = DAMAGE_YES;
107 //flag.flags = FL_ITEM; // does this need set? same as above. // eh wtf is with these weird values?
108 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
109 flag.pain_finished = time + autocvar_g_ctf_flag_returntime; // replace this later
111 flag.ctf_droptime = time;
112 flag.ctf_dropperid = player.playerid;
113 flag.ctf_status = FLAG_DROPPED;
115 // messages and sounds
116 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
117 sound(flag, CHAN_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE);
118 ctf_EventLog("dropped", player.team, player);
121 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
122 PlayerScore_Add(player, SP_CTF_DROPS, 1);
125 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, player.team, flag, wps_flagdropped, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
126 WaypointSprite_Ping(player.wps_flagcarrier);
127 WaypointSprite_Kill(player.wps_flagcarrier);
130 ctf_CaptureShield_Update(player, 0); // shield only
133 trace_startsolid = FALSE;
134 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
136 dprint("FLAG FALLTHROUGH will happen SOON\n");
139 void ctf_Handle_Capture(entity flag, entity player) // make sure this works
142 float cap_time, cap_record, success;
143 string cap_message, refername;
146 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
147 cap_record = ctf_captimerecord;
148 cap_time = (time - player.flagcarried.ctf_pickuptime);
150 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
151 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
153 if(!ctf_captimerecord)
154 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
155 else if(cap_time < cap_record)
156 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
158 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
161 ctf_captimerecord = cap_time;
162 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
163 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
164 write_recordmarker(player, (time - cap_time), cap_time); } }
166 // messages and sounds
167 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
168 sound(player, CHAN_AUTO, flag.noise2, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav"
169 ctf_EventLog("capture", player.flagcarried.team, player);
172 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
173 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
176 if (autocvar_g_ctf_flag_capture_effects)
178 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
179 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
183 WaypointSprite_Kill(player.wps_flagcarrier);
186 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
188 ctf_RespawnFlag(player.flagcarried);
191 void ctf_Handle_Return(entity flag, entity player) // make sure this works
193 // messages and sounds
194 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
195 sound(player, CHAN_AUTO, flag.noise1, VOL_BASE, ATTN_NONE);
196 ctf_EventLog("return", flag.team, player);
199 PlayerTeamScore_AddScore(player, ctf_ReadScore(strcat("score_return", ((player.playerid == flag.playerid) ? "_by_killer" : "")))); // reward for return
200 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
202 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
203 FOR_EACH_PLAYER(player) if(player.playerid == flag.ctf_dropperid) // punish the player who dropped the flag
205 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
206 ctf_CaptureShield_Update(player, 0); // shield only
210 WaypointSprite_Kill(player.wps_flagdropped);
213 ctf_RespawnFlag(flag);
216 void ctf_Handle_Pickup_Base(entity flag, entity player) // make sure this works
218 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
219 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
221 // attach the flag to the player
223 player.flagcarried = flag;
224 setattachment(flag, player, "");
225 setorigin(flag, FLAG_CARRY_POS);
228 flag.movetype = MOVETYPE_NONE;
229 flag.solid = SOLID_NOT;
230 flag.angles = '0 0 0';
231 flag.ctf_pickuptime = time; // used for timing runs
232 flag.ctf_pickupid = player.playerid;
233 flag.ctf_status = FLAG_CARRY;
235 // messages and sounds
236 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
237 sound(player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
238 ctf_EventLog("steal", flag.team, player);
239 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : ""); // replace TRUE with an autocvar for it.
240 FOR_EACH_PLAYER(tmp_player)
241 if(tmp_player.team == flag.team)
242 centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!"));
243 else if((tmp_player.team == player.team) && (tmp_player != player))
244 centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!"));
247 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
248 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
251 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
252 if((player.speedrunning) && (ctf_captimerecord))
253 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
256 if (autocvar_g_ctf_flag_pickup_effects)
258 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
262 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
263 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
264 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
265 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
266 WaypointSprite_Ping(player.wps_flagcarrier);
269 void ctf_Handle_Pickup_Dropped(entity flag, entity player) // todo: re-write this
272 if(flag.waypointsprite_attachedforcarrier)
273 WaypointSprite_DetachCarrier(flag);
275 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
276 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
279 flag.solid = SOLID_NOT;
280 setorigin(flag, flag.origin); // relink
282 player.flagcarried = flag;
283 flag.cnt = FLAG_CARRY;
284 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
285 //bprint(player.netname, "^7 picked up the ", flag.netname, "\n");
288 f = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1);
289 //print("factor is ", ftos(f), "\n");
290 f = ctf_ReadScore("score_pickup_dropped_late") * (1-f)
291 + ctf_ReadScore("score_pickup_dropped_early") * f;
293 flag.dropperid = player.playerid;
294 //print("score is ", ftos(f), "\n");
296 UpdateFrags(player, f);
297 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
298 LogCTF("pickup", flag.team, player);
299 sound (player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
301 FOR_EACH_PLAYER(player)
302 if(player.team == flag.team)
303 centerprint(player, "The enemy got your flag! Retrieve it!");
305 flag.movetype = MOVETYPE_NONE; // flag must have MOVETYPE_NONE here, playerwise it will drop through the floor...
306 setorigin(flag, FLAG_CARRY_POS);
307 setattachment(flag, player, "");
308 flag.damageforcescale = 0;
309 flag.takedamage = DAMAGE_NO;
310 WaypointSprite_AttachCarrier("flagcarrier", player);
311 WaypointSprite_UpdateTeamRadar(player.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 1 0');
316 // ===================
317 // Main Flag Functions
318 // ===================
323 if(self.owner.classname == "player")
324 ctf_Handle_Drop(self.owner);
326 ctf_RespawnFlag(self);
329 void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
332 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.
335 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified
336 ctf_worldflaglist = flag;
338 setattachment(flag, world, "");
340 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
341 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
342 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
343 flag.classname = "item_flag_team";
344 flag.target = "###item###"; // wut?
345 flag.flags = FL_ITEM;
346 flag.solid = SOLID_TRIGGER;
347 flag.velocity = '0 0 0';
348 flag.ctf_status = FLAG_BASE;
349 flag.mangle = flag.angles;
350 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
352 if(flag.spawnflags & 1) // I don't understand what all this is about.
355 flag.dropped_origin = flag.origin;
356 flag.movetype = MOVETYPE_NONE;
357 print("This map was loaded with flags using MOVETYPE_NONE\n");
361 flag.noalign = FALSE;
363 flag.movetype = MOVETYPE_TOSS;
364 print("This map was loaded with flags using MOVETYPE_TOSS\n");
367 flag.reset = ctf_Reset;
368 flag.touch = ctf_FlagTouch;
371 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
372 setmodel (flag, flag.model); // precision set below
373 setsize(flag, FLAG_MIN, FLAG_MAX);
374 setorigin(flag, flag.origin);
375 if(!flag.scale) { flag.scale = 0.6; }
377 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
379 if(autocvar_g_ctf_flag_glowtrails)
381 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
386 flag.effects |= EF_LOWPRECISION;
387 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
388 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
391 if(!flag.noise) { flag.noise = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
392 if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
393 if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
394 if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
395 if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
396 //if(!flag.noise5) { flag.noise5 = "ctf/flag_touch.wav"; }
399 precache_sound(flag.noise);
400 precache_sound(flag.noise1);
401 precache_sound(flag.noise2);
402 precache_sound(flag.noise3);
403 precache_sound(flag.noise4);
404 //precache_sound(flag.noise5);
405 precache_model(flag.model);
406 precache_model("models/ctf/shield.md3");
407 precache_model("models/ctf/shockwavetransring.md3");
410 waypoint_spawnforitem_force(flag, flag.origin);
411 flag.nearestwaypointtimeout = 0; // activate waypointing again
412 flag.bot_basewaypoint = flag.nearestwaypoint;
415 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 64', flag, wps_flagbase);
416 WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
418 // captureshield setup
419 ctf_CaptureShield_Spawn(flag);
422 void ctf_RespawnFlag(entity flag) // todo: re-write this
424 //if((self) && (!flag) { flag = self }
425 if(flag.classname != "item_flag_team") { backtrace("ctf_RespawnFlag was called incorrectly."); return; }
428 if(flag.owner.flagcarried == flag)
430 WaypointSprite_DetachCarrier(flag.owner);
431 flag.owner.flagcarried = world;
433 if(flag.speedrunning)
434 ctf_FakeTimeLimit(flag.owner, -1);
438 if(flag.waypointsprite_attachedforcarrier)
439 WaypointSprite_DetachCarrier(flag);
441 setattachment(flag, world, "");
442 flag.damageforcescale = 0;
443 flag.takedamage = DAMAGE_NO;
444 flag.movetype = MOVETYPE_NONE;
446 flag.movetype = MOVETYPE_TOSS;
447 flag.velocity = '0 0 0';
448 flag.solid = SOLID_TRIGGER;
449 // TODO: play a sound here
450 setorigin(flag, flag.dropped_origin);
451 flag.angles = flag.mangle;
452 flag.ctf_status = FLAG_BASE;
454 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.
457 void ctf_FlagThink() // todo: re-write this
461 self.nextthink = time + 0.1;
463 // sorry, we have to reset the flag size if it got squished by something
464 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX)
466 // if we can grow back, grow back
467 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
468 if(!trace_startsolid)
469 setsize(self, FLAG_MIN, FLAG_MAX);
472 if(self == ctf_worldflaglist) // only for the first flag
475 ctf_CaptureShield_Update(e, 1); // release shield only
478 if(self.speedrunning)
479 if(self.ctf_status == FLAG_CARRY)
482 if(ctf_captimerecord)
483 if(time >= self.ctf_pickuptime + ctf_captimerecord)
485 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
487 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
488 self.owner.impulse = 141; // returning!
499 if(self.ctf_status == FLAG_BASE)
502 if(self.ctf_status == FLAG_DROPPED)
504 // flag fallthrough? FIXME remove this if bug is really fixed now
505 if(self.origin_z < -131072)
507 dprint("FLAG FALLTHROUGH just happened\n");
508 self.pain_finished = 0;
510 setattachment(self, world, "");
511 if(time > self.pain_finished)
513 bprint("The ", self.netname, " has returned to base\n");
514 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
515 ctf_EventLog("returned", self.team, world);
516 ctf_RespawnFlag(self);
522 if(e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
524 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
532 if(gameover) { return; }
533 if(!self) { return; }
534 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
535 { // The flag fell off the map, respawn it since players can't get to it
536 //ctf_RespawnFlag(self);
539 if(other.deadflag != DEAD_NO) { return; }
540 if(other.classname != "player")
541 { // The flag just touched an object, most likely the world
542 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
543 sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
546 else if(self.wait > time) { return; }
548 switch(self.ctf_status)
551 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
552 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
553 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
554 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
558 if(other.team == self.team)
559 ctf_Handle_Return(self, other); // other just returned his own flag
560 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect)))
561 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
566 dprint("Someone touched a flag even though it was being carried? wtf?\n");
567 break; // this should never happen
572 // =======================
573 // CaptureShield Functions
574 // =======================
576 float ctf_CaptureShield_CheckStatus(entity p) // check to see
580 float players_worseeq, players_total;
582 if(ctf_captureshield_max_ratio <= 0)
585 s = PlayerScore_Add(p, SP_SCORE, 0);
586 if(s >= -ctf_captureshield_min_negscore)
589 players_total = players_worseeq = 0;
594 se = PlayerScore_Add(e, SP_SCORE, 0);
600 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
601 // use this rule here
603 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
609 void ctf_CaptureShield_Update(entity player, float wanted_status)
611 float updated_status = ctf_CaptureShield_CheckStatus(player);
612 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
614 if(updated_status) // TODO csqc notifier for this // Samual: How?
615 centerprint_atprio(player, 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.");
617 centerprint_atprio(player, 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.");
619 player.ctf_captureshielded = updated_status;
623 float ctf_CaptureShield_Customize()
625 if not(other.ctf_captureshielded)
627 if(self.team == other.team)
632 void ctf_CaptureShield_Touch()
634 if not(other.ctf_captureshielded)
636 if(self.team == other.team)
640 mymid = (self.absmin + self.absmax) * 0.5;
641 othermid = (other.absmin + other.absmax) * 0.5;
642 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
643 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.");
646 void ctf_CaptureShield_Spawn(entity flag)
652 e.touch = ctf_CaptureShield_Touch;
653 e.customizeentityforclient = ctf_CaptureShield_Customize;
654 e.classname = "ctf_captureshield";
655 e.effects = EF_ADDITIVE;
656 e.movetype = MOVETYPE_NOCLIP;
657 e.solid = SOLID_TRIGGER;
658 e.avelocity = '7 0 11';
659 setorigin(e, self.origin);
660 setmodel(e, "models/ctf/shield.md3");
662 setsize(e, e.scale * e.mins, e.scale * e.maxs);
670 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
672 if(self.flagcarried) { ctf_Handle_Drop(self); } // figure this out
677 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
681 // initially clear items so they can be set as necessary later.
682 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
683 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
685 // item for stopping players from capturing the flag too often
686 if(self.ctf_captureshielded)
687 self.items |= IT_CTF_SHIELDED;
689 // scan through all the flags and notify the client about them
690 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
692 if(flag.ctf_status == FLAG_CARRY)
693 if(flag.owner == self)
694 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
696 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
697 else if(flag.ctf_status == FLAG_DROPPED)
698 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
701 if((autocvar_g_ctf_allow_drop) && (self.BUTTON_USE))
702 ctf_Handle_Drop(self);
712 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
713 CTF Starting point for a player in team one (Red).
714 Keys: "angle" viewing angle when spawning. */
715 void spawnfunc_info_player_team1()
717 if(g_assault) { remove(self); return; }
719 self.team = COLOR_TEAM1; // red
720 spawnfunc_info_player_deathmatch();
724 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
725 CTF Starting point for a player in team two (Blue).
726 Keys: "angle" viewing angle when spawning. */
727 void spawnfunc_info_player_team2()
729 if(g_assault) { remove(self); return; }
731 self.team = COLOR_TEAM2; // blue
732 spawnfunc_info_player_deathmatch();
735 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
736 CTF Starting point for a player in team three (Yellow).
737 Keys: "angle" viewing angle when spawning. */
738 void spawnfunc_info_player_team3()
740 if(g_assault) { remove(self); return; }
742 self.team = COLOR_TEAM3; // yellow
743 spawnfunc_info_player_deathmatch();
747 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
748 CTF Starting point for a player in team four (Purple).
749 Keys: "angle" viewing angle when spawning. */
750 void spawnfunc_info_player_team4()
752 if(g_assault) { remove(self); return; }
754 self.team = COLOR_TEAM4; // purple
755 spawnfunc_info_player_deathmatch();
758 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
759 CTF flag for team one (Red). Multiple flags are allowed.
761 "angle" Angle the flag will point (minus 90 degrees)...
762 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
763 "noise" sound played when flag is picked up (default ctf/take.wav)...
764 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
765 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
766 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
767 void spawnfunc_item_flag_team1()
769 if(!g_ctf) { remove(self); return; }
771 ctf_SetupFlag(1, self); // 1 = red
774 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
775 CTF flag for team two (Blue). Multiple flags are allowed.
777 "angle" Angle the flag will point (minus 90 degrees)...
778 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
779 "noise" sound played when flag is picked up (default ctf/take.wav)...
780 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
781 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
782 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
783 void spawnfunc_item_flag_team2()
785 if(!g_ctf) { remove(self); return; }
787 ctf_SetupFlag(0, self); // the 0 is misleading, but -- 0 = blue.
790 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
791 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
792 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.
794 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
795 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
796 void spawnfunc_ctf_team()
798 if(!g_ctf) { remove(self); return; }
800 self.classname = "ctf_team";
801 self.team = self.cnt + 1;
809 // code from here on is just to support maps that don't have flag and team entities
810 void ctf_SpawnTeam (string teamname, float teamcolor)
812 local entity oldself;
815 self.classname = "ctf_team";
816 self.netname = teamname;
817 self.cnt = teamcolor;
819 spawnfunc_ctf_team();
824 void ctf_DelayedInit()
826 // if no teams are found, spawn defaults
827 if(find(world, classname, "ctf_team") == world)
829 print("NO TEAMS FOUND FOR CTF! creating them anyway.\n");
830 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
831 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
835 void ctf_Initialize()
837 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
839 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
840 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
841 ctf_captureshield_force = autocvar_g_ctf_shield_force;
843 g_ctf_win_mode = cvar("g_ctf_win_mode");
848 //InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
852 MUTATOR_DEFINITION(gamemode_ctf)
854 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
855 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
856 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
857 //MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
858 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
859 //MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
860 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
864 if(time > 1) // game loads at time 1
865 error("This is a game type and it cannot be added at runtime.");
873 error("This is a game type and it cannot be removed at runtime.");