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 (!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 if(player.flags & FL_MONSTER)
436 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
440 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
441 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
443 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
444 ctf_EventLog("return", flag.team, player);
447 if(IS_PLAYER(player))
449 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
450 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
453 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
457 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
458 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
459 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
463 ctf_RespawnFlag(flag);
466 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
469 float pickup_dropped_score; // used to calculate dropped pickup score
471 // attach the flag to the player
473 player.flagcarried = flag;
474 setattachment(flag, player, "");
475 setorigin(flag, FLAG_CARRY_OFFSET);
478 flag.movetype = MOVETYPE_NONE;
479 flag.takedamage = DAMAGE_NO;
480 flag.solid = SOLID_NOT;
481 flag.angles = '0 0 0';
482 flag.ctf_status = FLAG_CARRY;
486 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
487 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
491 // messages and sounds
492 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
493 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
494 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
496 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
497 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
499 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
502 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
507 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
508 ctf_EventLog("steal", flag.team, player);
514 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);
515 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);
516 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
517 PlayerTeamScore_AddScore(player, pickup_dropped_score);
518 ctf_EventLog("pickup", flag.team, player);
526 if(pickuptype == PICKUP_BASE)
528 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
529 if((player.speedrunning) && (ctf_captimerecord))
530 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
534 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
537 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
538 ctf_FlagcarrierWaypoints(player);
539 WaypointSprite_Ping(player.wps_flagcarrier);
543 // ===================
544 // Main Flag Functions
545 // ===================
547 void ctf_CheckFlagReturn(entity flag, float returntype)
549 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
551 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
553 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
557 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
558 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
559 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
560 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
564 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
566 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
567 ctf_EventLog("returned", flag.team, world);
568 ctf_RespawnFlag(flag);
573 void ctf_CheckStalemate(void)
576 float stale_red_flags = 0, stale_blue_flags = 0;
579 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
581 // build list of stale flags
582 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
584 if(autocvar_g_ctf_stalemate)
585 if(tmp_entity.ctf_status != FLAG_BASE)
586 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
588 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
589 ctf_staleflaglist = tmp_entity;
591 switch(tmp_entity.team)
593 case NUM_TEAM_1: ++stale_red_flags; break;
594 case NUM_TEAM_2: ++stale_blue_flags; break;
599 if(stale_red_flags && stale_blue_flags)
600 ctf_stalemate = TRUE;
601 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
602 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
603 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
604 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
606 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
609 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
611 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
612 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));
615 if (!wpforenemy_announced)
617 FOR_EACH_REALPLAYER(tmp_entity)
618 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
620 wpforenemy_announced = TRUE;
625 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
627 if(ITEM_DAMAGE_NEEDKILL(deathtype))
629 // automatically kill the flag and return it
631 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
634 if(autocvar_g_ctf_flag_return_damage)
636 // reduce health and check if it should be returned
637 self.health = self.health - damage;
638 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
648 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
651 if(self == ctf_worldflaglist) // only for the first flag
652 FOR_EACH_CLIENT(tmp_entity)
653 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
656 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
657 dprint("wtf the flag got squashed?\n");
658 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
659 if(!trace_startsolid) // can we resize it without getting stuck?
660 setsize(self, FLAG_MIN, FLAG_MAX); }
662 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
666 self.angles = '0 0 0';
674 switch(self.ctf_status)
678 if(autocvar_g_ctf_dropped_capture_radius)
680 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
681 if(tmp_entity.ctf_status == FLAG_DROPPED)
682 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
683 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
684 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
691 if(autocvar_g_ctf_flag_dropped_floatinwater)
693 vector midpoint = ((self.absmin + self.absmax) * 0.5);
694 if(pointcontents(midpoint) == CONTENT_WATER)
696 self.velocity = self.velocity * 0.5;
698 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
699 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
701 { self.movetype = MOVETYPE_FLY; }
703 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
705 if(autocvar_g_ctf_flag_return_dropped)
707 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
710 ctf_CheckFlagReturn(self, RETURN_DROPPED);
714 if(autocvar_g_ctf_flag_return_time)
716 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
717 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
725 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
728 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
732 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
736 if(autocvar_g_ctf_stalemate)
738 if(time >= wpforenemy_nextthink)
740 ctf_CheckStalemate();
741 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
749 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
750 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
751 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
753 if((self.pass_target == world)
754 || (self.pass_target.deadflag != DEAD_NO)
755 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
756 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
757 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
759 // give up, pass failed
760 ctf_Handle_Drop(self, world, DROP_PASS);
764 // still a viable target, go for it
765 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
770 default: // this should never happen
772 dprint("ctf_FlagThink(): Flag exists with no status?\n");
780 if(gameover) { return; }
782 entity toucher = other;
783 float is_not_monster = (!(toucher.flags & FL_MONSTER));
785 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
786 if(ITEM_TOUCH_NEEDKILL())
789 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
793 // special touch behaviors
794 if(toucher.vehicle_flags & VHF_ISVEHICLE)
796 if(autocvar_g_ctf_allow_vehicle_touch)
797 toucher = toucher.owner; // the player is actually the vehicle owner, not other
799 return; // do nothing
801 else if(toucher.flags & FL_MONSTER)
803 if(!autocvar_g_ctf_allow_monster_touch)
804 return; // do nothing
806 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
808 if(time > self.wait) // if we haven't in a while, play a sound/effect
810 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
811 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
812 self.wait = time + FLAG_TOUCHRATE;
816 else if(toucher.deadflag != DEAD_NO) { return; }
818 switch(self.ctf_status)
822 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
823 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
824 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
825 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
831 if(SAME_TEAM(toucher, self))
832 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
833 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
834 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
840 dprint("Someone touched a flag even though it was being carried?\n");
846 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
848 if(DIFF_TEAM(toucher, self.pass_sender))
849 ctf_Handle_Return(self, toucher);
851 ctf_Handle_Retrieve(self, toucher);
859 void ctf_RespawnFlag(entity flag)
861 // check for flag respawn being called twice in a row
862 if(flag.last_respawn > time - 0.5)
863 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
865 flag.last_respawn = time;
867 // reset the player (if there is one)
868 if((flag.owner) && (flag.owner.flagcarried == flag))
870 if(flag.owner.wps_enemyflagcarrier)
871 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
873 WaypointSprite_Kill(flag.wps_flagcarrier);
875 flag.owner.flagcarried = world;
877 if(flag.speedrunning)
878 ctf_FakeTimeLimit(flag.owner, -1);
881 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
882 { WaypointSprite_Kill(flag.wps_flagdropped); }
885 setattachment(flag, world, "");
886 setorigin(flag, flag.ctf_spawnorigin);
888 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
889 flag.takedamage = DAMAGE_NO;
890 flag.health = flag.max_flag_health;
891 flag.solid = SOLID_TRIGGER;
892 flag.velocity = '0 0 0';
893 flag.angles = flag.mangle;
894 flag.flags = FL_ITEM | FL_NOTARGET;
896 flag.ctf_status = FLAG_BASE;
898 flag.pass_distance = 0;
899 flag.pass_sender = world;
900 flag.pass_target = world;
901 flag.ctf_dropper = world;
902 flag.ctf_pickuptime = 0;
903 flag.ctf_droptime = 0;
909 if(IS_PLAYER(self.owner))
910 ctf_Handle_Throw(self.owner, world, DROP_RESET);
912 ctf_RespawnFlag(self);
915 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
918 waypoint_spawnforitem_force(self, self.origin);
919 self.nearestwaypointtimeout = 0; // activate waypointing again
920 self.bot_basewaypoint = self.nearestwaypoint;
923 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
924 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
926 // captureshield setup
927 ctf_CaptureShield_Spawn(self);
930 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
933 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.
934 self = flag; // for later usage with droptofloor()
937 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
938 ctf_worldflaglist = flag;
940 setattachment(flag, world, "");
942 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
943 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
944 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
945 flag.classname = "item_flag_team";
946 flag.target = "###item###"; // wut?
947 flag.flags = FL_ITEM | FL_NOTARGET;
948 flag.solid = SOLID_TRIGGER;
949 flag.takedamage = DAMAGE_NO;
950 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
951 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
952 flag.health = flag.max_flag_health;
953 flag.event_damage = ctf_FlagDamage;
954 flag.pushable = TRUE;
955 flag.teleportable = TELEPORT_NORMAL;
956 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
957 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
958 flag.velocity = '0 0 0';
959 flag.mangle = flag.angles;
960 flag.reset = ctf_Reset;
961 flag.touch = ctf_FlagTouch;
962 flag.think = ctf_FlagThink;
963 flag.nextthink = time + FLAG_THINKRATE;
964 flag.ctf_status = FLAG_BASE;
967 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
968 if(!flag.scale) { flag.scale = FLAG_SCALE; }
969 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
970 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
971 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
972 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
975 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
976 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
977 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
978 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.
979 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
980 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
981 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
984 precache_sound(flag.snd_flag_taken);
985 precache_sound(flag.snd_flag_returned);
986 precache_sound(flag.snd_flag_capture);
987 precache_sound(flag.snd_flag_respawn);
988 precache_sound(flag.snd_flag_dropped);
989 precache_sound(flag.snd_flag_touch);
990 precache_sound(flag.snd_flag_pass);
991 precache_model(flag.model);
992 precache_model("models/ctf/shield.md3");
993 precache_model("models/ctf/shockwavetransring.md3");
996 setmodel(flag, flag.model); // precision set below
997 setsize(flag, FLAG_MIN, FLAG_MAX);
998 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1000 if(autocvar_g_ctf_flag_glowtrails)
1002 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1003 flag.glow_size = 25;
1004 flag.glow_trail = 1;
1007 flag.effects |= EF_LOWPRECISION;
1008 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1009 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1012 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1014 flag.dropped_origin = flag.origin;
1015 flag.noalign = TRUE;
1016 flag.movetype = MOVETYPE_NONE;
1018 else // drop to floor, automatically find a platform and set that as spawn origin
1020 flag.noalign = FALSE;
1023 flag.movetype = MOVETYPE_TOSS;
1026 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1034 // NOTE: LEGACY CODE, needs to be re-written!
1036 void havocbot_calculate_middlepoint()
1040 vector fo = '0 0 0';
1043 f = ctf_worldflaglist;
1048 f = f.ctf_worldflagnext;
1052 havocbot_ctf_middlepoint = s * (1.0 / n);
1053 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1057 entity havocbot_ctf_find_flag(entity bot)
1060 f = ctf_worldflaglist;
1063 if (bot.team == f.team)
1065 f = f.ctf_worldflagnext;
1070 entity havocbot_ctf_find_enemy_flag(entity bot)
1073 f = ctf_worldflaglist;
1076 if (bot.team != f.team)
1078 f = f.ctf_worldflagnext;
1083 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1091 FOR_EACH_PLAYER(head)
1093 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1096 if(vlen(head.origin - org) < tc_radius)
1103 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1106 head = ctf_worldflaglist;
1109 if (self.team == head.team)
1111 head = head.ctf_worldflagnext;
1114 navigation_routerating(head, ratingscale, 10000);
1117 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1120 head = ctf_worldflaglist;
1123 if (self.team == head.team)
1125 head = head.ctf_worldflagnext;
1130 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1133 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1136 head = ctf_worldflaglist;
1139 if (self.team != head.team)
1141 head = head.ctf_worldflagnext;
1144 navigation_routerating(head, ratingscale, 10000);
1147 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1149 if (!bot_waypoints_for_items)
1151 havocbot_goalrating_ctf_enemyflag(ratingscale);
1157 head = havocbot_ctf_find_enemy_flag(self);
1162 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1165 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1169 mf = havocbot_ctf_find_flag(self);
1171 if(mf.ctf_status == FLAG_BASE)
1175 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1178 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1181 head = ctf_worldflaglist;
1184 // flag is out in the field
1185 if(head.ctf_status != FLAG_BASE)
1186 if(head.tag_entity==world) // dropped
1190 if(vlen(org-head.origin)<df_radius)
1191 navigation_routerating(head, ratingscale, 10000);
1194 navigation_routerating(head, ratingscale, 10000);
1197 head = head.ctf_worldflagnext;
1201 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1205 head = findchainfloat(bot_pickup, TRUE);
1208 // gather health and armor only
1210 if (head.health || head.armorvalue)
1211 if (vlen(head.origin - org) < sradius)
1213 // get the value of the item
1214 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1216 navigation_routerating(head, t * ratingscale, 500);
1222 void havocbot_ctf_reset_role(entity bot)
1224 float cdefense, cmiddle, coffense;
1225 entity mf, ef, head;
1228 if(bot.deadflag != DEAD_NO)
1231 if(vlen(havocbot_ctf_middlepoint)==0)
1232 havocbot_calculate_middlepoint();
1235 if (bot.flagcarried)
1237 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1241 mf = havocbot_ctf_find_flag(bot);
1242 ef = havocbot_ctf_find_enemy_flag(bot);
1244 // Retrieve stolen flag
1245 if(mf.ctf_status!=FLAG_BASE)
1247 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1251 // If enemy flag is taken go to the middle to intercept pursuers
1252 if(ef.ctf_status!=FLAG_BASE)
1254 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1258 // if there is only me on the team switch to offense
1260 FOR_EACH_PLAYER(head)
1261 if(head.team==bot.team)
1266 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1270 // Evaluate best position to take
1271 // Count mates on middle position
1272 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1274 // Count mates on defense position
1275 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1277 // Count mates on offense position
1278 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1280 if(cdefense<=coffense)
1281 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1282 else if(coffense<=cmiddle)
1283 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1285 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1288 void havocbot_role_ctf_carrier()
1290 if(self.deadflag != DEAD_NO)
1292 havocbot_ctf_reset_role(self);
1296 if (self.flagcarried == world)
1298 havocbot_ctf_reset_role(self);
1302 if (self.bot_strategytime < time)
1304 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1306 navigation_goalrating_start();
1307 havocbot_goalrating_ctf_ourbase(50000);
1310 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1312 navigation_goalrating_end();
1314 if (self.navigation_hasgoals)
1315 self.havocbot_cantfindflag = time + 10;
1316 else if (time > self.havocbot_cantfindflag)
1318 // Can't navigate to my own base, suicide!
1319 // TODO: drop it and wander around
1320 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1326 void havocbot_role_ctf_escort()
1330 if(self.deadflag != DEAD_NO)
1332 havocbot_ctf_reset_role(self);
1336 if (self.flagcarried)
1338 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1342 // If enemy flag is back on the base switch to previous role
1343 ef = havocbot_ctf_find_enemy_flag(self);
1344 if(ef.ctf_status==FLAG_BASE)
1346 self.havocbot_role = self.havocbot_previous_role;
1347 self.havocbot_role_timeout = 0;
1351 // If the flag carrier reached the base switch to defense
1352 mf = havocbot_ctf_find_flag(self);
1353 if(mf.ctf_status!=FLAG_BASE)
1354 if(vlen(ef.origin - mf.dropped_origin) < 300)
1356 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1360 // Set the role timeout if necessary
1361 if (!self.havocbot_role_timeout)
1363 self.havocbot_role_timeout = time + random() * 30 + 60;
1366 // If nothing happened just switch to previous role
1367 if (time > self.havocbot_role_timeout)
1369 self.havocbot_role = self.havocbot_previous_role;
1370 self.havocbot_role_timeout = 0;
1374 // Chase the flag carrier
1375 if (self.bot_strategytime < time)
1377 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1378 navigation_goalrating_start();
1379 havocbot_goalrating_ctf_enemyflag(30000);
1380 havocbot_goalrating_ctf_ourstolenflag(40000);
1381 havocbot_goalrating_items(10000, self.origin, 10000);
1382 navigation_goalrating_end();
1386 void havocbot_role_ctf_offense()
1391 if(self.deadflag != DEAD_NO)
1393 havocbot_ctf_reset_role(self);
1397 if (self.flagcarried)
1399 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1404 mf = havocbot_ctf_find_flag(self);
1405 ef = havocbot_ctf_find_enemy_flag(self);
1408 if(mf.ctf_status!=FLAG_BASE)
1411 pos = mf.tag_entity.origin;
1415 // Try to get it if closer than the enemy base
1416 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1418 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1423 // Escort flag carrier
1424 if(ef.ctf_status!=FLAG_BASE)
1427 pos = ef.tag_entity.origin;
1431 if(vlen(pos-mf.dropped_origin)>700)
1433 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1438 // About to fail, switch to middlefield
1441 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1445 // Set the role timeout if necessary
1446 if (!self.havocbot_role_timeout)
1447 self.havocbot_role_timeout = time + 120;
1449 if (time > self.havocbot_role_timeout)
1451 havocbot_ctf_reset_role(self);
1455 if (self.bot_strategytime < time)
1457 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1458 navigation_goalrating_start();
1459 havocbot_goalrating_ctf_ourstolenflag(50000);
1460 havocbot_goalrating_ctf_enemybase(20000);
1461 havocbot_goalrating_items(5000, self.origin, 1000);
1462 havocbot_goalrating_items(1000, self.origin, 10000);
1463 navigation_goalrating_end();
1467 // Retriever (temporary role):
1468 void havocbot_role_ctf_retriever()
1472 if(self.deadflag != DEAD_NO)
1474 havocbot_ctf_reset_role(self);
1478 if (self.flagcarried)
1480 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1484 // If flag is back on the base switch to previous role
1485 mf = havocbot_ctf_find_flag(self);
1486 if(mf.ctf_status==FLAG_BASE)
1488 havocbot_ctf_reset_role(self);
1492 if (!self.havocbot_role_timeout)
1493 self.havocbot_role_timeout = time + 20;
1495 if (time > self.havocbot_role_timeout)
1497 havocbot_ctf_reset_role(self);
1501 if (self.bot_strategytime < time)
1506 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1507 navigation_goalrating_start();
1508 havocbot_goalrating_ctf_ourstolenflag(50000);
1509 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1510 havocbot_goalrating_ctf_enemybase(30000);
1511 havocbot_goalrating_items(500, self.origin, rt_radius);
1512 navigation_goalrating_end();
1516 void havocbot_role_ctf_middle()
1520 if(self.deadflag != DEAD_NO)
1522 havocbot_ctf_reset_role(self);
1526 if (self.flagcarried)
1528 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1532 mf = havocbot_ctf_find_flag(self);
1533 if(mf.ctf_status!=FLAG_BASE)
1535 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1539 if (!self.havocbot_role_timeout)
1540 self.havocbot_role_timeout = time + 10;
1542 if (time > self.havocbot_role_timeout)
1544 havocbot_ctf_reset_role(self);
1548 if (self.bot_strategytime < time)
1552 org = havocbot_ctf_middlepoint;
1553 org_z = self.origin_z;
1555 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1556 navigation_goalrating_start();
1557 havocbot_goalrating_ctf_ourstolenflag(50000);
1558 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1559 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1560 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1561 havocbot_goalrating_items(2500, self.origin, 10000);
1562 havocbot_goalrating_ctf_enemybase(2500);
1563 navigation_goalrating_end();
1567 void havocbot_role_ctf_defense()
1571 if(self.deadflag != DEAD_NO)
1573 havocbot_ctf_reset_role(self);
1577 if (self.flagcarried)
1579 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1583 // If own flag was captured
1584 mf = havocbot_ctf_find_flag(self);
1585 if(mf.ctf_status!=FLAG_BASE)
1587 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1591 if (!self.havocbot_role_timeout)
1592 self.havocbot_role_timeout = time + 30;
1594 if (time > self.havocbot_role_timeout)
1596 havocbot_ctf_reset_role(self);
1599 if (self.bot_strategytime < time)
1604 org = mf.dropped_origin;
1605 mp_radius = havocbot_ctf_middlepoint_radius;
1607 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1608 navigation_goalrating_start();
1610 // if enemies are closer to our base, go there
1611 entity head, closestplayer = world;
1612 float distance, bestdistance = 10000;
1613 FOR_EACH_PLAYER(head)
1615 if(head.deadflag!=DEAD_NO)
1618 distance = vlen(org - head.origin);
1619 if(distance<bestdistance)
1621 closestplayer = head;
1622 bestdistance = distance;
1627 if(closestplayer.team!=self.team)
1628 if(vlen(org - self.origin)>1000)
1629 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1630 havocbot_goalrating_ctf_ourbase(30000);
1632 havocbot_goalrating_ctf_ourstolenflag(20000);
1633 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1634 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1635 havocbot_goalrating_items(10000, org, mp_radius);
1636 havocbot_goalrating_items(5000, self.origin, 10000);
1637 navigation_goalrating_end();
1641 void havocbot_role_ctf_setrole(entity bot, float role)
1643 dprint(strcat(bot.netname," switched to "));
1646 case HAVOCBOT_CTF_ROLE_CARRIER:
1648 bot.havocbot_role = havocbot_role_ctf_carrier;
1649 bot.havocbot_role_timeout = 0;
1650 bot.havocbot_cantfindflag = time + 10;
1651 bot.bot_strategytime = 0;
1653 case HAVOCBOT_CTF_ROLE_DEFENSE:
1655 bot.havocbot_role = havocbot_role_ctf_defense;
1656 bot.havocbot_role_timeout = 0;
1658 case HAVOCBOT_CTF_ROLE_MIDDLE:
1660 bot.havocbot_role = havocbot_role_ctf_middle;
1661 bot.havocbot_role_timeout = 0;
1663 case HAVOCBOT_CTF_ROLE_OFFENSE:
1665 bot.havocbot_role = havocbot_role_ctf_offense;
1666 bot.havocbot_role_timeout = 0;
1668 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1669 dprint("retriever");
1670 bot.havocbot_previous_role = bot.havocbot_role;
1671 bot.havocbot_role = havocbot_role_ctf_retriever;
1672 bot.havocbot_role_timeout = time + 10;
1673 bot.bot_strategytime = 0;
1675 case HAVOCBOT_CTF_ROLE_ESCORT:
1677 bot.havocbot_previous_role = bot.havocbot_role;
1678 bot.havocbot_role = havocbot_role_ctf_escort;
1679 bot.havocbot_role_timeout = time + 30;
1680 bot.bot_strategytime = 0;
1691 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1695 // initially clear items so they can be set as necessary later.
1696 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1697 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1699 // scan through all the flags and notify the client about them
1700 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1702 switch(flag.ctf_status)
1707 if((flag.owner == self) || (flag.pass_sender == self))
1708 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1710 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1715 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1721 // item for stopping players from capturing the flag too often
1722 if(self.ctf_captureshielded)
1723 self.items |= IT_CTF_SHIELDED;
1725 // update the health of the flag carrier waypointsprite
1726 if(self.wps_flagcarrier)
1727 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1732 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1734 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1736 if(frag_target == frag_attacker) // damage done to yourself
1738 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1739 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1741 else // damage done to everyone else
1743 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1744 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1747 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1749 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)))
1750 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1752 frag_target.wps_helpme_time = time;
1753 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1755 // todo: add notification for when flag carrier needs help?
1760 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1762 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1764 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1765 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1768 if(frag_target.flagcarried)
1769 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1774 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1777 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1780 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1782 entity flag; // temporary entity for the search method
1784 if(self.flagcarried)
1785 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1787 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1789 if(flag.pass_sender == self) { flag.pass_sender = world; }
1790 if(flag.pass_target == self) { flag.pass_target = world; }
1791 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1797 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1799 if(self.flagcarried)
1800 if(!autocvar_g_ctf_portalteleport)
1801 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1806 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1808 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1810 entity player = self;
1812 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1814 // pass the flag to a team mate
1815 if(autocvar_g_ctf_pass)
1817 entity head, closest_target = world;
1818 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1820 while(head) // find the closest acceptable target to pass to
1822 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1823 if(head != player && SAME_TEAM(head, player))
1824 if(!head.speedrunning && !head.vehicle)
1826 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1827 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1828 vector passer_center = CENTER_OR_VIEWOFS(player);
1830 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1832 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1834 if(IS_BOT_CLIENT(head))
1836 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1837 ctf_Handle_Throw(head, player, DROP_PASS);
1841 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1842 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1844 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1847 else if(player.flagcarried)
1851 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1852 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1853 { closest_target = head; }
1855 else { closest_target = head; }
1862 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1865 // throw the flag in front of you
1866 if(autocvar_g_ctf_throw && player.flagcarried)
1868 if(player.throw_count == -1)
1870 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1872 player.throw_prevtime = time;
1873 player.throw_count = 1;
1874 ctf_Handle_Throw(player, world, DROP_THROW);
1879 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1885 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1886 else { player.throw_count += 1; }
1887 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1889 player.throw_prevtime = time;
1890 ctf_Handle_Throw(player, world, DROP_THROW);
1899 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1901 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1903 self.wps_helpme_time = time;
1904 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1906 else // create a normal help me waypointsprite
1908 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');
1909 WaypointSprite_Ping(self.wps_helpme);
1915 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1917 if(vh_player.flagcarried)
1919 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1921 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1925 setattachment(vh_player.flagcarried, vh_vehicle, "");
1926 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1927 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1928 //vh_player.flagcarried.angles = '0 0 0';
1936 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1938 if(vh_player.flagcarried)
1940 setattachment(vh_player.flagcarried, vh_player, "");
1941 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1942 vh_player.flagcarried.scale = FLAG_SCALE;
1943 vh_player.flagcarried.angles = '0 0 0';
1950 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1952 if(self.flagcarried)
1954 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1955 ctf_RespawnFlag(self.flagcarried);
1962 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1964 entity flag; // temporary entity for the search method
1966 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1968 switch(flag.ctf_status)
1973 // lock the flag, game is over
1974 flag.movetype = MOVETYPE_NONE;
1975 flag.takedamage = DAMAGE_NO;
1976 flag.solid = SOLID_NOT;
1977 flag.nextthink = FALSE; // stop thinking
1979 //dprint("stopping the ", flag.netname, " from moving.\n");
1987 // do nothing for these flags
1996 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1998 havocbot_ctf_reset_role(self);
2007 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2008 CTF Starting point for a player in team one (Red).
2009 Keys: "angle" viewing angle when spawning. */
2010 void spawnfunc_info_player_team1()
2012 if(g_assault) { remove(self); return; }
2014 self.team = NUM_TEAM_1; // red
2015 spawnfunc_info_player_deathmatch();
2019 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2020 CTF Starting point for a player in team two (Blue).
2021 Keys: "angle" viewing angle when spawning. */
2022 void spawnfunc_info_player_team2()
2024 if(g_assault) { remove(self); return; }
2026 self.team = NUM_TEAM_2; // blue
2027 spawnfunc_info_player_deathmatch();
2030 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2031 CTF Starting point for a player in team three (Yellow).
2032 Keys: "angle" viewing angle when spawning. */
2033 void spawnfunc_info_player_team3()
2035 if(g_assault) { remove(self); return; }
2037 self.team = NUM_TEAM_3; // yellow
2038 spawnfunc_info_player_deathmatch();
2042 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2043 CTF Starting point for a player in team four (Purple).
2044 Keys: "angle" viewing angle when spawning. */
2045 void spawnfunc_info_player_team4()
2047 if(g_assault) { remove(self); return; }
2049 self.team = NUM_TEAM_4; // purple
2050 spawnfunc_info_player_deathmatch();
2053 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2054 CTF flag for team one (Red).
2056 "angle" Angle the flag will point (minus 90 degrees)...
2057 "model" model to use, note this needs red and blue as skins 0 and 1...
2058 "noise" sound played when flag is picked up...
2059 "noise1" sound played when flag is returned by a teammate...
2060 "noise2" sound played when flag is captured...
2061 "noise3" sound played when flag is lost in the field and respawns itself...
2062 "noise4" sound played when flag is dropped by a player...
2063 "noise5" sound played when flag touches the ground... */
2064 void spawnfunc_item_flag_team1()
2066 if(!g_ctf) { remove(self); return; }
2068 ctf_FlagSetup(1, self); // 1 = red
2071 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2072 CTF flag for team two (Blue).
2074 "angle" Angle the flag will point (minus 90 degrees)...
2075 "model" model to use, note this needs red and blue as skins 0 and 1...
2076 "noise" sound played when flag is picked up...
2077 "noise1" sound played when flag is returned by a teammate...
2078 "noise2" sound played when flag is captured...
2079 "noise3" sound played when flag is lost in the field and respawns itself...
2080 "noise4" sound played when flag is dropped by a player...
2081 "noise5" sound played when flag touches the ground... */
2082 void spawnfunc_item_flag_team2()
2084 if(!g_ctf) { remove(self); return; }
2086 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2089 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2090 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2091 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.
2093 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2094 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2095 void spawnfunc_ctf_team()
2097 if(!g_ctf) { remove(self); return; }
2099 self.classname = "ctf_team";
2100 self.team = self.cnt + 1;
2103 // compatibility for quake maps
2104 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2105 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2106 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2107 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2108 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2109 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2117 void ctf_ScoreRules()
2119 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2120 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2121 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2122 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2123 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2124 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2125 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2126 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2127 ScoreRules_basics_end();
2130 // code from here on is just to support maps that don't have flag and team entities
2131 void ctf_SpawnTeam (string teamname, float teamcolor)
2136 self.classname = "ctf_team";
2137 self.netname = teamname;
2138 self.cnt = teamcolor;
2140 spawnfunc_ctf_team();
2145 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2147 // if no teams are found, spawn defaults
2148 if(find(world, classname, "ctf_team") == world)
2150 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2151 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2152 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2158 void ctf_Initialize()
2160 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2162 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2163 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2164 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2166 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2170 MUTATOR_DEFINITION(gamemode_ctf)
2172 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2173 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2174 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2175 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2176 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2177 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2178 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2179 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2180 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2181 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2182 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2183 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2184 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2185 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2189 if(time > 1) // game loads at time 1
2190 error("This is a game type and it cannot be added at runtime.");
2194 MUTATOR_ONROLLBACK_OR_REMOVE
2196 // we actually cannot roll back ctf_Initialize here
2197 // BUT: we don't need to! If this gets called, adding always
2203 print("This is a game type and it cannot be removed at runtime.");