1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: March 30th, 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, entity reciever)
130 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
131 entity flag = player.flagcarried;
132 if(!flag) { return; }
135 player.flagcarried = world;
136 WaypointSprite_Ping(player.wps_flagcarrier);
137 WaypointSprite_Kill(player.wps_flagcarrier);
139 // transfer flag to reciever
140 flag.owner = reciever;
141 flag.owner.flagcarried = flag;
142 flag.ctf_pickupid = reciever.playerid;
143 setattachment(flag, reciever, "");
144 setorigin(flag, FLAG_CARRY_OFFSET);
146 // messages and sounds
147 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
148 ctf_EventLog("pass", flag.team, player);
149 ctf_EventLog("recieve", flag.team, reciever);
150 FOR_EACH_PLAYER(tmp_player)
151 if(tmp_player == player)
152 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", reciever.netname));
153 else if(tmp_player == reciever)
154 centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", player.netname));
155 else if(tmp_player.team == player.team)
156 centerprint(tmp_player, strcat(player.netname, " passed the ", flag.netname, " to ", reciever.netname));
158 // create new waypoint
159 WaypointSprite_Spawn("flagcarrier", 0, 0, reciever, '0 0 64', world, reciever.team, reciever, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0');
160 WaypointSprite_UpdateMaxHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
161 WaypointSprite_UpdateHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(reciever.health, reciever.armorvalue, autocvar_g_balance_armor_blockpercent));
162 WaypointSprite_UpdateTeamRadar(reciever.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
165 void ctf_Handle_Drop(entity player, float droptype)
167 entity flag = player.flagcarried;
168 if(!flag) { return; }
170 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
173 setattachment(flag, world, "");
174 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
175 flag.owner.flagcarried = world;
177 flag.movetype = MOVETYPE_TOSS;
178 flag.solid = SOLID_TRIGGER;
179 flag.takedamage = DAMAGE_YES;
180 flag.health = flag.max_flag_health;
184 case DROPTYPE_THROWN:
186 makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
187 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_throw_velocity)), FALSE);
192 case DROPTYPE_NORMAL:
194 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
199 flag.ctf_droptime = time;
200 flag.ctf_dropperid = player.playerid;
201 flag.ctf_status = FLAG_DROPPED;
203 // messages and sounds
204 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
205 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
206 ctf_EventLog("dropped", player.team, player);
209 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
210 PlayerScore_Add(player, SP_CTF_DROPS, 1);
213 if(autocvar_g_ctf_flag_dropped_waypoint)
214 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)
216 WaypointSprite_Ping(player.wps_flagcarrier);
217 WaypointSprite_Kill(player.wps_flagcarrier);
219 if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
221 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
222 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
226 ctf_CaptureShield_Update(player, 0); // shield only
228 // check if the flag will fall off the map
229 trace_startsolid = FALSE;
230 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
232 dprint("FLAG FALLTHROUGH will happen SOON\n");
235 void ctf_Handle_Capture(entity flag, entity player)
238 float cap_time, cap_record, success;
239 string cap_message, refername;
242 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
243 cap_record = ctf_captimerecord;
244 cap_time = (time - player.flagcarried.ctf_pickuptime);
246 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
247 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
249 if(!ctf_captimerecord)
250 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
251 else if(cap_time < cap_record)
252 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
254 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
257 ctf_captimerecord = cap_time;
258 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
259 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
260 write_recordmarker(player, (time - cap_time), cap_time); } }
262 // messages and sounds
263 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
264 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav"
265 ctf_EventLog("capture", player.flagcarried.team, player);
268 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
269 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
272 if (autocvar_g_ctf_flag_capture_effects)
274 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
275 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
279 WaypointSprite_Kill(player.wps_flagcarrier);
282 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
284 ctf_RespawnFlag(player.flagcarried);
287 void ctf_Handle_Return(entity flag, entity player)
289 // messages and sounds
290 //centerprint(player, strcat("You returned ", flag.netname));
291 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
292 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
293 ctf_EventLog("return", flag.team, player);
296 PlayerTeamScore_AddScore(player, ctf_ReadScore(strcat("score_return", ((player.playerid == flag.playerid) ? "_by_killer" : "")))); // reward for return
297 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
299 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
300 FOR_EACH_PLAYER(player) if(player.playerid == flag.ctf_dropperid) // punish the player who dropped the flag
302 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
303 ctf_CaptureShield_Update(player, 0); // shield only
307 ctf_RespawnFlag(flag);
310 void ctf_Handle_Pickup_Base(entity flag, entity player)
312 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
313 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
315 // attach the flag to the player
317 player.flagcarried = flag;
318 setattachment(flag, player, "");
319 setorigin(flag, FLAG_CARRY_OFFSET);
322 flag.movetype = MOVETYPE_NONE;
323 flag.takedamage = DAMAGE_NO;
324 flag.solid = SOLID_NOT;
325 flag.angles = '0 0 0';
326 flag.ctf_pickuptime = time; // used for timing runs
327 flag.ctf_pickupid = player.playerid;
328 flag.ctf_status = FLAG_CARRY;
330 // messages and sounds
331 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
332 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
333 ctf_EventLog("steal", flag.team, player);
334 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
335 FOR_EACH_PLAYER(tmp_player)
336 if(tmp_player == player)
337 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
338 else if(tmp_player.team == player.team)
339 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
340 else if(tmp_player.team == flag.team)
341 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
344 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
345 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
348 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
349 if((player.speedrunning) && (ctf_captimerecord))
350 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
353 if (autocvar_g_ctf_flag_pickup_effects)
355 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
359 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)
360 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
361 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
362 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
363 WaypointSprite_Ping(player.wps_flagcarrier);
366 void ctf_Handle_Pickup_Dropped(entity flag, entity player)
369 float returnscore = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1); // can this be division by zero? FIXME
370 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
371 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
373 // attach the flag to the player
375 player.flagcarried = flag;
376 setattachment(flag, player, "");
377 setorigin(flag, FLAG_CARRY_OFFSET);
380 flag.movetype = MOVETYPE_NONE;
381 flag.takedamage = DAMAGE_NO;
382 flag.health = flag.max_flag_health;
383 flag.solid = SOLID_NOT;
384 flag.angles = '0 0 0';
385 //flag.ctf_pickuptime = time; // don't update pickuptime since this isn't a real steal.
386 flag.ctf_pickupid = player.playerid;
387 flag.ctf_status = FLAG_CARRY;
389 // messages and sounds
390 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
391 sound (player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
392 ctf_EventLog("pickup", flag.team, player);
393 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
394 FOR_EACH_PLAYER(tmp_player)
395 if(tmp_player == player)
396 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
397 else if(tmp_player.team == player.team)
398 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
399 else if(tmp_player.team == flag.team)
400 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
403 returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5);
404 print("score is ", ftos(returnscore), "\n");
405 PlayerTeamScore_AddScore(player, returnscore);
406 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
409 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
411 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
415 WaypointSprite_Kill(flag.wps_flagdropped);
416 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)
417 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
418 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
419 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
420 WaypointSprite_Ping(player.wps_flagcarrier);
424 // ===================
425 // Main Flag Functions
426 // ===================
428 void ctf_CheckFlagReturn(entity flag)
430 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
432 if((flag.health <= 0) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_returntime))
434 bprint("The ", flag.netname, " has returned to base\n");
435 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
436 ctf_EventLog("returned", flag.team, world);
437 ctf_RespawnFlag(flag);
441 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
443 if(deathtype == DEATH_HURTTRIGGER || deathtype == DEATH_SLIME || deathtype == DEATH_LAVA)
445 // automatically kill the flag and return it
447 ctf_CheckFlagReturn(self);
450 if(autocvar_g_ctf_flag_take_damage)
452 self.health = self.health - damage;
453 ctf_CheckFlagReturn(self);
462 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
465 if(self == ctf_worldflaglist) // only for the first flag
466 FOR_EACH_CLIENT(tmp_entity)
467 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
470 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
471 dprint("wtf the flag got squished?\n");
472 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
473 if(!trace_startsolid) // can we resize it without getting stuck?
474 setsize(self, FLAG_MIN, FLAG_MAX); }
477 switch(self.ctf_status)
479 case FLAG_BASE: // nothing to do here
483 if(autocvar_g_ctf_flag_returntime)
485 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_returntime) * FLAG_THINKRATE);
486 ctf_CheckFlagReturn(self);
491 if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord))
493 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
494 sound(self, CH_TRIGGER, self.snd_flag_respawn, VOL_BASE, ATTN_NONE);
495 ctf_EventLog("returned", self.team, world);
496 ctf_RespawnFlag(tmp_entity);
500 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
506 default: // this should never happen
507 dprint("Think: Flag exists with no status?\n");
514 if(gameover) { return; }
515 if(!self) { return; }
516 if(other.deadflag != DEAD_NO) { return; }
517 if(other.classname != "player")
518 { // The flag just touched an object, most likely the world
519 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
520 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
523 else if(self.wait > time) { return; }
525 switch(self.ctf_status)
528 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
529 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
530 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
531 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
535 if(other.team == self.team)
536 ctf_Handle_Return(self, other); // other just returned his own flag
537 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
538 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
542 dprint("Someone touched a flag even though it was being carried?\n");
545 default: // this should never happen
546 dprint("Touch: Flag exists with no status?\n");
551 void ctf_RespawnFlag(entity flag)
553 // reset the player (if there is one)
554 if((flag.owner) && (flag.owner.flagcarried == flag))
556 WaypointSprite_Kill(flag.wps_flagcarrier);
557 flag.owner.flagcarried = world;
559 if(flag.speedrunning)
560 ctf_FakeTimeLimit(flag.owner, -1);
563 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
564 { WaypointSprite_Kill(flag.wps_flagdropped); }
567 setattachment(flag, world, "");
568 setorigin(flag, flag.ctf_spawnorigin); // replace with flag.ctf_spawnorigin
569 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
570 flag.takedamage = DAMAGE_NO;
571 flag.health = flag.max_flag_health;
572 flag.solid = SOLID_TRIGGER;
573 flag.velocity = '0 0 0';
574 flag.angles = flag.mangle;
575 flag.ctf_status = FLAG_BASE;
576 flag.flags = FL_ITEM | FL_NOTARGET;
583 if(self.owner.classname == "player")
584 ctf_Handle_Drop(self.owner, DROPTYPE_NORMAL);
586 ctf_RespawnFlag(self);
589 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
592 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.
595 waypoint_spawnforitem_force(self, self.origin);
596 self.nearestwaypointtimeout = 0; // activate waypointing again
597 self.bot_basewaypoint = self.nearestwaypoint;
600 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), self.origin + '0 0 64', self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
601 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
603 // captureshield setup
604 ctf_CaptureShield_Spawn(self);
607 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
610 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.
611 self = flag; // for later usage with droptofloor()
614 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
615 ctf_worldflaglist = flag;
617 setattachment(flag, world, "");
619 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
620 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
621 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
622 flag.classname = "item_flag_team";
623 flag.target = "###item###"; // wut?
624 flag.flags = FL_ITEM | FL_NOTARGET;
625 flag.solid = SOLID_TRIGGER;
626 flag.takedamage = DAMAGE_NO;
627 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
628 flag.max_flag_health = ((autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
629 flag.health = flag.max_flag_health;
630 flag.event_damage = ctf_FlagDamage;
631 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
632 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
633 flag.velocity = '0 0 0';
634 flag.mangle = flag.angles;
635 flag.reset = ctf_Reset;
636 flag.touch = ctf_FlagTouch;
637 flag.think = ctf_FlagThink;
638 flag.nextthink = time + 0.2;
639 flag.ctf_status = FLAG_BASE;
642 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
643 setmodel(flag, flag.model); // precision set below
644 setsize(flag, FLAG_MIN, FLAG_MAX);
645 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
646 if(!flag.scale) { flag.scale = FLAG_SCALE; }
648 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
650 if(autocvar_g_ctf_flag_glowtrails)
652 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
657 flag.effects |= EF_LOWPRECISION;
658 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
659 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
662 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
663 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
664 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
665 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.
666 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
667 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "keepaway/touch.wav"; } // again has no team-based sound // FIXME
670 precache_sound(flag.snd_flag_taken);
671 precache_sound(flag.snd_flag_returned);
672 precache_sound(flag.snd_flag_capture);
673 precache_sound(flag.snd_flag_respawn);
674 precache_sound(flag.snd_flag_dropped);
675 precache_sound(flag.snd_flag_touch);
676 precache_model(flag.model);
677 precache_model("models/ctf/shield.md3");
678 precache_model("models/ctf/shockwavetransring.md3");
681 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
683 flag.dropped_origin = flag.origin;
685 flag.movetype = MOVETYPE_NONE;
687 else // drop to floor, automatically find a platform and set that as spawn origin
689 flag.noalign = FALSE;
692 flag.movetype = MOVETYPE_TOSS;
695 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
703 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
705 if(self.flagcarried) { ctf_Handle_Drop(self, DROPTYPE_NORMAL); }
709 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
713 // initially clear items so they can be set as necessary later.
714 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
715 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
717 // item for stopping players from capturing the flag too often
718 if(self.ctf_captureshielded)
719 self.items |= IT_CTF_SHIELDED;
721 // scan through all the flags and notify the client about them
722 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
724 if(flag.ctf_status == FLAG_CARRY)
725 if(flag.owner == self)
726 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
728 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
729 else if(flag.ctf_status == FLAG_DROPPED)
730 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
733 if(self.wps_flagcarrier) { WaypointSprite_UpdateHealth(self.wps_flagcarrier, self.health); }
738 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
740 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
742 if(frag_target == frag_attacker) // damage done to yourself
744 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
745 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
747 else // damage done to noncarriers
749 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
750 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
756 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
758 frag_score = 0; // no frags counted in ctf
759 return (g_ctf_ignore_frags); // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
762 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
764 entity player = self;
766 if((player.flagcarried) && !(player.speedrunning))
768 if(autocvar_g_ctf_allow_pass)
771 head = findradius(player.origin, autocvar_g_ctf_pass_radius);
775 if(head.classname == "player" && head.deadflag == DEAD_NO)
776 if(head != player && !IsDifferentTeam(head, player))
778 ctf_Handle_Pass(player, head);
785 if(autocvar_g_ctf_allow_drop) { ctf_Handle_Drop(player, DROPTYPE_THROWN); }
795 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
796 CTF Starting point for a player in team one (Red).
797 Keys: "angle" viewing angle when spawning. */
798 void spawnfunc_info_player_team1()
800 if(g_assault) { remove(self); return; }
802 self.team = COLOR_TEAM1; // red
803 spawnfunc_info_player_deathmatch();
807 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
808 CTF Starting point for a player in team two (Blue).
809 Keys: "angle" viewing angle when spawning. */
810 void spawnfunc_info_player_team2()
812 if(g_assault) { remove(self); return; }
814 self.team = COLOR_TEAM2; // blue
815 spawnfunc_info_player_deathmatch();
818 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
819 CTF Starting point for a player in team three (Yellow).
820 Keys: "angle" viewing angle when spawning. */
821 void spawnfunc_info_player_team3()
823 if(g_assault) { remove(self); return; }
825 self.team = COLOR_TEAM3; // yellow
826 spawnfunc_info_player_deathmatch();
830 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
831 CTF Starting point for a player in team four (Purple).
832 Keys: "angle" viewing angle when spawning. */
833 void spawnfunc_info_player_team4()
835 if(g_assault) { remove(self); return; }
837 self.team = COLOR_TEAM4; // purple
838 spawnfunc_info_player_deathmatch();
841 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
842 CTF flag for team one (Red).
844 "angle" Angle the flag will point (minus 90 degrees)...
845 "model" model to use, note this needs red and blue as skins 0 and 1...
846 "noise" sound played when flag is picked up...
847 "noise1" sound played when flag is returned by a teammate...
848 "noise2" sound played when flag is captured...
849 "noise3" sound played when flag is lost in the field and respawns itself...
850 "noise4" sound played when flag is dropped by a player...
851 "noise5" sound played when flag touches the ground... */
852 void spawnfunc_item_flag_team1()
854 if(!g_ctf) { remove(self); return; }
856 ctf_FlagSetup(1, self); // 1 = red
859 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
860 CTF flag for team two (Blue).
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_team2()
872 if(!g_ctf) { remove(self); return; }
874 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
877 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
878 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
879 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.
881 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
882 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
883 void spawnfunc_ctf_team()
885 if(!g_ctf) { remove(self); return; }
887 self.classname = "ctf_team";
888 self.team = self.cnt + 1;
896 // code from here on is just to support maps that don't have flag and team entities
897 void ctf_SpawnTeam (string teamname, float teamcolor)
902 self.classname = "ctf_team";
903 self.netname = teamname;
904 self.cnt = teamcolor;
906 spawnfunc_ctf_team();
911 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
913 // if no teams are found, spawn defaults
914 if(find(world, classname, "ctf_team") == world)
916 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
917 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
918 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
924 void ctf_Initialize()
926 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
928 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
929 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
930 ctf_captureshield_force = autocvar_g_ctf_shield_force;
932 //g_ctf_win_mode = cvar("g_ctf_win_mode");
934 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
938 MUTATOR_DEFINITION(gamemode_ctf)
940 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
941 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
942 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
943 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
944 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
945 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
946 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
947 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
951 if(time > 1) // game loads at time 1
952 error("This is a game type and it cannot be added at runtime.");
960 error("This is a game type and it cannot be removed at runtime.");