1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 string ctf_CaptureRecord(entity flag, entity player)
25 float cap_time, cap_record, success;
26 string cap_message, refername;
28 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots))
30 cap_record = ctf_captimerecord;
31 cap_time = (time - flag.ctf_pickuptime);
33 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
34 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
36 if(!ctf_captimerecord)
37 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
38 else if(cap_time < cap_record)
39 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
41 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
45 ctf_captimerecord = cap_time;
46 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
47 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
48 write_recordmarker(player, (time - cap_time), cap_time);
55 void ctf_FlagcarrierWaypoints(entity player)
57 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
58 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
59 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
60 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
64 // =======================
65 // CaptureShield Functions
66 // =======================
68 float ctf_CaptureShield_CheckStatus(entity p)
72 float players_worseeq, players_total;
74 if(ctf_captureshield_max_ratio <= 0)
77 s = PlayerScore_Add(p, SP_SCORE, 0);
78 if(s >= -ctf_captureshield_min_negscore)
81 players_total = players_worseeq = 0;
84 if(IsDifferentTeam(e, p))
86 se = PlayerScore_Add(e, SP_SCORE, 0);
92 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
95 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
101 void ctf_CaptureShield_Update(entity player, float wanted_status)
103 float updated_status = ctf_CaptureShield_CheckStatus(player);
104 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
106 if(updated_status) // TODO csqc notifier for this // Samual: How?
107 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Make some defensive scores before trying again.", 5, 0);
109 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
111 player.ctf_captureshielded = updated_status;
115 float ctf_CaptureShield_Customize()
117 if(!other.ctf_captureshielded) { return FALSE; }
118 if(!IsDifferentTeam(self, other)) { return FALSE; }
123 void ctf_CaptureShield_Touch()
125 if(!other.ctf_captureshielded) { return; }
126 if(!IsDifferentTeam(self, other)) { return; }
128 vector mymid = (self.absmin + self.absmax) * 0.5;
129 vector othermid = (other.absmin + other.absmax) * 0.5;
131 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
132 Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
135 void ctf_CaptureShield_Spawn(entity flag)
137 entity shield = spawn();
140 shield.team = self.team;
141 shield.touch = ctf_CaptureShield_Touch;
142 shield.customizeentityforclient = ctf_CaptureShield_Customize;
143 shield.classname = "ctf_captureshield";
144 shield.effects = EF_ADDITIVE;
145 shield.movetype = MOVETYPE_NOCLIP;
146 shield.solid = SOLID_TRIGGER;
147 shield.avelocity = '7 0 11';
150 setorigin(shield, self.origin);
151 setmodel(shield, "models/ctf/shield.md3");
152 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
156 // ====================
157 // Drop/Pass/Throw Code
158 // ====================
160 void ctf_Handle_Drop(entity flag, entity player, float droptype)
163 player = (player ? player : flag.pass_sender);
166 flag.movetype = MOVETYPE_TOSS;
167 flag.takedamage = DAMAGE_YES;
168 flag.health = flag.max_flag_health;
169 flag.ctf_droptime = time;
170 flag.ctf_dropper = player;
171 flag.ctf_status = FLAG_DROPPED;
173 // messages and sounds
174 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
175 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
176 ctf_EventLog("dropped", player.team, player);
179 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
180 PlayerScore_Add(player, SP_CTF_DROPS, 1);
183 if(autocvar_g_ctf_flag_dropped_waypoint)
184 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
186 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
188 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
189 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
192 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
194 if(droptype == DROP_PASS)
196 flag.pass_sender = world;
197 flag.pass_target = world;
201 void ctf_Handle_Retrieve(entity flag, entity player)
203 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
204 entity sender = flag.pass_sender;
206 // transfer flag to player
208 flag.owner.flagcarried = flag;
211 setattachment(flag, player, "");
212 setorigin(flag, FLAG_CARRY_OFFSET);
213 flag.movetype = MOVETYPE_NONE;
214 flag.takedamage = DAMAGE_NO;
215 flag.solid = SOLID_NOT;
216 flag.ctf_status = FLAG_CARRY;
218 // messages and sounds
219 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
220 ctf_EventLog("receive", flag.team, player);
222 FOR_EACH_REALPLAYER(tmp_player)
224 if(tmp_player == sender)
225 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
226 else if(tmp_player == player)
227 centerprint(tmp_player, strcat("You received the ", flag.netname, " from ", sender.netname));
228 else if(!IsDifferentTeam(tmp_player, sender))
229 centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
232 // create new waypoint
233 ctf_FlagcarrierWaypoints(player);
235 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
236 player.throw_antispam = sender.throw_antispam;
238 flag.pass_sender = world;
239 flag.pass_target = world;
242 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
244 entity flag = player.flagcarried;
245 vector targ_origin, flag_velocity;
247 if(!flag) { return; }
248 if((droptype == DROP_PASS) && !receiver) { return; }
250 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
253 setattachment(flag, world, "");
254 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
255 flag.owner.flagcarried = world;
257 flag.solid = SOLID_TRIGGER;
258 flag.ctf_dropper = player;
259 flag.ctf_droptime = time;
261 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
268 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
269 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
270 WarpZone_RefSys_Copy(flag, receiver);
271 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
272 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
273 flag.velocity = (normalize(targ_origin - player.origin) * autocvar_g_ctf_pass_velocity);
276 flag.movetype = MOVETYPE_FLY;
277 flag.takedamage = DAMAGE_NO;
278 flag.pass_sender = player;
279 flag.pass_target = receiver;
280 flag.ctf_status = FLAG_PASSING;
283 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
284 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
285 ctf_EventLog("pass", flag.team, player);
291 makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
293 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
294 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
295 ctf_Handle_Drop(flag, player, droptype);
301 flag.velocity = '0 0 0'; // do nothing
308 flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
309 ctf_Handle_Drop(flag, player, droptype);
314 // kill old waypointsprite
315 WaypointSprite_Ping(player.wps_flagcarrier);
316 WaypointSprite_Kill(player.wps_flagcarrier);
318 if(player.wps_enemyflagcarrier)
319 WaypointSprite_Kill(player.wps_enemyflagcarrier);
322 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
330 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
332 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
333 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
334 float old_time, new_time;
336 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
338 // messages and sounds
339 Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
340 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
344 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
345 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
350 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
351 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
353 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
354 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
355 if(!old_time || new_time < old_time)
356 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
359 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
360 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
363 if(capturetype == CAPTURE_NORMAL)
365 WaypointSprite_Kill(player.wps_flagcarrier);
366 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
368 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
369 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
373 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
374 ctf_RespawnFlag(enemy_flag);
377 void ctf_Handle_Return(entity flag, entity player)
379 // messages and sounds
380 //centerprint(player, strcat("You returned the ", flag.netname));
381 Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
382 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
383 ctf_EventLog("return", flag.team, player);
386 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
387 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
389 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
393 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
394 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
395 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
399 ctf_RespawnFlag(flag);
402 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
405 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
406 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
407 float pickup_dropped_score; // used to calculate dropped pickup score
409 // attach the flag to the player
411 player.flagcarried = flag;
412 setattachment(flag, player, "");
413 setorigin(flag, FLAG_CARRY_OFFSET);
416 flag.movetype = MOVETYPE_NONE;
417 flag.takedamage = DAMAGE_NO;
418 flag.solid = SOLID_NOT;
419 flag.angles = '0 0 0';
420 flag.ctf_status = FLAG_CARRY;
424 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
425 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
429 // messages and sounds
430 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
431 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
432 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
434 FOR_EACH_REALPLAYER(tmp_player)
436 if(tmp_player == player)
437 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
438 else if(!IsDifferentTeam(tmp_player, player))
439 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
440 else if(!IsDifferentTeam(tmp_player, flag))
441 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
445 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
450 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
451 ctf_EventLog("steal", flag.team, player);
457 pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
458 pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
459 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
460 PlayerTeamScore_AddScore(player, pickup_dropped_score);
461 ctf_EventLog("pickup", flag.team, player);
469 if(pickuptype == PICKUP_BASE)
471 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
472 if((player.speedrunning) && (ctf_captimerecord))
473 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
477 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
480 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
481 ctf_FlagcarrierWaypoints(player);
482 WaypointSprite_Ping(player.wps_flagcarrier);
486 // ===================
487 // Main Flag Functions
488 // ===================
490 void ctf_CheckFlagReturn(entity flag, float returntype)
492 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
494 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
496 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
500 case RETURN_DROPPED: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
501 case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
502 case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
503 case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
507 { bprint("The ", flag.netname, " has returned to base\n"); break; }
509 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
510 ctf_EventLog("returned", flag.team, world);
511 ctf_RespawnFlag(flag);
516 void ctf_CheckStalemate(void)
519 float stale_red_flags, stale_blue_flags;
522 entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
524 // build list of stale flags
525 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
527 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
528 if(tmp_entity.ctf_status != FLAG_BASE)
529 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
531 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
532 ctf_staleflaglist = tmp_entity;
534 switch(tmp_entity.team)
536 case COLOR_TEAM1: ++stale_red_flags; break;
537 case COLOR_TEAM2: ++stale_blue_flags; break;
542 if(stale_red_flags && stale_blue_flags)
543 ctf_stalemate = TRUE;
544 else if(!stale_red_flags && !stale_blue_flags)
545 ctf_stalemate = FALSE;
547 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
550 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
552 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
553 WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
556 if not(wpforenemy_announced)
558 FOR_EACH_REALPLAYER(tmp_entity)
559 if(tmp_entity.flagcarried)
560 centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
562 centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
564 wpforenemy_announced = TRUE;
569 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
571 if(ITEM_DAMAGE_NEEDKILL(deathtype))
573 // automatically kill the flag and return it
575 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
578 if(autocvar_g_ctf_flag_return_damage)
580 // reduce health and check if it should be returned
581 self.health = self.health - damage;
582 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
592 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
595 if(self == ctf_worldflaglist) // only for the first flag
596 FOR_EACH_CLIENT(tmp_entity)
597 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
600 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
601 dprint("wtf the flag got squashed?\n");
602 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
603 if(!trace_startsolid) // can we resize it without getting stuck?
604 setsize(self, FLAG_MIN, FLAG_MAX); }
606 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
611 self.angles = '0 0 0';
619 switch(self.ctf_status)
623 if(autocvar_g_ctf_dropped_capture_radius)
625 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
626 if(tmp_entity.ctf_status == FLAG_DROPPED)
627 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
628 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
635 if(autocvar_g_ctf_flag_dropped_floatinwater)
637 vector midpoint = ((self.absmin + self.absmax) * 0.5);
638 if(pointcontents(midpoint) == CONTENT_WATER)
640 self.velocity = self.velocity * 0.5;
642 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
643 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
645 { self.movetype = MOVETYPE_FLY; }
647 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
649 if(autocvar_g_ctf_flag_return_dropped)
651 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
654 ctf_CheckFlagReturn(self, RETURN_DROPPED);
658 if(autocvar_g_ctf_flag_return_time)
660 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
661 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
669 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
672 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
676 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
680 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
682 if(time >= wpforenemy_nextthink)
684 ctf_CheckStalemate();
685 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
693 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
694 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
695 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
697 if((self.pass_target == world)
698 || (self.pass_target.deadflag != DEAD_NO)
699 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
700 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
701 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
703 ctf_Handle_Drop(self, world, DROP_PASS);
705 else // still a viable target, go for it
707 vector desired_direction = normalize(targ_origin - self.origin);
708 vector current_direction = normalize(self.velocity);
709 self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity);
714 default: // this should never happen
716 dprint("ctf_FlagThink(): Flag exists with no status?\n");
724 if(gameover) { return; }
726 entity toucher = other;
728 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
729 if(ITEM_TOUCH_NEEDKILL())
732 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
736 // special touch behaviors
737 if(toucher.vehicle_flags & VHF_ISVEHICLE)
739 if(autocvar_g_ctf_allow_vehicle_touch)
740 toucher = toucher.owner; // the player is actually the vehicle owner, not other
742 return; // do nothing
744 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
746 if(time > self.wait) // if we haven't in a while, play a sound/effect
748 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
749 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
750 self.wait = time + FLAG_TOUCHRATE;
754 else if(toucher.deadflag != DEAD_NO) { return; }
756 switch(self.ctf_status)
760 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
761 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
762 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
763 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
769 if(!IsDifferentTeam(toucher, self))
770 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
771 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
772 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
778 dprint("Someone touched a flag even though it was being carried?\n");
784 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
786 if(IsDifferentTeam(toucher, self.pass_sender))
787 ctf_Handle_Return(self, toucher);
789 ctf_Handle_Retrieve(self, toucher);
797 void ctf_RespawnFlag(entity flag)
799 // check for flag respawn being called twice in a row
800 if(flag.last_respawn > time - 0.5)
801 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
803 flag.last_respawn = time;
805 // reset the player (if there is one)
806 if((flag.owner) && (flag.owner.flagcarried == flag))
808 if(flag.owner.wps_enemyflagcarrier)
809 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
811 WaypointSprite_Kill(flag.wps_flagcarrier);
813 flag.owner.flagcarried = world;
815 if(flag.speedrunning)
816 ctf_FakeTimeLimit(flag.owner, -1);
819 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
820 { WaypointSprite_Kill(flag.wps_flagdropped); }
823 setattachment(flag, world, "");
824 setorigin(flag, flag.ctf_spawnorigin);
826 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
827 flag.takedamage = DAMAGE_NO;
828 flag.health = flag.max_flag_health;
829 flag.solid = SOLID_TRIGGER;
830 flag.velocity = '0 0 0';
831 flag.angles = flag.mangle;
832 flag.flags = FL_ITEM | FL_NOTARGET;
834 flag.ctf_status = FLAG_BASE;
836 flag.pass_sender = world;
837 flag.pass_target = world;
838 flag.ctf_dropper = world;
839 flag.ctf_pickuptime = 0;
840 flag.ctf_droptime = 0;
842 wpforenemy_announced = FALSE;
848 if(self.owner.classname == "player")
849 ctf_Handle_Throw(self.owner, world, DROP_RESET);
851 ctf_RespawnFlag(self);
854 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
857 waypoint_spawnforitem_force(self, self.origin);
858 self.nearestwaypointtimeout = 0; // activate waypointing again
859 self.bot_basewaypoint = self.nearestwaypoint;
862 WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
863 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
865 // captureshield setup
866 ctf_CaptureShield_Spawn(self);
869 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
872 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.
873 self = flag; // for later usage with droptofloor()
876 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
877 ctf_worldflaglist = flag;
879 setattachment(flag, world, "");
881 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
882 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
883 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
884 flag.classname = "item_flag_team";
885 flag.target = "###item###"; // wut?
886 flag.flags = FL_ITEM | FL_NOTARGET;
887 flag.solid = SOLID_TRIGGER;
888 flag.takedamage = DAMAGE_NO;
889 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
890 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
891 flag.health = flag.max_flag_health;
892 flag.event_damage = ctf_FlagDamage;
893 flag.pushable = TRUE;
894 flag.teleportable = TELEPORT_NORMAL;
895 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
896 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
897 flag.velocity = '0 0 0';
898 flag.mangle = flag.angles;
899 flag.reset = ctf_Reset;
900 flag.touch = ctf_FlagTouch;
901 flag.think = ctf_FlagThink;
902 flag.nextthink = time + FLAG_THINKRATE;
903 flag.ctf_status = FLAG_BASE;
905 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
906 if(!flag.scale) { flag.scale = FLAG_SCALE; }
907 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
908 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
909 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
910 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
913 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
914 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
915 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
916 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.
917 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
918 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
919 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
922 precache_sound(flag.snd_flag_taken);
923 precache_sound(flag.snd_flag_returned);
924 precache_sound(flag.snd_flag_capture);
925 precache_sound(flag.snd_flag_respawn);
926 precache_sound(flag.snd_flag_dropped);
927 precache_sound(flag.snd_flag_touch);
928 precache_sound(flag.snd_flag_pass);
929 precache_model(flag.model);
930 precache_model("models/ctf/shield.md3");
931 precache_model("models/ctf/shockwavetransring.md3");
934 setmodel(flag, flag.model); // precision set below
935 setsize(flag, FLAG_MIN, FLAG_MAX);
936 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
938 if(autocvar_g_ctf_flag_glowtrails)
940 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
945 flag.effects |= EF_LOWPRECISION;
946 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
947 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
950 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
952 flag.dropped_origin = flag.origin;
954 flag.movetype = MOVETYPE_NONE;
956 else // drop to floor, automatically find a platform and set that as spawn origin
958 flag.noalign = FALSE;
961 flag.movetype = MOVETYPE_TOSS;
964 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
972 // NOTE: LEGACY CODE, needs to be re-written!
974 void havocbot_calculate_middlepoint()
981 f = ctf_worldflaglist;
986 f = f.ctf_worldflagnext;
990 havocbot_ctf_middlepoint = s * (1.0 / n);
991 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
995 entity havocbot_ctf_find_flag(entity bot)
998 f = ctf_worldflaglist;
1001 if (bot.team == f.team)
1003 f = f.ctf_worldflagnext;
1008 entity havocbot_ctf_find_enemy_flag(entity bot)
1011 f = ctf_worldflaglist;
1014 if (bot.team != f.team)
1016 f = f.ctf_worldflagnext;
1021 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1029 FOR_EACH_PLAYER(head)
1031 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1034 if(vlen(head.origin - org) < tc_radius)
1041 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1044 head = ctf_worldflaglist;
1047 if (self.team == head.team)
1049 head = head.ctf_worldflagnext;
1052 navigation_routerating(head, ratingscale, 10000);
1055 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1058 head = ctf_worldflaglist;
1061 if (self.team == head.team)
1063 head = head.ctf_worldflagnext;
1068 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1071 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1074 head = ctf_worldflaglist;
1077 if (self.team != head.team)
1079 head = head.ctf_worldflagnext;
1082 navigation_routerating(head, ratingscale, 10000);
1085 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1087 if not(bot_waypoints_for_items)
1089 havocbot_goalrating_ctf_enemyflag(ratingscale);
1095 head = havocbot_ctf_find_enemy_flag(self);
1100 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1103 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1107 mf = havocbot_ctf_find_flag(self);
1109 if(mf.ctf_status == FLAG_BASE)
1113 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1116 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1119 head = ctf_worldflaglist;
1122 // flag is out in the field
1123 if(head.ctf_status != FLAG_BASE)
1124 if(head.tag_entity==world) // dropped
1128 if(vlen(org-head.origin)<df_radius)
1129 navigation_routerating(head, ratingscale, 10000);
1132 navigation_routerating(head, ratingscale, 10000);
1135 head = head.ctf_worldflagnext;
1139 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1143 head = findchainfloat(bot_pickup, TRUE);
1146 // gather health and armor only
1148 if (head.health || head.armorvalue)
1149 if (vlen(head.origin - org) < sradius)
1151 // get the value of the item
1152 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1154 navigation_routerating(head, t * ratingscale, 500);
1160 void havocbot_ctf_reset_role(entity bot)
1162 float cdefense, cmiddle, coffense;
1163 entity mf, ef, head;
1166 if(bot.deadflag != DEAD_NO)
1169 if(vlen(havocbot_ctf_middlepoint)==0)
1170 havocbot_calculate_middlepoint();
1173 if (bot.flagcarried)
1175 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1179 mf = havocbot_ctf_find_flag(bot);
1180 ef = havocbot_ctf_find_enemy_flag(bot);
1182 // Retrieve stolen flag
1183 if(mf.ctf_status!=FLAG_BASE)
1185 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1189 // If enemy flag is taken go to the middle to intercept pursuers
1190 if(ef.ctf_status!=FLAG_BASE)
1192 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1196 // if there is only me on the team switch to offense
1198 FOR_EACH_PLAYER(head)
1199 if(head.team==bot.team)
1204 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1208 // Evaluate best position to take
1209 // Count mates on middle position
1210 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1212 // Count mates on defense position
1213 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1215 // Count mates on offense position
1216 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1218 if(cdefense<=coffense)
1219 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1220 else if(coffense<=cmiddle)
1221 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1223 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1226 void havocbot_role_ctf_carrier()
1228 if(self.deadflag != DEAD_NO)
1230 havocbot_ctf_reset_role(self);
1234 if (self.flagcarried == world)
1236 havocbot_ctf_reset_role(self);
1240 if (self.bot_strategytime < time)
1242 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1244 navigation_goalrating_start();
1245 havocbot_goalrating_ctf_ourbase(50000);
1248 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1250 navigation_goalrating_end();
1252 if (self.navigation_hasgoals)
1253 self.havocbot_cantfindflag = time + 10;
1254 else if (time > self.havocbot_cantfindflag)
1256 // Can't navigate to my own base, suicide!
1257 // TODO: drop it and wander around
1258 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1264 void havocbot_role_ctf_escort()
1268 if(self.deadflag != DEAD_NO)
1270 havocbot_ctf_reset_role(self);
1274 if (self.flagcarried)
1276 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1280 // If enemy flag is back on the base switch to previous role
1281 ef = havocbot_ctf_find_enemy_flag(self);
1282 if(ef.ctf_status==FLAG_BASE)
1284 self.havocbot_role = self.havocbot_previous_role;
1285 self.havocbot_role_timeout = 0;
1289 // If the flag carrier reached the base switch to defense
1290 mf = havocbot_ctf_find_flag(self);
1291 if(mf.ctf_status!=FLAG_BASE)
1292 if(vlen(ef.origin - mf.dropped_origin) < 300)
1294 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1298 // Set the role timeout if necessary
1299 if (!self.havocbot_role_timeout)
1301 self.havocbot_role_timeout = time + random() * 30 + 60;
1304 // If nothing happened just switch to previous role
1305 if (time > self.havocbot_role_timeout)
1307 self.havocbot_role = self.havocbot_previous_role;
1308 self.havocbot_role_timeout = 0;
1312 // Chase the flag carrier
1313 if (self.bot_strategytime < time)
1315 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1316 navigation_goalrating_start();
1317 havocbot_goalrating_ctf_enemyflag(30000);
1318 havocbot_goalrating_ctf_ourstolenflag(40000);
1319 havocbot_goalrating_items(10000, self.origin, 10000);
1320 navigation_goalrating_end();
1324 void havocbot_role_ctf_offense()
1329 if(self.deadflag != DEAD_NO)
1331 havocbot_ctf_reset_role(self);
1335 if (self.flagcarried)
1337 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1342 mf = havocbot_ctf_find_flag(self);
1343 ef = havocbot_ctf_find_enemy_flag(self);
1346 if(mf.ctf_status!=FLAG_BASE)
1349 pos = mf.tag_entity.origin;
1353 // Try to get it if closer than the enemy base
1354 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1356 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1361 // Escort flag carrier
1362 if(ef.ctf_status!=FLAG_BASE)
1365 pos = ef.tag_entity.origin;
1369 if(vlen(pos-mf.dropped_origin)>700)
1371 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1376 // About to fail, switch to middlefield
1379 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1383 // Set the role timeout if necessary
1384 if (!self.havocbot_role_timeout)
1385 self.havocbot_role_timeout = time + 120;
1387 if (time > self.havocbot_role_timeout)
1389 havocbot_ctf_reset_role(self);
1393 if (self.bot_strategytime < time)
1395 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1396 navigation_goalrating_start();
1397 havocbot_goalrating_ctf_ourstolenflag(50000);
1398 havocbot_goalrating_ctf_enemybase(20000);
1399 havocbot_goalrating_items(5000, self.origin, 1000);
1400 havocbot_goalrating_items(1000, self.origin, 10000);
1401 navigation_goalrating_end();
1405 // Retriever (temporary role):
1406 void havocbot_role_ctf_retriever()
1410 if(self.deadflag != DEAD_NO)
1412 havocbot_ctf_reset_role(self);
1416 if (self.flagcarried)
1418 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1422 // If flag is back on the base switch to previous role
1423 mf = havocbot_ctf_find_flag(self);
1424 if(mf.ctf_status==FLAG_BASE)
1426 havocbot_ctf_reset_role(self);
1430 if (!self.havocbot_role_timeout)
1431 self.havocbot_role_timeout = time + 20;
1433 if (time > self.havocbot_role_timeout)
1435 havocbot_ctf_reset_role(self);
1439 if (self.bot_strategytime < time)
1444 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1445 navigation_goalrating_start();
1446 havocbot_goalrating_ctf_ourstolenflag(50000);
1447 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1448 havocbot_goalrating_ctf_enemybase(30000);
1449 havocbot_goalrating_items(500, self.origin, rt_radius);
1450 navigation_goalrating_end();
1454 void havocbot_role_ctf_middle()
1458 if(self.deadflag != DEAD_NO)
1460 havocbot_ctf_reset_role(self);
1464 if (self.flagcarried)
1466 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1470 mf = havocbot_ctf_find_flag(self);
1471 if(mf.ctf_status!=FLAG_BASE)
1473 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1477 if (!self.havocbot_role_timeout)
1478 self.havocbot_role_timeout = time + 10;
1480 if (time > self.havocbot_role_timeout)
1482 havocbot_ctf_reset_role(self);
1486 if (self.bot_strategytime < time)
1490 org = havocbot_ctf_middlepoint;
1491 org_z = self.origin_z;
1493 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1494 navigation_goalrating_start();
1495 havocbot_goalrating_ctf_ourstolenflag(50000);
1496 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1497 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1498 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1499 havocbot_goalrating_items(2500, self.origin, 10000);
1500 havocbot_goalrating_ctf_enemybase(2500);
1501 navigation_goalrating_end();
1505 void havocbot_role_ctf_defense()
1509 if(self.deadflag != DEAD_NO)
1511 havocbot_ctf_reset_role(self);
1515 if (self.flagcarried)
1517 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1521 // If own flag was captured
1522 mf = havocbot_ctf_find_flag(self);
1523 if(mf.ctf_status!=FLAG_BASE)
1525 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1529 if (!self.havocbot_role_timeout)
1530 self.havocbot_role_timeout = time + 30;
1532 if (time > self.havocbot_role_timeout)
1534 havocbot_ctf_reset_role(self);
1537 if (self.bot_strategytime < time)
1542 org = mf.dropped_origin;
1543 mp_radius = havocbot_ctf_middlepoint_radius;
1545 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1546 navigation_goalrating_start();
1548 // if enemies are closer to our base, go there
1549 entity head, closestplayer = world;
1550 float distance, bestdistance = 10000;
1551 FOR_EACH_PLAYER(head)
1553 if(head.deadflag!=DEAD_NO)
1556 distance = vlen(org - head.origin);
1557 if(distance<bestdistance)
1559 closestplayer = head;
1560 bestdistance = distance;
1565 if(closestplayer.team!=self.team)
1566 if(vlen(org - self.origin)>1000)
1567 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1568 havocbot_goalrating_ctf_ourbase(30000);
1570 havocbot_goalrating_ctf_ourstolenflag(20000);
1571 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1572 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1573 havocbot_goalrating_items(10000, org, mp_radius);
1574 havocbot_goalrating_items(5000, self.origin, 10000);
1575 navigation_goalrating_end();
1579 void havocbot_role_ctf_setrole(entity bot, float role)
1581 dprint(strcat(bot.netname," switched to "));
1584 case HAVOCBOT_CTF_ROLE_CARRIER:
1586 bot.havocbot_role = havocbot_role_ctf_carrier;
1587 bot.havocbot_role_timeout = 0;
1588 bot.havocbot_cantfindflag = time + 10;
1589 bot.bot_strategytime = 0;
1591 case HAVOCBOT_CTF_ROLE_DEFENSE:
1593 bot.havocbot_role = havocbot_role_ctf_defense;
1594 bot.havocbot_role_timeout = 0;
1596 case HAVOCBOT_CTF_ROLE_MIDDLE:
1598 bot.havocbot_role = havocbot_role_ctf_middle;
1599 bot.havocbot_role_timeout = 0;
1601 case HAVOCBOT_CTF_ROLE_OFFENSE:
1603 bot.havocbot_role = havocbot_role_ctf_offense;
1604 bot.havocbot_role_timeout = 0;
1606 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1607 dprint("retriever");
1608 bot.havocbot_previous_role = bot.havocbot_role;
1609 bot.havocbot_role = havocbot_role_ctf_retriever;
1610 bot.havocbot_role_timeout = time + 10;
1611 bot.bot_strategytime = 0;
1613 case HAVOCBOT_CTF_ROLE_ESCORT:
1615 bot.havocbot_previous_role = bot.havocbot_role;
1616 bot.havocbot_role = havocbot_role_ctf_escort;
1617 bot.havocbot_role_timeout = time + 30;
1618 bot.bot_strategytime = 0;
1629 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1633 // initially clear items so they can be set as necessary later.
1634 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1635 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1637 // scan through all the flags and notify the client about them
1638 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1640 switch(flag.ctf_status)
1645 if((flag.owner == self) || (flag.pass_sender == self))
1646 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1648 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1653 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1659 // item for stopping players from capturing the flag too often
1660 if(self.ctf_captureshielded)
1661 self.items |= IT_CTF_SHIELDED;
1663 // update the health of the flag carrier waypointsprite
1664 if(self.wps_flagcarrier)
1665 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1670 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1672 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1674 if(frag_target == frag_attacker) // damage done to yourself
1676 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1677 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1679 else // damage done to everyone else
1681 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1682 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1685 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1687 if(autocvar_g_ctf_flagcarrier_auto_helpme_when_damaged > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1688 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1693 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1695 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1697 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1698 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1701 if(frag_target.flagcarried)
1702 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1707 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1710 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1713 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1715 entity flag; // temporary entity for the search method
1717 if(self.flagcarried)
1718 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1720 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1722 if(flag.pass_sender == self) { flag.pass_sender = world; }
1723 if(flag.pass_target == self) { flag.pass_target = world; }
1724 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1730 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1732 if(self.flagcarried)
1733 if(!autocvar_g_ctf_portalteleport)
1734 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1739 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1741 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1743 entity player = self;
1745 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1747 // pass the flag to a team mate
1748 if(autocvar_g_ctf_pass)
1750 entity head, closest_target;
1751 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1753 while(head) // find the closest acceptable target to pass to
1755 if(head.classname == "player" && head.deadflag == DEAD_NO)
1756 if(head != player && !IsDifferentTeam(head, player))
1757 if(!head.speedrunning && !head.vehicle)
1759 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1761 if(clienttype(head) == CLIENTTYPE_BOT)
1763 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1764 ctf_Handle_Throw(head, player, DROP_PASS);
1768 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
1769 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1771 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1774 else if(player.flagcarried)
1778 if(vlen(player.origin - WarpZone_UnTransformOrigin(head, head.origin)) < vlen(player.origin - WarpZone_UnTransformOrigin(closest_target, closest_target.origin)))
1779 { closest_target = head; }
1781 else { closest_target = head; }
1787 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1790 // throw the flag in front of you
1791 if(autocvar_g_ctf_throw && player.flagcarried)
1792 { ctf_Handle_Throw(player, world, DROP_THROW); return TRUE; }
1798 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1800 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1802 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1804 else // create a normal help me waypointsprite
1806 WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0');
1807 WaypointSprite_Ping(self.wps_helpme);
1813 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1815 if(vh_player.flagcarried)
1817 if(!autocvar_g_ctf_allow_vehicle_carry)
1819 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1823 setattachment(vh_player.flagcarried, vh_vehicle, "");
1824 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1825 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1826 //vh_player.flagcarried.angles = '0 0 0';
1834 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1836 if(vh_player.flagcarried)
1838 setattachment(vh_player.flagcarried, vh_player, "");
1839 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1840 vh_player.flagcarried.scale = FLAG_SCALE;
1841 vh_player.flagcarried.angles = '0 0 0';
1848 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1850 if(self.flagcarried)
1852 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1853 ctf_RespawnFlag(self);
1860 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1862 entity flag; // temporary entity for the search method
1864 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1866 switch(flag.ctf_status)
1871 // lock the flag, game is over
1872 flag.movetype = MOVETYPE_NONE;
1873 flag.takedamage = DAMAGE_NO;
1874 flag.solid = SOLID_NOT;
1875 flag.nextthink = FALSE; // stop thinking
1877 print("stopping the ", flag.netname, " from moving.\n");
1885 // do nothing for these flags
1894 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1896 havocbot_ctf_reset_role(self);
1905 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1906 CTF Starting point for a player in team one (Red).
1907 Keys: "angle" viewing angle when spawning. */
1908 void spawnfunc_info_player_team1()
1910 if(g_assault) { remove(self); return; }
1912 self.team = COLOR_TEAM1; // red
1913 spawnfunc_info_player_deathmatch();
1917 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1918 CTF Starting point for a player in team two (Blue).
1919 Keys: "angle" viewing angle when spawning. */
1920 void spawnfunc_info_player_team2()
1922 if(g_assault) { remove(self); return; }
1924 self.team = COLOR_TEAM2; // blue
1925 spawnfunc_info_player_deathmatch();
1928 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
1929 CTF Starting point for a player in team three (Yellow).
1930 Keys: "angle" viewing angle when spawning. */
1931 void spawnfunc_info_player_team3()
1933 if(g_assault) { remove(self); return; }
1935 self.team = COLOR_TEAM3; // yellow
1936 spawnfunc_info_player_deathmatch();
1940 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
1941 CTF Starting point for a player in team four (Purple).
1942 Keys: "angle" viewing angle when spawning. */
1943 void spawnfunc_info_player_team4()
1945 if(g_assault) { remove(self); return; }
1947 self.team = COLOR_TEAM4; // purple
1948 spawnfunc_info_player_deathmatch();
1951 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1952 CTF flag for team one (Red).
1954 "angle" Angle the flag will point (minus 90 degrees)...
1955 "model" model to use, note this needs red and blue as skins 0 and 1...
1956 "noise" sound played when flag is picked up...
1957 "noise1" sound played when flag is returned by a teammate...
1958 "noise2" sound played when flag is captured...
1959 "noise3" sound played when flag is lost in the field and respawns itself...
1960 "noise4" sound played when flag is dropped by a player...
1961 "noise5" sound played when flag touches the ground... */
1962 void spawnfunc_item_flag_team1()
1964 if(!g_ctf) { remove(self); return; }
1966 ctf_FlagSetup(1, self); // 1 = red
1969 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1970 CTF flag for team two (Blue).
1972 "angle" Angle the flag will point (minus 90 degrees)...
1973 "model" model to use, note this needs red and blue as skins 0 and 1...
1974 "noise" sound played when flag is picked up...
1975 "noise1" sound played when flag is returned by a teammate...
1976 "noise2" sound played when flag is captured...
1977 "noise3" sound played when flag is lost in the field and respawns itself...
1978 "noise4" sound played when flag is dropped by a player...
1979 "noise5" sound played when flag touches the ground... */
1980 void spawnfunc_item_flag_team2()
1982 if(!g_ctf) { remove(self); return; }
1984 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
1987 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
1988 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
1989 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.
1991 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
1992 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
1993 void spawnfunc_ctf_team()
1995 if(!g_ctf) { remove(self); return; }
1997 self.classname = "ctf_team";
1998 self.team = self.cnt + 1;
2001 // compatibility for quake maps
2002 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2003 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2004 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2005 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2006 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2007 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2015 void ctf_ScoreRules()
2017 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2018 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2019 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2020 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2021 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2022 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2023 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2024 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2025 ScoreRules_basics_end();
2028 // code from here on is just to support maps that don't have flag and team entities
2029 void ctf_SpawnTeam (string teamname, float teamcolor)
2034 self.classname = "ctf_team";
2035 self.netname = teamname;
2036 self.cnt = teamcolor;
2038 spawnfunc_ctf_team();
2043 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2045 // if no teams are found, spawn defaults
2046 if(find(world, classname, "ctf_team") == world)
2048 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2049 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
2050 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
2056 void ctf_Initialize()
2058 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2060 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2061 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2062 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2064 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2068 MUTATOR_DEFINITION(gamemode_ctf)
2070 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2071 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2072 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2073 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2074 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2075 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2076 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2077 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2078 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2079 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2080 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2081 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2082 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2083 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2087 if(time > 1) // game loads at time 1
2088 error("This is a game type and it cannot be added at runtime.");
2096 error("This is a game type and it cannot be removed at runtime.");