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) * 2);
48 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
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;
135 if(IsDifferentTeam(e, p))
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(!IsDifferentTeam(self, other)) { return FALSE; }
170 void ctf_CaptureShield_Touch()
172 if(!other.ctf_captureshielded) { return; }
173 if(!IsDifferentTeam(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, ATTN_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, ATTN_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(!IsDifferentTeam(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, ATTN_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 not(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, ATTN_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 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
435 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
436 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
437 ctf_EventLog("return", flag.team, player);
440 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
441 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
443 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
447 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
448 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
449 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
453 ctf_RespawnFlag(flag);
456 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
459 float pickup_dropped_score; // used to calculate dropped pickup score
461 // attach the flag to the player
463 player.flagcarried = flag;
464 setattachment(flag, player, "");
465 setorigin(flag, FLAG_CARRY_OFFSET);
468 flag.movetype = MOVETYPE_NONE;
469 flag.takedamage = DAMAGE_NO;
470 flag.solid = SOLID_NOT;
471 flag.angles = '0 0 0';
472 flag.ctf_status = FLAG_CARRY;
476 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
477 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
481 // messages and sounds
482 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
484 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
485 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
487 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
488 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
490 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
493 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
498 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
499 ctf_EventLog("steal", flag.team, player);
505 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);
506 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);
507 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
508 PlayerTeamScore_AddScore(player, pickup_dropped_score);
509 ctf_EventLog("pickup", flag.team, player);
517 if(pickuptype == PICKUP_BASE)
519 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
520 if((player.speedrunning) && (ctf_captimerecord))
521 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
525 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
528 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
529 ctf_FlagcarrierWaypoints(player);
530 WaypointSprite_Ping(player.wps_flagcarrier);
534 // ===================
535 // Main Flag Functions
536 // ===================
538 void ctf_CheckFlagReturn(entity flag, float returntype)
540 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
542 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
544 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
548 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
549 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
550 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
551 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
555 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
557 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
558 ctf_EventLog("returned", flag.team, world);
559 ctf_RespawnFlag(flag);
564 void ctf_CheckStalemate(void)
567 float stale_red_flags = 0, stale_blue_flags = 0;
570 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
572 // build list of stale flags
573 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
575 if(autocvar_g_ctf_stalemate)
576 if(tmp_entity.ctf_status != FLAG_BASE)
577 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
579 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
580 ctf_staleflaglist = tmp_entity;
582 switch(tmp_entity.team)
584 case NUM_TEAM_1: ++stale_red_flags; break;
585 case NUM_TEAM_2: ++stale_blue_flags; break;
590 if(stale_red_flags && stale_blue_flags)
591 ctf_stalemate = TRUE;
592 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
593 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
594 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
595 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
597 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
600 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
602 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
603 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));
606 if not(wpforenemy_announced)
608 FOR_EACH_REALPLAYER(tmp_entity)
609 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
611 wpforenemy_announced = TRUE;
616 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
618 if(ITEM_DAMAGE_NEEDKILL(deathtype))
620 // automatically kill the flag and return it
622 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
625 if(autocvar_g_ctf_flag_return_damage)
627 // reduce health and check if it should be returned
628 self.health = self.health - damage;
629 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
639 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
642 if(self == ctf_worldflaglist) // only for the first flag
643 FOR_EACH_CLIENT(tmp_entity)
644 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
647 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
648 dprint("wtf the flag got squashed?\n");
649 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
650 if(!trace_startsolid) // can we resize it without getting stuck?
651 setsize(self, FLAG_MIN, FLAG_MAX); }
653 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
657 self.angles = '0 0 0';
665 switch(self.ctf_status)
669 if(autocvar_g_ctf_dropped_capture_radius)
671 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
672 if(tmp_entity.ctf_status == FLAG_DROPPED)
673 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
674 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
675 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
682 if(autocvar_g_ctf_flag_dropped_floatinwater)
684 vector midpoint = ((self.absmin + self.absmax) * 0.5);
685 if(pointcontents(midpoint) == CONTENT_WATER)
687 self.velocity = self.velocity * 0.5;
689 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
690 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
692 { self.movetype = MOVETYPE_FLY; }
694 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
696 if(autocvar_g_ctf_flag_return_dropped)
698 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
701 ctf_CheckFlagReturn(self, RETURN_DROPPED);
705 if(autocvar_g_ctf_flag_return_time)
707 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
708 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
716 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
719 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
723 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
727 if(autocvar_g_ctf_stalemate)
729 if(time >= wpforenemy_nextthink)
731 ctf_CheckStalemate();
732 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
740 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
741 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
742 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
744 if((self.pass_target == world)
745 || (self.pass_target.deadflag != DEAD_NO)
746 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
747 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
748 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
750 // give up, pass failed
751 ctf_Handle_Drop(self, world, DROP_PASS);
755 // still a viable target, go for it
756 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
761 default: // this should never happen
763 dprint("ctf_FlagThink(): Flag exists with no status?\n");
771 if(gameover) { return; }
773 entity toucher = other;
775 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
776 if(ITEM_TOUCH_NEEDKILL())
779 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
783 // special touch behaviors
784 if(toucher.vehicle_flags & VHF_ISVEHICLE)
786 if(autocvar_g_ctf_allow_vehicle_touch)
787 toucher = toucher.owner; // the player is actually the vehicle owner, not other
789 return; // do nothing
791 else if not(IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
793 if(time > self.wait) // if we haven't in a while, play a sound/effect
795 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
796 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
797 self.wait = time + FLAG_TOUCHRATE;
801 else if(toucher.deadflag != DEAD_NO) { return; }
803 switch(self.ctf_status)
807 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
808 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
809 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
810 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
816 if(!IsDifferentTeam(toucher, self))
817 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
818 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
819 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
825 dprint("Someone touched a flag even though it was being carried?\n");
831 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
833 if(IsDifferentTeam(toucher, self.pass_sender))
834 ctf_Handle_Return(self, toucher);
836 ctf_Handle_Retrieve(self, toucher);
844 void ctf_RespawnFlag(entity flag)
846 // check for flag respawn being called twice in a row
847 if(flag.last_respawn > time - 0.5)
848 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
850 flag.last_respawn = time;
852 // reset the player (if there is one)
853 if((flag.owner) && (flag.owner.flagcarried == flag))
855 if(flag.owner.wps_enemyflagcarrier)
856 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
858 WaypointSprite_Kill(flag.wps_flagcarrier);
860 flag.owner.flagcarried = world;
862 if(flag.speedrunning)
863 ctf_FakeTimeLimit(flag.owner, -1);
866 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
867 { WaypointSprite_Kill(flag.wps_flagdropped); }
870 setattachment(flag, world, "");
871 setorigin(flag, flag.ctf_spawnorigin);
873 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
874 flag.takedamage = DAMAGE_NO;
875 flag.health = flag.max_flag_health;
876 flag.solid = SOLID_TRIGGER;
877 flag.velocity = '0 0 0';
878 flag.angles = flag.mangle;
879 flag.flags = FL_ITEM | FL_NOTARGET;
881 flag.ctf_status = FLAG_BASE;
883 flag.pass_distance = 0;
884 flag.pass_sender = world;
885 flag.pass_target = world;
886 flag.ctf_dropper = world;
887 flag.ctf_pickuptime = 0;
888 flag.ctf_droptime = 0;
894 if(IS_PLAYER(self.owner))
895 ctf_Handle_Throw(self.owner, world, DROP_RESET);
897 ctf_RespawnFlag(self);
900 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
903 waypoint_spawnforitem_force(self, self.origin);
904 self.nearestwaypointtimeout = 0; // activate waypointing again
905 self.bot_basewaypoint = self.nearestwaypoint;
908 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
909 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
911 // captureshield setup
912 ctf_CaptureShield_Spawn(self);
915 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
918 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.
919 self = flag; // for later usage with droptofloor()
922 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
923 ctf_worldflaglist = flag;
925 setattachment(flag, world, "");
927 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
928 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
929 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
930 flag.classname = "item_flag_team";
931 flag.target = "###item###"; // wut?
932 flag.flags = FL_ITEM | FL_NOTARGET;
933 flag.solid = SOLID_TRIGGER;
934 flag.takedamage = DAMAGE_NO;
935 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
936 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
937 flag.health = flag.max_flag_health;
938 flag.event_damage = ctf_FlagDamage;
939 flag.pushable = TRUE;
940 flag.teleportable = TELEPORT_NORMAL;
941 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
942 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
943 flag.velocity = '0 0 0';
944 flag.mangle = flag.angles;
945 flag.reset = ctf_Reset;
946 flag.touch = ctf_FlagTouch;
947 flag.think = ctf_FlagThink;
948 flag.nextthink = time + FLAG_THINKRATE;
949 flag.ctf_status = FLAG_BASE;
952 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
953 if(!flag.scale) { flag.scale = FLAG_SCALE; }
954 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
955 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
956 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
957 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
960 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
961 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
962 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
963 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.
964 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
965 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
966 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
969 precache_sound(flag.snd_flag_taken);
970 precache_sound(flag.snd_flag_returned);
971 precache_sound(flag.snd_flag_capture);
972 precache_sound(flag.snd_flag_respawn);
973 precache_sound(flag.snd_flag_dropped);
974 precache_sound(flag.snd_flag_touch);
975 precache_sound(flag.snd_flag_pass);
976 precache_model(flag.model);
977 precache_model("models/ctf/shield.md3");
978 precache_model("models/ctf/shockwavetransring.md3");
981 setmodel(flag, flag.model); // precision set below
982 setsize(flag, FLAG_MIN, FLAG_MAX);
983 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
985 if(autocvar_g_ctf_flag_glowtrails)
987 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
992 flag.effects |= EF_LOWPRECISION;
993 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
994 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
997 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
999 flag.dropped_origin = flag.origin;
1000 flag.noalign = TRUE;
1001 flag.movetype = MOVETYPE_NONE;
1003 else // drop to floor, automatically find a platform and set that as spawn origin
1005 flag.noalign = FALSE;
1008 flag.movetype = MOVETYPE_TOSS;
1011 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1019 // NOTE: LEGACY CODE, needs to be re-written!
1021 void havocbot_calculate_middlepoint()
1025 vector fo = '0 0 0';
1028 f = ctf_worldflaglist;
1033 f = f.ctf_worldflagnext;
1037 havocbot_ctf_middlepoint = s * (1.0 / n);
1038 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1042 entity havocbot_ctf_find_flag(entity bot)
1045 f = ctf_worldflaglist;
1048 if (bot.team == f.team)
1050 f = f.ctf_worldflagnext;
1055 entity havocbot_ctf_find_enemy_flag(entity bot)
1058 f = ctf_worldflaglist;
1061 if (bot.team != f.team)
1063 f = f.ctf_worldflagnext;
1068 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1076 FOR_EACH_PLAYER(head)
1078 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1081 if(vlen(head.origin - org) < tc_radius)
1088 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1091 head = ctf_worldflaglist;
1094 if (self.team == head.team)
1096 head = head.ctf_worldflagnext;
1099 navigation_routerating(head, ratingscale, 10000);
1102 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1105 head = ctf_worldflaglist;
1108 if (self.team == head.team)
1110 head = head.ctf_worldflagnext;
1115 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1118 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1121 head = ctf_worldflaglist;
1124 if (self.team != head.team)
1126 head = head.ctf_worldflagnext;
1129 navigation_routerating(head, ratingscale, 10000);
1132 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1134 if not(bot_waypoints_for_items)
1136 havocbot_goalrating_ctf_enemyflag(ratingscale);
1142 head = havocbot_ctf_find_enemy_flag(self);
1147 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1150 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1154 mf = havocbot_ctf_find_flag(self);
1156 if(mf.ctf_status == FLAG_BASE)
1160 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1163 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1166 head = ctf_worldflaglist;
1169 // flag is out in the field
1170 if(head.ctf_status != FLAG_BASE)
1171 if(head.tag_entity==world) // dropped
1175 if(vlen(org-head.origin)<df_radius)
1176 navigation_routerating(head, ratingscale, 10000);
1179 navigation_routerating(head, ratingscale, 10000);
1182 head = head.ctf_worldflagnext;
1186 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1190 head = findchainfloat(bot_pickup, TRUE);
1193 // gather health and armor only
1195 if (head.health || head.armorvalue)
1196 if (vlen(head.origin - org) < sradius)
1198 // get the value of the item
1199 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1201 navigation_routerating(head, t * ratingscale, 500);
1207 void havocbot_ctf_reset_role(entity bot)
1209 float cdefense, cmiddle, coffense;
1210 entity mf, ef, head;
1213 if(bot.deadflag != DEAD_NO)
1216 if(vlen(havocbot_ctf_middlepoint)==0)
1217 havocbot_calculate_middlepoint();
1220 if (bot.flagcarried)
1222 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1226 mf = havocbot_ctf_find_flag(bot);
1227 ef = havocbot_ctf_find_enemy_flag(bot);
1229 // Retrieve stolen flag
1230 if(mf.ctf_status!=FLAG_BASE)
1232 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1236 // If enemy flag is taken go to the middle to intercept pursuers
1237 if(ef.ctf_status!=FLAG_BASE)
1239 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1243 // if there is only me on the team switch to offense
1245 FOR_EACH_PLAYER(head)
1246 if(head.team==bot.team)
1251 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1255 // Evaluate best position to take
1256 // Count mates on middle position
1257 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1259 // Count mates on defense position
1260 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1262 // Count mates on offense position
1263 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1265 if(cdefense<=coffense)
1266 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1267 else if(coffense<=cmiddle)
1268 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1270 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1273 void havocbot_role_ctf_carrier()
1275 if(self.deadflag != DEAD_NO)
1277 havocbot_ctf_reset_role(self);
1281 if (self.flagcarried == world)
1283 havocbot_ctf_reset_role(self);
1287 if (self.bot_strategytime < time)
1289 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1291 navigation_goalrating_start();
1292 havocbot_goalrating_ctf_ourbase(50000);
1295 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1297 navigation_goalrating_end();
1299 if (self.navigation_hasgoals)
1300 self.havocbot_cantfindflag = time + 10;
1301 else if (time > self.havocbot_cantfindflag)
1303 // Can't navigate to my own base, suicide!
1304 // TODO: drop it and wander around
1305 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1311 void havocbot_role_ctf_escort()
1315 if(self.deadflag != DEAD_NO)
1317 havocbot_ctf_reset_role(self);
1321 if (self.flagcarried)
1323 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1327 // If enemy flag is back on the base switch to previous role
1328 ef = havocbot_ctf_find_enemy_flag(self);
1329 if(ef.ctf_status==FLAG_BASE)
1331 self.havocbot_role = self.havocbot_previous_role;
1332 self.havocbot_role_timeout = 0;
1336 // If the flag carrier reached the base switch to defense
1337 mf = havocbot_ctf_find_flag(self);
1338 if(mf.ctf_status!=FLAG_BASE)
1339 if(vlen(ef.origin - mf.dropped_origin) < 300)
1341 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1345 // Set the role timeout if necessary
1346 if (!self.havocbot_role_timeout)
1348 self.havocbot_role_timeout = time + random() * 30 + 60;
1351 // If nothing happened just switch to previous role
1352 if (time > self.havocbot_role_timeout)
1354 self.havocbot_role = self.havocbot_previous_role;
1355 self.havocbot_role_timeout = 0;
1359 // Chase the flag carrier
1360 if (self.bot_strategytime < time)
1362 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1363 navigation_goalrating_start();
1364 havocbot_goalrating_ctf_enemyflag(30000);
1365 havocbot_goalrating_ctf_ourstolenflag(40000);
1366 havocbot_goalrating_items(10000, self.origin, 10000);
1367 navigation_goalrating_end();
1371 void havocbot_role_ctf_offense()
1376 if(self.deadflag != DEAD_NO)
1378 havocbot_ctf_reset_role(self);
1382 if (self.flagcarried)
1384 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1389 mf = havocbot_ctf_find_flag(self);
1390 ef = havocbot_ctf_find_enemy_flag(self);
1393 if(mf.ctf_status!=FLAG_BASE)
1396 pos = mf.tag_entity.origin;
1400 // Try to get it if closer than the enemy base
1401 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1403 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1408 // Escort flag carrier
1409 if(ef.ctf_status!=FLAG_BASE)
1412 pos = ef.tag_entity.origin;
1416 if(vlen(pos-mf.dropped_origin)>700)
1418 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1423 // About to fail, switch to middlefield
1426 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1430 // Set the role timeout if necessary
1431 if (!self.havocbot_role_timeout)
1432 self.havocbot_role_timeout = time + 120;
1434 if (time > self.havocbot_role_timeout)
1436 havocbot_ctf_reset_role(self);
1440 if (self.bot_strategytime < time)
1442 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1443 navigation_goalrating_start();
1444 havocbot_goalrating_ctf_ourstolenflag(50000);
1445 havocbot_goalrating_ctf_enemybase(20000);
1446 havocbot_goalrating_items(5000, self.origin, 1000);
1447 havocbot_goalrating_items(1000, self.origin, 10000);
1448 navigation_goalrating_end();
1452 // Retriever (temporary role):
1453 void havocbot_role_ctf_retriever()
1457 if(self.deadflag != DEAD_NO)
1459 havocbot_ctf_reset_role(self);
1463 if (self.flagcarried)
1465 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1469 // If flag is back on the base switch to previous role
1470 mf = havocbot_ctf_find_flag(self);
1471 if(mf.ctf_status==FLAG_BASE)
1473 havocbot_ctf_reset_role(self);
1477 if (!self.havocbot_role_timeout)
1478 self.havocbot_role_timeout = time + 20;
1480 if (time > self.havocbot_role_timeout)
1482 havocbot_ctf_reset_role(self);
1486 if (self.bot_strategytime < time)
1491 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1492 navigation_goalrating_start();
1493 havocbot_goalrating_ctf_ourstolenflag(50000);
1494 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1495 havocbot_goalrating_ctf_enemybase(30000);
1496 havocbot_goalrating_items(500, self.origin, rt_radius);
1497 navigation_goalrating_end();
1501 void havocbot_role_ctf_middle()
1505 if(self.deadflag != DEAD_NO)
1507 havocbot_ctf_reset_role(self);
1511 if (self.flagcarried)
1513 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1517 mf = havocbot_ctf_find_flag(self);
1518 if(mf.ctf_status!=FLAG_BASE)
1520 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1524 if (!self.havocbot_role_timeout)
1525 self.havocbot_role_timeout = time + 10;
1527 if (time > self.havocbot_role_timeout)
1529 havocbot_ctf_reset_role(self);
1533 if (self.bot_strategytime < time)
1537 org = havocbot_ctf_middlepoint;
1538 org_z = self.origin_z;
1540 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1541 navigation_goalrating_start();
1542 havocbot_goalrating_ctf_ourstolenflag(50000);
1543 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1544 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1545 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1546 havocbot_goalrating_items(2500, self.origin, 10000);
1547 havocbot_goalrating_ctf_enemybase(2500);
1548 navigation_goalrating_end();
1552 void havocbot_role_ctf_defense()
1556 if(self.deadflag != DEAD_NO)
1558 havocbot_ctf_reset_role(self);
1562 if (self.flagcarried)
1564 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1568 // If own flag was captured
1569 mf = havocbot_ctf_find_flag(self);
1570 if(mf.ctf_status!=FLAG_BASE)
1572 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1576 if (!self.havocbot_role_timeout)
1577 self.havocbot_role_timeout = time + 30;
1579 if (time > self.havocbot_role_timeout)
1581 havocbot_ctf_reset_role(self);
1584 if (self.bot_strategytime < time)
1589 org = mf.dropped_origin;
1590 mp_radius = havocbot_ctf_middlepoint_radius;
1592 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1593 navigation_goalrating_start();
1595 // if enemies are closer to our base, go there
1596 entity head, closestplayer = world;
1597 float distance, bestdistance = 10000;
1598 FOR_EACH_PLAYER(head)
1600 if(head.deadflag!=DEAD_NO)
1603 distance = vlen(org - head.origin);
1604 if(distance<bestdistance)
1606 closestplayer = head;
1607 bestdistance = distance;
1612 if(closestplayer.team!=self.team)
1613 if(vlen(org - self.origin)>1000)
1614 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1615 havocbot_goalrating_ctf_ourbase(30000);
1617 havocbot_goalrating_ctf_ourstolenflag(20000);
1618 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1619 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1620 havocbot_goalrating_items(10000, org, mp_radius);
1621 havocbot_goalrating_items(5000, self.origin, 10000);
1622 navigation_goalrating_end();
1626 void havocbot_role_ctf_setrole(entity bot, float role)
1628 dprint(strcat(bot.netname," switched to "));
1631 case HAVOCBOT_CTF_ROLE_CARRIER:
1633 bot.havocbot_role = havocbot_role_ctf_carrier;
1634 bot.havocbot_role_timeout = 0;
1635 bot.havocbot_cantfindflag = time + 10;
1636 bot.bot_strategytime = 0;
1638 case HAVOCBOT_CTF_ROLE_DEFENSE:
1640 bot.havocbot_role = havocbot_role_ctf_defense;
1641 bot.havocbot_role_timeout = 0;
1643 case HAVOCBOT_CTF_ROLE_MIDDLE:
1645 bot.havocbot_role = havocbot_role_ctf_middle;
1646 bot.havocbot_role_timeout = 0;
1648 case HAVOCBOT_CTF_ROLE_OFFENSE:
1650 bot.havocbot_role = havocbot_role_ctf_offense;
1651 bot.havocbot_role_timeout = 0;
1653 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1654 dprint("retriever");
1655 bot.havocbot_previous_role = bot.havocbot_role;
1656 bot.havocbot_role = havocbot_role_ctf_retriever;
1657 bot.havocbot_role_timeout = time + 10;
1658 bot.bot_strategytime = 0;
1660 case HAVOCBOT_CTF_ROLE_ESCORT:
1662 bot.havocbot_previous_role = bot.havocbot_role;
1663 bot.havocbot_role = havocbot_role_ctf_escort;
1664 bot.havocbot_role_timeout = time + 30;
1665 bot.bot_strategytime = 0;
1676 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1680 // initially clear items so they can be set as necessary later.
1681 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1682 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1684 // scan through all the flags and notify the client about them
1685 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1687 switch(flag.ctf_status)
1692 if((flag.owner == self) || (flag.pass_sender == self))
1693 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1695 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1700 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1706 // item for stopping players from capturing the flag too often
1707 if(self.ctf_captureshielded)
1708 self.items |= IT_CTF_SHIELDED;
1710 // update the health of the flag carrier waypointsprite
1711 if(self.wps_flagcarrier)
1712 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1717 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1719 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1721 if(frag_target == frag_attacker) // damage done to yourself
1723 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1724 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1726 else // damage done to everyone else
1728 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1729 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1732 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1734 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1735 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1737 frag_target.wps_helpme_time = time;
1738 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1740 // todo: add notification for when flag carrier needs help?
1745 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1747 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1749 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1750 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1753 if(frag_target.flagcarried)
1754 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1759 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1762 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1765 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1767 entity flag; // temporary entity for the search method
1769 if(self.flagcarried)
1770 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1772 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1774 if(flag.pass_sender == self) { flag.pass_sender = world; }
1775 if(flag.pass_target == self) { flag.pass_target = world; }
1776 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1782 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1784 if(self.flagcarried)
1785 if(!autocvar_g_ctf_portalteleport)
1786 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1791 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1793 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1795 entity player = self;
1797 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1799 // pass the flag to a team mate
1800 if(autocvar_g_ctf_pass)
1802 entity head, closest_target = world;
1803 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1805 while(head) // find the closest acceptable target to pass to
1807 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1808 if(head != player && !IsDifferentTeam(head, player))
1809 if(!head.speedrunning && !head.vehicle)
1811 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1812 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1813 vector passer_center = CENTER_OR_VIEWOFS(player);
1815 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1817 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1819 if(IS_BOT_CLIENT(head))
1821 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1822 ctf_Handle_Throw(head, player, DROP_PASS);
1826 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1827 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1829 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1832 else if(player.flagcarried)
1836 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1837 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1838 { closest_target = head; }
1840 else { closest_target = head; }
1847 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1850 // throw the flag in front of you
1851 if(autocvar_g_ctf_throw && player.flagcarried)
1853 if(player.throw_count == -1)
1855 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1857 player.throw_prevtime = time;
1858 player.throw_count = 1;
1859 ctf_Handle_Throw(player, world, DROP_THROW);
1864 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1870 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1871 else { player.throw_count += 1; }
1872 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1874 player.throw_prevtime = time;
1875 ctf_Handle_Throw(player, world, DROP_THROW);
1884 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1886 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1888 self.wps_helpme_time = time;
1889 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1891 else // create a normal help me waypointsprite
1893 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');
1894 WaypointSprite_Ping(self.wps_helpme);
1900 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1902 if(vh_player.flagcarried)
1904 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1906 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1910 setattachment(vh_player.flagcarried, vh_vehicle, "");
1911 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1912 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1913 //vh_player.flagcarried.angles = '0 0 0';
1921 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1923 if(vh_player.flagcarried)
1925 setattachment(vh_player.flagcarried, vh_player, "");
1926 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1927 vh_player.flagcarried.scale = FLAG_SCALE;
1928 vh_player.flagcarried.angles = '0 0 0';
1935 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1937 if(self.flagcarried)
1939 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1940 ctf_RespawnFlag(self.flagcarried);
1947 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1949 entity flag; // temporary entity for the search method
1951 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1953 switch(flag.ctf_status)
1958 // lock the flag, game is over
1959 flag.movetype = MOVETYPE_NONE;
1960 flag.takedamage = DAMAGE_NO;
1961 flag.solid = SOLID_NOT;
1962 flag.nextthink = FALSE; // stop thinking
1964 //dprint("stopping the ", flag.netname, " from moving.\n");
1972 // do nothing for these flags
1981 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1983 havocbot_ctf_reset_role(self);
1992 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1993 CTF Starting point for a player in team one (Red).
1994 Keys: "angle" viewing angle when spawning. */
1995 void spawnfunc_info_player_team1()
1997 if(g_assault) { remove(self); return; }
1999 self.team = NUM_TEAM_1; // red
2000 spawnfunc_info_player_deathmatch();
2004 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2005 CTF Starting point for a player in team two (Blue).
2006 Keys: "angle" viewing angle when spawning. */
2007 void spawnfunc_info_player_team2()
2009 if(g_assault) { remove(self); return; }
2011 self.team = NUM_TEAM_2; // blue
2012 spawnfunc_info_player_deathmatch();
2015 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2016 CTF Starting point for a player in team three (Yellow).
2017 Keys: "angle" viewing angle when spawning. */
2018 void spawnfunc_info_player_team3()
2020 if(g_assault) { remove(self); return; }
2022 self.team = NUM_TEAM_3; // yellow
2023 spawnfunc_info_player_deathmatch();
2027 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2028 CTF Starting point for a player in team four (Purple).
2029 Keys: "angle" viewing angle when spawning. */
2030 void spawnfunc_info_player_team4()
2032 if(g_assault) { remove(self); return; }
2034 self.team = NUM_TEAM_4; // purple
2035 spawnfunc_info_player_deathmatch();
2038 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2039 CTF flag for team one (Red).
2041 "angle" Angle the flag will point (minus 90 degrees)...
2042 "model" model to use, note this needs red and blue as skins 0 and 1...
2043 "noise" sound played when flag is picked up...
2044 "noise1" sound played when flag is returned by a teammate...
2045 "noise2" sound played when flag is captured...
2046 "noise3" sound played when flag is lost in the field and respawns itself...
2047 "noise4" sound played when flag is dropped by a player...
2048 "noise5" sound played when flag touches the ground... */
2049 void spawnfunc_item_flag_team1()
2051 if(!g_ctf) { remove(self); return; }
2053 ctf_FlagSetup(1, self); // 1 = red
2056 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2057 CTF flag for team two (Blue).
2059 "angle" Angle the flag will point (minus 90 degrees)...
2060 "model" model to use, note this needs red and blue as skins 0 and 1...
2061 "noise" sound played when flag is picked up...
2062 "noise1" sound played when flag is returned by a teammate...
2063 "noise2" sound played when flag is captured...
2064 "noise3" sound played when flag is lost in the field and respawns itself...
2065 "noise4" sound played when flag is dropped by a player...
2066 "noise5" sound played when flag touches the ground... */
2067 void spawnfunc_item_flag_team2()
2069 if(!g_ctf) { remove(self); return; }
2071 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2074 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2075 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2076 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.
2078 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2079 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2080 void spawnfunc_ctf_team()
2082 if(!g_ctf) { remove(self); return; }
2084 self.classname = "ctf_team";
2085 self.team = self.cnt + 1;
2088 // compatibility for quake maps
2089 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2090 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2091 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2092 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2093 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2094 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2102 void ctf_ScoreRules()
2104 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2105 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2106 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2107 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2108 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2109 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2110 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2111 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2112 ScoreRules_basics_end();
2115 // code from here on is just to support maps that don't have flag and team entities
2116 void ctf_SpawnTeam (string teamname, float teamcolor)
2121 self.classname = "ctf_team";
2122 self.netname = teamname;
2123 self.cnt = teamcolor;
2125 spawnfunc_ctf_team();
2130 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2132 // if no teams are found, spawn defaults
2133 if(find(world, classname, "ctf_team") == world)
2135 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2136 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2137 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2143 void ctf_Initialize()
2145 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2147 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2148 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2149 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2151 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2155 MUTATOR_DEFINITION(gamemode_ctf)
2157 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2158 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2159 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2160 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2161 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2162 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2163 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2164 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2165 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2166 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2167 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2168 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2169 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2170 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2174 if(time > 1) // game loads at time 1
2175 error("This is a game type and it cannot be added at runtime.");
2179 MUTATOR_ONROLLBACK_OR_REMOVE
2181 // we actually cannot roll back ctf_Initialize here
2182 // BUT: we don't need to! If this gets called, adding always
2188 print("This is a game type and it cannot be removed at runtime.");