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_AnnounceStolenFlag(entity flag, entity player)
65 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
66 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
68 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
70 FOR_EACH_PLAYER(tmp_player)
71 if(tmp_player == player)
72 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
73 else if(tmp_player.team == player.team)
74 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
75 else if(tmp_player.team == flag.team)
76 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
79 void ctf_FlagcarrierWaypoints(entity player)
81 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0');
82 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
83 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
84 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
88 // =======================
89 // CaptureShield Functions
90 // =======================
92 float ctf_CaptureShield_CheckStatus(entity p)
96 float players_worseeq, players_total;
98 if(ctf_captureshield_max_ratio <= 0)
101 s = PlayerScore_Add(p, SP_SCORE, 0);
102 if(s >= -ctf_captureshield_min_negscore)
105 players_total = players_worseeq = 0;
110 se = PlayerScore_Add(e, SP_SCORE, 0);
116 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
117 // use this rule here
119 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
125 void ctf_CaptureShield_Update(entity player, float wanted_status)
127 float updated_status = ctf_CaptureShield_CheckStatus(player);
128 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
130 if(updated_status) // TODO csqc notifier for this // Samual: How?
131 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);
133 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);
135 player.ctf_captureshielded = updated_status;
139 float ctf_CaptureShield_Customize()
141 if not(other.ctf_captureshielded) { return FALSE; }
142 if(self.team == other.team) { return FALSE; }
147 void ctf_CaptureShield_Touch()
149 if not(other.ctf_captureshielded) { return; }
150 if(self.team == other.team) { return; }
152 vector mymid = (self.absmin + self.absmax) * 0.5;
153 vector othermid = (other.absmin + other.absmax) * 0.5;
155 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
156 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);
159 void ctf_CaptureShield_Spawn(entity flag)
161 entity shield = spawn();
164 shield.team = self.team;
165 shield.touch = ctf_CaptureShield_Touch;
166 shield.customizeentityforclient = ctf_CaptureShield_Customize;
167 shield.classname = "ctf_captureshield";
168 shield.effects = EF_ADDITIVE;
169 shield.movetype = MOVETYPE_NOCLIP;
170 shield.solid = SOLID_TRIGGER;
171 shield.avelocity = '7 0 11';
174 setorigin(shield, self.origin);
175 setmodel(shield, "models/ctf/shield.md3");
176 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
179 // ====================
180 // Drop/Pass/Throw Code
181 // ====================
183 void ctf_Handle_Failed_Pass(entity flag)
185 print("ctf_Handle_Failed_Pass(entity flag) called.\n");
186 entity sender = flag.pass_sender;
188 flag.movetype = MOVETYPE_TOSS;
189 flag.takedamage = DAMAGE_YES;
190 flag.health = flag.max_flag_health;
191 flag.ctf_droptime = time;
192 flag.ctf_dropper = sender;
193 flag.ctf_status = FLAG_DROPPED;
195 // messages and sounds
196 Send_KillNotification(sender.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
197 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
198 ctf_EventLog("dropped", sender.team, sender);
201 PlayerTeamScore_AddScore(sender, -ctf_ReadScore("penalty_drop"));
202 PlayerScore_Add(sender, SP_CTF_DROPS, 1);
205 if(autocvar_g_ctf_flag_dropped_waypoint)
206 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : sender.team), flag, wps_flagdropped, FALSE, RADARICON_FLAG, '0 0.5 0' + ((flag.team == COLOR_TEAM1) ? '0.75 0 0' : '0 0 0.75'));
208 if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
210 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
211 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
214 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
216 flag.pass_sender = world;
217 flag.pass_target = world;
220 void ctf_Handle_Retrieve(entity flag, entity player)
222 print("ctf_Handle_Retrieve(entity flag, entity player) called.\n");
223 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
224 entity sender = flag.pass_sender;
226 // transfer flag to player
227 flag.ctf_carrier = player;
229 flag.owner.flagcarried = flag;
232 setattachment(flag, player, "");
233 setorigin(flag, FLAG_CARRY_OFFSET);
234 flag.movetype = MOVETYPE_NONE;
235 flag.takedamage = DAMAGE_NO;
236 flag.solid = SOLID_NOT;
237 flag.ctf_carrier = player;
238 flag.ctf_status = FLAG_CARRY;
240 // messages and sounds
241 sound(player, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTN_NORM);
242 ctf_EventLog("recieve", flag.team, player);
243 FOR_EACH_PLAYER(tmp_player)
244 if(tmp_player == sender)
245 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
246 else if(tmp_player == player)
247 centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", sender.netname));
248 else if(tmp_player.team == sender.team)
249 centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
251 // create new waypoint
252 ctf_FlagcarrierWaypoints(player);
254 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
255 player.throw_antispam = sender.throw_antispam;
257 flag.pass_sender = world;
258 flag.pass_target = world;
261 void ctf_Handle_Throw(entity player, entity reciever, float droptype)
263 entity flag = player.flagcarried;
265 if(!flag) { return; }
266 if((droptype == DROPTYPE_PASS) && !reciever) { return; }
268 //if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
271 setattachment(flag, world, "");
272 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
273 flag.owner.flagcarried = world;
275 flag.solid = SOLID_TRIGGER;
276 flag.ctf_droptime = time;
282 vector targ_origin = (0.5 * (reciever.absmin + reciever.absmax));
283 flag.velocity = (normalize(targ_origin - player.origin) * autocvar_g_ctf_throw_velocity);
289 makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
290 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_throw_velocity)), FALSE);
297 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
302 print("flag.velocity = ", vtos(flag.velocity), ".\n");
309 flag.movetype = MOVETYPE_FLY;
310 flag.takedamage = DAMAGE_NO;
311 flag.pass_sender = player;
312 flag.pass_target = reciever;
313 flag.ctf_status = FLAG_PASSING;
316 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
317 ctf_EventLog("pass", flag.team, player);
318 te_lightning2(world, reciever.origin, player.origin);
327 flag.movetype = MOVETYPE_TOSS;
328 flag.takedamage = DAMAGE_YES;
329 flag.health = flag.max_flag_health;
330 flag.ctf_dropper = player;
331 flag.ctf_status = FLAG_DROPPED;
333 // messages and sounds
334 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
335 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
336 ctf_EventLog("dropped", player.team, player);
339 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
340 PlayerScore_Add(player, SP_CTF_DROPS, 1);
343 if(autocvar_g_ctf_flag_dropped_waypoint)
344 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)
346 if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
348 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
349 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
355 // kill old waypointsprite
356 WaypointSprite_Ping(player.wps_flagcarrier);
357 WaypointSprite_Kill(player.wps_flagcarrier);
360 //ctf_CaptureShield_Update(player, 0); // shield only
368 void ctf_Handle_Dropped_Capture(entity flag, entity enemy_flag)
372 entity player = enemy_flag.ctf_dropper;
374 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
376 // messages and sounds
377 Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
378 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
379 ctf_EventLog("droppedcapture", enemy_flag.team, player);
382 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
383 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
386 if(autocvar_g_ctf_flag_capture_effects)
388 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
389 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
392 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
395 ctf_RespawnFlag(enemy_flag);
398 void ctf_Handle_Capture(entity flag, entity player)
400 // messages and sounds
401 Send_KillNotification(player.netname, player.flagcarried.netname, ctf_CaptureRecord(player.flagcarried, player), INFO_CAPTUREFLAG, MSG_INFO);
402 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
403 ctf_EventLog("capture", player.flagcarried.team, player);
406 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
407 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
410 if(autocvar_g_ctf_flag_capture_effects)
412 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
413 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
417 WaypointSprite_Kill(player.wps_flagcarrier);
420 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
421 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
423 ctf_RespawnFlag(player.flagcarried);
426 void ctf_Handle_Return(entity flag, entity player)
428 // messages and sounds
429 //centerprint(player, strcat("You returned ", flag.netname));
430 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
431 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
432 ctf_EventLog("return", flag.team, player);
435 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_return")); // reward for return
436 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
438 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
439 FOR_EACH_PLAYER(player) if(player == flag.ctf_dropper) // punish the player who dropped the flag
441 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
442 ctf_CaptureShield_Update(player, 0); // shield only
446 ctf_RespawnFlag(flag);
449 void ctf_Handle_Pickup_Base(entity flag, entity player)
451 // attach the flag to the player
453 player.flagcarried = flag;
454 setattachment(flag, player, "");
455 setorigin(flag, FLAG_CARRY_OFFSET);
458 flag.movetype = MOVETYPE_NONE;
459 flag.takedamage = DAMAGE_NO;
460 flag.solid = SOLID_NOT;
461 flag.angles = '0 0 0';
462 flag.ctf_pickuptime = time; // used for timing runs
463 flag.ctf_carrier = player;
464 flag.ctf_status = FLAG_CARRY;
466 // messages and sounds
467 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
468 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
469 ctf_EventLog("steal", flag.team, player);
470 ctf_AnnounceStolenFlag(flag, player);
473 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
474 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
477 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
478 if((player.speedrunning) && (ctf_captimerecord))
479 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
482 if (autocvar_g_ctf_flag_pickup_effects)
484 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
488 ctf_FlagcarrierWaypoints(player);
489 WaypointSprite_Ping(player.wps_flagcarrier);
492 void ctf_Handle_Pickup_Dropped(entity flag, entity player)
495 float returnscore = (autocvar_g_ctf_flag_returntime ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_returntime) - time) / autocvar_g_ctf_flag_returntime, 1) : 1);
497 // attach the flag to the player
499 player.flagcarried = flag;
500 setattachment(flag, player, "");
501 setorigin(flag, FLAG_CARRY_OFFSET);
504 flag.movetype = MOVETYPE_NONE;
505 flag.takedamage = DAMAGE_NO;
506 flag.health = flag.max_flag_health;
507 flag.solid = SOLID_NOT;
508 flag.angles = '0 0 0';
509 //flag.ctf_pickuptime = time; // don't update pickuptime since this isn't a real steal.
510 flag.ctf_carrier = player;
511 flag.ctf_status = FLAG_CARRY;
513 // messages and sounds
514 Send_KillNotification(player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
515 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
516 ctf_EventLog("pickup", flag.team, player);
517 ctf_AnnounceStolenFlag(flag, player);
520 returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5);
521 print("score is ", ftos(returnscore), "\n");
522 PlayerTeamScore_AddScore(player, returnscore);
523 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
526 if(autocvar_g_ctf_flag_pickup_effects) // field pickup effect
528 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
532 WaypointSprite_Kill(flag.wps_flagdropped);
533 ctf_FlagcarrierWaypoints(player);
534 WaypointSprite_Ping(player.wps_flagcarrier);
538 // ===================
539 // Main Flag Functions
540 // ===================
542 void ctf_CheckFlagReturn(entity flag)
544 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
546 if((flag.health <= 0) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_returntime))
548 bprint("The ", flag.netname, " has returned to base\n");
549 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
550 ctf_EventLog("returned", flag.team, world);
551 ctf_RespawnFlag(flag);
555 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
557 if(ITEM_DAMAGE_NEEDKILL(deathtype))
559 // automatically kill the flag and return it
561 ctf_CheckFlagReturn(self);
564 if(autocvar_g_ctf_flag_take_damage)
566 self.health = self.health - damage;
567 ctf_CheckFlagReturn(self);
576 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
579 if(self == ctf_worldflaglist) // only for the first flag
580 FOR_EACH_CLIENT(tmp_entity)
581 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
584 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
585 dprint("wtf the flag got squashed?\n");
586 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
587 if(!trace_startsolid) // can we resize it without getting stuck?
588 setsize(self, FLAG_MIN, FLAG_MAX); }
591 switch(self.ctf_status)
595 if(autocvar_g_ctf_dropped_capture_radius)
597 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
598 if(tmp_entity.ctf_status == FLAG_DROPPED)
599 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
600 ctf_Handle_Dropped_Capture(self, tmp_entity);
607 if(autocvar_g_ctf_flag_returntime)
609 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_returntime) * FLAG_THINKRATE);
610 ctf_CheckFlagReturn(self);
617 if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord))
619 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
620 sound(self, CH_TRIGGER, self.snd_flag_respawn, VOL_BASE, ATTN_NONE);
621 ctf_EventLog("returned", self.team, world);
622 ctf_RespawnFlag(tmp_entity);
626 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
635 vector targ_origin = (0.5 * (self.pass_target.absmin + self.pass_target.absmax));
637 traceline(self.origin, targ_origin, MOVE_NOMONSTERS, self);
639 if((self.pass_target.deadflag != DEAD_NO)
640 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
641 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
642 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
644 ctf_Handle_Failed_Pass(self);
646 else // still a viable target, go for it
648 vector desired_direction = normalize(targ_origin - self.origin);
649 vector current_direction = normalize(self.velocity);
651 self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_throw_velocity);
656 default: // this should never happen
658 dprint("ctf_FlagThink(): Flag exists with no status?\n");
666 if(gameover) { return; }
667 if(!self) { return; }
668 if(other.deadflag != DEAD_NO) { return; }
669 if(ITEM_TOUCH_NEEDKILL())
671 // automatically kill the flag and return it
673 ctf_CheckFlagReturn(self);
676 if(other.classname != "player") // The flag just touched an object, most likely the world
678 if(time > self.wait) // if we haven't in a while, play a sound/effect
680 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
681 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
682 self.wait = time + FLAG_TOUCHRATE;
687 switch(self.ctf_status)
691 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
692 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
693 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded) && (time > other.next_take_time))
694 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
700 if(other.team == self.team)
701 ctf_Handle_Return(self, other); // other just returned his own flag
702 else if((!other.flagcarried) && ((other != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
703 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
709 dprint("Someone touched a flag even though it was being carried?\n");
715 if((other.classname == "player") && (other.deadflag == DEAD_NO) && (other != self.pass_sender))
717 if(IsDifferentTeam(other, self.pass_sender))
718 ctf_Handle_Return(self, other);
720 ctf_Handle_Retrieve(self, other);
725 default: // this should never happen
727 dprint("Touch: Flag exists with no status?\n");
733 void ctf_RespawnFlag(entity flag)
735 // reset the player (if there is one)
736 if((flag.owner) && (flag.owner.flagcarried == flag))
738 WaypointSprite_Kill(flag.wps_flagcarrier);
739 flag.owner.flagcarried = world;
741 if(flag.speedrunning)
742 ctf_FakeTimeLimit(flag.owner, -1);
745 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
746 { WaypointSprite_Kill(flag.wps_flagdropped); }
749 setattachment(flag, world, "");
750 setorigin(flag, flag.ctf_spawnorigin);
752 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
753 flag.takedamage = DAMAGE_NO;
754 flag.health = flag.max_flag_health;
755 flag.solid = SOLID_TRIGGER;
756 flag.velocity = '0 0 0';
757 flag.angles = flag.mangle;
758 flag.flags = FL_ITEM | FL_NOTARGET;
760 flag.ctf_status = FLAG_BASE;
762 flag.pass_sender = world;
763 flag.pass_target = world;
764 flag.ctf_carrier = world;
765 flag.ctf_dropper = world;
766 flag.ctf_pickuptime = 0;
767 flag.ctf_droptime = 0;
773 if(self.owner.classname == "player")
774 ctf_Handle_Throw(self.owner, world, DROPTYPE_DROP);
776 ctf_RespawnFlag(self);
779 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
782 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.
785 waypoint_spawnforitem_force(self, self.origin);
786 self.nearestwaypointtimeout = 0; // activate waypointing again
787 self.bot_basewaypoint = self.nearestwaypoint;
790 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), self.origin + '0 0 64', self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
791 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
793 // captureshield setup
794 ctf_CaptureShield_Spawn(self);
797 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
800 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.
801 self = flag; // for later usage with droptofloor()
804 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
805 ctf_worldflaglist = flag;
807 setattachment(flag, world, "");
809 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
810 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
811 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
812 flag.classname = "item_flag_team";
813 flag.target = "###item###"; // wut?
814 flag.flags = FL_ITEM | FL_NOTARGET;
815 flag.solid = SOLID_TRIGGER;
816 flag.takedamage = DAMAGE_NO;
817 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
818 flag.max_flag_health = ((autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
819 flag.health = flag.max_flag_health;
820 flag.event_damage = ctf_FlagDamage;
821 flag.pushable = TRUE;
822 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
823 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
824 flag.velocity = '0 0 0';
825 flag.mangle = flag.angles;
826 flag.reset = ctf_Reset;
827 flag.touch = ctf_FlagTouch;
828 flag.think = ctf_FlagThink;
829 flag.nextthink = time + FLAG_THINKRATE;
830 flag.ctf_status = FLAG_BASE;
832 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
833 if(!flag.scale) { flag.scale = FLAG_SCALE; }
834 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
837 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
838 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
839 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
840 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.
841 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
842 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "keepaway/touch.wav"; } // again has no team-based sound // FIXME
845 precache_sound(flag.snd_flag_taken);
846 precache_sound(flag.snd_flag_returned);
847 precache_sound(flag.snd_flag_capture);
848 precache_sound(flag.snd_flag_respawn);
849 precache_sound(flag.snd_flag_dropped);
850 precache_sound(flag.snd_flag_touch);
851 precache_model(flag.model);
852 precache_model("models/ctf/shield.md3");
853 precache_model("models/ctf/shockwavetransring.md3");
856 setmodel(flag, flag.model); // precision set below
857 setsize(flag, FLAG_MIN, FLAG_MAX);
858 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
860 if(autocvar_g_ctf_flag_glowtrails)
862 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
867 flag.effects |= EF_LOWPRECISION;
868 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
869 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
872 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
874 flag.dropped_origin = flag.origin;
876 flag.movetype = MOVETYPE_NONE;
878 else // drop to floor, automatically find a platform and set that as spawn origin
880 flag.noalign = FALSE;
883 flag.movetype = MOVETYPE_TOSS;
886 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
894 MUTATOR_HOOKFUNCTION(ctf_HookedDrop)
896 if(self.flagcarried) { ctf_Handle_Throw(self, world, DROPTYPE_DROP); }
900 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
904 // initially clear items so they can be set as necessary later.
905 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
906 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
908 // scan through all the flags and notify the client about them
909 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
911 if(flag.ctf_status == FLAG_CARRY)
912 if(flag.owner == self)
913 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
915 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
916 else if(flag.ctf_status == FLAG_DROPPED)
917 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
920 // item for stopping players from capturing the flag too often
921 if(self.ctf_captureshielded)
922 self.items |= IT_CTF_SHIELDED;
924 // update the health of the flag carrier waypointsprite
925 if(self.wps_flagcarrier)
926 WaypointSprite_UpdateHealth(self.wps_flagcarrier, self.health);
931 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
933 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
935 if(frag_target == frag_attacker) // damage done to yourself
937 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
938 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
940 else // damage done everyone else
942 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
943 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
949 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
952 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
955 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
957 entity player = self;
959 if(time > player.throw_antispam)
961 // pass the flag to a team mate
962 if(autocvar_g_ctf_allow_pass)
964 entity head, closest_target;
965 head = findradius(player.origin, autocvar_g_ctf_pass_radius);
967 while(head) // find the closest acceptable target to pass to
969 if(head.classname == "player" && head.deadflag == DEAD_NO)
970 if(head != player && !IsDifferentTeam(head, player))
971 if(!player.speedrunning && !head.speedrunning)
973 traceline(player.origin, head.origin, MOVE_NOMONSTERS, player);
974 if not((trace_fraction < 1) && (trace_ent != head))
976 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
978 if(clienttype(head) == CLIENTTYPE_BOT)
980 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
981 ctf_Handle_Throw(head, player, DROPTYPE_PASS);
985 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
986 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
988 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
991 else if(player.flagcarried)
993 if(closest_target) { if(vlen(player.origin - head.origin) < vlen(player.origin - closest_target.origin)) { closest_target = head; } }
994 else { closest_target = head; }
1001 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROPTYPE_PASS); return 0; }
1004 // throw the flag in front of you
1005 if(autocvar_g_ctf_allow_drop && player.flagcarried && !player.speedrunning)
1006 { ctf_Handle_Throw(player, world, DROPTYPE_THROW); }
1016 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1017 CTF Starting point for a player in team one (Red).
1018 Keys: "angle" viewing angle when spawning. */
1019 void spawnfunc_info_player_team1()
1021 if(g_assault) { remove(self); return; }
1023 self.team = COLOR_TEAM1; // red
1024 spawnfunc_info_player_deathmatch();
1028 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1029 CTF Starting point for a player in team two (Blue).
1030 Keys: "angle" viewing angle when spawning. */
1031 void spawnfunc_info_player_team2()
1033 if(g_assault) { remove(self); return; }
1035 self.team = COLOR_TEAM2; // blue
1036 spawnfunc_info_player_deathmatch();
1039 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
1040 CTF Starting point for a player in team three (Yellow).
1041 Keys: "angle" viewing angle when spawning. */
1042 void spawnfunc_info_player_team3()
1044 if(g_assault) { remove(self); return; }
1046 self.team = COLOR_TEAM3; // yellow
1047 spawnfunc_info_player_deathmatch();
1051 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
1052 CTF Starting point for a player in team four (Purple).
1053 Keys: "angle" viewing angle when spawning. */
1054 void spawnfunc_info_player_team4()
1056 if(g_assault) { remove(self); return; }
1058 self.team = COLOR_TEAM4; // purple
1059 spawnfunc_info_player_deathmatch();
1062 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1063 CTF flag for team one (Red).
1065 "angle" Angle the flag will point (minus 90 degrees)...
1066 "model" model to use, note this needs red and blue as skins 0 and 1...
1067 "noise" sound played when flag is picked up...
1068 "noise1" sound played when flag is returned by a teammate...
1069 "noise2" sound played when flag is captured...
1070 "noise3" sound played when flag is lost in the field and respawns itself...
1071 "noise4" sound played when flag is dropped by a player...
1072 "noise5" sound played when flag touches the ground... */
1073 void spawnfunc_item_flag_team1()
1075 if(!g_ctf) { remove(self); return; }
1077 ctf_FlagSetup(1, self); // 1 = red
1080 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1081 CTF flag for team two (Blue).
1083 "angle" Angle the flag will point (minus 90 degrees)...
1084 "model" model to use, note this needs red and blue as skins 0 and 1...
1085 "noise" sound played when flag is picked up...
1086 "noise1" sound played when flag is returned by a teammate...
1087 "noise2" sound played when flag is captured...
1088 "noise3" sound played when flag is lost in the field and respawns itself...
1089 "noise4" sound played when flag is dropped by a player...
1090 "noise5" sound played when flag touches the ground... */
1091 void spawnfunc_item_flag_team2()
1093 if(!g_ctf) { remove(self); return; }
1095 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
1098 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
1099 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
1100 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.
1102 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
1103 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
1104 void spawnfunc_ctf_team()
1106 if(!g_ctf) { remove(self); return; }
1108 self.classname = "ctf_team";
1109 self.team = self.cnt + 1;
1117 // code from here on is just to support maps that don't have flag and team entities
1118 void ctf_SpawnTeam (string teamname, float teamcolor)
1123 self.classname = "ctf_team";
1124 self.netname = teamname;
1125 self.cnt = teamcolor;
1127 spawnfunc_ctf_team();
1132 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
1134 // if no teams are found, spawn defaults
1135 if(find(world, classname, "ctf_team") == world)
1137 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
1138 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
1139 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
1145 void ctf_Initialize()
1147 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
1149 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
1150 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
1151 ctf_captureshield_force = autocvar_g_ctf_shield_force;
1153 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
1157 MUTATOR_DEFINITION(gamemode_ctf)
1159 MUTATOR_HOOK(MakePlayerObserver, ctf_HookedDrop, CBC_ORDER_ANY);
1160 MUTATOR_HOOK(ClientDisconnect, ctf_HookedDrop, CBC_ORDER_ANY);
1161 MUTATOR_HOOK(PlayerDies, ctf_HookedDrop, CBC_ORDER_ANY);
1162 MUTATOR_HOOK(PortalTeleport, ctf_HookedDrop, CBC_ORDER_ANY);
1163 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
1164 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
1165 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
1166 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
1170 if(time > 1) // game loads at time 1
1171 error("This is a game type and it cannot be added at runtime.");
1179 error("This is a game type and it cannot be removed at runtime.");