1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 void ctf_CaptureRecord(entity flag, entity player)
25 float cap_record = ctf_captimerecord;
26 float cap_time = (time - flag.ctf_pickuptime);
27 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
30 if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
31 else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
32 else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
34 // write that shit in the database
35 if((!ctf_captimerecord) || (cap_time < cap_record))
37 ctf_captimerecord = cap_time;
38 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
39 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
40 write_recordmarker(player, (time - cap_time), cap_time);
44 void ctf_FlagcarrierWaypoints(entity player)
46 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
47 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
48 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
49 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
52 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
54 float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis
55 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
56 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
57 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
60 if(current_height) // make sure we can actually do this arcing path
62 targpos = (to + ('0 0 1' * current_height));
63 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
64 if(trace_fraction < 1)
66 //print("normal arc line failed, trying to find new pos...");
67 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
68 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
69 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
70 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
71 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
74 else { targpos = to; }
76 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
78 vector desired_direction = normalize(targpos - from);
79 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
80 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
83 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
85 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
87 // directional tracing only
89 makevectors(passer_angle);
91 // find the closest point on the enemy to the center of the attack
92 float ang; // angle between shotdir and h
93 float h; // hypotenuse, which is the distance between attacker to head
94 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
96 h = vlen(head_center - passer_center);
97 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
100 vector nearest_on_line = (passer_center + a * v_forward);
101 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
103 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
104 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
106 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
111 else { return TRUE; }
115 // =======================
116 // CaptureShield Functions
117 // =======================
119 float ctf_CaptureShield_CheckStatus(entity p)
123 float players_worseeq, players_total;
125 if(ctf_captureshield_max_ratio <= 0)
128 s = PlayerScore_Add(p, SP_SCORE, 0);
129 if(s >= -ctf_captureshield_min_negscore)
132 players_total = players_worseeq = 0;
137 se = PlayerScore_Add(e, SP_SCORE, 0);
143 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
144 // use this rule here
146 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
152 void ctf_CaptureShield_Update(entity player, float wanted_status)
154 float updated_status = ctf_CaptureShield_CheckStatus(player);
155 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
157 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
158 player.ctf_captureshielded = updated_status;
162 float ctf_CaptureShield_Customize()
164 if(!other.ctf_captureshielded) { return FALSE; }
165 if(SAME_TEAM(self, other)) { return FALSE; }
170 void ctf_CaptureShield_Touch()
172 if(!other.ctf_captureshielded) { return; }
173 if(SAME_TEAM(self, other)) { return; }
175 vector mymid = (self.absmin + self.absmax) * 0.5;
176 vector othermid = (other.absmin + other.absmax) * 0.5;
178 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
179 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
182 void ctf_CaptureShield_Spawn(entity flag)
184 entity shield = spawn();
187 shield.team = self.team;
188 shield.touch = ctf_CaptureShield_Touch;
189 shield.customizeentityforclient = ctf_CaptureShield_Customize;
190 shield.classname = "ctf_captureshield";
191 shield.effects = EF_ADDITIVE;
192 shield.movetype = MOVETYPE_NOCLIP;
193 shield.solid = SOLID_TRIGGER;
194 shield.avelocity = '7 0 11';
197 setorigin(shield, self.origin);
198 setmodel(shield, "models/ctf/shield.md3");
199 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
203 // ====================
204 // Drop/Pass/Throw Code
205 // ====================
207 void ctf_Handle_Drop(entity flag, entity player, float droptype)
210 player = (player ? player : flag.pass_sender);
213 flag.movetype = MOVETYPE_TOSS;
214 flag.takedamage = DAMAGE_YES;
215 flag.angles = '0 0 0';
216 flag.health = flag.max_flag_health;
217 flag.ctf_droptime = time;
218 flag.ctf_dropper = player;
219 flag.ctf_status = FLAG_DROPPED;
221 // messages and sounds
222 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
223 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
224 ctf_EventLog("dropped", player.team, player);
227 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
228 PlayerScore_Add(player, SP_CTF_DROPS, 1);
231 if(autocvar_g_ctf_flag_dropped_waypoint)
232 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
234 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
236 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
237 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
240 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
242 if(droptype == DROP_PASS)
244 flag.pass_distance = 0;
245 flag.pass_sender = world;
246 flag.pass_target = world;
250 void ctf_Handle_Retrieve(entity flag, entity player)
252 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
253 entity sender = flag.pass_sender;
255 // transfer flag to player
257 flag.owner.flagcarried = flag;
262 setattachment(flag, player.vehicle, "");
263 setorigin(flag, VEHICLE_FLAG_OFFSET);
264 flag.scale = VEHICLE_FLAG_SCALE;
268 setattachment(flag, player, "");
269 setorigin(flag, FLAG_CARRY_OFFSET);
271 flag.movetype = MOVETYPE_NONE;
272 flag.takedamage = DAMAGE_NO;
273 flag.solid = SOLID_NOT;
274 flag.angles = '0 0 0';
275 flag.ctf_status = FLAG_CARRY;
277 // messages and sounds
278 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
279 ctf_EventLog("receive", flag.team, player);
281 FOR_EACH_REALPLAYER(tmp_player)
283 if(tmp_player == sender)
284 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
285 else if(tmp_player == player)
286 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
287 else if(SAME_TEAM(tmp_player, sender))
288 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
291 // create new waypoint
292 ctf_FlagcarrierWaypoints(player);
294 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
295 player.throw_antispam = sender.throw_antispam;
297 flag.pass_distance = 0;
298 flag.pass_sender = world;
299 flag.pass_target = world;
302 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
304 entity flag = player.flagcarried;
305 vector targ_origin, flag_velocity;
307 if(!flag) { return; }
308 if((droptype == DROP_PASS) && !receiver) { return; }
310 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
313 setattachment(flag, world, "");
314 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
315 flag.owner.flagcarried = world;
317 flag.solid = SOLID_TRIGGER;
318 flag.ctf_dropper = player;
319 flag.ctf_droptime = time;
321 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
328 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
329 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
330 WarpZone_RefSys_Copy(flag, receiver);
331 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
332 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
334 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
335 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
338 flag.movetype = MOVETYPE_FLY;
339 flag.takedamage = DAMAGE_NO;
340 flag.pass_sender = player;
341 flag.pass_target = receiver;
342 flag.ctf_status = FLAG_PASSING;
345 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
346 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
347 ctf_EventLog("pass", flag.team, player);
353 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'));
355 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)));
356 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
357 ctf_Handle_Drop(flag, player, droptype);
363 flag.velocity = '0 0 0'; // do nothing
370 flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
371 ctf_Handle_Drop(flag, player, droptype);
376 // kill old waypointsprite
377 WaypointSprite_Ping(player.wps_flagcarrier);
378 WaypointSprite_Kill(player.wps_flagcarrier);
380 if(player.wps_enemyflagcarrier)
381 WaypointSprite_Kill(player.wps_enemyflagcarrier);
384 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
392 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
394 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
395 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
396 float old_time, new_time;
398 if (!player) { return; } // without someone to give the reward to, we can't possibly cap
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.origin, '0 0 0', 1);
423 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.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
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 setorigin(flag, VEHICLE_FLAG_OFFSET);
487 flag.scale = VEHICLE_FLAG_SCALE;
491 setattachment(flag, player, "");
492 setorigin(flag, FLAG_CARRY_OFFSET);
496 flag.movetype = MOVETYPE_NONE;
497 flag.takedamage = DAMAGE_NO;
498 flag.solid = SOLID_NOT;
499 flag.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);
525 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
526 ctf_EventLog("steal", flag.team, player);
532 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);
533 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);
534 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
535 PlayerTeamScore_AddScore(player, pickup_dropped_score);
536 ctf_EventLog("pickup", flag.team, player);
544 if(pickuptype == PICKUP_BASE)
546 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
547 if((player.speedrunning) && (ctf_captimerecord))
548 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
552 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
555 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
556 ctf_FlagcarrierWaypoints(player);
557 WaypointSprite_Ping(player.wps_flagcarrier);
561 // ===================
562 // Main Flag Functions
563 // ===================
565 void ctf_CheckFlagReturn(entity flag, float returntype)
567 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
569 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
571 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
575 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
576 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
577 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
578 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
582 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
584 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
585 ctf_EventLog("returned", flag.team, world);
586 ctf_RespawnFlag(flag);
591 void ctf_CheckStalemate(void)
594 float stale_red_flags = 0, stale_blue_flags = 0;
597 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
599 // build list of stale flags
600 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
602 if(autocvar_g_ctf_stalemate)
603 if(tmp_entity.ctf_status != FLAG_BASE)
604 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
606 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
607 ctf_staleflaglist = tmp_entity;
609 switch(tmp_entity.team)
611 case NUM_TEAM_1: ++stale_red_flags; break;
612 case NUM_TEAM_2: ++stale_blue_flags; break;
617 if(stale_red_flags && stale_blue_flags)
618 ctf_stalemate = TRUE;
619 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
620 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
621 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
622 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
624 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
627 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
629 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
630 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));
633 if (!wpforenemy_announced)
635 FOR_EACH_REALPLAYER(tmp_entity)
636 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
638 wpforenemy_announced = TRUE;
643 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
645 if(ITEM_DAMAGE_NEEDKILL(deathtype))
647 // automatically kill the flag and return it
649 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
652 if(autocvar_g_ctf_flag_return_damage)
654 // reduce health and check if it should be returned
655 self.health = self.health - damage;
656 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
666 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
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.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
677 if(!trace_startsolid) // 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.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.origin - tmp_entity.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.velocity = self.velocity * 0.5;
716 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
717 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
719 { self.movetype = MOVETYPE_FLY; }
721 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
723 if(autocvar_g_ctf_flag_return_dropped)
725 if((vlen(self.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.origin, targ_origin, MOVE_NOMONSTERS, self);
771 if((self.pass_target == world)
772 || (self.pass_target.deadflag != DEAD_NO)
773 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
774 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
775 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
777 // give up, pass failed
778 ctf_Handle_Drop(self, world, DROP_PASS);
782 // still a viable target, go for it
783 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
788 default: // this should never happen
790 dprint("ctf_FlagThink(): Flag exists with no status?\n");
798 if(gameover) { return; }
800 entity toucher = other;
801 float is_not_monster = (!(toucher.flags & FL_MONSTER));
803 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
804 if(ITEM_TOUCH_NEEDKILL())
807 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
811 // special touch behaviors
812 if(toucher.vehicle_flags & VHF_ISVEHICLE)
814 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
815 toucher = toucher.owner; // the player is actually the vehicle owner, not other
817 return; // do nothing
819 else if(toucher.flags & FL_MONSTER)
821 if(!autocvar_g_ctf_allow_monster_touch)
822 return; // do nothing
824 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
826 if(time > self.wait) // if we haven't in a while, play a sound/effect
828 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
829 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
830 self.wait = time + FLAG_TOUCHRATE;
834 else if(toucher.deadflag != DEAD_NO) { return; }
836 switch(self.ctf_status)
840 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
841 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
842 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
843 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
849 if(SAME_TEAM(toucher, self))
850 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
851 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
852 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
858 dprint("Someone touched a flag even though it was being carried?\n");
864 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
866 if(DIFF_TEAM(toucher, self.pass_sender))
867 ctf_Handle_Return(self, toucher);
869 ctf_Handle_Retrieve(self, toucher);
877 void ctf_RespawnFlag(entity flag)
879 // check for flag respawn being called twice in a row
880 if(flag.last_respawn > time - 0.5)
881 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
883 flag.last_respawn = time;
885 // reset the player (if there is one)
886 if((flag.owner) && (flag.owner.flagcarried == flag))
888 if(flag.owner.wps_enemyflagcarrier)
889 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
891 WaypointSprite_Kill(flag.wps_flagcarrier);
893 flag.owner.flagcarried = world;
895 if(flag.speedrunning)
896 ctf_FakeTimeLimit(flag.owner, -1);
899 if((flag.owner) && (flag.owner.vehicle))
900 flag.scale = FLAG_SCALE;
902 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
903 { WaypointSprite_Kill(flag.wps_flagdropped); }
906 setattachment(flag, world, "");
907 setorigin(flag, flag.ctf_spawnorigin);
909 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
910 flag.takedamage = DAMAGE_NO;
911 flag.health = flag.max_flag_health;
912 flag.solid = SOLID_TRIGGER;
913 flag.velocity = '0 0 0';
914 flag.angles = flag.mangle;
915 flag.flags = FL_ITEM | FL_NOTARGET;
917 flag.ctf_status = FLAG_BASE;
919 flag.pass_distance = 0;
920 flag.pass_sender = world;
921 flag.pass_target = world;
922 flag.ctf_dropper = world;
923 flag.ctf_pickuptime = 0;
924 flag.ctf_droptime = 0;
930 if(IS_PLAYER(self.owner))
931 ctf_Handle_Throw(self.owner, world, DROP_RESET);
933 ctf_RespawnFlag(self);
936 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
939 waypoint_spawnforitem_force(self, self.origin);
940 self.nearestwaypointtimeout = 0; // activate waypointing again
941 self.bot_basewaypoint = self.nearestwaypoint;
944 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
945 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
947 // captureshield setup
948 ctf_CaptureShield_Spawn(self);
951 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
954 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.
955 self = flag; // for later usage with droptofloor()
958 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
959 ctf_worldflaglist = flag;
961 setattachment(flag, world, "");
963 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
964 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
965 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
966 flag.classname = "item_flag_team";
967 flag.target = "###item###"; // wut?
968 flag.flags = FL_ITEM | FL_NOTARGET;
969 flag.solid = SOLID_TRIGGER;
970 flag.takedamage = DAMAGE_NO;
971 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
972 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
973 flag.health = flag.max_flag_health;
974 flag.event_damage = ctf_FlagDamage;
975 flag.pushable = TRUE;
976 flag.teleportable = TELEPORT_NORMAL;
977 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
978 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
979 flag.velocity = '0 0 0';
980 flag.mangle = flag.angles;
981 flag.reset = ctf_Reset;
982 flag.touch = ctf_FlagTouch;
983 flag.think = ctf_FlagThink;
984 flag.nextthink = time + FLAG_THINKRATE;
985 flag.ctf_status = FLAG_BASE;
988 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
989 if(!flag.scale) { flag.scale = FLAG_SCALE; }
990 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
991 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
992 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
993 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
996 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
997 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
998 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
999 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.
1000 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1001 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1002 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1005 precache_sound(flag.snd_flag_taken);
1006 precache_sound(flag.snd_flag_returned);
1007 precache_sound(flag.snd_flag_capture);
1008 precache_sound(flag.snd_flag_respawn);
1009 precache_sound(flag.snd_flag_dropped);
1010 precache_sound(flag.snd_flag_touch);
1011 precache_sound(flag.snd_flag_pass);
1012 precache_model(flag.model);
1013 precache_model("models/ctf/shield.md3");
1014 precache_model("models/ctf/shockwavetransring.md3");
1017 setmodel(flag, flag.model); // precision set below
1018 setsize(flag, FLAG_MIN, FLAG_MAX);
1019 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1021 if(autocvar_g_ctf_flag_glowtrails)
1023 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1024 flag.glow_size = 25;
1025 flag.glow_trail = 1;
1028 flag.effects |= EF_LOWPRECISION;
1029 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1030 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1033 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1035 flag.dropped_origin = flag.origin;
1036 flag.noalign = TRUE;
1037 flag.movetype = MOVETYPE_NONE;
1039 else // drop to floor, automatically find a platform and set that as spawn origin
1041 flag.noalign = FALSE;
1044 flag.movetype = MOVETYPE_TOSS;
1047 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1055 // NOTE: LEGACY CODE, needs to be re-written!
1057 void havocbot_calculate_middlepoint()
1061 vector fo = '0 0 0';
1064 f = ctf_worldflaglist;
1069 f = f.ctf_worldflagnext;
1073 havocbot_ctf_middlepoint = s * (1.0 / n);
1074 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1078 entity havocbot_ctf_find_flag(entity bot)
1081 f = ctf_worldflaglist;
1084 if (bot.team == f.team)
1086 f = f.ctf_worldflagnext;
1091 entity havocbot_ctf_find_enemy_flag(entity bot)
1094 f = ctf_worldflaglist;
1097 if (bot.team != f.team)
1099 f = f.ctf_worldflagnext;
1104 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1112 FOR_EACH_PLAYER(head)
1114 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1117 if(vlen(head.origin - org) < tc_radius)
1124 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1127 head = ctf_worldflaglist;
1130 if (self.team == head.team)
1132 head = head.ctf_worldflagnext;
1135 navigation_routerating(head, ratingscale, 10000);
1138 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1141 head = ctf_worldflaglist;
1144 if (self.team == head.team)
1146 head = head.ctf_worldflagnext;
1151 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1154 void havocbot_goalrating_ctf_enemyflag(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_enemybase(float ratingscale)
1170 if (!bot_waypoints_for_items)
1172 havocbot_goalrating_ctf_enemyflag(ratingscale);
1178 head = havocbot_ctf_find_enemy_flag(self);
1183 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1186 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1190 mf = havocbot_ctf_find_flag(self);
1192 if(mf.ctf_status == FLAG_BASE)
1196 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1199 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1202 head = ctf_worldflaglist;
1205 // flag is out in the field
1206 if(head.ctf_status != FLAG_BASE)
1207 if(head.tag_entity==world) // dropped
1211 if(vlen(org-head.origin)<df_radius)
1212 navigation_routerating(head, ratingscale, 10000);
1215 navigation_routerating(head, ratingscale, 10000);
1218 head = head.ctf_worldflagnext;
1222 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1226 head = findchainfloat(bot_pickup, TRUE);
1229 // gather health and armor only
1231 if (head.health || head.armorvalue)
1232 if (vlen(head.origin - org) < sradius)
1234 // get the value of the item
1235 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1237 navigation_routerating(head, t * ratingscale, 500);
1243 void havocbot_ctf_reset_role(entity bot)
1245 float cdefense, cmiddle, coffense;
1246 entity mf, ef, head;
1249 if(bot.deadflag != DEAD_NO)
1252 if(vlen(havocbot_ctf_middlepoint)==0)
1253 havocbot_calculate_middlepoint();
1256 if (bot.flagcarried)
1258 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1262 mf = havocbot_ctf_find_flag(bot);
1263 ef = havocbot_ctf_find_enemy_flag(bot);
1265 // Retrieve stolen flag
1266 if(mf.ctf_status!=FLAG_BASE)
1268 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1272 // If enemy flag is taken go to the middle to intercept pursuers
1273 if(ef.ctf_status!=FLAG_BASE)
1275 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1279 // if there is only me on the team switch to offense
1281 FOR_EACH_PLAYER(head)
1282 if(head.team==bot.team)
1287 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1291 // Evaluate best position to take
1292 // Count mates on middle position
1293 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1295 // Count mates on defense position
1296 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1298 // Count mates on offense position
1299 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1301 if(cdefense<=coffense)
1302 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1303 else if(coffense<=cmiddle)
1304 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1306 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1309 void havocbot_role_ctf_carrier()
1311 if(self.deadflag != DEAD_NO)
1313 havocbot_ctf_reset_role(self);
1317 if (self.flagcarried == world)
1319 havocbot_ctf_reset_role(self);
1323 if (self.bot_strategytime < time)
1325 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1327 navigation_goalrating_start();
1328 havocbot_goalrating_ctf_ourbase(50000);
1331 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1333 navigation_goalrating_end();
1335 if (self.navigation_hasgoals)
1336 self.havocbot_cantfindflag = time + 10;
1337 else if (time > self.havocbot_cantfindflag)
1339 // Can't navigate to my own base, suicide!
1340 // TODO: drop it and wander around
1341 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1347 void havocbot_role_ctf_escort()
1351 if(self.deadflag != DEAD_NO)
1353 havocbot_ctf_reset_role(self);
1357 if (self.flagcarried)
1359 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1363 // If enemy flag is back on the base switch to previous role
1364 ef = havocbot_ctf_find_enemy_flag(self);
1365 if(ef.ctf_status==FLAG_BASE)
1367 self.havocbot_role = self.havocbot_previous_role;
1368 self.havocbot_role_timeout = 0;
1372 // If the flag carrier reached the base switch to defense
1373 mf = havocbot_ctf_find_flag(self);
1374 if(mf.ctf_status!=FLAG_BASE)
1375 if(vlen(ef.origin - mf.dropped_origin) < 300)
1377 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1381 // Set the role timeout if necessary
1382 if (!self.havocbot_role_timeout)
1384 self.havocbot_role_timeout = time + random() * 30 + 60;
1387 // If nothing happened just switch to previous role
1388 if (time > self.havocbot_role_timeout)
1390 self.havocbot_role = self.havocbot_previous_role;
1391 self.havocbot_role_timeout = 0;
1395 // Chase the flag carrier
1396 if (self.bot_strategytime < time)
1398 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1399 navigation_goalrating_start();
1400 havocbot_goalrating_ctf_enemyflag(30000);
1401 havocbot_goalrating_ctf_ourstolenflag(40000);
1402 havocbot_goalrating_items(10000, self.origin, 10000);
1403 navigation_goalrating_end();
1407 void havocbot_role_ctf_offense()
1412 if(self.deadflag != DEAD_NO)
1414 havocbot_ctf_reset_role(self);
1418 if (self.flagcarried)
1420 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1425 mf = havocbot_ctf_find_flag(self);
1426 ef = havocbot_ctf_find_enemy_flag(self);
1429 if(mf.ctf_status!=FLAG_BASE)
1432 pos = mf.tag_entity.origin;
1436 // Try to get it if closer than the enemy base
1437 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1439 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1444 // Escort flag carrier
1445 if(ef.ctf_status!=FLAG_BASE)
1448 pos = ef.tag_entity.origin;
1452 if(vlen(pos-mf.dropped_origin)>700)
1454 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1459 // About to fail, switch to middlefield
1462 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1466 // Set the role timeout if necessary
1467 if (!self.havocbot_role_timeout)
1468 self.havocbot_role_timeout = time + 120;
1470 if (time > self.havocbot_role_timeout)
1472 havocbot_ctf_reset_role(self);
1476 if (self.bot_strategytime < time)
1478 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1479 navigation_goalrating_start();
1480 havocbot_goalrating_ctf_ourstolenflag(50000);
1481 havocbot_goalrating_ctf_enemybase(20000);
1482 havocbot_goalrating_items(5000, self.origin, 1000);
1483 havocbot_goalrating_items(1000, self.origin, 10000);
1484 navigation_goalrating_end();
1488 // Retriever (temporary role):
1489 void havocbot_role_ctf_retriever()
1493 if(self.deadflag != DEAD_NO)
1495 havocbot_ctf_reset_role(self);
1499 if (self.flagcarried)
1501 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1505 // If flag is back on the base switch to previous role
1506 mf = havocbot_ctf_find_flag(self);
1507 if(mf.ctf_status==FLAG_BASE)
1509 havocbot_ctf_reset_role(self);
1513 if (!self.havocbot_role_timeout)
1514 self.havocbot_role_timeout = time + 20;
1516 if (time > self.havocbot_role_timeout)
1518 havocbot_ctf_reset_role(self);
1522 if (self.bot_strategytime < time)
1527 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1528 navigation_goalrating_start();
1529 havocbot_goalrating_ctf_ourstolenflag(50000);
1530 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1531 havocbot_goalrating_ctf_enemybase(30000);
1532 havocbot_goalrating_items(500, self.origin, rt_radius);
1533 navigation_goalrating_end();
1537 void havocbot_role_ctf_middle()
1541 if(self.deadflag != DEAD_NO)
1543 havocbot_ctf_reset_role(self);
1547 if (self.flagcarried)
1549 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1553 mf = havocbot_ctf_find_flag(self);
1554 if(mf.ctf_status!=FLAG_BASE)
1556 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1560 if (!self.havocbot_role_timeout)
1561 self.havocbot_role_timeout = time + 10;
1563 if (time > self.havocbot_role_timeout)
1565 havocbot_ctf_reset_role(self);
1569 if (self.bot_strategytime < time)
1573 org = havocbot_ctf_middlepoint;
1574 org_z = self.origin_z;
1576 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1577 navigation_goalrating_start();
1578 havocbot_goalrating_ctf_ourstolenflag(50000);
1579 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1580 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1581 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1582 havocbot_goalrating_items(2500, self.origin, 10000);
1583 havocbot_goalrating_ctf_enemybase(2500);
1584 navigation_goalrating_end();
1588 void havocbot_role_ctf_defense()
1592 if(self.deadflag != DEAD_NO)
1594 havocbot_ctf_reset_role(self);
1598 if (self.flagcarried)
1600 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1604 // If own flag was captured
1605 mf = havocbot_ctf_find_flag(self);
1606 if(mf.ctf_status!=FLAG_BASE)
1608 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1612 if (!self.havocbot_role_timeout)
1613 self.havocbot_role_timeout = time + 30;
1615 if (time > self.havocbot_role_timeout)
1617 havocbot_ctf_reset_role(self);
1620 if (self.bot_strategytime < time)
1625 org = mf.dropped_origin;
1626 mp_radius = havocbot_ctf_middlepoint_radius;
1628 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1629 navigation_goalrating_start();
1631 // if enemies are closer to our base, go there
1632 entity head, closestplayer = world;
1633 float distance, bestdistance = 10000;
1634 FOR_EACH_PLAYER(head)
1636 if(head.deadflag!=DEAD_NO)
1639 distance = vlen(org - head.origin);
1640 if(distance<bestdistance)
1642 closestplayer = head;
1643 bestdistance = distance;
1648 if(closestplayer.team!=self.team)
1649 if(vlen(org - self.origin)>1000)
1650 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1651 havocbot_goalrating_ctf_ourbase(30000);
1653 havocbot_goalrating_ctf_ourstolenflag(20000);
1654 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1655 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1656 havocbot_goalrating_items(10000, org, mp_radius);
1657 havocbot_goalrating_items(5000, self.origin, 10000);
1658 navigation_goalrating_end();
1662 void havocbot_role_ctf_setrole(entity bot, float role)
1664 dprint(strcat(bot.netname," switched to "));
1667 case HAVOCBOT_CTF_ROLE_CARRIER:
1669 bot.havocbot_role = havocbot_role_ctf_carrier;
1670 bot.havocbot_role_timeout = 0;
1671 bot.havocbot_cantfindflag = time + 10;
1672 bot.bot_strategytime = 0;
1674 case HAVOCBOT_CTF_ROLE_DEFENSE:
1676 bot.havocbot_role = havocbot_role_ctf_defense;
1677 bot.havocbot_role_timeout = 0;
1679 case HAVOCBOT_CTF_ROLE_MIDDLE:
1681 bot.havocbot_role = havocbot_role_ctf_middle;
1682 bot.havocbot_role_timeout = 0;
1684 case HAVOCBOT_CTF_ROLE_OFFENSE:
1686 bot.havocbot_role = havocbot_role_ctf_offense;
1687 bot.havocbot_role_timeout = 0;
1689 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1690 dprint("retriever");
1691 bot.havocbot_previous_role = bot.havocbot_role;
1692 bot.havocbot_role = havocbot_role_ctf_retriever;
1693 bot.havocbot_role_timeout = time + 10;
1694 bot.bot_strategytime = 0;
1696 case HAVOCBOT_CTF_ROLE_ESCORT:
1698 bot.havocbot_previous_role = bot.havocbot_role;
1699 bot.havocbot_role = havocbot_role_ctf_escort;
1700 bot.havocbot_role_timeout = time + 30;
1701 bot.bot_strategytime = 0;
1712 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1716 // initially clear items so they can be set as necessary later.
1717 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1718 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1720 // scan through all the flags and notify the client about them
1721 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1723 switch(flag.ctf_status)
1728 if((flag.owner == self) || (flag.pass_sender == self))
1729 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1731 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1736 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1742 // item for stopping players from capturing the flag too often
1743 if(self.ctf_captureshielded)
1744 self.items |= IT_CTF_SHIELDED;
1746 // update the health of the flag carrier waypointsprite
1747 if(self.wps_flagcarrier)
1748 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1753 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1755 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1757 if(frag_target == frag_attacker) // damage done to yourself
1759 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1760 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1762 else // damage done to everyone else
1764 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1765 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1768 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1770 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)))
1771 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1773 frag_target.wps_helpme_time = time;
1774 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1776 // todo: add notification for when flag carrier needs help?
1781 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1783 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1785 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1786 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1789 if(frag_target.flagcarried)
1790 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1795 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1798 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1801 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1803 entity flag; // temporary entity for the search method
1805 if(self.flagcarried)
1806 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1808 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1810 if(flag.pass_sender == self) { flag.pass_sender = world; }
1811 if(flag.pass_target == self) { flag.pass_target = world; }
1812 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1818 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1820 if(self.flagcarried)
1821 if(!autocvar_g_ctf_portalteleport)
1822 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1827 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1829 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1831 entity player = self;
1833 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1835 // pass the flag to a team mate
1836 if(autocvar_g_ctf_pass)
1838 entity head, closest_target = world;
1839 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1841 while(head) // find the closest acceptable target to pass to
1843 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1844 if(head != player && SAME_TEAM(head, player))
1845 if(!head.speedrunning && !head.vehicle)
1847 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1848 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1849 vector passer_center = CENTER_OR_VIEWOFS(player);
1851 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1853 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1855 if(IS_BOT_CLIENT(head))
1857 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1858 ctf_Handle_Throw(head, player, DROP_PASS);
1862 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1863 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1865 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1868 else if(player.flagcarried)
1872 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1873 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1874 { closest_target = head; }
1876 else { closest_target = head; }
1883 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1886 // throw the flag in front of you
1887 if(autocvar_g_ctf_throw && player.flagcarried)
1889 if(player.throw_count == -1)
1891 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1893 player.throw_prevtime = time;
1894 player.throw_count = 1;
1895 ctf_Handle_Throw(player, world, DROP_THROW);
1900 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1906 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1907 else { player.throw_count += 1; }
1908 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1910 player.throw_prevtime = time;
1911 ctf_Handle_Throw(player, world, DROP_THROW);
1920 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1922 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1924 self.wps_helpme_time = time;
1925 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1927 else // create a normal help me waypointsprite
1929 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');
1930 WaypointSprite_Ping(self.wps_helpme);
1936 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1938 if(vh_player.flagcarried)
1940 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1942 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1946 setattachment(vh_player.flagcarried, vh_vehicle, "");
1947 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1948 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1949 //vh_player.flagcarried.angles = '0 0 0';
1957 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1959 if(vh_player.flagcarried)
1961 setattachment(vh_player.flagcarried, vh_player, "");
1962 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1963 vh_player.flagcarried.scale = FLAG_SCALE;
1964 vh_player.flagcarried.angles = '0 0 0';
1971 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1973 if(self.flagcarried)
1975 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1976 ctf_RespawnFlag(self.flagcarried);
1983 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1985 entity flag; // temporary entity for the search method
1987 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1989 switch(flag.ctf_status)
1994 // lock the flag, game is over
1995 flag.movetype = MOVETYPE_NONE;
1996 flag.takedamage = DAMAGE_NO;
1997 flag.solid = SOLID_NOT;
1998 flag.nextthink = FALSE; // stop thinking
2000 //dprint("stopping the ", flag.netname, " from moving.\n");
2008 // do nothing for these flags
2017 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2019 havocbot_ctf_reset_role(self);
2028 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2029 CTF Starting point for a player in team one (Red).
2030 Keys: "angle" viewing angle when spawning. */
2031 void spawnfunc_info_player_team1()
2033 if(g_assault) { remove(self); return; }
2035 self.team = NUM_TEAM_1; // red
2036 spawnfunc_info_player_deathmatch();
2040 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2041 CTF Starting point for a player in team two (Blue).
2042 Keys: "angle" viewing angle when spawning. */
2043 void spawnfunc_info_player_team2()
2045 if(g_assault) { remove(self); return; }
2047 self.team = NUM_TEAM_2; // blue
2048 spawnfunc_info_player_deathmatch();
2051 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2052 CTF Starting point for a player in team three (Yellow).
2053 Keys: "angle" viewing angle when spawning. */
2054 void spawnfunc_info_player_team3()
2056 if(g_assault) { remove(self); return; }
2058 self.team = NUM_TEAM_3; // yellow
2059 spawnfunc_info_player_deathmatch();
2063 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2064 CTF Starting point for a player in team four (Purple).
2065 Keys: "angle" viewing angle when spawning. */
2066 void spawnfunc_info_player_team4()
2068 if(g_assault) { remove(self); return; }
2070 self.team = NUM_TEAM_4; // purple
2071 spawnfunc_info_player_deathmatch();
2074 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2075 CTF flag for team one (Red).
2077 "angle" Angle the flag will point (minus 90 degrees)...
2078 "model" model to use, note this needs red and blue as skins 0 and 1...
2079 "noise" sound played when flag is picked up...
2080 "noise1" sound played when flag is returned by a teammate...
2081 "noise2" sound played when flag is captured...
2082 "noise3" sound played when flag is lost in the field and respawns itself...
2083 "noise4" sound played when flag is dropped by a player...
2084 "noise5" sound played when flag touches the ground... */
2085 void spawnfunc_item_flag_team1()
2087 if(!g_ctf) { remove(self); return; }
2089 ctf_FlagSetup(1, self); // 1 = red
2092 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2093 CTF flag for team two (Blue).
2095 "angle" Angle the flag will point (minus 90 degrees)...
2096 "model" model to use, note this needs red and blue as skins 0 and 1...
2097 "noise" sound played when flag is picked up...
2098 "noise1" sound played when flag is returned by a teammate...
2099 "noise2" sound played when flag is captured...
2100 "noise3" sound played when flag is lost in the field and respawns itself...
2101 "noise4" sound played when flag is dropped by a player...
2102 "noise5" sound played when flag touches the ground... */
2103 void spawnfunc_item_flag_team2()
2105 if(!g_ctf) { remove(self); return; }
2107 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2110 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2111 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2112 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.
2114 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2115 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2116 void spawnfunc_ctf_team()
2118 if(!g_ctf) { remove(self); return; }
2120 self.classname = "ctf_team";
2121 self.team = self.cnt + 1;
2124 // compatibility for quake maps
2125 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2126 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2127 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2128 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2129 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2130 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2138 void ctf_ScoreRules()
2140 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2141 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2142 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2143 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2144 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2145 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2146 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2147 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2148 ScoreRules_basics_end();
2151 // code from here on is just to support maps that don't have flag and team entities
2152 void ctf_SpawnTeam (string teamname, float teamcolor)
2157 self.classname = "ctf_team";
2158 self.netname = teamname;
2159 self.cnt = teamcolor;
2161 spawnfunc_ctf_team();
2166 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2168 // if no teams are found, spawn defaults
2169 if(find(world, classname, "ctf_team") == world)
2171 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2172 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2173 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2179 void ctf_Initialize()
2181 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2183 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2184 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2185 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2187 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2191 MUTATOR_DEFINITION(gamemode_ctf)
2193 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2194 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2195 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2196 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2197 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2198 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2199 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2200 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2201 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2202 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2203 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2204 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2205 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2206 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2210 if(time > 1) // game loads at time 1
2211 error("This is a game type and it cannot be added at runtime.");
2215 MUTATOR_ONROLLBACK_OR_REMOVE
2217 // we actually cannot roll back ctf_Initialize here
2218 // BUT: we don't need to! If this gets called, adding always
2224 print("This is a game type and it cannot be removed at runtime.");