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)
25 float cap_record = ctf_captimerecord;
26 float cap_time = (time - flag.ctf_pickuptime);
27 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
30 if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
31 else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
32 else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
34 // write that shit in the database
35 if((!ctf_captimerecord) || (cap_time < cap_record))
37 ctf_captimerecord = cap_time;
38 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
39 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
40 write_recordmarker(player, (time - cap_time), cap_time);
44 void ctf_FlagcarrierWaypoints(entity player)
46 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
47 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
48 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
49 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
52 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
54 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
55 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
56 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
57 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
60 if(current_height) // make sure we can actually do this arcing path
62 targpos = (to + ('0 0 1' * current_height));
63 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
64 if(trace_fraction < 1)
66 //print("normal arc line failed, trying to find new pos...");
67 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
68 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
69 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
70 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
71 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
74 else { targpos = to; }
76 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
78 vector desired_direction = normalize(targpos - from);
79 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
80 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
83 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
85 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
87 // directional tracing only
89 makevectors(passer_angle);
91 // find the closest point on the enemy to the center of the attack
92 float ang; // angle between shotdir and h
93 float h; // hypotenuse, which is the distance between attacker to head
94 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
96 h = vlen(head_center - passer_center);
97 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
100 vector nearest_on_line = (passer_center + a * v_forward);
101 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
103 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
104 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
106 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
111 else { return TRUE; }
115 // =======================
116 // CaptureShield Functions
117 // =======================
119 float ctf_CaptureShield_CheckStatus(entity p)
123 float players_worseeq, players_total;
125 if(ctf_captureshield_max_ratio <= 0)
128 s = PlayerScore_Add(p, SP_SCORE, 0);
129 if(s >= -ctf_captureshield_min_negscore)
132 players_total = players_worseeq = 0;
137 se = PlayerScore_Add(e, SP_SCORE, 0);
143 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
144 // use this rule here
146 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
152 void ctf_CaptureShield_Update(entity player, float wanted_status)
154 float updated_status = ctf_CaptureShield_CheckStatus(player);
155 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
157 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
158 player.ctf_captureshielded = updated_status;
162 float ctf_CaptureShield_Customize()
164 if(!other.ctf_captureshielded) { return FALSE; }
165 if(SAME_TEAM(self, other)) { return FALSE; }
170 void ctf_CaptureShield_Touch()
172 if(!other.ctf_captureshielded) { return; }
173 if(SAME_TEAM(self, other)) { return; }
175 vector mymid = (self.absmin + self.absmax) * 0.5;
176 vector othermid = (other.absmin + other.absmax) * 0.5;
178 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
179 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
182 void ctf_CaptureShield_Spawn(entity flag)
184 entity shield = spawn();
187 shield.team = self.team;
188 shield.touch = ctf_CaptureShield_Touch;
189 shield.customizeentityforclient = ctf_CaptureShield_Customize;
190 shield.classname = "ctf_captureshield";
191 shield.effects = EF_ADDITIVE;
192 shield.movetype = MOVETYPE_NOCLIP;
193 shield.solid = SOLID_TRIGGER;
194 shield.avelocity = '7 0 11';
197 setorigin(shield, self.origin);
198 setmodel(shield, "models/ctf/shield.md3");
199 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
203 // ====================
204 // Drop/Pass/Throw Code
205 // ====================
207 void ctf_Handle_Drop(entity flag, entity player, float droptype)
210 player = (player ? player : flag.pass_sender);
213 flag.movetype = MOVETYPE_TOSS;
214 flag.takedamage = DAMAGE_YES;
215 flag.angles = '0 0 0';
216 flag.health = flag.max_flag_health;
217 flag.ctf_droptime = time;
218 flag.ctf_dropper = player;
219 flag.ctf_status = FLAG_DROPPED;
221 // messages and sounds
222 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
223 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
224 ctf_EventLog("dropped", player.team, player);
227 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
228 PlayerScore_Add(player, SP_CTF_DROPS, 1);
231 if(autocvar_g_ctf_flag_dropped_waypoint)
232 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));
234 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
236 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
237 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
240 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
242 if(droptype == DROP_PASS)
244 flag.pass_distance = 0;
245 flag.pass_sender = world;
246 flag.pass_target = world;
250 void ctf_Handle_Retrieve(entity flag, entity player)
252 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
253 entity sender = flag.pass_sender;
255 // transfer flag to player
257 flag.owner.flagcarried = flag;
260 setattachment(flag, player, "");
261 setorigin(flag, FLAG_CARRY_OFFSET);
262 flag.movetype = MOVETYPE_NONE;
263 flag.takedamage = DAMAGE_NO;
264 flag.solid = SOLID_NOT;
265 flag.angles = '0 0 0';
266 flag.ctf_status = FLAG_CARRY;
268 // messages and sounds
269 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
270 ctf_EventLog("receive", flag.team, player);
272 FOR_EACH_REALPLAYER(tmp_player)
274 if(tmp_player == sender)
275 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
276 else if(tmp_player == player)
277 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
278 else if(SAME_TEAM(tmp_player, sender))
279 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
282 // create new waypoint
283 ctf_FlagcarrierWaypoints(player);
285 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
286 player.throw_antispam = sender.throw_antispam;
288 flag.pass_distance = 0;
289 flag.pass_sender = world;
290 flag.pass_target = world;
293 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
295 entity flag = player.flagcarried;
296 vector targ_origin, flag_velocity;
298 if(!flag) { return; }
299 if((droptype == DROP_PASS) && !receiver) { return; }
301 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
304 setattachment(flag, world, "");
305 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
306 flag.owner.flagcarried = world;
308 flag.solid = SOLID_TRIGGER;
309 flag.ctf_dropper = player;
310 flag.ctf_droptime = time;
312 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
319 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
320 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
321 WarpZone_RefSys_Copy(flag, receiver);
322 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
323 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
325 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
326 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
329 flag.movetype = MOVETYPE_FLY;
330 flag.takedamage = DAMAGE_NO;
331 flag.pass_sender = player;
332 flag.pass_target = receiver;
333 flag.ctf_status = FLAG_PASSING;
336 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
337 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
338 ctf_EventLog("pass", flag.team, player);
344 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'));
346 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)));
347 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
348 ctf_Handle_Drop(flag, player, droptype);
354 flag.velocity = '0 0 0'; // do nothing
361 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);
362 ctf_Handle_Drop(flag, player, droptype);
367 // kill old waypointsprite
368 WaypointSprite_Ping(player.wps_flagcarrier);
369 WaypointSprite_Kill(player.wps_flagcarrier);
371 if(player.wps_enemyflagcarrier)
372 WaypointSprite_Kill(player.wps_enemyflagcarrier);
375 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
383 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
385 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
386 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
387 float old_time, new_time;
389 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
391 // messages and sounds
392 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
393 ctf_CaptureRecord(enemy_flag, player);
394 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
398 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
399 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
404 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
405 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
407 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
408 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
409 if(!old_time || new_time < old_time)
410 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
413 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
414 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
417 if(capturetype == CAPTURE_NORMAL)
419 WaypointSprite_Kill(player.wps_flagcarrier);
420 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
422 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
423 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
427 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
428 ctf_RespawnFlag(enemy_flag);
431 void ctf_Handle_Return(entity flag, entity player)
433 // messages and sounds
434 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
435 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
436 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
437 ctf_EventLog("return", flag.team, player);
440 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
441 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
443 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
447 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
448 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
449 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
453 ctf_RespawnFlag(flag);
456 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
459 float pickup_dropped_score; // used to calculate dropped pickup score
461 // attach the flag to the player
463 player.flagcarried = flag;
464 setattachment(flag, player, "");
465 setorigin(flag, FLAG_CARRY_OFFSET);
468 flag.movetype = MOVETYPE_NONE;
469 flag.takedamage = DAMAGE_NO;
470 flag.solid = SOLID_NOT;
471 flag.angles = '0 0 0';
472 flag.ctf_status = FLAG_CARRY;
476 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
477 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
481 // messages and sounds
482 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
483 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
484 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
486 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
487 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
489 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
492 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
497 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
498 ctf_EventLog("steal", flag.team, player);
504 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);
505 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);
506 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
507 PlayerTeamScore_AddScore(player, pickup_dropped_score);
508 ctf_EventLog("pickup", flag.team, player);
516 if(pickuptype == PICKUP_BASE)
518 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
519 if((player.speedrunning) && (ctf_captimerecord))
520 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
524 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
527 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
528 ctf_FlagcarrierWaypoints(player);
529 WaypointSprite_Ping(player.wps_flagcarrier);
533 // ===================
534 // Main Flag Functions
535 // ===================
537 void ctf_CheckFlagReturn(entity flag, float returntype)
539 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
541 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
543 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
547 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
548 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
549 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
550 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
554 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
556 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
557 ctf_EventLog("returned", flag.team, world);
558 ctf_RespawnFlag(flag);
563 void ctf_CheckStalemate(void)
566 float stale_red_flags = 0, stale_blue_flags = 0;
569 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
571 // build list of stale flags
572 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
574 if(autocvar_g_ctf_stalemate)
575 if(tmp_entity.ctf_status != FLAG_BASE)
576 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
578 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
579 ctf_staleflaglist = tmp_entity;
581 switch(tmp_entity.team)
583 case NUM_TEAM_1: ++stale_red_flags; break;
584 case NUM_TEAM_2: ++stale_blue_flags; break;
589 if(stale_red_flags && stale_blue_flags)
590 ctf_stalemate = TRUE;
591 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
592 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
593 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
594 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
596 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
599 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
601 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
602 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));
605 if not(wpforenemy_announced)
607 FOR_EACH_REALPLAYER(tmp_entity)
608 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
610 wpforenemy_announced = TRUE;
615 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
617 if(ITEM_DAMAGE_NEEDKILL(deathtype))
619 // automatically kill the flag and return it
621 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
624 if(autocvar_g_ctf_flag_return_damage)
626 // reduce health and check if it should be returned
627 self.health = self.health - damage;
628 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
638 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
641 if(self == ctf_worldflaglist) // only for the first flag
642 FOR_EACH_CLIENT(tmp_entity)
643 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
646 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
647 dprint("wtf the flag got squashed?\n");
648 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
649 if(!trace_startsolid) // can we resize it without getting stuck?
650 setsize(self, FLAG_MIN, FLAG_MAX); }
652 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
656 self.angles = '0 0 0';
664 switch(self.ctf_status)
668 if(autocvar_g_ctf_dropped_capture_radius)
670 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
671 if(tmp_entity.ctf_status == FLAG_DROPPED)
672 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
673 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
674 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
681 if(autocvar_g_ctf_flag_dropped_floatinwater)
683 vector midpoint = ((self.absmin + self.absmax) * 0.5);
684 if(pointcontents(midpoint) == CONTENT_WATER)
686 self.velocity = self.velocity * 0.5;
688 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
689 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
691 { self.movetype = MOVETYPE_FLY; }
693 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
695 if(autocvar_g_ctf_flag_return_dropped)
697 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
700 ctf_CheckFlagReturn(self, RETURN_DROPPED);
704 if(autocvar_g_ctf_flag_return_time)
706 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
707 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
715 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
718 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
722 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
726 if(autocvar_g_ctf_stalemate)
728 if(time >= wpforenemy_nextthink)
730 ctf_CheckStalemate();
731 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
739 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
740 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
741 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
743 if((self.pass_target == world)
744 || (self.pass_target.deadflag != DEAD_NO)
745 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
746 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
747 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
749 // give up, pass failed
750 ctf_Handle_Drop(self, world, DROP_PASS);
754 // still a viable target, go for it
755 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
760 default: // this should never happen
762 dprint("ctf_FlagThink(): Flag exists with no status?\n");
770 if(gameover) { return; }
772 entity toucher = other;
774 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
775 if(ITEM_TOUCH_NEEDKILL())
778 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
782 // special touch behaviors
783 if(toucher.vehicle_flags & VHF_ISVEHICLE)
785 if(autocvar_g_ctf_allow_vehicle_touch)
786 toucher = toucher.owner; // the player is actually the vehicle owner, not other
788 return; // do nothing
790 else if not(IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
792 if(time > self.wait) // if we haven't in a while, play a sound/effect
794 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
795 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
796 self.wait = time + FLAG_TOUCHRATE;
800 else if(toucher.deadflag != DEAD_NO) { return; }
802 switch(self.ctf_status)
806 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self))
807 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
808 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
809 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
815 if(SAME_TEAM(toucher, self))
816 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
817 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
818 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
824 dprint("Someone touched a flag even though it was being carried?\n");
830 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
832 if(DIFF_TEAM(toucher, self.pass_sender))
833 ctf_Handle_Return(self, toucher);
835 ctf_Handle_Retrieve(self, toucher);
843 void ctf_RespawnFlag(entity flag)
845 // check for flag respawn being called twice in a row
846 if(flag.last_respawn > time - 0.5)
847 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
849 flag.last_respawn = time;
851 // reset the player (if there is one)
852 if((flag.owner) && (flag.owner.flagcarried == flag))
854 if(flag.owner.wps_enemyflagcarrier)
855 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
857 WaypointSprite_Kill(flag.wps_flagcarrier);
859 flag.owner.flagcarried = world;
861 if(flag.speedrunning)
862 ctf_FakeTimeLimit(flag.owner, -1);
865 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
866 { WaypointSprite_Kill(flag.wps_flagdropped); }
869 setattachment(flag, world, "");
870 setorigin(flag, flag.ctf_spawnorigin);
872 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
873 flag.takedamage = DAMAGE_NO;
874 flag.health = flag.max_flag_health;
875 flag.solid = SOLID_TRIGGER;
876 flag.velocity = '0 0 0';
877 flag.angles = flag.mangle;
878 flag.flags = FL_ITEM | FL_NOTARGET;
880 flag.ctf_status = FLAG_BASE;
882 flag.pass_distance = 0;
883 flag.pass_sender = world;
884 flag.pass_target = world;
885 flag.ctf_dropper = world;
886 flag.ctf_pickuptime = 0;
887 flag.ctf_droptime = 0;
893 if(IS_PLAYER(self.owner))
894 ctf_Handle_Throw(self.owner, world, DROP_RESET);
896 ctf_RespawnFlag(self);
899 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
902 waypoint_spawnforitem_force(self, self.origin);
903 self.nearestwaypointtimeout = 0; // activate waypointing again
904 self.bot_basewaypoint = self.nearestwaypoint;
907 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
908 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
910 // captureshield setup
911 ctf_CaptureShield_Spawn(self);
914 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
917 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.
918 self = flag; // for later usage with droptofloor()
921 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
922 ctf_worldflaglist = flag;
924 setattachment(flag, world, "");
926 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
927 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
928 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
929 flag.classname = "item_flag_team";
930 flag.target = "###item###"; // wut?
931 flag.flags = FL_ITEM | FL_NOTARGET;
932 flag.solid = SOLID_TRIGGER;
933 flag.takedamage = DAMAGE_NO;
934 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
935 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
936 flag.health = flag.max_flag_health;
937 flag.event_damage = ctf_FlagDamage;
938 flag.pushable = TRUE;
939 flag.teleportable = TELEPORT_NORMAL;
940 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
941 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
942 flag.velocity = '0 0 0';
943 flag.mangle = flag.angles;
944 flag.reset = ctf_Reset;
945 flag.touch = ctf_FlagTouch;
946 flag.think = ctf_FlagThink;
947 flag.nextthink = time + FLAG_THINKRATE;
948 flag.ctf_status = FLAG_BASE;
951 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
952 if(!flag.scale) { flag.scale = FLAG_SCALE; }
953 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
954 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
955 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
956 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
959 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
960 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
961 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
962 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.
963 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
964 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
965 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
968 precache_sound(flag.snd_flag_taken);
969 precache_sound(flag.snd_flag_returned);
970 precache_sound(flag.snd_flag_capture);
971 precache_sound(flag.snd_flag_respawn);
972 precache_sound(flag.snd_flag_dropped);
973 precache_sound(flag.snd_flag_touch);
974 precache_sound(flag.snd_flag_pass);
975 precache_model(flag.model);
976 precache_model("models/ctf/shield.md3");
977 precache_model("models/ctf/shockwavetransring.md3");
980 setmodel(flag, flag.model); // precision set below
981 setsize(flag, FLAG_MIN, FLAG_MAX);
982 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
984 if(autocvar_g_ctf_flag_glowtrails)
986 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
991 flag.effects |= EF_LOWPRECISION;
992 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
993 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
996 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
998 flag.dropped_origin = flag.origin;
1000 flag.movetype = MOVETYPE_NONE;
1002 else // drop to floor, automatically find a platform and set that as spawn origin
1004 flag.noalign = FALSE;
1007 flag.movetype = MOVETYPE_TOSS;
1010 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1018 // NOTE: LEGACY CODE, needs to be re-written!
1020 void havocbot_calculate_middlepoint()
1024 vector fo = '0 0 0';
1027 f = ctf_worldflaglist;
1032 f = f.ctf_worldflagnext;
1036 havocbot_ctf_middlepoint = s * (1.0 / n);
1037 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1041 entity havocbot_ctf_find_flag(entity bot)
1044 f = ctf_worldflaglist;
1047 if (bot.team == f.team)
1049 f = f.ctf_worldflagnext;
1054 entity havocbot_ctf_find_enemy_flag(entity bot)
1057 f = ctf_worldflaglist;
1060 if (bot.team != f.team)
1062 f = f.ctf_worldflagnext;
1067 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1075 FOR_EACH_PLAYER(head)
1077 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1080 if(vlen(head.origin - org) < tc_radius)
1087 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1090 head = ctf_worldflaglist;
1093 if (self.team == head.team)
1095 head = head.ctf_worldflagnext;
1098 navigation_routerating(head, ratingscale, 10000);
1101 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1104 head = ctf_worldflaglist;
1107 if (self.team == head.team)
1109 head = head.ctf_worldflagnext;
1114 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1117 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1120 head = ctf_worldflaglist;
1123 if (self.team != head.team)
1125 head = head.ctf_worldflagnext;
1128 navigation_routerating(head, ratingscale, 10000);
1131 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1133 if not(bot_waypoints_for_items)
1135 havocbot_goalrating_ctf_enemyflag(ratingscale);
1141 head = havocbot_ctf_find_enemy_flag(self);
1146 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1149 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1153 mf = havocbot_ctf_find_flag(self);
1155 if(mf.ctf_status == FLAG_BASE)
1159 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1162 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1165 head = ctf_worldflaglist;
1168 // flag is out in the field
1169 if(head.ctf_status != FLAG_BASE)
1170 if(head.tag_entity==world) // dropped
1174 if(vlen(org-head.origin)<df_radius)
1175 navigation_routerating(head, ratingscale, 10000);
1178 navigation_routerating(head, ratingscale, 10000);
1181 head = head.ctf_worldflagnext;
1185 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1189 head = findchainfloat(bot_pickup, TRUE);
1192 // gather health and armor only
1194 if (head.health || head.armorvalue)
1195 if (vlen(head.origin - org) < sradius)
1197 // get the value of the item
1198 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1200 navigation_routerating(head, t * ratingscale, 500);
1206 void havocbot_ctf_reset_role(entity bot)
1208 float cdefense, cmiddle, coffense;
1209 entity mf, ef, head;
1212 if(bot.deadflag != DEAD_NO)
1215 if(vlen(havocbot_ctf_middlepoint)==0)
1216 havocbot_calculate_middlepoint();
1219 if (bot.flagcarried)
1221 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1225 mf = havocbot_ctf_find_flag(bot);
1226 ef = havocbot_ctf_find_enemy_flag(bot);
1228 // Retrieve stolen flag
1229 if(mf.ctf_status!=FLAG_BASE)
1231 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1235 // If enemy flag is taken go to the middle to intercept pursuers
1236 if(ef.ctf_status!=FLAG_BASE)
1238 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1242 // if there is only me on the team switch to offense
1244 FOR_EACH_PLAYER(head)
1245 if(head.team==bot.team)
1250 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1254 // Evaluate best position to take
1255 // Count mates on middle position
1256 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1258 // Count mates on defense position
1259 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1261 // Count mates on offense position
1262 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1264 if(cdefense<=coffense)
1265 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1266 else if(coffense<=cmiddle)
1267 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1269 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1272 void havocbot_role_ctf_carrier()
1274 if(self.deadflag != DEAD_NO)
1276 havocbot_ctf_reset_role(self);
1280 if (self.flagcarried == world)
1282 havocbot_ctf_reset_role(self);
1286 if (self.bot_strategytime < time)
1288 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1290 navigation_goalrating_start();
1291 havocbot_goalrating_ctf_ourbase(50000);
1294 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1296 navigation_goalrating_end();
1298 if (self.navigation_hasgoals)
1299 self.havocbot_cantfindflag = time + 10;
1300 else if (time > self.havocbot_cantfindflag)
1302 // Can't navigate to my own base, suicide!
1303 // TODO: drop it and wander around
1304 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1310 void havocbot_role_ctf_escort()
1314 if(self.deadflag != DEAD_NO)
1316 havocbot_ctf_reset_role(self);
1320 if (self.flagcarried)
1322 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1326 // If enemy flag is back on the base switch to previous role
1327 ef = havocbot_ctf_find_enemy_flag(self);
1328 if(ef.ctf_status==FLAG_BASE)
1330 self.havocbot_role = self.havocbot_previous_role;
1331 self.havocbot_role_timeout = 0;
1335 // If the flag carrier reached the base switch to defense
1336 mf = havocbot_ctf_find_flag(self);
1337 if(mf.ctf_status!=FLAG_BASE)
1338 if(vlen(ef.origin - mf.dropped_origin) < 300)
1340 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1344 // Set the role timeout if necessary
1345 if (!self.havocbot_role_timeout)
1347 self.havocbot_role_timeout = time + random() * 30 + 60;
1350 // If nothing happened just switch to previous role
1351 if (time > self.havocbot_role_timeout)
1353 self.havocbot_role = self.havocbot_previous_role;
1354 self.havocbot_role_timeout = 0;
1358 // Chase the flag carrier
1359 if (self.bot_strategytime < time)
1361 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1362 navigation_goalrating_start();
1363 havocbot_goalrating_ctf_enemyflag(30000);
1364 havocbot_goalrating_ctf_ourstolenflag(40000);
1365 havocbot_goalrating_items(10000, self.origin, 10000);
1366 navigation_goalrating_end();
1370 void havocbot_role_ctf_offense()
1375 if(self.deadflag != DEAD_NO)
1377 havocbot_ctf_reset_role(self);
1381 if (self.flagcarried)
1383 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1388 mf = havocbot_ctf_find_flag(self);
1389 ef = havocbot_ctf_find_enemy_flag(self);
1392 if(mf.ctf_status!=FLAG_BASE)
1395 pos = mf.tag_entity.origin;
1399 // Try to get it if closer than the enemy base
1400 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1402 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1407 // Escort flag carrier
1408 if(ef.ctf_status!=FLAG_BASE)
1411 pos = ef.tag_entity.origin;
1415 if(vlen(pos-mf.dropped_origin)>700)
1417 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1422 // About to fail, switch to middlefield
1425 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1429 // Set the role timeout if necessary
1430 if (!self.havocbot_role_timeout)
1431 self.havocbot_role_timeout = time + 120;
1433 if (time > self.havocbot_role_timeout)
1435 havocbot_ctf_reset_role(self);
1439 if (self.bot_strategytime < time)
1441 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1442 navigation_goalrating_start();
1443 havocbot_goalrating_ctf_ourstolenflag(50000);
1444 havocbot_goalrating_ctf_enemybase(20000);
1445 havocbot_goalrating_items(5000, self.origin, 1000);
1446 havocbot_goalrating_items(1000, self.origin, 10000);
1447 navigation_goalrating_end();
1451 // Retriever (temporary role):
1452 void havocbot_role_ctf_retriever()
1456 if(self.deadflag != DEAD_NO)
1458 havocbot_ctf_reset_role(self);
1462 if (self.flagcarried)
1464 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1468 // If flag is back on the base switch to previous role
1469 mf = havocbot_ctf_find_flag(self);
1470 if(mf.ctf_status==FLAG_BASE)
1472 havocbot_ctf_reset_role(self);
1476 if (!self.havocbot_role_timeout)
1477 self.havocbot_role_timeout = time + 20;
1479 if (time > self.havocbot_role_timeout)
1481 havocbot_ctf_reset_role(self);
1485 if (self.bot_strategytime < time)
1490 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1491 navigation_goalrating_start();
1492 havocbot_goalrating_ctf_ourstolenflag(50000);
1493 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1494 havocbot_goalrating_ctf_enemybase(30000);
1495 havocbot_goalrating_items(500, self.origin, rt_radius);
1496 navigation_goalrating_end();
1500 void havocbot_role_ctf_middle()
1504 if(self.deadflag != DEAD_NO)
1506 havocbot_ctf_reset_role(self);
1510 if (self.flagcarried)
1512 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1516 mf = havocbot_ctf_find_flag(self);
1517 if(mf.ctf_status!=FLAG_BASE)
1519 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1523 if (!self.havocbot_role_timeout)
1524 self.havocbot_role_timeout = time + 10;
1526 if (time > self.havocbot_role_timeout)
1528 havocbot_ctf_reset_role(self);
1532 if (self.bot_strategytime < time)
1536 org = havocbot_ctf_middlepoint;
1537 org_z = self.origin_z;
1539 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1540 navigation_goalrating_start();
1541 havocbot_goalrating_ctf_ourstolenflag(50000);
1542 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1543 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1544 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1545 havocbot_goalrating_items(2500, self.origin, 10000);
1546 havocbot_goalrating_ctf_enemybase(2500);
1547 navigation_goalrating_end();
1551 void havocbot_role_ctf_defense()
1555 if(self.deadflag != DEAD_NO)
1557 havocbot_ctf_reset_role(self);
1561 if (self.flagcarried)
1563 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1567 // If own flag was captured
1568 mf = havocbot_ctf_find_flag(self);
1569 if(mf.ctf_status!=FLAG_BASE)
1571 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1575 if (!self.havocbot_role_timeout)
1576 self.havocbot_role_timeout = time + 30;
1578 if (time > self.havocbot_role_timeout)
1580 havocbot_ctf_reset_role(self);
1583 if (self.bot_strategytime < time)
1588 org = mf.dropped_origin;
1589 mp_radius = havocbot_ctf_middlepoint_radius;
1591 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1592 navigation_goalrating_start();
1594 // if enemies are closer to our base, go there
1595 entity head, closestplayer = world;
1596 float distance, bestdistance = 10000;
1597 FOR_EACH_PLAYER(head)
1599 if(head.deadflag!=DEAD_NO)
1602 distance = vlen(org - head.origin);
1603 if(distance<bestdistance)
1605 closestplayer = head;
1606 bestdistance = distance;
1611 if(closestplayer.team!=self.team)
1612 if(vlen(org - self.origin)>1000)
1613 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1614 havocbot_goalrating_ctf_ourbase(30000);
1616 havocbot_goalrating_ctf_ourstolenflag(20000);
1617 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1618 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1619 havocbot_goalrating_items(10000, org, mp_radius);
1620 havocbot_goalrating_items(5000, self.origin, 10000);
1621 navigation_goalrating_end();
1625 void havocbot_role_ctf_setrole(entity bot, float role)
1627 dprint(strcat(bot.netname," switched to "));
1630 case HAVOCBOT_CTF_ROLE_CARRIER:
1632 bot.havocbot_role = havocbot_role_ctf_carrier;
1633 bot.havocbot_role_timeout = 0;
1634 bot.havocbot_cantfindflag = time + 10;
1635 bot.bot_strategytime = 0;
1637 case HAVOCBOT_CTF_ROLE_DEFENSE:
1639 bot.havocbot_role = havocbot_role_ctf_defense;
1640 bot.havocbot_role_timeout = 0;
1642 case HAVOCBOT_CTF_ROLE_MIDDLE:
1644 bot.havocbot_role = havocbot_role_ctf_middle;
1645 bot.havocbot_role_timeout = 0;
1647 case HAVOCBOT_CTF_ROLE_OFFENSE:
1649 bot.havocbot_role = havocbot_role_ctf_offense;
1650 bot.havocbot_role_timeout = 0;
1652 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1653 dprint("retriever");
1654 bot.havocbot_previous_role = bot.havocbot_role;
1655 bot.havocbot_role = havocbot_role_ctf_retriever;
1656 bot.havocbot_role_timeout = time + 10;
1657 bot.bot_strategytime = 0;
1659 case HAVOCBOT_CTF_ROLE_ESCORT:
1661 bot.havocbot_previous_role = bot.havocbot_role;
1662 bot.havocbot_role = havocbot_role_ctf_escort;
1663 bot.havocbot_role_timeout = time + 30;
1664 bot.bot_strategytime = 0;
1675 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1679 // initially clear items so they can be set as necessary later.
1680 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1681 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1683 // scan through all the flags and notify the client about them
1684 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1686 switch(flag.ctf_status)
1691 if((flag.owner == self) || (flag.pass_sender == self))
1692 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1694 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1699 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1705 // item for stopping players from capturing the flag too often
1706 if(self.ctf_captureshielded)
1707 self.items |= IT_CTF_SHIELDED;
1709 // update the health of the flag carrier waypointsprite
1710 if(self.wps_flagcarrier)
1711 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1716 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1718 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1720 if(frag_target == frag_attacker) // damage done to yourself
1722 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1723 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1725 else // damage done to everyone else
1727 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1728 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1731 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1733 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)))
1734 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1736 frag_target.wps_helpme_time = time;
1737 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1739 // todo: add notification for when flag carrier needs help?
1744 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1746 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1748 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1749 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1752 if(frag_target.flagcarried)
1753 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1758 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1761 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1764 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1766 entity flag; // temporary entity for the search method
1768 if(self.flagcarried)
1769 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1771 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1773 if(flag.pass_sender == self) { flag.pass_sender = world; }
1774 if(flag.pass_target == self) { flag.pass_target = world; }
1775 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1781 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1783 if(self.flagcarried)
1784 if(!autocvar_g_ctf_portalteleport)
1785 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1790 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1792 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1794 entity player = self;
1796 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1798 // pass the flag to a team mate
1799 if(autocvar_g_ctf_pass)
1801 entity head, closest_target = world;
1802 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1804 while(head) // find the closest acceptable target to pass to
1806 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1807 if(head != player && SAME_TEAM(head, player))
1808 if(!head.speedrunning && !head.vehicle)
1810 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1811 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1812 vector passer_center = CENTER_OR_VIEWOFS(player);
1814 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1816 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1818 if(IS_BOT_CLIENT(head))
1820 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1821 ctf_Handle_Throw(head, player, DROP_PASS);
1825 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1826 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1828 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1831 else if(player.flagcarried)
1835 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1836 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1837 { closest_target = head; }
1839 else { closest_target = head; }
1846 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1849 // throw the flag in front of you
1850 if(autocvar_g_ctf_throw && player.flagcarried)
1852 if(player.throw_count == -1)
1854 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1856 player.throw_prevtime = time;
1857 player.throw_count = 1;
1858 ctf_Handle_Throw(player, world, DROP_THROW);
1863 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1869 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1870 else { player.throw_count += 1; }
1871 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1873 player.throw_prevtime = time;
1874 ctf_Handle_Throw(player, world, DROP_THROW);
1883 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1885 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1887 self.wps_helpme_time = time;
1888 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1890 else // create a normal help me waypointsprite
1892 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');
1893 WaypointSprite_Ping(self.wps_helpme);
1899 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1901 if(vh_player.flagcarried)
1903 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1905 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1909 setattachment(vh_player.flagcarried, vh_vehicle, "");
1910 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1911 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1912 //vh_player.flagcarried.angles = '0 0 0';
1920 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1922 if(vh_player.flagcarried)
1924 setattachment(vh_player.flagcarried, vh_player, "");
1925 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1926 vh_player.flagcarried.scale = FLAG_SCALE;
1927 vh_player.flagcarried.angles = '0 0 0';
1934 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1936 if(self.flagcarried)
1938 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1939 ctf_RespawnFlag(self.flagcarried);
1946 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1948 entity flag; // temporary entity for the search method
1950 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1952 switch(flag.ctf_status)
1957 // lock the flag, game is over
1958 flag.movetype = MOVETYPE_NONE;
1959 flag.takedamage = DAMAGE_NO;
1960 flag.solid = SOLID_NOT;
1961 flag.nextthink = FALSE; // stop thinking
1963 //dprint("stopping the ", flag.netname, " from moving.\n");
1971 // do nothing for these flags
1980 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1982 havocbot_ctf_reset_role(self);
1991 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1992 CTF Starting point for a player in team one (Red).
1993 Keys: "angle" viewing angle when spawning. */
1994 void spawnfunc_info_player_team1()
1996 if(g_assault) { remove(self); return; }
1998 self.team = NUM_TEAM_1; // red
1999 spawnfunc_info_player_deathmatch();
2003 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2004 CTF Starting point for a player in team two (Blue).
2005 Keys: "angle" viewing angle when spawning. */
2006 void spawnfunc_info_player_team2()
2008 if(g_assault) { remove(self); return; }
2010 self.team = NUM_TEAM_2; // blue
2011 spawnfunc_info_player_deathmatch();
2014 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2015 CTF Starting point for a player in team three (Yellow).
2016 Keys: "angle" viewing angle when spawning. */
2017 void spawnfunc_info_player_team3()
2019 if(g_assault) { remove(self); return; }
2021 self.team = NUM_TEAM_3; // yellow
2022 spawnfunc_info_player_deathmatch();
2026 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2027 CTF Starting point for a player in team four (Purple).
2028 Keys: "angle" viewing angle when spawning. */
2029 void spawnfunc_info_player_team4()
2031 if(g_assault) { remove(self); return; }
2033 self.team = NUM_TEAM_4; // purple
2034 spawnfunc_info_player_deathmatch();
2037 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2038 CTF flag for team one (Red).
2040 "angle" Angle the flag will point (minus 90 degrees)...
2041 "model" model to use, note this needs red and blue as skins 0 and 1...
2042 "noise" sound played when flag is picked up...
2043 "noise1" sound played when flag is returned by a teammate...
2044 "noise2" sound played when flag is captured...
2045 "noise3" sound played when flag is lost in the field and respawns itself...
2046 "noise4" sound played when flag is dropped by a player...
2047 "noise5" sound played when flag touches the ground... */
2048 void spawnfunc_item_flag_team1()
2050 if(!g_ctf) { remove(self); return; }
2052 ctf_FlagSetup(1, self); // 1 = red
2055 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2056 CTF flag for team two (Blue).
2058 "angle" Angle the flag will point (minus 90 degrees)...
2059 "model" model to use, note this needs red and blue as skins 0 and 1...
2060 "noise" sound played when flag is picked up...
2061 "noise1" sound played when flag is returned by a teammate...
2062 "noise2" sound played when flag is captured...
2063 "noise3" sound played when flag is lost in the field and respawns itself...
2064 "noise4" sound played when flag is dropped by a player...
2065 "noise5" sound played when flag touches the ground... */
2066 void spawnfunc_item_flag_team2()
2068 if(!g_ctf) { remove(self); return; }
2070 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2073 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2074 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2075 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.
2077 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2078 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2079 void spawnfunc_ctf_team()
2081 if(!g_ctf) { remove(self); return; }
2083 self.classname = "ctf_team";
2084 self.team = self.cnt + 1;
2087 // compatibility for quake maps
2088 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2089 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2090 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2091 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2092 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2093 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2101 void ctf_ScoreRules()
2103 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2104 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2105 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2106 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2107 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2108 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2109 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2110 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2111 ScoreRules_basics_end();
2114 // code from here on is just to support maps that don't have flag and team entities
2115 void ctf_SpawnTeam (string teamname, float teamcolor)
2120 self.classname = "ctf_team";
2121 self.netname = teamname;
2122 self.cnt = teamcolor;
2124 spawnfunc_ctf_team();
2129 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2131 // if no teams are found, spawn defaults
2132 if(find(world, classname, "ctf_team") == world)
2134 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2135 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2136 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2142 void ctf_Initialize()
2144 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2146 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2147 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2148 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2150 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2154 MUTATOR_DEFINITION(gamemode_ctf)
2156 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2157 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2158 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2159 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2160 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2161 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2162 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2163 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2164 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2165 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2166 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2167 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2168 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2169 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2173 if(time > 1) // game loads at time 1
2174 error("This is a game type and it cannot be added at runtime.");
2178 MUTATOR_ONROLLBACK_OR_REMOVE
2180 // we actually cannot roll back ctf_Initialize here
2181 // BUT: we don't need to! If this gets called, adding always
2187 print("This is a game type and it cannot be removed at runtime.");