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;
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 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
779 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
780 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
782 // give up, pass failed
783 ctf_Handle_Drop(self, world, DROP_PASS);
787 // still a viable target, go for it
788 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
793 default: // this should never happen
795 dprint("ctf_FlagThink(): Flag exists with no status?\n");
803 if(gameover) { return; }
805 entity toucher = other;
806 float is_not_monster = (!(toucher.flags & FL_MONSTER));
808 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
809 if(ITEM_TOUCH_NEEDKILL())
812 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
816 // special touch behaviors
817 if(toucher.frozen) { return; }
818 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
820 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
821 toucher = toucher.owner; // the player is actually the vehicle owner, not other
823 return; // do nothing
825 else if(toucher.flags & FL_MONSTER)
827 if(!autocvar_g_ctf_allow_monster_touch)
828 return; // do nothing
830 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
832 if(time > self.wait) // if we haven't in a while, play a sound/effect
834 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
835 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
836 self.wait = time + FLAG_TOUCHRATE;
840 else if(toucher.deadflag != DEAD_NO) { return; }
842 switch(self.ctf_status)
846 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
847 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
848 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
849 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
855 if(SAME_TEAM(toucher, self))
856 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
857 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
858 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
864 dprint("Someone touched a flag even though it was being carried?\n");
870 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
872 if(DIFF_TEAM(toucher, self.pass_sender))
873 ctf_Handle_Return(self, toucher);
875 ctf_Handle_Retrieve(self, toucher);
883 void ctf_RespawnFlag(entity flag)
885 // check for flag respawn being called twice in a row
886 if(flag.last_respawn > time - 0.5)
887 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
889 flag.last_respawn = time;
891 // reset the player (if there is one)
892 if((flag.owner) && (flag.owner.flagcarried == flag))
894 if(flag.owner.wps_enemyflagcarrier)
895 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
897 WaypointSprite_Kill(flag.wps_flagcarrier);
899 flag.owner.flagcarried = world;
901 if(flag.speedrunning)
902 ctf_FakeTimeLimit(flag.owner, -1);
905 if((flag.owner) && (flag.owner.vehicle))
906 flag.scale = FLAG_SCALE;
908 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
909 { WaypointSprite_Kill(flag.wps_flagdropped); }
912 setattachment(flag, world, "");
913 setorigin(flag, flag.ctf_spawnorigin);
915 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
916 flag.takedamage = DAMAGE_NO;
917 flag.health = flag.max_flag_health;
918 flag.solid = SOLID_TRIGGER;
919 flag.velocity = '0 0 0';
920 flag.angles = flag.mangle;
921 flag.flags = FL_ITEM | FL_NOTARGET;
923 flag.ctf_status = FLAG_BASE;
925 flag.pass_distance = 0;
926 flag.pass_sender = world;
927 flag.pass_target = world;
928 flag.ctf_dropper = world;
929 flag.ctf_pickuptime = 0;
930 flag.ctf_droptime = 0;
936 if(IS_PLAYER(self.owner))
937 ctf_Handle_Throw(self.owner, world, DROP_RESET);
939 ctf_RespawnFlag(self);
942 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
945 waypoint_spawnforitem_force(self, self.origin);
946 self.nearestwaypointtimeout = 0; // activate waypointing again
947 self.bot_basewaypoint = self.nearestwaypoint;
950 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
951 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
953 // captureshield setup
954 ctf_CaptureShield_Spawn(self);
957 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
960 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.
961 self = flag; // for later usage with droptofloor()
964 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
965 ctf_worldflaglist = flag;
967 setattachment(flag, world, "");
969 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
970 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
971 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
972 flag.classname = "item_flag_team";
973 flag.target = "###item###"; // wut?
974 flag.flags = FL_ITEM | FL_NOTARGET;
975 flag.solid = SOLID_TRIGGER;
976 flag.takedamage = DAMAGE_NO;
977 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
978 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
979 flag.health = flag.max_flag_health;
980 flag.event_damage = ctf_FlagDamage;
981 flag.pushable = TRUE;
982 flag.teleportable = TELEPORT_NORMAL;
983 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
984 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
985 flag.velocity = '0 0 0';
986 flag.mangle = flag.angles;
987 flag.reset = ctf_Reset;
988 flag.touch = ctf_FlagTouch;
989 flag.think = ctf_FlagThink;
990 flag.nextthink = time + FLAG_THINKRATE;
991 flag.ctf_status = FLAG_BASE;
994 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
995 if(!flag.scale) { flag.scale = FLAG_SCALE; }
996 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
997 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
998 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
999 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
1002 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
1003 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
1004 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
1005 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.
1006 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1007 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1008 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1011 precache_sound(flag.snd_flag_taken);
1012 precache_sound(flag.snd_flag_returned);
1013 precache_sound(flag.snd_flag_capture);
1014 precache_sound(flag.snd_flag_respawn);
1015 precache_sound(flag.snd_flag_dropped);
1016 precache_sound(flag.snd_flag_touch);
1017 precache_sound(flag.snd_flag_pass);
1018 precache_model(flag.model);
1019 precache_model("models/ctf/shield.md3");
1020 precache_model("models/ctf/shockwavetransring.md3");
1023 setmodel(flag, flag.model); // precision set below
1024 setsize(flag, FLAG_MIN, FLAG_MAX);
1025 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1027 if(autocvar_g_ctf_flag_glowtrails)
1029 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1030 flag.glow_size = 25;
1031 flag.glow_trail = 1;
1034 flag.effects |= EF_LOWPRECISION;
1035 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1036 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1039 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1041 flag.dropped_origin = flag.origin;
1042 flag.noalign = TRUE;
1043 flag.movetype = MOVETYPE_NONE;
1045 else // drop to floor, automatically find a platform and set that as spawn origin
1047 flag.noalign = FALSE;
1050 flag.movetype = MOVETYPE_TOSS;
1053 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1061 // NOTE: LEGACY CODE, needs to be re-written!
1063 void havocbot_calculate_middlepoint()
1067 vector fo = '0 0 0';
1070 f = ctf_worldflaglist;
1075 f = f.ctf_worldflagnext;
1079 havocbot_ctf_middlepoint = s * (1.0 / n);
1080 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1084 entity havocbot_ctf_find_flag(entity bot)
1087 f = ctf_worldflaglist;
1090 if (bot.team == f.team)
1092 f = f.ctf_worldflagnext;
1097 entity havocbot_ctf_find_enemy_flag(entity bot)
1100 f = ctf_worldflaglist;
1103 if (bot.team != f.team)
1105 f = f.ctf_worldflagnext;
1110 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1118 FOR_EACH_PLAYER(head)
1120 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1123 if(vlen(head.origin - org) < tc_radius)
1130 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1133 head = ctf_worldflaglist;
1136 if (self.team == head.team)
1138 head = head.ctf_worldflagnext;
1141 navigation_routerating(head, ratingscale, 10000);
1144 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1147 head = ctf_worldflaglist;
1150 if (self.team == head.team)
1152 head = head.ctf_worldflagnext;
1157 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1160 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1163 head = ctf_worldflaglist;
1166 if (self.team != head.team)
1168 head = head.ctf_worldflagnext;
1171 navigation_routerating(head, ratingscale, 10000);
1174 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1176 if (!bot_waypoints_for_items)
1178 havocbot_goalrating_ctf_enemyflag(ratingscale);
1184 head = havocbot_ctf_find_enemy_flag(self);
1189 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1192 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1196 mf = havocbot_ctf_find_flag(self);
1198 if(mf.ctf_status == FLAG_BASE)
1202 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1205 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1208 head = ctf_worldflaglist;
1211 // flag is out in the field
1212 if(head.ctf_status != FLAG_BASE)
1213 if(head.tag_entity==world) // dropped
1217 if(vlen(org-head.origin)<df_radius)
1218 navigation_routerating(head, ratingscale, 10000);
1221 navigation_routerating(head, ratingscale, 10000);
1224 head = head.ctf_worldflagnext;
1228 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1232 head = findchainfloat(bot_pickup, TRUE);
1235 // gather health and armor only
1237 if (head.health || head.armorvalue)
1238 if (vlen(head.origin - org) < sradius)
1240 // get the value of the item
1241 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1243 navigation_routerating(head, t * ratingscale, 500);
1249 void havocbot_ctf_reset_role(entity bot)
1251 float cdefense, cmiddle, coffense;
1252 entity mf, ef, head;
1255 if(bot.deadflag != DEAD_NO)
1258 if(vlen(havocbot_ctf_middlepoint)==0)
1259 havocbot_calculate_middlepoint();
1262 if (bot.flagcarried)
1264 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1268 mf = havocbot_ctf_find_flag(bot);
1269 ef = havocbot_ctf_find_enemy_flag(bot);
1271 // Retrieve stolen flag
1272 if(mf.ctf_status!=FLAG_BASE)
1274 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1278 // If enemy flag is taken go to the middle to intercept pursuers
1279 if(ef.ctf_status!=FLAG_BASE)
1281 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1285 // if there is only me on the team switch to offense
1287 FOR_EACH_PLAYER(head)
1288 if(head.team==bot.team)
1293 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1297 // Evaluate best position to take
1298 // Count mates on middle position
1299 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1301 // Count mates on defense position
1302 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1304 // Count mates on offense position
1305 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1307 if(cdefense<=coffense)
1308 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1309 else if(coffense<=cmiddle)
1310 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1312 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1315 void havocbot_role_ctf_carrier()
1317 if(self.deadflag != DEAD_NO)
1319 havocbot_ctf_reset_role(self);
1323 if (self.flagcarried == world)
1325 havocbot_ctf_reset_role(self);
1329 if (self.bot_strategytime < time)
1331 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1333 navigation_goalrating_start();
1334 havocbot_goalrating_ctf_ourbase(50000);
1337 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1339 navigation_goalrating_end();
1341 if (self.navigation_hasgoals)
1342 self.havocbot_cantfindflag = time + 10;
1343 else if (time > self.havocbot_cantfindflag)
1345 // Can't navigate to my own base, suicide!
1346 // TODO: drop it and wander around
1347 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1353 void havocbot_role_ctf_escort()
1357 if(self.deadflag != DEAD_NO)
1359 havocbot_ctf_reset_role(self);
1363 if (self.flagcarried)
1365 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1369 // If enemy flag is back on the base switch to previous role
1370 ef = havocbot_ctf_find_enemy_flag(self);
1371 if(ef.ctf_status==FLAG_BASE)
1373 self.havocbot_role = self.havocbot_previous_role;
1374 self.havocbot_role_timeout = 0;
1378 // If the flag carrier reached the base switch to defense
1379 mf = havocbot_ctf_find_flag(self);
1380 if(mf.ctf_status!=FLAG_BASE)
1381 if(vlen(ef.origin - mf.dropped_origin) < 300)
1383 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1387 // Set the role timeout if necessary
1388 if (!self.havocbot_role_timeout)
1390 self.havocbot_role_timeout = time + random() * 30 + 60;
1393 // If nothing happened just switch to previous role
1394 if (time > self.havocbot_role_timeout)
1396 self.havocbot_role = self.havocbot_previous_role;
1397 self.havocbot_role_timeout = 0;
1401 // Chase the flag carrier
1402 if (self.bot_strategytime < time)
1404 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1405 navigation_goalrating_start();
1406 havocbot_goalrating_ctf_enemyflag(30000);
1407 havocbot_goalrating_ctf_ourstolenflag(40000);
1408 havocbot_goalrating_items(10000, self.origin, 10000);
1409 navigation_goalrating_end();
1413 void havocbot_role_ctf_offense()
1418 if(self.deadflag != DEAD_NO)
1420 havocbot_ctf_reset_role(self);
1424 if (self.flagcarried)
1426 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1431 mf = havocbot_ctf_find_flag(self);
1432 ef = havocbot_ctf_find_enemy_flag(self);
1435 if(mf.ctf_status!=FLAG_BASE)
1438 pos = mf.tag_entity.origin;
1442 // Try to get it if closer than the enemy base
1443 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1445 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1450 // Escort flag carrier
1451 if(ef.ctf_status!=FLAG_BASE)
1454 pos = ef.tag_entity.origin;
1458 if(vlen(pos-mf.dropped_origin)>700)
1460 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1465 // About to fail, switch to middlefield
1468 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1472 // Set the role timeout if necessary
1473 if (!self.havocbot_role_timeout)
1474 self.havocbot_role_timeout = time + 120;
1476 if (time > self.havocbot_role_timeout)
1478 havocbot_ctf_reset_role(self);
1482 if (self.bot_strategytime < time)
1484 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1485 navigation_goalrating_start();
1486 havocbot_goalrating_ctf_ourstolenflag(50000);
1487 havocbot_goalrating_ctf_enemybase(20000);
1488 havocbot_goalrating_items(5000, self.origin, 1000);
1489 havocbot_goalrating_items(1000, self.origin, 10000);
1490 navigation_goalrating_end();
1494 // Retriever (temporary role):
1495 void havocbot_role_ctf_retriever()
1499 if(self.deadflag != DEAD_NO)
1501 havocbot_ctf_reset_role(self);
1505 if (self.flagcarried)
1507 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1511 // If flag is back on the base switch to previous role
1512 mf = havocbot_ctf_find_flag(self);
1513 if(mf.ctf_status==FLAG_BASE)
1515 havocbot_ctf_reset_role(self);
1519 if (!self.havocbot_role_timeout)
1520 self.havocbot_role_timeout = time + 20;
1522 if (time > self.havocbot_role_timeout)
1524 havocbot_ctf_reset_role(self);
1528 if (self.bot_strategytime < time)
1533 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1534 navigation_goalrating_start();
1535 havocbot_goalrating_ctf_ourstolenflag(50000);
1536 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1537 havocbot_goalrating_ctf_enemybase(30000);
1538 havocbot_goalrating_items(500, self.origin, rt_radius);
1539 navigation_goalrating_end();
1543 void havocbot_role_ctf_middle()
1547 if(self.deadflag != DEAD_NO)
1549 havocbot_ctf_reset_role(self);
1553 if (self.flagcarried)
1555 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1559 mf = havocbot_ctf_find_flag(self);
1560 if(mf.ctf_status!=FLAG_BASE)
1562 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1566 if (!self.havocbot_role_timeout)
1567 self.havocbot_role_timeout = time + 10;
1569 if (time > self.havocbot_role_timeout)
1571 havocbot_ctf_reset_role(self);
1575 if (self.bot_strategytime < time)
1579 org = havocbot_ctf_middlepoint;
1580 org_z = self.origin_z;
1582 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1583 navigation_goalrating_start();
1584 havocbot_goalrating_ctf_ourstolenflag(50000);
1585 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1586 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1587 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1588 havocbot_goalrating_items(2500, self.origin, 10000);
1589 havocbot_goalrating_ctf_enemybase(2500);
1590 navigation_goalrating_end();
1594 void havocbot_role_ctf_defense()
1598 if(self.deadflag != DEAD_NO)
1600 havocbot_ctf_reset_role(self);
1604 if (self.flagcarried)
1606 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1610 // If own flag was captured
1611 mf = havocbot_ctf_find_flag(self);
1612 if(mf.ctf_status!=FLAG_BASE)
1614 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1618 if (!self.havocbot_role_timeout)
1619 self.havocbot_role_timeout = time + 30;
1621 if (time > self.havocbot_role_timeout)
1623 havocbot_ctf_reset_role(self);
1626 if (self.bot_strategytime < time)
1631 org = mf.dropped_origin;
1632 mp_radius = havocbot_ctf_middlepoint_radius;
1634 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1635 navigation_goalrating_start();
1637 // if enemies are closer to our base, go there
1638 entity head, closestplayer = world;
1639 float distance, bestdistance = 10000;
1640 FOR_EACH_PLAYER(head)
1642 if(head.deadflag!=DEAD_NO)
1645 distance = vlen(org - head.origin);
1646 if(distance<bestdistance)
1648 closestplayer = head;
1649 bestdistance = distance;
1654 if(closestplayer.team!=self.team)
1655 if(vlen(org - self.origin)>1000)
1656 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1657 havocbot_goalrating_ctf_ourbase(30000);
1659 havocbot_goalrating_ctf_ourstolenflag(20000);
1660 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1661 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1662 havocbot_goalrating_items(10000, org, mp_radius);
1663 havocbot_goalrating_items(5000, self.origin, 10000);
1664 navigation_goalrating_end();
1668 void havocbot_role_ctf_setrole(entity bot, float role)
1670 dprint(strcat(bot.netname," switched to "));
1673 case HAVOCBOT_CTF_ROLE_CARRIER:
1675 bot.havocbot_role = havocbot_role_ctf_carrier;
1676 bot.havocbot_role_timeout = 0;
1677 bot.havocbot_cantfindflag = time + 10;
1678 bot.bot_strategytime = 0;
1680 case HAVOCBOT_CTF_ROLE_DEFENSE:
1682 bot.havocbot_role = havocbot_role_ctf_defense;
1683 bot.havocbot_role_timeout = 0;
1685 case HAVOCBOT_CTF_ROLE_MIDDLE:
1687 bot.havocbot_role = havocbot_role_ctf_middle;
1688 bot.havocbot_role_timeout = 0;
1690 case HAVOCBOT_CTF_ROLE_OFFENSE:
1692 bot.havocbot_role = havocbot_role_ctf_offense;
1693 bot.havocbot_role_timeout = 0;
1695 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1696 dprint("retriever");
1697 bot.havocbot_previous_role = bot.havocbot_role;
1698 bot.havocbot_role = havocbot_role_ctf_retriever;
1699 bot.havocbot_role_timeout = time + 10;
1700 bot.bot_strategytime = 0;
1702 case HAVOCBOT_CTF_ROLE_ESCORT:
1704 bot.havocbot_previous_role = bot.havocbot_role;
1705 bot.havocbot_role = havocbot_role_ctf_escort;
1706 bot.havocbot_role_timeout = time + 30;
1707 bot.bot_strategytime = 0;
1718 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1722 // initially clear items so they can be set as necessary later.
1723 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1724 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1726 // scan through all the flags and notify the client about them
1727 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1729 switch(flag.ctf_status)
1734 if((flag.owner == self) || (flag.pass_sender == self))
1735 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1737 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1742 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1748 // item for stopping players from capturing the flag too often
1749 if(self.ctf_captureshielded)
1750 self.items |= IT_CTF_SHIELDED;
1752 // update the health of the flag carrier waypointsprite
1753 if(self.wps_flagcarrier)
1754 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1759 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1761 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1763 if(frag_target == frag_attacker) // damage done to yourself
1765 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1766 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1768 else // damage done to everyone else
1770 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1771 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1774 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1776 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)))
1777 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1779 frag_target.wps_helpme_time = time;
1780 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1782 // todo: add notification for when flag carrier needs help?
1787 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1789 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1791 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1792 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1795 if(frag_target.flagcarried)
1796 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1801 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1804 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1807 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1809 entity flag; // temporary entity for the search method
1811 if(self.flagcarried)
1812 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1814 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1816 if(flag.pass_sender == self) { flag.pass_sender = world; }
1817 if(flag.pass_target == self) { flag.pass_target = world; }
1818 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1824 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1826 if(self.flagcarried)
1827 if(!autocvar_g_ctf_portalteleport)
1828 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1833 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1835 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1837 entity player = self;
1839 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1841 // pass the flag to a team mate
1842 if(autocvar_g_ctf_pass)
1844 entity head, closest_target = world;
1845 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1847 while(head) // find the closest acceptable target to pass to
1849 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1850 if(head != player && SAME_TEAM(head, player))
1851 if(!head.speedrunning && !head.vehicle)
1853 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1854 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1855 vector passer_center = CENTER_OR_VIEWOFS(player);
1857 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1859 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1861 if(IS_BOT_CLIENT(head))
1863 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1864 ctf_Handle_Throw(head, player, DROP_PASS);
1868 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1869 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1871 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1874 else if(player.flagcarried)
1878 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1879 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1880 { closest_target = head; }
1882 else { closest_target = head; }
1889 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1892 // throw the flag in front of you
1893 if(autocvar_g_ctf_throw && player.flagcarried)
1895 if(player.throw_count == -1)
1897 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1899 player.throw_prevtime = time;
1900 player.throw_count = 1;
1901 ctf_Handle_Throw(player, world, DROP_THROW);
1906 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1912 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1913 else { player.throw_count += 1; }
1914 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1916 player.throw_prevtime = time;
1917 ctf_Handle_Throw(player, world, DROP_THROW);
1926 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1928 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1930 self.wps_helpme_time = time;
1931 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1933 else // create a normal help me waypointsprite
1935 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');
1936 WaypointSprite_Ping(self.wps_helpme);
1942 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1944 if(vh_player.flagcarried)
1946 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1948 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1952 setattachment(vh_player.flagcarried, vh_vehicle, "");
1953 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1954 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1955 //vh_player.flagcarried.angles = '0 0 0';
1963 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1965 if(vh_player.flagcarried)
1967 setattachment(vh_player.flagcarried, vh_player, "");
1968 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1969 vh_player.flagcarried.scale = FLAG_SCALE;
1970 vh_player.flagcarried.angles = '0 0 0';
1977 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1979 if(self.flagcarried)
1981 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1982 ctf_RespawnFlag(self.flagcarried);
1989 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1991 entity flag; // temporary entity for the search method
1993 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1995 switch(flag.ctf_status)
2000 // lock the flag, game is over
2001 flag.movetype = MOVETYPE_NONE;
2002 flag.takedamage = DAMAGE_NO;
2003 flag.solid = SOLID_NOT;
2004 flag.nextthink = FALSE; // stop thinking
2006 //dprint("stopping the ", flag.netname, " from moving.\n");
2014 // do nothing for these flags
2023 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2025 havocbot_ctf_reset_role(self);
2034 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2035 CTF Starting point for a player in team one (Red).
2036 Keys: "angle" viewing angle when spawning. */
2037 void spawnfunc_info_player_team1()
2039 if(g_assault) { remove(self); return; }
2041 self.team = NUM_TEAM_1; // red
2042 spawnfunc_info_player_deathmatch();
2046 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2047 CTF Starting point for a player in team two (Blue).
2048 Keys: "angle" viewing angle when spawning. */
2049 void spawnfunc_info_player_team2()
2051 if(g_assault) { remove(self); return; }
2053 self.team = NUM_TEAM_2; // blue
2054 spawnfunc_info_player_deathmatch();
2057 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2058 CTF Starting point for a player in team three (Yellow).
2059 Keys: "angle" viewing angle when spawning. */
2060 void spawnfunc_info_player_team3()
2062 if(g_assault) { remove(self); return; }
2064 self.team = NUM_TEAM_3; // yellow
2065 spawnfunc_info_player_deathmatch();
2069 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2070 CTF Starting point for a player in team four (Purple).
2071 Keys: "angle" viewing angle when spawning. */
2072 void spawnfunc_info_player_team4()
2074 if(g_assault) { remove(self); return; }
2076 self.team = NUM_TEAM_4; // purple
2077 spawnfunc_info_player_deathmatch();
2080 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2081 CTF flag for team one (Red).
2083 "angle" Angle the flag will point (minus 90 degrees)...
2084 "model" model to use, note this needs red and blue as skins 0 and 1...
2085 "noise" sound played when flag is picked up...
2086 "noise1" sound played when flag is returned by a teammate...
2087 "noise2" sound played when flag is captured...
2088 "noise3" sound played when flag is lost in the field and respawns itself...
2089 "noise4" sound played when flag is dropped by a player...
2090 "noise5" sound played when flag touches the ground... */
2091 void spawnfunc_item_flag_team1()
2093 if(!g_ctf) { remove(self); return; }
2095 ctf_FlagSetup(1, self); // 1 = red
2098 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2099 CTF flag for team two (Blue).
2101 "angle" Angle the flag will point (minus 90 degrees)...
2102 "model" model to use, note this needs red and blue as skins 0 and 1...
2103 "noise" sound played when flag is picked up...
2104 "noise1" sound played when flag is returned by a teammate...
2105 "noise2" sound played when flag is captured...
2106 "noise3" sound played when flag is lost in the field and respawns itself...
2107 "noise4" sound played when flag is dropped by a player...
2108 "noise5" sound played when flag touches the ground... */
2109 void spawnfunc_item_flag_team2()
2111 if(!g_ctf) { remove(self); return; }
2113 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2116 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2117 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2118 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.
2120 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2121 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2122 void spawnfunc_ctf_team()
2124 if(!g_ctf) { remove(self); return; }
2126 self.classname = "ctf_team";
2127 self.team = self.cnt + 1;
2130 // compatibility for quake maps
2131 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2132 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2133 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2134 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2135 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2136 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2144 void ctf_ScoreRules()
2146 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2147 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2148 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2149 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2150 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2151 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2152 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2153 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2154 ScoreRules_basics_end();
2157 // code from here on is just to support maps that don't have flag and team entities
2158 void ctf_SpawnTeam (string teamname, float teamcolor)
2163 self.classname = "ctf_team";
2164 self.netname = teamname;
2165 self.cnt = teamcolor;
2167 spawnfunc_ctf_team();
2172 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2174 // if no teams are found, spawn defaults
2175 if(find(world, classname, "ctf_team") == world)
2177 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2178 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2179 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2185 void ctf_Initialize()
2187 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2189 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2190 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2191 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2193 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2197 MUTATOR_DEFINITION(gamemode_ctf)
2199 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2200 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2201 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2202 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2203 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2204 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2205 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2206 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2207 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2208 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2209 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2210 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2211 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2212 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2216 if(time > 1) // game loads at time 1
2217 error("This is a game type and it cannot be added at runtime.");
2221 MUTATOR_ONROLLBACK_OR_REMOVE
2223 // we actually cannot roll back ctf_Initialize here
2224 // BUT: we don't need to! If this gets called, adding always
2230 print("This is a game type and it cannot be removed at runtime.");