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))) : "")));
31 string ctf_CaptureRecord(entity flag, entity player)
33 float cap_time, cap_record, success;
34 string cap_message, refername;
36 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots))
38 cap_record = ctf_captimerecord;
39 cap_time = (time - flag.ctf_pickuptime);
41 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
42 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
44 if(!ctf_captimerecord)
45 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
46 else if(cap_time < cap_record)
47 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
49 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
53 ctf_captimerecord = cap_time;
54 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
55 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
56 write_recordmarker(player, (time - cap_time), cap_time);
63 void ctf_FlagcarrierWaypoints(entity player)
65 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
66 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
67 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
68 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
72 // =======================
73 // CaptureShield Functions
74 // =======================
76 float ctf_CaptureShield_CheckStatus(entity p)
80 float players_worseeq, players_total;
82 if(ctf_captureshield_max_ratio <= 0)
85 s = PlayerScore_Add(p, SP_SCORE, 0);
86 if(s >= -ctf_captureshield_min_negscore)
89 players_total = players_worseeq = 0;
92 if(IsDifferentTeam(e, p))
94 se = PlayerScore_Add(e, SP_SCORE, 0);
100 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
101 // use this rule here
103 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
109 void ctf_CaptureShield_Update(entity player, float wanted_status)
111 float updated_status = ctf_CaptureShield_CheckStatus(player);
112 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
114 if(updated_status) // TODO csqc notifier for this // Samual: How?
115 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);
117 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);
119 player.ctf_captureshielded = updated_status;
123 float ctf_CaptureShield_Customize()
125 if(!other.ctf_captureshielded) { return FALSE; }
126 if(!IsDifferentTeam(self, other)) { return FALSE; }
131 void ctf_CaptureShield_Touch()
133 if(!other.ctf_captureshielded) { return; }
134 if(!IsDifferentTeam(self, other)) { return; }
136 vector mymid = (self.absmin + self.absmax) * 0.5;
137 vector othermid = (other.absmin + other.absmax) * 0.5;
139 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
140 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);
143 void ctf_CaptureShield_Spawn(entity flag)
145 entity shield = spawn();
148 shield.team = self.team;
149 shield.touch = ctf_CaptureShield_Touch;
150 shield.customizeentityforclient = ctf_CaptureShield_Customize;
151 shield.classname = "ctf_captureshield";
152 shield.effects = EF_ADDITIVE;
153 shield.movetype = MOVETYPE_NOCLIP;
154 shield.solid = SOLID_TRIGGER;
155 shield.avelocity = '7 0 11';
158 setorigin(shield, self.origin);
159 setmodel(shield, "models/ctf/shield.md3");
160 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
164 // ====================
165 // Drop/Pass/Throw Code
166 // ====================
168 void ctf_Handle_Drop(entity flag, entity player, float droptype)
171 player = (player ? player : flag.pass_sender);
174 flag.movetype = MOVETYPE_TOSS;
175 flag.takedamage = DAMAGE_YES;
176 flag.health = flag.max_flag_health;
177 flag.ctf_droptime = time;
178 flag.ctf_dropper = player;
179 flag.ctf_status = FLAG_DROPPED;
181 // messages and sounds
182 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
183 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
184 ctf_EventLog("dropped", player.team, player);
187 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
188 PlayerScore_Add(player, SP_CTF_DROPS, 1);
191 if(autocvar_g_ctf_flag_dropped_waypoint)
192 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));
194 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
196 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
197 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
200 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
202 if(droptype == DROP_PASS)
204 flag.pass_sender = world;
205 flag.pass_target = world;
209 void ctf_Handle_Retrieve(entity flag, entity player)
211 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
212 entity sender = flag.pass_sender;
214 // transfer flag to player
215 flag.ctf_carrier = player;
217 flag.owner.flagcarried = flag;
220 setattachment(flag, player, "");
221 setorigin(flag, FLAG_CARRY_OFFSET);
222 flag.movetype = MOVETYPE_NONE;
223 flag.takedamage = DAMAGE_NO;
224 flag.solid = SOLID_NOT;
225 flag.ctf_carrier = player;
226 flag.ctf_status = FLAG_CARRY;
228 // messages and sounds
229 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
230 ctf_EventLog("recieve", flag.team, player);
232 FOR_EACH_REALPLAYER(tmp_player)
234 if(tmp_player == sender)
235 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
236 else if(tmp_player == player)
237 centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", sender.netname));
238 else if(!IsDifferentTeam(tmp_player, sender))
239 centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
242 // create new waypoint
243 ctf_FlagcarrierWaypoints(player);
245 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
246 player.throw_antispam = sender.throw_antispam;
248 flag.pass_sender = world;
249 flag.pass_target = world;
252 void ctf_Handle_Throw(entity player, entity reciever, float droptype)
254 entity flag = player.flagcarried;
257 if(!flag) { return; }
258 if((droptype == DROP_PASS) && !reciever) { return; }
260 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
263 setattachment(flag, world, "");
264 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
265 flag.owner.flagcarried = world;
267 flag.solid = SOLID_TRIGGER;
268 flag.ctf_droptime = time;
270 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
276 WarpZone_RefSys_MakeSameRefSys(flag, player);
277 targ_origin = WarpZone_RefSys_TransformOrigin(reciever, flag, (0.5 * (reciever.absmin + reciever.absmax)));
278 flag.velocity = (normalize(targ_origin - player.origin) * autocvar_g_ctf_pass_velocity);
284 makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
285 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_drop_velocity)), FALSE);
291 flag.velocity = '0 0 0'; // do nothing
298 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom())), FALSE);
308 flag.movetype = MOVETYPE_FLY;
309 flag.takedamage = DAMAGE_NO;
310 flag.pass_sender = player;
311 flag.pass_target = reciever;
312 flag.ctf_status = FLAG_PASSING;
315 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
316 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), targ_origin, player.origin);
317 ctf_EventLog("pass", flag.team, player);
331 ctf_Handle_Drop(flag, player, droptype);
336 // kill old waypointsprite
337 WaypointSprite_Ping(player.wps_flagcarrier);
338 WaypointSprite_Kill(player.wps_flagcarrier);
340 if(player.wps_enemyflagcarrier)
341 WaypointSprite_Kill(player.wps_enemyflagcarrier);
344 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
352 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
354 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
355 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
356 float old_time, new_time;
358 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
360 // messages and sounds
361 Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
362 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
366 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
367 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
372 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
373 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
375 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
376 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
377 if(!old_time || new_time < old_time)
378 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
381 if(autocvar_g_ctf_flag_capture_effects)
383 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
384 shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
388 if(capturetype == CAPTURE_NORMAL)
390 WaypointSprite_Kill(player.wps_flagcarrier);
391 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
395 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
396 ctf_RespawnFlag(enemy_flag);
399 void ctf_Handle_Return(entity flag, entity player)
401 // messages and sounds
402 //centerprint(player, strcat("You returned the ", flag.netname));
403 Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
404 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
405 ctf_EventLog("return", flag.team, player);
408 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_return")); // reward for return
409 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
411 TeamScore_AddToTeam(flag.team, ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
415 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -ctf_ReadScore("penalty_returned")); // punish the player who dropped the flag
416 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
417 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
421 ctf_RespawnFlag(flag);
424 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
427 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
428 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
429 float pickup_dropped_score; // used to calculate dropped pickup score
431 // attach the flag to the player
433 player.flagcarried = flag;
434 setattachment(flag, player, "");
435 setorigin(flag, FLAG_CARRY_OFFSET);
438 flag.movetype = MOVETYPE_NONE;
439 flag.takedamage = DAMAGE_NO;
440 flag.solid = SOLID_NOT;
441 flag.angles = '0 0 0';
442 flag.ctf_carrier = player;
443 flag.ctf_status = FLAG_CARRY;
447 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
448 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
452 // messages and sounds
453 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
454 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
455 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
457 FOR_EACH_REALPLAYER(tmp_player)
459 if(tmp_player == player)
460 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
461 else if(!IsDifferentTeam(tmp_player, player))
462 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
463 else if(!IsDifferentTeam(tmp_player, flag))
464 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
469 case PICKUP_BASE: ctf_EventLog("steal", flag.team, player); break;
470 case PICKUP_DROPPED: ctf_EventLog("pickup", flag.team, player); break;
475 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
480 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
486 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);
487 pickup_dropped_score = floor((ctf_ReadScore("score_pickup_dropped_late") * (1 - pickup_dropped_score) + ctf_ReadScore("score_pickup_dropped_early") * pickup_dropped_score) + 0.5);
488 print("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
489 PlayerTeamScore_AddScore(player, pickup_dropped_score);
497 if(pickuptype == PICKUP_BASE)
499 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
500 if((player.speedrunning) && (ctf_captimerecord))
501 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
505 if(autocvar_g_ctf_flag_pickup_effects)
506 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
509 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
510 ctf_FlagcarrierWaypoints(player);
511 WaypointSprite_Ping(player.wps_flagcarrier);
515 // ===================
516 // Main Flag Functions
517 // ===================
519 void ctf_CheckFlagReturn(entity flag, float returntype)
521 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
523 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
527 case RETURN_DROPPED: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
528 case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
529 case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
530 case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
534 { bprint("The ", flag.netname, " has returned to base\n"); break; }
536 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
537 ctf_EventLog("returned", flag.team, world);
538 ctf_RespawnFlag(flag);
542 void ctf_CheckStalemate(void)
545 float stale_red_flags, stale_blue_flags;
548 entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
550 // build list of stale flags
551 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
553 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
554 if(tmp_entity.ctf_status != FLAG_BASE)
555 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
557 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
558 ctf_staleflaglist = tmp_entity;
560 switch(tmp_entity.team)
562 case COLOR_TEAM1: ++stale_red_flags; break;
563 case COLOR_TEAM2: ++stale_blue_flags; break;
568 if(stale_red_flags && stale_blue_flags)
569 ctf_stalemate = TRUE;
570 else if(!stale_red_flags && !stale_blue_flags)
571 ctf_stalemate = FALSE;
573 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
576 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
578 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
579 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));
582 if not(wpforenemy_announced)
584 FOR_EACH_REALPLAYER(tmp_entity)
585 if(tmp_entity.flagcarried)
586 centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
588 centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
590 wpforenemy_announced = TRUE;
595 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
597 if(ITEM_DAMAGE_NEEDKILL(deathtype))
599 // automatically kill the flag and return it
601 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
604 if(autocvar_g_ctf_flag_return_damage)
606 // reduce health and check if it should be returned
607 self.health = self.health - damage;
608 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
618 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
621 if(self == ctf_worldflaglist) // only for the first flag
622 FOR_EACH_CLIENT(tmp_entity)
623 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
626 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
627 dprint("wtf the flag got squashed?\n");
628 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
629 if(!trace_startsolid) // can we resize it without getting stuck?
630 setsize(self, FLAG_MIN, FLAG_MAX); }
632 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
637 self.angles = '0 0 0';
645 switch(self.ctf_status)
649 if(autocvar_g_ctf_dropped_capture_radius)
651 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
652 if(tmp_entity.ctf_status == FLAG_DROPPED)
653 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
654 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
661 if(autocvar_g_ctf_flag_dropped_floatinwater)
663 vector midpoint = ((self.absmin + self.absmax) * 0.5);
664 if(pointcontents(midpoint) == CONTENT_WATER)
666 self.velocity = self.velocity * 0.5;
668 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
669 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
671 { self.movetype = MOVETYPE_FLY; }
673 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
675 if(autocvar_g_ctf_flag_return_dropped)
677 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
680 ctf_CheckFlagReturn(self, RETURN_DROPPED);
684 if(autocvar_g_ctf_flag_return_time)
686 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
687 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
695 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
698 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
702 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
706 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
708 if(time >= wpforenemy_nextthink)
710 ctf_CheckStalemate();
711 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
717 case FLAG_PASSING: // todo make work with warpzones
719 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
720 vector old_targ_origin = targ_origin;
721 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin);
722 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
724 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");
726 if((self.pass_target.deadflag != DEAD_NO)
727 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
728 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
729 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
731 ctf_Handle_Drop(self, world, DROP_PASS);
733 else // still a viable target, go for it
735 vector desired_direction = normalize(targ_origin - self.origin);
736 vector current_direction = normalize(self.velocity);
738 self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity);
743 default: // this should never happen
745 dprint("ctf_FlagThink(): Flag exists with no status?\n");
753 if(gameover) { return; }
755 entity toucher = other;
757 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
758 if(ITEM_TOUCH_NEEDKILL())
761 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
765 // special touch behaviors
766 if(toucher.vehicle_flags & VHF_ISVEHICLE)
768 if(autocvar_g_ctf_allow_vehicle_touch)
769 toucher = toucher.owner; // the player is actually the vehicle owner, not other
771 return; // do nothing
773 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
775 if(time > self.wait) // if we haven't in a while, play a sound/effect
777 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
778 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
779 self.wait = time + FLAG_TOUCHRATE;
783 else if(toucher.deadflag != DEAD_NO) { return; }
785 switch(self.ctf_status)
789 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
790 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
791 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
792 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
798 if(!IsDifferentTeam(toucher, self))
799 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
800 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
801 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
807 dprint("Someone touched a flag even though it was being carried?\n");
813 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
815 if(IsDifferentTeam(toucher, self.pass_sender))
816 ctf_Handle_Return(self, toucher);
818 ctf_Handle_Retrieve(self, toucher);
825 void ctf_RespawnFlag(entity flag)
827 // reset the player (if there is one)
828 if((flag.owner) && (flag.owner.flagcarried == flag))
830 if(flag.owner.wps_enemyflagcarrier)
831 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
833 WaypointSprite_Kill(flag.wps_flagcarrier);
835 flag.owner.flagcarried = world;
837 if(flag.speedrunning)
838 ctf_FakeTimeLimit(flag.owner, -1);
841 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
842 { WaypointSprite_Kill(flag.wps_flagdropped); }
845 setattachment(flag, world, "");
846 setorigin(flag, flag.ctf_spawnorigin);
848 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
849 flag.takedamage = DAMAGE_NO;
850 flag.health = flag.max_flag_health;
851 flag.solid = SOLID_TRIGGER;
852 flag.velocity = '0 0 0';
853 flag.angles = flag.mangle;
854 flag.flags = FL_ITEM | FL_NOTARGET;
856 flag.ctf_status = FLAG_BASE;
858 flag.pass_sender = world;
859 flag.pass_target = world;
860 flag.ctf_carrier = world;
861 flag.ctf_dropper = world;
862 flag.ctf_pickuptime = 0;
863 flag.ctf_droptime = 0;
865 wpforenemy_announced = FALSE;
871 if(self.owner.classname == "player")
872 ctf_Handle_Throw(self.owner, world, DROP_RESET);
874 ctf_RespawnFlag(self);
877 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
880 waypoint_spawnforitem_force(self, self.origin);
881 self.nearestwaypointtimeout = 0; // activate waypointing again
882 self.bot_basewaypoint = self.nearestwaypoint;
885 WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
886 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
888 // captureshield setup
889 ctf_CaptureShield_Spawn(self);
892 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
895 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.
896 self = flag; // for later usage with droptofloor()
899 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
900 ctf_worldflaglist = flag;
902 setattachment(flag, world, "");
904 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
905 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
906 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
907 flag.classname = "item_flag_team";
908 flag.target = "###item###"; // wut?
909 flag.flags = FL_ITEM | FL_NOTARGET;
910 flag.solid = SOLID_TRIGGER;
911 flag.takedamage = DAMAGE_NO;
912 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
913 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
914 flag.health = flag.max_flag_health;
915 flag.event_damage = ctf_FlagDamage;
916 flag.pushable = TRUE;
917 flag.teleportable = TELEPORT_NORMAL;
918 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
919 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
920 flag.velocity = '0 0 0';
921 flag.mangle = flag.angles;
922 flag.reset = ctf_Reset;
923 flag.touch = ctf_FlagTouch;
924 flag.think = ctf_FlagThink;
925 flag.nextthink = time + FLAG_THINKRATE;
926 flag.ctf_status = FLAG_BASE;
928 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
929 if(!flag.scale) { flag.scale = FLAG_SCALE; }
930 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
931 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
932 if(!flag.passeffect) { flag.passeffect = ((!teamnumber) ? "red_pass" : "blue_pass"); } // invert the team number of the flag to pass as enemy team color
935 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
936 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
937 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
938 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.
939 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
940 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
941 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
944 precache_sound(flag.snd_flag_taken);
945 precache_sound(flag.snd_flag_returned);
946 precache_sound(flag.snd_flag_capture);
947 precache_sound(flag.snd_flag_respawn);
948 precache_sound(flag.snd_flag_dropped);
949 precache_sound(flag.snd_flag_touch);
950 precache_sound(flag.snd_flag_pass);
951 precache_model(flag.model);
952 precache_model("models/ctf/shield.md3");
953 precache_model("models/ctf/shockwavetransring.md3");
956 setmodel(flag, flag.model); // precision set below
957 setsize(flag, FLAG_MIN, FLAG_MAX);
958 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
960 if(autocvar_g_ctf_flag_glowtrails)
962 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
967 flag.effects |= EF_LOWPRECISION;
968 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
969 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
972 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
974 flag.dropped_origin = flag.origin;
976 flag.movetype = MOVETYPE_NONE;
978 else // drop to floor, automatically find a platform and set that as spawn origin
980 flag.noalign = FALSE;
983 flag.movetype = MOVETYPE_TOSS;
986 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
994 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
998 // initially clear items so they can be set as necessary later.
999 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1000 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1002 // scan through all the flags and notify the client about them
1003 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1005 switch(flag.ctf_status)
1010 if((flag.owner == self) || (flag.pass_sender == self))
1011 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1013 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1018 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1024 // item for stopping players from capturing the flag too often
1025 if(self.ctf_captureshielded)
1026 self.items |= IT_CTF_SHIELDED;
1028 // update the health of the flag carrier waypointsprite
1029 if(self.wps_flagcarrier)
1030 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1035 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1037 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1039 if(frag_target == frag_attacker) // damage done to yourself
1041 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1042 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1044 else // damage done to everyone else
1046 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1047 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1050 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1052 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)))
1053 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier); // TODO: only do this if there is a significant loss of health?
1058 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1060 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1062 PlayerTeamScore_AddScore(frag_attacker, ctf_ReadScore("score_kill"));
1063 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1066 if(frag_target.flagcarried)
1067 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1072 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1075 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1078 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1080 if(self.flagcarried)
1081 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1086 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1088 if(self.flagcarried)
1089 if(!autocvar_g_ctf_portalteleport)
1090 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1095 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1097 entity player = self;
1099 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1101 // pass the flag to a team mate
1102 if(autocvar_g_ctf_pass)
1104 entity head, closest_target;
1105 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1107 while(head) // find the closest acceptable target to pass to
1109 if(head.classname == "player" && head.deadflag == DEAD_NO)
1110 if(head != player && !IsDifferentTeam(head, player))
1111 if(!head.speedrunning && (!head.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1113 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1115 if(clienttype(head) == CLIENTTYPE_BOT)
1117 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1118 ctf_Handle_Throw(head, player, DROP_PASS);
1122 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
1123 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1125 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1128 else if(player.flagcarried)
1132 if(vlen(player.origin - WarpZone_UnTransformOrigin(head, head.origin)) < vlen(player.origin - WarpZone_UnTransformOrigin(closest_target, closest_target.origin)))
1133 { closest_target = head; }
1135 else { closest_target = head; }
1141 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return 0; }
1144 // throw the flag in front of you
1145 if(autocvar_g_ctf_drop && player.flagcarried)
1146 { ctf_Handle_Throw(player, world, DROP_THROW); }
1152 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1154 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1156 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1158 else // create a normal help me waypointsprite
1160 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');
1161 WaypointSprite_Ping(self.wps_helpme);
1167 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1169 if(vh_player.flagcarried)
1171 if(!autocvar_g_ctf_flagcarrier_allow_vehicle_carry)
1173 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1177 setattachment(vh_player.flagcarried, vh_vehicle, "");
1178 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1179 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1180 //vh_player.flagcarried.angles = '0 0 0';
1187 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1189 if(vh_player.flagcarried)
1191 setattachment(vh_player.flagcarried, vh_player, "");
1192 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1193 vh_player.flagcarried.scale = FLAG_SCALE;
1194 vh_player.flagcarried.angles = '0 0 0';
1200 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1202 if(self.flagcarried)
1204 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1205 ctf_RespawnFlag(self);
1211 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1213 entity flag; // temporary entity for the search method
1215 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1217 switch(flag.ctf_status)
1222 // lock the flag, game is over
1223 flag.movetype = MOVETYPE_NONE;
1224 flag.takedamage = DAMAGE_NO;
1225 flag.solid = SOLID_NOT;
1226 flag.nextthink = 0; // stop thinking
1228 print("stopping the ", flag.netname, " from moving.\n");
1236 // do nothing for these flags
1250 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1251 CTF Starting point for a player in team one (Red).
1252 Keys: "angle" viewing angle when spawning. */
1253 void spawnfunc_info_player_team1()
1255 if(g_assault) { remove(self); return; }
1257 self.team = COLOR_TEAM1; // red
1258 spawnfunc_info_player_deathmatch();
1262 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1263 CTF Starting point for a player in team two (Blue).
1264 Keys: "angle" viewing angle when spawning. */
1265 void spawnfunc_info_player_team2()
1267 if(g_assault) { remove(self); return; }
1269 self.team = COLOR_TEAM2; // blue
1270 spawnfunc_info_player_deathmatch();
1273 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
1274 CTF Starting point for a player in team three (Yellow).
1275 Keys: "angle" viewing angle when spawning. */
1276 void spawnfunc_info_player_team3()
1278 if(g_assault) { remove(self); return; }
1280 self.team = COLOR_TEAM3; // yellow
1281 spawnfunc_info_player_deathmatch();
1285 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
1286 CTF Starting point for a player in team four (Purple).
1287 Keys: "angle" viewing angle when spawning. */
1288 void spawnfunc_info_player_team4()
1290 if(g_assault) { remove(self); return; }
1292 self.team = COLOR_TEAM4; // purple
1293 spawnfunc_info_player_deathmatch();
1296 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1297 CTF flag for team one (Red).
1299 "angle" Angle the flag will point (minus 90 degrees)...
1300 "model" model to use, note this needs red and blue as skins 0 and 1...
1301 "noise" sound played when flag is picked up...
1302 "noise1" sound played when flag is returned by a teammate...
1303 "noise2" sound played when flag is captured...
1304 "noise3" sound played when flag is lost in the field and respawns itself...
1305 "noise4" sound played when flag is dropped by a player...
1306 "noise5" sound played when flag touches the ground... */
1307 void spawnfunc_item_flag_team1()
1309 if(!g_ctf) { remove(self); return; }
1311 ctf_FlagSetup(1, self); // 1 = red
1314 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1315 CTF flag for team two (Blue).
1317 "angle" Angle the flag will point (minus 90 degrees)...
1318 "model" model to use, note this needs red and blue as skins 0 and 1...
1319 "noise" sound played when flag is picked up...
1320 "noise1" sound played when flag is returned by a teammate...
1321 "noise2" sound played when flag is captured...
1322 "noise3" sound played when flag is lost in the field and respawns itself...
1323 "noise4" sound played when flag is dropped by a player...
1324 "noise5" sound played when flag touches the ground... */
1325 void spawnfunc_item_flag_team2()
1327 if(!g_ctf) { remove(self); return; }
1329 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
1332 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
1333 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
1334 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.
1336 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
1337 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
1338 void spawnfunc_ctf_team()
1340 if(!g_ctf) { remove(self); return; }
1342 self.classname = "ctf_team";
1343 self.team = self.cnt + 1;
1351 // code from here on is just to support maps that don't have flag and team entities
1352 void ctf_SpawnTeam (string teamname, float teamcolor)
1357 self.classname = "ctf_team";
1358 self.netname = teamname;
1359 self.cnt = teamcolor;
1361 spawnfunc_ctf_team();
1366 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
1368 // if no teams are found, spawn defaults
1369 if(find(world, classname, "ctf_team") == world)
1371 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
1372 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
1373 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
1379 void ctf_Initialize()
1381 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
1383 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
1384 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
1385 ctf_captureshield_force = autocvar_g_ctf_shield_force;
1387 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
1391 MUTATOR_DEFINITION(gamemode_ctf)
1393 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
1394 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
1395 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
1396 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
1397 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
1398 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
1399 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
1400 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
1401 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
1402 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
1403 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
1404 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
1405 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
1409 if(time > 1) // game loads at time 1
1410 error("This is a game type and it cannot be added at runtime.");
1418 error("This is a game type and it cannot be removed at runtime.");