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) ? (strcat(":", ftos(actor.playerid))) : "")));
80 void ctf_Handle_Drop(entity player) // make sure this works
82 entity flag = player.flagcarried;
85 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
88 setattachment(flag, world, "");
89 setorigin(flag, player.origin - '0 0 24' + '0 0 37');
90 flag.owner.flagcarried = world;
92 flag.movetype = MOVETYPE_TOSS;
93 flag.solid = SOLID_TRIGGER;
94 flag.takedamage = DAMAGE_YES;
95 //flag.flags = FL_ITEM; // does this need set? same as above. // eh wtf is with these weird values?
96 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
97 flag.pain_finished = time + autocvar_g_ctf_flag_returntime; // replace this later
99 flag.ctf_droptime = time;
100 flag.ctf_dropperid = player.playerid;
101 flag.ctf_status = FLAG_DROPPED;
103 // messages and sounds
104 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
105 sound(flag, CHAN_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE);
106 ctf_EventLog("dropped", player.team, player);
109 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
110 PlayerScore_Add(player, SP_CTF_DROPS, 1);
113 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, player.team, flag, wps_flagdropped, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
114 WaypointSprite_Ping(player.wps_flagcarrier);
115 WaypointSprite_Kill(player.wps_flagcarrier);
118 ctf_CaptureShield_Update(player, 0); // shield only
121 trace_startsolid = FALSE;
122 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
124 dprint("FLAG FALLTHROUGH will happen SOON\n");
127 void ctf_Handle_Capture(entity flag, entity player) // make sure this works
130 float cap_time, cap_record, success;
131 string cap_message, refername;
134 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
135 cap_record = ctf_captimerecord;
136 cap_time = (time - player.flagcarried.ctf_pickuptime);
138 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
139 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
141 if(!ctf_captimerecord)
142 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
143 else if(cap_time < cap_record)
144 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
146 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
149 ctf_captimerecord = cap_time;
150 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
151 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
152 write_recordmarker(player, (time - cap_time), cap_time); } }
154 // messages and sounds
155 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
156 sound(player, CHAN_AUTO, flag.noise2, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav"
157 ctf_EventLog("capture", player.flagcarried.team, player);
160 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
161 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
164 if (autocvar_g_ctf_flag_capture_effects)
166 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
167 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
171 WaypointSprite_Kill(player.wps_flagcarrier);
174 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
176 ctf_RespawnFlag(player.flagcarried);
179 void ctf_Handle_Return(entity flag, entity player) // make sure this works
181 // messages and sounds
182 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
183 sound(player, CHAN_AUTO, flag.noise1, VOL_BASE, ATTN_NONE);
184 ctf_EventLog("return", flag.team, player);
187 PlayerTeamScore_AddScore(player, ctf_ReadScore(strcat("score_return", ((player.playerid == flag.playerid) ? "_by_killer" : "")))); // reward for return
188 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
190 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
191 FOR_EACH_PLAYER(player) if(player.playerid == flag.ctf_dropperid) // punish the player who dropped the flag
193 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
194 ctf_CaptureShield_Update(player, 0); // shield only
198 WaypointSprite_Kill(player.wps_flagdropped);
201 ctf_RespawnFlag(flag);
204 void ctf_Handle_Pickup_Base(entity flag, entity player) // make sure this works
206 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
207 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
209 // attach the flag to the player
211 player.flagcarried = flag;
212 setattachment(flag, player, "");
213 setorigin(flag, FLAG_CARRY_POS);
216 flag.movetype = MOVETYPE_NONE;
217 flag.solid = SOLID_NOT;
218 flag.angles = '0 0 0';
219 flag.ctf_pickuptime = time; // used for timing runs
220 flag.ctf_pickupid = player.playerid;
221 flag.ctf_status = FLAG_CARRY;
223 // messages and sounds
224 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
225 sound(player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
226 ctf_EventLog("steal", flag.team, player);
227 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : ""); // replace TRUE with an autocvar for it.
228 FOR_EACH_PLAYER(tmp_player)
229 if(tmp_player.team == flag.team)
230 centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!"));
231 else if((tmp_player.team == player.team) && (tmp_player != player))
232 centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!"));
235 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
236 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
239 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
240 if((player.speedrunning) && (ctf_captimerecord))
241 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
244 if (autocvar_g_ctf_flag_pickup_effects)
246 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
250 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
251 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
252 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
253 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
254 WaypointSprite_Ping(player.wps_flagcarrier);
257 void ctf_Handle_Pickup_Dropped(entity flag, entity player) // todo: re-write this
260 if(flag.waypointsprite_attachedforcarrier)
261 WaypointSprite_DetachCarrier(flag);
263 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
264 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
267 flag.solid = SOLID_NOT;
268 setorigin(flag, flag.origin); // relink
270 player.flagcarried = flag;
271 flag.cnt = FLAG_CARRY;
272 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
273 //bprint(player.netname, "^7 picked up the ", flag.netname, "\n");
276 f = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1);
277 //print("factor is ", ftos(f), "\n");
278 f = ctf_ReadScore("score_pickup_dropped_late") * (1-f)
279 + ctf_ReadScore("score_pickup_dropped_early") * f;
281 flag.dropperid = player.playerid;
282 //print("score is ", ftos(f), "\n");
284 UpdateFrags(player, f);
285 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
286 LogCTF("pickup", flag.team, player);
287 sound (player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
289 FOR_EACH_PLAYER(player)
290 if(player.team == flag.team)
291 centerprint(player, "The enemy got your flag! Retrieve it!");
293 flag.movetype = MOVETYPE_NONE; // flag must have MOVETYPE_NONE here, playerwise it will drop through the floor...
294 setorigin(flag, FLAG_CARRY_POS);
295 setattachment(flag, player, "");
296 flag.damageforcescale = 0;
297 flag.takedamage = DAMAGE_NO;
298 WaypointSprite_AttachCarrier("flagcarrier", player);
299 WaypointSprite_UpdateTeamRadar(player.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 1 0');
304 // ===================
305 // Main Flag Functions
306 // ===================
311 if(self.owner.classname == "player")
312 ctf_Handle_Drop(self.owner);
314 ctf_RespawnFlag(self);
317 void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
320 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.
323 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified
324 ctf_worldflaglist = flag;
326 setattachment(flag, world, "");
328 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
329 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
330 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
331 flag.classname = "item_flag_team";
332 flag.target = "###item###"; // wut?
333 flag.flags = FL_ITEM;
334 flag.solid = SOLID_TRIGGER;
335 flag.velocity = '0 0 0';
336 flag.ctf_status = FLAG_BASE;
337 flag.mangle = flag.angles;
338 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
340 if(flag.spawnflags & 1) // I don't understand what all this is about.
343 flag.dropped_origin = flag.origin;
344 flag.movetype = MOVETYPE_NONE;
345 print("This map was loaded with flags using MOVETYPE_NONE\n");
349 flag.noalign = FALSE;
351 flag.movetype = MOVETYPE_TOSS;
352 print("This map was loaded with flags using MOVETYPE_TOSS\n");
355 flag.reset = ctf_Reset;
356 flag.touch = ctf_FlagTouch;
359 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
360 setmodel (flag, flag.model); // precision set below
361 setsize(flag, FLAG_MIN, FLAG_MAX);
362 setorigin(flag, flag.origin);
363 if(!flag.scale) { flag.scale = 0.6; }
365 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
367 if(autocvar_g_ctf_flag_glowtrails)
369 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
374 flag.effects |= EF_LOWPRECISION;
375 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
376 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
379 if(!flag.noise) { flag.noise = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
380 if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
381 if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
382 if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
383 if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
384 //if(!flag.noise5) { flag.noise5 = "ctf/flag_touch.wav"; }
387 precache_sound(flag.noise);
388 precache_sound(flag.noise1);
389 precache_sound(flag.noise2);
390 precache_sound(flag.noise3);
391 precache_sound(flag.noise4);
392 //precache_sound(flag.noise5);
393 precache_model(flag.model);
394 precache_model("models/ctf/shield.md3");
395 precache_model("models/ctf/shockwavetransring.md3");
398 waypoint_spawnforitem_force(flag, flag.origin);
399 flag.nearestwaypointtimeout = 0; // activate waypointing again
400 flag.bot_basewaypoint = flag.nearestwaypoint;
403 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 64', flag, wps_flagbase);
404 WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
406 // captureshield setup
407 ctf_CaptureShield_Spawn(flag);
410 void ctf_RespawnFlag(entity flag) // todo: re-write this
412 //if((self) && (!flag) { flag = self }
413 if(flag.classname != "item_flag_team") { backtrace("ctf_RespawnFlag was called incorrectly."); return; }
416 if(flag.owner.flagcarried == flag)
418 WaypointSprite_DetachCarrier(flag.owner);
419 flag.owner.flagcarried = world;
421 if(flag.speedrunning)
422 ctf_FakeTimeLimit(flag.owner, -1);
426 if(flag.waypointsprite_attachedforcarrier)
427 WaypointSprite_DetachCarrier(flag);
429 setattachment(flag, world, "");
430 flag.damageforcescale = 0;
431 flag.takedamage = DAMAGE_NO;
432 flag.movetype = MOVETYPE_NONE;
434 flag.movetype = MOVETYPE_TOSS;
435 flag.velocity = '0 0 0';
436 flag.solid = SOLID_TRIGGER;
437 // TODO: play a sound here
438 setorigin(flag, flag.dropped_origin);
439 flag.angles = flag.mangle;
440 flag.ctf_status = FLAG_BASE;
442 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.
445 void ctf_FlagThink() // todo: re-write this
449 self.nextthink = time + 0.1;
451 // sorry, we have to reset the flag size if it got squished by something
452 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX)
454 // if we can grow back, grow back
455 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
456 if(!trace_startsolid)
457 setsize(self, FLAG_MIN, FLAG_MAX);
460 if(self == ctf_worldflaglist) // only for the first flag
463 ctf_CaptureShield_Update(e, 1); // release shield only
466 if(self.speedrunning)
467 if(self.ctf_status == FLAG_CARRY)
470 if(ctf_captimerecord)
471 if(time >= self.ctf_pickuptime + ctf_captimerecord)
473 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
475 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
476 self.owner.impulse = 141; // returning!
487 if(self.ctf_status == FLAG_BASE)
490 if(self.ctf_status == FLAG_DROPPED)
492 // flag fallthrough? FIXME remove this if bug is really fixed now
493 if(self.origin_z < -131072)
495 dprint("FLAG FALLTHROUGH just happened\n");
496 self.pain_finished = 0;
498 setattachment(self, world, "");
499 if(time > self.pain_finished)
501 bprint("The ", self.netname, " has returned to base\n");
502 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
503 ctf_EventLog("returned", self.team, world);
504 ctf_RespawnFlag(self);
510 if(e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
512 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
520 if(gameover) { return; }
521 if(!self) { return; }
522 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
523 { // The flag fell off the map, respawn it since players can't get to it
524 //ctf_RespawnFlag(self);
527 if(other.deadflag != DEAD_NO) { return; }
528 if(other.classname != "player")
529 { // The flag just touched an object, most likely the world
530 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
531 sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
534 else if(self.wait > time) { return; }
536 switch(self.ctf_status)
539 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
540 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
541 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
542 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
546 if(other.team == self.team)
547 ctf_Handle_Return(self, other); // other just returned his own flag
548 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect)))
549 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
554 dprint("Someone touched a flag even though it was being carried? wtf?\n");
555 break; // this should never happen
560 // =======================
561 // CaptureShield Functions
562 // =======================
564 float ctf_CaptureShield_CheckStatus(entity p) // check to see
568 float players_worseeq, players_total;
570 if(ctf_captureshield_max_ratio <= 0)
573 s = PlayerScore_Add(p, SP_SCORE, 0);
574 if(s >= -ctf_captureshield_min_negscore)
577 players_total = players_worseeq = 0;
582 se = PlayerScore_Add(e, SP_SCORE, 0);
588 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
589 // use this rule here
591 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
597 void ctf_CaptureShield_Update(entity player, float wanted_status)
599 float updated_status = ctf_CaptureShield_CheckStatus(player);
600 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
602 if(updated_status) // TODO csqc notifier for this // Samual: How?
603 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.");
605 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.");
607 player.ctf_captureshielded = updated_status;
611 float ctf_CaptureShield_Customize()
613 if not(other.ctf_captureshielded)
615 if(self.team == other.team)
620 void ctf_CaptureShield_Touch()
622 if not(other.ctf_captureshielded)
624 if(self.team == other.team)
628 mymid = (self.absmin + self.absmax) * 0.5;
629 othermid = (other.absmin + other.absmax) * 0.5;
630 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
631 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.");
634 void ctf_CaptureShield_Spawn(entity flag)
640 e.touch = ctf_CaptureShield_Touch;
641 e.customizeentityforclient = ctf_CaptureShield_Customize;
642 e.classname = "ctf_captureshield";
643 e.effects = EF_ADDITIVE;
644 e.movetype = MOVETYPE_NOCLIP;
645 e.solid = SOLID_TRIGGER;
646 e.avelocity = '7 0 11';
647 setorigin(e, self.origin);
648 setmodel(e, "models/ctf/shield.md3");
650 setsize(e, e.scale * e.mins, e.scale * e.maxs);
658 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
660 if(self.flagcarried) { ctf_Handle_Drop(self); } // figure this out
665 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
669 // initially clear items so they can be set as necessary later.
670 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
671 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
673 // item for stopping players from capturing the flag too often
674 if(self.ctf_captureshielded)
675 self.items |= IT_CTF_SHIELDED;
677 // scan through all the flags and notify the client about them
678 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
680 if(flag.ctf_status == FLAG_CARRY)
681 if(flag.owner == self)
682 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
684 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
685 else if(flag.ctf_status == FLAG_DROPPED)
686 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
689 if((autocvar_g_ctf_allow_drop) && (self.BUTTON_USE))
690 ctf_Handle_Drop(self);
700 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
701 CTF Starting point for a player in team one (Red).
702 Keys: "angle" viewing angle when spawning. */
703 void spawnfunc_info_player_team1()
705 if(g_assault) { remove(self); return; }
707 self.team = COLOR_TEAM1; // red
708 spawnfunc_info_player_deathmatch();
712 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
713 CTF Starting point for a player in team two (Blue).
714 Keys: "angle" viewing angle when spawning. */
715 void spawnfunc_info_player_team2()
717 if(g_assault) { remove(self); return; }
719 self.team = COLOR_TEAM2; // blue
720 spawnfunc_info_player_deathmatch();
723 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
724 CTF Starting point for a player in team three (Yellow).
725 Keys: "angle" viewing angle when spawning. */
726 void spawnfunc_info_player_team3()
728 if(g_assault) { remove(self); return; }
730 self.team = COLOR_TEAM3; // yellow
731 spawnfunc_info_player_deathmatch();
735 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
736 CTF Starting point for a player in team four (Purple).
737 Keys: "angle" viewing angle when spawning. */
738 void spawnfunc_info_player_team4()
740 if(g_assault) { remove(self); return; }
742 self.team = COLOR_TEAM4; // purple
743 spawnfunc_info_player_deathmatch();
746 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
747 CTF flag for team one (Red). Multiple flags are allowed.
749 "angle" Angle the flag will point (minus 90 degrees)...
750 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
751 "noise" sound played when flag is picked up (default ctf/take.wav)...
752 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
753 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
754 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
755 void spawnfunc_item_flag_team1()
757 if(!g_ctf) { remove(self); return; }
759 ctf_SetupFlag(1, self); // 1 = red
762 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
763 CTF flag for team two (Blue). Multiple flags are allowed.
765 "angle" Angle the flag will point (minus 90 degrees)...
766 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
767 "noise" sound played when flag is picked up (default ctf/take.wav)...
768 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
769 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
770 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
771 void spawnfunc_item_flag_team2()
773 if(!g_ctf) { remove(self); return; }
775 ctf_SetupFlag(0, self); // the 0 is misleading, but -- 0 = blue.
778 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
779 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
780 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.
782 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
783 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
784 void spawnfunc_ctf_team()
786 if(!g_ctf) { remove(self); return; }
788 self.classname = "ctf_team";
789 self.team = self.cnt + 1;
797 // code from here on is just to support maps that don't have flag and team entities
798 void ctf_SpawnTeam (string teamname, float teamcolor)
800 local entity oldself;
803 self.classname = "ctf_team";
804 self.netname = teamname;
805 self.cnt = teamcolor;
807 spawnfunc_ctf_team();
812 void ctf_DelayedInit()
814 // if no teams are found, spawn defaults
815 if(find(world, classname, "ctf_team") == world)
817 print("NO TEAMS FOUND FOR CTF! creating them anyway.\n");
818 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
819 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
823 void ctf_Initialize()
825 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
827 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
828 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
829 ctf_captureshield_force = autocvar_g_ctf_shield_force;
831 g_ctf_win_mode = cvar("g_ctf_win_mode");
836 //InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
840 MUTATOR_DEFINITION(gamemode_ctf)
842 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
843 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
844 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
845 //MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
846 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
847 //MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
848 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
852 if(time > 1) // game loads at time 1
853 error("This is a game type and it cannot be added at runtime.");
861 error("This is a game type and it cannot be removed at runtime.");