1 #include "../../common/movetypes/movetypes.qh"
3 void ctf_FakeTimeLimit(entity e, float t)
6 WriteByte(MSG_ONE, 3); // svc_updatestat
7 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
9 WriteCoord(MSG_ONE, autocvar_timelimit);
11 WriteCoord(MSG_ONE, (t + 1) / 60);
14 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
16 if(autocvar_sv_eventlog)
17 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
20 void ctf_CaptureRecord(entity flag, entity player)
22 float cap_record = ctf_captimerecord;
23 float cap_time = (time - flag.ctf_pickuptime);
24 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
27 if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
28 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)); }
29 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)); }
31 // write that shit in the database
32 if((!ctf_captimerecord) || (cap_time < cap_record))
34 ctf_captimerecord = cap_time;
35 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
36 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
37 write_recordmarker(player, (time - cap_time), cap_time);
41 void ctf_FlagcarrierWaypoints(entity player)
43 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
44 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
45 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
46 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
49 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
51 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
52 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
53 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
54 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
57 if(current_height) // make sure we can actually do this arcing path
59 targpos = (to + ('0 0 1' * current_height));
60 WarpZone_TraceLine(flag.move_origin, targpos, MOVE_NOMONSTERS, flag);
61 if(trace_fraction < 1)
63 //print("normal arc line failed, trying to find new pos...");
64 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
65 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
66 WarpZone_TraceLine(flag.move_origin, targpos, MOVE_NOMONSTERS, flag);
67 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
68 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
71 else { targpos = to; }
73 //flag.move_angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
75 vector desired_direction = normalize(targpos - from);
76 if(turnrate) { flag.move_velocity = (normalize(normalize(flag.move_velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
77 else { flag.move_velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
80 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
82 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
84 // directional tracing only
86 makevectors(passer_angle);
88 // find the closest point on the enemy to the center of the attack
89 float h; // hypotenuse, which is the distance between attacker to head
90 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
92 h = vlen(head_center - passer_center);
93 a = h * (normalize(head_center - passer_center) * v_forward);
95 vector nearest_on_line = (passer_center + a * v_forward);
96 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
98 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
99 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
101 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
106 else { return true; }
110 // =======================
111 // CaptureShield Functions
112 // =======================
114 float ctf_CaptureShield_CheckStatus(entity p)
118 float players_worseeq, players_total;
120 if(ctf_captureshield_max_ratio <= 0)
123 s = PlayerScore_Add(p, SP_SCORE, 0);
124 if(s >= -ctf_captureshield_min_negscore)
127 players_total = players_worseeq = 0;
132 se = PlayerScore_Add(e, SP_SCORE, 0);
138 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
139 // use this rule here
141 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
147 void ctf_CaptureShield_Update(entity player, float wanted_status)
149 float updated_status = ctf_CaptureShield_CheckStatus(player);
150 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
152 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
153 player.ctf_captureshielded = updated_status;
157 float ctf_CaptureShield_Customize()
159 if(!other.ctf_captureshielded) { return false; }
160 if(SAME_TEAM(self, other)) { return false; }
165 void ctf_CaptureShield_Touch()
167 if(!other.ctf_captureshielded) { return; }
168 if(SAME_TEAM(self, other)) { return; }
170 vector mymid = (self.absmin + self.absmax) * 0.5;
171 vector othermid = (other.absmin + other.absmax) * 0.5;
173 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
174 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
177 void ctf_CaptureShield_Spawn(entity flag)
179 entity shield = spawn();
182 shield.team = self.team;
183 shield.touch = ctf_CaptureShield_Touch;
184 shield.customizeentityforclient = ctf_CaptureShield_Customize;
185 shield.classname = "ctf_captureshield";
186 shield.effects = EF_ADDITIVE;
187 shield.movetype = MOVETYPE_NOCLIP;
188 shield.solid = SOLID_TRIGGER;
189 shield.avelocity = '7 0 11';
192 setorigin(shield, self.origin);
193 setmodel(shield, "models/ctf/shield.md3");
194 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
198 // ====================
199 // Drop/Pass/Throw Code
200 // ====================
202 void ctf_Handle_Drop(entity flag, entity player, float droptype)
205 player = (player ? player : flag.pass_sender);
208 flag.move_movetype = MOVETYPE_TOSS;
209 flag.takedamage = DAMAGE_YES;
210 flag.move_angles = '0 0 0';
211 flag.health = flag.max_flag_health;
212 flag.ctf_droptime = time;
213 flag.ctf_dropper = player;
214 flag.ctf_status = FLAG_DROPPED;
216 // messages and sounds
217 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
218 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
219 ctf_EventLog("dropped", player.team, player);
222 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
223 PlayerScore_Add(player, SP_CTF_DROPS, 1);
226 if(autocvar_g_ctf_flag_dropped_waypoint)
227 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));
229 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
231 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
232 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
235 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
237 if(droptype == DROP_PASS)
239 flag.pass_distance = 0;
240 flag.pass_sender = world;
241 flag.pass_target = world;
245 void ctf_Handle_Retrieve(entity flag, entity player)
247 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
248 entity sender = flag.pass_sender;
250 // transfer flag to player
252 flag.owner.flagcarried = flag;
257 setattachment(flag, player.vehicle, "");
258 flag.move_origin = VEHICLE_FLAG_OFFSET;
259 flag.scale = VEHICLE_FLAG_SCALE;
263 setattachment(flag, player, "");
264 flag.move_origin = VEHICLE_FLAG_OFFSET;
266 flag.move_movetype = MOVETYPE_NONE;
267 flag.takedamage = DAMAGE_NO;
268 flag.solid = SOLID_NOT;
269 flag.move_angles = '0 0 0';
270 flag.ctf_status = FLAG_CARRY;
272 // messages and sounds
273 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
274 ctf_EventLog("receive", flag.team, player);
276 FOR_EACH_REALPLAYER(tmp_player)
278 if(tmp_player == sender)
279 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
280 else if(tmp_player == player)
281 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
282 else if(SAME_TEAM(tmp_player, sender))
283 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
286 // create new waypoint
287 ctf_FlagcarrierWaypoints(player);
289 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
290 player.throw_antispam = sender.throw_antispam;
292 flag.pass_distance = 0;
293 flag.pass_sender = world;
294 flag.pass_target = world;
297 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
299 entity flag = player.flagcarried;
300 vector targ_origin, flag_velocity;
302 if(!flag) { return; }
303 if((droptype == DROP_PASS) && !receiver) { return; }
305 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
308 setattachment(flag, world, "");
309 flag.move_origin = player.origin + FLAG_DROP_OFFSET;
310 flag.owner.flagcarried = world;
312 flag.solid = SOLID_TRIGGER;
313 flag.ctf_dropper = player;
314 flag.ctf_droptime = time;
316 flag.move_flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
317 flag.flags = flag.move_flags;
324 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
325 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
326 WarpZone_RefSys_Copy(flag, receiver);
327 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
328 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
330 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
331 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
334 flag.move_movetype = MOVETYPE_FLY;
335 flag.takedamage = DAMAGE_NO;
336 flag.pass_sender = player;
337 flag.pass_target = receiver;
338 flag.ctf_status = FLAG_PASSING;
341 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
342 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
343 ctf_EventLog("pass", flag.team, player);
349 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'));
351 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)));
352 flag.move_velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
353 ctf_Handle_Drop(flag, player, droptype);
359 flag.move_velocity = '0 0 0'; // do nothing
366 flag.move_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);
367 ctf_Handle_Drop(flag, player, droptype);
372 // kill old waypointsprite
373 WaypointSprite_Ping(player.wps_flagcarrier);
374 WaypointSprite_Kill(player.wps_flagcarrier);
376 if(player.wps_enemyflagcarrier)
377 WaypointSprite_Kill(player.wps_enemyflagcarrier);
380 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
388 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
390 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
391 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
392 float old_time, new_time;
394 if (!player) { return; } // without someone to give the reward to, we can't possibly cap
396 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
398 // messages and sounds
399 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
400 ctf_CaptureRecord(enemy_flag, player);
401 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
405 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
406 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
411 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
412 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
414 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
415 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
416 if(!old_time || new_time < old_time)
417 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
420 pointparticles(particleeffectnum(flag.capeffect), flag.move_origin, '0 0 0', 1);
421 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.move_origin - '0 0 15', -0.8, 0, 1);
424 if(capturetype == CAPTURE_NORMAL)
426 WaypointSprite_Kill(player.wps_flagcarrier);
427 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
429 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
430 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
434 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
435 ctf_RespawnFlag(enemy_flag);
438 void ctf_Handle_Return(entity flag, entity player)
440 // messages and sounds
441 if(player.flags & FL_MONSTER)
443 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
447 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
448 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
450 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
451 ctf_EventLog("return", flag.team, player);
454 if(IS_PLAYER(player))
456 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
457 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
459 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
462 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
466 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
467 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
468 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
472 ctf_RespawnFlag(flag);
475 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
478 float pickup_dropped_score; // used to calculate dropped pickup score
480 // attach the flag to the player
482 player.flagcarried = flag;
485 setattachment(flag, player.vehicle, "");
486 flag.move_origin = VEHICLE_FLAG_OFFSET;
487 flag.scale = VEHICLE_FLAG_SCALE;
491 setattachment(flag, player, "");
492 flag.move_origin = FLAG_CARRY_OFFSET;
496 flag.move_movetype = MOVETYPE_NONE;
497 flag.takedamage = DAMAGE_NO;
498 flag.solid = SOLID_NOT;
499 flag.move_angles = '0 0 0';
500 flag.ctf_status = FLAG_CARRY;
504 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
505 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
509 // messages and sounds
510 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
511 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
512 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
514 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
515 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
517 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
520 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
521 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
526 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
527 ctf_EventLog("steal", flag.team, player);
533 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);
534 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);
535 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
536 PlayerTeamScore_AddScore(player, pickup_dropped_score);
537 ctf_EventLog("pickup", flag.team, player);
545 if(pickuptype == PICKUP_BASE)
547 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
548 if((player.speedrunning) && (ctf_captimerecord))
549 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
553 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
556 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
557 ctf_FlagcarrierWaypoints(player);
558 WaypointSprite_Ping(player.wps_flagcarrier);
562 // ===================
563 // Main Flag Functions
564 // ===================
566 void ctf_CheckFlagReturn(entity flag, float returntype)
568 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
570 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
572 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
576 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
577 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
578 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
579 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
583 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
585 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
586 ctf_EventLog("returned", flag.team, world);
587 ctf_RespawnFlag(flag);
592 void ctf_CheckStalemate(void)
595 float stale_red_flags = 0, stale_blue_flags = 0;
598 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
600 // build list of stale flags
601 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
603 if(autocvar_g_ctf_stalemate)
604 if(tmp_entity.ctf_status != FLAG_BASE)
605 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
607 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
608 ctf_staleflaglist = tmp_entity;
610 switch(tmp_entity.team)
612 case NUM_TEAM_1: ++stale_red_flags; break;
613 case NUM_TEAM_2: ++stale_blue_flags; break;
618 if(stale_red_flags && stale_blue_flags)
619 ctf_stalemate = true;
620 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
621 { ctf_stalemate = false; wpforenemy_announced = false; }
622 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
623 { ctf_stalemate = false; wpforenemy_announced = false; }
625 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
628 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
630 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
631 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));
634 if (!wpforenemy_announced)
636 FOR_EACH_REALPLAYER(tmp_entity)
637 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
639 wpforenemy_announced = true;
644 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
646 self.move_velocity = self.velocity;
647 if(ITEM_DAMAGE_NEEDKILL(deathtype))
649 // automatically kill the flag and return it
651 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
654 if(autocvar_g_ctf_flag_return_damage)
656 // reduce health and check if it should be returned
657 self.health = self.health - damage;
658 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
663 void ctf_FlagUpdate()
669 if(self == ctf_worldflaglist) // only for the first flag
670 FOR_EACH_CLIENT(tmp_entity)
671 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
674 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
675 dprint("wtf the flag got squashed?\n");
676 tracebox(self.move_origin, FLAG_MIN, FLAG_MAX, self.move_origin, MOVE_NOMONSTERS, self);
677 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
678 setsize(self, FLAG_MIN, FLAG_MAX); }
680 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
684 self.move_angles = '0 0 0';
692 switch(self.ctf_status)
696 if(autocvar_g_ctf_dropped_capture_radius)
698 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
699 if(tmp_entity.ctf_status == FLAG_DROPPED)
700 if(vlen(self.move_origin - tmp_entity.move_origin) < autocvar_g_ctf_dropped_capture_radius)
701 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
702 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
709 if(autocvar_g_ctf_flag_dropped_floatinwater)
711 vector midpoint = ((self.absmin + self.absmax) * 0.5);
712 if(pointcontents(midpoint) == CONTENT_WATER)
714 self.move_velocity = self.move_velocity * 0.5;
716 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
717 { self.move_velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
719 { self.move_movetype = MOVETYPE_FLY; }
721 else if(self.move_movetype == MOVETYPE_FLY) { self.move_movetype = MOVETYPE_TOSS; }
723 if(autocvar_g_ctf_flag_return_dropped)
725 if((vlen(self.move_origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
728 ctf_CheckFlagReturn(self, RETURN_DROPPED);
732 if(autocvar_g_ctf_flag_return_time)
734 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
735 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
743 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
746 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
750 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
754 if(autocvar_g_ctf_stalemate)
756 if(time >= wpforenemy_nextthink)
758 ctf_CheckStalemate();
759 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
767 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
768 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
769 WarpZone_TraceLine(self.move_origin, targ_origin, MOVE_NOMONSTERS, self);
771 if((self.pass_target == world)
772 || (self.pass_target.deadflag != DEAD_NO)
773 || (self.pass_target.flagcarried)
774 || (vlen(self.move_origin - targ_origin) > autocvar_g_ctf_pass_radius)
775 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
776 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
778 // give up, pass failed
779 ctf_Handle_Drop(self, world, DROP_PASS);
783 // still a viable target, go for it
784 ctf_CalculatePassVelocity(self, targ_origin, self.move_origin, true);
789 default: // this should never happen
791 dprint("ctf_FlagThink(): Flag exists with no status?\n");
799 self.nextthink = time;
801 if(time >= self.ctf_thinkrate)
803 self.ctf_thinkrate = time + FLAG_THINKRATE;
807 //Movetype_Physics_NoMatchServer();
808 Movetype_Physics_MatchTicrate(sys_frametime, 0);
813 if(gameover) { return; }
814 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
816 entity toucher = other;
817 float is_not_monster = (!(toucher.flags & FL_MONSTER));
819 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
820 if(ITEM_TOUCH_NEEDKILL())
823 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
827 // special touch behaviors
828 if(toucher.frozen) { return; }
829 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
831 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
832 toucher = toucher.owner; // the player is actually the vehicle owner, not other
834 return; // do nothing
836 else if(toucher.flags & FL_MONSTER)
838 if(!autocvar_g_ctf_allow_monster_touch)
839 return; // do nothing
841 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
843 if(time > self.wait) // if we haven't in a while, play a sound/effect
845 pointparticles(particleeffectnum(self.toucheffect), self.move_origin, '0 0 0', 1);
846 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
847 self.wait = time + FLAG_TOUCHRATE;
851 else if(toucher.deadflag != DEAD_NO) { return; }
853 switch(self.ctf_status)
857 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
858 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
859 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
860 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
866 if(SAME_TEAM(toucher, self))
867 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
868 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
869 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
875 dprint("Someone touched a flag even though it was being carried?\n");
881 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
883 if(DIFF_TEAM(toucher, self.pass_sender))
884 ctf_Handle_Return(self, toucher);
886 ctf_Handle_Retrieve(self, toucher);
894 void ctf_RespawnFlag(entity flag)
896 // check for flag respawn being called twice in a row
897 if(flag.last_respawn > time - 0.5)
898 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
900 flag.last_respawn = time;
902 // reset the player (if there is one)
903 if((flag.owner) && (flag.owner.flagcarried == flag))
905 if(flag.owner.wps_enemyflagcarrier)
906 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
908 WaypointSprite_Kill(flag.wps_flagcarrier);
910 flag.owner.flagcarried = world;
912 if(flag.speedrunning)
913 ctf_FakeTimeLimit(flag.owner, -1);
916 if((flag.owner) && (flag.owner.vehicle))
917 flag.scale = FLAG_SCALE;
919 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
920 { WaypointSprite_Kill(flag.wps_flagdropped); }
923 setattachment(flag, world, "");
924 flag.move_origin = flag.ctf_spawnorigin;
926 flag.move_movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
927 flag.takedamage = DAMAGE_NO;
928 flag.health = flag.max_flag_health;
929 flag.solid = SOLID_TRIGGER;
930 flag.move_velocity = '0 0 0';
931 flag.move_angles = flag.mangle;
932 flag.move_flags = FL_ITEM | FL_NOTARGET;
933 flag.flags = flag.move_flags;
935 flag.ctf_status = FLAG_BASE;
937 flag.pass_distance = 0;
938 flag.pass_sender = world;
939 flag.pass_target = world;
940 flag.ctf_dropper = world;
941 flag.ctf_pickuptime = 0;
942 flag.ctf_droptime = 0;
944 ctf_CheckStalemate();
950 if(IS_PLAYER(self.owner))
951 ctf_Handle_Throw(self.owner, world, DROP_RESET);
953 ctf_RespawnFlag(self);
956 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
959 waypoint_spawnforitem_force(self, self.origin);
960 self.nearestwaypointtimeout = 0; // activate waypointing again
961 self.bot_basewaypoint = self.nearestwaypoint;
964 // move_origin isnt accessible just yet
965 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
966 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
968 // captureshield setup
969 ctf_CaptureShield_Spawn(self);
971 self.move_origin = self.origin;
972 self.move_angles = self.angles;
973 self.move_velocity = self.velocity;
976 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
979 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.
980 self = flag; // for later usage with droptofloor()
983 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
984 ctf_worldflaglist = flag;
986 setattachment(flag, world, "");
988 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
989 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
990 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
991 flag.classname = "item_flag_team";
992 flag.target = "###item###"; // wut?
993 flag.move_flags = FL_ITEM | FL_NOTARGET;
994 flag.flags = flag.move_flags;
995 flag.solid = SOLID_TRIGGER;
996 flag.takedamage = DAMAGE_NO;
997 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
998 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
999 flag.health = flag.max_flag_health;
1000 flag.event_damage = ctf_FlagDamage;
1001 flag.pushable = true;
1002 flag.teleportable = TELEPORT_NORMAL;
1003 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1004 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1005 flag.move_velocity = '0 0 0';
1006 flag.mangle = flag.angles;
1007 flag.reset = ctf_Reset;
1008 flag.touch = ctf_FlagTouch;
1009 flag.move_touch = flag.touch;
1010 flag.think = ctf_FlagThink;
1011 flag.nextthink = time + FLAG_THINKRATE;
1012 flag.ctf_status = FLAG_BASE;
1013 flag.move_time = time;
1016 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
1017 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1018 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
1019 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
1020 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
1021 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
1024 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
1025 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
1026 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
1027 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.
1028 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1029 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1030 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1033 precache_sound(flag.snd_flag_taken);
1034 precache_sound(flag.snd_flag_returned);
1035 precache_sound(flag.snd_flag_capture);
1036 precache_sound(flag.snd_flag_respawn);
1037 precache_sound(flag.snd_flag_dropped);
1038 precache_sound(flag.snd_flag_touch);
1039 precache_sound(flag.snd_flag_pass);
1040 precache_model(flag.model);
1041 precache_model("models/ctf/shield.md3");
1042 precache_model("models/ctf/shockwavetransring.md3");
1045 setmodel(flag, flag.model); // precision set below
1046 setsize(flag, FLAG_MIN, FLAG_MAX);
1047 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1049 if(autocvar_g_ctf_flag_glowtrails)
1051 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1052 flag.glow_size = 25;
1053 flag.glow_trail = 1;
1056 flag.effects |= EF_LOWPRECISION;
1057 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1058 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1061 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1063 flag.dropped_origin = flag.origin;
1064 flag.noalign = true;
1065 flag.move_movetype = MOVETYPE_NONE;
1067 else // drop to floor, automatically find a platform and set that as spawn origin
1069 flag.noalign = false;
1072 flag.move_movetype = MOVETYPE_TOSS;
1075 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1083 // NOTE: LEGACY CODE, needs to be re-written!
1085 void havocbot_calculate_middlepoint()
1089 vector fo = '0 0 0';
1092 f = ctf_worldflaglist;
1097 f = f.ctf_worldflagnext;
1101 havocbot_ctf_middlepoint = s * (1.0 / n);
1102 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1106 entity havocbot_ctf_find_flag(entity bot)
1109 f = ctf_worldflaglist;
1112 if (bot.team == f.team)
1114 f = f.ctf_worldflagnext;
1119 entity havocbot_ctf_find_enemy_flag(entity bot)
1122 f = ctf_worldflaglist;
1125 if (bot.team != f.team)
1127 f = f.ctf_worldflagnext;
1132 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1140 FOR_EACH_PLAYER(head)
1142 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1145 if(vlen(head.origin - org) < tc_radius)
1152 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1155 head = ctf_worldflaglist;
1158 if (self.team == head.team)
1160 head = head.ctf_worldflagnext;
1163 navigation_routerating(head, ratingscale, 10000);
1166 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1169 head = ctf_worldflaglist;
1172 if (self.team == head.team)
1174 head = head.ctf_worldflagnext;
1179 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1182 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1185 head = ctf_worldflaglist;
1188 if (self.team != head.team)
1190 head = head.ctf_worldflagnext;
1193 navigation_routerating(head, ratingscale, 10000);
1196 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1198 if (!bot_waypoints_for_items)
1200 havocbot_goalrating_ctf_enemyflag(ratingscale);
1206 head = havocbot_ctf_find_enemy_flag(self);
1211 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1214 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1218 mf = havocbot_ctf_find_flag(self);
1220 if(mf.ctf_status == FLAG_BASE)
1224 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1227 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1230 head = ctf_worldflaglist;
1233 // flag is out in the field
1234 if(head.ctf_status != FLAG_BASE)
1235 if(head.tag_entity==world) // dropped
1239 if(vlen(org-head.move_origin)<df_radius)
1240 navigation_routerating(head, ratingscale, 10000);
1243 navigation_routerating(head, ratingscale, 10000);
1246 head = head.ctf_worldflagnext;
1250 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1254 head = findchainfloat(bot_pickup, true);
1257 // gather health and armor only
1259 if (head.health || head.armorvalue)
1260 if (vlen(head.origin - org) < sradius)
1262 // get the value of the item
1263 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1265 navigation_routerating(head, t * ratingscale, 500);
1271 void havocbot_ctf_reset_role(entity bot)
1273 float cdefense, cmiddle, coffense;
1274 entity mf, ef, head;
1277 if(bot.deadflag != DEAD_NO)
1280 if(vlen(havocbot_ctf_middlepoint)==0)
1281 havocbot_calculate_middlepoint();
1284 if (bot.flagcarried)
1286 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1290 mf = havocbot_ctf_find_flag(bot);
1291 ef = havocbot_ctf_find_enemy_flag(bot);
1293 // Retrieve stolen flag
1294 if(mf.ctf_status!=FLAG_BASE)
1296 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1300 // If enemy flag is taken go to the middle to intercept pursuers
1301 if(ef.ctf_status!=FLAG_BASE)
1303 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1307 // if there is only me on the team switch to offense
1309 FOR_EACH_PLAYER(head)
1310 if(SAME_TEAM(head, bot))
1315 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1319 // Evaluate best position to take
1320 // Count mates on middle position
1321 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1323 // Count mates on defense position
1324 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1326 // Count mates on offense position
1327 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1329 if(cdefense<=coffense)
1330 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1331 else if(coffense<=cmiddle)
1332 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1334 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1337 void havocbot_role_ctf_carrier()
1339 if(self.deadflag != DEAD_NO)
1341 havocbot_ctf_reset_role(self);
1345 if (self.flagcarried == world)
1347 havocbot_ctf_reset_role(self);
1351 if (self.bot_strategytime < time)
1353 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1355 navigation_goalrating_start();
1356 havocbot_goalrating_ctf_ourbase(50000);
1359 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1361 navigation_goalrating_end();
1363 if (self.navigation_hasgoals)
1364 self.havocbot_cantfindflag = time + 10;
1365 else if (time > self.havocbot_cantfindflag)
1367 // Can't navigate to my own base, suicide!
1368 // TODO: drop it and wander around
1369 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1375 void havocbot_role_ctf_escort()
1379 if(self.deadflag != DEAD_NO)
1381 havocbot_ctf_reset_role(self);
1385 if (self.flagcarried)
1387 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1391 // If enemy flag is back on the base switch to previous role
1392 ef = havocbot_ctf_find_enemy_flag(self);
1393 if(ef.ctf_status==FLAG_BASE)
1395 self.havocbot_role = self.havocbot_previous_role;
1396 self.havocbot_role_timeout = 0;
1400 // If the flag carrier reached the base switch to defense
1401 mf = havocbot_ctf_find_flag(self);
1402 if(mf.ctf_status!=FLAG_BASE)
1403 if(vlen(ef.move_origin - mf.dropped_origin) < 300)
1405 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1409 // Set the role timeout if necessary
1410 if (!self.havocbot_role_timeout)
1412 self.havocbot_role_timeout = time + random() * 30 + 60;
1415 // If nothing happened just switch to previous role
1416 if (time > self.havocbot_role_timeout)
1418 self.havocbot_role = self.havocbot_previous_role;
1419 self.havocbot_role_timeout = 0;
1423 // Chase the flag carrier
1424 if (self.bot_strategytime < time)
1426 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1427 navigation_goalrating_start();
1428 havocbot_goalrating_ctf_enemyflag(30000);
1429 havocbot_goalrating_ctf_ourstolenflag(40000);
1430 havocbot_goalrating_items(10000, self.origin, 10000);
1431 navigation_goalrating_end();
1435 void havocbot_role_ctf_offense()
1440 if(self.deadflag != DEAD_NO)
1442 havocbot_ctf_reset_role(self);
1446 if (self.flagcarried)
1448 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1453 mf = havocbot_ctf_find_flag(self);
1454 ef = havocbot_ctf_find_enemy_flag(self);
1457 if(mf.ctf_status!=FLAG_BASE)
1460 pos = mf.tag_entity.origin;
1462 pos = mf.move_origin;
1464 // Try to get it if closer than the enemy base
1465 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1467 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1472 // Escort flag carrier
1473 if(ef.ctf_status!=FLAG_BASE)
1476 pos = ef.tag_entity.origin;
1478 pos = ef.move_origin;
1480 if(vlen(pos-mf.dropped_origin)>700)
1482 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1487 // About to fail, switch to middlefield
1490 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1494 // Set the role timeout if necessary
1495 if (!self.havocbot_role_timeout)
1496 self.havocbot_role_timeout = time + 120;
1498 if (time > self.havocbot_role_timeout)
1500 havocbot_ctf_reset_role(self);
1504 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_enemybase(20000);
1510 havocbot_goalrating_items(5000, self.origin, 1000);
1511 havocbot_goalrating_items(1000, self.origin, 10000);
1512 navigation_goalrating_end();
1516 // Retriever (temporary role):
1517 void havocbot_role_ctf_retriever()
1521 if(self.deadflag != DEAD_NO)
1523 havocbot_ctf_reset_role(self);
1527 if (self.flagcarried)
1529 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1533 // If flag is back on the base switch to previous role
1534 mf = havocbot_ctf_find_flag(self);
1535 if(mf.ctf_status==FLAG_BASE)
1537 havocbot_ctf_reset_role(self);
1541 if (!self.havocbot_role_timeout)
1542 self.havocbot_role_timeout = time + 20;
1544 if (time > self.havocbot_role_timeout)
1546 havocbot_ctf_reset_role(self);
1550 if (self.bot_strategytime < time)
1555 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1556 navigation_goalrating_start();
1557 havocbot_goalrating_ctf_ourstolenflag(50000);
1558 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1559 havocbot_goalrating_ctf_enemybase(30000);
1560 havocbot_goalrating_items(500, self.origin, rt_radius);
1561 navigation_goalrating_end();
1565 void havocbot_role_ctf_middle()
1569 if(self.deadflag != DEAD_NO)
1571 havocbot_ctf_reset_role(self);
1575 if (self.flagcarried)
1577 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1581 mf = havocbot_ctf_find_flag(self);
1582 if(mf.ctf_status!=FLAG_BASE)
1584 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1588 if (!self.havocbot_role_timeout)
1589 self.havocbot_role_timeout = time + 10;
1591 if (time > self.havocbot_role_timeout)
1593 havocbot_ctf_reset_role(self);
1597 if (self.bot_strategytime < time)
1601 org = havocbot_ctf_middlepoint;
1602 org.z = self.origin.z;
1604 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1605 navigation_goalrating_start();
1606 havocbot_goalrating_ctf_ourstolenflag(50000);
1607 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1608 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1609 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1610 havocbot_goalrating_items(2500, self.origin, 10000);
1611 havocbot_goalrating_ctf_enemybase(2500);
1612 navigation_goalrating_end();
1616 void havocbot_role_ctf_defense()
1620 if(self.deadflag != DEAD_NO)
1622 havocbot_ctf_reset_role(self);
1626 if (self.flagcarried)
1628 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1632 // If own flag was captured
1633 mf = havocbot_ctf_find_flag(self);
1634 if(mf.ctf_status!=FLAG_BASE)
1636 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1640 if (!self.havocbot_role_timeout)
1641 self.havocbot_role_timeout = time + 30;
1643 if (time > self.havocbot_role_timeout)
1645 havocbot_ctf_reset_role(self);
1648 if (self.bot_strategytime < time)
1653 org = mf.dropped_origin;
1654 mp_radius = havocbot_ctf_middlepoint_radius;
1656 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1657 navigation_goalrating_start();
1659 // if enemies are closer to our base, go there
1660 entity head, closestplayer = world;
1661 float distance, bestdistance = 10000;
1662 FOR_EACH_PLAYER(head)
1664 if(head.deadflag!=DEAD_NO)
1667 distance = vlen(org - head.origin);
1668 if(distance<bestdistance)
1670 closestplayer = head;
1671 bestdistance = distance;
1676 if(DIFF_TEAM(closestplayer, self))
1677 if(vlen(org - self.origin)>1000)
1678 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1679 havocbot_goalrating_ctf_ourbase(30000);
1681 havocbot_goalrating_ctf_ourstolenflag(20000);
1682 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1683 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1684 havocbot_goalrating_items(10000, org, mp_radius);
1685 havocbot_goalrating_items(5000, self.origin, 10000);
1686 navigation_goalrating_end();
1690 void havocbot_role_ctf_setrole(entity bot, float role)
1692 dprint(strcat(bot.netname," switched to "));
1695 case HAVOCBOT_CTF_ROLE_CARRIER:
1697 bot.havocbot_role = havocbot_role_ctf_carrier;
1698 bot.havocbot_role_timeout = 0;
1699 bot.havocbot_cantfindflag = time + 10;
1700 bot.bot_strategytime = 0;
1702 case HAVOCBOT_CTF_ROLE_DEFENSE:
1704 bot.havocbot_role = havocbot_role_ctf_defense;
1705 bot.havocbot_role_timeout = 0;
1707 case HAVOCBOT_CTF_ROLE_MIDDLE:
1709 bot.havocbot_role = havocbot_role_ctf_middle;
1710 bot.havocbot_role_timeout = 0;
1712 case HAVOCBOT_CTF_ROLE_OFFENSE:
1714 bot.havocbot_role = havocbot_role_ctf_offense;
1715 bot.havocbot_role_timeout = 0;
1717 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1718 dprint("retriever");
1719 bot.havocbot_previous_role = bot.havocbot_role;
1720 bot.havocbot_role = havocbot_role_ctf_retriever;
1721 bot.havocbot_role_timeout = time + 10;
1722 bot.bot_strategytime = 0;
1724 case HAVOCBOT_CTF_ROLE_ESCORT:
1726 bot.havocbot_previous_role = bot.havocbot_role;
1727 bot.havocbot_role = havocbot_role_ctf_escort;
1728 bot.havocbot_role_timeout = time + 30;
1729 bot.bot_strategytime = 0;
1740 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1744 // initially clear items so they can be set as necessary later.
1745 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1746 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1748 // scan through all the flags and notify the client about them
1749 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1751 switch(flag.ctf_status)
1756 if((flag.owner == self) || (flag.pass_sender == self))
1757 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1759 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1764 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1770 // item for stopping players from capturing the flag too often
1771 if(self.ctf_captureshielded)
1772 self.items |= IT_CTF_SHIELDED;
1774 // update the health of the flag carrier waypointsprite
1775 if(self.wps_flagcarrier)
1776 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1781 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1783 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1785 if(frag_target == frag_attacker) // damage done to yourself
1787 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1788 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1790 else // damage done to everyone else
1792 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1793 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1796 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1798 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)))
1799 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1801 frag_target.wps_helpme_time = time;
1802 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1804 // todo: add notification for when flag carrier needs help?
1809 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1811 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1813 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1814 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1817 if(frag_target.flagcarried)
1818 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1823 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1826 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1829 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1831 entity flag; // temporary entity for the search method
1833 if(self.flagcarried)
1834 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1836 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1838 if(flag.pass_sender == self) { flag.pass_sender = world; }
1839 if(flag.pass_target == self) { flag.pass_target = world; }
1840 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1846 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1848 if(self.flagcarried)
1849 if(!autocvar_g_ctf_portalteleport)
1850 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1855 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1857 if(MUTATOR_RETURNVALUE || gameover) { return false; }
1859 entity player = self;
1861 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1863 // pass the flag to a team mate
1864 if(autocvar_g_ctf_pass)
1866 entity head, closest_target = world;
1867 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
1869 while(head) // find the closest acceptable target to pass to
1871 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1872 if(head != player && SAME_TEAM(head, player))
1873 if(!head.speedrunning && !head.vehicle)
1875 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1876 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1877 vector passer_center = CENTER_OR_VIEWOFS(player);
1879 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1881 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1883 if(IS_BOT_CLIENT(head))
1885 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1886 ctf_Handle_Throw(head, player, DROP_PASS);
1890 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1891 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1893 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1896 else if(player.flagcarried)
1900 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1901 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1902 { closest_target = head; }
1904 else { closest_target = head; }
1911 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
1914 // throw the flag in front of you
1915 if(autocvar_g_ctf_throw && player.flagcarried)
1917 if(player.throw_count == -1)
1919 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1921 player.throw_prevtime = time;
1922 player.throw_count = 1;
1923 ctf_Handle_Throw(player, world, DROP_THROW);
1928 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1934 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1935 else { player.throw_count += 1; }
1936 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1938 player.throw_prevtime = time;
1939 ctf_Handle_Throw(player, world, DROP_THROW);
1948 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1950 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1952 self.wps_helpme_time = time;
1953 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1955 else // create a normal help me waypointsprite
1957 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');
1958 WaypointSprite_Ping(self.wps_helpme);
1964 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1966 if(vh_player.flagcarried)
1968 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1970 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1974 setattachment(vh_player.flagcarried, vh_vehicle, "");
1975 vh_player.flagcarried.move_origin = VEHICLE_FLAG_OFFSET;
1976 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1977 //vh_player.flagcarried.move_angles = '0 0 0';
1985 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1987 if(vh_player.flagcarried)
1989 setattachment(vh_player.flagcarried, vh_player, "");
1990 vh_player.flagcarried.move_origin = VEHICLE_FLAG_OFFSET;
1991 vh_player.flagcarried.scale = FLAG_SCALE;
1992 vh_player.flagcarried.move_angles = '0 0 0';
1999 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2001 if(self.flagcarried)
2003 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
2004 ctf_RespawnFlag(self.flagcarried);
2011 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2013 entity flag; // temporary entity for the search method
2015 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2017 switch(flag.ctf_status)
2022 // lock the flag, game is over
2023 flag.move_movetype = MOVETYPE_NONE;
2024 flag.takedamage = DAMAGE_NO;
2025 flag.solid = SOLID_NOT;
2026 flag.nextthink = false; // stop thinking
2028 //dprint("stopping the ", flag.netname, " from moving.\n");
2036 // do nothing for these flags
2045 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2047 havocbot_ctf_reset_role(self);
2056 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2057 CTF Starting point for a player in team one (Red).
2058 Keys: "angle" viewing angle when spawning. */
2059 void spawnfunc_info_player_team1()
2061 if(g_assault) { remove(self); return; }
2063 self.team = NUM_TEAM_1; // red
2064 spawnfunc_info_player_deathmatch();
2068 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2069 CTF Starting point for a player in team two (Blue).
2070 Keys: "angle" viewing angle when spawning. */
2071 void spawnfunc_info_player_team2()
2073 if(g_assault) { remove(self); return; }
2075 self.team = NUM_TEAM_2; // blue
2076 spawnfunc_info_player_deathmatch();
2079 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2080 CTF Starting point for a player in team three (Yellow).
2081 Keys: "angle" viewing angle when spawning. */
2082 void spawnfunc_info_player_team3()
2084 if(g_assault) { remove(self); return; }
2086 self.team = NUM_TEAM_3; // yellow
2087 spawnfunc_info_player_deathmatch();
2091 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2092 CTF Starting point for a player in team four (Purple).
2093 Keys: "angle" viewing angle when spawning. */
2094 void spawnfunc_info_player_team4()
2096 if(g_assault) { remove(self); return; }
2098 self.team = NUM_TEAM_4; // purple
2099 spawnfunc_info_player_deathmatch();
2102 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2103 CTF flag for team one (Red).
2105 "angle" Angle the flag will point (minus 90 degrees)...
2106 "model" model to use, note this needs red and blue as skins 0 and 1...
2107 "noise" sound played when flag is picked up...
2108 "noise1" sound played when flag is returned by a teammate...
2109 "noise2" sound played when flag is captured...
2110 "noise3" sound played when flag is lost in the field and respawns itself...
2111 "noise4" sound played when flag is dropped by a player...
2112 "noise5" sound played when flag touches the ground... */
2113 void spawnfunc_item_flag_team1()
2115 if(!g_ctf) { remove(self); return; }
2117 ctf_FlagSetup(1, self); // 1 = red
2120 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2121 CTF flag for team two (Blue).
2123 "angle" Angle the flag will point (minus 90 degrees)...
2124 "model" model to use, note this needs red and blue as skins 0 and 1...
2125 "noise" sound played when flag is picked up...
2126 "noise1" sound played when flag is returned by a teammate...
2127 "noise2" sound played when flag is captured...
2128 "noise3" sound played when flag is lost in the field and respawns itself...
2129 "noise4" sound played when flag is dropped by a player...
2130 "noise5" sound played when flag touches the ground... */
2131 void spawnfunc_item_flag_team2()
2133 if(!g_ctf) { remove(self); return; }
2135 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2138 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2139 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2140 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.
2142 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2143 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2144 void spawnfunc_ctf_team()
2146 if(!g_ctf) { remove(self); return; }
2148 self.classname = "ctf_team";
2149 self.team = self.cnt + 1;
2152 // compatibility for quake maps
2153 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2154 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2155 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2156 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2157 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2158 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2166 void ctf_ScoreRules()
2168 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, true);
2169 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2170 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2171 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2172 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2173 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2174 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2175 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2176 ScoreRules_basics_end();
2179 // code from here on is just to support maps that don't have flag and team entities
2180 void ctf_SpawnTeam (string teamname, float teamcolor)
2185 self.classname = "ctf_team";
2186 self.netname = teamname;
2187 self.cnt = teamcolor;
2189 spawnfunc_ctf_team();
2194 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2196 // if no teams are found, spawn defaults
2197 if(find(world, classname, "ctf_team") == world)
2199 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2200 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2201 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2207 void ctf_Initialize()
2209 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2211 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2212 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2213 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2215 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2219 MUTATOR_DEFINITION(gamemode_ctf)
2221 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2222 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2223 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2224 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2225 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2226 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2227 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2228 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2229 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2230 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2231 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2232 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2233 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2234 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2238 if(time > 1) // game loads at time 1
2239 error("This is a game type and it cannot be added at runtime.");
2243 MUTATOR_ONROLLBACK_OR_REMOVE
2245 // we actually cannot roll back ctf_Initialize here
2246 // BUT: we don't need to! If this gets called, adding always
2252 print("This is a game type and it cannot be removed at runtime.");