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);
20 MUTATOR_ONROLLBACK_OR_REMOVE
22 // we actually cannot roll back ctf_Initialize here
23 // BUT: we don't need to! If this gets called, adding always
29 LOG_INFO("This is a game type and it cannot be removed at runtime.");
38 #include <common/vehicles/all.qh>
39 #include <server/teamplay.qh>
42 #include <lib/warpzone/common.qh>
44 bool autocvar_g_ctf_allow_vehicle_carry;
45 bool autocvar_g_ctf_allow_vehicle_touch;
46 bool autocvar_g_ctf_allow_monster_touch;
47 bool autocvar_g_ctf_throw;
48 float autocvar_g_ctf_throw_angle_max;
49 float autocvar_g_ctf_throw_angle_min;
50 int autocvar_g_ctf_throw_punish_count;
51 float autocvar_g_ctf_throw_punish_delay;
52 float autocvar_g_ctf_throw_punish_time;
53 float autocvar_g_ctf_throw_strengthmultiplier;
54 float autocvar_g_ctf_throw_velocity_forward;
55 float autocvar_g_ctf_throw_velocity_up;
56 float autocvar_g_ctf_drop_velocity_up;
57 float autocvar_g_ctf_drop_velocity_side;
58 bool autocvar_g_ctf_oneflag_reverse;
59 bool autocvar_g_ctf_portalteleport;
60 bool autocvar_g_ctf_pass;
61 float autocvar_g_ctf_pass_arc;
62 float autocvar_g_ctf_pass_arc_max;
63 float autocvar_g_ctf_pass_directional_max;
64 float autocvar_g_ctf_pass_directional_min;
65 float autocvar_g_ctf_pass_radius;
66 float autocvar_g_ctf_pass_wait;
67 bool autocvar_g_ctf_pass_request;
68 float autocvar_g_ctf_pass_turnrate;
69 float autocvar_g_ctf_pass_timelimit;
70 float autocvar_g_ctf_pass_velocity;
71 bool autocvar_g_ctf_dynamiclights;
72 float autocvar_g_ctf_flag_collect_delay;
73 float autocvar_g_ctf_flag_damageforcescale;
74 bool autocvar_g_ctf_flag_dropped_waypoint;
75 bool autocvar_g_ctf_flag_dropped_floatinwater;
76 bool autocvar_g_ctf_flag_glowtrails;
77 int autocvar_g_ctf_flag_health;
78 bool autocvar_g_ctf_flag_return;
79 bool autocvar_g_ctf_flag_return_carrying;
80 float autocvar_g_ctf_flag_return_carried_radius;
81 float autocvar_g_ctf_flag_return_time;
82 bool autocvar_g_ctf_flag_return_when_unreachable;
83 float autocvar_g_ctf_flag_return_damage;
84 float autocvar_g_ctf_flag_return_damage_delay;
85 float autocvar_g_ctf_flag_return_dropped;
86 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
87 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
88 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
89 float autocvar_g_ctf_flagcarrier_selfforcefactor;
90 float autocvar_g_ctf_flagcarrier_damagefactor;
91 float autocvar_g_ctf_flagcarrier_forcefactor;
92 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
93 bool autocvar_g_ctf_fullbrightflags;
94 bool autocvar_g_ctf_ignore_frags;
95 bool autocvar_g_ctf_score_ignore_fields;
96 int autocvar_g_ctf_score_capture;
97 int autocvar_g_ctf_score_capture_assist;
98 int autocvar_g_ctf_score_kill;
99 int autocvar_g_ctf_score_penalty_drop;
100 int autocvar_g_ctf_score_penalty_returned;
101 int autocvar_g_ctf_score_pickup_base;
102 int autocvar_g_ctf_score_pickup_dropped_early;
103 int autocvar_g_ctf_score_pickup_dropped_late;
104 int autocvar_g_ctf_score_return;
105 float autocvar_g_ctf_shield_force;
106 float autocvar_g_ctf_shield_max_ratio;
107 int autocvar_g_ctf_shield_min_negscore;
108 bool autocvar_g_ctf_stalemate;
109 int autocvar_g_ctf_stalemate_endcondition;
110 float autocvar_g_ctf_stalemate_time;
111 bool autocvar_g_ctf_reverse;
112 float autocvar_g_ctf_dropped_capture_delay;
113 float autocvar_g_ctf_dropped_capture_radius;
115 void ctf_FakeTimeLimit(entity e, float t)
118 WriteByte(MSG_ONE, 3); // svc_updatestat
119 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
121 WriteCoord(MSG_ONE, autocvar_timelimit);
123 WriteCoord(MSG_ONE, (t + 1) / 60);
126 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
128 if(autocvar_sv_eventlog)
129 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
130 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
133 void ctf_CaptureRecord(entity flag, entity player)
135 float cap_record = ctf_captimerecord;
136 float cap_time = (time - flag.ctf_pickuptime);
137 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
141 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
142 else if(!ctf_captimerecord)
143 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
144 else if(cap_time < cap_record)
145 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));
147 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));
149 // write that shit in the database
150 if(!ctf_oneflag) // but not in 1-flag mode
151 if((!ctf_captimerecord) || (cap_time < cap_record))
153 ctf_captimerecord = cap_time;
154 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
155 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
156 write_recordmarker(player, flag.ctf_pickuptime, cap_time);
159 if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
160 race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
163 bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
166 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
168 // automatically return if there's only 1 player on the team
169 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
173 bool ctf_Return_Customize(entity this, entity client)
175 // only to the carrier
176 return boolean(client == this.owner);
179 void ctf_FlagcarrierWaypoints(entity player)
181 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
182 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
183 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
184 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
186 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
188 if(!player.wps_enemyflagcarrier)
190 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
191 wp.colormod = WPCOLOR_ENEMYFC(player.team);
192 setcefc(wp, ctf_Stalemate_Customize);
194 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
195 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
198 if(!player.wps_flagreturn)
200 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
201 owp.colormod = '0 0.8 0.8';
202 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
203 setcefc(owp, ctf_Return_Customize);
208 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
210 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
211 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
212 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
213 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
216 if(current_height) // make sure we can actually do this arcing path
218 targpos = (to + ('0 0 1' * current_height));
219 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
220 if(trace_fraction < 1)
222 //print("normal arc line failed, trying to find new pos...");
223 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
224 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
225 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
226 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
227 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
230 else { targpos = to; }
232 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
234 vector desired_direction = normalize(targpos - from);
235 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
236 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
239 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
241 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
243 // directional tracing only
245 makevectors(passer_angle);
247 // find the closest point on the enemy to the center of the attack
248 float h; // hypotenuse, which is the distance between attacker to head
249 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
251 h = vlen(head_center - passer_center);
252 a = h * (normalize(head_center - passer_center) * v_forward);
254 vector nearest_on_line = (passer_center + a * v_forward);
255 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
257 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
258 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
260 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
265 else { return true; }
269 // =======================
270 // CaptureShield Functions
271 // =======================
273 bool ctf_CaptureShield_CheckStatus(entity p)
275 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
276 int players_worseeq, players_total;
278 if(ctf_captureshield_max_ratio <= 0)
281 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
282 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
283 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
284 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
286 sr = ((s - s2) + (s3 + s4));
288 if(sr >= -ctf_captureshield_min_negscore)
291 players_total = players_worseeq = 0;
292 FOREACH_CLIENT(IS_PLAYER(it), {
295 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
296 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
297 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
298 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
300 ser = ((se - se2) + (se3 + se4));
307 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
308 // use this rule here
310 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
316 void ctf_CaptureShield_Update(entity player, bool wanted_status)
318 bool updated_status = ctf_CaptureShield_CheckStatus(player);
319 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
321 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
322 player.ctf_captureshielded = updated_status;
326 bool ctf_CaptureShield_Customize(entity this, entity client)
328 if(!client.ctf_captureshielded) { return false; }
329 if(CTF_SAMETEAM(this, client)) { return false; }
334 void ctf_CaptureShield_Touch(entity this, entity toucher)
336 if(!toucher.ctf_captureshielded) { return; }
337 if(CTF_SAMETEAM(this, toucher)) { return; }
339 vector mymid = (this.absmin + this.absmax) * 0.5;
340 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
342 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
343 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
346 void ctf_CaptureShield_Spawn(entity flag)
348 entity shield = new(ctf_captureshield);
351 shield.team = flag.team;
352 settouch(shield, ctf_CaptureShield_Touch);
353 setcefc(shield, ctf_CaptureShield_Customize);
354 shield.effects = EF_ADDITIVE;
355 set_movetype(shield, MOVETYPE_NOCLIP);
356 shield.solid = SOLID_TRIGGER;
357 shield.avelocity = '7 0 11';
360 setorigin(shield, flag.origin);
361 setmodel(shield, MDL_CTF_SHIELD);
362 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
366 // ====================
367 // Drop/Pass/Throw Code
368 // ====================
370 void ctf_Handle_Drop(entity flag, entity player, int droptype)
373 player = (player ? player : flag.pass_sender);
376 set_movetype(flag, MOVETYPE_TOSS);
377 flag.takedamage = DAMAGE_YES;
378 flag.angles = '0 0 0';
379 flag.health = flag.max_flag_health;
380 flag.ctf_droptime = time;
381 flag.ctf_dropper = player;
382 flag.ctf_status = FLAG_DROPPED;
384 // messages and sounds
385 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
386 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
387 ctf_EventLog("dropped", player.team, player);
390 PlayerTeamScore_AddScore(player, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
391 PlayerScore_Add(player, SP_CTF_DROPS, 1);
394 if(autocvar_g_ctf_flag_dropped_waypoint) {
395 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);
396 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
399 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
401 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
402 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
405 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
407 if(droptype == DROP_PASS)
409 flag.pass_distance = 0;
410 flag.pass_sender = NULL;
411 flag.pass_target = NULL;
415 void ctf_Handle_Retrieve(entity flag, entity player)
417 entity sender = flag.pass_sender;
419 // transfer flag to player
421 flag.owner.flagcarried = flag;
422 GameRules_scoring_vip(player, true);
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;
480 GameRules_scoring_vip(flag.owner, false);
482 flag.solid = SOLID_TRIGGER;
483 flag.ctf_dropper = player;
484 flag.ctf_droptime = time;
485 navigation_dynamicgoal_set(flag);
487 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
494 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
495 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
496 WarpZone_RefSys_Copy(flag, receiver);
497 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
498 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
500 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
501 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
504 set_movetype(flag, MOVETYPE_FLY);
505 flag.takedamage = DAMAGE_NO;
506 flag.pass_sender = player;
507 flag.pass_target = receiver;
508 flag.ctf_status = FLAG_PASSING;
511 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
512 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
513 ctf_EventLog("pass", flag.team, player);
519 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'));
521 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)));
522 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
523 ctf_Handle_Drop(flag, player, droptype);
529 flag.velocity = '0 0 0'; // do nothing
536 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);
537 ctf_Handle_Drop(flag, player, droptype);
542 // kill old waypointsprite
543 WaypointSprite_Ping(player.wps_flagcarrier);
544 WaypointSprite_Kill(player.wps_flagcarrier);
546 if(player.wps_enemyflagcarrier)
547 WaypointSprite_Kill(player.wps_enemyflagcarrier);
549 if(player.wps_flagreturn)
550 WaypointSprite_Kill(player.wps_flagreturn);
553 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
556 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
558 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
565 void nades_GiveBonus(entity player, float score);
567 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
569 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
570 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
571 entity player_team_flag = NULL, tmp_entity;
572 float old_time, new_time;
574 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
575 if(CTF_DIFFTEAM(player, flag)) { return; }
576 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)
579 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
580 if(SAME_TEAM(tmp_entity, player))
582 player_team_flag = tmp_entity;
586 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
588 player.throw_prevtime = time;
589 player.throw_count = 0;
591 // messages and sounds
592 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
593 ctf_CaptureRecord(enemy_flag, player);
594 _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);
598 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
599 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
605 if(enemy_flag.score_capture || flag.score_capture)
606 pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
607 PlayerTeamScore_AddScore(player, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
609 if(enemy_flag.score_team_capture || flag.score_team_capture)
610 capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
611 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, ((capscore) ? capscore : 1));
613 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
614 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
615 if(!old_time || new_time < old_time)
616 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
619 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
620 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
623 if(capturetype == CAPTURE_NORMAL)
625 WaypointSprite_Kill(player.wps_flagcarrier);
626 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
628 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
629 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
633 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
634 ctf_RespawnFlag(enemy_flag);
637 void ctf_Handle_Return(entity flag, entity player)
639 // messages and sounds
640 if(IS_MONSTER(player))
642 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
646 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
647 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
649 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
650 ctf_EventLog("return", flag.team, player);
653 if(IS_PLAYER(player))
655 PlayerTeamScore_AddScore(player, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
656 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
658 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
661 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
665 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
666 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
667 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
671 if(player.flagcarried == flag)
672 WaypointSprite_Kill(player.wps_flagcarrier);
675 ctf_RespawnFlag(flag);
678 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
681 float pickup_dropped_score; // used to calculate dropped pickup score
683 // attach the flag to the player
685 player.flagcarried = flag;
686 GameRules_scoring_vip(player, true);
689 setattachment(flag, player.vehicle, "");
690 setorigin(flag, VEHICLE_FLAG_OFFSET);
691 flag.scale = VEHICLE_FLAG_SCALE;
695 setattachment(flag, player, "");
696 setorigin(flag, FLAG_CARRY_OFFSET);
700 set_movetype(flag, MOVETYPE_NONE);
701 flag.takedamage = DAMAGE_NO;
702 flag.solid = SOLID_NOT;
703 flag.angles = '0 0 0';
704 flag.ctf_status = FLAG_CARRY;
708 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
709 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
713 // messages and sounds
714 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
716 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
718 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
719 else if(CTF_DIFFTEAM(player, flag))
720 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
722 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
724 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
727 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); });
730 FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
731 if(CTF_SAMETEAM(flag, it))
732 if(SAME_TEAM(player, it))
733 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
735 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);
738 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
741 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
742 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
747 PlayerTeamScore_AddScore(player, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
748 ctf_EventLog("steal", flag.team, player);
754 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);
755 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);
756 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
757 PlayerTeamScore_AddScore(player, pickup_dropped_score);
758 ctf_EventLog("pickup", flag.team, player);
766 if(pickuptype == PICKUP_BASE)
768 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
769 if((player.speedrunning) && (ctf_captimerecord))
770 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
774 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
777 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
778 ctf_FlagcarrierWaypoints(player);
779 WaypointSprite_Ping(player.wps_flagcarrier);
783 // ===================
784 // Main Flag Functions
785 // ===================
787 void ctf_CheckFlagReturn(entity flag, int returntype)
789 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
791 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
793 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
798 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
800 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
801 case RETURN_SPEEDRUN:
802 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
803 case RETURN_NEEDKILL:
804 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
807 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
809 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
810 ctf_EventLog("returned", flag.team, NULL);
811 ctf_RespawnFlag(flag);
816 bool ctf_Stalemate_Customize(entity this, entity client)
818 // make spectators see what the player would see
819 entity e = WaypointSprite_getviewentity(client);
820 entity wp_owner = this.owner;
823 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
824 if(SAME_TEAM(wp_owner, e)) { return false; }
825 if(!IS_PLAYER(e)) { return false; }
830 void ctf_CheckStalemate()
833 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
836 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
838 // build list of stale flags
839 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
841 if(autocvar_g_ctf_stalemate)
842 if(tmp_entity.ctf_status != FLAG_BASE)
843 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
845 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
846 ctf_staleflaglist = tmp_entity;
848 switch(tmp_entity.team)
850 case NUM_TEAM_1: ++stale_red_flags; break;
851 case NUM_TEAM_2: ++stale_blue_flags; break;
852 case NUM_TEAM_3: ++stale_yellow_flags; break;
853 case NUM_TEAM_4: ++stale_pink_flags; break;
854 default: ++stale_neutral_flags; break;
860 stale_flags = (stale_neutral_flags >= 1);
862 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
864 if(ctf_oneflag && stale_flags == 1)
865 ctf_stalemate = true;
866 else if(stale_flags >= 2)
867 ctf_stalemate = true;
868 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
869 { ctf_stalemate = false; wpforenemy_announced = false; }
870 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
871 { ctf_stalemate = false; wpforenemy_announced = false; }
873 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
876 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
878 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
880 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);
881 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
882 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
886 if (!wpforenemy_announced)
888 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)); });
890 wpforenemy_announced = true;
895 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
897 if(ITEM_DAMAGE_NEEDKILL(deathtype))
899 if(autocvar_g_ctf_flag_return_damage_delay)
900 this.ctf_flagdamaged_byworld = true;
904 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
908 if(autocvar_g_ctf_flag_return_damage)
910 // reduce health and check if it should be returned
911 this.health = this.health - damage;
912 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
917 void ctf_FlagThink(entity this)
922 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
925 if(this == ctf_worldflaglist) // only for the first flag
926 FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
929 if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
930 LOG_TRACE("wtf the flag got squashed?");
931 tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
932 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
933 setsize(this, this.m_mins, this.m_maxs);
937 switch(this.ctf_status)
941 if(autocvar_g_ctf_dropped_capture_radius)
943 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
944 if(tmp_entity.ctf_status == FLAG_DROPPED)
945 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
946 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
947 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
954 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
956 if(autocvar_g_ctf_flag_dropped_floatinwater)
958 vector midpoint = ((this.absmin + this.absmax) * 0.5);
959 if(pointcontents(midpoint) == CONTENT_WATER)
961 this.velocity = this.velocity * 0.5;
963 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
964 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
966 { set_movetype(this, MOVETYPE_FLY); }
968 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
970 if(autocvar_g_ctf_flag_return_dropped)
972 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
975 ctf_CheckFlagReturn(this, RETURN_DROPPED);
979 if(this.ctf_flagdamaged_byworld)
981 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
982 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
985 else if(autocvar_g_ctf_flag_return_time)
987 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
988 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
996 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
999 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
1001 CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
1002 ImpulseCommands(this.owner);
1004 if(autocvar_g_ctf_stalemate)
1006 if(time >= wpforenemy_nextthink)
1008 ctf_CheckStalemate();
1009 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1012 if(CTF_SAMETEAM(this, this.owner) && this.team)
1014 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1015 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1016 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1017 ctf_Handle_Return(this, this.owner);
1024 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1025 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1026 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1028 if((this.pass_target == NULL)
1029 || (IS_DEAD(this.pass_target))
1030 || (this.pass_target.flagcarried)
1031 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1032 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1033 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1035 // give up, pass failed
1036 ctf_Handle_Drop(this, NULL, DROP_PASS);
1040 // still a viable target, go for it
1041 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1046 default: // this should never happen
1048 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1054 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1057 if(game_stopped) return;
1058 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1060 bool is_not_monster = (!IS_MONSTER(toucher));
1062 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1063 if(ITEM_TOUCH_NEEDKILL())
1065 if(!autocvar_g_ctf_flag_return_damage_delay)
1068 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1070 if(!flag.ctf_flagdamaged_byworld) { return; }
1073 // special touch behaviors
1074 if(STAT(FROZEN, toucher)) { return; }
1075 else if(IS_VEHICLE(toucher))
1077 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1078 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1080 return; // do nothing
1082 else if(IS_MONSTER(toucher))
1084 if(!autocvar_g_ctf_allow_monster_touch)
1085 return; // do nothing
1087 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1089 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1091 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1092 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1093 flag.wait = time + FLAG_TOUCHRATE;
1097 else if(IS_DEAD(toucher)) { return; }
1099 switch(flag.ctf_status)
1105 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1106 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1107 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1108 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1110 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1111 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1112 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)
1114 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1115 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1117 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1118 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1124 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1125 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1126 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1127 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1133 LOG_TRACE("Someone touched a flag even though it was being carried?");
1139 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1141 if(DIFF_TEAM(toucher, flag.pass_sender))
1143 if(ctf_Immediate_Return_Allowed(flag, toucher))
1144 ctf_Handle_Return(flag, toucher);
1145 else if(is_not_monster && (!toucher.flagcarried))
1146 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1148 else if(!toucher.flagcarried)
1149 ctf_Handle_Retrieve(flag, toucher);
1156 .float last_respawn;
1157 void ctf_RespawnFlag(entity flag)
1159 // check for flag respawn being called twice in a row
1160 if(flag.last_respawn > time - 0.5)
1161 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1163 flag.last_respawn = time;
1165 // reset the player (if there is one)
1166 if((flag.owner) && (flag.owner.flagcarried == flag))
1168 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1169 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1170 WaypointSprite_Kill(flag.wps_flagcarrier);
1172 flag.owner.flagcarried = NULL;
1173 GameRules_scoring_vip(flag.owner, false);
1175 if(flag.speedrunning)
1176 ctf_FakeTimeLimit(flag.owner, -1);
1179 if((flag.owner) && (flag.owner.vehicle))
1180 flag.scale = FLAG_SCALE;
1182 if(flag.ctf_status == FLAG_DROPPED)
1183 { WaypointSprite_Kill(flag.wps_flagdropped); }
1186 setattachment(flag, NULL, "");
1187 setorigin(flag, flag.ctf_spawnorigin);
1189 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1190 flag.takedamage = DAMAGE_NO;
1191 flag.health = flag.max_flag_health;
1192 flag.solid = SOLID_TRIGGER;
1193 flag.velocity = '0 0 0';
1194 flag.angles = flag.mangle;
1195 flag.flags = FL_ITEM | FL_NOTARGET;
1197 flag.ctf_status = FLAG_BASE;
1199 flag.pass_distance = 0;
1200 flag.pass_sender = NULL;
1201 flag.pass_target = NULL;
1202 flag.ctf_dropper = NULL;
1203 flag.ctf_pickuptime = 0;
1204 flag.ctf_droptime = 0;
1205 flag.ctf_flagdamaged_byworld = false;
1206 navigation_dynamicgoal_unset(flag);
1208 ctf_CheckStalemate();
1211 void ctf_Reset(entity this)
1213 if(this.owner && IS_PLAYER(this.owner))
1214 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1216 ctf_RespawnFlag(this);
1219 bool ctf_FlagBase_Customize(entity this, entity client)
1221 entity e = WaypointSprite_getviewentity(client);
1222 entity wp_owner = this.owner;
1223 entity flag = e.flagcarried;
1224 if(flag && CTF_SAMETEAM(e, flag))
1226 if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1231 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1234 waypoint_spawnforitem_force(this, this.origin);
1235 navigation_dynamicgoal_init(this, true);
1241 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1242 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1243 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1244 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1245 default: basename = WP_FlagBaseNeutral; break;
1248 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1249 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1250 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1251 setcefc(wp, ctf_FlagBase_Customize);
1253 // captureshield setup
1254 ctf_CaptureShield_Spawn(this);
1259 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1262 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1263 ctf_worldflaglist = flag;
1265 setattachment(flag, NULL, "");
1267 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1268 flag.team = teamnumber;
1269 flag.classname = "item_flag_team";
1270 flag.target = "###item###"; // wut?
1271 flag.flags = FL_ITEM | FL_NOTARGET;
1272 IL_PUSH(g_items, flag);
1273 flag.solid = SOLID_TRIGGER;
1274 flag.takedamage = DAMAGE_NO;
1275 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1276 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1277 flag.health = flag.max_flag_health;
1278 flag.event_damage = ctf_FlagDamage;
1279 flag.pushable = true;
1280 flag.teleportable = TELEPORT_NORMAL;
1281 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1282 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1283 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1284 if(flag.damagedbycontents)
1285 IL_PUSH(g_damagedbycontents, flag);
1286 flag.velocity = '0 0 0';
1287 flag.mangle = flag.angles;
1288 flag.reset = ctf_Reset;
1289 settouch(flag, ctf_FlagTouch);
1290 setthink(flag, ctf_FlagThink);
1291 flag.nextthink = time + FLAG_THINKRATE;
1292 flag.ctf_status = FLAG_BASE;
1294 // crudely force them all to 0
1295 if(autocvar_g_ctf_score_ignore_fields)
1296 flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1298 string teamname = Static_Team_ColorName_Lower(teamnumber);
1300 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1301 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1302 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1303 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1304 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1305 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1309 if(flag.s == "") flag.s = b; \
1310 precache_sound(flag.s);
1312 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1313 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1314 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1315 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1316 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1317 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1318 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1322 precache_model(flag.model);
1325 _setmodel(flag, flag.model); // precision set below
1326 setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
1327 flag.m_mins = flag.mins; // store these for squash checks
1328 flag.m_maxs = flag.maxs;
1329 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1331 if(autocvar_g_ctf_flag_glowtrails)
1335 case NUM_TEAM_1: flag.glow_color = 251; break;
1336 case NUM_TEAM_2: flag.glow_color = 210; break;
1337 case NUM_TEAM_3: flag.glow_color = 110; break;
1338 case NUM_TEAM_4: flag.glow_color = 145; break;
1339 default: flag.glow_color = 254; break;
1341 flag.glow_size = 25;
1342 flag.glow_trail = 1;
1345 flag.effects |= EF_LOWPRECISION;
1346 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1347 if(autocvar_g_ctf_dynamiclights)
1351 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1352 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1353 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1354 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1355 default: flag.effects |= EF_DIMLIGHT; break;
1360 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1362 flag.dropped_origin = flag.origin;
1363 flag.noalign = true;
1364 set_movetype(flag, MOVETYPE_NONE);
1366 else // drop to floor, automatically find a platform and set that as spawn origin
1368 flag.noalign = false;
1370 set_movetype(flag, MOVETYPE_NONE);
1373 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1381 // NOTE: LEGACY CODE, needs to be re-written!
1383 void havocbot_ctf_calculate_middlepoint()
1387 vector fo = '0 0 0';
1390 f = ctf_worldflaglist;
1395 f = f.ctf_worldflagnext;
1401 havocbot_middlepoint = s / n;
1402 havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
1404 havocbot_symmetryaxis_equation = '0 0 0';
1407 // for symmetrical editing of waypoints
1408 entity f1 = ctf_worldflaglist;
1409 entity f2 = f1.ctf_worldflagnext;
1410 float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
1411 float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
1412 havocbot_symmetryaxis_equation.x = m;
1413 havocbot_symmetryaxis_equation.y = q;
1415 // store number of flags in this otherwise unused vector component
1416 havocbot_symmetryaxis_equation.z = n;
1420 entity havocbot_ctf_find_flag(entity bot)
1423 f = ctf_worldflaglist;
1426 if (CTF_SAMETEAM(bot, f))
1428 f = f.ctf_worldflagnext;
1433 entity havocbot_ctf_find_enemy_flag(entity bot)
1436 f = ctf_worldflaglist;
1441 if(CTF_DIFFTEAM(bot, f))
1448 else if(!bot.flagcarried)
1452 else if (CTF_DIFFTEAM(bot, f))
1454 f = f.ctf_worldflagnext;
1459 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1466 FOREACH_CLIENT(IS_PLAYER(it), {
1467 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1470 if(vdist(it.origin - org, <, tc_radius))
1479 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1482 head = ctf_worldflaglist;
1485 if (CTF_SAMETEAM(this, head))
1487 head = head.ctf_worldflagnext;
1490 navigation_routerating(this, head, ratingscale, 10000);
1494 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1497 head = ctf_worldflaglist;
1500 if (CTF_SAMETEAM(this, head))
1502 if (this.flagcarried)
1503 if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
1505 head = head.ctf_worldflagnext; // skip base if it has a different group
1510 head = head.ctf_worldflagnext;
1515 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1518 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1521 head = ctf_worldflaglist;
1526 if(CTF_DIFFTEAM(this, head))
1530 if(this.flagcarried)
1533 else if(!this.flagcarried)
1537 else if(CTF_DIFFTEAM(this, head))
1539 head = head.ctf_worldflagnext;
1542 navigation_routerating(this, head, ratingscale, 10000);
1545 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1547 if (!bot_waypoints_for_items)
1549 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1555 head = havocbot_ctf_find_enemy_flag(this);
1560 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1563 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1567 mf = havocbot_ctf_find_flag(this);
1569 if(mf.ctf_status == FLAG_BASE)
1573 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1576 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1579 head = ctf_worldflaglist;
1582 // flag is out in the field
1583 if(head.ctf_status != FLAG_BASE)
1584 if(head.tag_entity==NULL) // dropped
1588 if(vdist(org - head.origin, <, df_radius))
1589 navigation_routerating(this, head, ratingscale, 10000);
1592 navigation_routerating(this, head, ratingscale, 10000);
1595 head = head.ctf_worldflagnext;
1599 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1601 IL_EACH(g_items, it.bot_pickup,
1603 // gather health and armor only
1605 if (it.health || it.armorvalue)
1606 if (vdist(it.origin - org, <, sradius))
1608 // get the value of the item
1609 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1611 navigation_routerating(this, it, t * ratingscale, 500);
1616 void havocbot_ctf_reset_role(entity this)
1618 float cdefense, cmiddle, coffense;
1626 if (this.flagcarried)
1628 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1632 mf = havocbot_ctf_find_flag(this);
1633 ef = havocbot_ctf_find_enemy_flag(this);
1635 // Retrieve stolen flag
1636 if(mf.ctf_status!=FLAG_BASE)
1638 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1642 // If enemy flag is taken go to the middle to intercept pursuers
1643 if(ef.ctf_status!=FLAG_BASE)
1645 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1649 // if there is only me on the team switch to offense
1651 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
1655 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1659 // Evaluate best position to take
1660 // Count mates on middle position
1661 cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
1663 // Count mates on defense position
1664 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
1666 // Count mates on offense position
1667 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
1669 if(cdefense<=coffense)
1670 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1671 else if(coffense<=cmiddle)
1672 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1674 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1677 void havocbot_role_ctf_carrier(entity this)
1681 havocbot_ctf_reset_role(this);
1685 if (this.flagcarried == NULL)
1687 havocbot_ctf_reset_role(this);
1691 if (this.bot_strategytime < time)
1693 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1695 navigation_goalrating_start(this);
1697 havocbot_goalrating_ctf_enemybase(this, 50000);
1699 havocbot_goalrating_ctf_ourbase(this, 50000);
1702 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1704 navigation_goalrating_end(this);
1706 if (this.goalentity)
1707 this.havocbot_cantfindflag = time + 10;
1708 else if (time > this.havocbot_cantfindflag)
1710 // Can't navigate to my own base, suicide!
1711 // TODO: drop it and wander around
1712 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1718 void havocbot_role_ctf_escort(entity this)
1724 havocbot_ctf_reset_role(this);
1728 if (this.flagcarried)
1730 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1734 // If enemy flag is back on the base switch to previous role
1735 ef = havocbot_ctf_find_enemy_flag(this);
1736 if(ef.ctf_status==FLAG_BASE)
1738 this.havocbot_role = this.havocbot_previous_role;
1739 this.havocbot_role_timeout = 0;
1743 // If the flag carrier reached the base switch to defense
1744 mf = havocbot_ctf_find_flag(this);
1745 if(mf.ctf_status!=FLAG_BASE)
1746 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1748 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1752 // Set the role timeout if necessary
1753 if (!this.havocbot_role_timeout)
1755 this.havocbot_role_timeout = time + random() * 30 + 60;
1758 // If nothing happened just switch to previous role
1759 if (time > this.havocbot_role_timeout)
1761 this.havocbot_role = this.havocbot_previous_role;
1762 this.havocbot_role_timeout = 0;
1766 // Chase the flag carrier
1767 if (this.bot_strategytime < time)
1769 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1770 navigation_goalrating_start(this);
1771 havocbot_goalrating_ctf_enemyflag(this, 30000);
1772 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1773 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1774 navigation_goalrating_end(this);
1778 void havocbot_role_ctf_offense(entity this)
1785 havocbot_ctf_reset_role(this);
1789 if (this.flagcarried)
1791 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1796 mf = havocbot_ctf_find_flag(this);
1797 ef = havocbot_ctf_find_enemy_flag(this);
1800 if(mf.ctf_status!=FLAG_BASE)
1803 pos = mf.tag_entity.origin;
1807 // Try to get it if closer than the enemy base
1808 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1810 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1815 // Escort flag carrier
1816 if(ef.ctf_status!=FLAG_BASE)
1819 pos = ef.tag_entity.origin;
1823 if(vdist(pos - mf.dropped_origin, >, 700))
1825 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1830 // About to fail, switch to middlefield
1833 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1837 // Set the role timeout if necessary
1838 if (!this.havocbot_role_timeout)
1839 this.havocbot_role_timeout = time + 120;
1841 if (time > this.havocbot_role_timeout)
1843 havocbot_ctf_reset_role(this);
1847 if (this.bot_strategytime < time)
1849 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1850 navigation_goalrating_start(this);
1851 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1852 havocbot_goalrating_ctf_enemybase(this, 20000);
1853 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1854 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1855 navigation_goalrating_end(this);
1859 // Retriever (temporary role):
1860 void havocbot_role_ctf_retriever(entity this)
1866 havocbot_ctf_reset_role(this);
1870 if (this.flagcarried)
1872 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1876 // If flag is back on the base switch to previous role
1877 mf = havocbot_ctf_find_flag(this);
1878 if(mf.ctf_status==FLAG_BASE)
1880 if(this.goalcurrent == mf)
1882 navigation_clearroute(this);
1883 this.bot_strategytime = 0;
1885 havocbot_ctf_reset_role(this);
1889 if (!this.havocbot_role_timeout)
1890 this.havocbot_role_timeout = time + 20;
1892 if (time > this.havocbot_role_timeout)
1894 havocbot_ctf_reset_role(this);
1898 if (this.bot_strategytime < time)
1903 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1904 navigation_goalrating_start(this);
1905 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1906 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1907 havocbot_goalrating_ctf_enemybase(this, 30000);
1908 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1909 navigation_goalrating_end(this);
1913 void havocbot_role_ctf_middle(entity this)
1919 havocbot_ctf_reset_role(this);
1923 if (this.flagcarried)
1925 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1929 mf = havocbot_ctf_find_flag(this);
1930 if(mf.ctf_status!=FLAG_BASE)
1932 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1936 if (!this.havocbot_role_timeout)
1937 this.havocbot_role_timeout = time + 10;
1939 if (time > this.havocbot_role_timeout)
1941 havocbot_ctf_reset_role(this);
1945 if (this.bot_strategytime < time)
1949 org = havocbot_middlepoint;
1950 org.z = this.origin.z;
1952 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1953 navigation_goalrating_start(this);
1954 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1955 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1956 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
1957 havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
1958 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1959 havocbot_goalrating_ctf_enemybase(this, 2500);
1960 navigation_goalrating_end(this);
1964 void havocbot_role_ctf_defense(entity this)
1970 havocbot_ctf_reset_role(this);
1974 if (this.flagcarried)
1976 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1980 // If own flag was captured
1981 mf = havocbot_ctf_find_flag(this);
1982 if(mf.ctf_status!=FLAG_BASE)
1984 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1988 if (!this.havocbot_role_timeout)
1989 this.havocbot_role_timeout = time + 30;
1991 if (time > this.havocbot_role_timeout)
1993 havocbot_ctf_reset_role(this);
1996 if (this.bot_strategytime < time)
1998 vector org = mf.dropped_origin;
2000 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2001 navigation_goalrating_start(this);
2003 // if enemies are closer to our base, go there
2004 entity closestplayer = NULL;
2005 float distance, bestdistance = 10000;
2006 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
2007 distance = vlen(org - it.origin);
2008 if(distance<bestdistance)
2011 bestdistance = distance;
2016 if(DIFF_TEAM(closestplayer, this))
2017 if(vdist(org - this.origin, >, 1000))
2018 if(checkpvs(this.origin,closestplayer)||random()<0.5)
2019 havocbot_goalrating_ctf_ourbase(this, 30000);
2021 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
2022 havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
2023 havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
2024 havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
2025 havocbot_goalrating_items(this, 5000, this.origin, 10000);
2026 navigation_goalrating_end(this);
2030 void havocbot_role_ctf_setrole(entity bot, int role)
2032 string s = "(null)";
2035 case HAVOCBOT_CTF_ROLE_CARRIER:
2037 bot.havocbot_role = havocbot_role_ctf_carrier;
2038 bot.havocbot_role_timeout = 0;
2039 bot.havocbot_cantfindflag = time + 10;
2040 bot.bot_strategytime = 0;
2042 case HAVOCBOT_CTF_ROLE_DEFENSE:
2044 bot.havocbot_role = havocbot_role_ctf_defense;
2045 bot.havocbot_role_timeout = 0;
2047 case HAVOCBOT_CTF_ROLE_MIDDLE:
2049 bot.havocbot_role = havocbot_role_ctf_middle;
2050 bot.havocbot_role_timeout = 0;
2052 case HAVOCBOT_CTF_ROLE_OFFENSE:
2054 bot.havocbot_role = havocbot_role_ctf_offense;
2055 bot.havocbot_role_timeout = 0;
2057 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2059 bot.havocbot_previous_role = bot.havocbot_role;
2060 bot.havocbot_role = havocbot_role_ctf_retriever;
2061 bot.havocbot_role_timeout = time + 10;
2062 bot.bot_strategytime = 0;
2064 case HAVOCBOT_CTF_ROLE_ESCORT:
2066 bot.havocbot_previous_role = bot.havocbot_role;
2067 bot.havocbot_role = havocbot_role_ctf_escort;
2068 bot.havocbot_role_timeout = time + 30;
2069 bot.bot_strategytime = 0;
2072 LOG_TRACE(bot.netname, " switched to ", s);
2080 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2082 entity player = M_ARGV(0, entity);
2084 int t = 0, t2 = 0, t3 = 0;
2085 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)
2087 // initially clear items so they can be set as necessary later.
2088 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2089 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2090 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2091 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2092 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2093 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2095 // scan through all the flags and notify the client about them
2096 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2098 if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2099 if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2100 if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2101 if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2102 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; }
2104 switch(flag.ctf_status)
2109 if((flag.owner == player) || (flag.pass_sender == player))
2110 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2112 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2117 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2123 // item for stopping players from capturing the flag too often
2124 if(player.ctf_captureshielded)
2125 player.ctf_flagstatus |= CTF_SHIELDED;
2128 player.ctf_flagstatus |= CTF_STALEMATE;
2130 // update the health of the flag carrier waypointsprite
2131 if(player.wps_flagcarrier)
2132 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2135 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2137 entity frag_attacker = M_ARGV(1, entity);
2138 entity frag_target = M_ARGV(2, entity);
2139 float frag_damage = M_ARGV(4, float);
2140 vector frag_force = M_ARGV(6, vector);
2142 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2144 if(frag_target == frag_attacker) // damage done to yourself
2146 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2147 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2149 else // damage done to everyone else
2151 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2152 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2155 M_ARGV(4, float) = frag_damage;
2156 M_ARGV(6, vector) = frag_force;
2158 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2160 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)))
2161 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2163 frag_target.wps_helpme_time = time;
2164 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2166 // todo: add notification for when flag carrier needs help?
2170 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2172 entity frag_attacker = M_ARGV(1, entity);
2173 entity frag_target = M_ARGV(2, entity);
2175 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2177 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2178 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2181 if(frag_target.flagcarried)
2183 entity tmp_entity = frag_target.flagcarried;
2184 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2185 tmp_entity.ctf_dropper = NULL;
2189 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2191 M_ARGV(2, float) = 0; // frag score
2192 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2195 void ctf_RemovePlayer(entity player)
2197 if(player.flagcarried)
2198 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2200 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2202 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2203 if(flag.pass_target == player) { flag.pass_target = NULL; }
2204 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2208 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2210 entity player = M_ARGV(0, entity);
2212 ctf_RemovePlayer(player);
2215 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2217 entity player = M_ARGV(0, entity);
2219 ctf_RemovePlayer(player);
2222 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2224 if(!autocvar_g_ctf_leaderboard)
2227 entity player = M_ARGV(0, entity);
2229 if(IS_REAL_CLIENT(player))
2231 for(int i = 1; i <= RANKINGS_CNT; ++i)
2233 race_SendRankings(i, 0, 0, MSG_ONE);
2238 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2240 if(!autocvar_g_ctf_leaderboard)
2243 entity player = M_ARGV(0, entity);
2245 if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
2247 if (!player.stored_netname)
2248 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2249 if(player.stored_netname != player.netname)
2251 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2252 strunzone(player.stored_netname);
2253 player.stored_netname = strzone(player.netname);
2258 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2260 entity player = M_ARGV(0, entity);
2262 if(player.flagcarried)
2263 if(!autocvar_g_ctf_portalteleport)
2264 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2267 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2269 if(MUTATOR_RETURNVALUE || game_stopped) return;
2271 entity player = M_ARGV(0, entity);
2273 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2275 // pass the flag to a team mate
2276 if(autocvar_g_ctf_pass)
2278 entity head, closest_target = NULL;
2279 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2281 while(head) // find the closest acceptable target to pass to
2283 if(IS_PLAYER(head) && !IS_DEAD(head))
2284 if(head != player && SAME_TEAM(head, player))
2285 if(!head.speedrunning && !head.vehicle)
2287 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2288 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2289 vector passer_center = CENTER_OR_VIEWOFS(player);
2291 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2293 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2295 if(IS_BOT_CLIENT(head))
2297 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2298 ctf_Handle_Throw(head, player, DROP_PASS);
2302 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2303 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2305 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2308 else if(player.flagcarried && !head.flagcarried)
2312 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2313 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2314 { closest_target = head; }
2316 else { closest_target = head; }
2323 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2326 // throw the flag in front of you
2327 if(autocvar_g_ctf_throw && player.flagcarried)
2329 if(player.throw_count == -1)
2331 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2333 player.throw_prevtime = time;
2334 player.throw_count = 1;
2335 ctf_Handle_Throw(player, NULL, DROP_THROW);
2340 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2346 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2347 else { player.throw_count += 1; }
2348 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2350 player.throw_prevtime = time;
2351 ctf_Handle_Throw(player, NULL, DROP_THROW);
2358 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2360 entity player = M_ARGV(0, entity);
2362 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2364 player.wps_helpme_time = time;
2365 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2367 else // create a normal help me waypointsprite
2369 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2370 WaypointSprite_Ping(player.wps_helpme);
2376 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2378 entity player = M_ARGV(0, entity);
2379 entity veh = M_ARGV(1, entity);
2381 if(player.flagcarried)
2383 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2385 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2389 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2390 setattachment(player.flagcarried, veh, "");
2391 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2392 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2393 //player.flagcarried.angles = '0 0 0';
2399 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2401 entity player = M_ARGV(0, entity);
2403 if(player.flagcarried)
2405 setattachment(player.flagcarried, player, "");
2406 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2407 player.flagcarried.scale = FLAG_SCALE;
2408 player.flagcarried.angles = '0 0 0';
2409 player.flagcarried.nodrawtoclient = NULL;
2414 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2416 entity player = M_ARGV(0, entity);
2418 if(player.flagcarried)
2420 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2421 ctf_RespawnFlag(player.flagcarried);
2426 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2428 entity flag; // temporary entity for the search method
2430 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2432 switch(flag.ctf_status)
2437 // lock the flag, game is over
2438 set_movetype(flag, MOVETYPE_NONE);
2439 flag.takedamage = DAMAGE_NO;
2440 flag.solid = SOLID_NOT;
2441 flag.nextthink = false; // stop thinking
2443 //dprint("stopping the ", flag.netname, " from moving.\n");
2451 // do nothing for these flags
2458 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2460 entity bot = M_ARGV(0, entity);
2462 havocbot_ctf_reset_role(bot);
2466 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2468 //M_ARGV(0, float) = ctf_teams;
2469 M_ARGV(1, string) = "ctf_team";
2473 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2475 entity spectatee = M_ARGV(0, entity);
2476 entity client = M_ARGV(1, entity);
2478 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2481 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2483 int record_page = M_ARGV(0, int);
2484 string ret_string = M_ARGV(1, string);
2486 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2488 if (MapInfo_Get_ByID(i))
2490 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2496 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2497 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2501 M_ARGV(1, string) = ret_string;
2504 bool superspec_Spectate(entity this, entity targ); // TODO
2505 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2506 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2508 entity player = M_ARGV(0, entity);
2509 string cmd_name = M_ARGV(1, string);
2510 int cmd_argc = M_ARGV(2, int);
2512 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2514 if(cmd_name == "followfc")
2526 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2527 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2528 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2529 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2533 FOREACH_CLIENT(IS_PLAYER(it), {
2534 if(it.flagcarried && (it.team == _team || _team == 0))
2537 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2538 continue; // already spectating this fc, try another
2539 return superspec_Spectate(player, it);
2544 superspec_msg("", "", player, "No active flag carrier\n", 1);
2549 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2551 entity frag_target = M_ARGV(0, entity);
2553 if(frag_target.flagcarried)
2554 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2562 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2563 CTF flag for team one (Red).
2565 "angle" Angle the flag will point (minus 90 degrees)...
2566 "model" model to use, note this needs red and blue as skins 0 and 1...
2567 "noise" sound played when flag is picked up...
2568 "noise1" sound played when flag is returned by a teammate...
2569 "noise2" sound played when flag is captured...
2570 "noise3" sound played when flag is lost in the field and respawns itself...
2571 "noise4" sound played when flag is dropped by a player...
2572 "noise5" sound played when flag touches the ground... */
2573 spawnfunc(item_flag_team1)
2575 if(!g_ctf) { delete(this); return; }
2577 ctf_FlagSetup(NUM_TEAM_1, this);
2580 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2581 CTF flag for team two (Blue).
2583 "angle" Angle the flag will point (minus 90 degrees)...
2584 "model" model to use, note this needs red and blue as skins 0 and 1...
2585 "noise" sound played when flag is picked up...
2586 "noise1" sound played when flag is returned by a teammate...
2587 "noise2" sound played when flag is captured...
2588 "noise3" sound played when flag is lost in the field and respawns itself...
2589 "noise4" sound played when flag is dropped by a player...
2590 "noise5" sound played when flag touches the ground... */
2591 spawnfunc(item_flag_team2)
2593 if(!g_ctf) { delete(this); return; }
2595 ctf_FlagSetup(NUM_TEAM_2, this);
2598 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2599 CTF flag for team three (Yellow).
2601 "angle" Angle the flag will point (minus 90 degrees)...
2602 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2603 "noise" sound played when flag is picked up...
2604 "noise1" sound played when flag is returned by a teammate...
2605 "noise2" sound played when flag is captured...
2606 "noise3" sound played when flag is lost in the field and respawns itself...
2607 "noise4" sound played when flag is dropped by a player...
2608 "noise5" sound played when flag touches the ground... */
2609 spawnfunc(item_flag_team3)
2611 if(!g_ctf) { delete(this); return; }
2613 ctf_FlagSetup(NUM_TEAM_3, this);
2616 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2617 CTF flag for team four (Pink).
2619 "angle" Angle the flag will point (minus 90 degrees)...
2620 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2621 "noise" sound played when flag is picked up...
2622 "noise1" sound played when flag is returned by a teammate...
2623 "noise2" sound played when flag is captured...
2624 "noise3" sound played when flag is lost in the field and respawns itself...
2625 "noise4" sound played when flag is dropped by a player...
2626 "noise5" sound played when flag touches the ground... */
2627 spawnfunc(item_flag_team4)
2629 if(!g_ctf) { delete(this); return; }
2631 ctf_FlagSetup(NUM_TEAM_4, this);
2634 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2637 "angle" Angle the flag will point (minus 90 degrees)...
2638 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2639 "noise" sound played when flag is picked up...
2640 "noise1" sound played when flag is returned by a teammate...
2641 "noise2" sound played when flag is captured...
2642 "noise3" sound played when flag is lost in the field and respawns itself...
2643 "noise4" sound played when flag is dropped by a player...
2644 "noise5" sound played when flag touches the ground... */
2645 spawnfunc(item_flag_neutral)
2647 if(!g_ctf) { delete(this); return; }
2648 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2650 ctf_FlagSetup(0, this);
2653 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2654 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2655 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.
2657 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2658 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2661 if(!g_ctf) { delete(this); return; }
2663 this.classname = "ctf_team";
2664 this.team = this.cnt + 1;
2667 // compatibility for quake maps
2668 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2669 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2670 spawnfunc(info_player_team1);
2671 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2672 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2673 spawnfunc(info_player_team2);
2674 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2675 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2677 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2678 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2680 // compatibility for wop maps
2681 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2682 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2683 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2684 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2685 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2686 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2694 void ctf_ScoreRules(int teams)
2696 CheckAllowedTeams(NULL);
2697 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
2698 field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2699 field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2700 field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2701 field(SP_CTF_PICKUPS, "pickups", 0);
2702 field(SP_CTF_FCKILLS, "fckills", 0);
2703 field(SP_CTF_RETURNS, "returns", 0);
2704 field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2708 // code from here on is just to support maps that don't have flag and team entities
2709 void ctf_SpawnTeam (string teamname, int teamcolor)
2711 entity this = new_pure(ctf_team);
2712 this.netname = teamname;
2713 this.cnt = teamcolor - 1;
2714 this.spawnfunc_checked = true;
2715 this.team = teamcolor;
2718 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2723 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2725 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2726 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2728 switch(tmp_entity.team)
2730 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2731 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2732 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2733 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2735 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2738 havocbot_ctf_calculate_middlepoint();
2740 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2742 ctf_teams = 0; // so set the default red and blue teams
2743 BITSET_ASSIGN(ctf_teams, BIT(0));
2744 BITSET_ASSIGN(ctf_teams, BIT(1));
2747 //ctf_teams = bound(2, ctf_teams, 4);
2749 // if no teams are found, spawn defaults
2750 if(find(NULL, classname, "ctf_team") == NULL)
2752 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2753 if(ctf_teams & BIT(0))
2754 ctf_SpawnTeam("Red", NUM_TEAM_1);
2755 if(ctf_teams & BIT(1))
2756 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2757 if(ctf_teams & BIT(2))
2758 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2759 if(ctf_teams & BIT(3))
2760 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2763 ctf_ScoreRules(ctf_teams);
2766 void ctf_Initialize()
2768 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2770 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2771 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2772 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2774 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);