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));
159 te_lightning2(world, reciever.origin, player.origin);
161 // create new waypoint
162 WaypointSprite_Spawn("flagcarrier", 0, 0, reciever, '0 0 64', world, reciever.team, reciever, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0');
163 WaypointSprite_UpdateMaxHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
164 WaypointSprite_UpdateHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(reciever.health, reciever.armorvalue, autocvar_g_balance_armor_blockpercent));
165 WaypointSprite_UpdateTeamRadar(reciever.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
168 void ctf_Handle_Drop(entity player, float droptype)
170 entity flag = player.flagcarried;
171 if(!flag) { return; }
173 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
176 setattachment(flag, world, "");
177 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
178 flag.owner.flagcarried = world;
180 flag.movetype = MOVETYPE_TOSS;
181 flag.solid = SOLID_TRIGGER;
182 flag.takedamage = DAMAGE_YES;
183 flag.health = flag.max_flag_health;
187 case DROPTYPE_THROWN:
189 makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
190 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_throw_velocity)), FALSE);
195 case DROPTYPE_NORMAL:
197 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
202 flag.ctf_droptime = time;
203 flag.ctf_dropperid = player.playerid;
204 flag.ctf_status = FLAG_DROPPED;
206 // messages and sounds
207 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
208 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
209 ctf_EventLog("dropped", player.team, player);
212 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
213 PlayerScore_Add(player, SP_CTF_DROPS, 1);
216 if(autocvar_g_ctf_flag_dropped_waypoint)
217 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)
219 WaypointSprite_Ping(player.wps_flagcarrier);
220 WaypointSprite_Kill(player.wps_flagcarrier);
222 if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
224 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
225 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
229 ctf_CaptureShield_Update(player, 0); // shield only
231 // check if the flag will fall off the map
232 trace_startsolid = FALSE;
233 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
235 dprint("FLAG FALLTHROUGH will happen SOON\n");
238 void ctf_Handle_Capture(entity flag, entity player)
241 float cap_time, cap_record, success;
242 string cap_message, refername;
245 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
246 cap_record = ctf_captimerecord;
247 cap_time = (time - player.flagcarried.ctf_pickuptime);
249 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
250 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
252 if(!ctf_captimerecord)
253 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
254 else if(cap_time < cap_record)
255 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
257 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
260 ctf_captimerecord = cap_time;
261 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
262 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
263 write_recordmarker(player, (time - cap_time), cap_time); } }
265 // messages and sounds
266 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
267 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav"
268 ctf_EventLog("capture", player.flagcarried.team, player);
271 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
272 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
275 if (autocvar_g_ctf_flag_capture_effects)
277 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
278 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
282 WaypointSprite_Kill(player.wps_flagcarrier);
285 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
287 ctf_RespawnFlag(player.flagcarried);
290 void ctf_Handle_Return(entity flag, entity player)
292 // messages and sounds
293 //centerprint(player, strcat("You returned ", flag.netname));
294 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
295 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
296 ctf_EventLog("return", flag.team, player);
299 PlayerTeamScore_AddScore(player, ctf_ReadScore(strcat("score_return", ((player.playerid == flag.playerid) ? "_by_killer" : "")))); // reward for return
300 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
302 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
303 FOR_EACH_PLAYER(player) if(player.playerid == flag.ctf_dropperid) // punish the player who dropped the flag
305 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
306 ctf_CaptureShield_Update(player, 0); // shield only
310 ctf_RespawnFlag(flag);
313 void ctf_Handle_Pickup_Base(entity flag, entity player)
315 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
316 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
318 // attach the flag to the player
320 player.flagcarried = flag;
321 setattachment(flag, player, "");
322 setorigin(flag, FLAG_CARRY_OFFSET);
325 flag.movetype = MOVETYPE_NONE;
326 flag.takedamage = DAMAGE_NO;
327 flag.solid = SOLID_NOT;
328 flag.angles = '0 0 0';
329 flag.ctf_pickuptime = time; // used for timing runs
330 flag.ctf_pickupid = player.playerid;
331 flag.ctf_status = FLAG_CARRY;
333 // messages and sounds
334 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
335 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
336 ctf_EventLog("steal", flag.team, player);
337 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
338 FOR_EACH_PLAYER(tmp_player)
339 if(tmp_player == player)
340 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
341 else if(tmp_player.team == player.team)
342 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
343 else if(tmp_player.team == flag.team)
344 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
347 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
348 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
351 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
352 if((player.speedrunning) && (ctf_captimerecord))
353 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
356 if (autocvar_g_ctf_flag_pickup_effects)
358 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
362 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)
363 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
364 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
365 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
366 WaypointSprite_Ping(player.wps_flagcarrier);
369 void ctf_Handle_Pickup_Dropped(entity flag, entity player)
372 float returnscore = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1); // can this be division by zero? FIXME
373 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
374 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
376 // attach the flag to the player
378 player.flagcarried = flag;
379 setattachment(flag, player, "");
380 setorigin(flag, FLAG_CARRY_OFFSET);
383 flag.movetype = MOVETYPE_NONE;
384 flag.takedamage = DAMAGE_NO;
385 flag.health = flag.max_flag_health;
386 flag.solid = SOLID_NOT;
387 flag.angles = '0 0 0';
388 //flag.ctf_pickuptime = time; // don't update pickuptime since this isn't a real steal.
389 flag.ctf_pickupid = player.playerid;
390 flag.ctf_status = FLAG_CARRY;
392 // messages and sounds
393 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
394 sound (player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
395 ctf_EventLog("pickup", flag.team, player);
396 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
397 FOR_EACH_PLAYER(tmp_player)
398 if(tmp_player == player)
399 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
400 else if(tmp_player.team == player.team)
401 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
402 else if(tmp_player.team == flag.team)
403 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
406 returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5);
407 print("score is ", ftos(returnscore), "\n");
408 PlayerTeamScore_AddScore(player, returnscore);
409 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
412 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
414 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
418 WaypointSprite_Kill(flag.wps_flagdropped);
419 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)
420 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
421 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
422 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
423 WaypointSprite_Ping(player.wps_flagcarrier);
427 // ===================
428 // Main Flag Functions
429 // ===================
431 void ctf_CheckFlagReturn(entity flag)
433 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
435 if((flag.health <= 0) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_returntime))
437 bprint("The ", flag.netname, " has returned to base\n");
438 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
439 ctf_EventLog("returned", flag.team, world);
440 ctf_RespawnFlag(flag);
444 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
446 if(deathtype == DEATH_HURTTRIGGER || deathtype == DEATH_SLIME || deathtype == DEATH_LAVA)
448 // automatically kill the flag and return it
450 ctf_CheckFlagReturn(self);
453 if(autocvar_g_ctf_flag_take_damage)
455 self.health = self.health - damage;
456 ctf_CheckFlagReturn(self);
465 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
468 if(self == ctf_worldflaglist) // only for the first flag
469 FOR_EACH_CLIENT(tmp_entity)
470 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
473 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
474 dprint("wtf the flag got squished?\n");
475 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
476 if(!trace_startsolid) // can we resize it without getting stuck?
477 setsize(self, FLAG_MIN, FLAG_MAX); }
480 switch(self.ctf_status)
490 if(autocvar_g_ctf_flag_returntime)
492 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_returntime) * FLAG_THINKRATE);
493 ctf_CheckFlagReturn(self);
500 if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord))
502 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
503 sound(self, CH_TRIGGER, self.snd_flag_respawn, VOL_BASE, ATTN_NONE);
504 ctf_EventLog("returned", self.team, world);
505 ctf_RespawnFlag(tmp_entity);
509 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
516 default: // this should never happen
518 dprint("ctf_FlagThink(): Flag exists with no status?\n");
526 if(gameover) { return; }
527 if(!self) { return; }
528 if(other.deadflag != DEAD_NO) { return; }
530 if(other.classname != "player") // The flag just touched an object, most likely the world
532 if(self.wait > time) // if we haven't in a while, play a sound/effect
534 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
535 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
540 switch(self.ctf_status)
543 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
544 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
545 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
546 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
550 if(other.team == self.team)
551 ctf_Handle_Return(self, other); // other just returned his own flag
552 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
553 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
557 dprint("Someone touched a flag even though it was being carried?\n");
560 default: // this should never happen
561 dprint("Touch: Flag exists with no status?\n");
565 self.wait = time + FLAG_TOUCHRATE;
568 void ctf_RespawnFlag(entity flag)
570 // reset the player (if there is one)
571 if((flag.owner) && (flag.owner.flagcarried == flag))
573 WaypointSprite_Kill(flag.wps_flagcarrier);
574 flag.owner.flagcarried = world;
576 if(flag.speedrunning)
577 ctf_FakeTimeLimit(flag.owner, -1);
580 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
581 { WaypointSprite_Kill(flag.wps_flagdropped); }
584 setattachment(flag, world, "");
585 setorigin(flag, flag.ctf_spawnorigin); // replace with flag.ctf_spawnorigin
586 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
587 flag.takedamage = DAMAGE_NO;
588 flag.health = flag.max_flag_health;
589 flag.solid = SOLID_TRIGGER;
590 flag.velocity = '0 0 0';
591 flag.angles = flag.mangle;
592 flag.ctf_status = FLAG_BASE;
593 flag.flags = FL_ITEM | FL_NOTARGET;
600 if(self.owner.classname == "player")
601 ctf_Handle_Drop(self.owner, DROPTYPE_NORMAL);
603 ctf_RespawnFlag(self);
606 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
609 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.
612 waypoint_spawnforitem_force(self, self.origin);
613 self.nearestwaypointtimeout = 0; // activate waypointing again
614 self.bot_basewaypoint = self.nearestwaypoint;
617 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), self.origin + '0 0 64', self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
618 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
620 // captureshield setup
621 ctf_CaptureShield_Spawn(self);
624 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
627 teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1.
628 self = flag; // for later usage with droptofloor()
631 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
632 ctf_worldflaglist = flag;
634 setattachment(flag, world, "");
636 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
637 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
638 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
639 flag.classname = "item_flag_team";
640 flag.target = "###item###"; // wut?
641 flag.flags = FL_ITEM | FL_NOTARGET;
642 flag.solid = SOLID_TRIGGER;
643 flag.takedamage = DAMAGE_NO;
644 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
645 flag.max_flag_health = ((autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
646 flag.health = flag.max_flag_health;
647 flag.event_damage = ctf_FlagDamage;
648 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
649 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
650 flag.velocity = '0 0 0';
651 flag.mangle = flag.angles;
652 flag.reset = ctf_Reset;
653 flag.touch = ctf_FlagTouch;
654 flag.think = ctf_FlagThink;
655 flag.nextthink = time + FLAG_THINKRATE;
656 flag.ctf_status = FLAG_BASE;
658 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
659 if(!flag.scale) { flag.scale = FLAG_SCALE; }
660 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
663 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
664 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
665 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
666 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.
667 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
668 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "keepaway/touch.wav"; } // again has no team-based sound // FIXME
671 precache_sound(flag.snd_flag_taken);
672 precache_sound(flag.snd_flag_returned);
673 precache_sound(flag.snd_flag_capture);
674 precache_sound(flag.snd_flag_respawn);
675 precache_sound(flag.snd_flag_dropped);
676 precache_sound(flag.snd_flag_touch);
677 precache_model(flag.model);
678 precache_model("models/ctf/shield.md3");
679 precache_model("models/ctf/shockwavetransring.md3");
682 setmodel(flag, flag.model); // precision set below
683 setsize(flag, FLAG_MIN, FLAG_MAX);
684 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
686 if(autocvar_g_ctf_flag_glowtrails)
688 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
693 flag.effects |= EF_LOWPRECISION;
694 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
695 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
698 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
700 flag.dropped_origin = flag.origin;
702 flag.movetype = MOVETYPE_NONE;
704 else // drop to floor, automatically find a platform and set that as spawn origin
706 flag.noalign = FALSE;
709 flag.movetype = MOVETYPE_TOSS;
712 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
720 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
722 if(self.flagcarried) { ctf_Handle_Drop(self, DROPTYPE_NORMAL); }
726 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
730 // initially clear items so they can be set as necessary later.
731 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
732 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
734 // item for stopping players from capturing the flag too often
735 if(self.ctf_captureshielded)
736 self.items |= IT_CTF_SHIELDED;
738 // scan through all the flags and notify the client about them
739 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
741 if(flag.ctf_status == FLAG_CARRY)
742 if(flag.owner == self)
743 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
745 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
746 else if(flag.ctf_status == FLAG_DROPPED)
747 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
750 if(self.wps_flagcarrier) { WaypointSprite_UpdateHealth(self.wps_flagcarrier, self.health); }
755 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
757 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
759 if(frag_target == frag_attacker) // damage done to yourself
761 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
762 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
764 else // damage done to noncarriers
766 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
767 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
773 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
775 frag_score = 0; // no frags counted in ctf
776 return (g_ctf_ignore_frags); // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
779 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
781 entity player = self;
783 if((player.flagcarried) && !(player.speedrunning))
785 if(autocvar_g_ctf_allow_pass)
787 entity head, closest_target;
788 head = findradius(player.origin, autocvar_g_ctf_pass_radius);
790 while(head) // find the closest acceptable target to pass to
792 if(head.classname == "player" && head.deadflag == DEAD_NO)
793 if(head != player && !IsDifferentTeam(head, player))
797 if(vlen(player.origin - head.origin) < vlen(player.origin - closest_target.origin))
798 closest_target = head;
801 closest_target = head;
806 if(closest_target) { ctf_Handle_Pass(player, closest_target); }
810 if(autocvar_g_ctf_allow_drop) { ctf_Handle_Drop(player, DROPTYPE_THROWN); }
820 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
821 CTF Starting point for a player in team one (Red).
822 Keys: "angle" viewing angle when spawning. */
823 void spawnfunc_info_player_team1()
825 if(g_assault) { remove(self); return; }
827 self.team = COLOR_TEAM1; // red
828 spawnfunc_info_player_deathmatch();
832 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
833 CTF Starting point for a player in team two (Blue).
834 Keys: "angle" viewing angle when spawning. */
835 void spawnfunc_info_player_team2()
837 if(g_assault) { remove(self); return; }
839 self.team = COLOR_TEAM2; // blue
840 spawnfunc_info_player_deathmatch();
843 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
844 CTF Starting point for a player in team three (Yellow).
845 Keys: "angle" viewing angle when spawning. */
846 void spawnfunc_info_player_team3()
848 if(g_assault) { remove(self); return; }
850 self.team = COLOR_TEAM3; // yellow
851 spawnfunc_info_player_deathmatch();
855 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
856 CTF Starting point for a player in team four (Purple).
857 Keys: "angle" viewing angle when spawning. */
858 void spawnfunc_info_player_team4()
860 if(g_assault) { remove(self); return; }
862 self.team = COLOR_TEAM4; // purple
863 spawnfunc_info_player_deathmatch();
866 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
867 CTF flag for team one (Red).
869 "angle" Angle the flag will point (minus 90 degrees)...
870 "model" model to use, note this needs red and blue as skins 0 and 1...
871 "noise" sound played when flag is picked up...
872 "noise1" sound played when flag is returned by a teammate...
873 "noise2" sound played when flag is captured...
874 "noise3" sound played when flag is lost in the field and respawns itself...
875 "noise4" sound played when flag is dropped by a player...
876 "noise5" sound played when flag touches the ground... */
877 void spawnfunc_item_flag_team1()
879 if(!g_ctf) { remove(self); return; }
881 ctf_FlagSetup(1, self); // 1 = red
884 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
885 CTF flag for team two (Blue).
887 "angle" Angle the flag will point (minus 90 degrees)...
888 "model" model to use, note this needs red and blue as skins 0 and 1...
889 "noise" sound played when flag is picked up...
890 "noise1" sound played when flag is returned by a teammate...
891 "noise2" sound played when flag is captured...
892 "noise3" sound played when flag is lost in the field and respawns itself...
893 "noise4" sound played when flag is dropped by a player...
894 "noise5" sound played when flag touches the ground... */
895 void spawnfunc_item_flag_team2()
897 if(!g_ctf) { remove(self); return; }
899 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
902 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
903 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
904 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.
906 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
907 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
908 void spawnfunc_ctf_team()
910 if(!g_ctf) { remove(self); return; }
912 self.classname = "ctf_team";
913 self.team = self.cnt + 1;
921 // code from here on is just to support maps that don't have flag and team entities
922 void ctf_SpawnTeam (string teamname, float teamcolor)
927 self.classname = "ctf_team";
928 self.netname = teamname;
929 self.cnt = teamcolor;
931 spawnfunc_ctf_team();
936 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
938 // if no teams are found, spawn defaults
939 if(find(world, classname, "ctf_team") == world)
941 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
942 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
943 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
949 void ctf_Initialize()
951 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
953 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
954 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
955 ctf_captureshield_force = autocvar_g_ctf_shield_force;
957 //g_ctf_win_mode = cvar("g_ctf_win_mode");
959 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
963 MUTATOR_DEFINITION(gamemode_ctf)
965 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
966 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
967 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
968 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
969 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
970 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
971 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
972 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
976 if(time > 1) // game loads at time 1
977 error("This is a game type and it cannot be added at runtime.");
985 error("This is a game type and it cannot be removed at runtime.");