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)
874 this.ctf_flagdamaged_byworld = true;
878 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
882 if(autocvar_g_ctf_flag_return_damage)
884 // reduce health and check if it should be returned
885 this.health = this.health - damage;
886 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
891 void ctf_FlagThink(entity this)
896 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
899 if(this == ctf_worldflaglist) // only for the first flag
900 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
903 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
904 LOG_TRACE("wtf the flag got squashed?");
905 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
906 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
907 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
911 switch(this.ctf_status)
915 if(autocvar_g_ctf_dropped_capture_radius)
917 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
918 if(tmp_entity.ctf_status == FLAG_DROPPED)
919 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
920 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
921 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
928 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
930 if(autocvar_g_ctf_flag_dropped_floatinwater)
932 vector midpoint = ((this.absmin + this.absmax) * 0.5);
933 if(pointcontents(midpoint) == CONTENT_WATER)
935 this.velocity = this.velocity * 0.5;
937 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
938 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
940 { set_movetype(this, MOVETYPE_FLY); }
942 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
944 if(autocvar_g_ctf_flag_return_dropped)
946 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
949 ctf_CheckFlagReturn(this, RETURN_DROPPED);
953 if(this.ctf_flagdamaged_byworld)
955 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
956 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
959 else if(autocvar_g_ctf_flag_return_time)
961 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
962 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
970 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
973 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
975 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
976 ImpulseCommands(this.owner);
978 if(autocvar_g_ctf_stalemate)
980 if(time >= wpforenemy_nextthink)
982 ctf_CheckStalemate();
983 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
986 if(CTF_SAMETEAM(this, this.owner) && this.team)
988 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
989 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
990 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
991 ctf_Handle_Return(this, this.owner);
998 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
999 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1000 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1002 if((this.pass_target == NULL)
1003 || (IS_DEAD(this.pass_target))
1004 || (this.pass_target.flagcarried)
1005 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1006 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1007 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1009 // give up, pass failed
1010 ctf_Handle_Drop(this, NULL, DROP_PASS);
1014 // still a viable target, go for it
1015 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1020 default: // this should never happen
1022 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1028 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1031 if(gameover) { return; }
1032 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1034 bool is_not_monster = (!IS_MONSTER(toucher));
1036 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1037 if(ITEM_TOUCH_NEEDKILL())
1039 if(!autocvar_g_ctf_flag_return_damage_delay)
1042 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1044 if(!flag.ctf_flagdamaged_byworld) { return; }
1047 int num_perteam = 0;
1048 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), LAMBDA(++num_perteam));
1050 // special touch behaviors
1051 if(STAT(FROZEN, toucher)) { return; }
1052 else if(IS_VEHICLE(toucher))
1054 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1055 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1057 return; // do nothing
1059 else if(IS_MONSTER(toucher))
1061 if(!autocvar_g_ctf_allow_monster_touch)
1062 return; // do nothing
1064 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1066 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1068 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1069 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1070 flag.wait = time + FLAG_TOUCHRATE;
1074 else if(IS_DEAD(toucher)) { return; }
1076 switch(flag.ctf_status)
1082 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1083 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1084 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1085 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1087 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1088 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1089 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)
1091 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1092 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1094 else if(CTF_DIFFTEAM(toucher, flag) && (!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 enemies flag
1101 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
1102 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1103 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1104 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1110 LOG_TRACE("Someone touched a flag even though it was being carried?");
1116 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1118 if(DIFF_TEAM(toucher, flag.pass_sender))
1119 ctf_Handle_Return(flag, toucher);
1121 ctf_Handle_Retrieve(flag, toucher);
1128 .float last_respawn;
1129 void ctf_RespawnFlag(entity flag)
1131 // check for flag respawn being called twice in a row
1132 if(flag.last_respawn > time - 0.5)
1133 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1135 flag.last_respawn = time;
1137 // reset the player (if there is one)
1138 if((flag.owner) && (flag.owner.flagcarried == flag))
1140 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1141 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1142 WaypointSprite_Kill(flag.wps_flagcarrier);
1144 flag.owner.flagcarried = NULL;
1146 if(flag.speedrunning)
1147 ctf_FakeTimeLimit(flag.owner, -1);
1150 if((flag.owner) && (flag.owner.vehicle))
1151 flag.scale = FLAG_SCALE;
1153 if(flag.ctf_status == FLAG_DROPPED)
1154 { WaypointSprite_Kill(flag.wps_flagdropped); }
1157 setattachment(flag, NULL, "");
1158 setorigin(flag, flag.ctf_spawnorigin);
1160 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1161 flag.takedamage = DAMAGE_NO;
1162 flag.health = flag.max_flag_health;
1163 flag.solid = SOLID_TRIGGER;
1164 flag.velocity = '0 0 0';
1165 flag.angles = flag.mangle;
1166 flag.flags = FL_ITEM | FL_NOTARGET;
1168 flag.ctf_status = FLAG_BASE;
1170 flag.pass_distance = 0;
1171 flag.pass_sender = NULL;
1172 flag.pass_target = NULL;
1173 flag.ctf_dropper = NULL;
1174 flag.ctf_pickuptime = 0;
1175 flag.ctf_droptime = 0;
1176 flag.ctf_flagdamaged_byworld = false;
1178 ctf_CheckStalemate();
1181 void ctf_Reset(entity this)
1183 if(this.owner && IS_PLAYER(this.owner))
1184 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1186 ctf_RespawnFlag(this);
1189 bool ctf_FlagBase_Customize(entity this, entity client)
1191 if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
1196 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1199 waypoint_spawnforitem_force(this, this.origin);
1200 this.nearestwaypointtimeout = 0; // activate waypointing again
1201 this.bot_basewaypoint = this.nearestwaypoint;
1207 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1208 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1209 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1210 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1211 default: basename = WP_FlagBaseNeutral; break;
1214 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1215 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1216 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1217 setcefc(wp, ctf_FlagBase_Customize);
1219 // captureshield setup
1220 ctf_CaptureShield_Spawn(this);
1225 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1228 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1229 ctf_worldflaglist = flag;
1231 setattachment(flag, NULL, "");
1233 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1234 flag.team = teamnumber;
1235 flag.classname = "item_flag_team";
1236 flag.target = "###item###"; // wut?
1237 flag.flags = FL_ITEM | FL_NOTARGET;
1238 IL_PUSH(g_items, flag);
1239 flag.solid = SOLID_TRIGGER;
1240 flag.takedamage = DAMAGE_NO;
1241 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1242 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1243 flag.health = flag.max_flag_health;
1244 flag.event_damage = ctf_FlagDamage;
1245 flag.pushable = true;
1246 flag.teleportable = TELEPORT_NORMAL;
1247 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1248 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1249 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1250 if(flag.damagedbycontents)
1251 IL_PUSH(g_damagedbycontents, flag);
1252 flag.velocity = '0 0 0';
1253 flag.mangle = flag.angles;
1254 flag.reset = ctf_Reset;
1255 settouch(flag, ctf_FlagTouch);
1256 setthink(flag, ctf_FlagThink);
1257 flag.nextthink = time + FLAG_THINKRATE;
1258 flag.ctf_status = FLAG_BASE;
1260 string teamname = Static_Team_ColorName_Lower(teamnumber);
1262 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1263 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1264 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1265 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1266 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1267 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1271 if(flag.s == "") flag.s = b; \
1272 precache_sound(flag.s);
1274 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1275 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1276 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1277 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1278 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1279 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1280 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1284 precache_model(flag.model);
1287 _setmodel(flag, flag.model); // precision set below
1288 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1289 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1291 if(autocvar_g_ctf_flag_glowtrails)
1295 case NUM_TEAM_1: flag.glow_color = 251; break;
1296 case NUM_TEAM_2: flag.glow_color = 210; break;
1297 case NUM_TEAM_3: flag.glow_color = 110; break;
1298 case NUM_TEAM_4: flag.glow_color = 145; break;
1299 default: flag.glow_color = 254; break;
1301 flag.glow_size = 25;
1302 flag.glow_trail = 1;
1305 flag.effects |= EF_LOWPRECISION;
1306 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1307 if(autocvar_g_ctf_dynamiclights)
1311 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1312 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1313 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1314 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1315 default: flag.effects |= EF_DIMLIGHT; break;
1320 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1322 flag.dropped_origin = flag.origin;
1323 flag.noalign = true;
1324 set_movetype(flag, MOVETYPE_NONE);
1326 else // drop to floor, automatically find a platform and set that as spawn origin
1328 flag.noalign = false;
1330 set_movetype(flag, MOVETYPE_NONE);
1333 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1341 // NOTE: LEGACY CODE, needs to be re-written!
1343 void havocbot_calculate_middlepoint()
1347 vector fo = '0 0 0';
1350 f = ctf_worldflaglist;
1355 f = f.ctf_worldflagnext;
1359 havocbot_ctf_middlepoint = s * (1.0 / n);
1360 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1364 entity havocbot_ctf_find_flag(entity bot)
1367 f = ctf_worldflaglist;
1370 if (CTF_SAMETEAM(bot, f))
1372 f = f.ctf_worldflagnext;
1377 entity havocbot_ctf_find_enemy_flag(entity bot)
1380 f = ctf_worldflaglist;
1385 if(CTF_DIFFTEAM(bot, f))
1392 else if(!bot.flagcarried)
1396 else if (CTF_DIFFTEAM(bot, f))
1398 f = f.ctf_worldflagnext;
1403 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1410 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1411 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1414 if(vdist(it.origin - org, <, tc_radius))
1421 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1424 head = ctf_worldflaglist;
1427 if (CTF_SAMETEAM(this, head))
1429 head = head.ctf_worldflagnext;
1432 navigation_routerating(this, head, ratingscale, 10000);
1435 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1438 head = ctf_worldflaglist;
1441 if (CTF_SAMETEAM(this, head))
1443 head = head.ctf_worldflagnext;
1448 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1451 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1454 head = ctf_worldflaglist;
1459 if(CTF_DIFFTEAM(this, head))
1463 if(this.flagcarried)
1466 else if(!this.flagcarried)
1470 else if(CTF_DIFFTEAM(this, head))
1472 head = head.ctf_worldflagnext;
1475 navigation_routerating(this, head, ratingscale, 10000);
1478 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1480 if (!bot_waypoints_for_items)
1482 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1488 head = havocbot_ctf_find_enemy_flag(this);
1493 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1496 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1500 mf = havocbot_ctf_find_flag(this);
1502 if(mf.ctf_status == FLAG_BASE)
1506 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1509 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1512 head = ctf_worldflaglist;
1515 // flag is out in the field
1516 if(head.ctf_status != FLAG_BASE)
1517 if(head.tag_entity==NULL) // dropped
1521 if(vdist(org - head.origin, <, df_radius))
1522 navigation_routerating(this, head, ratingscale, 10000);
1525 navigation_routerating(this, head, ratingscale, 10000);
1528 head = head.ctf_worldflagnext;
1532 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1534 IL_EACH(g_items, it.bot_pickup,
1536 // gather health and armor only
1538 if (it.health || it.armorvalue)
1539 if (vdist(it.origin - org, <, sradius))
1541 // get the value of the item
1542 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1544 navigation_routerating(this, it, t * ratingscale, 500);
1549 void havocbot_ctf_reset_role(entity this)
1551 float cdefense, cmiddle, coffense;
1558 if(havocbot_ctf_middlepoint == '0 0 0')
1559 havocbot_calculate_middlepoint();
1562 if (this.flagcarried)
1564 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1568 mf = havocbot_ctf_find_flag(this);
1569 ef = havocbot_ctf_find_enemy_flag(this);
1571 // Retrieve stolen flag
1572 if(mf.ctf_status!=FLAG_BASE)
1574 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1578 // If enemy flag is taken go to the middle to intercept pursuers
1579 if(ef.ctf_status!=FLAG_BASE)
1581 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1585 // if there is only me on the team switch to offense
1587 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1591 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1595 // Evaluate best position to take
1596 // Count mates on middle position
1597 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1599 // Count mates on defense position
1600 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1602 // Count mates on offense position
1603 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1605 if(cdefense<=coffense)
1606 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1607 else if(coffense<=cmiddle)
1608 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1610 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1613 void havocbot_role_ctf_carrier(entity this)
1617 havocbot_ctf_reset_role(this);
1621 if (this.flagcarried == NULL)
1623 havocbot_ctf_reset_role(this);
1627 if (this.bot_strategytime < time)
1629 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1631 navigation_goalrating_start(this);
1633 havocbot_goalrating_ctf_enemybase(this, 50000);
1635 havocbot_goalrating_ctf_ourbase(this, 50000);
1638 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1640 navigation_goalrating_end(this);
1642 if (this.navigation_hasgoals)
1643 this.havocbot_cantfindflag = time + 10;
1644 else if (time > this.havocbot_cantfindflag)
1646 // Can't navigate to my own base, suicide!
1647 // TODO: drop it and wander around
1648 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1654 void havocbot_role_ctf_escort(entity this)
1660 havocbot_ctf_reset_role(this);
1664 if (this.flagcarried)
1666 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1670 // If enemy flag is back on the base switch to previous role
1671 ef = havocbot_ctf_find_enemy_flag(this);
1672 if(ef.ctf_status==FLAG_BASE)
1674 this.havocbot_role = this.havocbot_previous_role;
1675 this.havocbot_role_timeout = 0;
1679 // If the flag carrier reached the base switch to defense
1680 mf = havocbot_ctf_find_flag(this);
1681 if(mf.ctf_status!=FLAG_BASE)
1682 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1684 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1688 // Set the role timeout if necessary
1689 if (!this.havocbot_role_timeout)
1691 this.havocbot_role_timeout = time + random() * 30 + 60;
1694 // If nothing happened just switch to previous role
1695 if (time > this.havocbot_role_timeout)
1697 this.havocbot_role = this.havocbot_previous_role;
1698 this.havocbot_role_timeout = 0;
1702 // Chase the flag carrier
1703 if (this.bot_strategytime < time)
1705 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1706 navigation_goalrating_start(this);
1707 havocbot_goalrating_ctf_enemyflag(this, 30000);
1708 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1709 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1710 navigation_goalrating_end(this);
1714 void havocbot_role_ctf_offense(entity this)
1721 havocbot_ctf_reset_role(this);
1725 if (this.flagcarried)
1727 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1732 mf = havocbot_ctf_find_flag(this);
1733 ef = havocbot_ctf_find_enemy_flag(this);
1736 if(mf.ctf_status!=FLAG_BASE)
1739 pos = mf.tag_entity.origin;
1743 // Try to get it if closer than the enemy base
1744 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1746 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1751 // Escort flag carrier
1752 if(ef.ctf_status!=FLAG_BASE)
1755 pos = ef.tag_entity.origin;
1759 if(vdist(pos - mf.dropped_origin, >, 700))
1761 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1766 // About to fail, switch to middlefield
1769 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1773 // Set the role timeout if necessary
1774 if (!this.havocbot_role_timeout)
1775 this.havocbot_role_timeout = time + 120;
1777 if (time > this.havocbot_role_timeout)
1779 havocbot_ctf_reset_role(this);
1783 if (this.bot_strategytime < time)
1785 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1786 navigation_goalrating_start(this);
1787 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1788 havocbot_goalrating_ctf_enemybase(this, 20000);
1789 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1790 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1791 navigation_goalrating_end(this);
1795 // Retriever (temporary role):
1796 void havocbot_role_ctf_retriever(entity this)
1802 havocbot_ctf_reset_role(this);
1806 if (this.flagcarried)
1808 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1812 // If flag is back on the base switch to previous role
1813 mf = havocbot_ctf_find_flag(this);
1814 if(mf.ctf_status==FLAG_BASE)
1816 havocbot_ctf_reset_role(this);
1820 if (!this.havocbot_role_timeout)
1821 this.havocbot_role_timeout = time + 20;
1823 if (time > this.havocbot_role_timeout)
1825 havocbot_ctf_reset_role(this);
1829 if (this.bot_strategytime < time)
1834 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1835 navigation_goalrating_start(this);
1836 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1837 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1838 havocbot_goalrating_ctf_enemybase(this, 30000);
1839 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1840 navigation_goalrating_end(this);
1844 void havocbot_role_ctf_middle(entity this)
1850 havocbot_ctf_reset_role(this);
1854 if (this.flagcarried)
1856 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1860 mf = havocbot_ctf_find_flag(this);
1861 if(mf.ctf_status!=FLAG_BASE)
1863 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1867 if (!this.havocbot_role_timeout)
1868 this.havocbot_role_timeout = time + 10;
1870 if (time > this.havocbot_role_timeout)
1872 havocbot_ctf_reset_role(this);
1876 if (this.bot_strategytime < time)
1880 org = havocbot_ctf_middlepoint;
1881 org.z = this.origin.z;
1883 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1884 navigation_goalrating_start(this);
1885 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1886 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1887 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1888 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1889 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1890 havocbot_goalrating_ctf_enemybase(this, 2500);
1891 navigation_goalrating_end(this);
1895 void havocbot_role_ctf_defense(entity this)
1901 havocbot_ctf_reset_role(this);
1905 if (this.flagcarried)
1907 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1911 // If own flag was captured
1912 mf = havocbot_ctf_find_flag(this);
1913 if(mf.ctf_status!=FLAG_BASE)
1915 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1919 if (!this.havocbot_role_timeout)
1920 this.havocbot_role_timeout = time + 30;
1922 if (time > this.havocbot_role_timeout)
1924 havocbot_ctf_reset_role(this);
1927 if (this.bot_strategytime < time)
1932 org = mf.dropped_origin;
1933 mp_radius = havocbot_ctf_middlepoint_radius;
1935 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1936 navigation_goalrating_start(this);
1938 // if enemies are closer to our base, go there
1939 entity closestplayer = NULL;
1940 float distance, bestdistance = 10000;
1941 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1942 distance = vlen(org - it.origin);
1943 if(distance<bestdistance)
1946 bestdistance = distance;
1951 if(DIFF_TEAM(closestplayer, this))
1952 if(vdist(org - this.origin, >, 1000))
1953 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1954 havocbot_goalrating_ctf_ourbase(this, 30000);
1956 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1957 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1958 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1959 havocbot_goalrating_items(this, 10000, org, mp_radius);
1960 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1961 navigation_goalrating_end(this);
1965 void havocbot_role_ctf_setrole(entity bot, int role)
1967 string s = "(null)";
1970 case HAVOCBOT_CTF_ROLE_CARRIER:
1972 bot.havocbot_role = havocbot_role_ctf_carrier;
1973 bot.havocbot_role_timeout = 0;
1974 bot.havocbot_cantfindflag = time + 10;
1975 bot.bot_strategytime = 0;
1977 case HAVOCBOT_CTF_ROLE_DEFENSE:
1979 bot.havocbot_role = havocbot_role_ctf_defense;
1980 bot.havocbot_role_timeout = 0;
1982 case HAVOCBOT_CTF_ROLE_MIDDLE:
1984 bot.havocbot_role = havocbot_role_ctf_middle;
1985 bot.havocbot_role_timeout = 0;
1987 case HAVOCBOT_CTF_ROLE_OFFENSE:
1989 bot.havocbot_role = havocbot_role_ctf_offense;
1990 bot.havocbot_role_timeout = 0;
1992 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1994 bot.havocbot_previous_role = bot.havocbot_role;
1995 bot.havocbot_role = havocbot_role_ctf_retriever;
1996 bot.havocbot_role_timeout = time + 10;
1997 bot.bot_strategytime = 0;
1999 case HAVOCBOT_CTF_ROLE_ESCORT:
2001 bot.havocbot_previous_role = bot.havocbot_role;
2002 bot.havocbot_role = havocbot_role_ctf_escort;
2003 bot.havocbot_role_timeout = time + 30;
2004 bot.bot_strategytime = 0;
2007 LOG_TRACE(bot.netname, " switched to ", s);
2015 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2017 entity player = M_ARGV(0, entity);
2019 int t = 0, t2 = 0, t3 = 0;
2021 // initially clear items so they can be set as necessary later.
2022 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2023 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2024 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2025 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2026 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2027 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2029 // scan through all the flags and notify the client about them
2030 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2032 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2033 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2034 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2035 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2036 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; }
2038 switch(flag.ctf_status)
2043 if((flag.owner == player) || (flag.pass_sender == player))
2044 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2046 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2051 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2057 // item for stopping players from capturing the flag too often
2058 if(player.ctf_captureshielded)
2059 player.ctf_flagstatus |= CTF_SHIELDED;
2062 player.ctf_flagstatus |= CTF_STALEMATE;
2064 // update the health of the flag carrier waypointsprite
2065 if(player.wps_flagcarrier)
2066 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2069 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2071 entity frag_attacker = M_ARGV(1, entity);
2072 entity frag_target = M_ARGV(2, entity);
2073 float frag_damage = M_ARGV(4, float);
2074 vector frag_force = M_ARGV(6, vector);
2076 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2078 if(frag_target == frag_attacker) // damage done to yourself
2080 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2081 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2083 else // damage done to everyone else
2085 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2086 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2089 M_ARGV(4, float) = frag_damage;
2090 M_ARGV(6, vector) = frag_force;
2092 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2094 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)))
2095 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2097 frag_target.wps_helpme_time = time;
2098 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2100 // todo: add notification for when flag carrier needs help?
2104 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2106 entity frag_attacker = M_ARGV(1, entity);
2107 entity frag_target = M_ARGV(2, entity);
2109 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2111 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2112 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2115 if(frag_target.flagcarried)
2117 entity tmp_entity = frag_target.flagcarried;
2118 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2119 tmp_entity.ctf_dropper = NULL;
2123 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2125 M_ARGV(2, float) = 0; // frag score
2126 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2129 void ctf_RemovePlayer(entity player)
2131 if(player.flagcarried)
2132 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2134 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2136 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2137 if(flag.pass_target == player) { flag.pass_target = NULL; }
2138 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2142 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2144 entity player = M_ARGV(0, entity);
2146 ctf_RemovePlayer(player);
2149 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2151 entity player = M_ARGV(0, entity);
2153 ctf_RemovePlayer(player);
2156 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2158 entity player = M_ARGV(0, entity);
2160 if(player.flagcarried)
2161 if(!autocvar_g_ctf_portalteleport)
2162 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2165 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2167 if(MUTATOR_RETURNVALUE || gameover) { return; }
2169 entity player = M_ARGV(0, entity);
2171 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2173 // pass the flag to a team mate
2174 if(autocvar_g_ctf_pass)
2176 entity head, closest_target = NULL;
2177 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2179 while(head) // find the closest acceptable target to pass to
2181 if(IS_PLAYER(head) && !IS_DEAD(head))
2182 if(head != player && SAME_TEAM(head, player))
2183 if(!head.speedrunning && !head.vehicle)
2185 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2186 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2187 vector passer_center = CENTER_OR_VIEWOFS(player);
2189 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2191 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2193 if(IS_BOT_CLIENT(head))
2195 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2196 ctf_Handle_Throw(head, player, DROP_PASS);
2200 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2201 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2203 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2206 else if(player.flagcarried)
2210 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2211 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2212 { closest_target = head; }
2214 else { closest_target = head; }
2221 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2224 // throw the flag in front of you
2225 if(autocvar_g_ctf_throw && player.flagcarried)
2227 if(player.throw_count == -1)
2229 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2231 player.throw_prevtime = time;
2232 player.throw_count = 1;
2233 ctf_Handle_Throw(player, NULL, DROP_THROW);
2238 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2244 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2245 else { player.throw_count += 1; }
2246 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2248 player.throw_prevtime = time;
2249 ctf_Handle_Throw(player, NULL, DROP_THROW);
2256 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2258 entity player = M_ARGV(0, entity);
2260 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2262 player.wps_helpme_time = time;
2263 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2265 else // create a normal help me waypointsprite
2267 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2268 WaypointSprite_Ping(player.wps_helpme);
2274 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2276 entity player = M_ARGV(0, entity);
2277 entity veh = M_ARGV(1, entity);
2279 if(player.flagcarried)
2281 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2283 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2287 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2288 setattachment(player.flagcarried, veh, "");
2289 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2290 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2291 //player.flagcarried.angles = '0 0 0';
2297 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2299 entity player = M_ARGV(0, entity);
2301 if(player.flagcarried)
2303 setattachment(player.flagcarried, player, "");
2304 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2305 player.flagcarried.scale = FLAG_SCALE;
2306 player.flagcarried.angles = '0 0 0';
2307 player.flagcarried.nodrawtoclient = NULL;
2312 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2314 entity player = M_ARGV(0, entity);
2316 if(player.flagcarried)
2318 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2319 ctf_RespawnFlag(player.flagcarried);
2324 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2326 entity flag; // temporary entity for the search method
2328 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2330 switch(flag.ctf_status)
2335 // lock the flag, game is over
2336 set_movetype(flag, MOVETYPE_NONE);
2337 flag.takedamage = DAMAGE_NO;
2338 flag.solid = SOLID_NOT;
2339 flag.nextthink = false; // stop thinking
2341 //dprint("stopping the ", flag.netname, " from moving.\n");
2349 // do nothing for these flags
2356 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2358 entity bot = M_ARGV(0, entity);
2360 havocbot_ctf_reset_role(bot);
2364 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2366 //M_ARGV(0, float) = ctf_teams;
2367 M_ARGV(1, string) = "ctf_team";
2371 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2373 entity spectatee = M_ARGV(0, entity);
2374 entity client = M_ARGV(1, entity);
2376 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2379 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2381 int record_page = M_ARGV(0, int);
2382 string ret_string = M_ARGV(1, string);
2384 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2386 if (MapInfo_Get_ByID(i))
2388 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2394 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2395 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2399 M_ARGV(1, string) = ret_string;
2402 bool superspec_Spectate(entity this, entity targ); // TODO
2403 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2404 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2406 entity player = M_ARGV(0, entity);
2407 string cmd_name = M_ARGV(1, string);
2408 int cmd_argc = M_ARGV(2, int);
2410 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2412 if(cmd_name == "followfc")
2424 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2425 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2426 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2427 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2431 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2432 if(it.flagcarried && (it.team == _team || _team == 0))
2435 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2436 continue; // already spectating this fc, try another
2437 return superspec_Spectate(player, it);
2442 superspec_msg("", "", player, "No active flag carrier\n", 1);
2447 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2449 entity frag_target = M_ARGV(0, entity);
2451 if(frag_target.flagcarried)
2452 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2460 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2461 CTF flag for team one (Red).
2463 "angle" Angle the flag will point (minus 90 degrees)...
2464 "model" model to use, note this needs red and blue as skins 0 and 1...
2465 "noise" sound played when flag is picked up...
2466 "noise1" sound played when flag is returned by a teammate...
2467 "noise2" sound played when flag is captured...
2468 "noise3" sound played when flag is lost in the field and respawns itself...
2469 "noise4" sound played when flag is dropped by a player...
2470 "noise5" sound played when flag touches the ground... */
2471 spawnfunc(item_flag_team1)
2473 if(!g_ctf) { delete(this); return; }
2475 ctf_FlagSetup(NUM_TEAM_1, this);
2478 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2479 CTF flag for team two (Blue).
2481 "angle" Angle the flag will point (minus 90 degrees)...
2482 "model" model to use, note this needs red and blue as skins 0 and 1...
2483 "noise" sound played when flag is picked up...
2484 "noise1" sound played when flag is returned by a teammate...
2485 "noise2" sound played when flag is captured...
2486 "noise3" sound played when flag is lost in the field and respawns itself...
2487 "noise4" sound played when flag is dropped by a player...
2488 "noise5" sound played when flag touches the ground... */
2489 spawnfunc(item_flag_team2)
2491 if(!g_ctf) { delete(this); return; }
2493 ctf_FlagSetup(NUM_TEAM_2, this);
2496 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2497 CTF flag for team three (Yellow).
2499 "angle" Angle the flag will point (minus 90 degrees)...
2500 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2501 "noise" sound played when flag is picked up...
2502 "noise1" sound played when flag is returned by a teammate...
2503 "noise2" sound played when flag is captured...
2504 "noise3" sound played when flag is lost in the field and respawns itself...
2505 "noise4" sound played when flag is dropped by a player...
2506 "noise5" sound played when flag touches the ground... */
2507 spawnfunc(item_flag_team3)
2509 if(!g_ctf) { delete(this); return; }
2511 ctf_FlagSetup(NUM_TEAM_3, this);
2514 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2515 CTF flag for team four (Pink).
2517 "angle" Angle the flag will point (minus 90 degrees)...
2518 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2519 "noise" sound played when flag is picked up...
2520 "noise1" sound played when flag is returned by a teammate...
2521 "noise2" sound played when flag is captured...
2522 "noise3" sound played when flag is lost in the field and respawns itself...
2523 "noise4" sound played when flag is dropped by a player...
2524 "noise5" sound played when flag touches the ground... */
2525 spawnfunc(item_flag_team4)
2527 if(!g_ctf) { delete(this); return; }
2529 ctf_FlagSetup(NUM_TEAM_4, this);
2532 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2535 "angle" Angle the flag will point (minus 90 degrees)...
2536 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2537 "noise" sound played when flag is picked up...
2538 "noise1" sound played when flag is returned by a teammate...
2539 "noise2" sound played when flag is captured...
2540 "noise3" sound played when flag is lost in the field and respawns itself...
2541 "noise4" sound played when flag is dropped by a player...
2542 "noise5" sound played when flag touches the ground... */
2543 spawnfunc(item_flag_neutral)
2545 if(!g_ctf) { delete(this); return; }
2546 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2548 ctf_FlagSetup(0, this);
2551 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2552 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2553 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.
2555 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2556 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2559 if(!g_ctf) { delete(this); return; }
2561 this.classname = "ctf_team";
2562 this.team = this.cnt + 1;
2565 // compatibility for quake maps
2566 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2567 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2568 spawnfunc(info_player_team1);
2569 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2570 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2571 spawnfunc(info_player_team2);
2572 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2573 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2575 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2576 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2584 void ctf_ScoreRules(int teams)
2586 CheckAllowedTeams(NULL);
2587 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2588 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2589 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2590 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2591 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2592 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2593 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2594 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2595 ScoreRules_basics_end();
2598 // code from here on is just to support maps that don't have flag and team entities
2599 void ctf_SpawnTeam (string teamname, int teamcolor)
2601 entity this = new_pure(ctf_team);
2602 this.netname = teamname;
2603 this.cnt = teamcolor - 1;
2604 this.spawnfunc_checked = true;
2605 this.team = teamcolor;
2608 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2613 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2615 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2616 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2618 switch(tmp_entity.team)
2620 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2621 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2622 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2623 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2625 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2628 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2630 ctf_teams = 0; // so set the default red and blue teams
2631 BITSET_ASSIGN(ctf_teams, BIT(0));
2632 BITSET_ASSIGN(ctf_teams, BIT(1));
2635 //ctf_teams = bound(2, ctf_teams, 4);
2637 // if no teams are found, spawn defaults
2638 if(find(NULL, classname, "ctf_team") == NULL)
2640 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2641 if(ctf_teams & BIT(0))
2642 ctf_SpawnTeam("Red", NUM_TEAM_1);
2643 if(ctf_teams & BIT(1))
2644 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2645 if(ctf_teams & BIT(2))
2646 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2647 if(ctf_teams & BIT(3))
2648 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2651 ctf_ScoreRules(ctf_teams);
2654 void ctf_Initialize()
2656 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2658 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2659 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2660 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2662 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);