1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: March 27th, 2012
4 // ================================================================
6 float ctf_ReadScore(string parameter) // make this obsolete
8 //if(g_ctf_win_mode != 2)
9 return cvar(strcat("g_ctf_personal", parameter));
11 // return cvar(strcat("g_ctf_flag", parameter));
14 void ctf_FakeTimeLimit(entity e, float t)
17 WriteByte(MSG_ONE, 3); // svc_updatestat
18 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
20 WriteCoord(MSG_ONE, autocvar_timelimit);
22 WriteCoord(MSG_ONE, (t + 1) / 60);
25 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
27 if(autocvar_sv_eventlog)
28 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
32 // =======================
33 // CaptureShield Functions
34 // =======================
36 float ctf_CaptureShield_CheckStatus(entity p)
40 float players_worseeq, players_total;
42 if(ctf_captureshield_max_ratio <= 0)
45 s = PlayerScore_Add(p, SP_SCORE, 0);
46 if(s >= -ctf_captureshield_min_negscore)
49 players_total = players_worseeq = 0;
54 se = PlayerScore_Add(e, SP_SCORE, 0);
60 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
63 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
69 void ctf_CaptureShield_Update(entity player, float wanted_status)
71 float updated_status = ctf_CaptureShield_CheckStatus(player);
72 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
74 if(updated_status) // TODO csqc notifier for this // Samual: How?
75 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^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.", 5, 0);
77 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
79 player.ctf_captureshielded = updated_status;
83 float ctf_CaptureShield_Customize()
85 if not(other.ctf_captureshielded) { return FALSE; }
86 if(self.team == other.team) { return FALSE; }
91 void ctf_CaptureShield_Touch()
93 if not(other.ctf_captureshielded) { return; }
94 if(self.team == other.team) { return; }
96 vector mymid = (self.absmin + self.absmax) * 0.5;
97 vector othermid = (other.absmin + other.absmax) * 0.5;
99 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
100 Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^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.", 5, 0);
103 void ctf_CaptureShield_Spawn(entity flag)
105 entity shield = spawn();
108 shield.team = self.team;
109 shield.touch = ctf_CaptureShield_Touch;
110 shield.customizeentityforclient = ctf_CaptureShield_Customize;
111 shield.classname = "ctf_captureshield";
112 shield.effects = EF_ADDITIVE;
113 shield.movetype = MOVETYPE_NOCLIP;
114 shield.solid = SOLID_TRIGGER;
115 shield.avelocity = '7 0 11';
118 setorigin(shield, self.origin);
119 setmodel(shield, "models/ctf/shield.md3");
120 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
128 void ctf_Handle_Pass(entity player)
130 entity flag = player.flagcarried;
132 if(!flag) { return; }
133 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
136 setattachment(flag, world, "");
137 setorigin(flag, player.origin - '0 0 24' + '0 0 37');
138 flag.owner.flagcarried = world;
140 flag.movetype = MOVETYPE_TOSS;
141 flag.solid = SOLID_TRIGGER;
142 flag.takedamage = DAMAGE_YES;
143 flag.health = flag.max_flag_health;
144 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
146 flag.ctf_droptime = time;
147 flag.ctf_dropperid = player.playerid;
148 flag.ctf_status = FLAG_DROPPED;
150 // messages and sounds
151 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
152 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
153 ctf_EventLog("dropped", player.team, player);
156 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
157 PlayerScore_Add(player, SP_CTF_DROPS, 1);
160 if(autocvar_g_ctf_flag_dropped_waypoint)
161 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, FALSE, RADARICON_FLAG, '0 1 1'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
163 WaypointSprite_Ping(player.wps_flagcarrier);
164 WaypointSprite_Kill(player.wps_flagcarrier);
166 if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
168 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
169 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
173 ctf_CaptureShield_Update(player, 0); // shield only
175 // check if the flag will fall off the map
176 trace_startsolid = FALSE;
177 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
179 dprint("FLAG FALLTHROUGH will happen SOON\n");
182 void ctf_Handle_Drop(entity player, float droptype)
184 entity flag = player.flagcarried;
186 if(!flag) { return; }
187 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
189 makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
192 setattachment(flag, world, "");
193 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
194 flag.owner.flagcarried = world;
196 flag.movetype = MOVETYPE_TOSS;
197 flag.solid = SOLID_TRIGGER;
198 flag.takedamage = DAMAGE_YES;
199 flag.health = flag.max_flag_health;
203 case DROPTYPE_THROWN:
205 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_throw_velocity)), FALSE);
210 case DROPTYPE_NORMAL:
212 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
217 flag.ctf_droptime = time;
218 flag.ctf_dropperid = player.playerid;
219 flag.ctf_status = FLAG_DROPPED;
221 // messages and sounds
222 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
223 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
224 ctf_EventLog("dropped", player.team, player);
227 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
228 PlayerScore_Add(player, SP_CTF_DROPS, 1);
231 if(autocvar_g_ctf_flag_dropped_waypoint)
232 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, FALSE, RADARICON_FLAG, '0 0.5 0' + ((flag.team == COLOR_TEAM1) ? '0.75 0 0' : '0 0 0.75')); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
234 WaypointSprite_Ping(player.wps_flagcarrier);
235 WaypointSprite_Kill(player.wps_flagcarrier);
237 if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
239 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
240 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
244 ctf_CaptureShield_Update(player, 0); // shield only
246 // check if the flag will fall off the map
247 trace_startsolid = FALSE;
248 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
250 dprint("FLAG FALLTHROUGH will happen SOON\n");
253 void ctf_Handle_Capture(entity flag, entity player)
256 float cap_time, cap_record, success;
257 string cap_message, refername;
260 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
261 cap_record = ctf_captimerecord;
262 cap_time = (time - player.flagcarried.ctf_pickuptime);
264 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
265 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
267 if(!ctf_captimerecord)
268 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
269 else if(cap_time < cap_record)
270 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
272 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
275 ctf_captimerecord = cap_time;
276 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
277 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
278 write_recordmarker(player, (time - cap_time), cap_time); } }
280 // messages and sounds
281 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
282 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav"
283 ctf_EventLog("capture", player.flagcarried.team, player);
286 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
287 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
290 if (autocvar_g_ctf_flag_capture_effects)
292 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
293 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
297 WaypointSprite_Kill(player.wps_flagcarrier);
300 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
302 ctf_RespawnFlag(player.flagcarried);
305 void ctf_Handle_Return(entity flag, entity player)
307 // messages and sounds
308 //centerprint(player, strcat("You returned ", flag.netname));
309 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
310 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
311 ctf_EventLog("return", flag.team, player);
314 PlayerTeamScore_AddScore(player, ctf_ReadScore(strcat("score_return", ((player.playerid == flag.playerid) ? "_by_killer" : "")))); // reward for return
315 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
317 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
318 FOR_EACH_PLAYER(player) if(player.playerid == flag.ctf_dropperid) // punish the player who dropped the flag
320 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
321 ctf_CaptureShield_Update(player, 0); // shield only
325 ctf_RespawnFlag(flag);
328 void ctf_Handle_Pickup_Base(entity flag, entity player)
330 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
331 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
333 // attach the flag to the player
335 player.flagcarried = flag;
336 setattachment(flag, player, "");
337 setorigin(flag, FLAG_CARRY_OFFSET);
340 flag.movetype = MOVETYPE_NONE;
341 flag.takedamage = DAMAGE_NO;
342 flag.solid = SOLID_NOT;
343 flag.angles = '0 0 0';
344 flag.ctf_pickuptime = time; // used for timing runs
345 flag.ctf_pickupid = player.playerid;
346 flag.ctf_status = FLAG_CARRY;
348 // messages and sounds
349 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
350 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
351 ctf_EventLog("steal", flag.team, player);
352 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
353 FOR_EACH_PLAYER(tmp_player)
354 if(tmp_player == player)
355 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
356 else if(tmp_player.team == player.team)
357 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
358 else if(tmp_player.team == flag.team)
359 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
362 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
363 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
366 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
367 if((player.speedrunning) && (ctf_captimerecord))
368 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
371 if (autocvar_g_ctf_flag_pickup_effects)
373 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
377 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
378 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
379 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
380 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
381 WaypointSprite_Ping(player.wps_flagcarrier);
384 void ctf_Handle_Pickup_Dropped(entity flag, entity player) // make sure this works
387 float returnscore = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1); // can this be division by zero? FIXME
388 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
389 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
391 // attach the flag to the player
393 player.flagcarried = flag;
394 setattachment(flag, player, "");
395 setorigin(flag, FLAG_CARRY_OFFSET);
398 flag.movetype = MOVETYPE_NONE;
399 flag.takedamage = DAMAGE_NO;
400 flag.health = flag.max_flag_health;
401 flag.solid = SOLID_NOT;
402 flag.angles = '0 0 0';
403 //flag.ctf_pickuptime = time; // don't update pickuptime since this isn't a real steal.
404 flag.ctf_pickupid = player.playerid;
405 flag.ctf_status = FLAG_CARRY;
407 // messages and sounds
408 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
409 sound (player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
410 ctf_EventLog("pickup", flag.team, player);
411 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
412 FOR_EACH_PLAYER(tmp_player)
413 if(tmp_player == player)
414 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
415 else if(tmp_player.team == player.team)
416 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
417 else if(tmp_player.team == flag.team)
418 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
421 returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5);
422 print("score is ", ftos(returnscore), "\n");
423 PlayerTeamScore_AddScore(player, returnscore);
424 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
427 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
429 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
433 WaypointSprite_Kill(flag.wps_flagdropped);
434 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
435 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
436 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
437 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
438 WaypointSprite_Ping(player.wps_flagcarrier);
442 // ===================
443 // Main Flag Functions
444 // ===================
446 void ctf_CheckFlagReturn(entity flag)
448 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
450 if((flag.health <= 0) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_returntime))
452 bprint("The ", flag.netname, " has returned to base\n");
453 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
454 ctf_EventLog("returned", flag.team, world);
455 ctf_RespawnFlag(flag);
459 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
461 if(deathtype == DEATH_HURTTRIGGER || deathtype == DEATH_SLIME || deathtype == DEATH_LAVA)
463 // automatically kill the flag and return it
465 ctf_CheckFlagReturn(self);
468 if(autocvar_g_ctf_flag_take_damage)
470 self.health = self.health - damage;
471 ctf_CheckFlagReturn(self);
480 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
483 if(self == ctf_worldflaglist) // only for the first flag
484 FOR_EACH_CLIENT(tmp_entity)
485 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
488 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
489 dprint("wtf the flag got squished?\n");
490 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
491 if(!trace_startsolid) // can we resize it without getting stuck?
492 setsize(self, FLAG_MIN, FLAG_MAX); }
495 switch(self.ctf_status)
497 case FLAG_BASE: // nothing to do here
501 if(autocvar_g_ctf_flag_returntime)
503 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_returntime) * FLAG_THINKRATE);
504 ctf_CheckFlagReturn(self);
509 if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord))
511 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
512 sound(self, CH_TRIGGER, self.snd_flag_respawn, VOL_BASE, ATTN_NONE);
513 ctf_EventLog("returned", self.team, world);
514 ctf_RespawnFlag(tmp_entity);
518 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
524 default: // this should never happen
525 dprint("Think: Flag exists with no status?\n");
532 if(gameover) { return; }
533 if(!self) { return; }
534 if(other.deadflag != DEAD_NO) { return; }
535 if(other.classname != "player")
536 { // The flag just touched an object, most likely the world
537 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
538 sound(self, CH_TRIGGER, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
541 else if(self.wait > time) { return; }
543 switch(self.ctf_status)
546 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
547 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
548 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
549 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
553 if(other.team == self.team)
554 ctf_Handle_Return(self, other); // other just returned his own flag
555 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
556 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
560 dprint("Someone touched a flag even though it was being carried?\n");
563 default: // this should never happen
564 dprint("Touch: Flag exists with no status?\n");
569 void ctf_RespawnFlag(entity flag)
571 // reset the player (if there is one)
572 if((flag.owner) && (flag.owner.flagcarried == flag))
574 WaypointSprite_Kill(flag.wps_flagcarrier);
575 flag.owner.flagcarried = world;
577 if(flag.speedrunning)
578 ctf_FakeTimeLimit(flag.owner, -1);
581 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
582 { WaypointSprite_Kill(flag.wps_flagdropped); }
585 setattachment(flag, world, "");
586 setorigin(flag, flag.ctf_spawnorigin); // replace with flag.ctf_spawnorigin
587 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
588 flag.takedamage = DAMAGE_NO;
589 flag.health = flag.max_flag_health;
590 flag.solid = SOLID_TRIGGER;
591 flag.velocity = '0 0 0';
592 flag.angles = flag.mangle;
593 flag.ctf_status = FLAG_BASE;
594 flag.flags = FL_ITEM | FL_NOTARGET;
601 if(self.owner.classname == "player")
602 ctf_Handle_Drop(self.owner, DROPTYPE_NORMAL);
604 ctf_RespawnFlag(self);
607 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
610 float teamnumber = ((self.team == COLOR_TEAM1) ? TRUE : FALSE); // if we were originally 1, this will become 0. If we were originally 0, this will become 1.
613 waypoint_spawnforitem_force(self, self.origin);
614 self.nearestwaypointtimeout = 0; // activate waypointing again
615 self.bot_basewaypoint = self.nearestwaypoint;
618 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), self.origin + '0 0 64', self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
619 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
621 // captureshield setup
622 ctf_CaptureShield_Spawn(self);
625 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
628 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.
629 self = flag; // for later usage with droptofloor()
632 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
633 ctf_worldflaglist = flag;
635 setattachment(flag, world, "");
637 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
638 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
639 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
640 flag.classname = "item_flag_team";
641 flag.target = "###item###"; // wut?
642 flag.flags = FL_ITEM | FL_NOTARGET;
643 flag.solid = SOLID_TRIGGER;
644 flag.takedamage = DAMAGE_NO;
645 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
646 flag.max_flag_health = ((autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
647 flag.health = flag.max_flag_health;
648 flag.event_damage = ctf_FlagDamage;
649 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
650 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
651 flag.velocity = '0 0 0';
652 flag.mangle = flag.angles;
653 flag.reset = ctf_Reset;
654 flag.touch = ctf_FlagTouch;
655 flag.think = ctf_FlagThink;
656 flag.nextthink = time + 0.2;
657 flag.ctf_status = FLAG_BASE;
660 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
661 setmodel(flag, flag.model); // precision set below
662 setsize(flag, FLAG_MIN, FLAG_MAX);
663 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
664 if(!flag.scale) { flag.scale = FLAG_SCALE; }
666 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
668 if(autocvar_g_ctf_flag_glowtrails)
670 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
675 flag.effects |= EF_LOWPRECISION;
676 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
677 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
680 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
681 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
682 if(!flag.snd_flag_capture) { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
683 if(!flag.snd_flag_respawn) { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
684 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
685 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/flag_touch.wav"; } // again has no team-based sound
688 precache_sound(flag.snd_flag_taken);
689 precache_sound(flag.snd_flag_returned);
690 precache_sound(flag.snd_flag_capture);
691 precache_sound(flag.snd_flag_respawn);
692 precache_sound(flag.snd_flag_dropped);
693 precache_sound(flag.snd_flag_touch);
694 precache_model(flag.model);
695 precache_model("models/ctf/shield.md3");
696 precache_model("models/ctf/shockwavetransring.md3");
699 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
701 flag.dropped_origin = flag.origin;
703 flag.movetype = MOVETYPE_NONE;
705 else // drop to floor, automatically find a platform and set that as spawn origin
707 flag.noalign = FALSE;
710 flag.movetype = MOVETYPE_TOSS;
713 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
721 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
723 if(self.flagcarried) { ctf_Handle_Drop(self, DROPTYPE_NORMAL); }
727 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
731 // initially clear items so they can be set as necessary later.
732 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
733 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
735 // item for stopping players from capturing the flag too often
736 if(self.ctf_captureshielded)
737 self.items |= IT_CTF_SHIELDED;
739 // scan through all the flags and notify the client about them
740 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
742 if(flag.ctf_status == FLAG_CARRY)
743 if(flag.owner == self)
744 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
746 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
747 else if(flag.ctf_status == FLAG_DROPPED)
748 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
751 if(self.wps_flagcarrier) { WaypointSprite_UpdateHealth(self.wps_flagcarrier, self.health); }
756 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
758 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
760 if(frag_target == frag_attacker) // damage done to yourself
762 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
763 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
765 else // damage done to noncarriers
767 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
768 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
774 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
776 frag_score = 0; // no frags counted in ctf
777 return (g_ctf_ignore_frags); // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
780 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
782 entity player = self;
784 if(player.flagcarried)
786 if(autocvar_g_ctf_allow_pass)
789 head = findradius(player.origin, autocvar_g_ctf_pass_radius);
793 if(head.classname == "player" && head.deadflag == DEAD_NO)
794 if(head != player && !IsDifferentTeam(head, player))
796 ctf_Handle_Pass(player);
803 if(autocvar_g_ctf_allow_drop) { ctf_Handle_Drop(player, DROPTYPE_THROWN); }
813 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
814 CTF Starting point for a player in team one (Red).
815 Keys: "angle" viewing angle when spawning. */
816 void spawnfunc_info_player_team1()
818 if(g_assault) { remove(self); return; }
820 self.team = COLOR_TEAM1; // red
821 spawnfunc_info_player_deathmatch();
825 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
826 CTF Starting point for a player in team two (Blue).
827 Keys: "angle" viewing angle when spawning. */
828 void spawnfunc_info_player_team2()
830 if(g_assault) { remove(self); return; }
832 self.team = COLOR_TEAM2; // blue
833 spawnfunc_info_player_deathmatch();
836 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
837 CTF Starting point for a player in team three (Yellow).
838 Keys: "angle" viewing angle when spawning. */
839 void spawnfunc_info_player_team3()
841 if(g_assault) { remove(self); return; }
843 self.team = COLOR_TEAM3; // yellow
844 spawnfunc_info_player_deathmatch();
848 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
849 CTF Starting point for a player in team four (Purple).
850 Keys: "angle" viewing angle when spawning. */
851 void spawnfunc_info_player_team4()
853 if(g_assault) { remove(self); return; }
855 self.team = COLOR_TEAM4; // purple
856 spawnfunc_info_player_deathmatch();
859 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
860 CTF flag for team one (Red).
862 "angle" Angle the flag will point (minus 90 degrees)...
863 "model" model to use, note this needs red and blue as skins 0 and 1...
864 "noise" sound played when flag is picked up...
865 "noise1" sound played when flag is returned by a teammate...
866 "noise2" sound played when flag is captured...
867 "noise3" sound played when flag is lost in the field and respawns itself...
868 "noise4" sound played when flag is dropped by a player...
869 "noise5" sound played when flag touches the ground... */
870 void spawnfunc_item_flag_team1()
872 if(!g_ctf) { remove(self); return; }
874 ctf_FlagSetup(1, self); // 1 = red
877 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
878 CTF flag for team two (Blue).
880 "angle" Angle the flag will point (minus 90 degrees)...
881 "model" model to use, note this needs red and blue as skins 0 and 1...
882 "noise" sound played when flag is picked up...
883 "noise1" sound played when flag is returned by a teammate...
884 "noise2" sound played when flag is captured...
885 "noise3" sound played when flag is lost in the field and respawns itself...
886 "noise4" sound played when flag is dropped by a player...
887 "noise5" sound played when flag touches the ground... */
888 void spawnfunc_item_flag_team2()
890 if(!g_ctf) { remove(self); return; }
892 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
895 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
896 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
897 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.
899 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
900 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
901 void spawnfunc_ctf_team()
903 if(!g_ctf) { remove(self); return; }
905 self.classname = "ctf_team";
906 self.team = self.cnt + 1;
914 // code from here on is just to support maps that don't have flag and team entities
915 void ctf_SpawnTeam (string teamname, float teamcolor)
920 self.classname = "ctf_team";
921 self.netname = teamname;
922 self.cnt = teamcolor;
924 spawnfunc_ctf_team();
929 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
931 // if no teams are found, spawn defaults
932 if(find(world, classname, "ctf_team") == world)
934 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
935 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
936 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
942 void ctf_Initialize()
944 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
946 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
947 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
948 ctf_captureshield_force = autocvar_g_ctf_shield_force;
950 //g_ctf_win_mode = cvar("g_ctf_win_mode");
952 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
956 MUTATOR_DEFINITION(gamemode_ctf)
958 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
959 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
960 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
961 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
962 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
963 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
964 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
965 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
969 if(time > 1) // game loads at time 1
970 error("This is a game type and it cannot be added at runtime.");
978 error("This is a game type and it cannot be removed at runtime.");