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 void ctf_CaptureRecord(entity flag, entity player)
26 float cap_record = ctf_captimerecord;
27 float cap_time = (time - flag.ctf_pickuptime);
28 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
31 FOR_EACH_REALCLIENT(tmp_entity)
33 if(tmp_entity.CAPTURE_VERBOSE)
35 if(!ctf_captimerecord) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
36 else if(cap_time < cap_record) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
37 else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
39 else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_), player.netname); }
42 // the previous notification broadcast is only sent to real clients, this will notify server log too
43 if(server_is_dedicated)
45 if(autocvar_notification_ctf_capture_verbose)
47 if(!ctf_captimerecord) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
48 else if(cap_time < cap_record) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
49 else { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
51 else { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_), player.netname); }
54 // write that shit in the database
55 if((!ctf_captimerecord) || (cap_time < cap_record))
57 ctf_captimerecord = cap_time;
58 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
59 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
60 write_recordmarker(player, (time - cap_time), cap_time);
64 void ctf_FlagcarrierWaypoints(entity player)
66 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
67 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
68 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
69 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
72 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
74 float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis
75 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
76 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
77 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
80 if(current_height) // make sure we can actually do this arcing path
82 targpos = (to + ('0 0 1' * current_height));
83 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
84 if(trace_fraction < 1)
86 //print("normal arc line failed, trying to find new pos...");
87 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
88 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
89 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
90 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
91 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
94 else { targpos = to; }
96 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
98 vector desired_direction = normalize(targpos - from);
99 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
100 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
103 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
105 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
107 // directional tracing only
109 makevectors(passer_angle);
111 // find the closest point on the enemy to the center of the attack
112 float ang; // angle between shotdir and h
113 float h; // hypotenuse, which is the distance between attacker to head
114 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
116 h = vlen(head_center - passer_center);
117 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
120 vector nearest_on_line = (passer_center + a * v_forward);
121 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
123 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
124 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
126 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
131 else { return TRUE; }
135 // =======================
136 // CaptureShield Functions
137 // =======================
139 float ctf_CaptureShield_CheckStatus(entity p)
143 float players_worseeq, players_total;
145 if(ctf_captureshield_max_ratio <= 0)
148 s = PlayerScore_Add(p, SP_SCORE, 0);
149 if(s >= -ctf_captureshield_min_negscore)
152 players_total = players_worseeq = 0;
155 if(IsDifferentTeam(e, p))
157 se = PlayerScore_Add(e, SP_SCORE, 0);
163 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
164 // use this rule here
166 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
172 void ctf_CaptureShield_Update(entity player, float wanted_status)
174 float updated_status = ctf_CaptureShield_CheckStatus(player);
175 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
177 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
178 player.ctf_captureshielded = updated_status;
182 float ctf_CaptureShield_Customize()
184 if(!other.ctf_captureshielded) { return FALSE; }
185 if(!IsDifferentTeam(self, other)) { return FALSE; }
190 void ctf_CaptureShield_Touch()
192 if(!other.ctf_captureshielded) { return; }
193 if(!IsDifferentTeam(self, other)) { return; }
195 vector mymid = (self.absmin + self.absmax) * 0.5;
196 vector othermid = (other.absmin + other.absmax) * 0.5;
198 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
199 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
202 void ctf_CaptureShield_Spawn(entity flag)
204 entity shield = spawn();
207 shield.team = self.team;
208 shield.touch = ctf_CaptureShield_Touch;
209 shield.customizeentityforclient = ctf_CaptureShield_Customize;
210 shield.classname = "ctf_captureshield";
211 shield.effects = EF_ADDITIVE;
212 shield.movetype = MOVETYPE_NOCLIP;
213 shield.solid = SOLID_TRIGGER;
214 shield.avelocity = '7 0 11';
217 setorigin(shield, self.origin);
218 setmodel(shield, "models/ctf/shield.md3");
219 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
223 // ====================
224 // Drop/Pass/Throw Code
225 // ====================
227 void ctf_Handle_Drop(entity flag, entity player, float droptype)
230 player = (player ? player : flag.pass_sender);
233 flag.movetype = MOVETYPE_TOSS;
234 flag.takedamage = DAMAGE_YES;
235 flag.angles = '0 0 0';
236 flag.health = flag.max_flag_health;
237 flag.ctf_droptime = time;
238 flag.ctf_dropper = player;
239 flag.ctf_status = FLAG_DROPPED;
241 // messages and sounds
242 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
243 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
244 ctf_EventLog("dropped", player.team, player);
247 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
248 PlayerScore_Add(player, SP_CTF_DROPS, 1);
251 if(autocvar_g_ctf_flag_dropped_waypoint)
252 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));
254 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
256 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
257 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
260 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
262 if(droptype == DROP_PASS)
264 flag.pass_distance = 0;
265 flag.pass_sender = world;
266 flag.pass_target = world;
270 void ctf_Handle_Retrieve(entity flag, entity player)
272 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
273 entity sender = flag.pass_sender;
275 // transfer flag to player
277 flag.owner.flagcarried = flag;
280 setattachment(flag, player, "");
281 setorigin(flag, FLAG_CARRY_OFFSET);
282 flag.movetype = MOVETYPE_NONE;
283 flag.takedamage = DAMAGE_NO;
284 flag.solid = SOLID_NOT;
285 flag.angles = '0 0 0';
286 flag.ctf_status = FLAG_CARRY;
288 // messages and sounds
289 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
290 ctf_EventLog("receive", flag.team, player);
292 FOR_EACH_REALPLAYER(tmp_player)
294 if(tmp_player == sender)
295 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
296 else if(tmp_player == player)
297 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
298 else if(!IsDifferentTeam(tmp_player, sender))
299 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
302 // create new waypoint
303 ctf_FlagcarrierWaypoints(player);
305 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
306 player.throw_antispam = sender.throw_antispam;
308 flag.pass_distance = 0;
309 flag.pass_sender = world;
310 flag.pass_target = world;
313 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
315 entity flag = player.flagcarried;
316 vector targ_origin, flag_velocity;
318 if(!flag) { return; }
319 if((droptype == DROP_PASS) && !receiver) { return; }
321 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
324 setattachment(flag, world, "");
325 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
326 flag.owner.flagcarried = world;
328 flag.solid = SOLID_TRIGGER;
329 flag.ctf_dropper = player;
330 flag.ctf_droptime = time;
332 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
339 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
340 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
341 WarpZone_RefSys_Copy(flag, receiver);
342 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
343 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
345 flag.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' * player.origin_x) + ('0 1 0' * player.origin_y))); // for the sake of this check, exclude Z axis
346 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
349 flag.movetype = MOVETYPE_FLY;
350 flag.takedamage = DAMAGE_NO;
351 flag.pass_sender = player;
352 flag.pass_target = receiver;
353 flag.ctf_status = FLAG_PASSING;
356 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
357 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
358 ctf_EventLog("pass", flag.team, player);
364 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'));
366 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)));
367 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
368 ctf_Handle_Drop(flag, player, droptype);
374 flag.velocity = '0 0 0'; // do nothing
381 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);
382 ctf_Handle_Drop(flag, player, droptype);
387 // kill old waypointsprite
388 WaypointSprite_Ping(player.wps_flagcarrier);
389 WaypointSprite_Kill(player.wps_flagcarrier);
391 if(player.wps_enemyflagcarrier)
392 WaypointSprite_Kill(player.wps_enemyflagcarrier);
395 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
403 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
405 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
406 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
407 float old_time, new_time;
409 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
411 // messages and sounds
412 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
413 ctf_CaptureRecord(enemy_flag, player);
414 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
418 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
419 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
424 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
425 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
427 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
428 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
429 if(!old_time || new_time < old_time)
430 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
433 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
434 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
437 if(capturetype == CAPTURE_NORMAL)
439 WaypointSprite_Kill(player.wps_flagcarrier);
440 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
442 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
443 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
447 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
448 ctf_RespawnFlag(enemy_flag);
451 void ctf_Handle_Return(entity flag, entity player)
453 // messages and sounds
454 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
455 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
456 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
457 ctf_EventLog("return", flag.team, player);
460 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
461 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
463 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
467 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
468 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
469 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
473 ctf_RespawnFlag(flag);
476 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
479 float pickup_dropped_score; // used to calculate dropped pickup score
481 // attach the flag to the player
483 player.flagcarried = flag;
484 setattachment(flag, player, "");
485 setorigin(flag, FLAG_CARRY_OFFSET);
488 flag.movetype = MOVETYPE_NONE;
489 flag.takedamage = DAMAGE_NO;
490 flag.solid = SOLID_NOT;
491 flag.angles = '0 0 0';
492 flag.ctf_status = FLAG_CARRY;
496 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
497 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
501 // messages and sounds
502 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
503 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
504 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
505 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CENTER, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
506 Send_Notification(NOTIF_TEAM, flag, MSG_CENTER, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
507 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
510 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
515 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
516 ctf_EventLog("steal", flag.team, player);
522 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);
523 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);
524 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
525 PlayerTeamScore_AddScore(player, pickup_dropped_score);
526 ctf_EventLog("pickup", flag.team, player);
534 if(pickuptype == PICKUP_BASE)
536 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
537 if((player.speedrunning) && (ctf_captimerecord))
538 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
542 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
545 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
546 ctf_FlagcarrierWaypoints(player);
547 WaypointSprite_Ping(player.wps_flagcarrier);
551 // ===================
552 // Main Flag Functions
553 // ===================
555 void ctf_CheckFlagReturn(entity flag, float returntype)
557 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
559 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
561 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
565 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
566 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
567 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
568 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
572 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
574 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
575 ctf_EventLog("returned", flag.team, world);
576 ctf_RespawnFlag(flag);
581 void ctf_CheckStalemate(void)
584 float stale_red_flags = 0, stale_blue_flags = 0;
587 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
589 // build list of stale flags
590 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
592 if(autocvar_g_ctf_stalemate)
593 if(tmp_entity.ctf_status != FLAG_BASE)
594 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
596 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
597 ctf_staleflaglist = tmp_entity;
599 switch(tmp_entity.team)
601 case NUM_TEAM_1: ++stale_red_flags; break;
602 case NUM_TEAM_2: ++stale_blue_flags; break;
607 if(stale_red_flags && stale_blue_flags)
608 ctf_stalemate = TRUE;
609 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
610 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
611 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
612 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
614 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
617 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
619 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
620 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));
623 if not(wpforenemy_announced)
625 FOR_EACH_REALPLAYER(tmp_entity)
626 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
628 wpforenemy_announced = TRUE;
633 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
635 if(ITEM_DAMAGE_NEEDKILL(deathtype))
637 // automatically kill the flag and return it
639 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
642 if(autocvar_g_ctf_flag_return_damage)
644 // reduce health and check if it should be returned
645 self.health = self.health - damage;
646 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
656 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
659 if(self == ctf_worldflaglist) // only for the first flag
660 FOR_EACH_CLIENT(tmp_entity)
661 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
664 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
665 dprint("wtf the flag got squashed?\n");
666 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
667 if(!trace_startsolid) // can we resize it without getting stuck?
668 setsize(self, FLAG_MIN, FLAG_MAX); }
670 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
674 self.angles = '0 0 0';
682 switch(self.ctf_status)
686 if(autocvar_g_ctf_dropped_capture_radius)
688 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
689 if(tmp_entity.ctf_status == FLAG_DROPPED)
690 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
691 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
692 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
699 if(autocvar_g_ctf_flag_dropped_floatinwater)
701 vector midpoint = ((self.absmin + self.absmax) * 0.5);
702 if(pointcontents(midpoint) == CONTENT_WATER)
704 self.velocity = self.velocity * 0.5;
706 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
707 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
709 { self.movetype = MOVETYPE_FLY; }
711 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
713 if(autocvar_g_ctf_flag_return_dropped)
715 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
718 ctf_CheckFlagReturn(self, RETURN_DROPPED);
722 if(autocvar_g_ctf_flag_return_time)
724 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
725 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
733 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
736 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
740 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
744 if(autocvar_g_ctf_stalemate)
746 if(time >= wpforenemy_nextthink)
748 ctf_CheckStalemate();
749 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
757 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
758 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
759 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
761 if((self.pass_target == world)
762 || (self.pass_target.deadflag != DEAD_NO)
763 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
764 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
765 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
767 // give up, pass failed
768 ctf_Handle_Drop(self, world, DROP_PASS);
772 // still a viable target, go for it
773 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
778 default: // this should never happen
780 dprint("ctf_FlagThink(): Flag exists with no status?\n");
788 if(gameover) { return; }
790 entity toucher = other;
792 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
793 if(ITEM_TOUCH_NEEDKILL())
796 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
800 // special touch behaviors
801 if(toucher.vehicle_flags & VHF_ISVEHICLE)
803 if(autocvar_g_ctf_allow_vehicle_touch)
804 toucher = toucher.owner; // the player is actually the vehicle owner, not other
806 return; // do nothing
808 else if not(IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
810 if(time > self.wait) // if we haven't in a while, play a sound/effect
812 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
813 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
814 self.wait = time + FLAG_TOUCHRATE;
818 else if(toucher.deadflag != DEAD_NO) { return; }
820 switch(self.ctf_status)
824 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
825 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
826 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
827 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
833 if(!IsDifferentTeam(toucher, self))
834 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
835 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
836 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
842 dprint("Someone touched a flag even though it was being carried?\n");
848 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
850 if(IsDifferentTeam(toucher, self.pass_sender))
851 ctf_Handle_Return(self, toucher);
853 ctf_Handle_Retrieve(self, toucher);
861 void ctf_RespawnFlag(entity flag)
863 // check for flag respawn being called twice in a row
864 if(flag.last_respawn > time - 0.5)
865 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
867 flag.last_respawn = time;
869 // reset the player (if there is one)
870 if((flag.owner) && (flag.owner.flagcarried == flag))
872 if(flag.owner.wps_enemyflagcarrier)
873 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
875 WaypointSprite_Kill(flag.wps_flagcarrier);
877 flag.owner.flagcarried = world;
879 if(flag.speedrunning)
880 ctf_FakeTimeLimit(flag.owner, -1);
883 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
884 { WaypointSprite_Kill(flag.wps_flagdropped); }
887 setattachment(flag, world, "");
888 setorigin(flag, flag.ctf_spawnorigin);
890 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
891 flag.takedamage = DAMAGE_NO;
892 flag.health = flag.max_flag_health;
893 flag.solid = SOLID_TRIGGER;
894 flag.velocity = '0 0 0';
895 flag.angles = flag.mangle;
896 flag.flags = FL_ITEM | FL_NOTARGET;
898 flag.ctf_status = FLAG_BASE;
900 flag.pass_distance = 0;
901 flag.pass_sender = world;
902 flag.pass_target = world;
903 flag.ctf_dropper = world;
904 flag.ctf_pickuptime = 0;
905 flag.ctf_droptime = 0;
911 if(IS_PLAYER(self.owner))
912 ctf_Handle_Throw(self.owner, world, DROP_RESET);
914 ctf_RespawnFlag(self);
917 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
920 waypoint_spawnforitem_force(self, self.origin);
921 self.nearestwaypointtimeout = 0; // activate waypointing again
922 self.bot_basewaypoint = self.nearestwaypoint;
925 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
926 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
928 // captureshield setup
929 ctf_CaptureShield_Spawn(self);
932 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
935 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.
936 self = flag; // for later usage with droptofloor()
939 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
940 ctf_worldflaglist = flag;
942 setattachment(flag, world, "");
944 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
945 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
946 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
947 flag.classname = "item_flag_team";
948 flag.target = "###item###"; // wut?
949 flag.flags = FL_ITEM | FL_NOTARGET;
950 flag.solid = SOLID_TRIGGER;
951 flag.takedamage = DAMAGE_NO;
952 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
953 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
954 flag.health = flag.max_flag_health;
955 flag.event_damage = ctf_FlagDamage;
956 flag.pushable = TRUE;
957 flag.teleportable = TELEPORT_NORMAL;
958 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
959 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
960 flag.velocity = '0 0 0';
961 flag.mangle = flag.angles;
962 flag.reset = ctf_Reset;
963 flag.touch = ctf_FlagTouch;
964 flag.think = ctf_FlagThink;
965 flag.nextthink = time + FLAG_THINKRATE;
966 flag.ctf_status = FLAG_BASE;
969 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
970 if(!flag.scale) { flag.scale = FLAG_SCALE; }
971 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
972 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
973 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
974 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
977 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
978 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
979 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
980 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.
981 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
982 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
983 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
986 precache_sound(flag.snd_flag_taken);
987 precache_sound(flag.snd_flag_returned);
988 precache_sound(flag.snd_flag_capture);
989 precache_sound(flag.snd_flag_respawn);
990 precache_sound(flag.snd_flag_dropped);
991 precache_sound(flag.snd_flag_touch);
992 precache_sound(flag.snd_flag_pass);
993 precache_model(flag.model);
994 precache_model("models/ctf/shield.md3");
995 precache_model("models/ctf/shockwavetransring.md3");
998 setmodel(flag, flag.model); // precision set below
999 setsize(flag, FLAG_MIN, FLAG_MAX);
1000 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1002 if(autocvar_g_ctf_flag_glowtrails)
1004 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1005 flag.glow_size = 25;
1006 flag.glow_trail = 1;
1009 flag.effects |= EF_LOWPRECISION;
1010 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1011 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1014 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1016 flag.dropped_origin = flag.origin;
1017 flag.noalign = TRUE;
1018 flag.movetype = MOVETYPE_NONE;
1020 else // drop to floor, automatically find a platform and set that as spawn origin
1022 flag.noalign = FALSE;
1025 flag.movetype = MOVETYPE_TOSS;
1028 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1036 // NOTE: LEGACY CODE, needs to be re-written!
1038 void havocbot_calculate_middlepoint()
1042 vector fo = '0 0 0';
1045 f = ctf_worldflaglist;
1050 f = f.ctf_worldflagnext;
1054 havocbot_ctf_middlepoint = s * (1.0 / n);
1055 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1059 entity havocbot_ctf_find_flag(entity bot)
1062 f = ctf_worldflaglist;
1065 if (bot.team == f.team)
1067 f = f.ctf_worldflagnext;
1072 entity havocbot_ctf_find_enemy_flag(entity bot)
1075 f = ctf_worldflaglist;
1078 if (bot.team != f.team)
1080 f = f.ctf_worldflagnext;
1085 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1093 FOR_EACH_PLAYER(head)
1095 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1098 if(vlen(head.origin - org) < tc_radius)
1105 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1108 head = ctf_worldflaglist;
1111 if (self.team == head.team)
1113 head = head.ctf_worldflagnext;
1116 navigation_routerating(head, ratingscale, 10000);
1119 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1122 head = ctf_worldflaglist;
1125 if (self.team == head.team)
1127 head = head.ctf_worldflagnext;
1132 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1135 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1138 head = ctf_worldflaglist;
1141 if (self.team != head.team)
1143 head = head.ctf_worldflagnext;
1146 navigation_routerating(head, ratingscale, 10000);
1149 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1151 if not(bot_waypoints_for_items)
1153 havocbot_goalrating_ctf_enemyflag(ratingscale);
1159 head = havocbot_ctf_find_enemy_flag(self);
1164 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1167 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1171 mf = havocbot_ctf_find_flag(self);
1173 if(mf.ctf_status == FLAG_BASE)
1177 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1180 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1183 head = ctf_worldflaglist;
1186 // flag is out in the field
1187 if(head.ctf_status != FLAG_BASE)
1188 if(head.tag_entity==world) // dropped
1192 if(vlen(org-head.origin)<df_radius)
1193 navigation_routerating(head, ratingscale, 10000);
1196 navigation_routerating(head, ratingscale, 10000);
1199 head = head.ctf_worldflagnext;
1203 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1207 head = findchainfloat(bot_pickup, TRUE);
1210 // gather health and armor only
1212 if (head.health || head.armorvalue)
1213 if (vlen(head.origin - org) < sradius)
1215 // get the value of the item
1216 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1218 navigation_routerating(head, t * ratingscale, 500);
1224 void havocbot_ctf_reset_role(entity bot)
1226 float cdefense, cmiddle, coffense;
1227 entity mf, ef, head;
1230 if(bot.deadflag != DEAD_NO)
1233 if(vlen(havocbot_ctf_middlepoint)==0)
1234 havocbot_calculate_middlepoint();
1237 if (bot.flagcarried)
1239 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1243 mf = havocbot_ctf_find_flag(bot);
1244 ef = havocbot_ctf_find_enemy_flag(bot);
1246 // Retrieve stolen flag
1247 if(mf.ctf_status!=FLAG_BASE)
1249 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1253 // If enemy flag is taken go to the middle to intercept pursuers
1254 if(ef.ctf_status!=FLAG_BASE)
1256 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1260 // if there is only me on the team switch to offense
1262 FOR_EACH_PLAYER(head)
1263 if(head.team==bot.team)
1268 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1272 // Evaluate best position to take
1273 // Count mates on middle position
1274 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1276 // Count mates on defense position
1277 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1279 // Count mates on offense position
1280 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1282 if(cdefense<=coffense)
1283 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1284 else if(coffense<=cmiddle)
1285 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1287 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1290 void havocbot_role_ctf_carrier()
1292 if(self.deadflag != DEAD_NO)
1294 havocbot_ctf_reset_role(self);
1298 if (self.flagcarried == world)
1300 havocbot_ctf_reset_role(self);
1304 if (self.bot_strategytime < time)
1306 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1308 navigation_goalrating_start();
1309 havocbot_goalrating_ctf_ourbase(50000);
1312 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1314 navigation_goalrating_end();
1316 if (self.navigation_hasgoals)
1317 self.havocbot_cantfindflag = time + 10;
1318 else if (time > self.havocbot_cantfindflag)
1320 // Can't navigate to my own base, suicide!
1321 // TODO: drop it and wander around
1322 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1328 void havocbot_role_ctf_escort()
1332 if(self.deadflag != DEAD_NO)
1334 havocbot_ctf_reset_role(self);
1338 if (self.flagcarried)
1340 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1344 // If enemy flag is back on the base switch to previous role
1345 ef = havocbot_ctf_find_enemy_flag(self);
1346 if(ef.ctf_status==FLAG_BASE)
1348 self.havocbot_role = self.havocbot_previous_role;
1349 self.havocbot_role_timeout = 0;
1353 // If the flag carrier reached the base switch to defense
1354 mf = havocbot_ctf_find_flag(self);
1355 if(mf.ctf_status!=FLAG_BASE)
1356 if(vlen(ef.origin - mf.dropped_origin) < 300)
1358 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1362 // Set the role timeout if necessary
1363 if (!self.havocbot_role_timeout)
1365 self.havocbot_role_timeout = time + random() * 30 + 60;
1368 // If nothing happened just switch to previous role
1369 if (time > self.havocbot_role_timeout)
1371 self.havocbot_role = self.havocbot_previous_role;
1372 self.havocbot_role_timeout = 0;
1376 // Chase the flag carrier
1377 if (self.bot_strategytime < time)
1379 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1380 navigation_goalrating_start();
1381 havocbot_goalrating_ctf_enemyflag(30000);
1382 havocbot_goalrating_ctf_ourstolenflag(40000);
1383 havocbot_goalrating_items(10000, self.origin, 10000);
1384 navigation_goalrating_end();
1388 void havocbot_role_ctf_offense()
1393 if(self.deadflag != DEAD_NO)
1395 havocbot_ctf_reset_role(self);
1399 if (self.flagcarried)
1401 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1406 mf = havocbot_ctf_find_flag(self);
1407 ef = havocbot_ctf_find_enemy_flag(self);
1410 if(mf.ctf_status!=FLAG_BASE)
1413 pos = mf.tag_entity.origin;
1417 // Try to get it if closer than the enemy base
1418 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1420 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1425 // Escort flag carrier
1426 if(ef.ctf_status!=FLAG_BASE)
1429 pos = ef.tag_entity.origin;
1433 if(vlen(pos-mf.dropped_origin)>700)
1435 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1440 // About to fail, switch to middlefield
1443 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1447 // Set the role timeout if necessary
1448 if (!self.havocbot_role_timeout)
1449 self.havocbot_role_timeout = time + 120;
1451 if (time > self.havocbot_role_timeout)
1453 havocbot_ctf_reset_role(self);
1457 if (self.bot_strategytime < time)
1459 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1460 navigation_goalrating_start();
1461 havocbot_goalrating_ctf_ourstolenflag(50000);
1462 havocbot_goalrating_ctf_enemybase(20000);
1463 havocbot_goalrating_items(5000, self.origin, 1000);
1464 havocbot_goalrating_items(1000, self.origin, 10000);
1465 navigation_goalrating_end();
1469 // Retriever (temporary role):
1470 void havocbot_role_ctf_retriever()
1474 if(self.deadflag != DEAD_NO)
1476 havocbot_ctf_reset_role(self);
1480 if (self.flagcarried)
1482 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1486 // If flag is back on the base switch to previous role
1487 mf = havocbot_ctf_find_flag(self);
1488 if(mf.ctf_status==FLAG_BASE)
1490 havocbot_ctf_reset_role(self);
1494 if (!self.havocbot_role_timeout)
1495 self.havocbot_role_timeout = time + 20;
1497 if (time > self.havocbot_role_timeout)
1499 havocbot_ctf_reset_role(self);
1503 if (self.bot_strategytime < time)
1508 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1509 navigation_goalrating_start();
1510 havocbot_goalrating_ctf_ourstolenflag(50000);
1511 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1512 havocbot_goalrating_ctf_enemybase(30000);
1513 havocbot_goalrating_items(500, self.origin, rt_radius);
1514 navigation_goalrating_end();
1518 void havocbot_role_ctf_middle()
1522 if(self.deadflag != DEAD_NO)
1524 havocbot_ctf_reset_role(self);
1528 if (self.flagcarried)
1530 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1534 mf = havocbot_ctf_find_flag(self);
1535 if(mf.ctf_status!=FLAG_BASE)
1537 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1541 if (!self.havocbot_role_timeout)
1542 self.havocbot_role_timeout = time + 10;
1544 if (time > self.havocbot_role_timeout)
1546 havocbot_ctf_reset_role(self);
1550 if (self.bot_strategytime < time)
1554 org = havocbot_ctf_middlepoint;
1555 org_z = self.origin_z;
1557 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1558 navigation_goalrating_start();
1559 havocbot_goalrating_ctf_ourstolenflag(50000);
1560 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1561 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1562 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1563 havocbot_goalrating_items(2500, self.origin, 10000);
1564 havocbot_goalrating_ctf_enemybase(2500);
1565 navigation_goalrating_end();
1569 void havocbot_role_ctf_defense()
1573 if(self.deadflag != DEAD_NO)
1575 havocbot_ctf_reset_role(self);
1579 if (self.flagcarried)
1581 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1585 // If own flag was captured
1586 mf = havocbot_ctf_find_flag(self);
1587 if(mf.ctf_status!=FLAG_BASE)
1589 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1593 if (!self.havocbot_role_timeout)
1594 self.havocbot_role_timeout = time + 30;
1596 if (time > self.havocbot_role_timeout)
1598 havocbot_ctf_reset_role(self);
1601 if (self.bot_strategytime < time)
1606 org = mf.dropped_origin;
1607 mp_radius = havocbot_ctf_middlepoint_radius;
1609 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1610 navigation_goalrating_start();
1612 // if enemies are closer to our base, go there
1613 entity head, closestplayer = world;
1614 float distance, bestdistance = 10000;
1615 FOR_EACH_PLAYER(head)
1617 if(head.deadflag!=DEAD_NO)
1620 distance = vlen(org - head.origin);
1621 if(distance<bestdistance)
1623 closestplayer = head;
1624 bestdistance = distance;
1629 if(closestplayer.team!=self.team)
1630 if(vlen(org - self.origin)>1000)
1631 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1632 havocbot_goalrating_ctf_ourbase(30000);
1634 havocbot_goalrating_ctf_ourstolenflag(20000);
1635 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1636 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1637 havocbot_goalrating_items(10000, org, mp_radius);
1638 havocbot_goalrating_items(5000, self.origin, 10000);
1639 navigation_goalrating_end();
1643 void havocbot_role_ctf_setrole(entity bot, float role)
1645 dprint(strcat(bot.netname," switched to "));
1648 case HAVOCBOT_CTF_ROLE_CARRIER:
1650 bot.havocbot_role = havocbot_role_ctf_carrier;
1651 bot.havocbot_role_timeout = 0;
1652 bot.havocbot_cantfindflag = time + 10;
1653 bot.bot_strategytime = 0;
1655 case HAVOCBOT_CTF_ROLE_DEFENSE:
1657 bot.havocbot_role = havocbot_role_ctf_defense;
1658 bot.havocbot_role_timeout = 0;
1660 case HAVOCBOT_CTF_ROLE_MIDDLE:
1662 bot.havocbot_role = havocbot_role_ctf_middle;
1663 bot.havocbot_role_timeout = 0;
1665 case HAVOCBOT_CTF_ROLE_OFFENSE:
1667 bot.havocbot_role = havocbot_role_ctf_offense;
1668 bot.havocbot_role_timeout = 0;
1670 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1671 dprint("retriever");
1672 bot.havocbot_previous_role = bot.havocbot_role;
1673 bot.havocbot_role = havocbot_role_ctf_retriever;
1674 bot.havocbot_role_timeout = time + 10;
1675 bot.bot_strategytime = 0;
1677 case HAVOCBOT_CTF_ROLE_ESCORT:
1679 bot.havocbot_previous_role = bot.havocbot_role;
1680 bot.havocbot_role = havocbot_role_ctf_escort;
1681 bot.havocbot_role_timeout = time + 30;
1682 bot.bot_strategytime = 0;
1693 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1697 // initially clear items so they can be set as necessary later.
1698 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1699 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1701 // scan through all the flags and notify the client about them
1702 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1704 switch(flag.ctf_status)
1709 if((flag.owner == self) || (flag.pass_sender == self))
1710 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1712 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1717 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1723 // item for stopping players from capturing the flag too often
1724 if(self.ctf_captureshielded)
1725 self.items |= IT_CTF_SHIELDED;
1727 // update the health of the flag carrier waypointsprite
1728 if(self.wps_flagcarrier)
1729 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1734 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1736 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1738 if(frag_target == frag_attacker) // damage done to yourself
1740 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1741 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1743 else // damage done to everyone else
1745 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1746 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1749 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1751 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1752 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1754 frag_target.wps_helpme_time = time;
1755 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1757 // todo: add notification for when flag carrier needs help?
1762 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1764 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1766 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1767 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1770 if(frag_target.flagcarried)
1771 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1776 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1779 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1782 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1784 entity flag; // temporary entity for the search method
1786 if(self.flagcarried)
1787 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1789 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1791 if(flag.pass_sender == self) { flag.pass_sender = world; }
1792 if(flag.pass_target == self) { flag.pass_target = world; }
1793 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1799 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1801 if(self.flagcarried)
1802 if(!autocvar_g_ctf_portalteleport)
1803 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1808 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1810 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1812 entity player = self;
1814 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1816 // pass the flag to a team mate
1817 if(autocvar_g_ctf_pass)
1819 entity head, closest_target = world;
1820 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1822 while(head) // find the closest acceptable target to pass to
1824 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1825 if(head != player && !IsDifferentTeam(head, player))
1826 if(!head.speedrunning && !head.vehicle)
1828 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1829 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1830 vector passer_center = CENTER_OR_VIEWOFS(player);
1832 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1834 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1836 if(IS_BOT_CLIENT(head))
1838 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1839 ctf_Handle_Throw(head, player, DROP_PASS);
1843 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1844 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1846 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1849 else if(player.flagcarried)
1853 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1854 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1855 { closest_target = head; }
1857 else { closest_target = head; }
1864 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1867 // throw the flag in front of you
1868 if(autocvar_g_ctf_throw && player.flagcarried)
1870 if(player.throw_count == -1)
1872 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1874 player.throw_prevtime = time;
1875 player.throw_count = 1;
1876 ctf_Handle_Throw(player, world, DROP_THROW);
1881 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1887 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1888 else { player.throw_count += 1; }
1889 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1891 player.throw_prevtime = time;
1892 ctf_Handle_Throw(player, world, DROP_THROW);
1901 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1903 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1905 self.wps_helpme_time = time;
1906 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1908 else // create a normal help me waypointsprite
1910 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');
1911 WaypointSprite_Ping(self.wps_helpme);
1917 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1919 if(vh_player.flagcarried)
1921 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1923 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1927 setattachment(vh_player.flagcarried, vh_vehicle, "");
1928 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1929 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1930 //vh_player.flagcarried.angles = '0 0 0';
1938 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1940 if(vh_player.flagcarried)
1942 setattachment(vh_player.flagcarried, vh_player, "");
1943 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1944 vh_player.flagcarried.scale = FLAG_SCALE;
1945 vh_player.flagcarried.angles = '0 0 0';
1952 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1954 if(self.flagcarried)
1956 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1957 ctf_RespawnFlag(self.flagcarried);
1964 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1966 entity flag; // temporary entity for the search method
1968 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1970 switch(flag.ctf_status)
1975 // lock the flag, game is over
1976 flag.movetype = MOVETYPE_NONE;
1977 flag.takedamage = DAMAGE_NO;
1978 flag.solid = SOLID_NOT;
1979 flag.nextthink = FALSE; // stop thinking
1981 //dprint("stopping the ", flag.netname, " from moving.\n");
1989 // do nothing for these flags
1998 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2000 havocbot_ctf_reset_role(self);
2004 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2006 GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2015 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2016 CTF Starting point for a player in team one (Red).
2017 Keys: "angle" viewing angle when spawning. */
2018 void spawnfunc_info_player_team1()
2020 if(g_assault) { remove(self); return; }
2022 self.team = NUM_TEAM_1; // red
2023 spawnfunc_info_player_deathmatch();
2027 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2028 CTF Starting point for a player in team two (Blue).
2029 Keys: "angle" viewing angle when spawning. */
2030 void spawnfunc_info_player_team2()
2032 if(g_assault) { remove(self); return; }
2034 self.team = NUM_TEAM_2; // blue
2035 spawnfunc_info_player_deathmatch();
2038 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2039 CTF Starting point for a player in team three (Yellow).
2040 Keys: "angle" viewing angle when spawning. */
2041 void spawnfunc_info_player_team3()
2043 if(g_assault) { remove(self); return; }
2045 self.team = NUM_TEAM_3; // yellow
2046 spawnfunc_info_player_deathmatch();
2050 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2051 CTF Starting point for a player in team four (Purple).
2052 Keys: "angle" viewing angle when spawning. */
2053 void spawnfunc_info_player_team4()
2055 if(g_assault) { remove(self); return; }
2057 self.team = NUM_TEAM_4; // purple
2058 spawnfunc_info_player_deathmatch();
2061 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2062 CTF flag for team one (Red).
2064 "angle" Angle the flag will point (minus 90 degrees)...
2065 "model" model to use, note this needs red and blue as skins 0 and 1...
2066 "noise" sound played when flag is picked up...
2067 "noise1" sound played when flag is returned by a teammate...
2068 "noise2" sound played when flag is captured...
2069 "noise3" sound played when flag is lost in the field and respawns itself...
2070 "noise4" sound played when flag is dropped by a player...
2071 "noise5" sound played when flag touches the ground... */
2072 void spawnfunc_item_flag_team1()
2074 if(!g_ctf) { remove(self); return; }
2076 ctf_FlagSetup(1, self); // 1 = red
2079 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2080 CTF flag for team two (Blue).
2082 "angle" Angle the flag will point (minus 90 degrees)...
2083 "model" model to use, note this needs red and blue as skins 0 and 1...
2084 "noise" sound played when flag is picked up...
2085 "noise1" sound played when flag is returned by a teammate...
2086 "noise2" sound played when flag is captured...
2087 "noise3" sound played when flag is lost in the field and respawns itself...
2088 "noise4" sound played when flag is dropped by a player...
2089 "noise5" sound played when flag touches the ground... */
2090 void spawnfunc_item_flag_team2()
2092 if(!g_ctf) { remove(self); return; }
2094 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2097 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2098 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2099 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.
2101 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2102 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2103 void spawnfunc_ctf_team()
2105 if(!g_ctf) { remove(self); return; }
2107 self.classname = "ctf_team";
2108 self.team = self.cnt + 1;
2111 // compatibility for quake maps
2112 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2113 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2114 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2115 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2116 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2117 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2125 void ctf_ScoreRules()
2127 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2128 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2129 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2130 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2131 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2132 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2133 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2134 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2135 ScoreRules_basics_end();
2138 // code from here on is just to support maps that don't have flag and team entities
2139 void ctf_SpawnTeam (string teamname, float teamcolor)
2144 self.classname = "ctf_team";
2145 self.netname = teamname;
2146 self.cnt = teamcolor;
2148 spawnfunc_ctf_team();
2153 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2155 // if no teams are found, spawn defaults
2156 if(find(world, classname, "ctf_team") == world)
2158 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2159 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2160 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2166 void ctf_Initialize()
2168 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2170 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2171 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2172 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2174 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2178 MUTATOR_DEFINITION(gamemode_ctf)
2180 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2181 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2182 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2183 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2184 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2185 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2186 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2187 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2188 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2189 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2190 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2191 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2192 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2193 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2194 MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2198 if(time > 1) // game loads at time 1
2199 error("This is a game type and it cannot be added at runtime.");
2203 MUTATOR_ONROLLBACK_OR_REMOVE
2205 // we actually cannot roll back ctf_Initialize here
2206 // BUT: we don't need to! If this gets called, adding always
2212 print("This is a game type and it cannot be removed at runtime.");