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 h; // hypotenuse, which is the distance between attacker to head
93 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
95 h = vlen(head_center - passer_center);
96 a = h * (normalize(head_center - passer_center) * v_forward);
98 vector nearest_on_line = (passer_center + a * v_forward);
99 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
101 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
102 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
104 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
109 else { return true; }
113 // =======================
114 // CaptureShield Functions
115 // =======================
117 float ctf_CaptureShield_CheckStatus(entity p)
121 float players_worseeq, players_total;
123 if(ctf_captureshield_max_ratio <= 0)
126 s = PlayerScore_Add(p, SP_SCORE, 0);
127 if(s >= -ctf_captureshield_min_negscore)
130 players_total = players_worseeq = 0;
135 se = PlayerScore_Add(e, SP_SCORE, 0);
141 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
142 // use this rule here
144 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
150 void ctf_CaptureShield_Update(entity player, float wanted_status)
152 float updated_status = ctf_CaptureShield_CheckStatus(player);
153 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
155 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
156 player.ctf_captureshielded = updated_status;
160 float ctf_CaptureShield_Customize()
162 if(!other.ctf_captureshielded) { return false; }
163 if(SAME_TEAM(self, other)) { return false; }
168 void ctf_CaptureShield_Touch()
170 if(!other.ctf_captureshielded) { return; }
171 if(SAME_TEAM(self, other)) { return; }
173 vector mymid = (self.absmin + self.absmax) * 0.5;
174 vector othermid = (other.absmin + other.absmax) * 0.5;
176 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
177 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
180 void ctf_CaptureShield_Spawn(entity flag)
182 entity shield = spawn();
185 shield.team = self.team;
186 shield.touch = ctf_CaptureShield_Touch;
187 shield.customizeentityforclient = ctf_CaptureShield_Customize;
188 shield.classname = "ctf_captureshield";
189 shield.effects = EF_ADDITIVE;
190 shield.movetype = MOVETYPE_NOCLIP;
191 shield.solid = SOLID_TRIGGER;
192 shield.avelocity = '7 0 11';
195 setorigin(shield, self.origin);
196 setmodel(shield, "models/ctf/shield.md3");
197 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
201 // ====================
202 // Drop/Pass/Throw Code
203 // ====================
205 void ctf_Handle_Drop(entity flag, entity player, float droptype)
208 player = (player ? player : flag.pass_sender);
211 flag.movetype = MOVETYPE_TOSS;
212 flag.takedamage = DAMAGE_YES;
213 flag.angles = '0 0 0';
214 flag.health = flag.max_flag_health;
215 flag.ctf_droptime = time;
216 flag.ctf_dropper = player;
217 flag.ctf_status = FLAG_DROPPED;
219 // messages and sounds
220 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
221 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
222 ctf_EventLog("dropped", player.team, player);
225 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
226 PlayerScore_Add(player, SP_CTF_DROPS, 1);
229 if(autocvar_g_ctf_flag_dropped_waypoint)
230 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));
232 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
234 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
235 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
238 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
240 if(droptype == DROP_PASS)
242 flag.pass_distance = 0;
243 flag.pass_sender = world;
244 flag.pass_target = world;
248 void ctf_Handle_Retrieve(entity flag, entity player)
250 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
251 entity sender = flag.pass_sender;
253 // transfer flag to player
255 flag.owner.flagcarried = flag;
260 setattachment(flag, player.vehicle, "");
261 setorigin(flag, VEHICLE_FLAG_OFFSET);
262 flag.scale = VEHICLE_FLAG_SCALE;
266 setattachment(flag, player, "");
267 setorigin(flag, FLAG_CARRY_OFFSET);
269 flag.movetype = MOVETYPE_NONE;
270 flag.takedamage = DAMAGE_NO;
271 flag.solid = SOLID_NOT;
272 flag.angles = '0 0 0';
273 flag.ctf_status = FLAG_CARRY;
275 // messages and sounds
276 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
277 ctf_EventLog("receive", flag.team, player);
279 FOR_EACH_REALPLAYER(tmp_player)
281 if(tmp_player == sender)
282 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
283 else if(tmp_player == player)
284 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
285 else if(SAME_TEAM(tmp_player, sender))
286 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
289 // create new waypoint
290 ctf_FlagcarrierWaypoints(player);
292 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
293 player.throw_antispam = sender.throw_antispam;
295 flag.pass_distance = 0;
296 flag.pass_sender = world;
297 flag.pass_target = world;
300 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
302 entity flag = player.flagcarried;
303 vector targ_origin, flag_velocity;
305 if(!flag) { return; }
306 if((droptype == DROP_PASS) && !receiver) { return; }
308 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
311 setattachment(flag, world, "");
312 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
313 flag.owner.flagcarried = world;
315 flag.solid = SOLID_TRIGGER;
316 flag.ctf_dropper = player;
317 flag.ctf_droptime = time;
319 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
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.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.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
355 ctf_Handle_Drop(flag, player, droptype);
361 flag.velocity = '0 0 0'; // do nothing
368 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);
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.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
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 setorigin(flag, VEHICLE_FLAG_OFFSET);
489 flag.scale = VEHICLE_FLAG_SCALE;
493 setattachment(flag, player, "");
494 setorigin(flag, FLAG_CARRY_OFFSET);
498 flag.movetype = MOVETYPE_NONE;
499 flag.takedamage = DAMAGE_NO;
500 flag.solid = SOLID_NOT;
501 flag.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 if(ITEM_DAMAGE_NEEDKILL(deathtype))
650 // automatically kill the flag and return it
652 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
655 if(autocvar_g_ctf_flag_return_damage)
657 // reduce health and check if it should be returned
658 self.health = self.health - damage;
659 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
669 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
672 if(self == ctf_worldflaglist) // only for the first flag
673 FOR_EACH_CLIENT(tmp_entity)
674 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
677 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
678 dprint("wtf the flag got squashed?\n");
679 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
680 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
681 setsize(self, FLAG_MIN, FLAG_MAX); }
683 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
687 self.angles = '0 0 0';
695 switch(self.ctf_status)
699 if(autocvar_g_ctf_dropped_capture_radius)
701 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
702 if(tmp_entity.ctf_status == FLAG_DROPPED)
703 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
704 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
705 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
712 if(autocvar_g_ctf_flag_dropped_floatinwater)
714 vector midpoint = ((self.absmin + self.absmax) * 0.5);
715 if(pointcontents(midpoint) == CONTENT_WATER)
717 self.velocity = self.velocity * 0.5;
719 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
720 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
722 { self.movetype = MOVETYPE_FLY; }
724 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
726 if(autocvar_g_ctf_flag_return_dropped)
728 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
731 ctf_CheckFlagReturn(self, RETURN_DROPPED);
735 if(autocvar_g_ctf_flag_return_time)
737 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
738 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
746 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
749 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
753 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
757 if(autocvar_g_ctf_stalemate)
759 if(time >= wpforenemy_nextthink)
761 ctf_CheckStalemate();
762 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
770 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
771 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
772 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
774 if((self.pass_target == world)
775 || (self.pass_target.deadflag != DEAD_NO)
776 || (self.pass_target.flagcarried)
777 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
778 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
779 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
781 // give up, pass failed
782 ctf_Handle_Drop(self, world, DROP_PASS);
786 // still a viable target, go for it
787 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
792 default: // this should never happen
794 dprint("ctf_FlagThink(): Flag exists with no status?\n");
802 if(gameover) { return; }
803 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
805 entity toucher = other;
806 float is_not_monster = (!(toucher.flags & FL_MONSTER));
808 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
809 if(ITEM_TOUCH_NEEDKILL())
812 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
816 // special touch behaviors
817 if(toucher.frozen) { return; }
818 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
820 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
821 toucher = toucher.owner; // the player is actually the vehicle owner, not other
823 return; // do nothing
825 else if(toucher.flags & FL_MONSTER)
827 if(!autocvar_g_ctf_allow_monster_touch)
828 return; // do nothing
830 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
832 if(time > self.wait) // if we haven't in a while, play a sound/effect
834 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
835 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
836 self.wait = time + FLAG_TOUCHRATE;
840 else if(toucher.deadflag != DEAD_NO) { return; }
842 switch(self.ctf_status)
846 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
847 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
848 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
849 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
855 if(SAME_TEAM(toucher, self))
856 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
857 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
858 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
864 dprint("Someone touched a flag even though it was being carried?\n");
870 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
872 if(DIFF_TEAM(toucher, self.pass_sender))
873 ctf_Handle_Return(self, toucher);
875 ctf_Handle_Retrieve(self, toucher);
883 void ctf_RespawnFlag(entity flag)
885 // check for flag respawn being called twice in a row
886 if(flag.last_respawn > time - 0.5)
887 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
889 flag.last_respawn = time;
891 // reset the player (if there is one)
892 if((flag.owner) && (flag.owner.flagcarried == flag))
894 if(flag.owner.wps_enemyflagcarrier)
895 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
897 WaypointSprite_Kill(flag.wps_flagcarrier);
899 flag.owner.flagcarried = world;
901 if(flag.speedrunning)
902 ctf_FakeTimeLimit(flag.owner, -1);
905 if((flag.owner) && (flag.owner.vehicle))
906 flag.scale = FLAG_SCALE;
908 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
909 { WaypointSprite_Kill(flag.wps_flagdropped); }
912 setattachment(flag, world, "");
913 setorigin(flag, flag.ctf_spawnorigin);
915 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
916 flag.takedamage = DAMAGE_NO;
917 flag.health = flag.max_flag_health;
918 flag.solid = SOLID_TRIGGER;
919 flag.velocity = '0 0 0';
920 flag.angles = flag.mangle;
921 flag.flags = FL_ITEM | FL_NOTARGET;
923 flag.ctf_status = FLAG_BASE;
925 flag.pass_distance = 0;
926 flag.pass_sender = world;
927 flag.pass_target = world;
928 flag.ctf_dropper = world;
929 flag.ctf_pickuptime = 0;
930 flag.ctf_droptime = 0;
932 ctf_CheckStalemate();
938 if(IS_PLAYER(self.owner))
939 ctf_Handle_Throw(self.owner, world, DROP_RESET);
941 ctf_RespawnFlag(self);
944 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
947 waypoint_spawnforitem_force(self, self.origin);
948 self.nearestwaypointtimeout = 0; // activate waypointing again
949 self.bot_basewaypoint = self.nearestwaypoint;
952 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
953 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
955 // captureshield setup
956 ctf_CaptureShield_Spawn(self);
959 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
962 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.
963 self = flag; // for later usage with droptofloor()
966 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
967 ctf_worldflaglist = flag;
969 setattachment(flag, world, "");
971 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
972 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
973 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
974 flag.classname = "item_flag_team";
975 flag.target = "###item###"; // wut?
976 flag.flags = FL_ITEM | FL_NOTARGET;
977 flag.solid = SOLID_TRIGGER;
978 flag.takedamage = DAMAGE_NO;
979 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
980 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
981 flag.health = flag.max_flag_health;
982 flag.event_damage = ctf_FlagDamage;
983 flag.pushable = true;
984 flag.teleportable = TELEPORT_NORMAL;
985 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
986 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
987 flag.velocity = '0 0 0';
988 flag.mangle = flag.angles;
989 flag.reset = ctf_Reset;
990 flag.touch = ctf_FlagTouch;
991 flag.think = ctf_FlagThink;
992 flag.nextthink = time + FLAG_THINKRATE;
993 flag.ctf_status = FLAG_BASE;
996 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
997 if(!flag.scale) { flag.scale = FLAG_SCALE; }
998 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
999 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
1000 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
1001 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
1004 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
1005 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
1006 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
1007 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.
1008 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1009 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1010 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1013 precache_sound(flag.snd_flag_taken);
1014 precache_sound(flag.snd_flag_returned);
1015 precache_sound(flag.snd_flag_capture);
1016 precache_sound(flag.snd_flag_respawn);
1017 precache_sound(flag.snd_flag_dropped);
1018 precache_sound(flag.snd_flag_touch);
1019 precache_sound(flag.snd_flag_pass);
1020 precache_model(flag.model);
1021 precache_model("models/ctf/shield.md3");
1022 precache_model("models/ctf/shockwavetransring.md3");
1025 setmodel(flag, flag.model); // precision set below
1026 setsize(flag, FLAG_MIN, FLAG_MAX);
1027 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1029 if(autocvar_g_ctf_flag_glowtrails)
1031 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1032 flag.glow_size = 25;
1033 flag.glow_trail = 1;
1036 flag.effects |= EF_LOWPRECISION;
1037 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1038 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1041 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1043 flag.dropped_origin = flag.origin;
1044 flag.noalign = true;
1045 flag.movetype = MOVETYPE_NONE;
1047 else // drop to floor, automatically find a platform and set that as spawn origin
1049 flag.noalign = false;
1052 flag.movetype = MOVETYPE_TOSS;
1055 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1063 // NOTE: LEGACY CODE, needs to be re-written!
1065 void havocbot_calculate_middlepoint()
1069 vector fo = '0 0 0';
1072 f = ctf_worldflaglist;
1077 f = f.ctf_worldflagnext;
1081 havocbot_ctf_middlepoint = s * (1.0 / n);
1082 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1086 entity havocbot_ctf_find_flag(entity bot)
1089 f = ctf_worldflaglist;
1092 if (bot.team == f.team)
1094 f = f.ctf_worldflagnext;
1099 entity havocbot_ctf_find_enemy_flag(entity bot)
1102 f = ctf_worldflaglist;
1105 if (bot.team != f.team)
1107 f = f.ctf_worldflagnext;
1112 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1120 FOR_EACH_PLAYER(head)
1122 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1125 if(vlen(head.origin - org) < tc_radius)
1132 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1135 head = ctf_worldflaglist;
1138 if (self.team == head.team)
1140 head = head.ctf_worldflagnext;
1143 navigation_routerating(head, ratingscale, 10000);
1146 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1149 head = ctf_worldflaglist;
1152 if (self.team == head.team)
1154 head = head.ctf_worldflagnext;
1159 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1162 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1165 head = ctf_worldflaglist;
1168 if (self.team != head.team)
1170 head = head.ctf_worldflagnext;
1173 navigation_routerating(head, ratingscale, 10000);
1176 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1178 if (!bot_waypoints_for_items)
1180 havocbot_goalrating_ctf_enemyflag(ratingscale);
1186 head = havocbot_ctf_find_enemy_flag(self);
1191 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1194 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1198 mf = havocbot_ctf_find_flag(self);
1200 if(mf.ctf_status == FLAG_BASE)
1204 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1207 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1210 head = ctf_worldflaglist;
1213 // flag is out in the field
1214 if(head.ctf_status != FLAG_BASE)
1215 if(head.tag_entity==world) // dropped
1219 if(vlen(org-head.origin)<df_radius)
1220 navigation_routerating(head, ratingscale, 10000);
1223 navigation_routerating(head, ratingscale, 10000);
1226 head = head.ctf_worldflagnext;
1230 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1234 head = findchainfloat(bot_pickup, true);
1237 // gather health and armor only
1239 if (head.health || head.armorvalue)
1240 if (vlen(head.origin - org) < sradius)
1242 // get the value of the item
1243 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1245 navigation_routerating(head, t * ratingscale, 500);
1251 void havocbot_ctf_reset_role(entity bot)
1253 float cdefense, cmiddle, coffense;
1254 entity mf, ef, head;
1257 if(bot.deadflag != DEAD_NO)
1260 if(vlen(havocbot_ctf_middlepoint)==0)
1261 havocbot_calculate_middlepoint();
1264 if (bot.flagcarried)
1266 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1270 mf = havocbot_ctf_find_flag(bot);
1271 ef = havocbot_ctf_find_enemy_flag(bot);
1273 // Retrieve stolen flag
1274 if(mf.ctf_status!=FLAG_BASE)
1276 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1280 // If enemy flag is taken go to the middle to intercept pursuers
1281 if(ef.ctf_status!=FLAG_BASE)
1283 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1287 // if there is only me on the team switch to offense
1289 FOR_EACH_PLAYER(head)
1290 if(SAME_TEAM(head, bot))
1295 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1299 // Evaluate best position to take
1300 // Count mates on middle position
1301 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1303 // Count mates on defense position
1304 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1306 // Count mates on offense position
1307 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1309 if(cdefense<=coffense)
1310 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1311 else if(coffense<=cmiddle)
1312 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1314 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1317 void havocbot_role_ctf_carrier()
1319 if(self.deadflag != DEAD_NO)
1321 havocbot_ctf_reset_role(self);
1325 if (self.flagcarried == world)
1327 havocbot_ctf_reset_role(self);
1331 if (self.bot_strategytime < time)
1333 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1335 navigation_goalrating_start();
1336 havocbot_goalrating_ctf_ourbase(50000);
1339 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1341 navigation_goalrating_end();
1343 if (self.navigation_hasgoals)
1344 self.havocbot_cantfindflag = time + 10;
1345 else if (time > self.havocbot_cantfindflag)
1347 // Can't navigate to my own base, suicide!
1348 // TODO: drop it and wander around
1349 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1355 void havocbot_role_ctf_escort()
1359 if(self.deadflag != DEAD_NO)
1361 havocbot_ctf_reset_role(self);
1365 if (self.flagcarried)
1367 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1371 // If enemy flag is back on the base switch to previous role
1372 ef = havocbot_ctf_find_enemy_flag(self);
1373 if(ef.ctf_status==FLAG_BASE)
1375 self.havocbot_role = self.havocbot_previous_role;
1376 self.havocbot_role_timeout = 0;
1380 // If the flag carrier reached the base switch to defense
1381 mf = havocbot_ctf_find_flag(self);
1382 if(mf.ctf_status!=FLAG_BASE)
1383 if(vlen(ef.origin - mf.dropped_origin) < 300)
1385 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1389 // Set the role timeout if necessary
1390 if (!self.havocbot_role_timeout)
1392 self.havocbot_role_timeout = time + random() * 30 + 60;
1395 // If nothing happened just switch to previous role
1396 if (time > self.havocbot_role_timeout)
1398 self.havocbot_role = self.havocbot_previous_role;
1399 self.havocbot_role_timeout = 0;
1403 // Chase the flag carrier
1404 if (self.bot_strategytime < time)
1406 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1407 navigation_goalrating_start();
1408 havocbot_goalrating_ctf_enemyflag(30000);
1409 havocbot_goalrating_ctf_ourstolenflag(40000);
1410 havocbot_goalrating_items(10000, self.origin, 10000);
1411 navigation_goalrating_end();
1415 void havocbot_role_ctf_offense()
1420 if(self.deadflag != DEAD_NO)
1422 havocbot_ctf_reset_role(self);
1426 if (self.flagcarried)
1428 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1433 mf = havocbot_ctf_find_flag(self);
1434 ef = havocbot_ctf_find_enemy_flag(self);
1437 if(mf.ctf_status!=FLAG_BASE)
1440 pos = mf.tag_entity.origin;
1444 // Try to get it if closer than the enemy base
1445 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1447 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1452 // Escort flag carrier
1453 if(ef.ctf_status!=FLAG_BASE)
1456 pos = ef.tag_entity.origin;
1460 if(vlen(pos-mf.dropped_origin)>700)
1462 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1467 // About to fail, switch to middlefield
1470 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1474 // Set the role timeout if necessary
1475 if (!self.havocbot_role_timeout)
1476 self.havocbot_role_timeout = time + 120;
1478 if (time > self.havocbot_role_timeout)
1480 havocbot_ctf_reset_role(self);
1484 if (self.bot_strategytime < time)
1486 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1487 navigation_goalrating_start();
1488 havocbot_goalrating_ctf_ourstolenflag(50000);
1489 havocbot_goalrating_ctf_enemybase(20000);
1490 havocbot_goalrating_items(5000, self.origin, 1000);
1491 havocbot_goalrating_items(1000, self.origin, 10000);
1492 navigation_goalrating_end();
1496 // Retriever (temporary role):
1497 void havocbot_role_ctf_retriever()
1501 if(self.deadflag != DEAD_NO)
1503 havocbot_ctf_reset_role(self);
1507 if (self.flagcarried)
1509 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1513 // If flag is back on the base switch to previous role
1514 mf = havocbot_ctf_find_flag(self);
1515 if(mf.ctf_status==FLAG_BASE)
1517 havocbot_ctf_reset_role(self);
1521 if (!self.havocbot_role_timeout)
1522 self.havocbot_role_timeout = time + 20;
1524 if (time > self.havocbot_role_timeout)
1526 havocbot_ctf_reset_role(self);
1530 if (self.bot_strategytime < time)
1535 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1536 navigation_goalrating_start();
1537 havocbot_goalrating_ctf_ourstolenflag(50000);
1538 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1539 havocbot_goalrating_ctf_enemybase(30000);
1540 havocbot_goalrating_items(500, self.origin, rt_radius);
1541 navigation_goalrating_end();
1545 void havocbot_role_ctf_middle()
1549 if(self.deadflag != DEAD_NO)
1551 havocbot_ctf_reset_role(self);
1555 if (self.flagcarried)
1557 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1561 mf = havocbot_ctf_find_flag(self);
1562 if(mf.ctf_status!=FLAG_BASE)
1564 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1568 if (!self.havocbot_role_timeout)
1569 self.havocbot_role_timeout = time + 10;
1571 if (time > self.havocbot_role_timeout)
1573 havocbot_ctf_reset_role(self);
1577 if (self.bot_strategytime < time)
1581 org = havocbot_ctf_middlepoint;
1582 org.z = self.origin.z;
1584 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1585 navigation_goalrating_start();
1586 havocbot_goalrating_ctf_ourstolenflag(50000);
1587 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1588 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1589 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1590 havocbot_goalrating_items(2500, self.origin, 10000);
1591 havocbot_goalrating_ctf_enemybase(2500);
1592 navigation_goalrating_end();
1596 void havocbot_role_ctf_defense()
1600 if(self.deadflag != DEAD_NO)
1602 havocbot_ctf_reset_role(self);
1606 if (self.flagcarried)
1608 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1612 // If own flag was captured
1613 mf = havocbot_ctf_find_flag(self);
1614 if(mf.ctf_status!=FLAG_BASE)
1616 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1620 if (!self.havocbot_role_timeout)
1621 self.havocbot_role_timeout = time + 30;
1623 if (time > self.havocbot_role_timeout)
1625 havocbot_ctf_reset_role(self);
1628 if (self.bot_strategytime < time)
1633 org = mf.dropped_origin;
1634 mp_radius = havocbot_ctf_middlepoint_radius;
1636 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1637 navigation_goalrating_start();
1639 // if enemies are closer to our base, go there
1640 entity head, closestplayer = world;
1641 float distance, bestdistance = 10000;
1642 FOR_EACH_PLAYER(head)
1644 if(head.deadflag!=DEAD_NO)
1647 distance = vlen(org - head.origin);
1648 if(distance<bestdistance)
1650 closestplayer = head;
1651 bestdistance = distance;
1656 if(DIFF_TEAM(closestplayer, self))
1657 if(vlen(org - self.origin)>1000)
1658 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1659 havocbot_goalrating_ctf_ourbase(30000);
1661 havocbot_goalrating_ctf_ourstolenflag(20000);
1662 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1663 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1664 havocbot_goalrating_items(10000, org, mp_radius);
1665 havocbot_goalrating_items(5000, self.origin, 10000);
1666 navigation_goalrating_end();
1670 void havocbot_role_ctf_setrole(entity bot, float role)
1672 dprint(strcat(bot.netname," switched to "));
1675 case HAVOCBOT_CTF_ROLE_CARRIER:
1677 bot.havocbot_role = havocbot_role_ctf_carrier;
1678 bot.havocbot_role_timeout = 0;
1679 bot.havocbot_cantfindflag = time + 10;
1680 bot.bot_strategytime = 0;
1682 case HAVOCBOT_CTF_ROLE_DEFENSE:
1684 bot.havocbot_role = havocbot_role_ctf_defense;
1685 bot.havocbot_role_timeout = 0;
1687 case HAVOCBOT_CTF_ROLE_MIDDLE:
1689 bot.havocbot_role = havocbot_role_ctf_middle;
1690 bot.havocbot_role_timeout = 0;
1692 case HAVOCBOT_CTF_ROLE_OFFENSE:
1694 bot.havocbot_role = havocbot_role_ctf_offense;
1695 bot.havocbot_role_timeout = 0;
1697 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1698 dprint("retriever");
1699 bot.havocbot_previous_role = bot.havocbot_role;
1700 bot.havocbot_role = havocbot_role_ctf_retriever;
1701 bot.havocbot_role_timeout = time + 10;
1702 bot.bot_strategytime = 0;
1704 case HAVOCBOT_CTF_ROLE_ESCORT:
1706 bot.havocbot_previous_role = bot.havocbot_role;
1707 bot.havocbot_role = havocbot_role_ctf_escort;
1708 bot.havocbot_role_timeout = time + 30;
1709 bot.bot_strategytime = 0;
1720 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1724 // initially clear items so they can be set as necessary later.
1725 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1726 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1728 // scan through all the flags and notify the client about them
1729 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1731 switch(flag.ctf_status)
1736 if((flag.owner == self) || (flag.pass_sender == self))
1737 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1739 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1744 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1750 // item for stopping players from capturing the flag too often
1751 if(self.ctf_captureshielded)
1752 self.items |= IT_CTF_SHIELDED;
1754 // update the health of the flag carrier waypointsprite
1755 if(self.wps_flagcarrier)
1756 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1761 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1763 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1765 if(frag_target == frag_attacker) // damage done to yourself
1767 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1768 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1770 else // damage done to everyone else
1772 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1773 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1776 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1778 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)))
1779 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1781 frag_target.wps_helpme_time = time;
1782 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1784 // todo: add notification for when flag carrier needs help?
1789 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1791 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1793 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1794 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1797 if(frag_target.flagcarried)
1798 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1803 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1806 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1809 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1811 entity flag; // temporary entity for the search method
1813 if(self.flagcarried)
1814 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1816 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1818 if(flag.pass_sender == self) { flag.pass_sender = world; }
1819 if(flag.pass_target == self) { flag.pass_target = world; }
1820 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1826 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1828 if(self.flagcarried)
1829 if(!autocvar_g_ctf_portalteleport)
1830 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1835 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1837 if(MUTATOR_RETURNVALUE || gameover) { return false; }
1839 entity player = self;
1841 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1843 // pass the flag to a team mate
1844 if(autocvar_g_ctf_pass)
1846 entity head, closest_target = world;
1847 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
1849 while(head) // find the closest acceptable target to pass to
1851 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1852 if(head != player && SAME_TEAM(head, player))
1853 if(!head.speedrunning && !head.vehicle)
1855 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1856 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1857 vector passer_center = CENTER_OR_VIEWOFS(player);
1859 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1861 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1863 if(IS_BOT_CLIENT(head))
1865 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1866 ctf_Handle_Throw(head, player, DROP_PASS);
1870 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1871 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1873 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1876 else if(player.flagcarried)
1880 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1881 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1882 { closest_target = head; }
1884 else { closest_target = head; }
1891 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
1894 // throw the flag in front of you
1895 if(autocvar_g_ctf_throw && player.flagcarried)
1897 if(player.throw_count == -1)
1899 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1901 player.throw_prevtime = time;
1902 player.throw_count = 1;
1903 ctf_Handle_Throw(player, world, DROP_THROW);
1908 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1914 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1915 else { player.throw_count += 1; }
1916 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1918 player.throw_prevtime = time;
1919 ctf_Handle_Throw(player, world, DROP_THROW);
1928 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1930 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1932 self.wps_helpme_time = time;
1933 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1935 else // create a normal help me waypointsprite
1937 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');
1938 WaypointSprite_Ping(self.wps_helpme);
1944 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1946 if(vh_player.flagcarried)
1948 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1950 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1954 setattachment(vh_player.flagcarried, vh_vehicle, "");
1955 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1956 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1957 //vh_player.flagcarried.angles = '0 0 0';
1965 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1967 if(vh_player.flagcarried)
1969 setattachment(vh_player.flagcarried, vh_player, "");
1970 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1971 vh_player.flagcarried.scale = FLAG_SCALE;
1972 vh_player.flagcarried.angles = '0 0 0';
1979 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1981 if(self.flagcarried)
1983 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1984 ctf_RespawnFlag(self.flagcarried);
1991 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1993 entity flag; // temporary entity for the search method
1995 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1997 switch(flag.ctf_status)
2002 // lock the flag, game is over
2003 flag.movetype = MOVETYPE_NONE;
2004 flag.takedamage = DAMAGE_NO;
2005 flag.solid = SOLID_NOT;
2006 flag.nextthink = false; // stop thinking
2008 //dprint("stopping the ", flag.netname, " from moving.\n");
2016 // do nothing for these flags
2025 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2027 havocbot_ctf_reset_role(self);
2036 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2037 CTF Starting point for a player in team one (Red).
2038 Keys: "angle" viewing angle when spawning. */
2039 void spawnfunc_info_player_team1()
2041 if(g_assault) { remove(self); return; }
2043 self.team = NUM_TEAM_1; // red
2044 spawnfunc_info_player_deathmatch();
2048 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2049 CTF Starting point for a player in team two (Blue).
2050 Keys: "angle" viewing angle when spawning. */
2051 void spawnfunc_info_player_team2()
2053 if(g_assault) { remove(self); return; }
2055 self.team = NUM_TEAM_2; // blue
2056 spawnfunc_info_player_deathmatch();
2059 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2060 CTF Starting point for a player in team three (Yellow).
2061 Keys: "angle" viewing angle when spawning. */
2062 void spawnfunc_info_player_team3()
2064 if(g_assault) { remove(self); return; }
2066 self.team = NUM_TEAM_3; // yellow
2067 spawnfunc_info_player_deathmatch();
2071 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2072 CTF Starting point for a player in team four (Purple).
2073 Keys: "angle" viewing angle when spawning. */
2074 void spawnfunc_info_player_team4()
2076 if(g_assault) { remove(self); return; }
2078 self.team = NUM_TEAM_4; // purple
2079 spawnfunc_info_player_deathmatch();
2082 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2083 CTF flag for team one (Red).
2085 "angle" Angle the flag will point (minus 90 degrees)...
2086 "model" model to use, note this needs red and blue as skins 0 and 1...
2087 "noise" sound played when flag is picked up...
2088 "noise1" sound played when flag is returned by a teammate...
2089 "noise2" sound played when flag is captured...
2090 "noise3" sound played when flag is lost in the field and respawns itself...
2091 "noise4" sound played when flag is dropped by a player...
2092 "noise5" sound played when flag touches the ground... */
2093 void spawnfunc_item_flag_team1()
2095 if(!g_ctf) { remove(self); return; }
2097 ctf_FlagSetup(1, self); // 1 = red
2100 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2101 CTF flag for team two (Blue).
2103 "angle" Angle the flag will point (minus 90 degrees)...
2104 "model" model to use, note this needs red and blue as skins 0 and 1...
2105 "noise" sound played when flag is picked up...
2106 "noise1" sound played when flag is returned by a teammate...
2107 "noise2" sound played when flag is captured...
2108 "noise3" sound played when flag is lost in the field and respawns itself...
2109 "noise4" sound played when flag is dropped by a player...
2110 "noise5" sound played when flag touches the ground... */
2111 void spawnfunc_item_flag_team2()
2113 if(!g_ctf) { remove(self); return; }
2115 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2118 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2119 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2120 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.
2122 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2123 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2124 void spawnfunc_ctf_team()
2126 if(!g_ctf) { remove(self); return; }
2128 self.classname = "ctf_team";
2129 self.team = self.cnt + 1;
2132 // compatibility for quake maps
2133 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2134 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2135 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2136 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2137 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2138 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2146 void ctf_ScoreRules()
2148 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, true);
2149 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2150 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2151 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2152 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2153 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2154 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2155 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2156 ScoreRules_basics_end();
2159 // code from here on is just to support maps that don't have flag and team entities
2160 void ctf_SpawnTeam (string teamname, float teamcolor)
2165 self.classname = "ctf_team";
2166 self.netname = teamname;
2167 self.cnt = teamcolor;
2169 spawnfunc_ctf_team();
2174 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2176 // if no teams are found, spawn defaults
2177 if(find(world, classname, "ctf_team") == world)
2179 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2180 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2181 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2187 void ctf_Initialize()
2189 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2191 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2192 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2193 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2195 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2199 MUTATOR_DEFINITION(gamemode_ctf)
2201 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2202 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2203 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2204 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2205 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2206 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2207 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2208 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2209 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2210 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2211 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2212 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2213 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2214 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2218 if(time > 1) // game loads at time 1
2219 error("This is a game type and it cannot be added at runtime.");
2223 MUTATOR_ONROLLBACK_OR_REMOVE
2225 // we actually cannot roll back ctf_Initialize here
2226 // BUT: we don't need to! If this gets called, adding always
2232 print("This is a game type and it cannot be removed at runtime.");