1 #include "gamemode_ctf.qh"
6 REGISTER_MUTATOR(ctf, false)
10 if (time > 1) // game loads at time 1
11 error("This is a game type and it cannot be added at runtime.");
15 SetLimits(autocvar_capturelimit_override, autocvar_captureleadlimit_override, autocvar_timelimit_override, -1);
16 have_team_spawns = -1; // request team spawns
19 MUTATOR_ONROLLBACK_OR_REMOVE
21 // we actually cannot roll back ctf_Initialize here
22 // BUT: we don't need to! If this gets called, adding always
28 LOG_INFO("This is a game type and it cannot be removed at runtime.");
37 #include <common/vehicles/all.qh>
38 #include <server/teamplay.qh>
41 #include <lib/warpzone/common.qh>
43 bool autocvar_g_ctf_allow_vehicle_carry;
44 bool autocvar_g_ctf_allow_vehicle_touch;
45 bool autocvar_g_ctf_allow_monster_touch;
46 bool autocvar_g_ctf_throw;
47 float autocvar_g_ctf_throw_angle_max;
48 float autocvar_g_ctf_throw_angle_min;
49 int autocvar_g_ctf_throw_punish_count;
50 float autocvar_g_ctf_throw_punish_delay;
51 float autocvar_g_ctf_throw_punish_time;
52 float autocvar_g_ctf_throw_strengthmultiplier;
53 float autocvar_g_ctf_throw_velocity_forward;
54 float autocvar_g_ctf_throw_velocity_up;
55 float autocvar_g_ctf_drop_velocity_up;
56 float autocvar_g_ctf_drop_velocity_side;
57 bool autocvar_g_ctf_oneflag_reverse;
58 bool autocvar_g_ctf_portalteleport;
59 bool autocvar_g_ctf_pass;
60 float autocvar_g_ctf_pass_arc;
61 float autocvar_g_ctf_pass_arc_max;
62 float autocvar_g_ctf_pass_directional_max;
63 float autocvar_g_ctf_pass_directional_min;
64 float autocvar_g_ctf_pass_radius;
65 float autocvar_g_ctf_pass_wait;
66 bool autocvar_g_ctf_pass_request;
67 float autocvar_g_ctf_pass_turnrate;
68 float autocvar_g_ctf_pass_timelimit;
69 float autocvar_g_ctf_pass_velocity;
70 bool autocvar_g_ctf_dynamiclights;
71 float autocvar_g_ctf_flag_collect_delay;
72 float autocvar_g_ctf_flag_damageforcescale;
73 bool autocvar_g_ctf_flag_dropped_waypoint;
74 bool autocvar_g_ctf_flag_dropped_floatinwater;
75 bool autocvar_g_ctf_flag_glowtrails;
76 int autocvar_g_ctf_flag_health;
77 bool autocvar_g_ctf_flag_return;
78 bool autocvar_g_ctf_flag_return_carrying;
79 float autocvar_g_ctf_flag_return_carried_radius;
80 float autocvar_g_ctf_flag_return_time;
81 bool autocvar_g_ctf_flag_return_when_unreachable;
82 float autocvar_g_ctf_flag_return_damage;
83 float autocvar_g_ctf_flag_return_damage_delay;
84 float autocvar_g_ctf_flag_return_dropped;
85 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
86 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
87 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
88 float autocvar_g_ctf_flagcarrier_selfforcefactor;
89 float autocvar_g_ctf_flagcarrier_damagefactor;
90 float autocvar_g_ctf_flagcarrier_forcefactor;
91 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
92 bool autocvar_g_ctf_fullbrightflags;
93 bool autocvar_g_ctf_ignore_frags;
94 int autocvar_g_ctf_score_capture;
95 int autocvar_g_ctf_score_capture_assist;
96 int autocvar_g_ctf_score_kill;
97 int autocvar_g_ctf_score_penalty_drop;
98 int autocvar_g_ctf_score_penalty_returned;
99 int autocvar_g_ctf_score_pickup_base;
100 int autocvar_g_ctf_score_pickup_dropped_early;
101 int autocvar_g_ctf_score_pickup_dropped_late;
102 int autocvar_g_ctf_score_return;
103 float autocvar_g_ctf_shield_force;
104 float autocvar_g_ctf_shield_max_ratio;
105 int autocvar_g_ctf_shield_min_negscore;
106 bool autocvar_g_ctf_stalemate;
107 int autocvar_g_ctf_stalemate_endcondition;
108 float autocvar_g_ctf_stalemate_time;
109 bool autocvar_g_ctf_reverse;
110 float autocvar_g_ctf_dropped_capture_delay;
111 float autocvar_g_ctf_dropped_capture_radius;
113 void ctf_FakeTimeLimit(entity e, float t)
116 WriteByte(MSG_ONE, 3); // svc_updatestat
117 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
119 WriteCoord(MSG_ONE, autocvar_timelimit);
121 WriteCoord(MSG_ONE, (t + 1) / 60);
124 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
126 if(autocvar_sv_eventlog)
127 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
128 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
131 void ctf_CaptureRecord(entity flag, entity player)
133 float cap_record = ctf_captimerecord;
134 float cap_time = (time - flag.ctf_pickuptime);
135 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
139 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
140 else if(!ctf_captimerecord)
141 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, (cap_time * 100));
142 else if(cap_time < cap_record)
143 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100));
145 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100));
147 // write that shit in the database
148 if(!ctf_oneflag) // but not in 1-flag mode
149 if((!ctf_captimerecord) || (cap_time < cap_record))
151 ctf_captimerecord = cap_time;
152 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
153 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
154 write_recordmarker(player, (time - cap_time), cap_time);
158 bool ctf_Return_Customize(entity this, entity client)
160 // only to the carrier
161 return boolean(client == this.owner);
164 void ctf_FlagcarrierWaypoints(entity player)
166 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
167 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
168 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
169 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
171 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
173 if(!player.wps_enemyflagcarrier)
175 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
176 wp.colormod = WPCOLOR_ENEMYFC(player.team);
177 setcefc(wp, ctf_Stalemate_Customize);
179 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
180 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
183 if(!player.wps_flagreturn)
185 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
186 owp.colormod = '0 0.8 0.8';
187 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
188 setcefc(owp, ctf_Return_Customize);
193 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
195 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
196 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
197 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
198 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
201 if(current_height) // make sure we can actually do this arcing path
203 targpos = (to + ('0 0 1' * current_height));
204 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
205 if(trace_fraction < 1)
207 //print("normal arc line failed, trying to find new pos...");
208 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
209 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
210 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
211 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
212 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
215 else { targpos = to; }
217 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
219 vector desired_direction = normalize(targpos - from);
220 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
221 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
224 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
226 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
228 // directional tracing only
230 makevectors(passer_angle);
232 // find the closest point on the enemy to the center of the attack
233 float h; // hypotenuse, which is the distance between attacker to head
234 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
236 h = vlen(head_center - passer_center);
237 a = h * (normalize(head_center - passer_center) * v_forward);
239 vector nearest_on_line = (passer_center + a * v_forward);
240 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
242 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
243 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
245 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
250 else { return true; }
254 // =======================
255 // CaptureShield Functions
256 // =======================
258 bool ctf_CaptureShield_CheckStatus(entity p)
260 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
261 int players_worseeq, players_total;
263 if(ctf_captureshield_max_ratio <= 0)
266 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
267 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
268 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
269 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
271 sr = ((s - s2) + (s3 + s4));
273 if(sr >= -ctf_captureshield_min_negscore)
276 players_total = players_worseeq = 0;
277 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
280 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
281 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
282 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
283 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
285 ser = ((se - se2) + (se3 + se4));
292 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
293 // use this rule here
295 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
301 void ctf_CaptureShield_Update(entity player, bool wanted_status)
303 bool updated_status = ctf_CaptureShield_CheckStatus(player);
304 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
306 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
307 player.ctf_captureshielded = updated_status;
311 bool ctf_CaptureShield_Customize(entity this, entity client)
313 if(!client.ctf_captureshielded) { return false; }
314 if(CTF_SAMETEAM(this, client)) { return false; }
319 void ctf_CaptureShield_Touch(entity this, entity toucher)
321 if(!toucher.ctf_captureshielded) { return; }
322 if(CTF_SAMETEAM(this, toucher)) { return; }
324 vector mymid = (this.absmin + this.absmax) * 0.5;
325 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
327 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
328 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
331 void ctf_CaptureShield_Spawn(entity flag)
333 entity shield = new(ctf_captureshield);
336 shield.team = flag.team;
337 settouch(shield, ctf_CaptureShield_Touch);
338 setcefc(shield, ctf_CaptureShield_Customize);
339 shield.effects = EF_ADDITIVE;
340 set_movetype(shield, MOVETYPE_NOCLIP);
341 shield.solid = SOLID_TRIGGER;
342 shield.avelocity = '7 0 11';
345 setorigin(shield, flag.origin);
346 setmodel(shield, MDL_CTF_SHIELD);
347 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
351 // ====================
352 // Drop/Pass/Throw Code
353 // ====================
355 void ctf_Handle_Drop(entity flag, entity player, int droptype)
358 player = (player ? player : flag.pass_sender);
361 set_movetype(flag, MOVETYPE_TOSS);
362 flag.takedamage = DAMAGE_YES;
363 flag.angles = '0 0 0';
364 flag.health = flag.max_flag_health;
365 flag.ctf_droptime = time;
366 flag.ctf_dropper = player;
367 flag.ctf_status = FLAG_DROPPED;
369 // messages and sounds
370 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
371 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
372 ctf_EventLog("dropped", player.team, player);
375 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
376 PlayerScore_Add(player, SP_CTF_DROPS, 1);
379 if(autocvar_g_ctf_flag_dropped_waypoint) {
380 entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
381 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
384 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
386 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
387 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
390 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
392 if(droptype == DROP_PASS)
394 flag.pass_distance = 0;
395 flag.pass_sender = NULL;
396 flag.pass_target = NULL;
400 void ctf_Handle_Retrieve(entity flag, entity player)
402 entity sender = flag.pass_sender;
404 // transfer flag to player
406 flag.owner.flagcarried = flag;
411 setattachment(flag, player.vehicle, "");
412 setorigin(flag, VEHICLE_FLAG_OFFSET);
413 flag.scale = VEHICLE_FLAG_SCALE;
417 setattachment(flag, player, "");
418 setorigin(flag, FLAG_CARRY_OFFSET);
420 set_movetype(flag, MOVETYPE_NONE);
421 flag.takedamage = DAMAGE_NO;
422 flag.solid = SOLID_NOT;
423 flag.angles = '0 0 0';
424 flag.ctf_status = FLAG_CARRY;
426 // messages and sounds
427 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
428 ctf_EventLog("receive", flag.team, player);
430 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
432 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
433 else if(it == player)
434 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
435 else if(SAME_TEAM(it, sender))
436 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
439 // create new waypoint
440 ctf_FlagcarrierWaypoints(player);
442 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
443 player.throw_antispam = sender.throw_antispam;
445 flag.pass_distance = 0;
446 flag.pass_sender = NULL;
447 flag.pass_target = NULL;
450 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
452 entity flag = player.flagcarried;
453 vector targ_origin, flag_velocity;
455 if(!flag) { return; }
456 if((droptype == DROP_PASS) && !receiver) { return; }
458 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
461 setattachment(flag, NULL, "");
462 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
463 flag.owner.flagcarried = NULL;
465 flag.solid = SOLID_TRIGGER;
466 flag.ctf_dropper = player;
467 flag.ctf_droptime = time;
469 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
476 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
477 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
478 WarpZone_RefSys_Copy(flag, receiver);
479 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
480 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
482 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
483 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
486 set_movetype(flag, MOVETYPE_FLY);
487 flag.takedamage = DAMAGE_NO;
488 flag.pass_sender = player;
489 flag.pass_target = receiver;
490 flag.ctf_status = FLAG_PASSING;
493 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
494 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
495 ctf_EventLog("pass", flag.team, player);
501 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'));
503 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
504 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
505 ctf_Handle_Drop(flag, player, droptype);
511 flag.velocity = '0 0 0'; // do nothing
518 flag.velocity = W_CalculateProjectileVelocity(player, 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);
519 ctf_Handle_Drop(flag, player, droptype);
524 // kill old waypointsprite
525 WaypointSprite_Ping(player.wps_flagcarrier);
526 WaypointSprite_Kill(player.wps_flagcarrier);
528 if(player.wps_enemyflagcarrier)
529 WaypointSprite_Kill(player.wps_enemyflagcarrier);
531 if(player.wps_flagreturn)
532 WaypointSprite_Kill(player.wps_flagreturn);
535 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
538 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
540 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
547 void nades_GiveBonus(entity player, float score);
549 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
551 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
552 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
553 entity player_team_flag = NULL, tmp_entity;
554 float old_time, new_time;
556 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
557 if(CTF_DIFFTEAM(player, flag)) { return; }
560 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
561 if(SAME_TEAM(tmp_entity, player))
563 player_team_flag = tmp_entity;
567 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
569 player.throw_prevtime = time;
570 player.throw_count = 0;
572 // messages and sounds
573 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
574 ctf_CaptureRecord(enemy_flag, player);
575 _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
579 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
580 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
585 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
586 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
588 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
589 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
590 if(!old_time || new_time < old_time)
591 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
594 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
595 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
598 if(capturetype == CAPTURE_NORMAL)
600 WaypointSprite_Kill(player.wps_flagcarrier);
601 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
603 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
604 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
608 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
609 ctf_RespawnFlag(enemy_flag);
612 void ctf_Handle_Return(entity flag, entity player)
614 // messages and sounds
615 if(IS_MONSTER(player))
617 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
621 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
622 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
624 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
625 ctf_EventLog("return", flag.team, player);
628 if(IS_PLAYER(player))
630 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
631 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
633 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
636 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
640 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
641 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
642 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
646 if(player.flagcarried == flag)
647 WaypointSprite_Kill(player.wps_flagcarrier);
650 ctf_RespawnFlag(flag);
653 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
656 float pickup_dropped_score; // used to calculate dropped pickup score
658 // attach the flag to the player
660 player.flagcarried = flag;
663 setattachment(flag, player.vehicle, "");
664 setorigin(flag, VEHICLE_FLAG_OFFSET);
665 flag.scale = VEHICLE_FLAG_SCALE;
669 setattachment(flag, player, "");
670 setorigin(flag, FLAG_CARRY_OFFSET);
674 set_movetype(flag, MOVETYPE_NONE);
675 flag.takedamage = DAMAGE_NO;
676 flag.solid = SOLID_NOT;
677 flag.angles = '0 0 0';
678 flag.ctf_status = FLAG_CARRY;
682 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
683 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
687 // messages and sounds
688 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
690 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
692 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
693 else if(CTF_DIFFTEAM(player, flag))
694 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
696 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team));
698 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
701 FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), LAMBDA(Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname)));
704 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
705 if(CTF_SAMETEAM(flag, it))
706 if(SAME_TEAM(player, it))
707 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
709 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
712 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
715 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
716 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
721 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
722 ctf_EventLog("steal", flag.team, player);
728 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);
729 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);
730 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
731 PlayerTeamScore_AddScore(player, pickup_dropped_score);
732 ctf_EventLog("pickup", flag.team, player);
740 if(pickuptype == PICKUP_BASE)
742 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
743 if((player.speedrunning) && (ctf_captimerecord))
744 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
748 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
751 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
752 ctf_FlagcarrierWaypoints(player);
753 WaypointSprite_Ping(player.wps_flagcarrier);
757 // ===================
758 // Main Flag Functions
759 // ===================
761 void ctf_CheckFlagReturn(entity flag, int returntype)
763 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
765 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
767 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
772 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
774 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
775 case RETURN_SPEEDRUN:
776 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), ctf_captimerecord); break;
777 case RETURN_NEEDKILL:
778 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
781 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
783 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
784 ctf_EventLog("returned", flag.team, NULL);
785 ctf_RespawnFlag(flag);
790 bool ctf_Stalemate_Customize(entity this, entity client)
792 // make spectators see what the player would see
793 entity e = WaypointSprite_getviewentity(client);
794 entity wp_owner = this.owner;
797 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
798 if(SAME_TEAM(wp_owner, e)) { return false; }
799 if(!IS_PLAYER(e)) { return false; }
804 void ctf_CheckStalemate()
807 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
810 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
812 // build list of stale flags
813 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
815 if(autocvar_g_ctf_stalemate)
816 if(tmp_entity.ctf_status != FLAG_BASE)
817 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
819 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
820 ctf_staleflaglist = tmp_entity;
822 switch(tmp_entity.team)
824 case NUM_TEAM_1: ++stale_red_flags; break;
825 case NUM_TEAM_2: ++stale_blue_flags; break;
826 case NUM_TEAM_3: ++stale_yellow_flags; break;
827 case NUM_TEAM_4: ++stale_pink_flags; break;
828 default: ++stale_neutral_flags; break;
834 stale_flags = (stale_neutral_flags >= 1);
836 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
838 if(ctf_oneflag && stale_flags == 1)
839 ctf_stalemate = true;
840 else if(stale_flags >= 2)
841 ctf_stalemate = true;
842 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
843 { ctf_stalemate = false; wpforenemy_announced = false; }
844 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
845 { ctf_stalemate = false; wpforenemy_announced = false; }
847 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
850 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
852 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
854 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
855 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
856 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
860 if (!wpforenemy_announced)
862 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER))));
864 wpforenemy_announced = true;
869 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
871 if(ITEM_DAMAGE_NEEDKILL(deathtype))
873 if(autocvar_g_ctf_flag_return_damage_delay)
875 this.ctf_flagdamaged = true;
880 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
884 if(autocvar_g_ctf_flag_return_damage)
886 // reduce health and check if it should be returned
887 this.health = this.health - damage;
888 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
893 void ctf_FlagThink(entity this)
898 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
901 if(this == ctf_worldflaglist) // only for the first flag
902 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
905 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
906 LOG_TRACE("wtf the flag got squashed?");
907 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
908 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
909 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs); }
911 switch(this.ctf_status) // reset flag angles in case warpzones adjust it
915 this.angles = '0 0 0';
923 switch(this.ctf_status)
927 if(autocvar_g_ctf_dropped_capture_radius)
929 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
930 if(tmp_entity.ctf_status == FLAG_DROPPED)
931 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
932 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
933 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
940 if(autocvar_g_ctf_flag_dropped_floatinwater)
942 vector midpoint = ((this.absmin + this.absmax) * 0.5);
943 if(pointcontents(midpoint) == CONTENT_WATER)
945 this.velocity = this.velocity * 0.5;
947 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
948 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
950 { set_movetype(this, MOVETYPE_FLY); }
952 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
954 if(autocvar_g_ctf_flag_return_dropped)
956 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
959 ctf_CheckFlagReturn(this, RETURN_DROPPED);
963 if(this.ctf_flagdamaged)
965 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
966 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
969 else if(autocvar_g_ctf_flag_return_time)
971 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
972 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
980 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
983 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
985 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
986 ImpulseCommands(this.owner);
988 if(autocvar_g_ctf_stalemate)
990 if(time >= wpforenemy_nextthink)
992 ctf_CheckStalemate();
993 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
996 if(CTF_SAMETEAM(this, this.owner) && this.team)
998 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
999 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1000 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1001 ctf_Handle_Return(this, this.owner);
1008 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1009 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1010 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1012 if((this.pass_target == NULL)
1013 || (IS_DEAD(this.pass_target))
1014 || (this.pass_target.flagcarried)
1015 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1016 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1017 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1019 // give up, pass failed
1020 ctf_Handle_Drop(this, NULL, DROP_PASS);
1024 // still a viable target, go for it
1025 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1030 default: // this should never happen
1032 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1038 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1041 if(gameover) { return; }
1042 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1044 bool is_not_monster = (!IS_MONSTER(toucher));
1046 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1047 if(ITEM_TOUCH_NEEDKILL())
1049 if(!autocvar_g_ctf_flag_return_damage_delay)
1052 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1054 if(!flag.ctf_flagdamaged) { return; }
1057 int num_perteam = 0;
1058 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), LAMBDA(++num_perteam));
1060 // special touch behaviors
1061 if(STAT(FROZEN, toucher)) { return; }
1062 else if(IS_VEHICLE(toucher))
1064 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1065 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1067 return; // do nothing
1069 else if(IS_MONSTER(toucher))
1071 if(!autocvar_g_ctf_allow_monster_touch)
1072 return; // do nothing
1074 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1076 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1078 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1079 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1080 flag.wait = time + FLAG_TOUCHRATE;
1084 else if(IS_DEAD(toucher)) { return; }
1086 switch(flag.ctf_status)
1092 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1093 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1094 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1095 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1097 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1098 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1099 else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
1101 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1102 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1104 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1105 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1111 if(CTF_SAMETEAM(toucher, flag) && (autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried)) && flag.team) // automatically return if there's only 1 player on the team
1112 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1113 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1114 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1120 LOG_TRACE("Someone touched a flag even though it was being carried?");
1126 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1128 if(DIFF_TEAM(toucher, flag.pass_sender))
1129 ctf_Handle_Return(flag, toucher);
1131 ctf_Handle_Retrieve(flag, toucher);
1138 .float last_respawn;
1139 void ctf_RespawnFlag(entity flag)
1141 // check for flag respawn being called twice in a row
1142 if(flag.last_respawn > time - 0.5)
1143 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1145 flag.last_respawn = time;
1147 // reset the player (if there is one)
1148 if((flag.owner) && (flag.owner.flagcarried == flag))
1150 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1151 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1152 WaypointSprite_Kill(flag.wps_flagcarrier);
1154 flag.owner.flagcarried = NULL;
1156 if(flag.speedrunning)
1157 ctf_FakeTimeLimit(flag.owner, -1);
1160 if((flag.owner) && (flag.owner.vehicle))
1161 flag.scale = FLAG_SCALE;
1163 if(flag.ctf_status == FLAG_DROPPED)
1164 { WaypointSprite_Kill(flag.wps_flagdropped); }
1167 setattachment(flag, NULL, "");
1168 setorigin(flag, flag.ctf_spawnorigin);
1170 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1171 flag.takedamage = DAMAGE_NO;
1172 flag.health = flag.max_flag_health;
1173 flag.solid = SOLID_TRIGGER;
1174 flag.velocity = '0 0 0';
1175 flag.angles = flag.mangle;
1176 flag.flags = FL_ITEM | FL_NOTARGET;
1178 flag.ctf_status = FLAG_BASE;
1180 flag.pass_distance = 0;
1181 flag.pass_sender = NULL;
1182 flag.pass_target = NULL;
1183 flag.ctf_dropper = NULL;
1184 flag.ctf_pickuptime = 0;
1185 flag.ctf_droptime = 0;
1186 flag.ctf_flagdamaged = 0;
1188 ctf_CheckStalemate();
1191 void ctf_Reset(entity this)
1193 if(this.owner && IS_PLAYER(this.owner))
1194 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1196 ctf_RespawnFlag(this);
1199 bool ctf_FlagBase_Customize(entity this, entity client)
1201 if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
1206 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1209 waypoint_spawnforitem_force(this, this.origin);
1210 this.nearestwaypointtimeout = 0; // activate waypointing again
1211 this.bot_basewaypoint = this.nearestwaypoint;
1217 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1218 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1219 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1220 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1221 default: basename = WP_FlagBaseNeutral; break;
1224 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1225 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1226 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1227 setcefc(wp, ctf_FlagBase_Customize);
1229 // captureshield setup
1230 ctf_CaptureShield_Spawn(this);
1235 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1238 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1239 ctf_worldflaglist = flag;
1241 setattachment(flag, NULL, "");
1243 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1244 flag.team = teamnumber;
1245 flag.classname = "item_flag_team";
1246 flag.target = "###item###"; // wut?
1247 flag.flags = FL_ITEM | FL_NOTARGET;
1248 IL_PUSH(g_items, flag);
1249 flag.solid = SOLID_TRIGGER;
1250 flag.takedamage = DAMAGE_NO;
1251 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1252 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1253 flag.health = flag.max_flag_health;
1254 flag.event_damage = ctf_FlagDamage;
1255 flag.pushable = true;
1256 flag.teleportable = TELEPORT_NORMAL;
1257 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1258 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1259 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1260 if(flag.damagedbycontents)
1261 IL_PUSH(g_damagedbycontents, flag);
1262 flag.velocity = '0 0 0';
1263 flag.mangle = flag.angles;
1264 flag.reset = ctf_Reset;
1265 settouch(flag, ctf_FlagTouch);
1266 setthink(flag, ctf_FlagThink);
1267 flag.nextthink = time + FLAG_THINKRATE;
1268 flag.ctf_status = FLAG_BASE;
1270 string teamname = Static_Team_ColorName_Lower(teamnumber);
1272 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1273 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1274 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1275 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1276 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1277 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1281 if(flag.s == "") flag.s = b; \
1282 precache_sound(flag.s);
1284 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1285 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1286 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1287 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1288 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1289 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1290 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1294 precache_model(flag.model);
1297 _setmodel(flag, flag.model); // precision set below
1298 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1299 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1301 if(autocvar_g_ctf_flag_glowtrails)
1305 case NUM_TEAM_1: flag.glow_color = 251; break;
1306 case NUM_TEAM_2: flag.glow_color = 210; break;
1307 case NUM_TEAM_3: flag.glow_color = 110; break;
1308 case NUM_TEAM_4: flag.glow_color = 145; break;
1309 default: flag.glow_color = 254; break;
1311 flag.glow_size = 25;
1312 flag.glow_trail = 1;
1315 flag.effects |= EF_LOWPRECISION;
1316 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1317 if(autocvar_g_ctf_dynamiclights)
1321 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1322 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1323 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1324 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1325 default: flag.effects |= EF_DIMLIGHT; break;
1330 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1332 flag.dropped_origin = flag.origin;
1333 flag.noalign = true;
1334 set_movetype(flag, MOVETYPE_NONE);
1336 else // drop to floor, automatically find a platform and set that as spawn origin
1338 flag.noalign = false;
1340 set_movetype(flag, MOVETYPE_NONE);
1343 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1351 // NOTE: LEGACY CODE, needs to be re-written!
1353 void havocbot_calculate_middlepoint()
1357 vector fo = '0 0 0';
1360 f = ctf_worldflaglist;
1365 f = f.ctf_worldflagnext;
1369 havocbot_ctf_middlepoint = s * (1.0 / n);
1370 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1374 entity havocbot_ctf_find_flag(entity bot)
1377 f = ctf_worldflaglist;
1380 if (CTF_SAMETEAM(bot, f))
1382 f = f.ctf_worldflagnext;
1387 entity havocbot_ctf_find_enemy_flag(entity bot)
1390 f = ctf_worldflaglist;
1395 if(CTF_DIFFTEAM(bot, f))
1402 else if(!bot.flagcarried)
1406 else if (CTF_DIFFTEAM(bot, f))
1408 f = f.ctf_worldflagnext;
1413 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1420 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1421 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1424 if(vdist(it.origin - org, <, tc_radius))
1431 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1434 head = ctf_worldflaglist;
1437 if (CTF_SAMETEAM(this, head))
1439 head = head.ctf_worldflagnext;
1442 navigation_routerating(this, head, ratingscale, 10000);
1445 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1448 head = ctf_worldflaglist;
1451 if (CTF_SAMETEAM(this, head))
1453 head = head.ctf_worldflagnext;
1458 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1461 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1464 head = ctf_worldflaglist;
1469 if(CTF_DIFFTEAM(this, head))
1473 if(this.flagcarried)
1476 else if(!this.flagcarried)
1480 else if(CTF_DIFFTEAM(this, head))
1482 head = head.ctf_worldflagnext;
1485 navigation_routerating(this, head, ratingscale, 10000);
1488 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1490 if (!bot_waypoints_for_items)
1492 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1498 head = havocbot_ctf_find_enemy_flag(this);
1503 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1506 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1510 mf = havocbot_ctf_find_flag(this);
1512 if(mf.ctf_status == FLAG_BASE)
1516 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1519 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1522 head = ctf_worldflaglist;
1525 // flag is out in the field
1526 if(head.ctf_status != FLAG_BASE)
1527 if(head.tag_entity==NULL) // dropped
1531 if(vdist(org - head.origin, <, df_radius))
1532 navigation_routerating(this, head, ratingscale, 10000);
1535 navigation_routerating(this, head, ratingscale, 10000);
1538 head = head.ctf_worldflagnext;
1542 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1544 IL_EACH(g_items, it.bot_pickup,
1546 // gather health and armor only
1548 if (it.health || it.armorvalue)
1549 if (vdist(it.origin - org, <, sradius))
1551 // get the value of the item
1552 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1554 navigation_routerating(this, it, t * ratingscale, 500);
1559 void havocbot_ctf_reset_role(entity this)
1561 float cdefense, cmiddle, coffense;
1568 if(havocbot_ctf_middlepoint == '0 0 0')
1569 havocbot_calculate_middlepoint();
1572 if (this.flagcarried)
1574 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1578 mf = havocbot_ctf_find_flag(this);
1579 ef = havocbot_ctf_find_enemy_flag(this);
1581 // Retrieve stolen flag
1582 if(mf.ctf_status!=FLAG_BASE)
1584 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1588 // If enemy flag is taken go to the middle to intercept pursuers
1589 if(ef.ctf_status!=FLAG_BASE)
1591 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1595 // if there is only me on the team switch to offense
1597 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1601 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1605 // Evaluate best position to take
1606 // Count mates on middle position
1607 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1609 // Count mates on defense position
1610 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1612 // Count mates on offense position
1613 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1615 if(cdefense<=coffense)
1616 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1617 else if(coffense<=cmiddle)
1618 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1620 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1623 void havocbot_role_ctf_carrier(entity this)
1627 havocbot_ctf_reset_role(this);
1631 if (this.flagcarried == NULL)
1633 havocbot_ctf_reset_role(this);
1637 if (this.bot_strategytime < time)
1639 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1641 navigation_goalrating_start(this);
1643 havocbot_goalrating_ctf_enemybase(this, 50000);
1645 havocbot_goalrating_ctf_ourbase(this, 50000);
1648 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1650 navigation_goalrating_end(this);
1652 if (this.navigation_hasgoals)
1653 this.havocbot_cantfindflag = time + 10;
1654 else if (time > this.havocbot_cantfindflag)
1656 // Can't navigate to my own base, suicide!
1657 // TODO: drop it and wander around
1658 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1664 void havocbot_role_ctf_escort(entity this)
1670 havocbot_ctf_reset_role(this);
1674 if (this.flagcarried)
1676 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1680 // If enemy flag is back on the base switch to previous role
1681 ef = havocbot_ctf_find_enemy_flag(this);
1682 if(ef.ctf_status==FLAG_BASE)
1684 this.havocbot_role = this.havocbot_previous_role;
1685 this.havocbot_role_timeout = 0;
1689 // If the flag carrier reached the base switch to defense
1690 mf = havocbot_ctf_find_flag(this);
1691 if(mf.ctf_status!=FLAG_BASE)
1692 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1694 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1698 // Set the role timeout if necessary
1699 if (!this.havocbot_role_timeout)
1701 this.havocbot_role_timeout = time + random() * 30 + 60;
1704 // If nothing happened just switch to previous role
1705 if (time > this.havocbot_role_timeout)
1707 this.havocbot_role = this.havocbot_previous_role;
1708 this.havocbot_role_timeout = 0;
1712 // Chase the flag carrier
1713 if (this.bot_strategytime < time)
1715 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1716 navigation_goalrating_start(this);
1717 havocbot_goalrating_ctf_enemyflag(this, 30000);
1718 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1719 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1720 navigation_goalrating_end(this);
1724 void havocbot_role_ctf_offense(entity this)
1731 havocbot_ctf_reset_role(this);
1735 if (this.flagcarried)
1737 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1742 mf = havocbot_ctf_find_flag(this);
1743 ef = havocbot_ctf_find_enemy_flag(this);
1746 if(mf.ctf_status!=FLAG_BASE)
1749 pos = mf.tag_entity.origin;
1753 // Try to get it if closer than the enemy base
1754 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1756 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1761 // Escort flag carrier
1762 if(ef.ctf_status!=FLAG_BASE)
1765 pos = ef.tag_entity.origin;
1769 if(vdist(pos - mf.dropped_origin, >, 700))
1771 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1776 // About to fail, switch to middlefield
1779 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1783 // Set the role timeout if necessary
1784 if (!this.havocbot_role_timeout)
1785 this.havocbot_role_timeout = time + 120;
1787 if (time > this.havocbot_role_timeout)
1789 havocbot_ctf_reset_role(this);
1793 if (this.bot_strategytime < time)
1795 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1796 navigation_goalrating_start(this);
1797 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1798 havocbot_goalrating_ctf_enemybase(this, 20000);
1799 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1800 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1801 navigation_goalrating_end(this);
1805 // Retriever (temporary role):
1806 void havocbot_role_ctf_retriever(entity this)
1812 havocbot_ctf_reset_role(this);
1816 if (this.flagcarried)
1818 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1822 // If flag is back on the base switch to previous role
1823 mf = havocbot_ctf_find_flag(this);
1824 if(mf.ctf_status==FLAG_BASE)
1826 havocbot_ctf_reset_role(this);
1830 if (!this.havocbot_role_timeout)
1831 this.havocbot_role_timeout = time + 20;
1833 if (time > this.havocbot_role_timeout)
1835 havocbot_ctf_reset_role(this);
1839 if (this.bot_strategytime < time)
1844 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1845 navigation_goalrating_start(this);
1846 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1847 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1848 havocbot_goalrating_ctf_enemybase(this, 30000);
1849 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1850 navigation_goalrating_end(this);
1854 void havocbot_role_ctf_middle(entity this)
1860 havocbot_ctf_reset_role(this);
1864 if (this.flagcarried)
1866 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1870 mf = havocbot_ctf_find_flag(this);
1871 if(mf.ctf_status!=FLAG_BASE)
1873 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1877 if (!this.havocbot_role_timeout)
1878 this.havocbot_role_timeout = time + 10;
1880 if (time > this.havocbot_role_timeout)
1882 havocbot_ctf_reset_role(this);
1886 if (this.bot_strategytime < time)
1890 org = havocbot_ctf_middlepoint;
1891 org.z = this.origin.z;
1893 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1894 navigation_goalrating_start(this);
1895 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1896 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1897 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1898 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1899 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1900 havocbot_goalrating_ctf_enemybase(this, 2500);
1901 navigation_goalrating_end(this);
1905 void havocbot_role_ctf_defense(entity this)
1911 havocbot_ctf_reset_role(this);
1915 if (this.flagcarried)
1917 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1921 // If own flag was captured
1922 mf = havocbot_ctf_find_flag(this);
1923 if(mf.ctf_status!=FLAG_BASE)
1925 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1929 if (!this.havocbot_role_timeout)
1930 this.havocbot_role_timeout = time + 30;
1932 if (time > this.havocbot_role_timeout)
1934 havocbot_ctf_reset_role(this);
1937 if (this.bot_strategytime < time)
1942 org = mf.dropped_origin;
1943 mp_radius = havocbot_ctf_middlepoint_radius;
1945 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1946 navigation_goalrating_start(this);
1948 // if enemies are closer to our base, go there
1949 entity closestplayer = NULL;
1950 float distance, bestdistance = 10000;
1951 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1952 distance = vlen(org - it.origin);
1953 if(distance<bestdistance)
1956 bestdistance = distance;
1961 if(DIFF_TEAM(closestplayer, this))
1962 if(vdist(org - this.origin, >, 1000))
1963 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1964 havocbot_goalrating_ctf_ourbase(this, 30000);
1966 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1967 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1968 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1969 havocbot_goalrating_items(this, 10000, org, mp_radius);
1970 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1971 navigation_goalrating_end(this);
1975 void havocbot_role_ctf_setrole(entity bot, int role)
1977 string s = "(null)";
1980 case HAVOCBOT_CTF_ROLE_CARRIER:
1982 bot.havocbot_role = havocbot_role_ctf_carrier;
1983 bot.havocbot_role_timeout = 0;
1984 bot.havocbot_cantfindflag = time + 10;
1985 bot.bot_strategytime = 0;
1987 case HAVOCBOT_CTF_ROLE_DEFENSE:
1989 bot.havocbot_role = havocbot_role_ctf_defense;
1990 bot.havocbot_role_timeout = 0;
1992 case HAVOCBOT_CTF_ROLE_MIDDLE:
1994 bot.havocbot_role = havocbot_role_ctf_middle;
1995 bot.havocbot_role_timeout = 0;
1997 case HAVOCBOT_CTF_ROLE_OFFENSE:
1999 bot.havocbot_role = havocbot_role_ctf_offense;
2000 bot.havocbot_role_timeout = 0;
2002 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2004 bot.havocbot_previous_role = bot.havocbot_role;
2005 bot.havocbot_role = havocbot_role_ctf_retriever;
2006 bot.havocbot_role_timeout = time + 10;
2007 bot.bot_strategytime = 0;
2009 case HAVOCBOT_CTF_ROLE_ESCORT:
2011 bot.havocbot_previous_role = bot.havocbot_role;
2012 bot.havocbot_role = havocbot_role_ctf_escort;
2013 bot.havocbot_role_timeout = time + 30;
2014 bot.bot_strategytime = 0;
2017 LOG_TRACE(bot.netname, " switched to ", s);
2025 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2027 entity player = M_ARGV(0, entity);
2029 int t = 0, t2 = 0, t3 = 0;
2031 // initially clear items so they can be set as necessary later.
2032 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2033 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2034 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2035 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2036 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2037 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2039 // scan through all the flags and notify the client about them
2040 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2042 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2043 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2044 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2045 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2046 if(flag.team == 0) { t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; player.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
2048 switch(flag.ctf_status)
2053 if((flag.owner == player) || (flag.pass_sender == player))
2054 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2056 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2061 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2067 // item for stopping players from capturing the flag too often
2068 if(player.ctf_captureshielded)
2069 player.ctf_flagstatus |= CTF_SHIELDED;
2072 player.ctf_flagstatus |= CTF_STALEMATE;
2074 // update the health of the flag carrier waypointsprite
2075 if(player.wps_flagcarrier)
2076 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2079 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2081 entity frag_attacker = M_ARGV(1, entity);
2082 entity frag_target = M_ARGV(2, entity);
2083 float frag_damage = M_ARGV(4, float);
2084 vector frag_force = M_ARGV(6, vector);
2086 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2088 if(frag_target == frag_attacker) // damage done to yourself
2090 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2091 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2093 else // damage done to everyone else
2095 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2096 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2099 M_ARGV(4, float) = frag_damage;
2100 M_ARGV(6, vector) = frag_force;
2102 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2104 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.m_id)))
2105 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2107 frag_target.wps_helpme_time = time;
2108 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2110 // todo: add notification for when flag carrier needs help?
2114 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2116 entity frag_attacker = M_ARGV(1, entity);
2117 entity frag_target = M_ARGV(2, entity);
2119 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2121 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2122 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2125 if(frag_target.flagcarried)
2127 entity tmp_entity = frag_target.flagcarried;
2128 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2129 tmp_entity.ctf_dropper = NULL;
2133 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2135 M_ARGV(2, float) = 0; // frag score
2136 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2139 void ctf_RemovePlayer(entity player)
2141 if(player.flagcarried)
2142 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2144 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2146 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2147 if(flag.pass_target == player) { flag.pass_target = NULL; }
2148 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2152 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2154 entity player = M_ARGV(0, entity);
2156 ctf_RemovePlayer(player);
2159 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2161 entity player = M_ARGV(0, entity);
2163 ctf_RemovePlayer(player);
2166 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2168 entity player = M_ARGV(0, entity);
2170 if(player.flagcarried)
2171 if(!autocvar_g_ctf_portalteleport)
2172 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2175 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2177 if(MUTATOR_RETURNVALUE || gameover) { return; }
2179 entity player = M_ARGV(0, entity);
2181 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2183 // pass the flag to a team mate
2184 if(autocvar_g_ctf_pass)
2186 entity head, closest_target = NULL;
2187 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2189 while(head) // find the closest acceptable target to pass to
2191 if(IS_PLAYER(head) && !IS_DEAD(head))
2192 if(head != player && SAME_TEAM(head, player))
2193 if(!head.speedrunning && !head.vehicle)
2195 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2196 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2197 vector passer_center = CENTER_OR_VIEWOFS(player);
2199 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2201 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2203 if(IS_BOT_CLIENT(head))
2205 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2206 ctf_Handle_Throw(head, player, DROP_PASS);
2210 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2211 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2213 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2216 else if(player.flagcarried)
2220 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2221 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2222 { closest_target = head; }
2224 else { closest_target = head; }
2231 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2234 // throw the flag in front of you
2235 if(autocvar_g_ctf_throw && player.flagcarried)
2237 if(player.throw_count == -1)
2239 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2241 player.throw_prevtime = time;
2242 player.throw_count = 1;
2243 ctf_Handle_Throw(player, NULL, DROP_THROW);
2248 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2254 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2255 else { player.throw_count += 1; }
2256 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2258 player.throw_prevtime = time;
2259 ctf_Handle_Throw(player, NULL, DROP_THROW);
2266 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2268 entity player = M_ARGV(0, entity);
2270 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2272 player.wps_helpme_time = time;
2273 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2275 else // create a normal help me waypointsprite
2277 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2278 WaypointSprite_Ping(player.wps_helpme);
2284 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2286 entity player = M_ARGV(0, entity);
2287 entity veh = M_ARGV(1, entity);
2289 if(player.flagcarried)
2291 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2293 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2297 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2298 setattachment(player.flagcarried, veh, "");
2299 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2300 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2301 //player.flagcarried.angles = '0 0 0';
2307 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2309 entity player = M_ARGV(0, entity);
2311 if(player.flagcarried)
2313 setattachment(player.flagcarried, player, "");
2314 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2315 player.flagcarried.scale = FLAG_SCALE;
2316 player.flagcarried.angles = '0 0 0';
2317 player.flagcarried.nodrawtoclient = NULL;
2322 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2324 entity player = M_ARGV(0, entity);
2326 if(player.flagcarried)
2328 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2329 ctf_RespawnFlag(player.flagcarried);
2334 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2336 entity flag; // temporary entity for the search method
2338 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2340 switch(flag.ctf_status)
2345 // lock the flag, game is over
2346 set_movetype(flag, MOVETYPE_NONE);
2347 flag.takedamage = DAMAGE_NO;
2348 flag.solid = SOLID_NOT;
2349 flag.nextthink = false; // stop thinking
2351 //dprint("stopping the ", flag.netname, " from moving.\n");
2359 // do nothing for these flags
2366 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2368 entity bot = M_ARGV(0, entity);
2370 havocbot_ctf_reset_role(bot);
2374 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2376 //M_ARGV(0, float) = ctf_teams;
2377 M_ARGV(1, string) = "ctf_team";
2381 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2383 entity spectatee = M_ARGV(0, entity);
2384 entity client = M_ARGV(1, entity);
2386 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2389 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2391 int record_page = M_ARGV(0, int);
2392 string ret_string = M_ARGV(1, string);
2394 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2396 if (MapInfo_Get_ByID(i))
2398 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2404 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2405 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2409 M_ARGV(1, string) = ret_string;
2412 bool superspec_Spectate(entity this, entity targ); // TODO
2413 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2414 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2416 entity player = M_ARGV(0, entity);
2417 string cmd_name = M_ARGV(1, string);
2418 int cmd_argc = M_ARGV(2, int);
2420 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2422 if(cmd_name == "followfc")
2434 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2435 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2436 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2437 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2441 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2442 if(it.flagcarried && (it.team == _team || _team == 0))
2445 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2446 continue; // already spectating this fc, try another
2447 return superspec_Spectate(player, it);
2452 superspec_msg("", "", player, "No active flag carrier\n", 1);
2457 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2459 entity frag_target = M_ARGV(0, entity);
2461 if(frag_target.flagcarried)
2462 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2470 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2471 CTF flag for team one (Red).
2473 "angle" Angle the flag will point (minus 90 degrees)...
2474 "model" model to use, note this needs red and blue as skins 0 and 1...
2475 "noise" sound played when flag is picked up...
2476 "noise1" sound played when flag is returned by a teammate...
2477 "noise2" sound played when flag is captured...
2478 "noise3" sound played when flag is lost in the field and respawns itself...
2479 "noise4" sound played when flag is dropped by a player...
2480 "noise5" sound played when flag touches the ground... */
2481 spawnfunc(item_flag_team1)
2483 if(!g_ctf) { delete(this); return; }
2485 ctf_FlagSetup(NUM_TEAM_1, this);
2488 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2489 CTF flag for team two (Blue).
2491 "angle" Angle the flag will point (minus 90 degrees)...
2492 "model" model to use, note this needs red and blue as skins 0 and 1...
2493 "noise" sound played when flag is picked up...
2494 "noise1" sound played when flag is returned by a teammate...
2495 "noise2" sound played when flag is captured...
2496 "noise3" sound played when flag is lost in the field and respawns itself...
2497 "noise4" sound played when flag is dropped by a player...
2498 "noise5" sound played when flag touches the ground... */
2499 spawnfunc(item_flag_team2)
2501 if(!g_ctf) { delete(this); return; }
2503 ctf_FlagSetup(NUM_TEAM_2, this);
2506 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2507 CTF flag for team three (Yellow).
2509 "angle" Angle the flag will point (minus 90 degrees)...
2510 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2511 "noise" sound played when flag is picked up...
2512 "noise1" sound played when flag is returned by a teammate...
2513 "noise2" sound played when flag is captured...
2514 "noise3" sound played when flag is lost in the field and respawns itself...
2515 "noise4" sound played when flag is dropped by a player...
2516 "noise5" sound played when flag touches the ground... */
2517 spawnfunc(item_flag_team3)
2519 if(!g_ctf) { delete(this); return; }
2521 ctf_FlagSetup(NUM_TEAM_3, this);
2524 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2525 CTF flag for team four (Pink).
2527 "angle" Angle the flag will point (minus 90 degrees)...
2528 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2529 "noise" sound played when flag is picked up...
2530 "noise1" sound played when flag is returned by a teammate...
2531 "noise2" sound played when flag is captured...
2532 "noise3" sound played when flag is lost in the field and respawns itself...
2533 "noise4" sound played when flag is dropped by a player...
2534 "noise5" sound played when flag touches the ground... */
2535 spawnfunc(item_flag_team4)
2537 if(!g_ctf) { delete(this); return; }
2539 ctf_FlagSetup(NUM_TEAM_4, this);
2542 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2545 "angle" Angle the flag will point (minus 90 degrees)...
2546 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2547 "noise" sound played when flag is picked up...
2548 "noise1" sound played when flag is returned by a teammate...
2549 "noise2" sound played when flag is captured...
2550 "noise3" sound played when flag is lost in the field and respawns itself...
2551 "noise4" sound played when flag is dropped by a player...
2552 "noise5" sound played when flag touches the ground... */
2553 spawnfunc(item_flag_neutral)
2555 if(!g_ctf) { delete(this); return; }
2556 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2558 ctf_FlagSetup(0, this);
2561 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2562 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2563 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.
2565 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2566 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2569 if(!g_ctf) { delete(this); return; }
2571 this.classname = "ctf_team";
2572 this.team = this.cnt + 1;
2575 // compatibility for quake maps
2576 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2577 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2578 spawnfunc(info_player_team1);
2579 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2580 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2581 spawnfunc(info_player_team2);
2582 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2583 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2585 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2586 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2594 void ctf_ScoreRules(int teams)
2596 CheckAllowedTeams(NULL);
2597 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2598 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2599 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2600 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2601 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2602 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2603 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2604 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2605 ScoreRules_basics_end();
2608 // code from here on is just to support maps that don't have flag and team entities
2609 void ctf_SpawnTeam (string teamname, int teamcolor)
2611 entity this = new_pure(ctf_team);
2612 this.netname = teamname;
2613 this.cnt = teamcolor - 1;
2614 this.spawnfunc_checked = true;
2615 this.team = teamcolor;
2618 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2623 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2625 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2626 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2628 switch(tmp_entity.team)
2630 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2631 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2632 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2633 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2635 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2638 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2640 ctf_teams = 0; // so set the default red and blue teams
2641 BITSET_ASSIGN(ctf_teams, BIT(0));
2642 BITSET_ASSIGN(ctf_teams, BIT(1));
2645 //ctf_teams = bound(2, ctf_teams, 4);
2647 // if no teams are found, spawn defaults
2648 if(find(NULL, classname, "ctf_team") == NULL)
2650 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2651 if(ctf_teams & BIT(0))
2652 ctf_SpawnTeam("Red", NUM_TEAM_1);
2653 if(ctf_teams & BIT(1))
2654 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2655 if(ctf_teams & BIT(2))
2656 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2657 if(ctf_teams & BIT(3))
2658 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2661 ctf_ScoreRules(ctf_teams);
2664 void ctf_Initialize()
2666 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2668 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2669 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2670 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2672 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);