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 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
182 void ctf_CaptureShield_Spawn(entity flag)
184 entity shield = spawn();
187 shield.team = self.team;
188 shield.touch = ctf_CaptureShield_Touch;
189 shield.customizeentityforclient = ctf_CaptureShield_Customize;
190 shield.classname = "ctf_captureshield";
191 shield.effects = EF_ADDITIVE;
192 shield.movetype = MOVETYPE_NOCLIP;
193 shield.solid = SOLID_TRIGGER;
194 shield.avelocity = '7 0 11';
197 setorigin(shield, self.origin);
198 setmodel(shield, "models/ctf/shield.md3");
199 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
203 // ====================
204 // Drop/Pass/Throw Code
205 // ====================
207 void ctf_Handle_Drop(entity flag, entity player, float droptype)
210 player = (player ? player : flag.pass_sender);
213 flag.movetype = MOVETYPE_TOSS;
214 flag.takedamage = DAMAGE_YES;
215 flag.angles = '0 0 0';
216 flag.health = flag.max_flag_health;
217 flag.ctf_droptime = time;
218 flag.ctf_dropper = player;
219 flag.ctf_status = FLAG_DROPPED;
221 // messages and sounds
222 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
223 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
224 ctf_EventLog("dropped", player.team, player);
227 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
228 PlayerScore_Add(player, SP_CTF_DROPS, 1);
231 if(autocvar_g_ctf_flag_dropped_waypoint)
232 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
234 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
236 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
237 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
240 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
242 if(droptype == DROP_PASS)
244 flag.pass_distance = 0;
245 flag.pass_sender = world;
246 flag.pass_target = world;
250 void ctf_Handle_Retrieve(entity flag, entity player)
252 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
253 entity sender = flag.pass_sender;
255 // transfer flag to player
257 flag.owner.flagcarried = flag;
262 setattachment(flag, player.vehicle, "");
263 setorigin(flag, VEHICLE_FLAG_OFFSET);
264 flag.scale = VEHICLE_FLAG_SCALE;
268 setattachment(flag, player, "");
269 setorigin(flag, FLAG_CARRY_OFFSET);
271 flag.movetype = MOVETYPE_NONE;
272 flag.takedamage = DAMAGE_NO;
273 flag.solid = SOLID_NOT;
274 flag.angles = '0 0 0';
275 flag.ctf_status = FLAG_CARRY;
277 // messages and sounds
278 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
279 ctf_EventLog("receive", flag.team, player);
281 FOR_EACH_REALPLAYER(tmp_player)
283 if(tmp_player == sender)
284 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
285 else if(tmp_player == player)
286 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
287 else if(SAME_TEAM(tmp_player, sender))
288 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
291 // create new waypoint
292 ctf_FlagcarrierWaypoints(player);
294 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
295 player.throw_antispam = sender.throw_antispam;
297 flag.pass_distance = 0;
298 flag.pass_sender = world;
299 flag.pass_target = world;
302 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
304 entity flag = player.flagcarried;
305 vector targ_origin, flag_velocity;
307 if(!flag) { return; }
308 if((droptype == DROP_PASS) && !receiver) { return; }
310 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
313 setattachment(flag, world, "");
314 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
315 flag.owner.flagcarried = world;
317 flag.solid = SOLID_TRIGGER;
318 flag.ctf_dropper = player;
319 flag.ctf_droptime = time;
321 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
328 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
329 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
330 WarpZone_RefSys_Copy(flag, receiver);
331 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
332 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
334 flag.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' * player.origin_x) + ('0 1 0' * player.origin_y))); // for the sake of this check, exclude Z axis
335 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
338 flag.movetype = MOVETYPE_FLY;
339 flag.takedamage = DAMAGE_NO;
340 flag.pass_sender = player;
341 flag.pass_target = receiver;
342 flag.ctf_status = FLAG_PASSING;
345 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
346 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
347 ctf_EventLog("pass", flag.team, player);
353 makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
355 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
356 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
357 ctf_Handle_Drop(flag, player, droptype);
363 flag.velocity = '0 0 0'; // do nothing
370 flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
371 ctf_Handle_Drop(flag, player, droptype);
376 // kill old waypointsprite
377 WaypointSprite_Ping(player.wps_flagcarrier);
378 WaypointSprite_Kill(player.wps_flagcarrier);
380 if(player.wps_enemyflagcarrier)
381 WaypointSprite_Kill(player.wps_enemyflagcarrier);
384 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
392 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
394 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
395 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
396 float old_time, new_time;
398 if (!player) { return; } // without someone to give the reward to, we can't possibly cap
400 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
402 // messages and sounds
403 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
404 ctf_CaptureRecord(enemy_flag, player);
405 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
409 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
410 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
415 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
416 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
418 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
419 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
420 if(!old_time || new_time < old_time)
421 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
424 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
425 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
428 if(capturetype == CAPTURE_NORMAL)
430 WaypointSprite_Kill(player.wps_flagcarrier);
431 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
433 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
434 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
438 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
439 ctf_RespawnFlag(enemy_flag);
442 void ctf_Handle_Return(entity flag, entity player)
444 // messages and sounds
445 if(player.flags & FL_MONSTER)
447 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
451 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
452 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
454 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
455 ctf_EventLog("return", flag.team, player);
458 if(IS_PLAYER(player))
460 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
461 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
463 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
466 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
470 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
471 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
472 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
476 ctf_RespawnFlag(flag);
479 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
482 float pickup_dropped_score; // used to calculate dropped pickup score
484 // attach the flag to the player
486 player.flagcarried = flag;
489 setattachment(flag, player.vehicle, "");
490 setorigin(flag, VEHICLE_FLAG_OFFSET);
491 flag.scale = VEHICLE_FLAG_SCALE;
495 setattachment(flag, player, "");
496 setorigin(flag, FLAG_CARRY_OFFSET);
500 flag.movetype = MOVETYPE_NONE;
501 flag.takedamage = DAMAGE_NO;
502 flag.solid = SOLID_NOT;
503 flag.angles = '0 0 0';
504 flag.ctf_status = FLAG_CARRY;
508 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
509 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
513 // messages and sounds
514 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
515 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
516 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
518 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
519 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
521 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
524 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
525 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
530 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
531 ctf_EventLog("steal", flag.team, player);
537 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);
538 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);
539 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
540 PlayerTeamScore_AddScore(player, pickup_dropped_score);
541 ctf_EventLog("pickup", flag.team, player);
549 if(pickuptype == PICKUP_BASE)
551 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
552 if((player.speedrunning) && (ctf_captimerecord))
553 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
557 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
560 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
561 ctf_FlagcarrierWaypoints(player);
562 WaypointSprite_Ping(player.wps_flagcarrier);
566 // ===================
567 // Main Flag Functions
568 // ===================
570 void ctf_CheckFlagReturn(entity flag, float returntype)
572 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
574 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
576 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
580 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
581 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
582 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
583 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
587 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
589 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
590 ctf_EventLog("returned", flag.team, world);
591 ctf_RespawnFlag(flag);
596 void ctf_CheckStalemate(void)
599 float stale_red_flags = 0, stale_blue_flags = 0;
602 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
604 // build list of stale flags
605 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
607 if(autocvar_g_ctf_stalemate)
608 if(tmp_entity.ctf_status != FLAG_BASE)
609 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
611 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
612 ctf_staleflaglist = tmp_entity;
614 switch(tmp_entity.team)
616 case NUM_TEAM_1: ++stale_red_flags; break;
617 case NUM_TEAM_2: ++stale_blue_flags; break;
622 if(stale_red_flags && stale_blue_flags)
623 ctf_stalemate = TRUE;
624 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
625 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
626 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
627 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
629 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
632 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
634 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
635 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));
638 if (!wpforenemy_announced)
640 FOR_EACH_REALPLAYER(tmp_entity)
641 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
643 wpforenemy_announced = TRUE;
648 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
650 if(ITEM_DAMAGE_NEEDKILL(deathtype))
652 // automatically kill the flag and return it
654 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
657 if(autocvar_g_ctf_flag_return_damage)
659 // reduce health and check if it should be returned
660 self.health = self.health - damage;
661 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
671 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
674 if(self == ctf_worldflaglist) // only for the first flag
675 FOR_EACH_CLIENT(tmp_entity)
676 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
679 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
680 dprint("wtf the flag got squashed?\n");
681 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
682 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
683 setsize(self, FLAG_MIN, FLAG_MAX); }
685 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
689 self.angles = '0 0 0';
697 switch(self.ctf_status)
701 if(autocvar_g_ctf_dropped_capture_radius)
703 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
704 if(tmp_entity.ctf_status == FLAG_DROPPED)
705 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
706 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
707 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
714 if(autocvar_g_ctf_flag_dropped_floatinwater)
716 vector midpoint = ((self.absmin + self.absmax) * 0.5);
717 if(pointcontents(midpoint) == CONTENT_WATER)
719 self.velocity = self.velocity * 0.5;
721 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
722 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
724 { self.movetype = MOVETYPE_FLY; }
726 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
728 if(autocvar_g_ctf_flag_return_dropped)
730 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
733 ctf_CheckFlagReturn(self, RETURN_DROPPED);
737 if(autocvar_g_ctf_flag_return_time)
739 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
740 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
748 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
751 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
755 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
759 if(autocvar_g_ctf_stalemate)
761 if(time >= wpforenemy_nextthink)
763 ctf_CheckStalemate();
764 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
772 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
773 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
774 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
776 if((self.pass_target == world)
777 || (self.pass_target.deadflag != DEAD_NO)
778 || (self.pass_target.flagcarried)
779 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
780 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
781 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
783 // give up, pass failed
784 ctf_Handle_Drop(self, world, DROP_PASS);
788 // still a viable target, go for it
789 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
794 default: // this should never happen
796 dprint("ctf_FlagThink(): Flag exists with no status?\n");
804 if(gameover) { return; }
805 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
807 entity toucher = other;
808 float is_not_monster = (!(toucher.flags & FL_MONSTER));
810 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
811 if(ITEM_TOUCH_NEEDKILL())
814 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
818 // special touch behaviors
819 if(toucher.frozen) { return; }
820 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
822 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
823 toucher = toucher.owner; // the player is actually the vehicle owner, not other
825 return; // do nothing
827 else if(toucher.flags & FL_MONSTER)
829 if(!autocvar_g_ctf_allow_monster_touch)
830 return; // do nothing
832 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
834 if(time > self.wait) // if we haven't in a while, play a sound/effect
836 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
837 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
838 self.wait = time + FLAG_TOUCHRATE;
842 else if(toucher.deadflag != DEAD_NO) { return; }
844 switch(self.ctf_status)
848 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
849 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
850 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
851 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
857 if(SAME_TEAM(toucher, self))
858 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
859 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
860 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
866 dprint("Someone touched a flag even though it was being carried?\n");
872 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
874 if(DIFF_TEAM(toucher, self.pass_sender))
875 ctf_Handle_Return(self, toucher);
877 ctf_Handle_Retrieve(self, toucher);
885 void ctf_RespawnFlag(entity flag)
887 // check for flag respawn being called twice in a row
888 if(flag.last_respawn > time - 0.5)
889 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
891 flag.last_respawn = time;
893 // reset the player (if there is one)
894 if((flag.owner) && (flag.owner.flagcarried == flag))
896 if(flag.owner.wps_enemyflagcarrier)
897 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
899 WaypointSprite_Kill(flag.wps_flagcarrier);
901 flag.owner.flagcarried = world;
903 if(flag.speedrunning)
904 ctf_FakeTimeLimit(flag.owner, -1);
907 if((flag.owner) && (flag.owner.vehicle))
908 flag.scale = FLAG_SCALE;
910 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
911 { WaypointSprite_Kill(flag.wps_flagdropped); }
914 setattachment(flag, world, "");
915 setorigin(flag, flag.ctf_spawnorigin);
917 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
918 flag.takedamage = DAMAGE_NO;
919 flag.health = flag.max_flag_health;
920 flag.solid = SOLID_TRIGGER;
921 flag.velocity = '0 0 0';
922 flag.angles = flag.mangle;
923 flag.flags = FL_ITEM | FL_NOTARGET;
925 flag.ctf_status = FLAG_BASE;
927 flag.pass_distance = 0;
928 flag.pass_sender = world;
929 flag.pass_target = world;
930 flag.ctf_dropper = world;
931 flag.ctf_pickuptime = 0;
932 flag.ctf_droptime = 0;
934 ctf_CheckStalemate();
940 if(IS_PLAYER(self.owner))
941 ctf_Handle_Throw(self.owner, world, DROP_RESET);
943 ctf_RespawnFlag(self);
946 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
949 waypoint_spawnforitem_force(self, self.origin);
950 self.nearestwaypointtimeout = 0; // activate waypointing again
951 self.bot_basewaypoint = self.nearestwaypoint;
954 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
955 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
957 // captureshield setup
958 ctf_CaptureShield_Spawn(self);
961 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
964 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.
965 self = flag; // for later usage with droptofloor()
968 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
969 ctf_worldflaglist = flag;
971 setattachment(flag, world, "");
973 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
974 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
975 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
976 flag.classname = "item_flag_team";
977 flag.target = "###item###"; // wut?
978 flag.flags = FL_ITEM | FL_NOTARGET;
979 flag.solid = SOLID_TRIGGER;
980 flag.takedamage = DAMAGE_NO;
981 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
982 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
983 flag.health = flag.max_flag_health;
984 flag.event_damage = ctf_FlagDamage;
985 flag.pushable = TRUE;
986 flag.teleportable = TELEPORT_NORMAL;
987 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
988 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
989 flag.velocity = '0 0 0';
990 flag.mangle = flag.angles;
991 flag.reset = ctf_Reset;
992 flag.touch = ctf_FlagTouch;
993 flag.think = ctf_FlagThink;
994 flag.nextthink = time + FLAG_THINKRATE;
995 flag.ctf_status = FLAG_BASE;
998 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
999 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1000 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
1001 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
1002 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
1003 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
1006 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
1007 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
1008 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
1009 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.
1010 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1011 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1012 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1015 precache_sound(flag.snd_flag_taken);
1016 precache_sound(flag.snd_flag_returned);
1017 precache_sound(flag.snd_flag_capture);
1018 precache_sound(flag.snd_flag_respawn);
1019 precache_sound(flag.snd_flag_dropped);
1020 precache_sound(flag.snd_flag_touch);
1021 precache_sound(flag.snd_flag_pass);
1022 precache_model(flag.model);
1023 precache_model("models/ctf/shield.md3");
1024 precache_model("models/ctf/shockwavetransring.md3");
1027 setmodel(flag, flag.model); // precision set below
1028 setsize(flag, FLAG_MIN, FLAG_MAX);
1029 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1031 if(autocvar_g_ctf_flag_glowtrails)
1033 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1034 flag.glow_size = 25;
1035 flag.glow_trail = 1;
1038 flag.effects |= EF_LOWPRECISION;
1039 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1040 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1043 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1045 flag.dropped_origin = flag.origin;
1046 flag.noalign = TRUE;
1047 flag.movetype = MOVETYPE_NONE;
1049 else // drop to floor, automatically find a platform and set that as spawn origin
1051 flag.noalign = FALSE;
1054 flag.movetype = MOVETYPE_TOSS;
1057 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1065 // NOTE: LEGACY CODE, needs to be re-written!
1067 void havocbot_calculate_middlepoint()
1071 vector fo = '0 0 0';
1074 f = ctf_worldflaglist;
1079 f = f.ctf_worldflagnext;
1083 havocbot_ctf_middlepoint = s * (1.0 / n);
1084 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1088 entity havocbot_ctf_find_flag(entity bot)
1091 f = ctf_worldflaglist;
1094 if (bot.team == f.team)
1096 f = f.ctf_worldflagnext;
1101 entity havocbot_ctf_find_enemy_flag(entity bot)
1104 f = ctf_worldflaglist;
1107 if (bot.team != f.team)
1109 f = f.ctf_worldflagnext;
1114 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1122 FOR_EACH_PLAYER(head)
1124 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1127 if(vlen(head.origin - org) < tc_radius)
1134 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1137 head = ctf_worldflaglist;
1140 if (self.team == head.team)
1142 head = head.ctf_worldflagnext;
1145 navigation_routerating(head, ratingscale, 10000);
1148 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1151 head = ctf_worldflaglist;
1154 if (self.team == head.team)
1156 head = head.ctf_worldflagnext;
1161 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1164 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1167 head = ctf_worldflaglist;
1170 if (self.team != head.team)
1172 head = head.ctf_worldflagnext;
1175 navigation_routerating(head, ratingscale, 10000);
1178 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1180 if (!bot_waypoints_for_items)
1182 havocbot_goalrating_ctf_enemyflag(ratingscale);
1188 head = havocbot_ctf_find_enemy_flag(self);
1193 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1196 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1200 mf = havocbot_ctf_find_flag(self);
1202 if(mf.ctf_status == FLAG_BASE)
1206 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1209 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1212 head = ctf_worldflaglist;
1215 // flag is out in the field
1216 if(head.ctf_status != FLAG_BASE)
1217 if(head.tag_entity==world) // dropped
1221 if(vlen(org-head.origin)<df_radius)
1222 navigation_routerating(head, ratingscale, 10000);
1225 navigation_routerating(head, ratingscale, 10000);
1228 head = head.ctf_worldflagnext;
1232 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1236 head = findchainfloat(bot_pickup, TRUE);
1239 // gather health and armor only
1241 if (head.health || head.armorvalue)
1242 if (vlen(head.origin - org) < sradius)
1244 // get the value of the item
1245 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1247 navigation_routerating(head, t * ratingscale, 500);
1253 void havocbot_ctf_reset_role(entity bot)
1255 float cdefense, cmiddle, coffense;
1256 entity mf, ef, head;
1259 if(bot.deadflag != DEAD_NO)
1262 if(vlen(havocbot_ctf_middlepoint)==0)
1263 havocbot_calculate_middlepoint();
1266 if (bot.flagcarried)
1268 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1272 mf = havocbot_ctf_find_flag(bot);
1273 ef = havocbot_ctf_find_enemy_flag(bot);
1275 // Retrieve stolen flag
1276 if(mf.ctf_status!=FLAG_BASE)
1278 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1282 // If enemy flag is taken go to the middle to intercept pursuers
1283 if(ef.ctf_status!=FLAG_BASE)
1285 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1289 // if there is only me on the team switch to offense
1291 FOR_EACH_PLAYER(head)
1292 if(SAME_TEAM(head, bot))
1297 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1301 // Evaluate best position to take
1302 // Count mates on middle position
1303 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1305 // Count mates on defense position
1306 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1308 // Count mates on offense position
1309 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1311 if(cdefense<=coffense)
1312 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1313 else if(coffense<=cmiddle)
1314 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1316 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1319 void havocbot_role_ctf_carrier()
1321 if(self.deadflag != DEAD_NO)
1323 havocbot_ctf_reset_role(self);
1327 if (self.flagcarried == world)
1329 havocbot_ctf_reset_role(self);
1333 if (self.bot_strategytime < time)
1335 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1337 navigation_goalrating_start();
1338 havocbot_goalrating_ctf_ourbase(50000);
1341 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1343 navigation_goalrating_end();
1345 if (self.navigation_hasgoals)
1346 self.havocbot_cantfindflag = time + 10;
1347 else if (time > self.havocbot_cantfindflag)
1349 // Can't navigate to my own base, suicide!
1350 // TODO: drop it and wander around
1351 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1357 void havocbot_role_ctf_escort()
1361 if(self.deadflag != DEAD_NO)
1363 havocbot_ctf_reset_role(self);
1367 if (self.flagcarried)
1369 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1373 // If enemy flag is back on the base switch to previous role
1374 ef = havocbot_ctf_find_enemy_flag(self);
1375 if(ef.ctf_status==FLAG_BASE)
1377 self.havocbot_role = self.havocbot_previous_role;
1378 self.havocbot_role_timeout = 0;
1382 // If the flag carrier reached the base switch to defense
1383 mf = havocbot_ctf_find_flag(self);
1384 if(mf.ctf_status!=FLAG_BASE)
1385 if(vlen(ef.origin - mf.dropped_origin) < 300)
1387 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1391 // Set the role timeout if necessary
1392 if (!self.havocbot_role_timeout)
1394 self.havocbot_role_timeout = time + random() * 30 + 60;
1397 // If nothing happened just switch to previous role
1398 if (time > self.havocbot_role_timeout)
1400 self.havocbot_role = self.havocbot_previous_role;
1401 self.havocbot_role_timeout = 0;
1405 // Chase the flag carrier
1406 if (self.bot_strategytime < time)
1408 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1409 navigation_goalrating_start();
1410 havocbot_goalrating_ctf_enemyflag(30000);
1411 havocbot_goalrating_ctf_ourstolenflag(40000);
1412 havocbot_goalrating_items(10000, self.origin, 10000);
1413 navigation_goalrating_end();
1417 void havocbot_role_ctf_offense()
1422 if(self.deadflag != DEAD_NO)
1424 havocbot_ctf_reset_role(self);
1428 if (self.flagcarried)
1430 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1435 mf = havocbot_ctf_find_flag(self);
1436 ef = havocbot_ctf_find_enemy_flag(self);
1439 if(mf.ctf_status!=FLAG_BASE)
1442 pos = mf.tag_entity.origin;
1446 // Try to get it if closer than the enemy base
1447 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1449 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1454 // Escort flag carrier
1455 if(ef.ctf_status!=FLAG_BASE)
1458 pos = ef.tag_entity.origin;
1462 if(vlen(pos-mf.dropped_origin)>700)
1464 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1469 // About to fail, switch to middlefield
1472 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1476 // Set the role timeout if necessary
1477 if (!self.havocbot_role_timeout)
1478 self.havocbot_role_timeout = time + 120;
1480 if (time > self.havocbot_role_timeout)
1482 havocbot_ctf_reset_role(self);
1486 if (self.bot_strategytime < time)
1488 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1489 navigation_goalrating_start();
1490 havocbot_goalrating_ctf_ourstolenflag(50000);
1491 havocbot_goalrating_ctf_enemybase(20000);
1492 havocbot_goalrating_items(5000, self.origin, 1000);
1493 havocbot_goalrating_items(1000, self.origin, 10000);
1494 navigation_goalrating_end();
1498 // Retriever (temporary role):
1499 void havocbot_role_ctf_retriever()
1503 if(self.deadflag != DEAD_NO)
1505 havocbot_ctf_reset_role(self);
1509 if (self.flagcarried)
1511 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1515 // If flag is back on the base switch to previous role
1516 mf = havocbot_ctf_find_flag(self);
1517 if(mf.ctf_status==FLAG_BASE)
1519 havocbot_ctf_reset_role(self);
1523 if (!self.havocbot_role_timeout)
1524 self.havocbot_role_timeout = time + 20;
1526 if (time > self.havocbot_role_timeout)
1528 havocbot_ctf_reset_role(self);
1532 if (self.bot_strategytime < time)
1537 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1538 navigation_goalrating_start();
1539 havocbot_goalrating_ctf_ourstolenflag(50000);
1540 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1541 havocbot_goalrating_ctf_enemybase(30000);
1542 havocbot_goalrating_items(500, self.origin, rt_radius);
1543 navigation_goalrating_end();
1547 void havocbot_role_ctf_middle()
1551 if(self.deadflag != DEAD_NO)
1553 havocbot_ctf_reset_role(self);
1557 if (self.flagcarried)
1559 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1563 mf = havocbot_ctf_find_flag(self);
1564 if(mf.ctf_status!=FLAG_BASE)
1566 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1570 if (!self.havocbot_role_timeout)
1571 self.havocbot_role_timeout = time + 10;
1573 if (time > self.havocbot_role_timeout)
1575 havocbot_ctf_reset_role(self);
1579 if (self.bot_strategytime < time)
1583 org = havocbot_ctf_middlepoint;
1584 org_z = self.origin_z;
1586 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1587 navigation_goalrating_start();
1588 havocbot_goalrating_ctf_ourstolenflag(50000);
1589 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1590 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1591 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1592 havocbot_goalrating_items(2500, self.origin, 10000);
1593 havocbot_goalrating_ctf_enemybase(2500);
1594 navigation_goalrating_end();
1598 void havocbot_role_ctf_defense()
1602 if(self.deadflag != DEAD_NO)
1604 havocbot_ctf_reset_role(self);
1608 if (self.flagcarried)
1610 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1614 // If own flag was captured
1615 mf = havocbot_ctf_find_flag(self);
1616 if(mf.ctf_status!=FLAG_BASE)
1618 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1622 if (!self.havocbot_role_timeout)
1623 self.havocbot_role_timeout = time + 30;
1625 if (time > self.havocbot_role_timeout)
1627 havocbot_ctf_reset_role(self);
1630 if (self.bot_strategytime < time)
1635 org = mf.dropped_origin;
1636 mp_radius = havocbot_ctf_middlepoint_radius;
1638 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1639 navigation_goalrating_start();
1641 // if enemies are closer to our base, go there
1642 entity head, closestplayer = world;
1643 float distance, bestdistance = 10000;
1644 FOR_EACH_PLAYER(head)
1646 if(head.deadflag!=DEAD_NO)
1649 distance = vlen(org - head.origin);
1650 if(distance<bestdistance)
1652 closestplayer = head;
1653 bestdistance = distance;
1658 if(DIFF_TEAM(closestplayer, self))
1659 if(vlen(org - self.origin)>1000)
1660 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1661 havocbot_goalrating_ctf_ourbase(30000);
1663 havocbot_goalrating_ctf_ourstolenflag(20000);
1664 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1665 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1666 havocbot_goalrating_items(10000, org, mp_radius);
1667 havocbot_goalrating_items(5000, self.origin, 10000);
1668 navigation_goalrating_end();
1672 void havocbot_role_ctf_setrole(entity bot, float role)
1674 dprint(strcat(bot.netname," switched to "));
1677 case HAVOCBOT_CTF_ROLE_CARRIER:
1679 bot.havocbot_role = havocbot_role_ctf_carrier;
1680 bot.havocbot_role_timeout = 0;
1681 bot.havocbot_cantfindflag = time + 10;
1682 bot.bot_strategytime = 0;
1684 case HAVOCBOT_CTF_ROLE_DEFENSE:
1686 bot.havocbot_role = havocbot_role_ctf_defense;
1687 bot.havocbot_role_timeout = 0;
1689 case HAVOCBOT_CTF_ROLE_MIDDLE:
1691 bot.havocbot_role = havocbot_role_ctf_middle;
1692 bot.havocbot_role_timeout = 0;
1694 case HAVOCBOT_CTF_ROLE_OFFENSE:
1696 bot.havocbot_role = havocbot_role_ctf_offense;
1697 bot.havocbot_role_timeout = 0;
1699 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1700 dprint("retriever");
1701 bot.havocbot_previous_role = bot.havocbot_role;
1702 bot.havocbot_role = havocbot_role_ctf_retriever;
1703 bot.havocbot_role_timeout = time + 10;
1704 bot.bot_strategytime = 0;
1706 case HAVOCBOT_CTF_ROLE_ESCORT:
1708 bot.havocbot_previous_role = bot.havocbot_role;
1709 bot.havocbot_role = havocbot_role_ctf_escort;
1710 bot.havocbot_role_timeout = time + 30;
1711 bot.bot_strategytime = 0;
1722 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1726 // initially clear items so they can be set as necessary later.
1727 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1728 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1730 // scan through all the flags and notify the client about them
1731 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1733 switch(flag.ctf_status)
1738 if((flag.owner == self) || (flag.pass_sender == self))
1739 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1741 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1746 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1752 // item for stopping players from capturing the flag too often
1753 if(self.ctf_captureshielded)
1754 self.items |= IT_CTF_SHIELDED;
1756 // update the health of the flag carrier waypointsprite
1757 if(self.wps_flagcarrier)
1758 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1763 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1765 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1767 if(frag_target == frag_attacker) // damage done to yourself
1769 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1770 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1772 else // damage done to everyone else
1774 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1775 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1778 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1780 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)))
1781 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1783 frag_target.wps_helpme_time = time;
1784 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1786 // todo: add notification for when flag carrier needs help?
1791 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1793 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1795 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1796 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1799 if(frag_target.flagcarried)
1800 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1805 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1808 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1811 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1813 entity flag; // temporary entity for the search method
1815 if(self.flagcarried)
1816 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1818 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1820 if(flag.pass_sender == self) { flag.pass_sender = world; }
1821 if(flag.pass_target == self) { flag.pass_target = world; }
1822 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1828 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1830 if(self.flagcarried)
1831 if(!autocvar_g_ctf_portalteleport)
1832 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1837 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1839 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1841 entity player = self;
1843 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1845 // pass the flag to a team mate
1846 if(autocvar_g_ctf_pass)
1848 entity head, closest_target = world;
1849 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1851 while(head) // find the closest acceptable target to pass to
1853 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1854 if(head != player && SAME_TEAM(head, player))
1855 if(!head.speedrunning && !head.vehicle)
1857 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1858 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1859 vector passer_center = CENTER_OR_VIEWOFS(player);
1861 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1863 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1865 if(IS_BOT_CLIENT(head))
1867 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1868 ctf_Handle_Throw(head, player, DROP_PASS);
1872 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1873 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1875 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1878 else if(player.flagcarried)
1882 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1883 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1884 { closest_target = head; }
1886 else { closest_target = head; }
1893 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1896 // throw the flag in front of you
1897 if(autocvar_g_ctf_throw && player.flagcarried)
1899 if(player.throw_count == -1)
1901 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1903 player.throw_prevtime = time;
1904 player.throw_count = 1;
1905 ctf_Handle_Throw(player, world, DROP_THROW);
1910 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1916 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1917 else { player.throw_count += 1; }
1918 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1920 player.throw_prevtime = time;
1921 ctf_Handle_Throw(player, world, DROP_THROW);
1930 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1932 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1934 self.wps_helpme_time = time;
1935 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1937 else // create a normal help me waypointsprite
1939 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');
1940 WaypointSprite_Ping(self.wps_helpme);
1946 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1948 if(vh_player.flagcarried)
1950 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1952 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1956 setattachment(vh_player.flagcarried, vh_vehicle, "");
1957 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1958 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1959 //vh_player.flagcarried.angles = '0 0 0';
1967 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1969 if(vh_player.flagcarried)
1971 setattachment(vh_player.flagcarried, vh_player, "");
1972 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1973 vh_player.flagcarried.scale = FLAG_SCALE;
1974 vh_player.flagcarried.angles = '0 0 0';
1981 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1983 if(self.flagcarried)
1985 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1986 ctf_RespawnFlag(self.flagcarried);
1993 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1995 entity flag; // temporary entity for the search method
1997 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1999 switch(flag.ctf_status)
2004 // lock the flag, game is over
2005 flag.movetype = MOVETYPE_NONE;
2006 flag.takedamage = DAMAGE_NO;
2007 flag.solid = SOLID_NOT;
2008 flag.nextthink = FALSE; // stop thinking
2010 //dprint("stopping the ", flag.netname, " from moving.\n");
2018 // do nothing for these flags
2027 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2029 havocbot_ctf_reset_role(self);
2038 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2039 CTF Starting point for a player in team one (Red).
2040 Keys: "angle" viewing angle when spawning. */
2041 void spawnfunc_info_player_team1()
2043 if(g_assault) { remove(self); return; }
2045 self.team = NUM_TEAM_1; // red
2046 spawnfunc_info_player_deathmatch();
2050 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2051 CTF Starting point for a player in team two (Blue).
2052 Keys: "angle" viewing angle when spawning. */
2053 void spawnfunc_info_player_team2()
2055 if(g_assault) { remove(self); return; }
2057 self.team = NUM_TEAM_2; // blue
2058 spawnfunc_info_player_deathmatch();
2061 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2062 CTF Starting point for a player in team three (Yellow).
2063 Keys: "angle" viewing angle when spawning. */
2064 void spawnfunc_info_player_team3()
2066 if(g_assault) { remove(self); return; }
2068 self.team = NUM_TEAM_3; // yellow
2069 spawnfunc_info_player_deathmatch();
2073 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2074 CTF Starting point for a player in team four (Purple).
2075 Keys: "angle" viewing angle when spawning. */
2076 void spawnfunc_info_player_team4()
2078 if(g_assault) { remove(self); return; }
2080 self.team = NUM_TEAM_4; // purple
2081 spawnfunc_info_player_deathmatch();
2084 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2085 CTF flag for team one (Red).
2087 "angle" Angle the flag will point (minus 90 degrees)...
2088 "model" model to use, note this needs red and blue as skins 0 and 1...
2089 "noise" sound played when flag is picked up...
2090 "noise1" sound played when flag is returned by a teammate...
2091 "noise2" sound played when flag is captured...
2092 "noise3" sound played when flag is lost in the field and respawns itself...
2093 "noise4" sound played when flag is dropped by a player...
2094 "noise5" sound played when flag touches the ground... */
2095 void spawnfunc_item_flag_team1()
2097 if(!g_ctf) { remove(self); return; }
2099 ctf_FlagSetup(1, self); // 1 = red
2102 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2103 CTF flag for team two (Blue).
2105 "angle" Angle the flag will point (minus 90 degrees)...
2106 "model" model to use, note this needs red and blue as skins 0 and 1...
2107 "noise" sound played when flag is picked up...
2108 "noise1" sound played when flag is returned by a teammate...
2109 "noise2" sound played when flag is captured...
2110 "noise3" sound played when flag is lost in the field and respawns itself...
2111 "noise4" sound played when flag is dropped by a player...
2112 "noise5" sound played when flag touches the ground... */
2113 void spawnfunc_item_flag_team2()
2115 if(!g_ctf) { remove(self); return; }
2117 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2120 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2121 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2122 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.
2124 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2125 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2126 void spawnfunc_ctf_team()
2128 if(!g_ctf) { remove(self); return; }
2130 self.classname = "ctf_team";
2131 self.team = self.cnt + 1;
2134 // compatibility for quake maps
2135 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2136 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2137 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2138 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2139 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2140 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2148 void ctf_ScoreRules()
2150 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2151 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2152 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2153 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2154 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2155 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2156 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2157 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2158 ScoreRules_basics_end();
2161 // code from here on is just to support maps that don't have flag and team entities
2162 void ctf_SpawnTeam (string teamname, float teamcolor)
2167 self.classname = "ctf_team";
2168 self.netname = teamname;
2169 self.cnt = teamcolor;
2171 spawnfunc_ctf_team();
2176 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2178 // if no teams are found, spawn defaults
2179 if(find(world, classname, "ctf_team") == world)
2181 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2182 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2183 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2189 void ctf_Initialize()
2191 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2193 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2194 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2195 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2197 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2201 MUTATOR_DEFINITION(gamemode_ctf)
2203 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2204 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2205 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2206 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2207 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2208 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2209 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2210 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2211 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2212 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2213 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2214 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2215 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2216 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2220 if(time > 1) // game loads at time 1
2221 error("This is a game type and it cannot be added at runtime.");
2225 MUTATOR_ONROLLBACK_OR_REMOVE
2227 // we actually cannot roll back ctf_Initialize here
2228 // BUT: we don't need to! If this gets called, adding always
2234 print("This is a game type and it cannot be removed at runtime.");