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 ang; // angle between shotdir and h
90 float h; // hypotenuse, which is the distance between attacker to head
91 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
93 h = vlen(head_center - passer_center);
94 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
97 vector nearest_on_line = (passer_center + a * v_forward);
98 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
100 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
101 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
103 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
108 else { return true; }
112 // =======================
113 // CaptureShield Functions
114 // =======================
116 float ctf_CaptureShield_CheckStatus(entity p)
120 float players_worseeq, players_total;
122 if(ctf_captureshield_max_ratio <= 0)
125 s = PlayerScore_Add(p, SP_SCORE, 0);
126 if(s >= -ctf_captureshield_min_negscore)
129 players_total = players_worseeq = 0;
134 se = PlayerScore_Add(e, SP_SCORE, 0);
140 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
141 // use this rule here
143 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
149 void ctf_CaptureShield_Update(entity player, float wanted_status)
151 float updated_status = ctf_CaptureShield_CheckStatus(player);
152 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
154 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
155 player.ctf_captureshielded = updated_status;
159 float ctf_CaptureShield_Customize()
161 if(!other.ctf_captureshielded) { return false; }
162 if(SAME_TEAM(self, other)) { return false; }
167 void ctf_CaptureShield_Touch()
169 if(!other.ctf_captureshielded) { return; }
170 if(SAME_TEAM(self, other)) { return; }
172 vector mymid = (self.absmin + self.absmax) * 0.5;
173 vector othermid = (other.absmin + other.absmax) * 0.5;
175 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
176 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
179 void ctf_CaptureShield_Spawn(entity flag)
181 entity shield = spawn();
184 shield.team = self.team;
185 shield.touch = ctf_CaptureShield_Touch;
186 shield.customizeentityforclient = ctf_CaptureShield_Customize;
187 shield.classname = "ctf_captureshield";
188 shield.effects = EF_ADDITIVE;
189 shield.movetype = MOVETYPE_NOCLIP;
190 shield.solid = SOLID_TRIGGER;
191 shield.avelocity = '7 0 11';
194 setorigin(shield, self.origin);
195 setmodel(shield, "models/ctf/shield.md3");
196 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
200 // ====================
201 // Drop/Pass/Throw Code
202 // ====================
204 void ctf_Handle_Drop(entity flag, entity player, float droptype)
207 player = (player ? player : flag.pass_sender);
210 flag.move_movetype = MOVETYPE_TOSS;
211 flag.takedamage = DAMAGE_YES;
212 flag.move_angles = '0 0 0';
213 flag.health = flag.max_flag_health;
214 flag.ctf_droptime = time;
215 flag.ctf_dropper = player;
216 flag.ctf_status = FLAG_DROPPED;
218 // messages and sounds
219 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
220 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
221 ctf_EventLog("dropped", player.team, player);
224 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
225 PlayerScore_Add(player, SP_CTF_DROPS, 1);
228 if(autocvar_g_ctf_flag_dropped_waypoint)
229 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));
231 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
233 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
234 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
237 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
239 if(droptype == DROP_PASS)
241 flag.pass_distance = 0;
242 flag.pass_sender = world;
243 flag.pass_target = world;
247 void ctf_Handle_Retrieve(entity flag, entity player)
249 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
250 entity sender = flag.pass_sender;
252 // transfer flag to player
254 flag.owner.flagcarried = flag;
259 setattachment(flag, player.vehicle, "");
260 flag.move_origin = VEHICLE_FLAG_OFFSET;
261 flag.scale = VEHICLE_FLAG_SCALE;
265 setattachment(flag, player, "");
266 flag.move_origin = VEHICLE_FLAG_OFFSET;
268 flag.move_movetype = MOVETYPE_NONE;
269 flag.takedamage = DAMAGE_NO;
270 flag.solid = SOLID_NOT;
271 flag.move_angles = '0 0 0';
272 flag.ctf_status = FLAG_CARRY;
274 // messages and sounds
275 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
276 ctf_EventLog("receive", flag.team, player);
278 FOR_EACH_REALPLAYER(tmp_player)
280 if(tmp_player == sender)
281 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
282 else if(tmp_player == player)
283 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
284 else if(SAME_TEAM(tmp_player, sender))
285 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
288 // create new waypoint
289 ctf_FlagcarrierWaypoints(player);
291 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
292 player.throw_antispam = sender.throw_antispam;
294 flag.pass_distance = 0;
295 flag.pass_sender = world;
296 flag.pass_target = world;
299 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
301 entity flag = player.flagcarried;
302 vector targ_origin, flag_velocity;
304 if(!flag) { return; }
305 if((droptype == DROP_PASS) && !receiver) { return; }
307 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
310 setattachment(flag, world, "");
311 flag.move_origin = player.origin + FLAG_DROP_OFFSET;
312 flag.owner.flagcarried = world;
314 flag.solid = SOLID_TRIGGER;
315 flag.ctf_dropper = player;
316 flag.ctf_droptime = time;
318 flag.move_flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
319 flag.flags = flag.move_flags;
326 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
327 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
328 WarpZone_RefSys_Copy(flag, receiver);
329 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
330 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
332 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
333 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
336 flag.move_movetype = MOVETYPE_FLY;
337 flag.takedamage = DAMAGE_NO;
338 flag.pass_sender = player;
339 flag.pass_target = receiver;
340 flag.ctf_status = FLAG_PASSING;
343 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
344 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
345 ctf_EventLog("pass", flag.team, player);
351 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'));
353 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)));
354 flag.move_velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
355 ctf_Handle_Drop(flag, player, droptype);
361 flag.move_velocity = '0 0 0'; // do nothing
368 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);
369 ctf_Handle_Drop(flag, player, droptype);
374 // kill old waypointsprite
375 WaypointSprite_Ping(player.wps_flagcarrier);
376 WaypointSprite_Kill(player.wps_flagcarrier);
378 if(player.wps_enemyflagcarrier)
379 WaypointSprite_Kill(player.wps_enemyflagcarrier);
382 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
390 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
392 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
393 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
394 float old_time, new_time;
396 if (!player) { return; } // without someone to give the reward to, we can't possibly cap
398 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
400 // messages and sounds
401 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
402 ctf_CaptureRecord(enemy_flag, player);
403 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
407 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
408 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
413 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
414 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
416 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
417 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
418 if(!old_time || new_time < old_time)
419 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
422 pointparticles(particleeffectnum(flag.capeffect), flag.move_origin, '0 0 0', 1);
423 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.move_origin - '0 0 15', -0.8, 0, 1);
426 if(capturetype == CAPTURE_NORMAL)
428 WaypointSprite_Kill(player.wps_flagcarrier);
429 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
431 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
432 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
436 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
437 ctf_RespawnFlag(enemy_flag);
440 void ctf_Handle_Return(entity flag, entity player)
442 // messages and sounds
443 if(player.flags & FL_MONSTER)
445 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
449 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
450 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
452 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
453 ctf_EventLog("return", flag.team, player);
456 if(IS_PLAYER(player))
458 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
459 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
461 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
464 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
468 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
469 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
470 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
474 ctf_RespawnFlag(flag);
477 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
480 float pickup_dropped_score; // used to calculate dropped pickup score
482 // attach the flag to the player
484 player.flagcarried = flag;
487 setattachment(flag, player.vehicle, "");
488 flag.move_origin = VEHICLE_FLAG_OFFSET;
489 flag.scale = VEHICLE_FLAG_SCALE;
493 setattachment(flag, player, "");
494 flag.move_origin = FLAG_CARRY_OFFSET;
498 flag.move_movetype = MOVETYPE_NONE;
499 flag.takedamage = DAMAGE_NO;
500 flag.solid = SOLID_NOT;
501 flag.move_angles = '0 0 0';
502 flag.ctf_status = FLAG_CARRY;
506 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
507 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
511 // messages and sounds
512 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
513 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
514 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
516 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
517 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
519 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
522 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
523 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
528 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
529 ctf_EventLog("steal", flag.team, player);
535 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);
536 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);
537 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
538 PlayerTeamScore_AddScore(player, pickup_dropped_score);
539 ctf_EventLog("pickup", flag.team, player);
547 if(pickuptype == PICKUP_BASE)
549 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
550 if((player.speedrunning) && (ctf_captimerecord))
551 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
555 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
558 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
559 ctf_FlagcarrierWaypoints(player);
560 WaypointSprite_Ping(player.wps_flagcarrier);
564 // ===================
565 // Main Flag Functions
566 // ===================
568 void ctf_CheckFlagReturn(entity flag, float returntype)
570 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
572 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
574 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
578 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
579 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
580 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
581 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
585 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
587 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
588 ctf_EventLog("returned", flag.team, world);
589 ctf_RespawnFlag(flag);
594 void ctf_CheckStalemate(void)
597 float stale_red_flags = 0, stale_blue_flags = 0;
600 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
602 // build list of stale flags
603 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
605 if(autocvar_g_ctf_stalemate)
606 if(tmp_entity.ctf_status != FLAG_BASE)
607 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
609 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
610 ctf_staleflaglist = tmp_entity;
612 switch(tmp_entity.team)
614 case NUM_TEAM_1: ++stale_red_flags; break;
615 case NUM_TEAM_2: ++stale_blue_flags; break;
620 if(stale_red_flags && stale_blue_flags)
621 ctf_stalemate = true;
622 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
623 { ctf_stalemate = false; wpforenemy_announced = false; }
624 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
625 { ctf_stalemate = false; wpforenemy_announced = false; }
627 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
630 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
632 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
633 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));
636 if (!wpforenemy_announced)
638 FOR_EACH_REALPLAYER(tmp_entity)
639 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
641 wpforenemy_announced = true;
646 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
648 self.move_velocity = self.velocity;
649 if(ITEM_DAMAGE_NEEDKILL(deathtype))
651 // automatically kill the flag and return it
653 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
656 if(autocvar_g_ctf_flag_return_damage)
658 // reduce health and check if it should be returned
659 self.health = self.health - damage;
660 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
665 void ctf_FlagUpdate()
671 if(self == ctf_worldflaglist) // only for the first flag
672 FOR_EACH_CLIENT(tmp_entity)
673 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
676 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
677 dprint("wtf the flag got squashed?\n");
678 tracebox(self.move_origin, FLAG_MIN, FLAG_MAX, self.move_origin, MOVE_NOMONSTERS, self);
679 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
680 setsize(self, FLAG_MIN, FLAG_MAX); }
682 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
686 self.move_angles = '0 0 0';
694 switch(self.ctf_status)
698 if(autocvar_g_ctf_dropped_capture_radius)
700 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
701 if(tmp_entity.ctf_status == FLAG_DROPPED)
702 if(vlen(self.move_origin - tmp_entity.move_origin) < autocvar_g_ctf_dropped_capture_radius)
703 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
704 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
711 if(autocvar_g_ctf_flag_dropped_floatinwater)
713 vector midpoint = ((self.absmin + self.absmax) * 0.5);
714 if(pointcontents(midpoint) == CONTENT_WATER)
716 self.move_velocity = self.move_velocity * 0.5;
718 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
719 { self.move_velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
721 { self.move_movetype = MOVETYPE_FLY; }
723 else if(self.move_movetype == MOVETYPE_FLY) { self.move_movetype = MOVETYPE_TOSS; }
725 if(autocvar_g_ctf_flag_return_dropped)
727 if((vlen(self.move_origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
730 ctf_CheckFlagReturn(self, RETURN_DROPPED);
734 if(autocvar_g_ctf_flag_return_time)
736 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
737 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
745 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
748 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
752 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
756 if(autocvar_g_ctf_stalemate)
758 if(time >= wpforenemy_nextthink)
760 ctf_CheckStalemate();
761 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
769 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
770 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
771 WarpZone_TraceLine(self.move_origin, targ_origin, MOVE_NOMONSTERS, self);
773 if((self.pass_target == world)
774 || (self.pass_target.deadflag != DEAD_NO)
775 || (self.pass_target.flagcarried)
776 || (vlen(self.move_origin - targ_origin) > autocvar_g_ctf_pass_radius)
777 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
778 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
780 // give up, pass failed
781 ctf_Handle_Drop(self, world, DROP_PASS);
785 // still a viable target, go for it
786 ctf_CalculatePassVelocity(self, targ_origin, self.move_origin, true);
791 default: // this should never happen
793 dprint("ctf_FlagThink(): Flag exists with no status?\n");
801 self.nextthink = time;
803 if(time >= self.ctf_thinkrate)
805 self.ctf_thinkrate = time + FLAG_THINKRATE;
809 //Movetype_Physics_NoMatchServer();
810 Movetype_Physics_MatchTicrate(sys_frametime, 0);
815 if(gameover) { return; }
816 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
818 entity toucher = other;
819 float is_not_monster = (!(toucher.flags & FL_MONSTER));
821 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
822 if(ITEM_TOUCH_NEEDKILL())
825 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
829 // special touch behaviors
830 if(toucher.frozen) { return; }
831 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
833 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
834 toucher = toucher.owner; // the player is actually the vehicle owner, not other
836 return; // do nothing
838 else if(toucher.flags & FL_MONSTER)
840 if(!autocvar_g_ctf_allow_monster_touch)
841 return; // do nothing
843 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
845 if(time > self.wait) // if we haven't in a while, play a sound/effect
847 pointparticles(particleeffectnum(self.toucheffect), self.move_origin, '0 0 0', 1);
848 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
849 self.wait = time + FLAG_TOUCHRATE;
853 else if(toucher.deadflag != DEAD_NO) { return; }
855 switch(self.ctf_status)
859 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
860 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
861 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
862 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
868 if(SAME_TEAM(toucher, self))
869 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
870 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
871 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
877 dprint("Someone touched a flag even though it was being carried?\n");
883 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
885 if(DIFF_TEAM(toucher, self.pass_sender))
886 ctf_Handle_Return(self, toucher);
888 ctf_Handle_Retrieve(self, toucher);
896 void ctf_RespawnFlag(entity flag)
898 // check for flag respawn being called twice in a row
899 if(flag.last_respawn > time - 0.5)
900 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
902 flag.last_respawn = time;
904 // reset the player (if there is one)
905 if((flag.owner) && (flag.owner.flagcarried == flag))
907 if(flag.owner.wps_enemyflagcarrier)
908 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
910 WaypointSprite_Kill(flag.wps_flagcarrier);
912 flag.owner.flagcarried = world;
914 if(flag.speedrunning)
915 ctf_FakeTimeLimit(flag.owner, -1);
918 if((flag.owner) && (flag.owner.vehicle))
919 flag.scale = FLAG_SCALE;
921 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
922 { WaypointSprite_Kill(flag.wps_flagdropped); }
925 setattachment(flag, world, "");
926 flag.move_origin = flag.ctf_spawnorigin;
928 flag.move_movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
929 flag.takedamage = DAMAGE_NO;
930 flag.health = flag.max_flag_health;
931 flag.solid = SOLID_TRIGGER;
932 flag.move_velocity = '0 0 0';
933 flag.move_angles = flag.mangle;
934 flag.move_flags = FL_ITEM | FL_NOTARGET;
935 flag.flags = flag.move_flags;
937 flag.ctf_status = FLAG_BASE;
939 flag.pass_distance = 0;
940 flag.pass_sender = world;
941 flag.pass_target = world;
942 flag.ctf_dropper = world;
943 flag.ctf_pickuptime = 0;
944 flag.ctf_droptime = 0;
946 ctf_CheckStalemate();
952 if(IS_PLAYER(self.owner))
953 ctf_Handle_Throw(self.owner, world, DROP_RESET);
955 ctf_RespawnFlag(self);
958 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
961 waypoint_spawnforitem_force(self, self.origin);
962 self.nearestwaypointtimeout = 0; // activate waypointing again
963 self.bot_basewaypoint = self.nearestwaypoint;
966 // move_origin isnt accessible just yet
967 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
968 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
970 // captureshield setup
971 ctf_CaptureShield_Spawn(self);
973 self.move_origin = self.origin;
974 self.move_angles = self.angles;
975 self.move_velocity = self.velocity;
978 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
981 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.
982 self = flag; // for later usage with droptofloor()
985 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
986 ctf_worldflaglist = flag;
988 setattachment(flag, world, "");
990 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
991 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
992 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
993 flag.classname = "item_flag_team";
994 flag.target = "###item###"; // wut?
995 flag.move_flags = FL_ITEM | FL_NOTARGET;
996 flag.flags = flag.move_flags;
997 flag.solid = SOLID_TRIGGER;
998 flag.takedamage = DAMAGE_NO;
999 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1000 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1001 flag.health = flag.max_flag_health;
1002 flag.event_damage = ctf_FlagDamage;
1003 flag.pushable = true;
1004 flag.teleportable = TELEPORT_NORMAL;
1005 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1006 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1007 flag.move_velocity = '0 0 0';
1008 flag.mangle = flag.angles;
1009 flag.reset = ctf_Reset;
1010 flag.touch = ctf_FlagTouch;
1011 flag.move_touch = flag.touch;
1012 flag.think = ctf_FlagThink;
1013 flag.nextthink = time + FLAG_THINKRATE;
1014 flag.ctf_status = FLAG_BASE;
1015 flag.move_time = time;
1018 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
1019 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1020 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
1021 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
1022 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
1023 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
1026 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
1027 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
1028 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
1029 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.
1030 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1031 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1032 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1035 precache_sound(flag.snd_flag_taken);
1036 precache_sound(flag.snd_flag_returned);
1037 precache_sound(flag.snd_flag_capture);
1038 precache_sound(flag.snd_flag_respawn);
1039 precache_sound(flag.snd_flag_dropped);
1040 precache_sound(flag.snd_flag_touch);
1041 precache_sound(flag.snd_flag_pass);
1042 precache_model(flag.model);
1043 precache_model("models/ctf/shield.md3");
1044 precache_model("models/ctf/shockwavetransring.md3");
1047 setmodel(flag, flag.model); // precision set below
1048 setsize(flag, FLAG_MIN, FLAG_MAX);
1049 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1051 if(autocvar_g_ctf_flag_glowtrails)
1053 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1054 flag.glow_size = 25;
1055 flag.glow_trail = 1;
1058 flag.effects |= EF_LOWPRECISION;
1059 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1060 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1063 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1065 flag.dropped_origin = flag.origin;
1066 flag.noalign = true;
1067 flag.move_movetype = MOVETYPE_NONE;
1069 else // drop to floor, automatically find a platform and set that as spawn origin
1071 flag.noalign = false;
1074 flag.move_movetype = MOVETYPE_TOSS;
1077 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1085 // NOTE: LEGACY CODE, needs to be re-written!
1087 void havocbot_calculate_middlepoint()
1091 vector fo = '0 0 0';
1094 f = ctf_worldflaglist;
1099 f = f.ctf_worldflagnext;
1103 havocbot_ctf_middlepoint = s * (1.0 / n);
1104 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1108 entity havocbot_ctf_find_flag(entity bot)
1111 f = ctf_worldflaglist;
1114 if (bot.team == f.team)
1116 f = f.ctf_worldflagnext;
1121 entity havocbot_ctf_find_enemy_flag(entity bot)
1124 f = ctf_worldflaglist;
1127 if (bot.team != f.team)
1129 f = f.ctf_worldflagnext;
1134 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1142 FOR_EACH_PLAYER(head)
1144 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1147 if(vlen(head.origin - org) < tc_radius)
1154 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1157 head = ctf_worldflaglist;
1160 if (self.team == head.team)
1162 head = head.ctf_worldflagnext;
1165 navigation_routerating(head, ratingscale, 10000);
1168 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1171 head = ctf_worldflaglist;
1174 if (self.team == head.team)
1176 head = head.ctf_worldflagnext;
1181 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1184 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1187 head = ctf_worldflaglist;
1190 if (self.team != head.team)
1192 head = head.ctf_worldflagnext;
1195 navigation_routerating(head, ratingscale, 10000);
1198 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1200 if (!bot_waypoints_for_items)
1202 havocbot_goalrating_ctf_enemyflag(ratingscale);
1208 head = havocbot_ctf_find_enemy_flag(self);
1213 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1216 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1220 mf = havocbot_ctf_find_flag(self);
1222 if(mf.ctf_status == FLAG_BASE)
1226 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1229 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1232 head = ctf_worldflaglist;
1235 // flag is out in the field
1236 if(head.ctf_status != FLAG_BASE)
1237 if(head.tag_entity==world) // dropped
1241 if(vlen(org-head.move_origin)<df_radius)
1242 navigation_routerating(head, ratingscale, 10000);
1245 navigation_routerating(head, ratingscale, 10000);
1248 head = head.ctf_worldflagnext;
1252 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1256 head = findchainfloat(bot_pickup, true);
1259 // gather health and armor only
1261 if (head.health || head.armorvalue)
1262 if (vlen(head.origin - org) < sradius)
1264 // get the value of the item
1265 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1267 navigation_routerating(head, t * ratingscale, 500);
1273 void havocbot_ctf_reset_role(entity bot)
1275 float cdefense, cmiddle, coffense;
1276 entity mf, ef, head;
1279 if(bot.deadflag != DEAD_NO)
1282 if(vlen(havocbot_ctf_middlepoint)==0)
1283 havocbot_calculate_middlepoint();
1286 if (bot.flagcarried)
1288 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1292 mf = havocbot_ctf_find_flag(bot);
1293 ef = havocbot_ctf_find_enemy_flag(bot);
1295 // Retrieve stolen flag
1296 if(mf.ctf_status!=FLAG_BASE)
1298 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1302 // If enemy flag is taken go to the middle to intercept pursuers
1303 if(ef.ctf_status!=FLAG_BASE)
1305 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1309 // if there is only me on the team switch to offense
1311 FOR_EACH_PLAYER(head)
1312 if(SAME_TEAM(head, bot))
1317 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1321 // Evaluate best position to take
1322 // Count mates on middle position
1323 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1325 // Count mates on defense position
1326 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1328 // Count mates on offense position
1329 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1331 if(cdefense<=coffense)
1332 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1333 else if(coffense<=cmiddle)
1334 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1336 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1339 void havocbot_role_ctf_carrier()
1341 if(self.deadflag != DEAD_NO)
1343 havocbot_ctf_reset_role(self);
1347 if (self.flagcarried == world)
1349 havocbot_ctf_reset_role(self);
1353 if (self.bot_strategytime < time)
1355 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1357 navigation_goalrating_start();
1358 havocbot_goalrating_ctf_ourbase(50000);
1361 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1363 navigation_goalrating_end();
1365 if (self.navigation_hasgoals)
1366 self.havocbot_cantfindflag = time + 10;
1367 else if (time > self.havocbot_cantfindflag)
1369 // Can't navigate to my own base, suicide!
1370 // TODO: drop it and wander around
1371 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1377 void havocbot_role_ctf_escort()
1381 if(self.deadflag != DEAD_NO)
1383 havocbot_ctf_reset_role(self);
1387 if (self.flagcarried)
1389 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1393 // If enemy flag is back on the base switch to previous role
1394 ef = havocbot_ctf_find_enemy_flag(self);
1395 if(ef.ctf_status==FLAG_BASE)
1397 self.havocbot_role = self.havocbot_previous_role;
1398 self.havocbot_role_timeout = 0;
1402 // If the flag carrier reached the base switch to defense
1403 mf = havocbot_ctf_find_flag(self);
1404 if(mf.ctf_status!=FLAG_BASE)
1405 if(vlen(ef.move_origin - mf.dropped_origin) < 300)
1407 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1411 // Set the role timeout if necessary
1412 if (!self.havocbot_role_timeout)
1414 self.havocbot_role_timeout = time + random() * 30 + 60;
1417 // If nothing happened just switch to previous role
1418 if (time > self.havocbot_role_timeout)
1420 self.havocbot_role = self.havocbot_previous_role;
1421 self.havocbot_role_timeout = 0;
1425 // Chase the flag carrier
1426 if (self.bot_strategytime < time)
1428 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1429 navigation_goalrating_start();
1430 havocbot_goalrating_ctf_enemyflag(30000);
1431 havocbot_goalrating_ctf_ourstolenflag(40000);
1432 havocbot_goalrating_items(10000, self.origin, 10000);
1433 navigation_goalrating_end();
1437 void havocbot_role_ctf_offense()
1442 if(self.deadflag != DEAD_NO)
1444 havocbot_ctf_reset_role(self);
1448 if (self.flagcarried)
1450 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1455 mf = havocbot_ctf_find_flag(self);
1456 ef = havocbot_ctf_find_enemy_flag(self);
1459 if(mf.ctf_status!=FLAG_BASE)
1462 pos = mf.tag_entity.origin;
1464 pos = mf.move_origin;
1466 // Try to get it if closer than the enemy base
1467 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1469 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1474 // Escort flag carrier
1475 if(ef.ctf_status!=FLAG_BASE)
1478 pos = ef.tag_entity.origin;
1480 pos = ef.move_origin;
1482 if(vlen(pos-mf.dropped_origin)>700)
1484 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1489 // About to fail, switch to middlefield
1492 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1496 // Set the role timeout if necessary
1497 if (!self.havocbot_role_timeout)
1498 self.havocbot_role_timeout = time + 120;
1500 if (time > self.havocbot_role_timeout)
1502 havocbot_ctf_reset_role(self);
1506 if (self.bot_strategytime < time)
1508 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1509 navigation_goalrating_start();
1510 havocbot_goalrating_ctf_ourstolenflag(50000);
1511 havocbot_goalrating_ctf_enemybase(20000);
1512 havocbot_goalrating_items(5000, self.origin, 1000);
1513 havocbot_goalrating_items(1000, self.origin, 10000);
1514 navigation_goalrating_end();
1518 // Retriever (temporary role):
1519 void havocbot_role_ctf_retriever()
1523 if(self.deadflag != DEAD_NO)
1525 havocbot_ctf_reset_role(self);
1529 if (self.flagcarried)
1531 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1535 // If flag is back on the base switch to previous role
1536 mf = havocbot_ctf_find_flag(self);
1537 if(mf.ctf_status==FLAG_BASE)
1539 havocbot_ctf_reset_role(self);
1543 if (!self.havocbot_role_timeout)
1544 self.havocbot_role_timeout = time + 20;
1546 if (time > self.havocbot_role_timeout)
1548 havocbot_ctf_reset_role(self);
1552 if (self.bot_strategytime < time)
1557 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1558 navigation_goalrating_start();
1559 havocbot_goalrating_ctf_ourstolenflag(50000);
1560 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1561 havocbot_goalrating_ctf_enemybase(30000);
1562 havocbot_goalrating_items(500, self.origin, rt_radius);
1563 navigation_goalrating_end();
1567 void havocbot_role_ctf_middle()
1571 if(self.deadflag != DEAD_NO)
1573 havocbot_ctf_reset_role(self);
1577 if (self.flagcarried)
1579 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1583 mf = havocbot_ctf_find_flag(self);
1584 if(mf.ctf_status!=FLAG_BASE)
1586 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1590 if (!self.havocbot_role_timeout)
1591 self.havocbot_role_timeout = time + 10;
1593 if (time > self.havocbot_role_timeout)
1595 havocbot_ctf_reset_role(self);
1599 if (self.bot_strategytime < time)
1603 org = havocbot_ctf_middlepoint;
1604 org.z = self.origin.z;
1606 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1607 navigation_goalrating_start();
1608 havocbot_goalrating_ctf_ourstolenflag(50000);
1609 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1610 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1611 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1612 havocbot_goalrating_items(2500, self.origin, 10000);
1613 havocbot_goalrating_ctf_enemybase(2500);
1614 navigation_goalrating_end();
1618 void havocbot_role_ctf_defense()
1622 if(self.deadflag != DEAD_NO)
1624 havocbot_ctf_reset_role(self);
1628 if (self.flagcarried)
1630 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1634 // If own flag was captured
1635 mf = havocbot_ctf_find_flag(self);
1636 if(mf.ctf_status!=FLAG_BASE)
1638 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1642 if (!self.havocbot_role_timeout)
1643 self.havocbot_role_timeout = time + 30;
1645 if (time > self.havocbot_role_timeout)
1647 havocbot_ctf_reset_role(self);
1650 if (self.bot_strategytime < time)
1655 org = mf.dropped_origin;
1656 mp_radius = havocbot_ctf_middlepoint_radius;
1658 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1659 navigation_goalrating_start();
1661 // if enemies are closer to our base, go there
1662 entity head, closestplayer = world;
1663 float distance, bestdistance = 10000;
1664 FOR_EACH_PLAYER(head)
1666 if(head.deadflag!=DEAD_NO)
1669 distance = vlen(org - head.origin);
1670 if(distance<bestdistance)
1672 closestplayer = head;
1673 bestdistance = distance;
1678 if(DIFF_TEAM(closestplayer, self))
1679 if(vlen(org - self.origin)>1000)
1680 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1681 havocbot_goalrating_ctf_ourbase(30000);
1683 havocbot_goalrating_ctf_ourstolenflag(20000);
1684 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1685 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1686 havocbot_goalrating_items(10000, org, mp_radius);
1687 havocbot_goalrating_items(5000, self.origin, 10000);
1688 navigation_goalrating_end();
1692 void havocbot_role_ctf_setrole(entity bot, float role)
1694 dprint(strcat(bot.netname," switched to "));
1697 case HAVOCBOT_CTF_ROLE_CARRIER:
1699 bot.havocbot_role = havocbot_role_ctf_carrier;
1700 bot.havocbot_role_timeout = 0;
1701 bot.havocbot_cantfindflag = time + 10;
1702 bot.bot_strategytime = 0;
1704 case HAVOCBOT_CTF_ROLE_DEFENSE:
1706 bot.havocbot_role = havocbot_role_ctf_defense;
1707 bot.havocbot_role_timeout = 0;
1709 case HAVOCBOT_CTF_ROLE_MIDDLE:
1711 bot.havocbot_role = havocbot_role_ctf_middle;
1712 bot.havocbot_role_timeout = 0;
1714 case HAVOCBOT_CTF_ROLE_OFFENSE:
1716 bot.havocbot_role = havocbot_role_ctf_offense;
1717 bot.havocbot_role_timeout = 0;
1719 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1720 dprint("retriever");
1721 bot.havocbot_previous_role = bot.havocbot_role;
1722 bot.havocbot_role = havocbot_role_ctf_retriever;
1723 bot.havocbot_role_timeout = time + 10;
1724 bot.bot_strategytime = 0;
1726 case HAVOCBOT_CTF_ROLE_ESCORT:
1728 bot.havocbot_previous_role = bot.havocbot_role;
1729 bot.havocbot_role = havocbot_role_ctf_escort;
1730 bot.havocbot_role_timeout = time + 30;
1731 bot.bot_strategytime = 0;
1742 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1746 // initially clear items so they can be set as necessary later.
1747 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1748 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1750 // scan through all the flags and notify the client about them
1751 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1753 switch(flag.ctf_status)
1758 if((flag.owner == self) || (flag.pass_sender == self))
1759 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1761 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1766 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1772 // item for stopping players from capturing the flag too often
1773 if(self.ctf_captureshielded)
1774 self.items |= IT_CTF_SHIELDED;
1776 // update the health of the flag carrier waypointsprite
1777 if(self.wps_flagcarrier)
1778 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1783 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1785 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1787 if(frag_target == frag_attacker) // damage done to yourself
1789 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1790 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1792 else // damage done to everyone else
1794 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1795 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1798 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1800 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)))
1801 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1803 frag_target.wps_helpme_time = time;
1804 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1806 // todo: add notification for when flag carrier needs help?
1811 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1813 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1815 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1816 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1819 if(frag_target.flagcarried)
1820 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1825 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1828 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1831 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1833 entity flag; // temporary entity for the search method
1835 if(self.flagcarried)
1836 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1838 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1840 if(flag.pass_sender == self) { flag.pass_sender = world; }
1841 if(flag.pass_target == self) { flag.pass_target = world; }
1842 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1848 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1850 if(self.flagcarried)
1851 if(!autocvar_g_ctf_portalteleport)
1852 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1857 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1859 if(MUTATOR_RETURNVALUE || gameover) { return false; }
1861 entity player = self;
1863 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1865 // pass the flag to a team mate
1866 if(autocvar_g_ctf_pass)
1868 entity head, closest_target = world;
1869 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
1871 while(head) // find the closest acceptable target to pass to
1873 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1874 if(head != player && SAME_TEAM(head, player))
1875 if(!head.speedrunning && !head.vehicle)
1877 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1878 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1879 vector passer_center = CENTER_OR_VIEWOFS(player);
1881 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1883 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1885 if(IS_BOT_CLIENT(head))
1887 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1888 ctf_Handle_Throw(head, player, DROP_PASS);
1892 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1893 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1895 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1898 else if(player.flagcarried)
1902 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1903 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1904 { closest_target = head; }
1906 else { closest_target = head; }
1913 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
1916 // throw the flag in front of you
1917 if(autocvar_g_ctf_throw && player.flagcarried)
1919 if(player.throw_count == -1)
1921 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1923 player.throw_prevtime = time;
1924 player.throw_count = 1;
1925 ctf_Handle_Throw(player, world, DROP_THROW);
1930 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1936 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1937 else { player.throw_count += 1; }
1938 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1940 player.throw_prevtime = time;
1941 ctf_Handle_Throw(player, world, DROP_THROW);
1950 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1952 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1954 self.wps_helpme_time = time;
1955 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1957 else // create a normal help me waypointsprite
1959 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');
1960 WaypointSprite_Ping(self.wps_helpme);
1966 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1968 if(vh_player.flagcarried)
1970 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1972 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1976 setattachment(vh_player.flagcarried, vh_vehicle, "");
1977 vh_player.flagcarried.move_origin = VEHICLE_FLAG_OFFSET;
1978 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1979 //vh_player.flagcarried.move_angles = '0 0 0';
1987 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1989 if(vh_player.flagcarried)
1991 setattachment(vh_player.flagcarried, vh_player, "");
1992 vh_player.flagcarried.move_origin = VEHICLE_FLAG_OFFSET;
1993 vh_player.flagcarried.scale = FLAG_SCALE;
1994 vh_player.flagcarried.move_angles = '0 0 0';
2001 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2003 if(self.flagcarried)
2005 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
2006 ctf_RespawnFlag(self.flagcarried);
2013 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2015 entity flag; // temporary entity for the search method
2017 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2019 switch(flag.ctf_status)
2024 // lock the flag, game is over
2025 flag.move_movetype = MOVETYPE_NONE;
2026 flag.takedamage = DAMAGE_NO;
2027 flag.solid = SOLID_NOT;
2028 flag.nextthink = false; // stop thinking
2030 //dprint("stopping the ", flag.netname, " from moving.\n");
2038 // do nothing for these flags
2047 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2049 havocbot_ctf_reset_role(self);
2058 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2059 CTF Starting point for a player in team one (Red).
2060 Keys: "angle" viewing angle when spawning. */
2061 void spawnfunc_info_player_team1()
2063 if(g_assault) { remove(self); return; }
2065 self.team = NUM_TEAM_1; // red
2066 spawnfunc_info_player_deathmatch();
2070 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2071 CTF Starting point for a player in team two (Blue).
2072 Keys: "angle" viewing angle when spawning. */
2073 void spawnfunc_info_player_team2()
2075 if(g_assault) { remove(self); return; }
2077 self.team = NUM_TEAM_2; // blue
2078 spawnfunc_info_player_deathmatch();
2081 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2082 CTF Starting point for a player in team three (Yellow).
2083 Keys: "angle" viewing angle when spawning. */
2084 void spawnfunc_info_player_team3()
2086 if(g_assault) { remove(self); return; }
2088 self.team = NUM_TEAM_3; // yellow
2089 spawnfunc_info_player_deathmatch();
2093 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2094 CTF Starting point for a player in team four (Purple).
2095 Keys: "angle" viewing angle when spawning. */
2096 void spawnfunc_info_player_team4()
2098 if(g_assault) { remove(self); return; }
2100 self.team = NUM_TEAM_4; // purple
2101 spawnfunc_info_player_deathmatch();
2104 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2105 CTF flag for team one (Red).
2107 "angle" Angle the flag will point (minus 90 degrees)...
2108 "model" model to use, note this needs red and blue as skins 0 and 1...
2109 "noise" sound played when flag is picked up...
2110 "noise1" sound played when flag is returned by a teammate...
2111 "noise2" sound played when flag is captured...
2112 "noise3" sound played when flag is lost in the field and respawns itself...
2113 "noise4" sound played when flag is dropped by a player...
2114 "noise5" sound played when flag touches the ground... */
2115 void spawnfunc_item_flag_team1()
2117 if(!g_ctf) { remove(self); return; }
2119 ctf_FlagSetup(1, self); // 1 = red
2122 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2123 CTF flag for team two (Blue).
2125 "angle" Angle the flag will point (minus 90 degrees)...
2126 "model" model to use, note this needs red and blue as skins 0 and 1...
2127 "noise" sound played when flag is picked up...
2128 "noise1" sound played when flag is returned by a teammate...
2129 "noise2" sound played when flag is captured...
2130 "noise3" sound played when flag is lost in the field and respawns itself...
2131 "noise4" sound played when flag is dropped by a player...
2132 "noise5" sound played when flag touches the ground... */
2133 void spawnfunc_item_flag_team2()
2135 if(!g_ctf) { remove(self); return; }
2137 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2140 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2141 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2142 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.
2144 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2145 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2146 void spawnfunc_ctf_team()
2148 if(!g_ctf) { remove(self); return; }
2150 self.classname = "ctf_team";
2151 self.team = self.cnt + 1;
2154 // compatibility for quake maps
2155 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2156 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2157 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2158 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2159 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2160 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2168 void ctf_ScoreRules()
2170 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, true);
2171 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2172 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2173 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2174 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2175 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2176 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2177 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2178 ScoreRules_basics_end();
2181 // code from here on is just to support maps that don't have flag and team entities
2182 void ctf_SpawnTeam (string teamname, float teamcolor)
2187 self.classname = "ctf_team";
2188 self.netname = teamname;
2189 self.cnt = teamcolor;
2191 spawnfunc_ctf_team();
2196 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2198 // if no teams are found, spawn defaults
2199 if(find(world, classname, "ctf_team") == world)
2201 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2202 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2203 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2209 void ctf_Initialize()
2211 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2213 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2214 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2215 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2217 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2221 MUTATOR_DEFINITION(gamemode_ctf)
2223 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2224 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2225 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2226 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2227 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2228 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2229 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2230 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2231 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2232 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2233 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2234 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2235 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2236 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2240 if(time > 1) // game loads at time 1
2241 error("This is a game type and it cannot be added at runtime.");
2245 MUTATOR_ONROLLBACK_OR_REMOVE
2247 // we actually cannot roll back ctf_Initialize here
2248 // BUT: we don't need to! If this gets called, adding always
2254 print("This is a game type and it cannot be removed at runtime.");