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!"));
80 // =======================
81 // CaptureShield Functions
82 // =======================
84 float ctf_CaptureShield_CheckStatus(entity p)
88 float players_worseeq, players_total;
90 if(ctf_captureshield_max_ratio <= 0)
93 s = PlayerScore_Add(p, SP_SCORE, 0);
94 if(s >= -ctf_captureshield_min_negscore)
97 players_total = players_worseeq = 0;
102 se = PlayerScore_Add(e, SP_SCORE, 0);
108 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
109 // use this rule here
111 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
117 void ctf_CaptureShield_Update(entity player, float wanted_status)
119 float updated_status = ctf_CaptureShield_CheckStatus(player);
120 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
122 if(updated_status) // TODO csqc notifier for this // Samual: How?
123 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);
125 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);
127 player.ctf_captureshielded = updated_status;
131 float ctf_CaptureShield_Customize()
133 if not(other.ctf_captureshielded) { return FALSE; }
134 if(self.team == other.team) { return FALSE; }
139 void ctf_CaptureShield_Touch()
141 if not(other.ctf_captureshielded) { return; }
142 if(self.team == other.team) { return; }
144 vector mymid = (self.absmin + self.absmax) * 0.5;
145 vector othermid = (other.absmin + other.absmax) * 0.5;
147 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
148 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);
151 void ctf_CaptureShield_Spawn(entity flag)
153 entity shield = spawn();
156 shield.team = self.team;
157 shield.touch = ctf_CaptureShield_Touch;
158 shield.customizeentityforclient = ctf_CaptureShield_Customize;
159 shield.classname = "ctf_captureshield";
160 shield.effects = EF_ADDITIVE;
161 shield.movetype = MOVETYPE_NOCLIP;
162 shield.solid = SOLID_TRIGGER;
163 shield.avelocity = '7 0 11';
166 setorigin(shield, self.origin);
167 setmodel(shield, "models/ctf/shield.md3");
168 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
171 // ====================
172 // Drop/Pass/Throw Code
173 // ====================
175 void ctf_Handle_Recieve(entity player, entity reciever)
177 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
178 entity flag = player.flagcarried;
179 if(!flag) { return; }
180 if(time < flag.pass_antispam) { return; } // antispam of passing
183 player.flagcarried = world;
184 WaypointSprite_Ping(player.wps_flagcarrier);
185 WaypointSprite_Kill(player.wps_flagcarrier);
187 // transfer flag to reciever
188 flag.owner = reciever;
189 flag.owner.flagcarried = flag;
190 flag.ctf_pickupper = reciever;
191 setattachment(flag, reciever, "");
192 setorigin(flag, FLAG_CARRY_OFFSET);
194 // messages and sounds
195 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
196 ctf_EventLog("pass", flag.team, player);
197 ctf_EventLog("recieve", flag.team, reciever);
198 FOR_EACH_PLAYER(tmp_player)
199 if(tmp_player == player)
200 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", reciever.netname));
201 else if(tmp_player == reciever)
202 centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", player.netname));
203 else if(tmp_player.team == player.team)
204 centerprint(tmp_player, strcat(player.netname, " passed the ", flag.netname, " to ", reciever.netname));
207 te_lightning2(world, reciever.origin, player.origin);
209 // create new waypoint
210 WaypointSprite_Spawn("flagcarrier", 0, 0, reciever, '0 0 64', world, reciever.team, reciever, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0');
211 WaypointSprite_UpdateMaxHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
212 WaypointSprite_UpdateHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(reciever.health, reciever.armorvalue, autocvar_g_balance_armor_blockpercent));
213 WaypointSprite_UpdateTeamRadar(reciever.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
215 flag.pass_antispam = time + autocvar_g_ctf_pass_wait;
218 void ctf_Handle_Pass(entity player, entity reciever)
220 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
221 entity flag = player.flagcarried;
222 if(!flag) { return; }
223 if(time < flag.pass_antispam) { return; } // antispam of passing
226 player.flagcarried = world;
227 WaypointSprite_Ping(player.wps_flagcarrier);
228 WaypointSprite_Kill(player.wps_flagcarrier);
230 // transfer flag to reciever
231 flag.owner = reciever;
232 flag.owner.flagcarried = flag;
233 flag.ctf_pickupper = reciever;
234 setattachment(flag, reciever, "");
235 setorigin(flag, FLAG_CARRY_OFFSET);
237 // messages and sounds
238 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
239 ctf_EventLog("pass", flag.team, player);
240 ctf_EventLog("recieve", flag.team, reciever);
241 FOR_EACH_PLAYER(tmp_player)
242 if(tmp_player == player)
243 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", reciever.netname));
244 else if(tmp_player == reciever)
245 centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", player.netname));
246 else if(tmp_player.team == player.team)
247 centerprint(tmp_player, strcat(player.netname, " passed the ", flag.netname, " to ", reciever.netname));
250 te_lightning2(world, reciever.origin, player.origin);
252 // create new waypoint
253 WaypointSprite_Spawn("flagcarrier", 0, 0, reciever, '0 0 64', world, reciever.team, reciever, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0');
254 WaypointSprite_UpdateMaxHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
255 WaypointSprite_UpdateHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(reciever.health, reciever.armorvalue, autocvar_g_balance_armor_blockpercent));
256 WaypointSprite_UpdateTeamRadar(reciever.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
258 flag.pass_antispam = time + autocvar_g_ctf_pass_wait;
261 void ctf_Handle_Drop(entity player, float droptype)
263 entity flag = player.flagcarried;
264 if(!flag) { return; }
266 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
269 setattachment(flag, world, "");
270 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
271 flag.owner.flagcarried = world;
273 flag.movetype = MOVETYPE_TOSS;
274 flag.solid = SOLID_TRIGGER;
275 flag.takedamage = DAMAGE_YES;
276 flag.health = flag.max_flag_health;
280 case DROPTYPE_THROWN:
282 makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
283 flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_throw_velocity)), FALSE);
288 case DROPTYPE_NORMAL:
290 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
295 flag.ctf_droptime = time;
296 flag.ctf_dropper = player;
297 flag.ctf_status = FLAG_DROPPED;
299 // messages and sounds
300 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
301 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
302 ctf_EventLog("dropped", player.team, player);
305 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
306 PlayerScore_Add(player, SP_CTF_DROPS, 1);
309 if(autocvar_g_ctf_flag_dropped_waypoint)
310 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)
312 WaypointSprite_Ping(player.wps_flagcarrier);
313 WaypointSprite_Kill(player.wps_flagcarrier);
315 if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
317 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
318 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
322 ctf_CaptureShield_Update(player, 0); // shield only
324 // check if the flag will fall off the map
325 trace_startsolid = FALSE;
326 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
328 dprint("FLAG FALLTHROUGH will happen SOON\n");
336 void ctf_Handle_Dropped_Capture(entity flag, entity enemy_flag)
340 entity player = enemy_flag.ctf_dropper;
342 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
344 // messages and sounds
345 Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
346 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
347 ctf_EventLog("droppedcapture", enemy_flag.team, player);
350 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
351 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
354 if(autocvar_g_ctf_flag_capture_effects)
356 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
357 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
361 ctf_RespawnFlag(enemy_flag);
364 void ctf_Handle_Capture(entity flag, entity player)
366 // messages and sounds
367 Send_KillNotification(player.netname, player.flagcarried.netname, ctf_CaptureRecord(flag, player), INFO_CAPTUREFLAG, MSG_INFO);
368 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
369 ctf_EventLog("capture", player.flagcarried.team, player);
372 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
373 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
376 if(autocvar_g_ctf_flag_capture_effects)
378 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
379 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
383 WaypointSprite_Kill(player.wps_flagcarrier);
386 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
388 ctf_RespawnFlag(player.flagcarried);
391 void ctf_Handle_Return(entity flag, entity player)
393 // messages and sounds
394 //centerprint(player, strcat("You returned ", flag.netname));
395 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
396 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
397 ctf_EventLog("return", flag.team, player);
400 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_return")); // reward for return
401 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
403 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
404 FOR_EACH_PLAYER(player) if(player == flag.ctf_dropper) // punish the player who dropped the flag
406 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
407 ctf_CaptureShield_Update(player, 0); // shield only
411 ctf_RespawnFlag(flag);
414 void ctf_Handle_Pickup_Base(entity flag, entity player)
416 // attach the flag to the player
418 player.flagcarried = flag;
419 setattachment(flag, player, "");
420 setorigin(flag, FLAG_CARRY_OFFSET);
423 flag.movetype = MOVETYPE_NONE;
424 flag.takedamage = DAMAGE_NO;
425 flag.solid = SOLID_NOT;
426 flag.angles = '0 0 0';
427 flag.ctf_pickuptime = time; // used for timing runs
428 flag.ctf_pickupper = player;
429 flag.ctf_status = FLAG_CARRY;
431 // messages and sounds
432 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
433 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
434 ctf_EventLog("steal", flag.team, player);
435 ctf_AnnounceStolenFlag(flag, player);
438 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
439 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
442 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
443 if((player.speedrunning) && (ctf_captimerecord))
444 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
447 if (autocvar_g_ctf_flag_pickup_effects)
449 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
453 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
454 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
455 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
456 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
457 WaypointSprite_Ping(player.wps_flagcarrier);
460 void ctf_Handle_Pickup_Dropped(entity flag, entity player)
463 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);
465 // attach the flag to the player
467 player.flagcarried = flag;
468 setattachment(flag, player, "");
469 setorigin(flag, FLAG_CARRY_OFFSET);
472 flag.movetype = MOVETYPE_NONE;
473 flag.takedamage = DAMAGE_NO;
474 flag.health = flag.max_flag_health;
475 flag.solid = SOLID_NOT;
476 flag.angles = '0 0 0';
477 //flag.ctf_pickuptime = time; // don't update pickuptime since this isn't a real steal.
478 flag.ctf_pickupper = player;
479 flag.ctf_status = FLAG_CARRY;
481 // messages and sounds
482 Send_KillNotification(player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
483 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
484 ctf_EventLog("pickup", flag.team, player);
485 ctf_AnnounceStolenFlag(flag, player);
488 returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5);
489 print("score is ", ftos(returnscore), "\n");
490 PlayerTeamScore_AddScore(player, returnscore);
491 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
494 if(autocvar_g_ctf_flag_pickup_effects) // field pickup effect
496 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
500 WaypointSprite_Kill(flag.wps_flagdropped);
501 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
502 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
503 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
504 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
505 WaypointSprite_Ping(player.wps_flagcarrier);
509 // ===================
510 // Main Flag Functions
511 // ===================
513 void ctf_CheckFlagReturn(entity flag)
515 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
517 if((flag.health <= 0) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_returntime))
519 bprint("The ", flag.netname, " has returned to base\n");
520 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
521 ctf_EventLog("returned", flag.team, world);
522 ctf_RespawnFlag(flag);
526 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
528 if(ITEM_DAMAGE_NEEDKILL(deathtype))
530 // automatically kill the flag and return it
532 ctf_CheckFlagReturn(self);
535 if(autocvar_g_ctf_flag_take_damage)
537 self.health = self.health - damage;
538 ctf_CheckFlagReturn(self);
547 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
550 if(self == ctf_worldflaglist) // only for the first flag
551 FOR_EACH_CLIENT(tmp_entity)
552 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
555 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
556 dprint("wtf the flag got squashed?\n");
557 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
558 if(!trace_startsolid) // can we resize it without getting stuck?
559 setsize(self, FLAG_MIN, FLAG_MAX); }
562 switch(self.ctf_status)
566 if(autocvar_g_ctf_dropped_capture_radius)
568 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
569 if(tmp_entity.ctf_status == FLAG_DROPPED)
570 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
571 ctf_Handle_Dropped_Capture(self, tmp_entity);
578 if(autocvar_g_ctf_flag_returntime)
580 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_returntime) * FLAG_THINKRATE);
581 ctf_CheckFlagReturn(self);
588 if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord))
590 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
591 sound(self, CH_TRIGGER, self.snd_flag_respawn, VOL_BASE, ATTN_NONE);
592 ctf_EventLog("returned", self.team, world);
593 ctf_RespawnFlag(tmp_entity);
597 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
606 dprint("Someone touched a flag even though it was being carried?\n");
610 default: // this should never happen
612 dprint("ctf_FlagThink(): Flag exists with no status?\n");
620 if(gameover) { return; }
621 if(!self) { return; }
622 if(other.deadflag != DEAD_NO) { return; }
623 if(ITEM_TOUCH_NEEDKILL())
625 // automatically kill the flag and return it
627 ctf_CheckFlagReturn(self);
630 if(other.classname != "player") // The flag just touched an object, most likely the world
632 if(time > self.wait) // if we haven't in a while, play a sound/effect
634 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
635 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
640 switch(self.ctf_status)
644 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
645 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
646 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
647 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
653 if(other.team == self.team)
654 ctf_Handle_Return(self, other); // other just returned his own flag
655 else if((!other.flagcarried) && ((other != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
656 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
662 dprint("Someone touched a flag even though it was being carried?\n");
668 dprint("Someone touched a flag even though it was being carried?\n");
672 default: // this should never happen
674 dprint("Touch: Flag exists with no status?\n");
679 self.wait = time + FLAG_TOUCHRATE;
682 void ctf_RespawnFlag(entity flag)
684 // reset the player (if there is one)
685 if((flag.owner) && (flag.owner.flagcarried == flag))
687 WaypointSprite_Kill(flag.wps_flagcarrier);
688 flag.owner.flagcarried = world;
690 if(flag.speedrunning)
691 ctf_FakeTimeLimit(flag.owner, -1);
694 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
695 { WaypointSprite_Kill(flag.wps_flagdropped); }
698 setattachment(flag, world, "");
699 setorigin(flag, flag.ctf_spawnorigin); // replace with flag.ctf_spawnorigin
700 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
701 flag.takedamage = DAMAGE_NO;
702 flag.health = flag.max_flag_health;
703 flag.solid = SOLID_TRIGGER;
704 flag.velocity = '0 0 0';
705 flag.angles = flag.mangle;
706 flag.ctf_status = FLAG_BASE;
707 flag.flags = FL_ITEM | FL_NOTARGET;
714 if(self.owner.classname == "player")
715 ctf_Handle_Drop(self.owner, DROPTYPE_NORMAL);
717 ctf_RespawnFlag(self);
720 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
723 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.
726 waypoint_spawnforitem_force(self, self.origin);
727 self.nearestwaypointtimeout = 0; // activate waypointing again
728 self.bot_basewaypoint = self.nearestwaypoint;
731 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), self.origin + '0 0 64', self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
732 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
734 // captureshield setup
735 ctf_CaptureShield_Spawn(self);
738 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
741 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.
742 self = flag; // for later usage with droptofloor()
745 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
746 ctf_worldflaglist = flag;
748 setattachment(flag, world, "");
750 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
751 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
752 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
753 flag.classname = "item_flag_team";
754 flag.target = "###item###"; // wut?
755 flag.flags = FL_ITEM | FL_NOTARGET;
756 flag.solid = SOLID_TRIGGER;
757 flag.takedamage = DAMAGE_NO;
758 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
759 flag.max_flag_health = ((autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
760 flag.health = flag.max_flag_health;
761 flag.event_damage = ctf_FlagDamage;
762 flag.pushable = TRUE;
763 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
764 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
765 flag.velocity = '0 0 0';
766 flag.mangle = flag.angles;
767 flag.reset = ctf_Reset;
768 flag.touch = ctf_FlagTouch;
769 flag.think = ctf_FlagThink;
770 flag.nextthink = time + FLAG_THINKRATE;
771 flag.ctf_status = FLAG_BASE;
773 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
774 if(!flag.scale) { flag.scale = FLAG_SCALE; }
775 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
778 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
779 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
780 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
781 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.
782 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
783 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "keepaway/touch.wav"; } // again has no team-based sound // FIXME
786 precache_sound(flag.snd_flag_taken);
787 precache_sound(flag.snd_flag_returned);
788 precache_sound(flag.snd_flag_capture);
789 precache_sound(flag.snd_flag_respawn);
790 precache_sound(flag.snd_flag_dropped);
791 precache_sound(flag.snd_flag_touch);
792 precache_model(flag.model);
793 precache_model("models/ctf/shield.md3");
794 precache_model("models/ctf/shockwavetransring.md3");
797 setmodel(flag, flag.model); // precision set below
798 setsize(flag, FLAG_MIN, FLAG_MAX);
799 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
801 if(autocvar_g_ctf_flag_glowtrails)
803 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
808 flag.effects |= EF_LOWPRECISION;
809 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
810 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
813 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
815 flag.dropped_origin = flag.origin;
817 flag.movetype = MOVETYPE_NONE;
819 else // drop to floor, automatically find a platform and set that as spawn origin
821 flag.noalign = FALSE;
824 flag.movetype = MOVETYPE_TOSS;
827 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
835 MUTATOR_HOOKFUNCTION(ctf_HookedDrop)
837 if(self.flagcarried) { ctf_Handle_Drop(self, DROPTYPE_NORMAL); }
841 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
845 // initially clear items so they can be set as necessary later.
846 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
847 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
849 // item for stopping players from capturing the flag too often
850 if(self.ctf_captureshielded)
851 self.items |= IT_CTF_SHIELDED;
853 // scan through all the flags and notify the client about them
854 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
856 if(flag.ctf_status == FLAG_CARRY)
857 if(flag.owner == self)
858 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
860 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
861 else if(flag.ctf_status == FLAG_DROPPED)
862 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
865 if(self.wps_flagcarrier) { WaypointSprite_UpdateHealth(self.wps_flagcarrier, self.health); }
870 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
872 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
874 if(frag_target == frag_attacker) // damage done to yourself
876 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
877 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
879 else // damage done everyone else
881 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
882 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
888 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
890 frag_score = 0; // no frags counted in ctf
891 return (autocvar_g_ctf_ignore_frags); // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
894 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
896 entity player = self;
898 // pass the flag to a team mate
899 if(autocvar_g_ctf_allow_pass && (time > player.pass_antispam))
901 entity head, closest_target;
902 head = findradius(player.origin, autocvar_g_ctf_pass_radius);
904 while(head) // find the closest acceptable target to pass to
906 if(head.classname == "player" && head.deadflag == DEAD_NO)
907 if(head != player && !IsDifferentTeam(head, player))
908 if(!player.speedrunning && !head.speedrunning)
910 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
912 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
913 player.pass_antispam = time + autocvar_g_ctf_pass_wait;
916 else if(player.flagcarried)
918 if(closest_target) { if(vlen(player.origin - head.origin) < vlen(player.origin - closest_target.origin)) { closest_target = head; } }
919 else { closest_target = head; }
925 if(closest_target) { ctf_Handle_Pass(player, closest_target); return 0; }
928 // throw the flag in front of you
929 if(autocvar_g_ctf_allow_drop && player.flagcarried && !player.speedrunning)
930 { ctf_Handle_Drop(player, DROPTYPE_THROWN); }
939 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
940 CTF Starting point for a player in team one (Red).
941 Keys: "angle" viewing angle when spawning. */
942 void spawnfunc_info_player_team1()
944 if(g_assault) { remove(self); return; }
946 self.team = COLOR_TEAM1; // red
947 spawnfunc_info_player_deathmatch();
951 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
952 CTF Starting point for a player in team two (Blue).
953 Keys: "angle" viewing angle when spawning. */
954 void spawnfunc_info_player_team2()
956 if(g_assault) { remove(self); return; }
958 self.team = COLOR_TEAM2; // blue
959 spawnfunc_info_player_deathmatch();
962 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
963 CTF Starting point for a player in team three (Yellow).
964 Keys: "angle" viewing angle when spawning. */
965 void spawnfunc_info_player_team3()
967 if(g_assault) { remove(self); return; }
969 self.team = COLOR_TEAM3; // yellow
970 spawnfunc_info_player_deathmatch();
974 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
975 CTF Starting point for a player in team four (Purple).
976 Keys: "angle" viewing angle when spawning. */
977 void spawnfunc_info_player_team4()
979 if(g_assault) { remove(self); return; }
981 self.team = COLOR_TEAM4; // purple
982 spawnfunc_info_player_deathmatch();
985 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
986 CTF flag for team one (Red).
988 "angle" Angle the flag will point (minus 90 degrees)...
989 "model" model to use, note this needs red and blue as skins 0 and 1...
990 "noise" sound played when flag is picked up...
991 "noise1" sound played when flag is returned by a teammate...
992 "noise2" sound played when flag is captured...
993 "noise3" sound played when flag is lost in the field and respawns itself...
994 "noise4" sound played when flag is dropped by a player...
995 "noise5" sound played when flag touches the ground... */
996 void spawnfunc_item_flag_team1()
998 if(!g_ctf) { remove(self); return; }
1000 ctf_FlagSetup(1, self); // 1 = red
1003 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1004 CTF flag for team two (Blue).
1006 "angle" Angle the flag will point (minus 90 degrees)...
1007 "model" model to use, note this needs red and blue as skins 0 and 1...
1008 "noise" sound played when flag is picked up...
1009 "noise1" sound played when flag is returned by a teammate...
1010 "noise2" sound played when flag is captured...
1011 "noise3" sound played when flag is lost in the field and respawns itself...
1012 "noise4" sound played when flag is dropped by a player...
1013 "noise5" sound played when flag touches the ground... */
1014 void spawnfunc_item_flag_team2()
1016 if(!g_ctf) { remove(self); return; }
1018 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
1021 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
1022 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
1023 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.
1025 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
1026 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
1027 void spawnfunc_ctf_team()
1029 if(!g_ctf) { remove(self); return; }
1031 self.classname = "ctf_team";
1032 self.team = self.cnt + 1;
1040 // code from here on is just to support maps that don't have flag and team entities
1041 void ctf_SpawnTeam (string teamname, float teamcolor)
1046 self.classname = "ctf_team";
1047 self.netname = teamname;
1048 self.cnt = teamcolor;
1050 spawnfunc_ctf_team();
1055 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
1057 // if no teams are found, spawn defaults
1058 if(find(world, classname, "ctf_team") == world)
1060 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
1061 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
1062 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
1068 void ctf_Initialize()
1070 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
1072 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
1073 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
1074 ctf_captureshield_force = autocvar_g_ctf_shield_force;
1076 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
1080 MUTATOR_DEFINITION(gamemode_ctf)
1082 MUTATOR_HOOK(MakePlayerObserver, ctf_HookedDrop, CBC_ORDER_ANY);
1083 MUTATOR_HOOK(ClientDisconnect, ctf_HookedDrop, CBC_ORDER_ANY);
1084 MUTATOR_HOOK(PlayerDies, ctf_HookedDrop, CBC_ORDER_ANY);
1085 MUTATOR_HOOK(PortalTeleport, ctf_HookedDrop, CBC_ORDER_ANY);
1086 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
1087 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
1088 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
1089 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
1093 if(time > 1) // game loads at time 1
1094 error("This is a game type and it cannot be added at runtime.");
1102 error("This is a game type and it cannot be removed at runtime.");