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"));
138 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
139 else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_CAPTURE_TIME), player.netname, (cap_time * 100)); }
140 else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
141 else { Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
143 // write that shit in the database
144 if(!ctf_oneflag) // but not in 1-flag mode
145 if((!ctf_captimerecord) || (cap_time < cap_record))
147 ctf_captimerecord = cap_time;
148 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
149 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
150 write_recordmarker(player, (time - cap_time), cap_time);
154 bool ctf_Return_Customize(entity this, entity client)
156 // only to the carrier
157 return boolean(client == this.owner);
160 void ctf_FlagcarrierWaypoints(entity player)
162 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
163 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
164 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
165 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
167 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
169 if(!player.wps_enemyflagcarrier)
171 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
172 wp.colormod = WPCOLOR_ENEMYFC(player.team);
173 setcefc(wp, ctf_Stalemate_Customize);
175 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
176 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
179 if(!player.wps_flagreturn)
181 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
182 owp.colormod = '0 0.8 0.8';
183 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
184 setcefc(owp, ctf_Return_Customize);
189 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
191 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
192 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
193 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
194 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
197 if(current_height) // make sure we can actually do this arcing path
199 targpos = (to + ('0 0 1' * current_height));
200 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
201 if(trace_fraction < 1)
203 //print("normal arc line failed, trying to find new pos...");
204 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
205 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
206 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
207 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
208 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
211 else { targpos = to; }
213 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
215 vector desired_direction = normalize(targpos - from);
216 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
217 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
220 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
222 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
224 // directional tracing only
226 makevectors(passer_angle);
228 // find the closest point on the enemy to the center of the attack
229 float h; // hypotenuse, which is the distance between attacker to head
230 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
232 h = vlen(head_center - passer_center);
233 a = h * (normalize(head_center - passer_center) * v_forward);
235 vector nearest_on_line = (passer_center + a * v_forward);
236 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
238 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
239 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
241 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
246 else { return true; }
250 // =======================
251 // CaptureShield Functions
252 // =======================
254 bool ctf_CaptureShield_CheckStatus(entity p)
256 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
257 int players_worseeq, players_total;
259 if(ctf_captureshield_max_ratio <= 0)
262 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
263 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
264 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
265 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
267 sr = ((s - s2) + (s3 + s4));
269 if(sr >= -ctf_captureshield_min_negscore)
272 players_total = players_worseeq = 0;
273 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
276 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
277 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
278 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
279 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
281 ser = ((se - se2) + (se3 + se4));
288 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
289 // use this rule here
291 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
297 void ctf_CaptureShield_Update(entity player, bool wanted_status)
299 bool updated_status = ctf_CaptureShield_CheckStatus(player);
300 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
302 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
303 player.ctf_captureshielded = updated_status;
307 bool ctf_CaptureShield_Customize(entity this, entity client)
309 if(!client.ctf_captureshielded) { return false; }
310 if(CTF_SAMETEAM(this, client)) { return false; }
315 void ctf_CaptureShield_Touch(entity this, entity toucher)
317 if(!toucher.ctf_captureshielded) { return; }
318 if(CTF_SAMETEAM(this, toucher)) { return; }
320 vector mymid = (this.absmin + this.absmax) * 0.5;
321 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
323 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
324 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
327 void ctf_CaptureShield_Spawn(entity flag)
329 entity shield = new(ctf_captureshield);
332 shield.team = flag.team;
333 settouch(shield, ctf_CaptureShield_Touch);
334 setcefc(shield, ctf_CaptureShield_Customize);
335 shield.effects = EF_ADDITIVE;
336 set_movetype(shield, MOVETYPE_NOCLIP);
337 shield.solid = SOLID_TRIGGER;
338 shield.avelocity = '7 0 11';
341 setorigin(shield, flag.origin);
342 setmodel(shield, MDL_CTF_SHIELD);
343 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
347 // ====================
348 // Drop/Pass/Throw Code
349 // ====================
351 void ctf_Handle_Drop(entity flag, entity player, int droptype)
354 player = (player ? player : flag.pass_sender);
357 set_movetype(flag, MOVETYPE_TOSS);
358 flag.takedamage = DAMAGE_YES;
359 flag.angles = '0 0 0';
360 flag.health = flag.max_flag_health;
361 flag.ctf_droptime = time;
362 flag.ctf_dropper = player;
363 flag.ctf_status = FLAG_DROPPED;
365 // messages and sounds
366 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_LOST) : INFO_CTF_LOST_NEUTRAL), player.netname);
367 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
368 ctf_EventLog("dropped", player.team, player);
371 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
372 PlayerScore_Add(player, SP_CTF_DROPS, 1);
375 if(autocvar_g_ctf_flag_dropped_waypoint) {
376 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);
377 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
380 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
382 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
383 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
386 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
388 if(droptype == DROP_PASS)
390 flag.pass_distance = 0;
391 flag.pass_sender = NULL;
392 flag.pass_target = NULL;
396 void ctf_Handle_Retrieve(entity flag, entity player)
398 entity sender = flag.pass_sender;
400 // transfer flag to player
402 flag.owner.flagcarried = flag;
407 setattachment(flag, player.vehicle, "");
408 setorigin(flag, VEHICLE_FLAG_OFFSET);
409 flag.scale = VEHICLE_FLAG_SCALE;
413 setattachment(flag, player, "");
414 setorigin(flag, FLAG_CARRY_OFFSET);
416 set_movetype(flag, MOVETYPE_NONE);
417 flag.takedamage = DAMAGE_NO;
418 flag.solid = SOLID_NOT;
419 flag.angles = '0 0 0';
420 flag.ctf_status = FLAG_CARRY;
422 // messages and sounds
423 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
424 ctf_EventLog("receive", flag.team, player);
426 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
428 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_SENT) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
429 else if(it == player)
430 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_RECEIVED) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
431 else if(SAME_TEAM(it, sender))
432 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_OTHER) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
435 // create new waypoint
436 ctf_FlagcarrierWaypoints(player);
438 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
439 player.throw_antispam = sender.throw_antispam;
441 flag.pass_distance = 0;
442 flag.pass_sender = NULL;
443 flag.pass_target = NULL;
446 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
448 entity flag = player.flagcarried;
449 vector targ_origin, flag_velocity;
451 if(!flag) { return; }
452 if((droptype == DROP_PASS) && !receiver) { return; }
454 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
457 setattachment(flag, NULL, "");
458 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
459 flag.owner.flagcarried = NULL;
461 flag.solid = SOLID_TRIGGER;
462 flag.ctf_dropper = player;
463 flag.ctf_droptime = time;
465 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
472 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
473 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
474 WarpZone_RefSys_Copy(flag, receiver);
475 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
476 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
478 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
479 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
482 set_movetype(flag, MOVETYPE_FLY);
483 flag.takedamage = DAMAGE_NO;
484 flag.pass_sender = player;
485 flag.pass_target = receiver;
486 flag.ctf_status = FLAG_PASSING;
489 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
490 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
491 ctf_EventLog("pass", flag.team, player);
497 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'));
499 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)));
500 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
501 ctf_Handle_Drop(flag, player, droptype);
507 flag.velocity = '0 0 0'; // do nothing
514 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);
515 ctf_Handle_Drop(flag, player, droptype);
520 // kill old waypointsprite
521 WaypointSprite_Ping(player.wps_flagcarrier);
522 WaypointSprite_Kill(player.wps_flagcarrier);
524 if(player.wps_enemyflagcarrier)
525 WaypointSprite_Kill(player.wps_enemyflagcarrier);
527 if(player.wps_flagreturn)
528 WaypointSprite_Kill(player.wps_flagreturn);
531 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
534 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
536 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
543 void nades_GiveBonus(entity player, float score);
545 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
547 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
548 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
549 entity player_team_flag = NULL, tmp_entity;
550 float old_time, new_time;
552 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
553 if(CTF_DIFFTEAM(player, flag)) { return; }
556 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
557 if(SAME_TEAM(tmp_entity, player))
559 player_team_flag = tmp_entity;
563 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
565 player.throw_prevtime = time;
566 player.throw_count = 0;
568 // messages and sounds
569 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT(enemy_flag, CENTER_CTF_CAPTURE) : CENTER_CTF_CAPTURE_NEUTRAL));
570 ctf_CaptureRecord(enemy_flag, player);
571 _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);
575 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
576 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
581 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
582 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
584 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
585 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
586 if(!old_time || new_time < old_time)
587 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
590 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
591 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
594 if(capturetype == CAPTURE_NORMAL)
596 WaypointSprite_Kill(player.wps_flagcarrier);
597 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
599 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
600 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
604 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
605 ctf_RespawnFlag(enemy_flag);
608 void ctf_Handle_Return(entity flag, entity player)
610 // messages and sounds
611 if(IS_MONSTER(player))
613 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_ENT(flag, INFO_CTF_RETURN_MONSTER), player.monster_name);
617 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT(flag, CENTER_CTF_RETURN));
618 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_ENT(flag, INFO_CTF_RETURN), player.netname);
620 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
621 ctf_EventLog("return", flag.team, player);
624 if(IS_PLAYER(player))
626 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
627 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
629 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
632 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
636 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
637 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
638 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
642 if(player.flagcarried == flag)
643 WaypointSprite_Kill(player.wps_flagcarrier);
646 ctf_RespawnFlag(flag);
649 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
652 float pickup_dropped_score; // used to calculate dropped pickup score
654 // attach the flag to the player
656 player.flagcarried = flag;
659 setattachment(flag, player.vehicle, "");
660 setorigin(flag, VEHICLE_FLAG_OFFSET);
661 flag.scale = VEHICLE_FLAG_SCALE;
665 setattachment(flag, player, "");
666 setorigin(flag, FLAG_CARRY_OFFSET);
670 set_movetype(flag, MOVETYPE_NONE);
671 flag.takedamage = DAMAGE_NO;
672 flag.solid = SOLID_NOT;
673 flag.angles = '0 0 0';
674 flag.ctf_status = FLAG_CARRY;
678 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
679 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
683 // messages and sounds
684 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_PICKUP) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
685 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
686 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
687 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT(flag, CENTER_CTF_PICKUP)); }
688 else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); }
690 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT(flag, CHOICE_CTF_PICKUP_TEAM) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
693 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)));
696 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
697 if(CTF_SAMETEAM(flag, it))
698 if(SAME_TEAM(player, it))
699 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
701 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);
704 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
707 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
708 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
713 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
714 ctf_EventLog("steal", flag.team, player);
720 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);
721 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);
722 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
723 PlayerTeamScore_AddScore(player, pickup_dropped_score);
724 ctf_EventLog("pickup", flag.team, player);
732 if(pickuptype == PICKUP_BASE)
734 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
735 if((player.speedrunning) && (ctf_captimerecord))
736 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
740 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
743 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
744 ctf_FlagcarrierWaypoints(player);
745 WaypointSprite_Ping(player.wps_flagcarrier);
749 // ===================
750 // Main Flag Functions
751 // ===================
753 void ctf_CheckFlagReturn(entity flag, int returntype)
755 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
757 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
759 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
763 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_DROPPED) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
764 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_DAMAGED) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
765 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_SPEEDRUN) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
766 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_NEEDKILL) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
770 { Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_TIMEOUT) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
772 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
773 ctf_EventLog("returned", flag.team, NULL);
774 ctf_RespawnFlag(flag);
779 bool ctf_Stalemate_Customize(entity this, entity client)
781 // make spectators see what the player would see
782 entity e = WaypointSprite_getviewentity(client);
783 entity wp_owner = this.owner;
786 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
787 if(SAME_TEAM(wp_owner, e)) { return false; }
788 if(!IS_PLAYER(e)) { return false; }
793 void ctf_CheckStalemate()
796 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
799 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
801 // build list of stale flags
802 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
804 if(autocvar_g_ctf_stalemate)
805 if(tmp_entity.ctf_status != FLAG_BASE)
806 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
808 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
809 ctf_staleflaglist = tmp_entity;
811 switch(tmp_entity.team)
813 case NUM_TEAM_1: ++stale_red_flags; break;
814 case NUM_TEAM_2: ++stale_blue_flags; break;
815 case NUM_TEAM_3: ++stale_yellow_flags; break;
816 case NUM_TEAM_4: ++stale_pink_flags; break;
817 default: ++stale_neutral_flags; break;
823 stale_flags = (stale_neutral_flags >= 1);
825 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
827 if(ctf_oneflag && stale_flags == 1)
828 ctf_stalemate = true;
829 else if(stale_flags >= 2)
830 ctf_stalemate = true;
831 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
832 { ctf_stalemate = false; wpforenemy_announced = false; }
833 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
834 { ctf_stalemate = false; wpforenemy_announced = false; }
836 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
839 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
841 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
843 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);
844 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
845 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
849 if (!wpforenemy_announced)
851 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))));
853 wpforenemy_announced = true;
858 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
860 if(ITEM_DAMAGE_NEEDKILL(deathtype))
862 if(autocvar_g_ctf_flag_return_damage_delay)
864 this.ctf_flagdamaged = true;
869 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
873 if(autocvar_g_ctf_flag_return_damage)
875 // reduce health and check if it should be returned
876 this.health = this.health - damage;
877 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
882 void ctf_FlagThink(entity this)
887 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
890 if(this == ctf_worldflaglist) // only for the first flag
891 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
894 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
895 LOG_TRACE("wtf the flag got squashed?");
896 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
897 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
898 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs); }
900 switch(this.ctf_status) // reset flag angles in case warpzones adjust it
904 this.angles = '0 0 0';
912 switch(this.ctf_status)
916 if(autocvar_g_ctf_dropped_capture_radius)
918 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
919 if(tmp_entity.ctf_status == FLAG_DROPPED)
920 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
921 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
922 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
929 if(autocvar_g_ctf_flag_dropped_floatinwater)
931 vector midpoint = ((this.absmin + this.absmax) * 0.5);
932 if(pointcontents(midpoint) == CONTENT_WATER)
934 this.velocity = this.velocity * 0.5;
936 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
937 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
939 { set_movetype(this, MOVETYPE_FLY); }
941 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
943 if(autocvar_g_ctf_flag_return_dropped)
945 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
948 ctf_CheckFlagReturn(this, RETURN_DROPPED);
952 if(this.ctf_flagdamaged)
954 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
955 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
958 else if(autocvar_g_ctf_flag_return_time)
960 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
961 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
969 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
972 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
974 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
975 ImpulseCommands(this.owner);
977 if(autocvar_g_ctf_stalemate)
979 if(time >= wpforenemy_nextthink)
981 ctf_CheckStalemate();
982 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
985 if(CTF_SAMETEAM(this, this.owner) && this.team)
987 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
988 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
989 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
990 ctf_Handle_Return(this, this.owner);
997 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
998 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
999 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1001 if((this.pass_target == NULL)
1002 || (IS_DEAD(this.pass_target))
1003 || (this.pass_target.flagcarried)
1004 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1005 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1006 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1008 // give up, pass failed
1009 ctf_Handle_Drop(this, NULL, DROP_PASS);
1013 // still a viable target, go for it
1014 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1019 default: // this should never happen
1021 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1027 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1030 if(gameover) { return; }
1031 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1033 bool is_not_monster = (!IS_MONSTER(toucher));
1035 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1036 if(ITEM_TOUCH_NEEDKILL())
1038 if(!autocvar_g_ctf_flag_return_damage_delay)
1041 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1043 if(!flag.ctf_flagdamaged) { return; }
1046 int num_perteam = 0;
1047 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), LAMBDA(++num_perteam));
1049 // special touch behaviors
1050 if(STAT(FROZEN, toucher)) { return; }
1051 else if(IS_VEHICLE(toucher))
1053 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1054 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1056 return; // do nothing
1058 else if(IS_MONSTER(toucher))
1060 if(!autocvar_g_ctf_allow_monster_touch)
1061 return; // do nothing
1063 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1065 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1067 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1068 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1069 flag.wait = time + FLAG_TOUCHRATE;
1073 else if(IS_DEAD(toucher)) { return; }
1075 switch(flag.ctf_status)
1081 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1082 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1083 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1084 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1086 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1087 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1088 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)
1090 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1091 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1093 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1094 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1100 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
1101 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1102 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1103 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1109 LOG_TRACE("Someone touched a flag even though it was being carried?");
1115 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1117 if(DIFF_TEAM(toucher, flag.pass_sender))
1118 ctf_Handle_Return(flag, toucher);
1120 ctf_Handle_Retrieve(flag, toucher);
1127 .float last_respawn;
1128 void ctf_RespawnFlag(entity flag)
1130 // check for flag respawn being called twice in a row
1131 if(flag.last_respawn > time - 0.5)
1132 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1134 flag.last_respawn = time;
1136 // reset the player (if there is one)
1137 if((flag.owner) && (flag.owner.flagcarried == flag))
1139 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1140 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1141 WaypointSprite_Kill(flag.wps_flagcarrier);
1143 flag.owner.flagcarried = NULL;
1145 if(flag.speedrunning)
1146 ctf_FakeTimeLimit(flag.owner, -1);
1149 if((flag.owner) && (flag.owner.vehicle))
1150 flag.scale = FLAG_SCALE;
1152 if(flag.ctf_status == FLAG_DROPPED)
1153 { WaypointSprite_Kill(flag.wps_flagdropped); }
1156 setattachment(flag, NULL, "");
1157 setorigin(flag, flag.ctf_spawnorigin);
1159 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1160 flag.takedamage = DAMAGE_NO;
1161 flag.health = flag.max_flag_health;
1162 flag.solid = SOLID_TRIGGER;
1163 flag.velocity = '0 0 0';
1164 flag.angles = flag.mangle;
1165 flag.flags = FL_ITEM | FL_NOTARGET;
1167 flag.ctf_status = FLAG_BASE;
1169 flag.pass_distance = 0;
1170 flag.pass_sender = NULL;
1171 flag.pass_target = NULL;
1172 flag.ctf_dropper = NULL;
1173 flag.ctf_pickuptime = 0;
1174 flag.ctf_droptime = 0;
1175 flag.ctf_flagdamaged = 0;
1177 ctf_CheckStalemate();
1180 void ctf_Reset(entity this)
1182 if(this.owner && IS_PLAYER(this.owner))
1183 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1185 ctf_RespawnFlag(this);
1188 bool ctf_FlagBase_Customize(entity this, entity client)
1190 if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
1195 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1198 waypoint_spawnforitem_force(this, this.origin);
1199 this.nearestwaypointtimeout = 0; // activate waypointing again
1200 this.bot_basewaypoint = this.nearestwaypoint;
1206 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1207 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1208 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1209 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1210 default: basename = WP_FlagBaseNeutral; break;
1213 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1214 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1215 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1216 setcefc(wp, ctf_FlagBase_Customize);
1218 // captureshield setup
1219 ctf_CaptureShield_Spawn(this);
1224 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1227 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1228 ctf_worldflaglist = flag;
1230 setattachment(flag, NULL, "");
1232 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1233 flag.team = teamnumber;
1234 flag.classname = "item_flag_team";
1235 flag.target = "###item###"; // wut?
1236 flag.flags = FL_ITEM | FL_NOTARGET;
1237 flag.solid = SOLID_TRIGGER;
1238 flag.takedamage = DAMAGE_NO;
1239 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1240 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1241 flag.health = flag.max_flag_health;
1242 flag.event_damage = ctf_FlagDamage;
1243 flag.pushable = true;
1244 flag.teleportable = TELEPORT_NORMAL;
1245 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1246 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1247 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1248 flag.velocity = '0 0 0';
1249 flag.mangle = flag.angles;
1250 flag.reset = ctf_Reset;
1251 settouch(flag, ctf_FlagTouch);
1252 setthink(flag, ctf_FlagThink);
1253 flag.nextthink = time + FLAG_THINKRATE;
1254 flag.ctf_status = FLAG_BASE;
1256 string teamname = Static_Team_ColorName_Lower(teamnumber);
1258 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1259 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1260 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1261 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1262 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1263 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1267 if(flag.s == "") flag.s = b; \
1268 precache_sound(flag.s);
1270 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1271 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1272 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1273 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1274 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1275 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1276 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1280 precache_model(flag.model);
1283 _setmodel(flag, flag.model); // precision set below
1284 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1285 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1287 if(autocvar_g_ctf_flag_glowtrails)
1291 case NUM_TEAM_1: flag.glow_color = 251; break;
1292 case NUM_TEAM_2: flag.glow_color = 210; break;
1293 case NUM_TEAM_3: flag.glow_color = 110; break;
1294 case NUM_TEAM_4: flag.glow_color = 145; break;
1295 default: flag.glow_color = 254; break;
1297 flag.glow_size = 25;
1298 flag.glow_trail = 1;
1301 flag.effects |= EF_LOWPRECISION;
1302 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1303 if(autocvar_g_ctf_dynamiclights)
1307 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1308 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1309 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1310 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1311 default: flag.effects |= EF_DIMLIGHT; break;
1316 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1318 flag.dropped_origin = flag.origin;
1319 flag.noalign = true;
1320 set_movetype(flag, MOVETYPE_NONE);
1322 else // drop to floor, automatically find a platform and set that as spawn origin
1324 flag.noalign = false;
1326 set_movetype(flag, MOVETYPE_NONE);
1329 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1337 // NOTE: LEGACY CODE, needs to be re-written!
1339 void havocbot_calculate_middlepoint()
1343 vector fo = '0 0 0';
1346 f = ctf_worldflaglist;
1351 f = f.ctf_worldflagnext;
1355 havocbot_ctf_middlepoint = s * (1.0 / n);
1356 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1360 entity havocbot_ctf_find_flag(entity bot)
1363 f = ctf_worldflaglist;
1366 if (CTF_SAMETEAM(bot, f))
1368 f = f.ctf_worldflagnext;
1373 entity havocbot_ctf_find_enemy_flag(entity bot)
1376 f = ctf_worldflaglist;
1381 if(CTF_DIFFTEAM(bot, f))
1388 else if(!bot.flagcarried)
1392 else if (CTF_DIFFTEAM(bot, f))
1394 f = f.ctf_worldflagnext;
1399 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1406 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1407 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1410 if(vdist(it.origin - org, <, tc_radius))
1417 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1420 head = ctf_worldflaglist;
1423 if (CTF_SAMETEAM(this, head))
1425 head = head.ctf_worldflagnext;
1428 navigation_routerating(this, head, ratingscale, 10000);
1431 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1434 head = ctf_worldflaglist;
1437 if (CTF_SAMETEAM(this, head))
1439 head = head.ctf_worldflagnext;
1444 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1447 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1450 head = ctf_worldflaglist;
1455 if(CTF_DIFFTEAM(this, head))
1459 if(this.flagcarried)
1462 else if(!this.flagcarried)
1466 else if(CTF_DIFFTEAM(this, head))
1468 head = head.ctf_worldflagnext;
1471 navigation_routerating(this, head, ratingscale, 10000);
1474 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1476 if (!bot_waypoints_for_items)
1478 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1484 head = havocbot_ctf_find_enemy_flag(this);
1489 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1492 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1496 mf = havocbot_ctf_find_flag(this);
1498 if(mf.ctf_status == FLAG_BASE)
1502 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1505 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1508 head = ctf_worldflaglist;
1511 // flag is out in the field
1512 if(head.ctf_status != FLAG_BASE)
1513 if(head.tag_entity==NULL) // dropped
1517 if(vdist(org - head.origin, <, df_radius))
1518 navigation_routerating(this, head, ratingscale, 10000);
1521 navigation_routerating(this, head, ratingscale, 10000);
1524 head = head.ctf_worldflagnext;
1528 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1530 FOREACH_ENTITY_FLOAT(bot_pickup, true,
1532 // gather health and armor only
1534 if (it.health || it.armorvalue)
1535 if (vdist(it.origin - org, <, sradius))
1537 // get the value of the item
1538 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1540 navigation_routerating(this, it, t * ratingscale, 500);
1545 void havocbot_ctf_reset_role(entity this)
1547 float cdefense, cmiddle, coffense;
1554 if(havocbot_ctf_middlepoint == '0 0 0')
1555 havocbot_calculate_middlepoint();
1558 if (this.flagcarried)
1560 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1564 mf = havocbot_ctf_find_flag(this);
1565 ef = havocbot_ctf_find_enemy_flag(this);
1567 // Retrieve stolen flag
1568 if(mf.ctf_status!=FLAG_BASE)
1570 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1574 // If enemy flag is taken go to the middle to intercept pursuers
1575 if(ef.ctf_status!=FLAG_BASE)
1577 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1581 // if there is only me on the team switch to offense
1583 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1587 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1591 // Evaluate best position to take
1592 // Count mates on middle position
1593 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1595 // Count mates on defense position
1596 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1598 // Count mates on offense position
1599 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1601 if(cdefense<=coffense)
1602 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1603 else if(coffense<=cmiddle)
1604 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1606 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1609 void havocbot_role_ctf_carrier(entity this)
1613 havocbot_ctf_reset_role(this);
1617 if (this.flagcarried == NULL)
1619 havocbot_ctf_reset_role(this);
1623 if (this.bot_strategytime < time)
1625 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1627 navigation_goalrating_start(this);
1629 havocbot_goalrating_ctf_enemybase(this, 50000);
1631 havocbot_goalrating_ctf_ourbase(this, 50000);
1634 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1636 navigation_goalrating_end(this);
1638 if (this.navigation_hasgoals)
1639 this.havocbot_cantfindflag = time + 10;
1640 else if (time > this.havocbot_cantfindflag)
1642 // Can't navigate to my own base, suicide!
1643 // TODO: drop it and wander around
1644 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1650 void havocbot_role_ctf_escort(entity this)
1656 havocbot_ctf_reset_role(this);
1660 if (this.flagcarried)
1662 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1666 // If enemy flag is back on the base switch to previous role
1667 ef = havocbot_ctf_find_enemy_flag(this);
1668 if(ef.ctf_status==FLAG_BASE)
1670 this.havocbot_role = this.havocbot_previous_role;
1671 this.havocbot_role_timeout = 0;
1675 // If the flag carrier reached the base switch to defense
1676 mf = havocbot_ctf_find_flag(this);
1677 if(mf.ctf_status!=FLAG_BASE)
1678 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1680 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1684 // Set the role timeout if necessary
1685 if (!this.havocbot_role_timeout)
1687 this.havocbot_role_timeout = time + random() * 30 + 60;
1690 // If nothing happened just switch to previous role
1691 if (time > this.havocbot_role_timeout)
1693 this.havocbot_role = this.havocbot_previous_role;
1694 this.havocbot_role_timeout = 0;
1698 // Chase the flag carrier
1699 if (this.bot_strategytime < time)
1701 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1702 navigation_goalrating_start(this);
1703 havocbot_goalrating_ctf_enemyflag(this, 30000);
1704 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1705 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1706 navigation_goalrating_end(this);
1710 void havocbot_role_ctf_offense(entity this)
1717 havocbot_ctf_reset_role(this);
1721 if (this.flagcarried)
1723 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1728 mf = havocbot_ctf_find_flag(this);
1729 ef = havocbot_ctf_find_enemy_flag(this);
1732 if(mf.ctf_status!=FLAG_BASE)
1735 pos = mf.tag_entity.origin;
1739 // Try to get it if closer than the enemy base
1740 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1742 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1747 // Escort flag carrier
1748 if(ef.ctf_status!=FLAG_BASE)
1751 pos = ef.tag_entity.origin;
1755 if(vdist(pos - mf.dropped_origin, >, 700))
1757 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1762 // About to fail, switch to middlefield
1765 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1769 // Set the role timeout if necessary
1770 if (!this.havocbot_role_timeout)
1771 this.havocbot_role_timeout = time + 120;
1773 if (time > this.havocbot_role_timeout)
1775 havocbot_ctf_reset_role(this);
1779 if (this.bot_strategytime < time)
1781 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1782 navigation_goalrating_start(this);
1783 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1784 havocbot_goalrating_ctf_enemybase(this, 20000);
1785 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1786 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1787 navigation_goalrating_end(this);
1791 // Retriever (temporary role):
1792 void havocbot_role_ctf_retriever(entity this)
1798 havocbot_ctf_reset_role(this);
1802 if (this.flagcarried)
1804 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1808 // If flag is back on the base switch to previous role
1809 mf = havocbot_ctf_find_flag(this);
1810 if(mf.ctf_status==FLAG_BASE)
1812 havocbot_ctf_reset_role(this);
1816 if (!this.havocbot_role_timeout)
1817 this.havocbot_role_timeout = time + 20;
1819 if (time > this.havocbot_role_timeout)
1821 havocbot_ctf_reset_role(this);
1825 if (this.bot_strategytime < time)
1830 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1831 navigation_goalrating_start(this);
1832 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1833 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1834 havocbot_goalrating_ctf_enemybase(this, 30000);
1835 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1836 navigation_goalrating_end(this);
1840 void havocbot_role_ctf_middle(entity this)
1846 havocbot_ctf_reset_role(this);
1850 if (this.flagcarried)
1852 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1856 mf = havocbot_ctf_find_flag(this);
1857 if(mf.ctf_status!=FLAG_BASE)
1859 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1863 if (!this.havocbot_role_timeout)
1864 this.havocbot_role_timeout = time + 10;
1866 if (time > this.havocbot_role_timeout)
1868 havocbot_ctf_reset_role(this);
1872 if (this.bot_strategytime < time)
1876 org = havocbot_ctf_middlepoint;
1877 org.z = this.origin.z;
1879 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1880 navigation_goalrating_start(this);
1881 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1882 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1883 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1884 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1885 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1886 havocbot_goalrating_ctf_enemybase(this, 2500);
1887 navigation_goalrating_end(this);
1891 void havocbot_role_ctf_defense(entity this)
1897 havocbot_ctf_reset_role(this);
1901 if (this.flagcarried)
1903 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1907 // If own flag was captured
1908 mf = havocbot_ctf_find_flag(this);
1909 if(mf.ctf_status!=FLAG_BASE)
1911 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1915 if (!this.havocbot_role_timeout)
1916 this.havocbot_role_timeout = time + 30;
1918 if (time > this.havocbot_role_timeout)
1920 havocbot_ctf_reset_role(this);
1923 if (this.bot_strategytime < time)
1928 org = mf.dropped_origin;
1929 mp_radius = havocbot_ctf_middlepoint_radius;
1931 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1932 navigation_goalrating_start(this);
1934 // if enemies are closer to our base, go there
1935 entity closestplayer = NULL;
1936 float distance, bestdistance = 10000;
1937 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1938 distance = vlen(org - it.origin);
1939 if(distance<bestdistance)
1942 bestdistance = distance;
1947 if(DIFF_TEAM(closestplayer, this))
1948 if(vdist(org - this.origin, >, 1000))
1949 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1950 havocbot_goalrating_ctf_ourbase(this, 30000);
1952 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1953 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1954 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1955 havocbot_goalrating_items(this, 10000, org, mp_radius);
1956 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1957 navigation_goalrating_end(this);
1961 void havocbot_role_ctf_setrole(entity bot, int role)
1963 string s = "(null)";
1966 case HAVOCBOT_CTF_ROLE_CARRIER:
1968 bot.havocbot_role = havocbot_role_ctf_carrier;
1969 bot.havocbot_role_timeout = 0;
1970 bot.havocbot_cantfindflag = time + 10;
1971 bot.bot_strategytime = 0;
1973 case HAVOCBOT_CTF_ROLE_DEFENSE:
1975 bot.havocbot_role = havocbot_role_ctf_defense;
1976 bot.havocbot_role_timeout = 0;
1978 case HAVOCBOT_CTF_ROLE_MIDDLE:
1980 bot.havocbot_role = havocbot_role_ctf_middle;
1981 bot.havocbot_role_timeout = 0;
1983 case HAVOCBOT_CTF_ROLE_OFFENSE:
1985 bot.havocbot_role = havocbot_role_ctf_offense;
1986 bot.havocbot_role_timeout = 0;
1988 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1990 bot.havocbot_previous_role = bot.havocbot_role;
1991 bot.havocbot_role = havocbot_role_ctf_retriever;
1992 bot.havocbot_role_timeout = time + 10;
1993 bot.bot_strategytime = 0;
1995 case HAVOCBOT_CTF_ROLE_ESCORT:
1997 bot.havocbot_previous_role = bot.havocbot_role;
1998 bot.havocbot_role = havocbot_role_ctf_escort;
1999 bot.havocbot_role_timeout = time + 30;
2000 bot.bot_strategytime = 0;
2003 LOG_TRACE(bot.netname, " switched to ", s);
2011 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2013 entity player = M_ARGV(0, entity);
2015 int t = 0, t2 = 0, t3 = 0;
2017 // initially clear items so they can be set as necessary later.
2018 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2019 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2020 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2021 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2022 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2023 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2025 // scan through all the flags and notify the client about them
2026 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2028 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2029 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2030 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2031 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2032 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; }
2034 switch(flag.ctf_status)
2039 if((flag.owner == player) || (flag.pass_sender == player))
2040 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2042 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2047 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2053 // item for stopping players from capturing the flag too often
2054 if(player.ctf_captureshielded)
2055 player.ctf_flagstatus |= CTF_SHIELDED;
2058 player.ctf_flagstatus |= CTF_STALEMATE;
2060 // update the health of the flag carrier waypointsprite
2061 if(player.wps_flagcarrier)
2062 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2065 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2067 entity frag_attacker = M_ARGV(1, entity);
2068 entity frag_target = M_ARGV(2, entity);
2069 float frag_damage = M_ARGV(4, float);
2070 vector frag_force = M_ARGV(6, vector);
2072 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2074 if(frag_target == frag_attacker) // damage done to yourself
2076 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2077 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2079 else // damage done to everyone else
2081 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2082 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2085 M_ARGV(4, float) = frag_damage;
2086 M_ARGV(6, vector) = frag_force;
2088 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2090 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)))
2091 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2093 frag_target.wps_helpme_time = time;
2094 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2096 // todo: add notification for when flag carrier needs help?
2100 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2102 entity frag_attacker = M_ARGV(1, entity);
2103 entity frag_target = M_ARGV(2, entity);
2105 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2107 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2108 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2111 if(frag_target.flagcarried)
2113 entity tmp_entity = frag_target.flagcarried;
2114 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2115 tmp_entity.ctf_dropper = NULL;
2119 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2121 M_ARGV(2, float) = 0; // frag score
2122 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2125 void ctf_RemovePlayer(entity player)
2127 if(player.flagcarried)
2128 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2130 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2132 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2133 if(flag.pass_target == player) { flag.pass_target = NULL; }
2134 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2138 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2140 entity player = M_ARGV(0, entity);
2142 ctf_RemovePlayer(player);
2145 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2147 entity player = M_ARGV(0, entity);
2149 ctf_RemovePlayer(player);
2152 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2154 entity player = M_ARGV(0, entity);
2156 if(player.flagcarried)
2157 if(!autocvar_g_ctf_portalteleport)
2158 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2161 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2163 if(MUTATOR_RETURNVALUE || gameover) { return; }
2165 entity player = M_ARGV(0, entity);
2167 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2169 // pass the flag to a team mate
2170 if(autocvar_g_ctf_pass)
2172 entity head, closest_target = NULL;
2173 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2175 while(head) // find the closest acceptable target to pass to
2177 if(IS_PLAYER(head) && !IS_DEAD(head))
2178 if(head != player && SAME_TEAM(head, player))
2179 if(!head.speedrunning && !head.vehicle)
2181 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2182 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2183 vector passer_center = CENTER_OR_VIEWOFS(player);
2185 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2187 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2189 if(IS_BOT_CLIENT(head))
2191 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2192 ctf_Handle_Throw(head, player, DROP_PASS);
2196 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2197 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2199 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2202 else if(player.flagcarried)
2206 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2207 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2208 { closest_target = head; }
2210 else { closest_target = head; }
2217 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2220 // throw the flag in front of you
2221 if(autocvar_g_ctf_throw && player.flagcarried)
2223 if(player.throw_count == -1)
2225 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2227 player.throw_prevtime = time;
2228 player.throw_count = 1;
2229 ctf_Handle_Throw(player, NULL, DROP_THROW);
2234 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2240 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2241 else { player.throw_count += 1; }
2242 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2244 player.throw_prevtime = time;
2245 ctf_Handle_Throw(player, NULL, DROP_THROW);
2252 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2254 entity player = M_ARGV(0, entity);
2256 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2258 player.wps_helpme_time = time;
2259 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2261 else // create a normal help me waypointsprite
2263 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2264 WaypointSprite_Ping(player.wps_helpme);
2270 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2272 entity player = M_ARGV(0, entity);
2273 entity veh = M_ARGV(1, entity);
2275 if(player.flagcarried)
2277 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2279 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2283 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2284 setattachment(player.flagcarried, veh, "");
2285 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2286 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2287 //player.flagcarried.angles = '0 0 0';
2293 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2295 entity player = M_ARGV(0, entity);
2297 if(player.flagcarried)
2299 setattachment(player.flagcarried, player, "");
2300 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2301 player.flagcarried.scale = FLAG_SCALE;
2302 player.flagcarried.angles = '0 0 0';
2303 player.flagcarried.nodrawtoclient = NULL;
2308 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2310 entity player = M_ARGV(0, entity);
2312 if(player.flagcarried)
2314 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((player.flagcarried.team) ? APP_TEAM_ENT(player.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2315 ctf_RespawnFlag(player.flagcarried);
2320 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2322 entity flag; // temporary entity for the search method
2324 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2326 switch(flag.ctf_status)
2331 // lock the flag, game is over
2332 set_movetype(flag, MOVETYPE_NONE);
2333 flag.takedamage = DAMAGE_NO;
2334 flag.solid = SOLID_NOT;
2335 flag.nextthink = false; // stop thinking
2337 //dprint("stopping the ", flag.netname, " from moving.\n");
2345 // do nothing for these flags
2352 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2354 entity bot = M_ARGV(0, entity);
2356 havocbot_ctf_reset_role(bot);
2360 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2362 //M_ARGV(0, float) = ctf_teams;
2363 M_ARGV(1, string) = "ctf_team";
2367 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2369 entity spectatee = M_ARGV(0, entity);
2370 entity client = M_ARGV(1, entity);
2372 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2375 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2377 int record_page = M_ARGV(0, int);
2378 string ret_string = M_ARGV(1, string);
2380 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2382 if (MapInfo_Get_ByID(i))
2384 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2390 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2391 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2395 M_ARGV(1, string) = ret_string;
2398 bool superspec_Spectate(entity this, entity targ); // TODO
2399 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2400 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2402 entity player = M_ARGV(0, entity);
2403 string cmd_name = M_ARGV(1, string);
2404 int cmd_argc = M_ARGV(2, int);
2406 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2408 if(cmd_name == "followfc")
2420 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2421 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2422 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2423 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2427 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2428 if(it.flagcarried && (it.team == _team || _team == 0))
2431 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2432 continue; // already spectating this fc, try another
2433 return superspec_Spectate(player, it);
2438 superspec_msg("", "", player, "No active flag carrier\n", 1);
2443 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2445 entity frag_target = M_ARGV(0, entity);
2447 if(frag_target.flagcarried)
2448 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2456 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2457 CTF flag for team one (Red).
2459 "angle" Angle the flag will point (minus 90 degrees)...
2460 "model" model to use, note this needs red and blue as skins 0 and 1...
2461 "noise" sound played when flag is picked up...
2462 "noise1" sound played when flag is returned by a teammate...
2463 "noise2" sound played when flag is captured...
2464 "noise3" sound played when flag is lost in the field and respawns itself...
2465 "noise4" sound played when flag is dropped by a player...
2466 "noise5" sound played when flag touches the ground... */
2467 spawnfunc(item_flag_team1)
2469 if(!g_ctf) { delete(this); return; }
2471 ctf_FlagSetup(NUM_TEAM_1, this);
2474 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2475 CTF flag for team two (Blue).
2477 "angle" Angle the flag will point (minus 90 degrees)...
2478 "model" model to use, note this needs red and blue as skins 0 and 1...
2479 "noise" sound played when flag is picked up...
2480 "noise1" sound played when flag is returned by a teammate...
2481 "noise2" sound played when flag is captured...
2482 "noise3" sound played when flag is lost in the field and respawns itself...
2483 "noise4" sound played when flag is dropped by a player...
2484 "noise5" sound played when flag touches the ground... */
2485 spawnfunc(item_flag_team2)
2487 if(!g_ctf) { delete(this); return; }
2489 ctf_FlagSetup(NUM_TEAM_2, this);
2492 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2493 CTF flag for team three (Yellow).
2495 "angle" Angle the flag will point (minus 90 degrees)...
2496 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2497 "noise" sound played when flag is picked up...
2498 "noise1" sound played when flag is returned by a teammate...
2499 "noise2" sound played when flag is captured...
2500 "noise3" sound played when flag is lost in the field and respawns itself...
2501 "noise4" sound played when flag is dropped by a player...
2502 "noise5" sound played when flag touches the ground... */
2503 spawnfunc(item_flag_team3)
2505 if(!g_ctf) { delete(this); return; }
2507 ctf_FlagSetup(NUM_TEAM_3, this);
2510 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2511 CTF flag for team four (Pink).
2513 "angle" Angle the flag will point (minus 90 degrees)...
2514 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2515 "noise" sound played when flag is picked up...
2516 "noise1" sound played when flag is returned by a teammate...
2517 "noise2" sound played when flag is captured...
2518 "noise3" sound played when flag is lost in the field and respawns itself...
2519 "noise4" sound played when flag is dropped by a player...
2520 "noise5" sound played when flag touches the ground... */
2521 spawnfunc(item_flag_team4)
2523 if(!g_ctf) { delete(this); return; }
2525 ctf_FlagSetup(NUM_TEAM_4, this);
2528 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2531 "angle" Angle the flag will point (minus 90 degrees)...
2532 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2533 "noise" sound played when flag is picked up...
2534 "noise1" sound played when flag is returned by a teammate...
2535 "noise2" sound played when flag is captured...
2536 "noise3" sound played when flag is lost in the field and respawns itself...
2537 "noise4" sound played when flag is dropped by a player...
2538 "noise5" sound played when flag touches the ground... */
2539 spawnfunc(item_flag_neutral)
2541 if(!g_ctf) { delete(this); return; }
2542 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2544 ctf_FlagSetup(0, this);
2547 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2548 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2549 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.
2551 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2552 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2555 if(!g_ctf) { delete(this); return; }
2557 this.classname = "ctf_team";
2558 this.team = this.cnt + 1;
2561 // compatibility for quake maps
2562 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2563 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2564 spawnfunc(info_player_team1);
2565 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2566 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2567 spawnfunc(info_player_team2);
2568 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2569 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2571 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2572 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2580 void ctf_ScoreRules(int teams)
2582 CheckAllowedTeams(NULL);
2583 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2584 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2585 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2586 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2587 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2588 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2589 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2590 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2591 ScoreRules_basics_end();
2594 // code from here on is just to support maps that don't have flag and team entities
2595 void ctf_SpawnTeam (string teamname, int teamcolor)
2597 entity this = new_pure(ctf_team);
2598 this.netname = teamname;
2599 this.cnt = teamcolor - 1;
2600 this.spawnfunc_checked = true;
2601 this.team = teamcolor;
2604 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2609 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2611 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2612 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2614 switch(tmp_entity.team)
2616 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2617 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2618 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2619 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2621 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2624 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2626 ctf_teams = 0; // so set the default red and blue teams
2627 BITSET_ASSIGN(ctf_teams, BIT(0));
2628 BITSET_ASSIGN(ctf_teams, BIT(1));
2631 //ctf_teams = bound(2, ctf_teams, 4);
2633 // if no teams are found, spawn defaults
2634 if(find(NULL, classname, "ctf_team") == NULL)
2636 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2637 if(ctf_teams & BIT(0))
2638 ctf_SpawnTeam("Red", NUM_TEAM_1);
2639 if(ctf_teams & BIT(1))
2640 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2641 if(ctf_teams & BIT(2))
2642 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2643 if(ctf_teams & BIT(3))
2644 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2647 ctf_ScoreRules(ctf_teams);
2650 void ctf_Initialize()
2652 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2654 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2655 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2656 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2658 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);