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(IS_PLAYER(player))
435 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
437 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), (player.flags & FL_MONSTER) ? player.monster_name : player.netname);
438 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
439 ctf_EventLog("return", flag.team, player);
442 if(IS_PLAYER(player))
444 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
445 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
448 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
452 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
453 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
454 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
458 ctf_RespawnFlag(flag);
461 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
464 float pickup_dropped_score; // used to calculate dropped pickup score
466 // attach the flag to the player
468 player.flagcarried = flag;
469 setattachment(flag, player, "");
470 setorigin(flag, FLAG_CARRY_OFFSET);
473 flag.movetype = MOVETYPE_NONE;
474 flag.takedamage = DAMAGE_NO;
475 flag.solid = SOLID_NOT;
476 flag.angles = '0 0 0';
477 flag.ctf_status = FLAG_CARRY;
481 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
482 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
486 // messages and sounds
487 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
488 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
489 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
491 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
492 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
494 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
497 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
502 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
503 ctf_EventLog("steal", flag.team, player);
509 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);
510 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);
511 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
512 PlayerTeamScore_AddScore(player, pickup_dropped_score);
513 ctf_EventLog("pickup", flag.team, player);
521 if(pickuptype == PICKUP_BASE)
523 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
524 if((player.speedrunning) && (ctf_captimerecord))
525 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
529 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
532 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
533 ctf_FlagcarrierWaypoints(player);
534 WaypointSprite_Ping(player.wps_flagcarrier);
538 // ===================
539 // Main Flag Functions
540 // ===================
542 void ctf_CheckFlagReturn(entity flag, float returntype)
544 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
546 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
548 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
552 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
553 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
554 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
555 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
559 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
561 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
562 ctf_EventLog("returned", flag.team, world);
563 ctf_RespawnFlag(flag);
568 void ctf_CheckStalemate(void)
571 float stale_red_flags = 0, stale_blue_flags = 0;
574 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
576 // build list of stale flags
577 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
579 if(autocvar_g_ctf_stalemate)
580 if(tmp_entity.ctf_status != FLAG_BASE)
581 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
583 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
584 ctf_staleflaglist = tmp_entity;
586 switch(tmp_entity.team)
588 case NUM_TEAM_1: ++stale_red_flags; break;
589 case NUM_TEAM_2: ++stale_blue_flags; break;
594 if(stale_red_flags && stale_blue_flags)
595 ctf_stalemate = TRUE;
596 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
597 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
598 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
599 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
601 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
604 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
606 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
607 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));
610 if (!wpforenemy_announced)
612 FOR_EACH_REALPLAYER(tmp_entity)
613 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
615 wpforenemy_announced = TRUE;
620 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
622 if(ITEM_DAMAGE_NEEDKILL(deathtype))
624 // automatically kill the flag and return it
626 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
629 if(autocvar_g_ctf_flag_return_damage)
631 // reduce health and check if it should be returned
632 self.health = self.health - damage;
633 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
643 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
646 if(self == ctf_worldflaglist) // only for the first flag
647 FOR_EACH_CLIENT(tmp_entity)
648 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
651 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
652 dprint("wtf the flag got squashed?\n");
653 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
654 if(!trace_startsolid) // can we resize it without getting stuck?
655 setsize(self, FLAG_MIN, FLAG_MAX); }
657 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
661 self.angles = '0 0 0';
669 switch(self.ctf_status)
673 if(autocvar_g_ctf_dropped_capture_radius)
675 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
676 if(tmp_entity.ctf_status == FLAG_DROPPED)
677 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
678 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
679 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
686 if(autocvar_g_ctf_flag_dropped_floatinwater)
688 vector midpoint = ((self.absmin + self.absmax) * 0.5);
689 if(pointcontents(midpoint) == CONTENT_WATER)
691 self.velocity = self.velocity * 0.5;
693 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
694 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
696 { self.movetype = MOVETYPE_FLY; }
698 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
700 if(autocvar_g_ctf_flag_return_dropped)
702 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
705 ctf_CheckFlagReturn(self, RETURN_DROPPED);
709 if(autocvar_g_ctf_flag_return_time)
711 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
712 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
720 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
723 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
727 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
731 if(autocvar_g_ctf_stalemate)
733 if(time >= wpforenemy_nextthink)
735 ctf_CheckStalemate();
736 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
744 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
745 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
746 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
748 if((self.pass_target == world)
749 || (self.pass_target.deadflag != DEAD_NO)
750 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
751 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
752 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
754 // give up, pass failed
755 ctf_Handle_Drop(self, world, DROP_PASS);
759 // still a viable target, go for it
760 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
765 default: // this should never happen
767 dprint("ctf_FlagThink(): Flag exists with no status?\n");
775 if(gameover) { return; }
777 entity toucher = other;
779 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
780 if(ITEM_TOUCH_NEEDKILL())
783 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
787 // special touch behaviors
788 if(toucher.vehicle_flags & VHF_ISVEHICLE)
790 if(autocvar_g_ctf_allow_vehicle_touch)
791 toucher = toucher.owner; // the player is actually the vehicle owner, not other
793 return; // do nothing
795 else if(toucher.flags & FL_MONSTER)
797 if(!autocvar_g_ctf_allow_monster_touch)
798 return; // do nothing
800 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
802 if(time > self.wait) // if we haven't in a while, play a sound/effect
804 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
805 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
806 self.wait = time + FLAG_TOUCHRATE;
810 else if(toucher.deadflag != DEAD_NO) { return; }
812 switch(self.ctf_status)
816 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && !(toucher.flags & FL_MONSTER))
817 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
818 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && !(toucher.flags & FL_MONSTER))
819 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
825 if(SAME_TEAM(toucher, self))
826 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
827 else if(!(toucher.flags & FL_MONSTER) && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
828 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
834 dprint("Someone touched a flag even though it was being carried?\n");
840 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
842 if(DIFF_TEAM(toucher, self.pass_sender))
843 ctf_Handle_Return(self, toucher);
845 ctf_Handle_Retrieve(self, toucher);
853 void ctf_RespawnFlag(entity flag)
855 // check for flag respawn being called twice in a row
856 if(flag.last_respawn > time - 0.5)
857 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
859 flag.last_respawn = time;
861 // reset the player (if there is one)
862 if((flag.owner) && (flag.owner.flagcarried == flag))
864 if(flag.owner.wps_enemyflagcarrier)
865 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
867 WaypointSprite_Kill(flag.wps_flagcarrier);
869 flag.owner.flagcarried = world;
871 if(flag.speedrunning)
872 ctf_FakeTimeLimit(flag.owner, -1);
875 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
876 { WaypointSprite_Kill(flag.wps_flagdropped); }
879 setattachment(flag, world, "");
880 setorigin(flag, flag.ctf_spawnorigin);
882 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
883 flag.takedamage = DAMAGE_NO;
884 flag.health = flag.max_flag_health;
885 flag.solid = SOLID_TRIGGER;
886 flag.velocity = '0 0 0';
887 flag.angles = flag.mangle;
888 flag.flags = FL_ITEM | FL_NOTARGET;
890 flag.ctf_status = FLAG_BASE;
892 flag.pass_distance = 0;
893 flag.pass_sender = world;
894 flag.pass_target = world;
895 flag.ctf_dropper = world;
896 flag.ctf_pickuptime = 0;
897 flag.ctf_droptime = 0;
903 if(IS_PLAYER(self.owner))
904 ctf_Handle_Throw(self.owner, world, DROP_RESET);
906 ctf_RespawnFlag(self);
909 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
912 waypoint_spawnforitem_force(self, self.origin);
913 self.nearestwaypointtimeout = 0; // activate waypointing again
914 self.bot_basewaypoint = self.nearestwaypoint;
917 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
918 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
920 // captureshield setup
921 ctf_CaptureShield_Spawn(self);
924 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
927 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.
928 self = flag; // for later usage with droptofloor()
931 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
932 ctf_worldflaglist = flag;
934 setattachment(flag, world, "");
936 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
937 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
938 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
939 flag.classname = "item_flag_team";
940 flag.target = "###item###"; // wut?
941 flag.flags = FL_ITEM | FL_NOTARGET;
942 flag.solid = SOLID_TRIGGER;
943 flag.takedamage = DAMAGE_NO;
944 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
945 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
946 flag.health = flag.max_flag_health;
947 flag.event_damage = ctf_FlagDamage;
948 flag.pushable = TRUE;
949 flag.teleportable = TELEPORT_NORMAL;
950 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
951 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
952 flag.velocity = '0 0 0';
953 flag.mangle = flag.angles;
954 flag.reset = ctf_Reset;
955 flag.touch = ctf_FlagTouch;
956 flag.think = ctf_FlagThink;
957 flag.nextthink = time + FLAG_THINKRATE;
958 flag.ctf_status = FLAG_BASE;
961 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
962 if(!flag.scale) { flag.scale = FLAG_SCALE; }
963 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
964 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
965 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
966 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
969 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
970 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
971 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
972 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.
973 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
974 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
975 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
978 precache_sound(flag.snd_flag_taken);
979 precache_sound(flag.snd_flag_returned);
980 precache_sound(flag.snd_flag_capture);
981 precache_sound(flag.snd_flag_respawn);
982 precache_sound(flag.snd_flag_dropped);
983 precache_sound(flag.snd_flag_touch);
984 precache_sound(flag.snd_flag_pass);
985 precache_model(flag.model);
986 precache_model("models/ctf/shield.md3");
987 precache_model("models/ctf/shockwavetransring.md3");
990 setmodel(flag, flag.model); // precision set below
991 setsize(flag, FLAG_MIN, FLAG_MAX);
992 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
994 if(autocvar_g_ctf_flag_glowtrails)
996 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1001 flag.effects |= EF_LOWPRECISION;
1002 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1003 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1006 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1008 flag.dropped_origin = flag.origin;
1009 flag.noalign = TRUE;
1010 flag.movetype = MOVETYPE_NONE;
1012 else // drop to floor, automatically find a platform and set that as spawn origin
1014 flag.noalign = FALSE;
1017 flag.movetype = MOVETYPE_TOSS;
1020 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1028 // NOTE: LEGACY CODE, needs to be re-written!
1030 void havocbot_calculate_middlepoint()
1034 vector fo = '0 0 0';
1037 f = ctf_worldflaglist;
1042 f = f.ctf_worldflagnext;
1046 havocbot_ctf_middlepoint = s * (1.0 / n);
1047 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1051 entity havocbot_ctf_find_flag(entity bot)
1054 f = ctf_worldflaglist;
1057 if (bot.team == f.team)
1059 f = f.ctf_worldflagnext;
1064 entity havocbot_ctf_find_enemy_flag(entity bot)
1067 f = ctf_worldflaglist;
1070 if (bot.team != f.team)
1072 f = f.ctf_worldflagnext;
1077 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1085 FOR_EACH_PLAYER(head)
1087 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1090 if(vlen(head.origin - org) < tc_radius)
1097 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1100 head = ctf_worldflaglist;
1103 if (self.team == head.team)
1105 head = head.ctf_worldflagnext;
1108 navigation_routerating(head, ratingscale, 10000);
1111 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1114 head = ctf_worldflaglist;
1117 if (self.team == head.team)
1119 head = head.ctf_worldflagnext;
1124 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1127 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1130 head = ctf_worldflaglist;
1133 if (self.team != head.team)
1135 head = head.ctf_worldflagnext;
1138 navigation_routerating(head, ratingscale, 10000);
1141 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1143 if (!bot_waypoints_for_items)
1145 havocbot_goalrating_ctf_enemyflag(ratingscale);
1151 head = havocbot_ctf_find_enemy_flag(self);
1156 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1159 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1163 mf = havocbot_ctf_find_flag(self);
1165 if(mf.ctf_status == FLAG_BASE)
1169 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1172 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1175 head = ctf_worldflaglist;
1178 // flag is out in the field
1179 if(head.ctf_status != FLAG_BASE)
1180 if(head.tag_entity==world) // dropped
1184 if(vlen(org-head.origin)<df_radius)
1185 navigation_routerating(head, ratingscale, 10000);
1188 navigation_routerating(head, ratingscale, 10000);
1191 head = head.ctf_worldflagnext;
1195 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1199 head = findchainfloat(bot_pickup, TRUE);
1202 // gather health and armor only
1204 if (head.health || head.armorvalue)
1205 if (vlen(head.origin - org) < sradius)
1207 // get the value of the item
1208 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1210 navigation_routerating(head, t * ratingscale, 500);
1216 void havocbot_ctf_reset_role(entity bot)
1218 float cdefense, cmiddle, coffense;
1219 entity mf, ef, head;
1222 if(bot.deadflag != DEAD_NO)
1225 if(vlen(havocbot_ctf_middlepoint)==0)
1226 havocbot_calculate_middlepoint();
1229 if (bot.flagcarried)
1231 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1235 mf = havocbot_ctf_find_flag(bot);
1236 ef = havocbot_ctf_find_enemy_flag(bot);
1238 // Retrieve stolen flag
1239 if(mf.ctf_status!=FLAG_BASE)
1241 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1245 // If enemy flag is taken go to the middle to intercept pursuers
1246 if(ef.ctf_status!=FLAG_BASE)
1248 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1252 // if there is only me on the team switch to offense
1254 FOR_EACH_PLAYER(head)
1255 if(head.team==bot.team)
1260 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1264 // Evaluate best position to take
1265 // Count mates on middle position
1266 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1268 // Count mates on defense position
1269 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1271 // Count mates on offense position
1272 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1274 if(cdefense<=coffense)
1275 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1276 else if(coffense<=cmiddle)
1277 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1279 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1282 void havocbot_role_ctf_carrier()
1284 if(self.deadflag != DEAD_NO)
1286 havocbot_ctf_reset_role(self);
1290 if (self.flagcarried == world)
1292 havocbot_ctf_reset_role(self);
1296 if (self.bot_strategytime < time)
1298 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1300 navigation_goalrating_start();
1301 havocbot_goalrating_ctf_ourbase(50000);
1304 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1306 navigation_goalrating_end();
1308 if (self.navigation_hasgoals)
1309 self.havocbot_cantfindflag = time + 10;
1310 else if (time > self.havocbot_cantfindflag)
1312 // Can't navigate to my own base, suicide!
1313 // TODO: drop it and wander around
1314 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1320 void havocbot_role_ctf_escort()
1324 if(self.deadflag != DEAD_NO)
1326 havocbot_ctf_reset_role(self);
1330 if (self.flagcarried)
1332 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1336 // If enemy flag is back on the base switch to previous role
1337 ef = havocbot_ctf_find_enemy_flag(self);
1338 if(ef.ctf_status==FLAG_BASE)
1340 self.havocbot_role = self.havocbot_previous_role;
1341 self.havocbot_role_timeout = 0;
1345 // If the flag carrier reached the base switch to defense
1346 mf = havocbot_ctf_find_flag(self);
1347 if(mf.ctf_status!=FLAG_BASE)
1348 if(vlen(ef.origin - mf.dropped_origin) < 300)
1350 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1354 // Set the role timeout if necessary
1355 if (!self.havocbot_role_timeout)
1357 self.havocbot_role_timeout = time + random() * 30 + 60;
1360 // If nothing happened just switch to previous role
1361 if (time > self.havocbot_role_timeout)
1363 self.havocbot_role = self.havocbot_previous_role;
1364 self.havocbot_role_timeout = 0;
1368 // Chase the flag carrier
1369 if (self.bot_strategytime < time)
1371 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1372 navigation_goalrating_start();
1373 havocbot_goalrating_ctf_enemyflag(30000);
1374 havocbot_goalrating_ctf_ourstolenflag(40000);
1375 havocbot_goalrating_items(10000, self.origin, 10000);
1376 navigation_goalrating_end();
1380 void havocbot_role_ctf_offense()
1385 if(self.deadflag != DEAD_NO)
1387 havocbot_ctf_reset_role(self);
1391 if (self.flagcarried)
1393 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1398 mf = havocbot_ctf_find_flag(self);
1399 ef = havocbot_ctf_find_enemy_flag(self);
1402 if(mf.ctf_status!=FLAG_BASE)
1405 pos = mf.tag_entity.origin;
1409 // Try to get it if closer than the enemy base
1410 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1412 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1417 // Escort flag carrier
1418 if(ef.ctf_status!=FLAG_BASE)
1421 pos = ef.tag_entity.origin;
1425 if(vlen(pos-mf.dropped_origin)>700)
1427 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1432 // About to fail, switch to middlefield
1435 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1439 // Set the role timeout if necessary
1440 if (!self.havocbot_role_timeout)
1441 self.havocbot_role_timeout = time + 120;
1443 if (time > self.havocbot_role_timeout)
1445 havocbot_ctf_reset_role(self);
1449 if (self.bot_strategytime < time)
1451 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1452 navigation_goalrating_start();
1453 havocbot_goalrating_ctf_ourstolenflag(50000);
1454 havocbot_goalrating_ctf_enemybase(20000);
1455 havocbot_goalrating_items(5000, self.origin, 1000);
1456 havocbot_goalrating_items(1000, self.origin, 10000);
1457 navigation_goalrating_end();
1461 // Retriever (temporary role):
1462 void havocbot_role_ctf_retriever()
1466 if(self.deadflag != DEAD_NO)
1468 havocbot_ctf_reset_role(self);
1472 if (self.flagcarried)
1474 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1478 // If flag is back on the base switch to previous role
1479 mf = havocbot_ctf_find_flag(self);
1480 if(mf.ctf_status==FLAG_BASE)
1482 havocbot_ctf_reset_role(self);
1486 if (!self.havocbot_role_timeout)
1487 self.havocbot_role_timeout = time + 20;
1489 if (time > self.havocbot_role_timeout)
1491 havocbot_ctf_reset_role(self);
1495 if (self.bot_strategytime < time)
1500 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1501 navigation_goalrating_start();
1502 havocbot_goalrating_ctf_ourstolenflag(50000);
1503 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1504 havocbot_goalrating_ctf_enemybase(30000);
1505 havocbot_goalrating_items(500, self.origin, rt_radius);
1506 navigation_goalrating_end();
1510 void havocbot_role_ctf_middle()
1514 if(self.deadflag != DEAD_NO)
1516 havocbot_ctf_reset_role(self);
1520 if (self.flagcarried)
1522 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1526 mf = havocbot_ctf_find_flag(self);
1527 if(mf.ctf_status!=FLAG_BASE)
1529 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1533 if (!self.havocbot_role_timeout)
1534 self.havocbot_role_timeout = time + 10;
1536 if (time > self.havocbot_role_timeout)
1538 havocbot_ctf_reset_role(self);
1542 if (self.bot_strategytime < time)
1546 org = havocbot_ctf_middlepoint;
1547 org_z = self.origin_z;
1549 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1550 navigation_goalrating_start();
1551 havocbot_goalrating_ctf_ourstolenflag(50000);
1552 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1553 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1554 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1555 havocbot_goalrating_items(2500, self.origin, 10000);
1556 havocbot_goalrating_ctf_enemybase(2500);
1557 navigation_goalrating_end();
1561 void havocbot_role_ctf_defense()
1565 if(self.deadflag != DEAD_NO)
1567 havocbot_ctf_reset_role(self);
1571 if (self.flagcarried)
1573 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1577 // If own flag was captured
1578 mf = havocbot_ctf_find_flag(self);
1579 if(mf.ctf_status!=FLAG_BASE)
1581 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1585 if (!self.havocbot_role_timeout)
1586 self.havocbot_role_timeout = time + 30;
1588 if (time > self.havocbot_role_timeout)
1590 havocbot_ctf_reset_role(self);
1593 if (self.bot_strategytime < time)
1598 org = mf.dropped_origin;
1599 mp_radius = havocbot_ctf_middlepoint_radius;
1601 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1602 navigation_goalrating_start();
1604 // if enemies are closer to our base, go there
1605 entity head, closestplayer = world;
1606 float distance, bestdistance = 10000;
1607 FOR_EACH_PLAYER(head)
1609 if(head.deadflag!=DEAD_NO)
1612 distance = vlen(org - head.origin);
1613 if(distance<bestdistance)
1615 closestplayer = head;
1616 bestdistance = distance;
1621 if(closestplayer.team!=self.team)
1622 if(vlen(org - self.origin)>1000)
1623 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1624 havocbot_goalrating_ctf_ourbase(30000);
1626 havocbot_goalrating_ctf_ourstolenflag(20000);
1627 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1628 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1629 havocbot_goalrating_items(10000, org, mp_radius);
1630 havocbot_goalrating_items(5000, self.origin, 10000);
1631 navigation_goalrating_end();
1635 void havocbot_role_ctf_setrole(entity bot, float role)
1637 dprint(strcat(bot.netname," switched to "));
1640 case HAVOCBOT_CTF_ROLE_CARRIER:
1642 bot.havocbot_role = havocbot_role_ctf_carrier;
1643 bot.havocbot_role_timeout = 0;
1644 bot.havocbot_cantfindflag = time + 10;
1645 bot.bot_strategytime = 0;
1647 case HAVOCBOT_CTF_ROLE_DEFENSE:
1649 bot.havocbot_role = havocbot_role_ctf_defense;
1650 bot.havocbot_role_timeout = 0;
1652 case HAVOCBOT_CTF_ROLE_MIDDLE:
1654 bot.havocbot_role = havocbot_role_ctf_middle;
1655 bot.havocbot_role_timeout = 0;
1657 case HAVOCBOT_CTF_ROLE_OFFENSE:
1659 bot.havocbot_role = havocbot_role_ctf_offense;
1660 bot.havocbot_role_timeout = 0;
1662 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1663 dprint("retriever");
1664 bot.havocbot_previous_role = bot.havocbot_role;
1665 bot.havocbot_role = havocbot_role_ctf_retriever;
1666 bot.havocbot_role_timeout = time + 10;
1667 bot.bot_strategytime = 0;
1669 case HAVOCBOT_CTF_ROLE_ESCORT:
1671 bot.havocbot_previous_role = bot.havocbot_role;
1672 bot.havocbot_role = havocbot_role_ctf_escort;
1673 bot.havocbot_role_timeout = time + 30;
1674 bot.bot_strategytime = 0;
1685 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1689 // initially clear items so they can be set as necessary later.
1690 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1691 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1693 // scan through all the flags and notify the client about them
1694 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1696 switch(flag.ctf_status)
1701 if((flag.owner == self) || (flag.pass_sender == self))
1702 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1704 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1709 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1715 // item for stopping players from capturing the flag too often
1716 if(self.ctf_captureshielded)
1717 self.items |= IT_CTF_SHIELDED;
1719 // update the health of the flag carrier waypointsprite
1720 if(self.wps_flagcarrier)
1721 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1726 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1728 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1730 if(frag_target == frag_attacker) // damage done to yourself
1732 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1733 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1735 else // damage done to everyone else
1737 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1738 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1741 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1743 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)))
1744 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1746 frag_target.wps_helpme_time = time;
1747 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1749 // todo: add notification for when flag carrier needs help?
1754 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1756 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1758 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1759 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1762 if(frag_target.flagcarried)
1763 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1768 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1771 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1774 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1776 entity flag; // temporary entity for the search method
1778 if(self.flagcarried)
1779 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1781 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1783 if(flag.pass_sender == self) { flag.pass_sender = world; }
1784 if(flag.pass_target == self) { flag.pass_target = world; }
1785 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1791 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1793 if(self.flagcarried)
1794 if(!autocvar_g_ctf_portalteleport)
1795 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1800 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1802 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1804 entity player = self;
1806 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1808 // pass the flag to a team mate
1809 if(autocvar_g_ctf_pass)
1811 entity head, closest_target = world;
1812 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1814 while(head) // find the closest acceptable target to pass to
1816 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1817 if(head != player && SAME_TEAM(head, player))
1818 if(!head.speedrunning && !head.vehicle)
1820 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1821 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1822 vector passer_center = CENTER_OR_VIEWOFS(player);
1824 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1826 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1828 if(IS_BOT_CLIENT(head))
1830 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1831 ctf_Handle_Throw(head, player, DROP_PASS);
1835 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1836 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1838 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1841 else if(player.flagcarried)
1845 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1846 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1847 { closest_target = head; }
1849 else { closest_target = head; }
1856 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1859 // throw the flag in front of you
1860 if(autocvar_g_ctf_throw && player.flagcarried)
1862 if(player.throw_count == -1)
1864 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1866 player.throw_prevtime = time;
1867 player.throw_count = 1;
1868 ctf_Handle_Throw(player, world, DROP_THROW);
1873 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1879 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1880 else { player.throw_count += 1; }
1881 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1883 player.throw_prevtime = time;
1884 ctf_Handle_Throw(player, world, DROP_THROW);
1893 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1895 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1897 self.wps_helpme_time = time;
1898 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1900 else // create a normal help me waypointsprite
1902 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');
1903 WaypointSprite_Ping(self.wps_helpme);
1909 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1911 if(vh_player.flagcarried)
1913 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1915 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1919 setattachment(vh_player.flagcarried, vh_vehicle, "");
1920 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1921 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1922 //vh_player.flagcarried.angles = '0 0 0';
1930 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1932 if(vh_player.flagcarried)
1934 setattachment(vh_player.flagcarried, vh_player, "");
1935 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1936 vh_player.flagcarried.scale = FLAG_SCALE;
1937 vh_player.flagcarried.angles = '0 0 0';
1944 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1946 if(self.flagcarried)
1948 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1949 ctf_RespawnFlag(self.flagcarried);
1956 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1958 entity flag; // temporary entity for the search method
1960 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1962 switch(flag.ctf_status)
1967 // lock the flag, game is over
1968 flag.movetype = MOVETYPE_NONE;
1969 flag.takedamage = DAMAGE_NO;
1970 flag.solid = SOLID_NOT;
1971 flag.nextthink = FALSE; // stop thinking
1973 //dprint("stopping the ", flag.netname, " from moving.\n");
1981 // do nothing for these flags
1990 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1992 havocbot_ctf_reset_role(self);
2001 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2002 CTF Starting point for a player in team one (Red).
2003 Keys: "angle" viewing angle when spawning. */
2004 void spawnfunc_info_player_team1()
2006 if(g_assault) { remove(self); return; }
2008 self.team = NUM_TEAM_1; // red
2009 spawnfunc_info_player_deathmatch();
2013 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2014 CTF Starting point for a player in team two (Blue).
2015 Keys: "angle" viewing angle when spawning. */
2016 void spawnfunc_info_player_team2()
2018 if(g_assault) { remove(self); return; }
2020 self.team = NUM_TEAM_2; // blue
2021 spawnfunc_info_player_deathmatch();
2024 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2025 CTF Starting point for a player in team three (Yellow).
2026 Keys: "angle" viewing angle when spawning. */
2027 void spawnfunc_info_player_team3()
2029 if(g_assault) { remove(self); return; }
2031 self.team = NUM_TEAM_3; // yellow
2032 spawnfunc_info_player_deathmatch();
2036 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2037 CTF Starting point for a player in team four (Purple).
2038 Keys: "angle" viewing angle when spawning. */
2039 void spawnfunc_info_player_team4()
2041 if(g_assault) { remove(self); return; }
2043 self.team = NUM_TEAM_4; // purple
2044 spawnfunc_info_player_deathmatch();
2047 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2048 CTF flag for team one (Red).
2050 "angle" Angle the flag will point (minus 90 degrees)...
2051 "model" model to use, note this needs red and blue as skins 0 and 1...
2052 "noise" sound played when flag is picked up...
2053 "noise1" sound played when flag is returned by a teammate...
2054 "noise2" sound played when flag is captured...
2055 "noise3" sound played when flag is lost in the field and respawns itself...
2056 "noise4" sound played when flag is dropped by a player...
2057 "noise5" sound played when flag touches the ground... */
2058 void spawnfunc_item_flag_team1()
2060 if(!g_ctf) { remove(self); return; }
2062 ctf_FlagSetup(1, self); // 1 = red
2065 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2066 CTF flag for team two (Blue).
2068 "angle" Angle the flag will point (minus 90 degrees)...
2069 "model" model to use, note this needs red and blue as skins 0 and 1...
2070 "noise" sound played when flag is picked up...
2071 "noise1" sound played when flag is returned by a teammate...
2072 "noise2" sound played when flag is captured...
2073 "noise3" sound played when flag is lost in the field and respawns itself...
2074 "noise4" sound played when flag is dropped by a player...
2075 "noise5" sound played when flag touches the ground... */
2076 void spawnfunc_item_flag_team2()
2078 if(!g_ctf) { remove(self); return; }
2080 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2083 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2084 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2085 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.
2087 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2088 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2089 void spawnfunc_ctf_team()
2091 if(!g_ctf) { remove(self); return; }
2093 self.classname = "ctf_team";
2094 self.team = self.cnt + 1;
2097 // compatibility for quake maps
2098 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2099 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2100 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2101 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2102 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2103 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2111 void ctf_ScoreRules()
2113 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2114 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2115 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2116 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2117 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2118 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2119 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2120 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2121 ScoreRules_basics_end();
2124 // code from here on is just to support maps that don't have flag and team entities
2125 void ctf_SpawnTeam (string teamname, float teamcolor)
2130 self.classname = "ctf_team";
2131 self.netname = teamname;
2132 self.cnt = teamcolor;
2134 spawnfunc_ctf_team();
2139 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2141 // if no teams are found, spawn defaults
2142 if(find(world, classname, "ctf_team") == world)
2144 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2145 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2146 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2152 void ctf_Initialize()
2154 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2156 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2157 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2158 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2160 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2164 MUTATOR_DEFINITION(gamemode_ctf)
2166 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2167 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2168 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2169 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2170 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2171 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2172 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2173 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2174 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2175 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2176 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2177 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2178 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2179 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2183 if(time > 1) // game loads at time 1
2184 error("This is a game type and it cannot be added at runtime.");
2188 MUTATOR_ONROLLBACK_OR_REMOVE
2190 // we actually cannot roll back ctf_Initialize here
2191 // BUT: we don't need to! If this gets called, adding always
2197 print("This is a game type and it cannot be removed at runtime.");