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 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
393 // messages and sounds
394 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
395 ctf_CaptureRecord(enemy_flag, player);
396 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
400 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
401 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
406 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
407 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
409 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
410 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
411 if(!old_time || new_time < old_time)
412 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
415 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
416 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
419 if(capturetype == CAPTURE_NORMAL)
421 WaypointSprite_Kill(player.wps_flagcarrier);
422 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
424 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
425 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
429 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
430 ctf_RespawnFlag(enemy_flag);
433 void ctf_Handle_Return(entity flag, entity player)
435 // messages and sounds
436 if(player.flags & FL_MONSTER)
438 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
442 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
443 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
445 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
446 ctf_EventLog("return", flag.team, player);
449 if(IS_PLAYER(player))
451 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
452 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
454 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
457 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
461 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
462 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
463 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
467 ctf_RespawnFlag(flag);
470 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
473 float pickup_dropped_score; // used to calculate dropped pickup score
475 // attach the flag to the player
477 player.flagcarried = flag;
478 setattachment(flag, player, "");
479 setorigin(flag, FLAG_CARRY_OFFSET);
482 flag.movetype = MOVETYPE_NONE;
483 flag.takedamage = DAMAGE_NO;
484 flag.solid = SOLID_NOT;
485 flag.angles = '0 0 0';
486 flag.ctf_status = FLAG_CARRY;
490 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
491 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
495 // messages and sounds
496 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
497 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
498 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
500 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
501 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
503 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
506 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
507 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
512 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
513 ctf_EventLog("steal", flag.team, player);
519 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);
520 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);
521 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
522 PlayerTeamScore_AddScore(player, pickup_dropped_score);
523 ctf_EventLog("pickup", flag.team, player);
531 if(pickuptype == PICKUP_BASE)
533 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
534 if((player.speedrunning) && (ctf_captimerecord))
535 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
539 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
542 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
543 ctf_FlagcarrierWaypoints(player);
544 WaypointSprite_Ping(player.wps_flagcarrier);
548 // ===================
549 // Main Flag Functions
550 // ===================
552 void ctf_CheckFlagReturn(entity flag, float returntype)
554 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
556 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
558 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
562 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
563 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
564 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
565 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
569 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
571 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
572 ctf_EventLog("returned", flag.team, world);
573 ctf_RespawnFlag(flag);
578 void ctf_CheckStalemate(void)
581 float stale_red_flags = 0, stale_blue_flags = 0;
584 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
586 // build list of stale flags
587 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
589 if(autocvar_g_ctf_stalemate)
590 if(tmp_entity.ctf_status != FLAG_BASE)
591 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
593 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
594 ctf_staleflaglist = tmp_entity;
596 switch(tmp_entity.team)
598 case NUM_TEAM_1: ++stale_red_flags; break;
599 case NUM_TEAM_2: ++stale_blue_flags; break;
604 if(stale_red_flags && stale_blue_flags)
605 ctf_stalemate = TRUE;
606 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
607 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
608 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
609 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
611 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
614 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
616 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
617 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));
620 if (!wpforenemy_announced)
622 FOR_EACH_REALPLAYER(tmp_entity)
623 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
625 wpforenemy_announced = TRUE;
630 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
632 if(ITEM_DAMAGE_NEEDKILL(deathtype))
634 // automatically kill the flag and return it
636 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
639 if(autocvar_g_ctf_flag_return_damage)
641 // reduce health and check if it should be returned
642 self.health = self.health - damage;
643 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
653 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
656 if(self == ctf_worldflaglist) // only for the first flag
657 FOR_EACH_CLIENT(tmp_entity)
658 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
661 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
662 dprint("wtf the flag got squashed?\n");
663 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
664 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
665 setsize(self, FLAG_MIN, FLAG_MAX); }
667 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
671 self.angles = '0 0 0';
679 switch(self.ctf_status)
683 if(autocvar_g_ctf_dropped_capture_radius)
685 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
686 if(tmp_entity.ctf_status == FLAG_DROPPED)
687 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
688 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
689 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
696 if(autocvar_g_ctf_flag_dropped_floatinwater)
698 vector midpoint = ((self.absmin + self.absmax) * 0.5);
699 if(pointcontents(midpoint) == CONTENT_WATER)
701 self.velocity = self.velocity * 0.5;
703 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
704 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
706 { self.movetype = MOVETYPE_FLY; }
708 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
710 if(autocvar_g_ctf_flag_return_dropped)
712 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
715 ctf_CheckFlagReturn(self, RETURN_DROPPED);
719 if(autocvar_g_ctf_flag_return_time)
721 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
722 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
730 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
733 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
737 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
741 if(autocvar_g_ctf_stalemate)
743 if(time >= wpforenemy_nextthink)
745 ctf_CheckStalemate();
746 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
754 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
755 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
756 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
758 if((self.pass_target == world)
759 || (self.pass_target.deadflag != DEAD_NO)
760 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
761 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
762 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
764 // give up, pass failed
765 ctf_Handle_Drop(self, world, DROP_PASS);
769 // still a viable target, go for it
770 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
775 default: // this should never happen
777 dprint("ctf_FlagThink(): Flag exists with no status?\n");
785 if(gameover) { return; }
787 entity toucher = other;
788 float is_not_monster = (!(toucher.flags & FL_MONSTER));
790 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
791 if(ITEM_TOUCH_NEEDKILL())
794 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
798 // special touch behaviors
799 if(toucher.frozen) { return; }
800 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
802 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
803 toucher = toucher.owner; // the player is actually the vehicle owner, not other
805 return; // do nothing
807 else if(toucher.flags & FL_MONSTER)
809 if(!autocvar_g_ctf_allow_monster_touch)
810 return; // do nothing
812 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
814 if(time > self.wait) // if we haven't in a while, play a sound/effect
816 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
817 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
818 self.wait = time + FLAG_TOUCHRATE;
822 else if(toucher.deadflag != DEAD_NO) { return; }
824 switch(self.ctf_status)
828 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
829 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
830 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
831 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
837 if(SAME_TEAM(toucher, self))
838 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
839 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
840 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
846 dprint("Someone touched a flag even though it was being carried?\n");
852 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
854 if(DIFF_TEAM(toucher, self.pass_sender))
855 ctf_Handle_Return(self, toucher);
857 ctf_Handle_Retrieve(self, toucher);
865 void ctf_RespawnFlag(entity flag)
867 // check for flag respawn being called twice in a row
868 if(flag.last_respawn > time - 0.5)
869 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
871 flag.last_respawn = time;
873 // reset the player (if there is one)
874 if((flag.owner) && (flag.owner.flagcarried == flag))
876 if(flag.owner.wps_enemyflagcarrier)
877 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
879 WaypointSprite_Kill(flag.wps_flagcarrier);
881 flag.owner.flagcarried = world;
883 if(flag.speedrunning)
884 ctf_FakeTimeLimit(flag.owner, -1);
887 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
888 { WaypointSprite_Kill(flag.wps_flagdropped); }
891 setattachment(flag, world, "");
892 setorigin(flag, flag.ctf_spawnorigin);
894 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
895 flag.takedamage = DAMAGE_NO;
896 flag.health = flag.max_flag_health;
897 flag.solid = SOLID_TRIGGER;
898 flag.velocity = '0 0 0';
899 flag.angles = flag.mangle;
900 flag.flags = FL_ITEM | FL_NOTARGET;
902 flag.ctf_status = FLAG_BASE;
904 flag.pass_distance = 0;
905 flag.pass_sender = world;
906 flag.pass_target = world;
907 flag.ctf_dropper = world;
908 flag.ctf_pickuptime = 0;
909 flag.ctf_droptime = 0;
915 if(IS_PLAYER(self.owner))
916 ctf_Handle_Throw(self.owner, world, DROP_RESET);
918 ctf_RespawnFlag(self);
921 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
924 waypoint_spawnforitem_force(self, self.origin);
925 self.nearestwaypointtimeout = 0; // activate waypointing again
926 self.bot_basewaypoint = self.nearestwaypoint;
929 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
930 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
932 // captureshield setup
933 ctf_CaptureShield_Spawn(self);
936 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
939 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.
940 self = flag; // for later usage with droptofloor()
943 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
944 ctf_worldflaglist = flag;
946 setattachment(flag, world, "");
948 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
949 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
950 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
951 flag.classname = "item_flag_team";
952 flag.target = "###item###"; // wut?
953 flag.flags = FL_ITEM | FL_NOTARGET;
954 flag.solid = SOLID_TRIGGER;
955 flag.takedamage = DAMAGE_NO;
956 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
957 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
958 flag.health = flag.max_flag_health;
959 flag.event_damage = ctf_FlagDamage;
960 flag.pushable = TRUE;
961 flag.teleportable = TELEPORT_NORMAL;
962 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
963 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
964 flag.velocity = '0 0 0';
965 flag.mangle = flag.angles;
966 flag.reset = ctf_Reset;
967 flag.touch = ctf_FlagTouch;
968 flag.think = ctf_FlagThink;
969 flag.nextthink = time + FLAG_THINKRATE;
970 flag.ctf_status = FLAG_BASE;
973 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
974 if(!flag.scale) { flag.scale = FLAG_SCALE; }
975 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
976 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
977 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
978 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
981 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
982 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
983 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
984 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.
985 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
986 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
987 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
990 precache_sound(flag.snd_flag_taken);
991 precache_sound(flag.snd_flag_returned);
992 precache_sound(flag.snd_flag_capture);
993 precache_sound(flag.snd_flag_respawn);
994 precache_sound(flag.snd_flag_dropped);
995 precache_sound(flag.snd_flag_touch);
996 precache_sound(flag.snd_flag_pass);
997 precache_model(flag.model);
998 precache_model("models/ctf/shield.md3");
999 precache_model("models/ctf/shockwavetransring.md3");
1002 setmodel(flag, flag.model); // precision set below
1003 setsize(flag, FLAG_MIN, FLAG_MAX);
1004 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1006 if(autocvar_g_ctf_flag_glowtrails)
1008 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1009 flag.glow_size = 25;
1010 flag.glow_trail = 1;
1013 flag.effects |= EF_LOWPRECISION;
1014 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1015 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1018 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1020 flag.dropped_origin = flag.origin;
1021 flag.noalign = TRUE;
1022 flag.movetype = MOVETYPE_NONE;
1024 else // drop to floor, automatically find a platform and set that as spawn origin
1026 flag.noalign = FALSE;
1029 flag.movetype = MOVETYPE_TOSS;
1032 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1040 // NOTE: LEGACY CODE, needs to be re-written!
1042 void havocbot_calculate_middlepoint()
1046 vector fo = '0 0 0';
1049 f = ctf_worldflaglist;
1054 f = f.ctf_worldflagnext;
1058 havocbot_ctf_middlepoint = s * (1.0 / n);
1059 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1063 entity havocbot_ctf_find_flag(entity bot)
1066 f = ctf_worldflaglist;
1069 if (bot.team == f.team)
1071 f = f.ctf_worldflagnext;
1076 entity havocbot_ctf_find_enemy_flag(entity bot)
1079 f = ctf_worldflaglist;
1082 if (bot.team != f.team)
1084 f = f.ctf_worldflagnext;
1089 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1097 FOR_EACH_PLAYER(head)
1099 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1102 if(vlen(head.origin - org) < tc_radius)
1109 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1112 head = ctf_worldflaglist;
1115 if (self.team == head.team)
1117 head = head.ctf_worldflagnext;
1120 navigation_routerating(head, ratingscale, 10000);
1123 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1126 head = ctf_worldflaglist;
1129 if (self.team == head.team)
1131 head = head.ctf_worldflagnext;
1136 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1139 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1142 head = ctf_worldflaglist;
1145 if (self.team != head.team)
1147 head = head.ctf_worldflagnext;
1150 navigation_routerating(head, ratingscale, 10000);
1153 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1155 if (!bot_waypoints_for_items)
1157 havocbot_goalrating_ctf_enemyflag(ratingscale);
1163 head = havocbot_ctf_find_enemy_flag(self);
1168 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1171 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1175 mf = havocbot_ctf_find_flag(self);
1177 if(mf.ctf_status == FLAG_BASE)
1181 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1184 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1187 head = ctf_worldflaglist;
1190 // flag is out in the field
1191 if(head.ctf_status != FLAG_BASE)
1192 if(head.tag_entity==world) // dropped
1196 if(vlen(org-head.origin)<df_radius)
1197 navigation_routerating(head, ratingscale, 10000);
1200 navigation_routerating(head, ratingscale, 10000);
1203 head = head.ctf_worldflagnext;
1207 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1211 head = findchainfloat(bot_pickup, TRUE);
1214 // gather health and armor only
1216 if (head.health || head.armorvalue)
1217 if (vlen(head.origin - org) < sradius)
1219 // get the value of the item
1220 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1222 navigation_routerating(head, t * ratingscale, 500);
1228 void havocbot_ctf_reset_role(entity bot)
1230 float cdefense, cmiddle, coffense;
1231 entity mf, ef, head;
1234 if(bot.deadflag != DEAD_NO)
1237 if(vlen(havocbot_ctf_middlepoint)==0)
1238 havocbot_calculate_middlepoint();
1241 if (bot.flagcarried)
1243 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1247 mf = havocbot_ctf_find_flag(bot);
1248 ef = havocbot_ctf_find_enemy_flag(bot);
1250 // Retrieve stolen flag
1251 if(mf.ctf_status!=FLAG_BASE)
1253 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1257 // If enemy flag is taken go to the middle to intercept pursuers
1258 if(ef.ctf_status!=FLAG_BASE)
1260 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1264 // if there is only me on the team switch to offense
1266 FOR_EACH_PLAYER(head)
1267 if(head.team==bot.team)
1272 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1276 // Evaluate best position to take
1277 // Count mates on middle position
1278 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1280 // Count mates on defense position
1281 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1283 // Count mates on offense position
1284 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1286 if(cdefense<=coffense)
1287 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1288 else if(coffense<=cmiddle)
1289 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1291 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1294 void havocbot_role_ctf_carrier()
1296 if(self.deadflag != DEAD_NO)
1298 havocbot_ctf_reset_role(self);
1302 if (self.flagcarried == world)
1304 havocbot_ctf_reset_role(self);
1308 if (self.bot_strategytime < time)
1310 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1312 navigation_goalrating_start();
1313 havocbot_goalrating_ctf_ourbase(50000);
1316 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1318 navigation_goalrating_end();
1320 if (self.navigation_hasgoals)
1321 self.havocbot_cantfindflag = time + 10;
1322 else if (time > self.havocbot_cantfindflag)
1324 // Can't navigate to my own base, suicide!
1325 // TODO: drop it and wander around
1326 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1332 void havocbot_role_ctf_escort()
1336 if(self.deadflag != DEAD_NO)
1338 havocbot_ctf_reset_role(self);
1342 if (self.flagcarried)
1344 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1348 // If enemy flag is back on the base switch to previous role
1349 ef = havocbot_ctf_find_enemy_flag(self);
1350 if(ef.ctf_status==FLAG_BASE)
1352 self.havocbot_role = self.havocbot_previous_role;
1353 self.havocbot_role_timeout = 0;
1357 // If the flag carrier reached the base switch to defense
1358 mf = havocbot_ctf_find_flag(self);
1359 if(mf.ctf_status!=FLAG_BASE)
1360 if(vlen(ef.origin - mf.dropped_origin) < 300)
1362 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1366 // Set the role timeout if necessary
1367 if (!self.havocbot_role_timeout)
1369 self.havocbot_role_timeout = time + random() * 30 + 60;
1372 // If nothing happened just switch to previous role
1373 if (time > self.havocbot_role_timeout)
1375 self.havocbot_role = self.havocbot_previous_role;
1376 self.havocbot_role_timeout = 0;
1380 // Chase the flag carrier
1381 if (self.bot_strategytime < time)
1383 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1384 navigation_goalrating_start();
1385 havocbot_goalrating_ctf_enemyflag(30000);
1386 havocbot_goalrating_ctf_ourstolenflag(40000);
1387 havocbot_goalrating_items(10000, self.origin, 10000);
1388 navigation_goalrating_end();
1392 void havocbot_role_ctf_offense()
1397 if(self.deadflag != DEAD_NO)
1399 havocbot_ctf_reset_role(self);
1403 if (self.flagcarried)
1405 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1410 mf = havocbot_ctf_find_flag(self);
1411 ef = havocbot_ctf_find_enemy_flag(self);
1414 if(mf.ctf_status!=FLAG_BASE)
1417 pos = mf.tag_entity.origin;
1421 // Try to get it if closer than the enemy base
1422 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1424 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1429 // Escort flag carrier
1430 if(ef.ctf_status!=FLAG_BASE)
1433 pos = ef.tag_entity.origin;
1437 if(vlen(pos-mf.dropped_origin)>700)
1439 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1444 // About to fail, switch to middlefield
1447 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1451 // Set the role timeout if necessary
1452 if (!self.havocbot_role_timeout)
1453 self.havocbot_role_timeout = time + 120;
1455 if (time > self.havocbot_role_timeout)
1457 havocbot_ctf_reset_role(self);
1461 if (self.bot_strategytime < time)
1463 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1464 navigation_goalrating_start();
1465 havocbot_goalrating_ctf_ourstolenflag(50000);
1466 havocbot_goalrating_ctf_enemybase(20000);
1467 havocbot_goalrating_items(5000, self.origin, 1000);
1468 havocbot_goalrating_items(1000, self.origin, 10000);
1469 navigation_goalrating_end();
1473 // Retriever (temporary role):
1474 void havocbot_role_ctf_retriever()
1478 if(self.deadflag != DEAD_NO)
1480 havocbot_ctf_reset_role(self);
1484 if (self.flagcarried)
1486 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1490 // If flag is back on the base switch to previous role
1491 mf = havocbot_ctf_find_flag(self);
1492 if(mf.ctf_status==FLAG_BASE)
1494 havocbot_ctf_reset_role(self);
1498 if (!self.havocbot_role_timeout)
1499 self.havocbot_role_timeout = time + 20;
1501 if (time > self.havocbot_role_timeout)
1503 havocbot_ctf_reset_role(self);
1507 if (self.bot_strategytime < time)
1512 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1513 navigation_goalrating_start();
1514 havocbot_goalrating_ctf_ourstolenflag(50000);
1515 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1516 havocbot_goalrating_ctf_enemybase(30000);
1517 havocbot_goalrating_items(500, self.origin, rt_radius);
1518 navigation_goalrating_end();
1522 void havocbot_role_ctf_middle()
1526 if(self.deadflag != DEAD_NO)
1528 havocbot_ctf_reset_role(self);
1532 if (self.flagcarried)
1534 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1538 mf = havocbot_ctf_find_flag(self);
1539 if(mf.ctf_status!=FLAG_BASE)
1541 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1545 if (!self.havocbot_role_timeout)
1546 self.havocbot_role_timeout = time + 10;
1548 if (time > self.havocbot_role_timeout)
1550 havocbot_ctf_reset_role(self);
1554 if (self.bot_strategytime < time)
1558 org = havocbot_ctf_middlepoint;
1559 org_z = self.origin_z;
1561 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1562 navigation_goalrating_start();
1563 havocbot_goalrating_ctf_ourstolenflag(50000);
1564 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1565 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1566 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1567 havocbot_goalrating_items(2500, self.origin, 10000);
1568 havocbot_goalrating_ctf_enemybase(2500);
1569 navigation_goalrating_end();
1573 void havocbot_role_ctf_defense()
1577 if(self.deadflag != DEAD_NO)
1579 havocbot_ctf_reset_role(self);
1583 if (self.flagcarried)
1585 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1589 // If own flag was captured
1590 mf = havocbot_ctf_find_flag(self);
1591 if(mf.ctf_status!=FLAG_BASE)
1593 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1597 if (!self.havocbot_role_timeout)
1598 self.havocbot_role_timeout = time + 30;
1600 if (time > self.havocbot_role_timeout)
1602 havocbot_ctf_reset_role(self);
1605 if (self.bot_strategytime < time)
1610 org = mf.dropped_origin;
1611 mp_radius = havocbot_ctf_middlepoint_radius;
1613 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1614 navigation_goalrating_start();
1616 // if enemies are closer to our base, go there
1617 entity head, closestplayer = world;
1618 float distance, bestdistance = 10000;
1619 FOR_EACH_PLAYER(head)
1621 if(head.deadflag!=DEAD_NO)
1624 distance = vlen(org - head.origin);
1625 if(distance<bestdistance)
1627 closestplayer = head;
1628 bestdistance = distance;
1633 if(closestplayer.team!=self.team)
1634 if(vlen(org - self.origin)>1000)
1635 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1636 havocbot_goalrating_ctf_ourbase(30000);
1638 havocbot_goalrating_ctf_ourstolenflag(20000);
1639 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1640 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1641 havocbot_goalrating_items(10000, org, mp_radius);
1642 havocbot_goalrating_items(5000, self.origin, 10000);
1643 navigation_goalrating_end();
1647 void havocbot_role_ctf_setrole(entity bot, float role)
1649 dprint(strcat(bot.netname," switched to "));
1652 case HAVOCBOT_CTF_ROLE_CARRIER:
1654 bot.havocbot_role = havocbot_role_ctf_carrier;
1655 bot.havocbot_role_timeout = 0;
1656 bot.havocbot_cantfindflag = time + 10;
1657 bot.bot_strategytime = 0;
1659 case HAVOCBOT_CTF_ROLE_DEFENSE:
1661 bot.havocbot_role = havocbot_role_ctf_defense;
1662 bot.havocbot_role_timeout = 0;
1664 case HAVOCBOT_CTF_ROLE_MIDDLE:
1666 bot.havocbot_role = havocbot_role_ctf_middle;
1667 bot.havocbot_role_timeout = 0;
1669 case HAVOCBOT_CTF_ROLE_OFFENSE:
1671 bot.havocbot_role = havocbot_role_ctf_offense;
1672 bot.havocbot_role_timeout = 0;
1674 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1675 dprint("retriever");
1676 bot.havocbot_previous_role = bot.havocbot_role;
1677 bot.havocbot_role = havocbot_role_ctf_retriever;
1678 bot.havocbot_role_timeout = time + 10;
1679 bot.bot_strategytime = 0;
1681 case HAVOCBOT_CTF_ROLE_ESCORT:
1683 bot.havocbot_previous_role = bot.havocbot_role;
1684 bot.havocbot_role = havocbot_role_ctf_escort;
1685 bot.havocbot_role_timeout = time + 30;
1686 bot.bot_strategytime = 0;
1697 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1701 // initially clear items so they can be set as necessary later.
1702 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1703 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1705 // scan through all the flags and notify the client about them
1706 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1708 switch(flag.ctf_status)
1713 if((flag.owner == self) || (flag.pass_sender == self))
1714 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1716 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1721 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1727 // item for stopping players from capturing the flag too often
1728 if(self.ctf_captureshielded)
1729 self.items |= IT_CTF_SHIELDED;
1731 // update the health of the flag carrier waypointsprite
1732 if(self.wps_flagcarrier)
1733 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1738 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1740 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1742 if(frag_target == frag_attacker) // damage done to yourself
1744 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1745 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1747 else // damage done to everyone else
1749 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1750 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1753 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1755 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)))
1756 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1758 frag_target.wps_helpme_time = time;
1759 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1761 // todo: add notification for when flag carrier needs help?
1766 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1768 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1770 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1771 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1774 if(frag_target.flagcarried)
1775 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1780 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1783 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1786 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1788 entity flag; // temporary entity for the search method
1790 if(self.flagcarried)
1791 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1793 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1795 if(flag.pass_sender == self) { flag.pass_sender = world; }
1796 if(flag.pass_target == self) { flag.pass_target = world; }
1797 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1803 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1805 if(self.flagcarried)
1806 if(!autocvar_g_ctf_portalteleport)
1807 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1812 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1814 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1816 entity player = self;
1818 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1820 // pass the flag to a team mate
1821 if(autocvar_g_ctf_pass)
1823 entity head, closest_target = world;
1824 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1826 while(head) // find the closest acceptable target to pass to
1828 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1829 if(head != player && SAME_TEAM(head, player))
1830 if(!head.speedrunning && !head.vehicle)
1832 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1833 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1834 vector passer_center = CENTER_OR_VIEWOFS(player);
1836 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1838 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1840 if(IS_BOT_CLIENT(head))
1842 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1843 ctf_Handle_Throw(head, player, DROP_PASS);
1847 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1848 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1850 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1853 else if(player.flagcarried)
1857 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1858 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1859 { closest_target = head; }
1861 else { closest_target = head; }
1868 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1871 // throw the flag in front of you
1872 if(autocvar_g_ctf_throw && player.flagcarried)
1874 if(player.throw_count == -1)
1876 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1878 player.throw_prevtime = time;
1879 player.throw_count = 1;
1880 ctf_Handle_Throw(player, world, DROP_THROW);
1885 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1891 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1892 else { player.throw_count += 1; }
1893 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1895 player.throw_prevtime = time;
1896 ctf_Handle_Throw(player, world, DROP_THROW);
1905 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1907 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1909 self.wps_helpme_time = time;
1910 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1912 else // create a normal help me waypointsprite
1914 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');
1915 WaypointSprite_Ping(self.wps_helpme);
1921 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1923 if(vh_player.flagcarried)
1925 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1927 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1931 setattachment(vh_player.flagcarried, vh_vehicle, "");
1932 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1933 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1934 //vh_player.flagcarried.angles = '0 0 0';
1942 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1944 if(vh_player.flagcarried)
1946 setattachment(vh_player.flagcarried, vh_player, "");
1947 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1948 vh_player.flagcarried.scale = FLAG_SCALE;
1949 vh_player.flagcarried.angles = '0 0 0';
1956 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1958 if(self.flagcarried)
1960 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1961 ctf_RespawnFlag(self.flagcarried);
1968 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1970 entity flag; // temporary entity for the search method
1972 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1974 switch(flag.ctf_status)
1979 // lock the flag, game is over
1980 flag.movetype = MOVETYPE_NONE;
1981 flag.takedamage = DAMAGE_NO;
1982 flag.solid = SOLID_NOT;
1983 flag.nextthink = FALSE; // stop thinking
1985 //dprint("stopping the ", flag.netname, " from moving.\n");
1993 // do nothing for these flags
2002 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2004 havocbot_ctf_reset_role(self);
2013 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2014 CTF Starting point for a player in team one (Red).
2015 Keys: "angle" viewing angle when spawning. */
2016 void spawnfunc_info_player_team1()
2018 if(g_assault) { remove(self); return; }
2020 self.team = NUM_TEAM_1; // red
2021 spawnfunc_info_player_deathmatch();
2025 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2026 CTF Starting point for a player in team two (Blue).
2027 Keys: "angle" viewing angle when spawning. */
2028 void spawnfunc_info_player_team2()
2030 if(g_assault) { remove(self); return; }
2032 self.team = NUM_TEAM_2; // blue
2033 spawnfunc_info_player_deathmatch();
2036 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2037 CTF Starting point for a player in team three (Yellow).
2038 Keys: "angle" viewing angle when spawning. */
2039 void spawnfunc_info_player_team3()
2041 if(g_assault) { remove(self); return; }
2043 self.team = NUM_TEAM_3; // yellow
2044 spawnfunc_info_player_deathmatch();
2048 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2049 CTF Starting point for a player in team four (Purple).
2050 Keys: "angle" viewing angle when spawning. */
2051 void spawnfunc_info_player_team4()
2053 if(g_assault) { remove(self); return; }
2055 self.team = NUM_TEAM_4; // purple
2056 spawnfunc_info_player_deathmatch();
2059 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2060 CTF flag for team one (Red).
2062 "angle" Angle the flag will point (minus 90 degrees)...
2063 "model" model to use, note this needs red and blue as skins 0 and 1...
2064 "noise" sound played when flag is picked up...
2065 "noise1" sound played when flag is returned by a teammate...
2066 "noise2" sound played when flag is captured...
2067 "noise3" sound played when flag is lost in the field and respawns itself...
2068 "noise4" sound played when flag is dropped by a player...
2069 "noise5" sound played when flag touches the ground... */
2070 void spawnfunc_item_flag_team1()
2072 if(!g_ctf) { remove(self); return; }
2074 ctf_FlagSetup(1, self); // 1 = red
2077 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2078 CTF flag for team two (Blue).
2080 "angle" Angle the flag will point (minus 90 degrees)...
2081 "model" model to use, note this needs red and blue as skins 0 and 1...
2082 "noise" sound played when flag is picked up...
2083 "noise1" sound played when flag is returned by a teammate...
2084 "noise2" sound played when flag is captured...
2085 "noise3" sound played when flag is lost in the field and respawns itself...
2086 "noise4" sound played when flag is dropped by a player...
2087 "noise5" sound played when flag touches the ground... */
2088 void spawnfunc_item_flag_team2()
2090 if(!g_ctf) { remove(self); return; }
2092 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2095 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2096 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2097 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.
2099 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2100 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2101 void spawnfunc_ctf_team()
2103 if(!g_ctf) { remove(self); return; }
2105 self.classname = "ctf_team";
2106 self.team = self.cnt + 1;
2109 // compatibility for quake maps
2110 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2111 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2112 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2113 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2114 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2115 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2123 void ctf_ScoreRules()
2125 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2126 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2127 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2128 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2129 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2130 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2131 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2132 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2133 ScoreRules_basics_end();
2136 // code from here on is just to support maps that don't have flag and team entities
2137 void ctf_SpawnTeam (string teamname, float teamcolor)
2142 self.classname = "ctf_team";
2143 self.netname = teamname;
2144 self.cnt = teamcolor;
2146 spawnfunc_ctf_team();
2151 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2153 // if no teams are found, spawn defaults
2154 if(find(world, classname, "ctf_team") == world)
2156 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2157 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2158 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2164 void ctf_Initialize()
2166 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2168 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2169 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2170 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2172 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2176 MUTATOR_DEFINITION(gamemode_ctf)
2178 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2179 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2180 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2181 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2182 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2183 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2184 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2185 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2186 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2187 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2188 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2189 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2190 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2191 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2195 if(time > 1) // game loads at time 1
2196 error("This is a game type and it cannot be added at runtime.");
2200 MUTATOR_ONROLLBACK_OR_REMOVE
2202 // we actually cannot roll back ctf_Initialize here
2203 // BUT: we don't need to! If this gets called, adding always
2209 print("This is a game type and it cannot be removed at runtime.");