1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: March 30th, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 string ctf_CaptureRecord(entity flag, entity player)
25 float cap_time, cap_record, success;
26 string cap_message, refername;
28 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots))
30 cap_record = ctf_captimerecord;
31 cap_time = (time - flag.ctf_pickuptime);
33 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
34 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
36 if(!ctf_captimerecord)
37 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
38 else if(cap_time < cap_record)
39 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
41 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
45 ctf_captimerecord = cap_time;
46 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
47 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
48 write_recordmarker(player, (time - cap_time), cap_time);
55 void ctf_FlagcarrierWaypoints(entity player)
57 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
58 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
59 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
60 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
64 // =======================
65 // CaptureShield Functions
66 // =======================
68 float ctf_CaptureShield_CheckStatus(entity p)
72 float players_worseeq, players_total;
74 if(ctf_captureshield_max_ratio <= 0)
77 s = PlayerScore_Add(p, SP_SCORE, 0);
78 if(s >= -ctf_captureshield_min_negscore)
81 players_total = players_worseeq = 0;
84 if(IsDifferentTeam(e, p))
86 se = PlayerScore_Add(e, SP_SCORE, 0);
92 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
95 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
101 void ctf_CaptureShield_Update(entity player, float wanted_status)
103 float updated_status = ctf_CaptureShield_CheckStatus(player);
104 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
106 if(updated_status) // TODO csqc notifier for this // Samual: How?
107 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);
109 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);
111 player.ctf_captureshielded = updated_status;
115 float ctf_CaptureShield_Customize()
117 if(!other.ctf_captureshielded) { return FALSE; }
118 if(!IsDifferentTeam(self, other)) { return FALSE; }
123 void ctf_CaptureShield_Touch()
125 if(!other.ctf_captureshielded) { return; }
126 if(!IsDifferentTeam(self, other)) { return; }
128 vector mymid = (self.absmin + self.absmax) * 0.5;
129 vector othermid = (other.absmin + other.absmax) * 0.5;
131 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
132 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);
135 void ctf_CaptureShield_Spawn(entity flag)
137 entity shield = spawn();
140 shield.team = self.team;
141 shield.touch = ctf_CaptureShield_Touch;
142 shield.customizeentityforclient = ctf_CaptureShield_Customize;
143 shield.classname = "ctf_captureshield";
144 shield.effects = EF_ADDITIVE;
145 shield.movetype = MOVETYPE_NOCLIP;
146 shield.solid = SOLID_TRIGGER;
147 shield.avelocity = '7 0 11';
150 setorigin(shield, self.origin);
151 setmodel(shield, "models/ctf/shield.md3");
152 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
156 // ====================
157 // Drop/Pass/Throw Code
158 // ====================
160 void ctf_Handle_Drop(entity flag, entity player, float droptype)
163 player = (player ? player : flag.pass_sender);
166 flag.movetype = MOVETYPE_TOSS;
167 flag.takedamage = DAMAGE_YES;
168 flag.health = flag.max_flag_health;
169 flag.ctf_droptime = time;
170 flag.ctf_dropper = player;
171 flag.ctf_status = FLAG_DROPPED;
173 // messages and sounds
174 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
175 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
176 ctf_EventLog("dropped", player.team, player);
179 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_penalty_drop);
180 PlayerScore_Add(player, SP_CTF_DROPS, 1);
183 if(autocvar_g_ctf_flag_dropped_waypoint)
184 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
186 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
188 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
189 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
192 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
194 if(droptype == DROP_PASS)
196 flag.pass_sender = world;
197 flag.pass_target = world;
201 void ctf_Handle_Retrieve(entity flag, entity player)
203 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
204 entity sender = flag.pass_sender;
206 // transfer flag to player
208 flag.owner.flagcarried = flag;
211 setattachment(flag, player, "");
212 setorigin(flag, FLAG_CARRY_OFFSET);
213 flag.movetype = MOVETYPE_NONE;
214 flag.takedamage = DAMAGE_NO;
215 flag.solid = SOLID_NOT;
216 flag.ctf_carrier = player;
217 flag.ctf_status = FLAG_CARRY;
219 // messages and sounds
220 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
221 ctf_EventLog("recieve", flag.team, player);
223 FOR_EACH_REALPLAYER(tmp_player)
225 if(tmp_player == sender)
226 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
227 else if(tmp_player == player)
228 centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", sender.netname));
229 else if(!IsDifferentTeam(tmp_player, sender))
230 centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
233 // create new waypoint
234 ctf_FlagcarrierWaypoints(player);
236 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
237 player.throw_antispam = sender.throw_antispam;
239 flag.pass_sender = world;
240 flag.pass_target = world;
243 void ctf_Handle_Throw(entity player, entity reciever, float droptype)
245 entity flag = player.flagcarried;
248 if(!flag) { return; }
249 if((droptype == DROP_PASS) && !reciever) { return; }
251 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
254 setattachment(flag, world, "");
255 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
256 flag.owner.flagcarried = world;
258 flag.solid = SOLID_TRIGGER;
259 flag.ctf_droptime = time;
261 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
267 WarpZone_RefSys_MakeSameRefSys(flag, player);
268 targ_origin = WarpZone_RefSys_TransformOrigin(reciever, flag, (0.5 * (reciever.absmin + reciever.absmax)));
269 flag.velocity = (normalize(targ_origin - player.origin) * autocvar_g_ctf_pass_velocity);
275 makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
276 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_drop_velocity)), FALSE);
282 flag.velocity = '0 0 0'; // do nothing
289 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom())), FALSE);
299 flag.movetype = MOVETYPE_FLY;
300 flag.takedamage = DAMAGE_NO;
301 flag.pass_sender = player;
302 flag.pass_target = reciever;
303 flag.ctf_status = FLAG_PASSING;
306 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
307 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), targ_origin, player.origin);
308 ctf_EventLog("pass", flag.team, player);
322 ctf_Handle_Drop(flag, player, droptype);
327 // kill old waypointsprite
328 WaypointSprite_Ping(player.wps_flagcarrier);
329 WaypointSprite_Kill(player.wps_flagcarrier);
331 if(player.wps_enemyflagcarrier)
332 WaypointSprite_Kill(player.wps_enemyflagcarrier);
335 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
343 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
345 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
346 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
347 float old_time, new_time;
349 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
351 // messages and sounds
352 Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
353 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
357 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
358 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
363 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
364 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
366 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
367 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
368 if(!old_time || new_time < old_time)
369 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
372 if(autocvar_g_ctf_flag_capture_effects)
374 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
375 shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
379 if(capturetype == CAPTURE_NORMAL)
381 WaypointSprite_Kill(player.wps_flagcarrier);
382 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
386 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
387 ctf_RespawnFlag(enemy_flag);
390 void ctf_Handle_Return(entity flag, entity player)
392 // messages and sounds
393 //centerprint(player, strcat("You returned the ", flag.netname));
394 Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
395 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
396 ctf_EventLog("return", flag.team, player);
399 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
400 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
402 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
406 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
407 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
408 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
412 ctf_RespawnFlag(flag);
415 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
418 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
419 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
420 float pickup_dropped_score; // used to calculate dropped pickup score
422 // attach the flag to the player
424 player.flagcarried = flag;
425 setattachment(flag, player, "");
426 setorigin(flag, FLAG_CARRY_OFFSET);
429 flag.movetype = MOVETYPE_NONE;
430 flag.takedamage = DAMAGE_NO;
431 flag.solid = SOLID_NOT;
432 flag.angles = '0 0 0';
433 flag.ctf_carrier = player;
434 flag.ctf_status = FLAG_CARRY;
438 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
439 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
443 // messages and sounds
444 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
445 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
446 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
448 FOR_EACH_REALPLAYER(tmp_player)
450 if(tmp_player == player)
451 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
452 else if(!IsDifferentTeam(tmp_player, player))
453 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
454 else if(!IsDifferentTeam(tmp_player, flag))
455 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
460 case PICKUP_BASE: ctf_EventLog("steal", flag.team, player); break;
461 case PICKUP_DROPPED: ctf_EventLog("pickup", flag.team, player); break;
466 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
471 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
477 pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
478 pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
479 print("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
480 PlayerTeamScore_AddScore(player, pickup_dropped_score);
488 if(pickuptype == PICKUP_BASE)
490 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
491 if((player.speedrunning) && (ctf_captimerecord))
492 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
496 if(autocvar_g_ctf_flag_pickup_effects)
497 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
500 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
501 ctf_FlagcarrierWaypoints(player);
502 WaypointSprite_Ping(player.wps_flagcarrier);
506 // ===================
507 // Main Flag Functions
508 // ===================
510 void ctf_CheckFlagReturn(entity flag, float returntype)
512 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
514 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
518 case RETURN_DROPPED: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
519 case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
520 case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
521 case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
525 { bprint("The ", flag.netname, " has returned to base\n"); break; }
527 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
528 ctf_EventLog("returned", flag.team, world);
529 ctf_RespawnFlag(flag);
533 void ctf_CheckStalemate(void)
536 float stale_red_flags, stale_blue_flags;
539 entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
541 // build list of stale flags
542 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
544 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
545 if(tmp_entity.ctf_status != FLAG_BASE)
546 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
548 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
549 ctf_staleflaglist = tmp_entity;
551 switch(tmp_entity.team)
553 case COLOR_TEAM1: ++stale_red_flags; break;
554 case COLOR_TEAM2: ++stale_blue_flags; break;
559 if(stale_red_flags && stale_blue_flags)
560 ctf_stalemate = TRUE;
561 else if(!stale_red_flags && !stale_blue_flags)
562 ctf_stalemate = FALSE;
564 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
567 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
569 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
570 WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
573 if not(wpforenemy_announced)
575 FOR_EACH_REALPLAYER(tmp_entity)
576 if(tmp_entity.flagcarried)
577 centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
579 centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
581 wpforenemy_announced = TRUE;
586 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
588 if(ITEM_DAMAGE_NEEDKILL(deathtype))
590 // automatically kill the flag and return it
592 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
595 if(autocvar_g_ctf_flag_return_damage)
597 // reduce health and check if it should be returned
598 self.health = self.health - damage;
599 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
609 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
612 if(self == ctf_worldflaglist) // only for the first flag
613 FOR_EACH_CLIENT(tmp_entity)
614 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
617 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
618 dprint("wtf the flag got squashed?\n");
619 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
620 if(!trace_startsolid) // can we resize it without getting stuck?
621 setsize(self, FLAG_MIN, FLAG_MAX); }
623 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
628 self.angles = '0 0 0';
636 switch(self.ctf_status)
640 if(autocvar_g_ctf_dropped_capture_radius)
642 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
643 if(tmp_entity.ctf_status == FLAG_DROPPED)
644 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
645 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
652 if(autocvar_g_ctf_flag_dropped_floatinwater)
654 vector midpoint = ((self.absmin + self.absmax) * 0.5);
655 if(pointcontents(midpoint) == CONTENT_WATER)
657 self.velocity = self.velocity * 0.5;
659 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
660 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
662 { self.movetype = MOVETYPE_FLY; }
664 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
666 if(autocvar_g_ctf_flag_return_dropped)
668 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
671 ctf_CheckFlagReturn(self, RETURN_DROPPED);
675 if(autocvar_g_ctf_flag_return_time)
677 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
678 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
686 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
689 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
693 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
697 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
699 if(time >= wpforenemy_nextthink)
701 ctf_CheckStalemate();
702 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
708 case FLAG_PASSING: // todo make work with warpzones
710 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
711 vector old_targ_origin = targ_origin;
712 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin);
713 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
715 print(strcat("self: ", vtos(self.origin), ", old: ", vtos(old_targ_origin), " (", ftos(vlen(self.origin - old_targ_origin)), "qu)"), ", transformed: ", vtos(targ_origin), " (", ftos(vlen(self.origin - targ_origin)), "qu)", ".\n");
717 if((self.pass_target.deadflag != DEAD_NO)
718 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
719 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
720 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
722 ctf_Handle_Drop(self, world, DROP_PASS);
724 else // still a viable target, go for it
726 vector desired_direction = normalize(targ_origin - self.origin);
727 vector current_direction = normalize(self.velocity);
729 self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity);
734 default: // this should never happen
736 dprint("ctf_FlagThink(): Flag exists with no status?\n");
744 if(gameover) { return; }
746 entity toucher = other;
748 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
749 if(ITEM_TOUCH_NEEDKILL())
752 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
756 // special touch behaviors
757 if(toucher.vehicle_flags & VHF_ISVEHICLE)
759 if(autocvar_g_ctf_allow_vehicle_touch)
760 toucher = toucher.owner; // the player is actually the vehicle owner, not other
762 return; // do nothing
764 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
766 if(time > self.wait) // if we haven't in a while, play a sound/effect
768 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
769 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
770 self.wait = time + FLAG_TOUCHRATE;
774 else if(toucher.deadflag != DEAD_NO) { return; }
776 switch(self.ctf_status)
780 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
781 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
782 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
783 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
789 if(!IsDifferentTeam(toucher, self))
790 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
791 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
792 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
798 dprint("Someone touched a flag even though it was being carried?\n");
804 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
806 if(IsDifferentTeam(toucher, self.pass_sender))
807 ctf_Handle_Return(self, toucher);
809 ctf_Handle_Retrieve(self, toucher);
816 void ctf_RespawnFlag(entity flag)
818 // reset the player (if there is one)
819 if((flag.owner) && (flag.owner.flagcarried == flag))
821 if(flag.owner.wps_enemyflagcarrier)
822 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
824 WaypointSprite_Kill(flag.wps_flagcarrier);
826 flag.owner.flagcarried = world;
828 if(flag.speedrunning)
829 ctf_FakeTimeLimit(flag.owner, -1);
832 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
833 { WaypointSprite_Kill(flag.wps_flagdropped); }
836 setattachment(flag, world, "");
837 setorigin(flag, flag.ctf_spawnorigin);
839 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
840 flag.takedamage = DAMAGE_NO;
841 flag.health = flag.max_flag_health;
842 flag.solid = SOLID_TRIGGER;
843 flag.velocity = '0 0 0';
844 flag.angles = flag.mangle;
845 flag.flags = FL_ITEM | FL_NOTARGET;
847 flag.ctf_status = FLAG_BASE;
849 flag.pass_sender = world;
850 flag.pass_target = world;
851 flag.ctf_carrier = world;
852 flag.ctf_dropper = world;
853 flag.ctf_pickuptime = 0;
854 flag.ctf_droptime = 0;
856 wpforenemy_announced = FALSE;
862 if(self.owner.classname == "player")
863 ctf_Handle_Throw(self.owner, world, DROP_RESET);
865 ctf_RespawnFlag(self);
868 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
871 waypoint_spawnforitem_force(self, self.origin);
872 self.nearestwaypointtimeout = 0; // activate waypointing again
873 self.bot_basewaypoint = self.nearestwaypoint;
876 WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
877 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
879 // captureshield setup
880 ctf_CaptureShield_Spawn(self);
883 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
886 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.
887 self = flag; // for later usage with droptofloor()
890 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
891 ctf_worldflaglist = flag;
893 setattachment(flag, world, "");
895 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
896 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
897 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
898 flag.classname = "item_flag_team";
899 flag.target = "###item###"; // wut?
900 flag.flags = FL_ITEM | FL_NOTARGET;
901 flag.solid = SOLID_TRIGGER;
902 flag.takedamage = DAMAGE_NO;
903 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
904 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
905 flag.health = flag.max_flag_health;
906 flag.event_damage = ctf_FlagDamage;
907 flag.pushable = TRUE;
908 flag.teleportable = TELEPORT_NORMAL;
909 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
910 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
911 flag.velocity = '0 0 0';
912 flag.mangle = flag.angles;
913 flag.reset = ctf_Reset;
914 flag.touch = ctf_FlagTouch;
915 flag.think = ctf_FlagThink;
916 flag.nextthink = time + FLAG_THINKRATE;
917 flag.ctf_status = FLAG_BASE;
919 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
920 if(!flag.scale) { flag.scale = FLAG_SCALE; }
921 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
922 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
923 if(!flag.passeffect) { flag.passeffect = ((!teamnumber) ? "red_pass" : "blue_pass"); } // invert the team number of the flag to pass as enemy team color
926 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
927 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
928 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
929 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.
930 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
931 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
932 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
935 precache_sound(flag.snd_flag_taken);
936 precache_sound(flag.snd_flag_returned);
937 precache_sound(flag.snd_flag_capture);
938 precache_sound(flag.snd_flag_respawn);
939 precache_sound(flag.snd_flag_dropped);
940 precache_sound(flag.snd_flag_touch);
941 precache_sound(flag.snd_flag_pass);
942 precache_model(flag.model);
943 precache_model("models/ctf/shield.md3");
944 precache_model("models/ctf/shockwavetransring.md3");
947 setmodel(flag, flag.model); // precision set below
948 setsize(flag, FLAG_MIN, FLAG_MAX);
949 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
951 if(autocvar_g_ctf_flag_glowtrails)
953 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
958 flag.effects |= EF_LOWPRECISION;
959 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
960 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
963 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
965 flag.dropped_origin = flag.origin;
967 flag.movetype = MOVETYPE_NONE;
969 else // drop to floor, automatically find a platform and set that as spawn origin
971 flag.noalign = FALSE;
974 flag.movetype = MOVETYPE_TOSS;
977 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
985 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
989 // initially clear items so they can be set as necessary later.
990 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
991 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
993 // scan through all the flags and notify the client about them
994 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
996 switch(flag.ctf_status)
1001 if((flag.owner == self) || (flag.pass_sender == self))
1002 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1004 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1009 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1015 // item for stopping players from capturing the flag too often
1016 if(self.ctf_captureshielded)
1017 self.items |= IT_CTF_SHIELDED;
1019 // update the health of the flag carrier waypointsprite
1020 if(self.wps_flagcarrier)
1021 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1026 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1028 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1030 if(frag_target == frag_attacker) // damage done to yourself
1032 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1033 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1035 else // damage done to everyone else
1037 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1038 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1041 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1043 if(autocvar_g_ctf_flagcarrier_auto_helpme_when_damaged > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1044 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier); // TODO: only do this if there is a significant loss of health?
1049 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1051 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1053 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1054 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1057 if(frag_target.flagcarried)
1058 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1063 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1066 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1069 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1071 if(self.flagcarried)
1072 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1077 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1079 if(self.flagcarried)
1080 if(!autocvar_g_ctf_portalteleport)
1081 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1086 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1088 entity player = self;
1090 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1092 // pass the flag to a team mate
1093 if(autocvar_g_ctf_pass)
1095 entity head, closest_target;
1096 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1098 while(head) // find the closest acceptable target to pass to
1100 if(head.classname == "player" && head.deadflag == DEAD_NO)
1101 if(head != player && !IsDifferentTeam(head, player))
1102 if(!head.speedrunning && (!head.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1104 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1106 if(clienttype(head) == CLIENTTYPE_BOT)
1108 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1109 ctf_Handle_Throw(head, player, DROP_PASS);
1113 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
1114 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1116 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1119 else if(player.flagcarried)
1123 if(vlen(player.origin - WarpZone_UnTransformOrigin(head, head.origin)) < vlen(player.origin - WarpZone_UnTransformOrigin(closest_target, closest_target.origin)))
1124 { closest_target = head; }
1126 else { closest_target = head; }
1132 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return 0; }
1135 // throw the flag in front of you
1136 if(autocvar_g_ctf_drop && player.flagcarried)
1137 { ctf_Handle_Throw(player, world, DROP_THROW); }
1143 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1145 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1147 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1149 else // create a normal help me waypointsprite
1151 WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0');
1152 WaypointSprite_Ping(self.wps_helpme);
1158 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1160 if(vh_player.flagcarried)
1162 if(!autocvar_g_ctf_flagcarrier_allow_vehicle_carry)
1164 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1168 setattachment(vh_player.flagcarried, vh_vehicle, "");
1169 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1170 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1171 //vh_player.flagcarried.angles = '0 0 0';
1178 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1180 if(vh_player.flagcarried)
1182 setattachment(vh_player.flagcarried, vh_player, "");
1183 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1184 vh_player.flagcarried.scale = FLAG_SCALE;
1185 vh_player.flagcarried.angles = '0 0 0';
1191 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1193 if(self.flagcarried)
1195 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1196 ctf_RespawnFlag(self);
1202 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1204 entity flag; // temporary entity for the search method
1206 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1208 switch(flag.ctf_status)
1213 // lock the flag, game is over
1214 flag.movetype = MOVETYPE_NONE;
1215 flag.takedamage = DAMAGE_NO;
1216 flag.solid = SOLID_NOT;
1217 flag.nextthink = 0; // stop thinking
1219 print("stopping the ", flag.netname, " from moving.\n");
1227 // do nothing for these flags
1241 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1242 CTF Starting point for a player in team one (Red).
1243 Keys: "angle" viewing angle when spawning. */
1244 void spawnfunc_info_player_team1()
1246 if(g_assault) { remove(self); return; }
1248 self.team = COLOR_TEAM1; // red
1249 spawnfunc_info_player_deathmatch();
1253 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1254 CTF Starting point for a player in team two (Blue).
1255 Keys: "angle" viewing angle when spawning. */
1256 void spawnfunc_info_player_team2()
1258 if(g_assault) { remove(self); return; }
1260 self.team = COLOR_TEAM2; // blue
1261 spawnfunc_info_player_deathmatch();
1264 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
1265 CTF Starting point for a player in team three (Yellow).
1266 Keys: "angle" viewing angle when spawning. */
1267 void spawnfunc_info_player_team3()
1269 if(g_assault) { remove(self); return; }
1271 self.team = COLOR_TEAM3; // yellow
1272 spawnfunc_info_player_deathmatch();
1276 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
1277 CTF Starting point for a player in team four (Purple).
1278 Keys: "angle" viewing angle when spawning. */
1279 void spawnfunc_info_player_team4()
1281 if(g_assault) { remove(self); return; }
1283 self.team = COLOR_TEAM4; // purple
1284 spawnfunc_info_player_deathmatch();
1287 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1288 CTF flag for team one (Red).
1290 "angle" Angle the flag will point (minus 90 degrees)...
1291 "model" model to use, note this needs red and blue as skins 0 and 1...
1292 "noise" sound played when flag is picked up...
1293 "noise1" sound played when flag is returned by a teammate...
1294 "noise2" sound played when flag is captured...
1295 "noise3" sound played when flag is lost in the field and respawns itself...
1296 "noise4" sound played when flag is dropped by a player...
1297 "noise5" sound played when flag touches the ground... */
1298 void spawnfunc_item_flag_team1()
1300 if(!g_ctf) { remove(self); return; }
1302 ctf_FlagSetup(1, self); // 1 = red
1305 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1306 CTF flag for team two (Blue).
1308 "angle" Angle the flag will point (minus 90 degrees)...
1309 "model" model to use, note this needs red and blue as skins 0 and 1...
1310 "noise" sound played when flag is picked up...
1311 "noise1" sound played when flag is returned by a teammate...
1312 "noise2" sound played when flag is captured...
1313 "noise3" sound played when flag is lost in the field and respawns itself...
1314 "noise4" sound played when flag is dropped by a player...
1315 "noise5" sound played when flag touches the ground... */
1316 void spawnfunc_item_flag_team2()
1318 if(!g_ctf) { remove(self); return; }
1320 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
1323 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
1324 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
1325 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.
1327 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
1328 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
1329 void spawnfunc_ctf_team()
1331 if(!g_ctf) { remove(self); return; }
1333 self.classname = "ctf_team";
1334 self.team = self.cnt + 1;
1342 // code from here on is just to support maps that don't have flag and team entities
1343 void ctf_SpawnTeam (string teamname, float teamcolor)
1348 self.classname = "ctf_team";
1349 self.netname = teamname;
1350 self.cnt = teamcolor;
1352 spawnfunc_ctf_team();
1357 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
1359 // if no teams are found, spawn defaults
1360 if(find(world, classname, "ctf_team") == world)
1362 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
1363 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
1364 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
1370 void ctf_Initialize()
1372 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
1374 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
1375 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
1376 ctf_captureshield_force = autocvar_g_ctf_shield_force;
1378 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
1382 MUTATOR_DEFINITION(gamemode_ctf)
1384 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
1385 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
1386 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
1387 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
1388 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
1389 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
1390 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
1391 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
1392 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
1393 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
1394 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
1395 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
1396 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
1400 if(time > 1) // game loads at time 1
1401 error("This is a game type and it cannot be added at runtime.");
1409 error("This is a game type and it cannot be removed at runtime.");