1 #include "gamemode_ctf.qh"
4 #include <common/effects/all.qh>
7 REGISTER_MUTATOR(ctf, false)
11 if (time > 1) // game loads at time 1
12 error("This is a game type and it cannot be added at runtime.");
15 GameRules_teams(true);
16 GameRules_limit_score(autocvar_capturelimit_override);
17 GameRules_limit_lead(autocvar_captureleadlimit_override);
18 have_team_spawns = -1; // request team spawns
21 MUTATOR_ONROLLBACK_OR_REMOVE
23 // we actually cannot roll back ctf_Initialize here
24 // BUT: we don't need to! If this gets called, adding always
30 LOG_INFO("This is a game type and it cannot be removed at runtime.");
39 #include <common/vehicles/all.qh>
40 #include <server/teamplay.qh>
43 #include <lib/warpzone/common.qh>
45 bool autocvar_g_ctf_allow_vehicle_carry;
46 bool autocvar_g_ctf_allow_vehicle_touch;
47 bool autocvar_g_ctf_allow_monster_touch;
48 bool autocvar_g_ctf_throw;
49 float autocvar_g_ctf_throw_angle_max;
50 float autocvar_g_ctf_throw_angle_min;
51 int autocvar_g_ctf_throw_punish_count;
52 float autocvar_g_ctf_throw_punish_delay;
53 float autocvar_g_ctf_throw_punish_time;
54 float autocvar_g_ctf_throw_strengthmultiplier;
55 float autocvar_g_ctf_throw_velocity_forward;
56 float autocvar_g_ctf_throw_velocity_up;
57 float autocvar_g_ctf_drop_velocity_up;
58 float autocvar_g_ctf_drop_velocity_side;
59 bool autocvar_g_ctf_oneflag_reverse;
60 bool autocvar_g_ctf_portalteleport;
61 bool autocvar_g_ctf_pass;
62 float autocvar_g_ctf_pass_arc;
63 float autocvar_g_ctf_pass_arc_max;
64 float autocvar_g_ctf_pass_directional_max;
65 float autocvar_g_ctf_pass_directional_min;
66 float autocvar_g_ctf_pass_radius;
67 float autocvar_g_ctf_pass_wait;
68 bool autocvar_g_ctf_pass_request;
69 float autocvar_g_ctf_pass_turnrate;
70 float autocvar_g_ctf_pass_timelimit;
71 float autocvar_g_ctf_pass_velocity;
72 bool autocvar_g_ctf_dynamiclights;
73 float autocvar_g_ctf_flag_collect_delay;
74 float autocvar_g_ctf_flag_damageforcescale;
75 bool autocvar_g_ctf_flag_dropped_waypoint;
76 bool autocvar_g_ctf_flag_dropped_floatinwater;
77 bool autocvar_g_ctf_flag_glowtrails;
78 int autocvar_g_ctf_flag_health;
79 bool autocvar_g_ctf_flag_return;
80 bool autocvar_g_ctf_flag_return_carrying;
81 float autocvar_g_ctf_flag_return_carried_radius;
82 float autocvar_g_ctf_flag_return_time;
83 bool autocvar_g_ctf_flag_return_when_unreachable;
84 float autocvar_g_ctf_flag_return_damage;
85 float autocvar_g_ctf_flag_return_damage_delay;
86 float autocvar_g_ctf_flag_return_dropped;
87 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
88 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
89 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
90 float autocvar_g_ctf_flagcarrier_selfforcefactor;
91 float autocvar_g_ctf_flagcarrier_damagefactor;
92 float autocvar_g_ctf_flagcarrier_forcefactor;
93 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
94 bool autocvar_g_ctf_fullbrightflags;
95 bool autocvar_g_ctf_ignore_frags;
96 bool autocvar_g_ctf_score_ignore_fields;
97 int autocvar_g_ctf_score_capture;
98 int autocvar_g_ctf_score_capture_assist;
99 int autocvar_g_ctf_score_kill;
100 int autocvar_g_ctf_score_penalty_drop;
101 int autocvar_g_ctf_score_penalty_returned;
102 int autocvar_g_ctf_score_pickup_base;
103 int autocvar_g_ctf_score_pickup_dropped_early;
104 int autocvar_g_ctf_score_pickup_dropped_late;
105 int autocvar_g_ctf_score_return;
106 float autocvar_g_ctf_shield_force;
107 float autocvar_g_ctf_shield_max_ratio;
108 int autocvar_g_ctf_shield_min_negscore;
109 bool autocvar_g_ctf_stalemate;
110 int autocvar_g_ctf_stalemate_endcondition;
111 float autocvar_g_ctf_stalemate_time;
112 bool autocvar_g_ctf_reverse;
113 float autocvar_g_ctf_dropped_capture_delay;
114 float autocvar_g_ctf_dropped_capture_radius;
116 void ctf_FakeTimeLimit(entity e, float t)
119 WriteByte(MSG_ONE, 3); // svc_updatestat
120 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
122 WriteCoord(MSG_ONE, autocvar_timelimit);
124 WriteCoord(MSG_ONE, (t + 1) / 60);
127 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
129 if(autocvar_sv_eventlog)
130 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
131 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
134 void ctf_CaptureRecord(entity flag, entity player)
136 float cap_record = ctf_captimerecord;
137 float cap_time = (time - flag.ctf_pickuptime);
138 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
142 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
143 else if(!ctf_captimerecord)
144 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
145 else if(cap_time < cap_record)
146 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
148 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
150 // write that shit in the database
151 if(!ctf_oneflag) // but not in 1-flag mode
152 if((!ctf_captimerecord) || (cap_time < cap_record))
154 ctf_captimerecord = cap_time;
155 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
156 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
157 write_recordmarker(player, flag.ctf_pickuptime, cap_time);
160 if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
161 race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
164 bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
167 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
169 // automatically return if there's only 1 player on the team
170 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
174 bool ctf_Return_Customize(entity this, entity client)
176 // only to the carrier
177 return boolean(client == this.owner);
180 void ctf_FlagcarrierWaypoints(entity player)
182 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
183 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
184 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
185 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
187 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
189 if(!player.wps_enemyflagcarrier)
191 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
192 wp.colormod = WPCOLOR_ENEMYFC(player.team);
193 setcefc(wp, ctf_Stalemate_Customize);
195 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
196 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
199 if(!player.wps_flagreturn)
201 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
202 owp.colormod = '0 0.8 0.8';
203 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
204 setcefc(owp, ctf_Return_Customize);
209 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
211 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
212 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
213 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
214 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
217 if(current_height) // make sure we can actually do this arcing path
219 targpos = (to + ('0 0 1' * current_height));
220 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
221 if(trace_fraction < 1)
223 //print("normal arc line failed, trying to find new pos...");
224 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
225 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
226 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
227 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
228 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
231 else { targpos = to; }
233 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
235 vector desired_direction = normalize(targpos - from);
236 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
237 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
240 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
242 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
244 // directional tracing only
246 makevectors(passer_angle);
248 // find the closest point on the enemy to the center of the attack
249 float h; // hypotenuse, which is the distance between attacker to head
250 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
252 h = vlen(head_center - passer_center);
253 a = h * (normalize(head_center - passer_center) * v_forward);
255 vector nearest_on_line = (passer_center + a * v_forward);
256 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
258 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
259 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
261 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
266 else { return true; }
270 // =======================
271 // CaptureShield Functions
272 // =======================
274 bool ctf_CaptureShield_CheckStatus(entity p)
276 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
277 int players_worseeq, players_total;
279 if(ctf_captureshield_max_ratio <= 0)
282 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
283 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
284 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
285 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
287 sr = ((s - s2) + (s3 + s4));
289 if(sr >= -ctf_captureshield_min_negscore)
292 players_total = players_worseeq = 0;
293 FOREACH_CLIENT(IS_PLAYER(it), {
296 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
297 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
298 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
299 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
301 ser = ((se - se2) + (se3 + se4));
308 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
309 // use this rule here
311 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
317 void ctf_CaptureShield_Update(entity player, bool wanted_status)
319 bool updated_status = ctf_CaptureShield_CheckStatus(player);
320 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
322 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
323 player.ctf_captureshielded = updated_status;
327 bool ctf_CaptureShield_Customize(entity this, entity client)
329 if(!client.ctf_captureshielded) { return false; }
330 if(CTF_SAMETEAM(this, client)) { return false; }
335 void ctf_CaptureShield_Touch(entity this, entity toucher)
337 if(!toucher.ctf_captureshielded) { return; }
338 if(CTF_SAMETEAM(this, toucher)) { return; }
340 vector mymid = (this.absmin + this.absmax) * 0.5;
341 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
343 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
344 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
347 void ctf_CaptureShield_Spawn(entity flag)
349 entity shield = new(ctf_captureshield);
352 shield.team = flag.team;
353 settouch(shield, ctf_CaptureShield_Touch);
354 setcefc(shield, ctf_CaptureShield_Customize);
355 shield.effects = EF_ADDITIVE;
356 set_movetype(shield, MOVETYPE_NOCLIP);
357 shield.solid = SOLID_TRIGGER;
358 shield.avelocity = '7 0 11';
361 setorigin(shield, flag.origin);
362 setmodel(shield, MDL_CTF_SHIELD);
363 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
367 // ====================
368 // Drop/Pass/Throw Code
369 // ====================
371 void ctf_Handle_Drop(entity flag, entity player, int droptype)
374 player = (player ? player : flag.pass_sender);
377 set_movetype(flag, MOVETYPE_TOSS);
378 flag.takedamage = DAMAGE_YES;
379 flag.angles = '0 0 0';
380 flag.health = flag.max_flag_health;
381 flag.ctf_droptime = time;
382 flag.ctf_dropper = player;
383 flag.ctf_status = FLAG_DROPPED;
385 // messages and sounds
386 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
387 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
388 ctf_EventLog("dropped", player.team, player);
391 PlayerTeamScore_AddScore(player, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
392 PlayerScore_Add(player, SP_CTF_DROPS, 1);
395 if(autocvar_g_ctf_flag_dropped_waypoint) {
396 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);
397 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
400 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
402 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
403 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
406 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
408 if(droptype == DROP_PASS)
410 flag.pass_distance = 0;
411 flag.pass_sender = NULL;
412 flag.pass_target = NULL;
416 void ctf_Handle_Retrieve(entity flag, entity player)
418 entity sender = flag.pass_sender;
420 // transfer flag to player
422 flag.owner.flagcarried = flag;
427 setattachment(flag, player.vehicle, "");
428 setorigin(flag, VEHICLE_FLAG_OFFSET);
429 flag.scale = VEHICLE_FLAG_SCALE;
433 setattachment(flag, player, "");
434 setorigin(flag, FLAG_CARRY_OFFSET);
436 set_movetype(flag, MOVETYPE_NONE);
437 flag.takedamage = DAMAGE_NO;
438 flag.solid = SOLID_NOT;
439 flag.angles = '0 0 0';
440 flag.ctf_status = FLAG_CARRY;
442 // messages and sounds
443 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
444 ctf_EventLog("receive", flag.team, player);
446 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
448 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
449 else if(it == player)
450 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
451 else if(SAME_TEAM(it, sender))
452 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
455 // create new waypoint
456 ctf_FlagcarrierWaypoints(player);
458 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
459 player.throw_antispam = sender.throw_antispam;
461 flag.pass_distance = 0;
462 flag.pass_sender = NULL;
463 flag.pass_target = NULL;
466 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
468 entity flag = player.flagcarried;
469 vector targ_origin, flag_velocity;
471 if(!flag) { return; }
472 if((droptype == DROP_PASS) && !receiver) { return; }
474 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
477 setattachment(flag, NULL, "");
478 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
479 flag.owner.flagcarried = NULL;
481 flag.solid = SOLID_TRIGGER;
482 flag.ctf_dropper = player;
483 flag.ctf_droptime = time;
484 navigation_dynamicgoal_set(flag);
486 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
493 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
494 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
495 WarpZone_RefSys_Copy(flag, receiver);
496 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
497 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
499 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
500 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
503 set_movetype(flag, MOVETYPE_FLY);
504 flag.takedamage = DAMAGE_NO;
505 flag.pass_sender = player;
506 flag.pass_target = receiver;
507 flag.ctf_status = FLAG_PASSING;
510 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
511 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
512 ctf_EventLog("pass", flag.team, player);
518 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'));
520 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)));
521 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
522 ctf_Handle_Drop(flag, player, droptype);
528 flag.velocity = '0 0 0'; // do nothing
535 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);
536 ctf_Handle_Drop(flag, player, droptype);
541 // kill old waypointsprite
542 WaypointSprite_Ping(player.wps_flagcarrier);
543 WaypointSprite_Kill(player.wps_flagcarrier);
545 if(player.wps_enemyflagcarrier)
546 WaypointSprite_Kill(player.wps_enemyflagcarrier);
548 if(player.wps_flagreturn)
549 WaypointSprite_Kill(player.wps_flagreturn);
552 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
555 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
557 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
564 void nades_GiveBonus(entity player, float score);
566 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
568 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
569 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
570 entity player_team_flag = NULL, tmp_entity;
571 float old_time, new_time;
573 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
574 if(CTF_DIFFTEAM(player, flag)) { return; }
575 if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
578 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
579 if(SAME_TEAM(tmp_entity, player))
581 player_team_flag = tmp_entity;
585 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
587 player.throw_prevtime = time;
588 player.throw_count = 0;
590 // messages and sounds
591 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
592 ctf_CaptureRecord(enemy_flag, player);
593 _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);
597 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
598 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
604 if(enemy_flag.score_capture || flag.score_capture)
605 pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
606 PlayerTeamScore_AddScore(player, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
608 if(enemy_flag.score_team_capture || flag.score_team_capture)
609 capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
610 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, ((capscore) ? capscore : 1));
612 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
613 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
614 if(!old_time || new_time < old_time)
615 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
618 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
619 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
622 if(capturetype == CAPTURE_NORMAL)
624 WaypointSprite_Kill(player.wps_flagcarrier);
625 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
627 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
628 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
632 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
633 ctf_RespawnFlag(enemy_flag);
636 void ctf_Handle_Return(entity flag, entity player)
638 // messages and sounds
639 if(IS_MONSTER(player))
641 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
645 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
646 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
648 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
649 ctf_EventLog("return", flag.team, player);
652 if(IS_PLAYER(player))
654 PlayerTeamScore_AddScore(player, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
655 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
657 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
660 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
664 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
665 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
666 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
670 if(player.flagcarried == flag)
671 WaypointSprite_Kill(player.wps_flagcarrier);
674 ctf_RespawnFlag(flag);
677 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
680 float pickup_dropped_score; // used to calculate dropped pickup score
682 // attach the flag to the player
684 player.flagcarried = flag;
687 setattachment(flag, player.vehicle, "");
688 setorigin(flag, VEHICLE_FLAG_OFFSET);
689 flag.scale = VEHICLE_FLAG_SCALE;
693 setattachment(flag, player, "");
694 setorigin(flag, FLAG_CARRY_OFFSET);
698 set_movetype(flag, MOVETYPE_NONE);
699 flag.takedamage = DAMAGE_NO;
700 flag.solid = SOLID_NOT;
701 flag.angles = '0 0 0';
702 flag.ctf_status = FLAG_CARRY;
706 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
707 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
711 // messages and sounds
712 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
714 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
716 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
717 else if(CTF_DIFFTEAM(player, flag))
718 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
720 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
722 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
725 FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
728 FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
729 if(CTF_SAMETEAM(flag, it))
730 if(SAME_TEAM(player, it))
731 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
733 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);
736 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
739 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
740 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
745 PlayerTeamScore_AddScore(player, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
746 ctf_EventLog("steal", flag.team, player);
752 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);
753 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);
754 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
755 PlayerTeamScore_AddScore(player, pickup_dropped_score);
756 ctf_EventLog("pickup", flag.team, player);
764 if(pickuptype == PICKUP_BASE)
766 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
767 if((player.speedrunning) && (ctf_captimerecord))
768 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
772 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
775 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
776 ctf_FlagcarrierWaypoints(player);
777 WaypointSprite_Ping(player.wps_flagcarrier);
781 // ===================
782 // Main Flag Functions
783 // ===================
785 void ctf_CheckFlagReturn(entity flag, int returntype)
787 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
789 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
791 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
796 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
798 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
799 case RETURN_SPEEDRUN:
800 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
801 case RETURN_NEEDKILL:
802 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
805 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
807 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
808 ctf_EventLog("returned", flag.team, NULL);
809 ctf_RespawnFlag(flag);
814 bool ctf_Stalemate_Customize(entity this, entity client)
816 // make spectators see what the player would see
817 entity e = WaypointSprite_getviewentity(client);
818 entity wp_owner = this.owner;
821 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
822 if(SAME_TEAM(wp_owner, e)) { return false; }
823 if(!IS_PLAYER(e)) { return false; }
828 void ctf_CheckStalemate()
831 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
834 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
836 // build list of stale flags
837 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
839 if(autocvar_g_ctf_stalemate)
840 if(tmp_entity.ctf_status != FLAG_BASE)
841 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
843 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
844 ctf_staleflaglist = tmp_entity;
846 switch(tmp_entity.team)
848 case NUM_TEAM_1: ++stale_red_flags; break;
849 case NUM_TEAM_2: ++stale_blue_flags; break;
850 case NUM_TEAM_3: ++stale_yellow_flags; break;
851 case NUM_TEAM_4: ++stale_pink_flags; break;
852 default: ++stale_neutral_flags; break;
858 stale_flags = (stale_neutral_flags >= 1);
860 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
862 if(ctf_oneflag && stale_flags == 1)
863 ctf_stalemate = true;
864 else if(stale_flags >= 2)
865 ctf_stalemate = true;
866 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
867 { ctf_stalemate = false; wpforenemy_announced = false; }
868 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
869 { ctf_stalemate = false; wpforenemy_announced = false; }
871 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
874 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
876 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
878 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);
879 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
880 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
884 if (!wpforenemy_announced)
886 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
888 wpforenemy_announced = true;
893 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
895 if(ITEM_DAMAGE_NEEDKILL(deathtype))
897 if(autocvar_g_ctf_flag_return_damage_delay)
898 this.ctf_flagdamaged_byworld = true;
902 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
906 if(autocvar_g_ctf_flag_return_damage)
908 // reduce health and check if it should be returned
909 this.health = this.health - damage;
910 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
915 void ctf_FlagThink(entity this)
920 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
923 if(this == ctf_worldflaglist) // only for the first flag
924 FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
927 if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
928 LOG_TRACE("wtf the flag got squashed?");
929 tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
930 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
931 setsize(this, this.m_mins, this.m_maxs);
935 switch(this.ctf_status)
939 if(autocvar_g_ctf_dropped_capture_radius)
941 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
942 if(tmp_entity.ctf_status == FLAG_DROPPED)
943 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
944 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
945 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
952 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
954 if(autocvar_g_ctf_flag_dropped_floatinwater)
956 vector midpoint = ((this.absmin + this.absmax) * 0.5);
957 if(pointcontents(midpoint) == CONTENT_WATER)
959 this.velocity = this.velocity * 0.5;
961 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
962 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
964 { set_movetype(this, MOVETYPE_FLY); }
966 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
968 if(autocvar_g_ctf_flag_return_dropped)
970 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
973 ctf_CheckFlagReturn(this, RETURN_DROPPED);
977 if(this.ctf_flagdamaged_byworld)
979 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
980 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
983 else if(autocvar_g_ctf_flag_return_time)
985 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
986 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
994 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
997 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
999 CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
1000 ImpulseCommands(this.owner);
1002 if(autocvar_g_ctf_stalemate)
1004 if(time >= wpforenemy_nextthink)
1006 ctf_CheckStalemate();
1007 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1010 if(CTF_SAMETEAM(this, this.owner) && this.team)
1012 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1013 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1014 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1015 ctf_Handle_Return(this, this.owner);
1022 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1023 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1024 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1026 if((this.pass_target == NULL)
1027 || (IS_DEAD(this.pass_target))
1028 || (this.pass_target.flagcarried)
1029 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1030 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1031 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1033 // give up, pass failed
1034 ctf_Handle_Drop(this, NULL, DROP_PASS);
1038 // still a viable target, go for it
1039 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1044 default: // this should never happen
1046 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1052 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1055 if(game_stopped) return;
1056 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1058 bool is_not_monster = (!IS_MONSTER(toucher));
1060 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1061 if(ITEM_TOUCH_NEEDKILL())
1063 if(!autocvar_g_ctf_flag_return_damage_delay)
1066 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1068 if(!flag.ctf_flagdamaged_byworld) { return; }
1071 // special touch behaviors
1072 if(STAT(FROZEN, toucher)) { return; }
1073 else if(IS_VEHICLE(toucher))
1075 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1076 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1078 return; // do nothing
1080 else if(IS_MONSTER(toucher))
1082 if(!autocvar_g_ctf_allow_monster_touch)
1083 return; // do nothing
1085 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1087 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1089 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1090 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1091 flag.wait = time + FLAG_TOUCHRATE;
1095 else if(IS_DEAD(toucher)) { return; }
1097 switch(flag.ctf_status)
1103 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1104 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1105 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1106 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1108 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1109 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1110 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)
1112 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1113 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1115 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1116 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1122 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1123 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1124 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1125 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1131 LOG_TRACE("Someone touched a flag even though it was being carried?");
1137 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1139 if(DIFF_TEAM(toucher, flag.pass_sender))
1141 if(ctf_Immediate_Return_Allowed(flag, toucher))
1142 ctf_Handle_Return(flag, toucher);
1143 else if(is_not_monster && (!toucher.flagcarried))
1144 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1146 else if(!toucher.flagcarried)
1147 ctf_Handle_Retrieve(flag, toucher);
1154 .float last_respawn;
1155 void ctf_RespawnFlag(entity flag)
1157 // check for flag respawn being called twice in a row
1158 if(flag.last_respawn > time - 0.5)
1159 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1161 flag.last_respawn = time;
1163 // reset the player (if there is one)
1164 if((flag.owner) && (flag.owner.flagcarried == flag))
1166 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1167 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1168 WaypointSprite_Kill(flag.wps_flagcarrier);
1170 flag.owner.flagcarried = NULL;
1172 if(flag.speedrunning)
1173 ctf_FakeTimeLimit(flag.owner, -1);
1176 if((flag.owner) && (flag.owner.vehicle))
1177 flag.scale = FLAG_SCALE;
1179 if(flag.ctf_status == FLAG_DROPPED)
1180 { WaypointSprite_Kill(flag.wps_flagdropped); }
1183 setattachment(flag, NULL, "");
1184 setorigin(flag, flag.ctf_spawnorigin);
1186 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1187 flag.takedamage = DAMAGE_NO;
1188 flag.health = flag.max_flag_health;
1189 flag.solid = SOLID_TRIGGER;
1190 flag.velocity = '0 0 0';
1191 flag.angles = flag.mangle;
1192 flag.flags = FL_ITEM | FL_NOTARGET;
1194 flag.ctf_status = FLAG_BASE;
1196 flag.pass_distance = 0;
1197 flag.pass_sender = NULL;
1198 flag.pass_target = NULL;
1199 flag.ctf_dropper = NULL;
1200 flag.ctf_pickuptime = 0;
1201 flag.ctf_droptime = 0;
1202 flag.ctf_flagdamaged_byworld = false;
1203 navigation_dynamicgoal_unset(flag);
1205 ctf_CheckStalemate();
1208 void ctf_Reset(entity this)
1210 if(this.owner && IS_PLAYER(this.owner))
1211 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1213 ctf_RespawnFlag(this);
1216 bool ctf_FlagBase_Customize(entity this, entity client)
1218 entity e = WaypointSprite_getviewentity(client);
1219 entity wp_owner = this.owner;
1220 entity flag = e.flagcarried;
1221 if(flag && CTF_SAMETEAM(e, flag))
1223 if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1228 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1231 waypoint_spawnforitem_force(this, this.origin);
1232 navigation_dynamicgoal_init(this, true);
1238 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1239 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1240 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1241 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1242 default: basename = WP_FlagBaseNeutral; break;
1245 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1246 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1247 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1248 setcefc(wp, ctf_FlagBase_Customize);
1250 // captureshield setup
1251 ctf_CaptureShield_Spawn(this);
1256 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1259 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1260 ctf_worldflaglist = flag;
1262 setattachment(flag, NULL, "");
1264 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1265 flag.team = teamnumber;
1266 flag.classname = "item_flag_team";
1267 flag.target = "###item###"; // wut?
1268 flag.flags = FL_ITEM | FL_NOTARGET;
1269 IL_PUSH(g_items, flag);
1270 flag.solid = SOLID_TRIGGER;
1271 flag.takedamage = DAMAGE_NO;
1272 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1273 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1274 flag.health = flag.max_flag_health;
1275 flag.event_damage = ctf_FlagDamage;
1276 flag.pushable = true;
1277 flag.teleportable = TELEPORT_NORMAL;
1278 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1279 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1280 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1281 if(flag.damagedbycontents)
1282 IL_PUSH(g_damagedbycontents, flag);
1283 flag.velocity = '0 0 0';
1284 flag.mangle = flag.angles;
1285 flag.reset = ctf_Reset;
1286 settouch(flag, ctf_FlagTouch);
1287 setthink(flag, ctf_FlagThink);
1288 flag.nextthink = time + FLAG_THINKRATE;
1289 flag.ctf_status = FLAG_BASE;
1291 // crudely force them all to 0
1292 if(autocvar_g_ctf_score_ignore_fields)
1293 flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1295 string teamname = Static_Team_ColorName_Lower(teamnumber);
1297 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1298 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1299 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1300 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1301 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1302 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1306 if(flag.s == "") flag.s = b; \
1307 precache_sound(flag.s);
1309 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1310 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1311 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1312 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1313 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1314 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1315 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1319 precache_model(flag.model);
1322 _setmodel(flag, flag.model); // precision set below
1323 setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
1324 flag.m_mins = flag.mins; // store these for squash checks
1325 flag.m_maxs = flag.maxs;
1326 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1328 if(autocvar_g_ctf_flag_glowtrails)
1332 case NUM_TEAM_1: flag.glow_color = 251; break;
1333 case NUM_TEAM_2: flag.glow_color = 210; break;
1334 case NUM_TEAM_3: flag.glow_color = 110; break;
1335 case NUM_TEAM_4: flag.glow_color = 145; break;
1336 default: flag.glow_color = 254; break;
1338 flag.glow_size = 25;
1339 flag.glow_trail = 1;
1342 flag.effects |= EF_LOWPRECISION;
1343 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1344 if(autocvar_g_ctf_dynamiclights)
1348 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1349 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1350 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1351 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1352 default: flag.effects |= EF_DIMLIGHT; break;
1357 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1359 flag.dropped_origin = flag.origin;
1360 flag.noalign = true;
1361 set_movetype(flag, MOVETYPE_NONE);
1363 else // drop to floor, automatically find a platform and set that as spawn origin
1365 flag.noalign = false;
1367 set_movetype(flag, MOVETYPE_NONE);
1370 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1378 // NOTE: LEGACY CODE, needs to be re-written!
1380 void havocbot_ctf_calculate_middlepoint()
1384 vector fo = '0 0 0';
1387 f = ctf_worldflaglist;
1392 f = f.ctf_worldflagnext;
1398 havocbot_middlepoint = s / n;
1399 havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
1401 havocbot_symmetryaxis_equation = '0 0 0';
1404 // for symmetrical editing of waypoints
1405 entity f1 = ctf_worldflaglist;
1406 entity f2 = f1.ctf_worldflagnext;
1407 float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
1408 float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
1409 havocbot_symmetryaxis_equation.x = m;
1410 havocbot_symmetryaxis_equation.y = q;
1412 // store number of flags in this otherwise unused vector component
1413 havocbot_symmetryaxis_equation.z = n;
1417 entity havocbot_ctf_find_flag(entity bot)
1420 f = ctf_worldflaglist;
1423 if (CTF_SAMETEAM(bot, f))
1425 f = f.ctf_worldflagnext;
1430 entity havocbot_ctf_find_enemy_flag(entity bot)
1433 f = ctf_worldflaglist;
1438 if(CTF_DIFFTEAM(bot, f))
1445 else if(!bot.flagcarried)
1449 else if (CTF_DIFFTEAM(bot, f))
1451 f = f.ctf_worldflagnext;
1456 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1463 FOREACH_CLIENT(IS_PLAYER(it), {
1464 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1467 if(vdist(it.origin - org, <, tc_radius))
1476 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1479 head = ctf_worldflaglist;
1482 if (CTF_SAMETEAM(this, head))
1484 head = head.ctf_worldflagnext;
1487 navigation_routerating(this, head, ratingscale, 10000);
1491 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1494 head = ctf_worldflaglist;
1497 if (CTF_SAMETEAM(this, head))
1499 if (this.flagcarried)
1500 if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
1502 head = head.ctf_worldflagnext; // skip base if it has a different group
1507 head = head.ctf_worldflagnext;
1512 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1515 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1518 head = ctf_worldflaglist;
1523 if(CTF_DIFFTEAM(this, head))
1527 if(this.flagcarried)
1530 else if(!this.flagcarried)
1534 else if(CTF_DIFFTEAM(this, head))
1536 head = head.ctf_worldflagnext;
1539 navigation_routerating(this, head, ratingscale, 10000);
1542 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1544 if (!bot_waypoints_for_items)
1546 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1552 head = havocbot_ctf_find_enemy_flag(this);
1557 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1560 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1564 mf = havocbot_ctf_find_flag(this);
1566 if(mf.ctf_status == FLAG_BASE)
1570 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1573 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1576 head = ctf_worldflaglist;
1579 // flag is out in the field
1580 if(head.ctf_status != FLAG_BASE)
1581 if(head.tag_entity==NULL) // dropped
1585 if(vdist(org - head.origin, <, df_radius))
1586 navigation_routerating(this, head, ratingscale, 10000);
1589 navigation_routerating(this, head, ratingscale, 10000);
1592 head = head.ctf_worldflagnext;
1596 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1598 IL_EACH(g_items, it.bot_pickup,
1600 // gather health and armor only
1602 if (it.health || it.armorvalue)
1603 if (vdist(it.origin - org, <, sradius))
1605 // get the value of the item
1606 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1608 navigation_routerating(this, it, t * ratingscale, 500);
1613 void havocbot_ctf_reset_role(entity this)
1615 float cdefense, cmiddle, coffense;
1623 if (this.flagcarried)
1625 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1629 mf = havocbot_ctf_find_flag(this);
1630 ef = havocbot_ctf_find_enemy_flag(this);
1632 // Retrieve stolen flag
1633 if(mf.ctf_status!=FLAG_BASE)
1635 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1639 // If enemy flag is taken go to the middle to intercept pursuers
1640 if(ef.ctf_status!=FLAG_BASE)
1642 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1646 // if there is only me on the team switch to offense
1648 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
1652 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1656 // Evaluate best position to take
1657 // Count mates on middle position
1658 cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
1660 // Count mates on defense position
1661 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
1663 // Count mates on offense position
1664 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
1666 if(cdefense<=coffense)
1667 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1668 else if(coffense<=cmiddle)
1669 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1671 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1674 void havocbot_role_ctf_carrier(entity this)
1678 havocbot_ctf_reset_role(this);
1682 if (this.flagcarried == NULL)
1684 havocbot_ctf_reset_role(this);
1688 if (this.bot_strategytime < time)
1690 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1692 navigation_goalrating_start(this);
1694 havocbot_goalrating_ctf_enemybase(this, 50000);
1696 havocbot_goalrating_ctf_ourbase(this, 50000);
1699 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1701 navigation_goalrating_end(this);
1703 if (this.goalentity)
1704 this.havocbot_cantfindflag = time + 10;
1705 else if (time > this.havocbot_cantfindflag)
1707 // Can't navigate to my own base, suicide!
1708 // TODO: drop it and wander around
1709 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1715 void havocbot_role_ctf_escort(entity this)
1721 havocbot_ctf_reset_role(this);
1725 if (this.flagcarried)
1727 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1731 // If enemy flag is back on the base switch to previous role
1732 ef = havocbot_ctf_find_enemy_flag(this);
1733 if(ef.ctf_status==FLAG_BASE)
1735 this.havocbot_role = this.havocbot_previous_role;
1736 this.havocbot_role_timeout = 0;
1740 // If the flag carrier reached the base switch to defense
1741 mf = havocbot_ctf_find_flag(this);
1742 if(mf.ctf_status!=FLAG_BASE)
1743 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1745 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1749 // Set the role timeout if necessary
1750 if (!this.havocbot_role_timeout)
1752 this.havocbot_role_timeout = time + random() * 30 + 60;
1755 // If nothing happened just switch to previous role
1756 if (time > this.havocbot_role_timeout)
1758 this.havocbot_role = this.havocbot_previous_role;
1759 this.havocbot_role_timeout = 0;
1763 // Chase the flag carrier
1764 if (this.bot_strategytime < time)
1766 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1767 navigation_goalrating_start(this);
1768 havocbot_goalrating_ctf_enemyflag(this, 30000);
1769 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1770 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1771 navigation_goalrating_end(this);
1775 void havocbot_role_ctf_offense(entity this)
1782 havocbot_ctf_reset_role(this);
1786 if (this.flagcarried)
1788 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1793 mf = havocbot_ctf_find_flag(this);
1794 ef = havocbot_ctf_find_enemy_flag(this);
1797 if(mf.ctf_status!=FLAG_BASE)
1800 pos = mf.tag_entity.origin;
1804 // Try to get it if closer than the enemy base
1805 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1807 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1812 // Escort flag carrier
1813 if(ef.ctf_status!=FLAG_BASE)
1816 pos = ef.tag_entity.origin;
1820 if(vdist(pos - mf.dropped_origin, >, 700))
1822 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1827 // About to fail, switch to middlefield
1830 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1834 // Set the role timeout if necessary
1835 if (!this.havocbot_role_timeout)
1836 this.havocbot_role_timeout = time + 120;
1838 if (time > this.havocbot_role_timeout)
1840 havocbot_ctf_reset_role(this);
1844 if (this.bot_strategytime < time)
1846 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1847 navigation_goalrating_start(this);
1848 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1849 havocbot_goalrating_ctf_enemybase(this, 20000);
1850 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1851 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1852 navigation_goalrating_end(this);
1856 // Retriever (temporary role):
1857 void havocbot_role_ctf_retriever(entity this)
1863 havocbot_ctf_reset_role(this);
1867 if (this.flagcarried)
1869 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1873 // If flag is back on the base switch to previous role
1874 mf = havocbot_ctf_find_flag(this);
1875 if(mf.ctf_status==FLAG_BASE)
1877 if(this.goalcurrent == mf)
1879 navigation_clearroute(this);
1880 this.bot_strategytime = 0;
1882 havocbot_ctf_reset_role(this);
1886 if (!this.havocbot_role_timeout)
1887 this.havocbot_role_timeout = time + 20;
1889 if (time > this.havocbot_role_timeout)
1891 havocbot_ctf_reset_role(this);
1895 if (this.bot_strategytime < time)
1900 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1901 navigation_goalrating_start(this);
1902 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1903 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1904 havocbot_goalrating_ctf_enemybase(this, 30000);
1905 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1906 navigation_goalrating_end(this);
1910 void havocbot_role_ctf_middle(entity this)
1916 havocbot_ctf_reset_role(this);
1920 if (this.flagcarried)
1922 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1926 mf = havocbot_ctf_find_flag(this);
1927 if(mf.ctf_status!=FLAG_BASE)
1929 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1933 if (!this.havocbot_role_timeout)
1934 this.havocbot_role_timeout = time + 10;
1936 if (time > this.havocbot_role_timeout)
1938 havocbot_ctf_reset_role(this);
1942 if (this.bot_strategytime < time)
1946 org = havocbot_middlepoint;
1947 org.z = this.origin.z;
1949 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1950 navigation_goalrating_start(this);
1951 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1952 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1953 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
1954 havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
1955 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1956 havocbot_goalrating_ctf_enemybase(this, 2500);
1957 navigation_goalrating_end(this);
1961 void havocbot_role_ctf_defense(entity this)
1967 havocbot_ctf_reset_role(this);
1971 if (this.flagcarried)
1973 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1977 // If own flag was captured
1978 mf = havocbot_ctf_find_flag(this);
1979 if(mf.ctf_status!=FLAG_BASE)
1981 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1985 if (!this.havocbot_role_timeout)
1986 this.havocbot_role_timeout = time + 30;
1988 if (time > this.havocbot_role_timeout)
1990 havocbot_ctf_reset_role(this);
1993 if (this.bot_strategytime < time)
1995 vector org = mf.dropped_origin;
1997 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1998 navigation_goalrating_start(this);
2000 // if enemies are closer to our base, go there
2001 entity closestplayer = NULL;
2002 float distance, bestdistance = 10000;
2003 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
2004 distance = vlen(org - it.origin);
2005 if(distance<bestdistance)
2008 bestdistance = distance;
2013 if(DIFF_TEAM(closestplayer, this))
2014 if(vdist(org - this.origin, >, 1000))
2015 if(checkpvs(this.origin,closestplayer)||random()<0.5)
2016 havocbot_goalrating_ctf_ourbase(this, 30000);
2018 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
2019 havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
2020 havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
2021 havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
2022 havocbot_goalrating_items(this, 5000, this.origin, 10000);
2023 navigation_goalrating_end(this);
2027 void havocbot_role_ctf_setrole(entity bot, int role)
2029 string s = "(null)";
2032 case HAVOCBOT_CTF_ROLE_CARRIER:
2034 bot.havocbot_role = havocbot_role_ctf_carrier;
2035 bot.havocbot_role_timeout = 0;
2036 bot.havocbot_cantfindflag = time + 10;
2037 bot.bot_strategytime = 0;
2039 case HAVOCBOT_CTF_ROLE_DEFENSE:
2041 bot.havocbot_role = havocbot_role_ctf_defense;
2042 bot.havocbot_role_timeout = 0;
2044 case HAVOCBOT_CTF_ROLE_MIDDLE:
2046 bot.havocbot_role = havocbot_role_ctf_middle;
2047 bot.havocbot_role_timeout = 0;
2049 case HAVOCBOT_CTF_ROLE_OFFENSE:
2051 bot.havocbot_role = havocbot_role_ctf_offense;
2052 bot.havocbot_role_timeout = 0;
2054 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2056 bot.havocbot_previous_role = bot.havocbot_role;
2057 bot.havocbot_role = havocbot_role_ctf_retriever;
2058 bot.havocbot_role_timeout = time + 10;
2059 bot.bot_strategytime = 0;
2061 case HAVOCBOT_CTF_ROLE_ESCORT:
2063 bot.havocbot_previous_role = bot.havocbot_role;
2064 bot.havocbot_role = havocbot_role_ctf_escort;
2065 bot.havocbot_role_timeout = time + 30;
2066 bot.bot_strategytime = 0;
2069 LOG_TRACE(bot.netname, " switched to ", s);
2077 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2079 entity player = M_ARGV(0, entity);
2081 int t = 0, t2 = 0, t3 = 0;
2082 bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
2084 // initially clear items so they can be set as necessary later.
2085 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2086 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2087 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2088 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2089 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2090 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2092 // scan through all the flags and notify the client about them
2093 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2095 if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2096 if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2097 if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2098 if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2099 if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; player.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
2101 switch(flag.ctf_status)
2106 if((flag.owner == player) || (flag.pass_sender == player))
2107 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2109 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2114 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2120 // item for stopping players from capturing the flag too often
2121 if(player.ctf_captureshielded)
2122 player.ctf_flagstatus |= CTF_SHIELDED;
2125 player.ctf_flagstatus |= CTF_STALEMATE;
2127 // update the health of the flag carrier waypointsprite
2128 if(player.wps_flagcarrier)
2129 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2132 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2134 entity frag_attacker = M_ARGV(1, entity);
2135 entity frag_target = M_ARGV(2, entity);
2136 float frag_damage = M_ARGV(4, float);
2137 vector frag_force = M_ARGV(6, vector);
2139 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2141 if(frag_target == frag_attacker) // damage done to yourself
2143 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2144 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2146 else // damage done to everyone else
2148 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2149 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2152 M_ARGV(4, float) = frag_damage;
2153 M_ARGV(6, vector) = frag_force;
2155 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2157 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)))
2158 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2160 frag_target.wps_helpme_time = time;
2161 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2163 // todo: add notification for when flag carrier needs help?
2167 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2169 entity frag_attacker = M_ARGV(1, entity);
2170 entity frag_target = M_ARGV(2, entity);
2172 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2174 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2175 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2178 if(frag_target.flagcarried)
2180 entity tmp_entity = frag_target.flagcarried;
2181 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2182 tmp_entity.ctf_dropper = NULL;
2186 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2188 M_ARGV(2, float) = 0; // frag score
2189 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2192 void ctf_RemovePlayer(entity player)
2194 if(player.flagcarried)
2195 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2197 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2199 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2200 if(flag.pass_target == player) { flag.pass_target = NULL; }
2201 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2205 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2207 entity player = M_ARGV(0, entity);
2209 ctf_RemovePlayer(player);
2212 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2214 entity player = M_ARGV(0, entity);
2216 ctf_RemovePlayer(player);
2219 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2221 if(!autocvar_g_ctf_leaderboard)
2224 entity player = M_ARGV(0, entity);
2226 if(IS_REAL_CLIENT(player))
2228 for(int i = 1; i <= RANKINGS_CNT; ++i)
2230 race_SendRankings(i, 0, 0, MSG_ONE);
2235 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2237 if(!autocvar_g_ctf_leaderboard)
2240 entity player = M_ARGV(0, entity);
2242 if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
2244 if (!player.stored_netname)
2245 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2246 if(player.stored_netname != player.netname)
2248 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2249 strunzone(player.stored_netname);
2250 player.stored_netname = strzone(player.netname);
2255 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2257 entity player = M_ARGV(0, entity);
2259 if(player.flagcarried)
2260 if(!autocvar_g_ctf_portalteleport)
2261 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2264 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2266 if(MUTATOR_RETURNVALUE || game_stopped) return;
2268 entity player = M_ARGV(0, entity);
2270 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2272 // pass the flag to a team mate
2273 if(autocvar_g_ctf_pass)
2275 entity head, closest_target = NULL;
2276 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2278 while(head) // find the closest acceptable target to pass to
2280 if(IS_PLAYER(head) && !IS_DEAD(head))
2281 if(head != player && SAME_TEAM(head, player))
2282 if(!head.speedrunning && !head.vehicle)
2284 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2285 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2286 vector passer_center = CENTER_OR_VIEWOFS(player);
2288 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2290 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2292 if(IS_BOT_CLIENT(head))
2294 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2295 ctf_Handle_Throw(head, player, DROP_PASS);
2299 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2300 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2302 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2305 else if(player.flagcarried && !head.flagcarried)
2309 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2310 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2311 { closest_target = head; }
2313 else { closest_target = head; }
2320 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2323 // throw the flag in front of you
2324 if(autocvar_g_ctf_throw && player.flagcarried)
2326 if(player.throw_count == -1)
2328 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2330 player.throw_prevtime = time;
2331 player.throw_count = 1;
2332 ctf_Handle_Throw(player, NULL, DROP_THROW);
2337 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2343 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2344 else { player.throw_count += 1; }
2345 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2347 player.throw_prevtime = time;
2348 ctf_Handle_Throw(player, NULL, DROP_THROW);
2355 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2357 entity player = M_ARGV(0, entity);
2359 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2361 player.wps_helpme_time = time;
2362 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2364 else // create a normal help me waypointsprite
2366 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2367 WaypointSprite_Ping(player.wps_helpme);
2373 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2375 entity player = M_ARGV(0, entity);
2376 entity veh = M_ARGV(1, entity);
2378 if(player.flagcarried)
2380 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2382 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2386 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2387 setattachment(player.flagcarried, veh, "");
2388 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2389 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2390 //player.flagcarried.angles = '0 0 0';
2396 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2398 entity player = M_ARGV(0, entity);
2400 if(player.flagcarried)
2402 setattachment(player.flagcarried, player, "");
2403 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2404 player.flagcarried.scale = FLAG_SCALE;
2405 player.flagcarried.angles = '0 0 0';
2406 player.flagcarried.nodrawtoclient = NULL;
2411 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2413 entity player = M_ARGV(0, entity);
2415 if(player.flagcarried)
2417 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2418 ctf_RespawnFlag(player.flagcarried);
2423 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2425 entity flag; // temporary entity for the search method
2427 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2429 switch(flag.ctf_status)
2434 // lock the flag, game is over
2435 set_movetype(flag, MOVETYPE_NONE);
2436 flag.takedamage = DAMAGE_NO;
2437 flag.solid = SOLID_NOT;
2438 flag.nextthink = false; // stop thinking
2440 //dprint("stopping the ", flag.netname, " from moving.\n");
2448 // do nothing for these flags
2455 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2457 entity bot = M_ARGV(0, entity);
2459 havocbot_ctf_reset_role(bot);
2463 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2465 //M_ARGV(0, float) = ctf_teams;
2466 M_ARGV(1, string) = "ctf_team";
2470 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2472 entity spectatee = M_ARGV(0, entity);
2473 entity client = M_ARGV(1, entity);
2475 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2478 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2480 int record_page = M_ARGV(0, int);
2481 string ret_string = M_ARGV(1, string);
2483 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2485 if (MapInfo_Get_ByID(i))
2487 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2493 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2494 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2498 M_ARGV(1, string) = ret_string;
2501 bool superspec_Spectate(entity this, entity targ); // TODO
2502 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2503 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2505 entity player = M_ARGV(0, entity);
2506 string cmd_name = M_ARGV(1, string);
2507 int cmd_argc = M_ARGV(2, int);
2509 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2511 if(cmd_name == "followfc")
2523 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2524 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2525 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2526 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2530 FOREACH_CLIENT(IS_PLAYER(it), {
2531 if(it.flagcarried && (it.team == _team || _team == 0))
2534 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2535 continue; // already spectating this fc, try another
2536 return superspec_Spectate(player, it);
2541 superspec_msg("", "", player, "No active flag carrier\n", 1);
2546 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2548 entity frag_target = M_ARGV(0, entity);
2550 if(frag_target.flagcarried)
2551 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2559 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2560 CTF flag for team one (Red).
2562 "angle" Angle the flag will point (minus 90 degrees)...
2563 "model" model to use, note this needs red and blue as skins 0 and 1...
2564 "noise" sound played when flag is picked up...
2565 "noise1" sound played when flag is returned by a teammate...
2566 "noise2" sound played when flag is captured...
2567 "noise3" sound played when flag is lost in the field and respawns itself...
2568 "noise4" sound played when flag is dropped by a player...
2569 "noise5" sound played when flag touches the ground... */
2570 spawnfunc(item_flag_team1)
2572 if(!g_ctf) { delete(this); return; }
2574 ctf_FlagSetup(NUM_TEAM_1, this);
2577 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2578 CTF flag for team two (Blue).
2580 "angle" Angle the flag will point (minus 90 degrees)...
2581 "model" model to use, note this needs red and blue as skins 0 and 1...
2582 "noise" sound played when flag is picked up...
2583 "noise1" sound played when flag is returned by a teammate...
2584 "noise2" sound played when flag is captured...
2585 "noise3" sound played when flag is lost in the field and respawns itself...
2586 "noise4" sound played when flag is dropped by a player...
2587 "noise5" sound played when flag touches the ground... */
2588 spawnfunc(item_flag_team2)
2590 if(!g_ctf) { delete(this); return; }
2592 ctf_FlagSetup(NUM_TEAM_2, this);
2595 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2596 CTF flag for team three (Yellow).
2598 "angle" Angle the flag will point (minus 90 degrees)...
2599 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2600 "noise" sound played when flag is picked up...
2601 "noise1" sound played when flag is returned by a teammate...
2602 "noise2" sound played when flag is captured...
2603 "noise3" sound played when flag is lost in the field and respawns itself...
2604 "noise4" sound played when flag is dropped by a player...
2605 "noise5" sound played when flag touches the ground... */
2606 spawnfunc(item_flag_team3)
2608 if(!g_ctf) { delete(this); return; }
2610 ctf_FlagSetup(NUM_TEAM_3, this);
2613 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2614 CTF flag for team four (Pink).
2616 "angle" Angle the flag will point (minus 90 degrees)...
2617 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2618 "noise" sound played when flag is picked up...
2619 "noise1" sound played when flag is returned by a teammate...
2620 "noise2" sound played when flag is captured...
2621 "noise3" sound played when flag is lost in the field and respawns itself...
2622 "noise4" sound played when flag is dropped by a player...
2623 "noise5" sound played when flag touches the ground... */
2624 spawnfunc(item_flag_team4)
2626 if(!g_ctf) { delete(this); return; }
2628 ctf_FlagSetup(NUM_TEAM_4, this);
2631 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2634 "angle" Angle the flag will point (minus 90 degrees)...
2635 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2636 "noise" sound played when flag is picked up...
2637 "noise1" sound played when flag is returned by a teammate...
2638 "noise2" sound played when flag is captured...
2639 "noise3" sound played when flag is lost in the field and respawns itself...
2640 "noise4" sound played when flag is dropped by a player...
2641 "noise5" sound played when flag touches the ground... */
2642 spawnfunc(item_flag_neutral)
2644 if(!g_ctf) { delete(this); return; }
2645 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2647 ctf_FlagSetup(0, this);
2650 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2651 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2652 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.
2654 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2655 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2658 if(!g_ctf) { delete(this); return; }
2660 this.classname = "ctf_team";
2661 this.team = this.cnt + 1;
2664 // compatibility for quake maps
2665 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2666 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2667 spawnfunc(info_player_team1);
2668 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2669 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2670 spawnfunc(info_player_team2);
2671 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2672 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2674 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2675 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2677 // compatibility for wop maps
2678 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2679 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2680 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2681 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2682 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2683 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2691 void ctf_ScoreRules(int teams)
2693 CheckAllowedTeams(NULL);
2694 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
2695 field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2696 field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2697 field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2698 field(SP_CTF_PICKUPS, "pickups", 0);
2699 field(SP_CTF_FCKILLS, "fckills", 0);
2700 field(SP_CTF_RETURNS, "returns", 0);
2701 field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2705 // code from here on is just to support maps that don't have flag and team entities
2706 void ctf_SpawnTeam (string teamname, int teamcolor)
2708 entity this = new_pure(ctf_team);
2709 this.netname = teamname;
2710 this.cnt = teamcolor - 1;
2711 this.spawnfunc_checked = true;
2712 this.team = teamcolor;
2715 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2720 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2722 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2723 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2725 switch(tmp_entity.team)
2727 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2728 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2729 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2730 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2732 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2735 havocbot_ctf_calculate_middlepoint();
2737 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2739 ctf_teams = 0; // so set the default red and blue teams
2740 BITSET_ASSIGN(ctf_teams, BIT(0));
2741 BITSET_ASSIGN(ctf_teams, BIT(1));
2744 //ctf_teams = bound(2, ctf_teams, 4);
2746 // if no teams are found, spawn defaults
2747 if(find(NULL, classname, "ctf_team") == NULL)
2749 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2750 if(ctf_teams & BIT(0))
2751 ctf_SpawnTeam("Red", NUM_TEAM_1);
2752 if(ctf_teams & BIT(1))
2753 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2754 if(ctf_teams & BIT(2))
2755 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2756 if(ctf_teams & BIT(3))
2757 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2760 ctf_ScoreRules(ctf_teams);
2763 void ctf_Initialize()
2765 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2767 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2768 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2769 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2771 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);