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, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0');
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, '1 1 0');
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;
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 not(other.ctf_captureshielded) { return FALSE; }
126 if(self.team == other.team) { return FALSE; }
131 void ctf_CaptureShield_Touch()
133 if not(other.ctf_captureshielded) { return; }
134 if(self.team == other.team) { 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_Failed_Pass(entity flag)
170 entity sender = flag.pass_sender;
172 flag.movetype = MOVETYPE_TOSS;
173 flag.takedamage = DAMAGE_YES;
174 flag.health = flag.max_flag_health;
175 flag.ctf_droptime = time;
176 flag.ctf_dropper = sender;
177 flag.ctf_status = FLAG_DROPPED;
179 // messages and sounds
180 Send_KillNotification(sender.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
181 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
182 ctf_EventLog("dropped", sender.team, sender);
185 PlayerTeamScore_AddScore(sender, -ctf_ReadScore("penalty_drop"));
186 PlayerScore_Add(sender, SP_CTF_DROPS, 1);
189 if(autocvar_g_ctf_flag_dropped_waypoint)
190 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'));
192 if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
194 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
195 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
198 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
200 flag.pass_sender = world;
201 flag.pass_target = world;
204 void ctf_Handle_Retrieve(entity flag, entity player)
206 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
207 entity sender = flag.pass_sender;
209 // transfer flag to player
210 flag.ctf_carrier = player;
212 flag.owner.flagcarried = flag;
215 setattachment(flag, player, "");
216 setorigin(flag, FLAG_CARRY_OFFSET);
217 flag.movetype = MOVETYPE_NONE;
218 flag.takedamage = DAMAGE_NO;
219 flag.solid = SOLID_NOT;
220 flag.ctf_carrier = player;
221 flag.ctf_status = FLAG_CARRY;
223 // messages and sounds
224 sound(player, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTN_NORM);
225 ctf_EventLog("recieve", flag.team, player);
226 FOR_EACH_PLAYER(tmp_player)
227 if(tmp_player == sender)
228 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
229 else if(tmp_player == player)
230 centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", sender.netname));
231 else if(tmp_player.team == sender.team)
232 centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
234 // create new waypoint
235 ctf_FlagcarrierWaypoints(player);
237 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
238 player.throw_antispam = sender.throw_antispam;
240 flag.pass_sender = world;
241 flag.pass_target = world;
244 void ctf_Handle_Throw(entity player, entity reciever, float droptype)
246 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;
265 vector targ_origin = (0.5 * (reciever.absmin + reciever.absmax));
266 flag.velocity = (normalize(targ_origin - player.origin) * autocvar_g_ctf_throw_velocity);
272 makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
273 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_throw_velocity)), FALSE);
280 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
285 print("flag.velocity = ", vtos(flag.velocity), ".\n");
292 flag.movetype = MOVETYPE_FLY;
293 flag.takedamage = DAMAGE_NO;
294 flag.pass_sender = player;
295 flag.pass_target = reciever;
296 flag.ctf_status = FLAG_PASSING;
299 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
300 ctf_EventLog("pass", flag.team, player);
301 te_lightning2(world, reciever.origin, player.origin);
310 flag.movetype = MOVETYPE_TOSS;
311 flag.takedamage = DAMAGE_YES;
312 flag.health = flag.max_flag_health;
313 flag.ctf_dropper = player;
314 flag.ctf_status = FLAG_DROPPED;
316 // messages and sounds
317 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
318 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
319 ctf_EventLog("dropped", player.team, player);
322 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
323 PlayerScore_Add(player, SP_CTF_DROPS, 1);
326 if(autocvar_g_ctf_flag_dropped_waypoint)
327 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)
329 if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
331 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
332 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
338 // kill old waypointsprite
339 WaypointSprite_Ping(player.wps_flagcarrier);
340 WaypointSprite_Kill(player.wps_flagcarrier);
343 //ctf_CaptureShield_Update(player, 0); // shield only
351 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
353 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
354 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
356 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
358 // messages and sounds
359 Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
360 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
364 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
365 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
370 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
371 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
374 if(autocvar_g_ctf_flag_capture_effects)
376 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
377 shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
381 if(capturetype == CAPTURE_NORMAL)
383 WaypointSprite_Kill(player.wps_flagcarrier);
384 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
388 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
389 ctf_RespawnFlag(player.flagcarried);
392 void ctf_Handle_Return(entity flag, entity player)
394 // messages and sounds
395 //centerprint(player, strcat("You returned ", flag.netname));
396 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
397 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
398 ctf_EventLog("return", flag.team, player);
401 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_return")); // reward for return
402 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
404 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
405 FOR_EACH_PLAYER(player) if(player == flag.ctf_dropper) // punish the player who dropped the flag
407 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
408 ctf_CaptureShield_Update(player, 0); // shield only
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_PLAYER(tmp_player)
449 if(tmp_player == player)
450 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
451 else if(tmp_player.team == player.team)
452 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
453 else if(tmp_player.team == flag.team)
454 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
458 case PICKUP_BASE: ctf_EventLog("steal", flag.team, player); break;
459 case PICKUP_DROPPED: ctf_EventLog("pickup", flag.team, player); break;
464 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
469 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
475 pickup_dropped_score = (autocvar_g_ctf_flag_returntime ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_returntime) - time) / autocvar_g_ctf_flag_returntime, 1) : 1);
476 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);
477 print("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
478 PlayerTeamScore_AddScore(player, pickup_dropped_score);
486 if(pickuptype == PICKUP_BASE)
488 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
489 if((player.speedrunning) && (ctf_captimerecord))
490 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
494 if(autocvar_g_ctf_flag_pickup_effects)
495 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
498 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
499 ctf_FlagcarrierWaypoints(player);
500 WaypointSprite_Ping(player.wps_flagcarrier);
504 // ===================
505 // Main Flag Functions
506 // ===================
508 void ctf_CheckFlagReturn(entity flag)
510 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
512 if((flag.health <= 0) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_returntime))
514 bprint("The ", flag.netname, " has returned to base\n");
515 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
516 ctf_EventLog("returned", flag.team, world);
517 ctf_RespawnFlag(flag);
521 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
523 if(ITEM_DAMAGE_NEEDKILL(deathtype))
525 // automatically kill the flag and return it
527 ctf_CheckFlagReturn(self);
530 if(autocvar_g_ctf_flag_take_damage)
532 self.health = self.health - damage;
533 ctf_CheckFlagReturn(self);
542 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
545 if(self == ctf_worldflaglist) // only for the first flag
546 FOR_EACH_CLIENT(tmp_entity)
547 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
550 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
551 dprint("wtf the flag got squashed?\n");
552 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
553 if(!trace_startsolid) // can we resize it without getting stuck?
554 setsize(self, FLAG_MIN, FLAG_MAX); }
557 switch(self.ctf_status)
561 if(autocvar_g_ctf_dropped_capture_radius)
563 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
564 if(tmp_entity.ctf_status == FLAG_DROPPED)
565 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
566 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
573 if(autocvar_g_ctf_flag_returntime)
575 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_returntime) * FLAG_THINKRATE);
576 ctf_CheckFlagReturn(self);
583 if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord))
585 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
586 sound(self, CH_TRIGGER, self.snd_flag_respawn, VOL_BASE, ATTN_NONE);
587 ctf_EventLog("returned", self.team, world);
588 ctf_RespawnFlag(tmp_entity);
592 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
601 vector targ_origin = (0.5 * (self.pass_target.absmin + self.pass_target.absmax));
603 traceline(self.origin, targ_origin, MOVE_NOMONSTERS, self);
605 if((self.pass_target.deadflag != DEAD_NO)
606 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
607 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
608 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
610 ctf_Handle_Failed_Pass(self);
612 else // still a viable target, go for it
614 vector desired_direction = normalize(targ_origin - self.origin);
615 vector current_direction = normalize(self.velocity);
617 self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_throw_velocity);
622 default: // this should never happen
624 dprint("ctf_FlagThink(): Flag exists with no status?\n");
632 if(gameover) { return; }
633 if(!self) { return; }
634 if(other.deadflag != DEAD_NO) { return; }
635 if(ITEM_TOUCH_NEEDKILL())
637 // automatically kill the flag and return it
639 ctf_CheckFlagReturn(self);
642 if(other.classname != "player") // The flag just touched an object, most likely the world
644 if(time > self.wait) // if we haven't in a while, play a sound/effect
646 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
647 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
648 self.wait = time + FLAG_TOUCHRATE;
653 switch(self.ctf_status)
657 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
658 ctf_Handle_Capture(self, other, CAPTURE_NORMAL); // other just captured the enemies flag to his base
659 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded) && (time > other.next_take_time))
660 ctf_Handle_Pickup(self, other, PICKUP_BASE); // other just stole the enemies flag
666 if(other.team == self.team)
667 ctf_Handle_Return(self, other); // other just returned his own flag
668 else if((!other.flagcarried) && ((other != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
669 ctf_Handle_Pickup(self, other, PICKUP_DROPPED); // other just picked up a dropped enemy flag
675 dprint("Someone touched a flag even though it was being carried?\n");
681 if((other.classname == "player") && (other.deadflag == DEAD_NO) && (other != self.pass_sender))
683 if(IsDifferentTeam(other, self.pass_sender))
684 ctf_Handle_Return(self, other);
686 ctf_Handle_Retrieve(self, other);
691 default: // this should never happen
693 dprint("Touch: Flag exists with no status?\n");
699 void ctf_RespawnFlag(entity flag)
701 // reset the player (if there is one)
702 if((flag.owner) && (flag.owner.flagcarried == flag))
704 WaypointSprite_Kill(flag.wps_flagcarrier);
705 flag.owner.flagcarried = world;
707 if(flag.speedrunning)
708 ctf_FakeTimeLimit(flag.owner, -1);
711 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
712 { WaypointSprite_Kill(flag.wps_flagdropped); }
715 setattachment(flag, world, "");
716 setorigin(flag, flag.ctf_spawnorigin);
718 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
719 flag.takedamage = DAMAGE_NO;
720 flag.health = flag.max_flag_health;
721 flag.solid = SOLID_TRIGGER;
722 flag.velocity = '0 0 0';
723 flag.angles = flag.mangle;
724 flag.flags = FL_ITEM | FL_NOTARGET;
726 flag.ctf_status = FLAG_BASE;
728 flag.pass_sender = world;
729 flag.pass_target = world;
730 flag.ctf_carrier = world;
731 flag.ctf_dropper = world;
732 flag.ctf_pickuptime = 0;
733 flag.ctf_droptime = 0;
739 if(self.owner.classname == "player")
740 ctf_Handle_Throw(self.owner, world, DROP_NORMAL);
742 ctf_RespawnFlag(self);
745 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
748 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.
751 waypoint_spawnforitem_force(self, self.origin);
752 self.nearestwaypointtimeout = 0; // activate waypointing again
753 self.bot_basewaypoint = self.nearestwaypoint;
756 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), self.origin + '0 0 64', self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
757 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
759 // captureshield setup
760 ctf_CaptureShield_Spawn(self);
763 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
766 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.
767 self = flag; // for later usage with droptofloor()
770 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
771 ctf_worldflaglist = flag;
773 setattachment(flag, world, "");
775 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
776 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
777 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
778 flag.classname = "item_flag_team";
779 flag.target = "###item###"; // wut?
780 flag.flags = FL_ITEM | FL_NOTARGET;
781 flag.solid = SOLID_TRIGGER;
782 flag.takedamage = DAMAGE_NO;
783 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
784 flag.max_flag_health = ((autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
785 flag.health = flag.max_flag_health;
786 flag.event_damage = ctf_FlagDamage;
787 flag.pushable = TRUE;
788 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
789 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
790 flag.velocity = '0 0 0';
791 flag.mangle = flag.angles;
792 flag.reset = ctf_Reset;
793 flag.touch = ctf_FlagTouch;
794 flag.think = ctf_FlagThink;
795 flag.nextthink = time + FLAG_THINKRATE;
796 flag.ctf_status = FLAG_BASE;
798 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
799 if(!flag.scale) { flag.scale = FLAG_SCALE; }
800 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
803 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
804 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
805 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
806 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.
807 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
808 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "keepaway/touch.wav"; } // again has no team-based sound // FIXME
811 precache_sound(flag.snd_flag_taken);
812 precache_sound(flag.snd_flag_returned);
813 precache_sound(flag.snd_flag_capture);
814 precache_sound(flag.snd_flag_respawn);
815 precache_sound(flag.snd_flag_dropped);
816 precache_sound(flag.snd_flag_touch);
817 precache_model(flag.model);
818 precache_model("models/ctf/shield.md3");
819 precache_model("models/ctf/shockwavetransring.md3");
822 setmodel(flag, flag.model); // precision set below
823 setsize(flag, FLAG_MIN, FLAG_MAX);
824 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
826 if(autocvar_g_ctf_flag_glowtrails)
828 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
833 flag.effects |= EF_LOWPRECISION;
834 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
835 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
838 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
840 flag.dropped_origin = flag.origin;
842 flag.movetype = MOVETYPE_NONE;
844 else // drop to floor, automatically find a platform and set that as spawn origin
846 flag.noalign = FALSE;
849 flag.movetype = MOVETYPE_TOSS;
852 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
860 MUTATOR_HOOKFUNCTION(ctf_HookedDrop)
862 if(self.flagcarried) { ctf_Handle_Throw(self, world, DROP_NORMAL); }
866 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
870 // initially clear items so they can be set as necessary later.
871 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
872 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
874 // scan through all the flags and notify the client about them
875 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
877 if(flag.ctf_status == FLAG_CARRY)
878 if(flag.owner == self)
879 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
881 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
882 else if(flag.ctf_status == FLAG_DROPPED)
883 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
886 // item for stopping players from capturing the flag too often
887 if(self.ctf_captureshielded)
888 self.items |= IT_CTF_SHIELDED;
890 // update the health of the flag carrier waypointsprite
891 if(self.wps_flagcarrier)
892 WaypointSprite_UpdateHealth(self.wps_flagcarrier, self.health);
897 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
899 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
901 if(frag_target == frag_attacker) // damage done to yourself
903 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
904 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
906 else // damage done everyone else
908 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
909 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
915 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
918 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
921 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
923 entity player = self;
925 if(time > player.throw_antispam)
927 // pass the flag to a team mate
928 if(autocvar_g_ctf_allow_pass)
930 entity head, closest_target;
931 head = findradius(player.origin, autocvar_g_ctf_pass_radius);
933 while(head) // find the closest acceptable target to pass to
935 if(head.classname == "player" && head.deadflag == DEAD_NO)
936 if(head != player && !IsDifferentTeam(head, player))
937 if(!player.speedrunning && !head.speedrunning)
939 traceline(player.origin, head.origin, MOVE_NOMONSTERS, player);
940 if not((trace_fraction < 1) && (trace_ent != head))
942 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
944 if(clienttype(head) == CLIENTTYPE_BOT)
946 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
947 ctf_Handle_Throw(head, player, DROP_PASS);
951 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
952 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
954 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
957 else if(player.flagcarried)
959 if(closest_target) { if(vlen(player.origin - head.origin) < vlen(player.origin - closest_target.origin)) { closest_target = head; } }
960 else { closest_target = head; }
967 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return 0; }
970 // throw the flag in front of you
971 if(autocvar_g_ctf_allow_drop && player.flagcarried && !player.speedrunning)
972 { ctf_Handle_Throw(player, world, DROP_THROW); }
982 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
983 CTF Starting point for a player in team one (Red).
984 Keys: "angle" viewing angle when spawning. */
985 void spawnfunc_info_player_team1()
987 if(g_assault) { remove(self); return; }
989 self.team = COLOR_TEAM1; // red
990 spawnfunc_info_player_deathmatch();
994 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
995 CTF Starting point for a player in team two (Blue).
996 Keys: "angle" viewing angle when spawning. */
997 void spawnfunc_info_player_team2()
999 if(g_assault) { remove(self); return; }
1001 self.team = COLOR_TEAM2; // blue
1002 spawnfunc_info_player_deathmatch();
1005 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
1006 CTF Starting point for a player in team three (Yellow).
1007 Keys: "angle" viewing angle when spawning. */
1008 void spawnfunc_info_player_team3()
1010 if(g_assault) { remove(self); return; }
1012 self.team = COLOR_TEAM3; // yellow
1013 spawnfunc_info_player_deathmatch();
1017 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
1018 CTF Starting point for a player in team four (Purple).
1019 Keys: "angle" viewing angle when spawning. */
1020 void spawnfunc_info_player_team4()
1022 if(g_assault) { remove(self); return; }
1024 self.team = COLOR_TEAM4; // purple
1025 spawnfunc_info_player_deathmatch();
1028 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1029 CTF flag for team one (Red).
1031 "angle" Angle the flag will point (minus 90 degrees)...
1032 "model" model to use, note this needs red and blue as skins 0 and 1...
1033 "noise" sound played when flag is picked up...
1034 "noise1" sound played when flag is returned by a teammate...
1035 "noise2" sound played when flag is captured...
1036 "noise3" sound played when flag is lost in the field and respawns itself...
1037 "noise4" sound played when flag is dropped by a player...
1038 "noise5" sound played when flag touches the ground... */
1039 void spawnfunc_item_flag_team1()
1041 if(!g_ctf) { remove(self); return; }
1043 ctf_FlagSetup(1, self); // 1 = red
1046 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1047 CTF flag for team two (Blue).
1049 "angle" Angle the flag will point (minus 90 degrees)...
1050 "model" model to use, note this needs red and blue as skins 0 and 1...
1051 "noise" sound played when flag is picked up...
1052 "noise1" sound played when flag is returned by a teammate...
1053 "noise2" sound played when flag is captured...
1054 "noise3" sound played when flag is lost in the field and respawns itself...
1055 "noise4" sound played when flag is dropped by a player...
1056 "noise5" sound played when flag touches the ground... */
1057 void spawnfunc_item_flag_team2()
1059 if(!g_ctf) { remove(self); return; }
1061 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
1064 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
1065 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
1066 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.
1068 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
1069 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
1070 void spawnfunc_ctf_team()
1072 if(!g_ctf) { remove(self); return; }
1074 self.classname = "ctf_team";
1075 self.team = self.cnt + 1;
1083 // code from here on is just to support maps that don't have flag and team entities
1084 void ctf_SpawnTeam (string teamname, float teamcolor)
1089 self.classname = "ctf_team";
1090 self.netname = teamname;
1091 self.cnt = teamcolor;
1093 spawnfunc_ctf_team();
1098 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
1100 // if no teams are found, spawn defaults
1101 if(find(world, classname, "ctf_team") == world)
1103 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
1104 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
1105 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
1111 void ctf_Initialize()
1113 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
1115 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
1116 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
1117 ctf_captureshield_force = autocvar_g_ctf_shield_force;
1119 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
1123 MUTATOR_DEFINITION(gamemode_ctf)
1125 MUTATOR_HOOK(MakePlayerObserver, ctf_HookedDrop, CBC_ORDER_ANY);
1126 MUTATOR_HOOK(ClientDisconnect, ctf_HookedDrop, CBC_ORDER_ANY);
1127 MUTATOR_HOOK(PlayerDies, ctf_HookedDrop, CBC_ORDER_ANY);
1128 MUTATOR_HOOK(PortalTeleport, ctf_HookedDrop, CBC_ORDER_ANY);
1129 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
1130 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
1131 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
1132 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
1136 if(time > 1) // game loads at time 1
1137 error("This is a game type and it cannot be added at runtime.");
1145 error("This is a game type and it cannot be removed at runtime.");