1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 void ctf_CaptureRecord(entity flag, entity player)
25 float cap_record = ctf_captimerecord;
26 float cap_time = (time - flag.ctf_pickuptime);
27 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
30 if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
31 else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
32 else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
34 // write that shit in the database
35 if((!ctf_captimerecord) || (cap_time < cap_record))
37 ctf_captimerecord = cap_time;
38 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
39 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
40 write_recordmarker(player, (time - cap_time), cap_time);
44 void ctf_FlagcarrierWaypoints(entity player)
46 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
47 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
48 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
49 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
52 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
54 float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis
55 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
56 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
57 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
60 if(current_height) // make sure we can actually do this arcing path
62 targpos = (to + ('0 0 1' * current_height));
63 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
64 if(trace_fraction < 1)
66 //print("normal arc line failed, trying to find new pos...");
67 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
68 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
69 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
70 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
71 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
74 else { targpos = to; }
76 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
78 vector desired_direction = normalize(targpos - from);
79 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
80 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
83 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
85 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
87 // directional tracing only
89 makevectors(passer_angle);
91 // find the closest point on the enemy to the center of the attack
92 float ang; // angle between shotdir and h
93 float h; // hypotenuse, which is the distance between attacker to head
94 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
96 h = vlen(head_center - passer_center);
97 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
100 vector nearest_on_line = (passer_center + a * v_forward);
101 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
103 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
104 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
106 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
111 else { return TRUE; }
115 // =======================
116 // CaptureShield Functions
117 // =======================
119 float ctf_CaptureShield_CheckStatus(entity p)
123 float players_worseeq, players_total;
125 if(ctf_captureshield_max_ratio <= 0)
128 s = PlayerScore_Add(p, SP_SCORE, 0);
129 if(s >= -ctf_captureshield_min_negscore)
132 players_total = players_worseeq = 0;
137 se = PlayerScore_Add(e, SP_SCORE, 0);
143 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
144 // use this rule here
146 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
152 void ctf_CaptureShield_Update(entity player, float wanted_status)
154 float updated_status = ctf_CaptureShield_CheckStatus(player);
155 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
157 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
158 player.ctf_captureshielded = updated_status;
162 float ctf_CaptureShield_Customize()
164 if(!other.ctf_captureshielded) { return FALSE; }
165 if(SAME_TEAM(self, other)) { return FALSE; }
170 void ctf_CaptureShield_Touch()
172 if(!other.ctf_captureshielded) { return; }
173 if(SAME_TEAM(self, other)) { return; }
175 vector mymid = (self.absmin + self.absmax) * 0.5;
176 vector othermid = (other.absmin + other.absmax) * 0.5;
178 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
179 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
182 void ctf_CaptureShield_Spawn(entity flag)
184 entity shield = spawn();
187 shield.team = self.team;
188 shield.touch = ctf_CaptureShield_Touch;
189 shield.customizeentityforclient = ctf_CaptureShield_Customize;
190 shield.classname = "ctf_captureshield";
191 shield.effects = EF_ADDITIVE;
192 shield.movetype = MOVETYPE_NOCLIP;
193 shield.solid = SOLID_TRIGGER;
194 shield.avelocity = '7 0 11';
197 setorigin(shield, self.origin);
198 setmodel(shield, "models/ctf/shield.md3");
199 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
203 // ====================
204 // Drop/Pass/Throw Code
205 // ====================
207 void ctf_Handle_Drop(entity flag, entity player, float droptype)
210 player = (player ? player : flag.pass_sender);
213 flag.movetype = MOVETYPE_TOSS;
214 flag.takedamage = DAMAGE_YES;
215 flag.angles = '0 0 0';
216 flag.health = flag.max_flag_health;
217 flag.ctf_droptime = time;
218 flag.ctf_dropper = player;
219 flag.ctf_status = FLAG_DROPPED;
221 // messages and sounds
222 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
223 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
224 ctf_EventLog("dropped", player.team, player);
227 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
228 PlayerScore_Add(player, SP_CTF_DROPS, 1);
231 if(autocvar_g_ctf_flag_dropped_waypoint)
232 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
234 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
236 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
237 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
240 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
242 if(droptype == DROP_PASS)
244 flag.pass_distance = 0;
245 flag.pass_sender = world;
246 flag.pass_target = world;
250 void ctf_Handle_Retrieve(entity flag, entity player)
252 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
253 entity sender = flag.pass_sender;
255 // transfer flag to player
257 flag.owner.flagcarried = flag;
260 setattachment(flag, player, "");
261 setorigin(flag, FLAG_CARRY_OFFSET);
262 flag.movetype = MOVETYPE_NONE;
263 flag.takedamage = DAMAGE_NO;
264 flag.solid = SOLID_NOT;
265 flag.angles = '0 0 0';
266 flag.ctf_status = FLAG_CARRY;
268 // messages and sounds
269 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
270 ctf_EventLog("receive", flag.team, player);
272 FOR_EACH_REALPLAYER(tmp_player)
274 if(tmp_player == sender)
275 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
276 else if(tmp_player == player)
277 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
278 else if(SAME_TEAM(tmp_player, sender))
279 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
282 // create new waypoint
283 ctf_FlagcarrierWaypoints(player);
285 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
286 player.throw_antispam = sender.throw_antispam;
288 flag.pass_distance = 0;
289 flag.pass_sender = world;
290 flag.pass_target = world;
293 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
295 entity flag = player.flagcarried;
296 vector targ_origin, flag_velocity;
298 if(!flag) { return; }
299 if((droptype == DROP_PASS) && !receiver) { return; }
301 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
304 setattachment(flag, world, "");
305 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
306 flag.owner.flagcarried = world;
308 flag.solid = SOLID_TRIGGER;
309 flag.ctf_dropper = player;
310 flag.ctf_droptime = time;
312 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
319 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
320 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
321 WarpZone_RefSys_Copy(flag, receiver);
322 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
323 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
325 flag.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' * player.origin_x) + ('0 1 0' * player.origin_y))); // for the sake of this check, exclude Z axis
326 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
329 flag.movetype = MOVETYPE_FLY;
330 flag.takedamage = DAMAGE_NO;
331 flag.pass_sender = player;
332 flag.pass_target = receiver;
333 flag.ctf_status = FLAG_PASSING;
336 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
337 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
338 ctf_EventLog("pass", flag.team, player);
344 makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
346 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
347 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
348 ctf_Handle_Drop(flag, player, droptype);
354 flag.velocity = '0 0 0'; // do nothing
361 flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
362 ctf_Handle_Drop(flag, player, droptype);
367 // kill old waypointsprite
368 WaypointSprite_Ping(player.wps_flagcarrier);
369 WaypointSprite_Kill(player.wps_flagcarrier);
371 if(player.wps_enemyflagcarrier)
372 WaypointSprite_Kill(player.wps_enemyflagcarrier);
375 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
383 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
385 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
386 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
387 float old_time, new_time;
389 if (!player) { return; } // without someone to give the reward to, we can't possibly cap
391 // messages and sounds
392 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
393 ctf_CaptureRecord(enemy_flag, player);
394 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
398 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
399 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
404 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
405 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
407 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
408 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
409 if(!old_time || new_time < old_time)
410 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
413 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
414 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
417 if(capturetype == CAPTURE_NORMAL)
419 WaypointSprite_Kill(player.wps_flagcarrier);
420 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
422 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
423 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
427 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
428 ctf_RespawnFlag(enemy_flag);
431 void ctf_Handle_Return(entity flag, entity player)
433 // messages and sounds
434 if(IS_PLAYER(player))
435 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
437 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), (player.flags & FL_MONSTER) ? player.monster_name : player.netname);
438 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
439 ctf_EventLog("return", flag.team, player);
442 if(IS_PLAYER(player))
444 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
445 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
448 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
452 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
453 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
454 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
458 ctf_RespawnFlag(flag);
461 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
464 float pickup_dropped_score; // used to calculate dropped pickup score
466 // attach the flag to the player
468 player.flagcarried = flag;
469 setattachment(flag, player, "");
470 setorigin(flag, FLAG_CARRY_OFFSET);
473 flag.movetype = MOVETYPE_NONE;
474 flag.takedamage = DAMAGE_NO;
475 flag.solid = SOLID_NOT;
476 flag.angles = '0 0 0';
477 flag.ctf_status = FLAG_CARRY;
481 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
482 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
486 // messages and sounds
487 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
488 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
489 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
491 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
492 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
494 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
497 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
502 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
503 ctf_EventLog("steal", flag.team, player);
509 pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
510 pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
511 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
512 PlayerTeamScore_AddScore(player, pickup_dropped_score);
513 ctf_EventLog("pickup", flag.team, player);
521 if(pickuptype == PICKUP_BASE)
523 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
524 if((player.speedrunning) && (ctf_captimerecord))
525 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
529 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
532 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
533 ctf_FlagcarrierWaypoints(player);
534 WaypointSprite_Ping(player.wps_flagcarrier);
538 // ===================
539 // Main Flag Functions
540 // ===================
542 void ctf_CheckFlagReturn(entity flag, float returntype)
544 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
546 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
548 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
552 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
553 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
554 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
555 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
559 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
561 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
562 ctf_EventLog("returned", flag.team, world);
563 ctf_RespawnFlag(flag);
568 void ctf_CheckStalemate(void)
571 float stale_red_flags = 0, stale_blue_flags = 0;
574 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
576 // build list of stale flags
577 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
579 if(autocvar_g_ctf_stalemate)
580 if(tmp_entity.ctf_status != FLAG_BASE)
581 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
583 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
584 ctf_staleflaglist = tmp_entity;
586 switch(tmp_entity.team)
588 case NUM_TEAM_1: ++stale_red_flags; break;
589 case NUM_TEAM_2: ++stale_blue_flags; break;
594 if(stale_red_flags && stale_blue_flags)
595 ctf_stalemate = TRUE;
596 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
597 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
598 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
599 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
601 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
604 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
606 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
607 WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
610 if (!wpforenemy_announced)
612 FOR_EACH_REALPLAYER(tmp_entity)
613 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
615 wpforenemy_announced = TRUE;
620 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
622 if(ITEM_DAMAGE_NEEDKILL(deathtype))
624 // automatically kill the flag and return it
626 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
629 if(autocvar_g_ctf_flag_return_damage)
631 // reduce health and check if it should be returned
632 self.health = self.health - damage;
633 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
643 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
646 if(self == ctf_worldflaglist) // only for the first flag
647 FOR_EACH_CLIENT(tmp_entity)
648 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
651 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
652 dprint("wtf the flag got squashed?\n");
653 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
654 if(!trace_startsolid) // can we resize it without getting stuck?
655 setsize(self, FLAG_MIN, FLAG_MAX); }
657 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
661 self.angles = '0 0 0';
669 switch(self.ctf_status)
673 if(autocvar_g_ctf_dropped_capture_radius)
675 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
676 if(tmp_entity.ctf_status == FLAG_DROPPED)
677 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
678 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
679 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
686 if(autocvar_g_ctf_flag_dropped_floatinwater)
688 vector midpoint = ((self.absmin + self.absmax) * 0.5);
689 if(pointcontents(midpoint) == CONTENT_WATER)
691 self.velocity = self.velocity * 0.5;
693 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
694 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
696 { self.movetype = MOVETYPE_FLY; }
698 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
700 if(autocvar_g_ctf_flag_return_dropped)
702 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
705 ctf_CheckFlagReturn(self, RETURN_DROPPED);
709 if(autocvar_g_ctf_flag_return_time)
711 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
712 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
720 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
723 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
727 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
731 if(autocvar_g_ctf_stalemate)
733 if(time >= wpforenemy_nextthink)
735 ctf_CheckStalemate();
736 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
744 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
745 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
746 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
748 if((self.pass_target == world)
749 || (self.pass_target.deadflag != DEAD_NO)
750 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
751 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
752 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
754 // give up, pass failed
755 ctf_Handle_Drop(self, world, DROP_PASS);
759 // still a viable target, go for it
760 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
765 default: // this should never happen
767 dprint("ctf_FlagThink(): Flag exists with no status?\n");
775 if(gameover) { return; }
777 entity toucher = other;
779 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
780 if(ITEM_TOUCH_NEEDKILL())
783 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
787 // special touch behaviors
788 if(toucher.frozen) { return; }
789 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
791 if(autocvar_g_ctf_allow_vehicle_touch)
792 toucher = toucher.owner; // the player is actually the vehicle owner, not other
794 return; // do nothing
796 else if(toucher.flags & FL_MONSTER)
798 if(!autocvar_g_ctf_allow_monster_touch)
799 return; // do nothing
801 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
803 if(time > self.wait) // if we haven't in a while, play a sound/effect
805 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
806 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
807 self.wait = time + FLAG_TOUCHRATE;
811 else if(toucher.deadflag != DEAD_NO) { return; }
813 switch(self.ctf_status)
817 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && !(toucher.flags & FL_MONSTER))
818 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
819 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && !(toucher.flags & FL_MONSTER))
820 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
826 if(SAME_TEAM(toucher, self))
827 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
828 else if(!(toucher.flags & FL_MONSTER) && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
829 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
835 dprint("Someone touched a flag even though it was being carried?\n");
841 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
843 if(DIFF_TEAM(toucher, self.pass_sender))
844 ctf_Handle_Return(self, toucher);
846 ctf_Handle_Retrieve(self, toucher);
854 void ctf_RespawnFlag(entity flag)
856 // check for flag respawn being called twice in a row
857 if(flag.last_respawn > time - 0.5)
858 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
860 flag.last_respawn = time;
862 // reset the player (if there is one)
863 if((flag.owner) && (flag.owner.flagcarried == flag))
865 if(flag.owner.wps_enemyflagcarrier)
866 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
868 WaypointSprite_Kill(flag.wps_flagcarrier);
870 flag.owner.flagcarried = world;
872 if(flag.speedrunning)
873 ctf_FakeTimeLimit(flag.owner, -1);
876 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
877 { WaypointSprite_Kill(flag.wps_flagdropped); }
880 setattachment(flag, world, "");
881 setorigin(flag, flag.ctf_spawnorigin);
883 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
884 flag.takedamage = DAMAGE_NO;
885 flag.health = flag.max_flag_health;
886 flag.solid = SOLID_TRIGGER;
887 flag.velocity = '0 0 0';
888 flag.angles = flag.mangle;
889 flag.flags = FL_ITEM | FL_NOTARGET;
891 flag.ctf_status = FLAG_BASE;
893 flag.pass_distance = 0;
894 flag.pass_sender = world;
895 flag.pass_target = world;
896 flag.ctf_dropper = world;
897 flag.ctf_pickuptime = 0;
898 flag.ctf_droptime = 0;
904 if(IS_PLAYER(self.owner))
905 ctf_Handle_Throw(self.owner, world, DROP_RESET);
907 ctf_RespawnFlag(self);
910 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
913 waypoint_spawnforitem_force(self, self.origin);
914 self.nearestwaypointtimeout = 0; // activate waypointing again
915 self.bot_basewaypoint = self.nearestwaypoint;
918 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
919 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
921 // captureshield setup
922 ctf_CaptureShield_Spawn(self);
925 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
928 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.
929 self = flag; // for later usage with droptofloor()
932 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
933 ctf_worldflaglist = flag;
935 setattachment(flag, world, "");
937 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
938 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
939 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
940 flag.classname = "item_flag_team";
941 flag.target = "###item###"; // wut?
942 flag.flags = FL_ITEM | FL_NOTARGET;
943 flag.solid = SOLID_TRIGGER;
944 flag.takedamage = DAMAGE_NO;
945 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
946 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
947 flag.health = flag.max_flag_health;
948 flag.event_damage = ctf_FlagDamage;
949 flag.pushable = TRUE;
950 flag.teleportable = TELEPORT_NORMAL;
951 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
952 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
953 flag.velocity = '0 0 0';
954 flag.mangle = flag.angles;
955 flag.reset = ctf_Reset;
956 flag.touch = ctf_FlagTouch;
957 flag.think = ctf_FlagThink;
958 flag.nextthink = time + FLAG_THINKRATE;
959 flag.ctf_status = FLAG_BASE;
962 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
963 if(!flag.scale) { flag.scale = FLAG_SCALE; }
964 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
965 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
966 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
967 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
970 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
971 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
972 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
973 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.
974 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
975 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
976 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
979 precache_sound(flag.snd_flag_taken);
980 precache_sound(flag.snd_flag_returned);
981 precache_sound(flag.snd_flag_capture);
982 precache_sound(flag.snd_flag_respawn);
983 precache_sound(flag.snd_flag_dropped);
984 precache_sound(flag.snd_flag_touch);
985 precache_sound(flag.snd_flag_pass);
986 precache_model(flag.model);
987 precache_model("models/ctf/shield.md3");
988 precache_model("models/ctf/shockwavetransring.md3");
991 setmodel(flag, flag.model); // precision set below
992 setsize(flag, FLAG_MIN, FLAG_MAX);
993 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
995 if(autocvar_g_ctf_flag_glowtrails)
997 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1002 flag.effects |= EF_LOWPRECISION;
1003 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1004 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1007 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1009 flag.dropped_origin = flag.origin;
1010 flag.noalign = TRUE;
1011 flag.movetype = MOVETYPE_NONE;
1013 else // drop to floor, automatically find a platform and set that as spawn origin
1015 flag.noalign = FALSE;
1018 flag.movetype = MOVETYPE_TOSS;
1021 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1029 // NOTE: LEGACY CODE, needs to be re-written!
1031 void havocbot_calculate_middlepoint()
1035 vector fo = '0 0 0';
1038 f = ctf_worldflaglist;
1043 f = f.ctf_worldflagnext;
1047 havocbot_ctf_middlepoint = s * (1.0 / n);
1048 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1052 entity havocbot_ctf_find_flag(entity bot)
1055 f = ctf_worldflaglist;
1058 if (bot.team == f.team)
1060 f = f.ctf_worldflagnext;
1065 entity havocbot_ctf_find_enemy_flag(entity bot)
1068 f = ctf_worldflaglist;
1071 if (bot.team != f.team)
1073 f = f.ctf_worldflagnext;
1078 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1086 FOR_EACH_PLAYER(head)
1088 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1091 if(vlen(head.origin - org) < tc_radius)
1098 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1101 head = ctf_worldflaglist;
1104 if (self.team == head.team)
1106 head = head.ctf_worldflagnext;
1109 navigation_routerating(head, ratingscale, 10000);
1112 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1115 head = ctf_worldflaglist;
1118 if (self.team == head.team)
1120 head = head.ctf_worldflagnext;
1125 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1128 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1131 head = ctf_worldflaglist;
1134 if (self.team != head.team)
1136 head = head.ctf_worldflagnext;
1139 navigation_routerating(head, ratingscale, 10000);
1142 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1144 if (!bot_waypoints_for_items)
1146 havocbot_goalrating_ctf_enemyflag(ratingscale);
1152 head = havocbot_ctf_find_enemy_flag(self);
1157 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1160 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1164 mf = havocbot_ctf_find_flag(self);
1166 if(mf.ctf_status == FLAG_BASE)
1170 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1173 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1176 head = ctf_worldflaglist;
1179 // flag is out in the field
1180 if(head.ctf_status != FLAG_BASE)
1181 if(head.tag_entity==world) // dropped
1185 if(vlen(org-head.origin)<df_radius)
1186 navigation_routerating(head, ratingscale, 10000);
1189 navigation_routerating(head, ratingscale, 10000);
1192 head = head.ctf_worldflagnext;
1196 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1200 head = findchainfloat(bot_pickup, TRUE);
1203 // gather health and armor only
1205 if (head.health || head.armorvalue)
1206 if (vlen(head.origin - org) < sradius)
1208 // get the value of the item
1209 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1211 navigation_routerating(head, t * ratingscale, 500);
1217 void havocbot_ctf_reset_role(entity bot)
1219 float cdefense, cmiddle, coffense;
1220 entity mf, ef, head;
1223 if(bot.deadflag != DEAD_NO)
1226 if(vlen(havocbot_ctf_middlepoint)==0)
1227 havocbot_calculate_middlepoint();
1230 if (bot.flagcarried)
1232 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1236 mf = havocbot_ctf_find_flag(bot);
1237 ef = havocbot_ctf_find_enemy_flag(bot);
1239 // Retrieve stolen flag
1240 if(mf.ctf_status!=FLAG_BASE)
1242 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1246 // If enemy flag is taken go to the middle to intercept pursuers
1247 if(ef.ctf_status!=FLAG_BASE)
1249 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1253 // if there is only me on the team switch to offense
1255 FOR_EACH_PLAYER(head)
1256 if(head.team==bot.team)
1261 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1265 // Evaluate best position to take
1266 // Count mates on middle position
1267 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1269 // Count mates on defense position
1270 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1272 // Count mates on offense position
1273 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1275 if(cdefense<=coffense)
1276 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1277 else if(coffense<=cmiddle)
1278 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1280 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1283 void havocbot_role_ctf_carrier()
1285 if(self.deadflag != DEAD_NO)
1287 havocbot_ctf_reset_role(self);
1291 if (self.flagcarried == world)
1293 havocbot_ctf_reset_role(self);
1297 if (self.bot_strategytime < time)
1299 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1301 navigation_goalrating_start();
1302 havocbot_goalrating_ctf_ourbase(50000);
1305 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1307 navigation_goalrating_end();
1309 if (self.navigation_hasgoals)
1310 self.havocbot_cantfindflag = time + 10;
1311 else if (time > self.havocbot_cantfindflag)
1313 // Can't navigate to my own base, suicide!
1314 // TODO: drop it and wander around
1315 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1321 void havocbot_role_ctf_escort()
1325 if(self.deadflag != DEAD_NO)
1327 havocbot_ctf_reset_role(self);
1331 if (self.flagcarried)
1333 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1337 // If enemy flag is back on the base switch to previous role
1338 ef = havocbot_ctf_find_enemy_flag(self);
1339 if(ef.ctf_status==FLAG_BASE)
1341 self.havocbot_role = self.havocbot_previous_role;
1342 self.havocbot_role_timeout = 0;
1346 // If the flag carrier reached the base switch to defense
1347 mf = havocbot_ctf_find_flag(self);
1348 if(mf.ctf_status!=FLAG_BASE)
1349 if(vlen(ef.origin - mf.dropped_origin) < 300)
1351 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1355 // Set the role timeout if necessary
1356 if (!self.havocbot_role_timeout)
1358 self.havocbot_role_timeout = time + random() * 30 + 60;
1361 // If nothing happened just switch to previous role
1362 if (time > self.havocbot_role_timeout)
1364 self.havocbot_role = self.havocbot_previous_role;
1365 self.havocbot_role_timeout = 0;
1369 // Chase the flag carrier
1370 if (self.bot_strategytime < time)
1372 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1373 navigation_goalrating_start();
1374 havocbot_goalrating_ctf_enemyflag(30000);
1375 havocbot_goalrating_ctf_ourstolenflag(40000);
1376 havocbot_goalrating_items(10000, self.origin, 10000);
1377 navigation_goalrating_end();
1381 void havocbot_role_ctf_offense()
1386 if(self.deadflag != DEAD_NO)
1388 havocbot_ctf_reset_role(self);
1392 if (self.flagcarried)
1394 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1399 mf = havocbot_ctf_find_flag(self);
1400 ef = havocbot_ctf_find_enemy_flag(self);
1403 if(mf.ctf_status!=FLAG_BASE)
1406 pos = mf.tag_entity.origin;
1410 // Try to get it if closer than the enemy base
1411 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1413 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1418 // Escort flag carrier
1419 if(ef.ctf_status!=FLAG_BASE)
1422 pos = ef.tag_entity.origin;
1426 if(vlen(pos-mf.dropped_origin)>700)
1428 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1433 // About to fail, switch to middlefield
1436 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1440 // Set the role timeout if necessary
1441 if (!self.havocbot_role_timeout)
1442 self.havocbot_role_timeout = time + 120;
1444 if (time > self.havocbot_role_timeout)
1446 havocbot_ctf_reset_role(self);
1450 if (self.bot_strategytime < time)
1452 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1453 navigation_goalrating_start();
1454 havocbot_goalrating_ctf_ourstolenflag(50000);
1455 havocbot_goalrating_ctf_enemybase(20000);
1456 havocbot_goalrating_items(5000, self.origin, 1000);
1457 havocbot_goalrating_items(1000, self.origin, 10000);
1458 navigation_goalrating_end();
1462 // Retriever (temporary role):
1463 void havocbot_role_ctf_retriever()
1467 if(self.deadflag != DEAD_NO)
1469 havocbot_ctf_reset_role(self);
1473 if (self.flagcarried)
1475 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1479 // If flag is back on the base switch to previous role
1480 mf = havocbot_ctf_find_flag(self);
1481 if(mf.ctf_status==FLAG_BASE)
1483 havocbot_ctf_reset_role(self);
1487 if (!self.havocbot_role_timeout)
1488 self.havocbot_role_timeout = time + 20;
1490 if (time > self.havocbot_role_timeout)
1492 havocbot_ctf_reset_role(self);
1496 if (self.bot_strategytime < time)
1501 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1502 navigation_goalrating_start();
1503 havocbot_goalrating_ctf_ourstolenflag(50000);
1504 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1505 havocbot_goalrating_ctf_enemybase(30000);
1506 havocbot_goalrating_items(500, self.origin, rt_radius);
1507 navigation_goalrating_end();
1511 void havocbot_role_ctf_middle()
1515 if(self.deadflag != DEAD_NO)
1517 havocbot_ctf_reset_role(self);
1521 if (self.flagcarried)
1523 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1527 mf = havocbot_ctf_find_flag(self);
1528 if(mf.ctf_status!=FLAG_BASE)
1530 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1534 if (!self.havocbot_role_timeout)
1535 self.havocbot_role_timeout = time + 10;
1537 if (time > self.havocbot_role_timeout)
1539 havocbot_ctf_reset_role(self);
1543 if (self.bot_strategytime < time)
1547 org = havocbot_ctf_middlepoint;
1548 org_z = self.origin_z;
1550 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1551 navigation_goalrating_start();
1552 havocbot_goalrating_ctf_ourstolenflag(50000);
1553 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1554 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1555 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1556 havocbot_goalrating_items(2500, self.origin, 10000);
1557 havocbot_goalrating_ctf_enemybase(2500);
1558 navigation_goalrating_end();
1562 void havocbot_role_ctf_defense()
1566 if(self.deadflag != DEAD_NO)
1568 havocbot_ctf_reset_role(self);
1572 if (self.flagcarried)
1574 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1578 // If own flag was captured
1579 mf = havocbot_ctf_find_flag(self);
1580 if(mf.ctf_status!=FLAG_BASE)
1582 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1586 if (!self.havocbot_role_timeout)
1587 self.havocbot_role_timeout = time + 30;
1589 if (time > self.havocbot_role_timeout)
1591 havocbot_ctf_reset_role(self);
1594 if (self.bot_strategytime < time)
1599 org = mf.dropped_origin;
1600 mp_radius = havocbot_ctf_middlepoint_radius;
1602 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1603 navigation_goalrating_start();
1605 // if enemies are closer to our base, go there
1606 entity head, closestplayer = world;
1607 float distance, bestdistance = 10000;
1608 FOR_EACH_PLAYER(head)
1610 if(head.deadflag!=DEAD_NO)
1613 distance = vlen(org - head.origin);
1614 if(distance<bestdistance)
1616 closestplayer = head;
1617 bestdistance = distance;
1622 if(closestplayer.team!=self.team)
1623 if(vlen(org - self.origin)>1000)
1624 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1625 havocbot_goalrating_ctf_ourbase(30000);
1627 havocbot_goalrating_ctf_ourstolenflag(20000);
1628 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1629 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1630 havocbot_goalrating_items(10000, org, mp_radius);
1631 havocbot_goalrating_items(5000, self.origin, 10000);
1632 navigation_goalrating_end();
1636 void havocbot_role_ctf_setrole(entity bot, float role)
1638 dprint(strcat(bot.netname," switched to "));
1641 case HAVOCBOT_CTF_ROLE_CARRIER:
1643 bot.havocbot_role = havocbot_role_ctf_carrier;
1644 bot.havocbot_role_timeout = 0;
1645 bot.havocbot_cantfindflag = time + 10;
1646 bot.bot_strategytime = 0;
1648 case HAVOCBOT_CTF_ROLE_DEFENSE:
1650 bot.havocbot_role = havocbot_role_ctf_defense;
1651 bot.havocbot_role_timeout = 0;
1653 case HAVOCBOT_CTF_ROLE_MIDDLE:
1655 bot.havocbot_role = havocbot_role_ctf_middle;
1656 bot.havocbot_role_timeout = 0;
1658 case HAVOCBOT_CTF_ROLE_OFFENSE:
1660 bot.havocbot_role = havocbot_role_ctf_offense;
1661 bot.havocbot_role_timeout = 0;
1663 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1664 dprint("retriever");
1665 bot.havocbot_previous_role = bot.havocbot_role;
1666 bot.havocbot_role = havocbot_role_ctf_retriever;
1667 bot.havocbot_role_timeout = time + 10;
1668 bot.bot_strategytime = 0;
1670 case HAVOCBOT_CTF_ROLE_ESCORT:
1672 bot.havocbot_previous_role = bot.havocbot_role;
1673 bot.havocbot_role = havocbot_role_ctf_escort;
1674 bot.havocbot_role_timeout = time + 30;
1675 bot.bot_strategytime = 0;
1686 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1690 // initially clear items so they can be set as necessary later.
1691 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1692 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1694 // scan through all the flags and notify the client about them
1695 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1697 switch(flag.ctf_status)
1702 if((flag.owner == self) || (flag.pass_sender == self))
1703 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1705 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1710 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1716 // item for stopping players from capturing the flag too often
1717 if(self.ctf_captureshielded)
1718 self.items |= IT_CTF_SHIELDED;
1720 // update the health of the flag carrier waypointsprite
1721 if(self.wps_flagcarrier)
1722 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1727 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1729 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1731 if(frag_target == frag_attacker) // damage done to yourself
1733 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1734 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1736 else // damage done to everyone else
1738 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1739 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1742 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1744 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)))
1745 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1747 frag_target.wps_helpme_time = time;
1748 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1750 // todo: add notification for when flag carrier needs help?
1755 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1757 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1759 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1760 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1763 if(frag_target.flagcarried)
1764 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1769 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1772 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1775 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1777 entity flag; // temporary entity for the search method
1779 if(self.flagcarried)
1780 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1782 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1784 if(flag.pass_sender == self) { flag.pass_sender = world; }
1785 if(flag.pass_target == self) { flag.pass_target = world; }
1786 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1792 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1794 if(self.flagcarried)
1795 if(!autocvar_g_ctf_portalteleport)
1796 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1801 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1803 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1805 entity player = self;
1807 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1809 // pass the flag to a team mate
1810 if(autocvar_g_ctf_pass)
1812 entity head, closest_target = world;
1813 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1815 while(head) // find the closest acceptable target to pass to
1817 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1818 if(head != player && SAME_TEAM(head, player))
1819 if(!head.speedrunning && !head.vehicle)
1821 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1822 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1823 vector passer_center = CENTER_OR_VIEWOFS(player);
1825 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1827 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1829 if(IS_BOT_CLIENT(head))
1831 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1832 ctf_Handle_Throw(head, player, DROP_PASS);
1836 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1837 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1839 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1842 else if(player.flagcarried)
1846 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1847 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1848 { closest_target = head; }
1850 else { closest_target = head; }
1857 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1860 // throw the flag in front of you
1861 if(autocvar_g_ctf_throw && player.flagcarried)
1863 if(player.throw_count == -1)
1865 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1867 player.throw_prevtime = time;
1868 player.throw_count = 1;
1869 ctf_Handle_Throw(player, world, DROP_THROW);
1874 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1880 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1881 else { player.throw_count += 1; }
1882 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1884 player.throw_prevtime = time;
1885 ctf_Handle_Throw(player, world, DROP_THROW);
1894 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1896 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1898 self.wps_helpme_time = time;
1899 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1901 else // create a normal help me waypointsprite
1903 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');
1904 WaypointSprite_Ping(self.wps_helpme);
1910 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1912 if(vh_player.flagcarried)
1914 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1916 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1920 setattachment(vh_player.flagcarried, vh_vehicle, "");
1921 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1922 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1923 //vh_player.flagcarried.angles = '0 0 0';
1931 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1933 if(vh_player.flagcarried)
1935 setattachment(vh_player.flagcarried, vh_player, "");
1936 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1937 vh_player.flagcarried.scale = FLAG_SCALE;
1938 vh_player.flagcarried.angles = '0 0 0';
1945 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1947 if(self.flagcarried)
1949 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1950 ctf_RespawnFlag(self.flagcarried);
1957 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1959 entity flag; // temporary entity for the search method
1961 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1963 switch(flag.ctf_status)
1968 // lock the flag, game is over
1969 flag.movetype = MOVETYPE_NONE;
1970 flag.takedamage = DAMAGE_NO;
1971 flag.solid = SOLID_NOT;
1972 flag.nextthink = FALSE; // stop thinking
1974 //dprint("stopping the ", flag.netname, " from moving.\n");
1982 // do nothing for these flags
1991 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1993 havocbot_ctf_reset_role(self);
2002 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2003 CTF Starting point for a player in team one (Red).
2004 Keys: "angle" viewing angle when spawning. */
2005 void spawnfunc_info_player_team1()
2007 if(g_assault) { remove(self); return; }
2009 self.team = NUM_TEAM_1; // red
2010 spawnfunc_info_player_deathmatch();
2014 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2015 CTF Starting point for a player in team two (Blue).
2016 Keys: "angle" viewing angle when spawning. */
2017 void spawnfunc_info_player_team2()
2019 if(g_assault) { remove(self); return; }
2021 self.team = NUM_TEAM_2; // blue
2022 spawnfunc_info_player_deathmatch();
2025 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2026 CTF Starting point for a player in team three (Yellow).
2027 Keys: "angle" viewing angle when spawning. */
2028 void spawnfunc_info_player_team3()
2030 if(g_assault) { remove(self); return; }
2032 self.team = NUM_TEAM_3; // yellow
2033 spawnfunc_info_player_deathmatch();
2037 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2038 CTF Starting point for a player in team four (Purple).
2039 Keys: "angle" viewing angle when spawning. */
2040 void spawnfunc_info_player_team4()
2042 if(g_assault) { remove(self); return; }
2044 self.team = NUM_TEAM_4; // purple
2045 spawnfunc_info_player_deathmatch();
2048 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2049 CTF flag for team one (Red).
2051 "angle" Angle the flag will point (minus 90 degrees)...
2052 "model" model to use, note this needs red and blue as skins 0 and 1...
2053 "noise" sound played when flag is picked up...
2054 "noise1" sound played when flag is returned by a teammate...
2055 "noise2" sound played when flag is captured...
2056 "noise3" sound played when flag is lost in the field and respawns itself...
2057 "noise4" sound played when flag is dropped by a player...
2058 "noise5" sound played when flag touches the ground... */
2059 void spawnfunc_item_flag_team1()
2061 if(!g_ctf) { remove(self); return; }
2063 ctf_FlagSetup(1, self); // 1 = red
2066 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2067 CTF flag for team two (Blue).
2069 "angle" Angle the flag will point (minus 90 degrees)...
2070 "model" model to use, note this needs red and blue as skins 0 and 1...
2071 "noise" sound played when flag is picked up...
2072 "noise1" sound played when flag is returned by a teammate...
2073 "noise2" sound played when flag is captured...
2074 "noise3" sound played when flag is lost in the field and respawns itself...
2075 "noise4" sound played when flag is dropped by a player...
2076 "noise5" sound played when flag touches the ground... */
2077 void spawnfunc_item_flag_team2()
2079 if(!g_ctf) { remove(self); return; }
2081 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2084 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2085 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2086 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.
2088 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2089 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2090 void spawnfunc_ctf_team()
2092 if(!g_ctf) { remove(self); return; }
2094 self.classname = "ctf_team";
2095 self.team = self.cnt + 1;
2098 // compatibility for quake maps
2099 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2100 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2101 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2102 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2103 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2104 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2112 void ctf_ScoreRules()
2114 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2115 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2116 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2117 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2118 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2119 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2120 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2121 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2122 ScoreRules_basics_end();
2125 // code from here on is just to support maps that don't have flag and team entities
2126 void ctf_SpawnTeam (string teamname, float teamcolor)
2131 self.classname = "ctf_team";
2132 self.netname = teamname;
2133 self.cnt = teamcolor;
2135 spawnfunc_ctf_team();
2140 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2142 // if no teams are found, spawn defaults
2143 if(find(world, classname, "ctf_team") == world)
2145 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2146 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2147 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2153 void ctf_Initialize()
2155 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2157 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2158 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2159 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2161 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2165 MUTATOR_DEFINITION(gamemode_ctf)
2167 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2168 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2169 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2170 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2171 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2172 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2173 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2174 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2175 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2176 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2177 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2178 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2179 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2180 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2184 if(time > 1) // game loads at time 1
2185 error("This is a game type and it cannot be added at runtime.");
2189 MUTATOR_ONROLLBACK_OR_REMOVE
2191 // we actually cannot roll back ctf_Initialize here
2192 // BUT: we don't need to! If this gets called, adding always
2198 print("This is a game type and it cannot be removed at runtime.");