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.");
16 SetLimits(autocvar_capturelimit_override, autocvar_captureleadlimit_override, autocvar_timelimit_override, -1);
17 have_team_spawns = -1; // request team spawns
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;
426 setattachment(flag, player.vehicle, "");
427 setorigin(flag, VEHICLE_FLAG_OFFSET);
428 flag.scale = VEHICLE_FLAG_SCALE;
432 setattachment(flag, player, "");
433 setorigin(flag, FLAG_CARRY_OFFSET);
435 set_movetype(flag, MOVETYPE_NONE);
436 flag.takedamage = DAMAGE_NO;
437 flag.solid = SOLID_NOT;
438 flag.angles = '0 0 0';
439 flag.ctf_status = FLAG_CARRY;
441 // messages and sounds
442 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
443 ctf_EventLog("receive", flag.team, player);
445 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
447 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
448 else if(it == player)
449 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
450 else if(SAME_TEAM(it, sender))
451 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
454 // create new waypoint
455 ctf_FlagcarrierWaypoints(player);
457 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
458 player.throw_antispam = sender.throw_antispam;
460 flag.pass_distance = 0;
461 flag.pass_sender = NULL;
462 flag.pass_target = NULL;
465 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
467 entity flag = player.flagcarried;
468 vector targ_origin, flag_velocity;
470 if(!flag) { return; }
471 if((droptype == DROP_PASS) && !receiver) { return; }
473 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
476 setattachment(flag, NULL, "");
477 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
478 flag.owner.flagcarried = NULL;
480 flag.solid = SOLID_TRIGGER;
481 flag.ctf_dropper = player;
482 flag.ctf_droptime = time;
483 navigation_dynamicgoal_set(flag);
485 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
492 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
493 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
494 WarpZone_RefSys_Copy(flag, receiver);
495 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
496 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
498 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
499 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
502 set_movetype(flag, MOVETYPE_FLY);
503 flag.takedamage = DAMAGE_NO;
504 flag.pass_sender = player;
505 flag.pass_target = receiver;
506 flag.ctf_status = FLAG_PASSING;
509 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
510 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
511 ctf_EventLog("pass", flag.team, player);
517 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'));
519 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)));
520 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
521 ctf_Handle_Drop(flag, player, droptype);
527 flag.velocity = '0 0 0'; // do nothing
534 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);
535 ctf_Handle_Drop(flag, player, droptype);
540 // kill old waypointsprite
541 WaypointSprite_Ping(player.wps_flagcarrier);
542 WaypointSprite_Kill(player.wps_flagcarrier);
544 if(player.wps_enemyflagcarrier)
545 WaypointSprite_Kill(player.wps_enemyflagcarrier);
547 if(player.wps_flagreturn)
548 WaypointSprite_Kill(player.wps_flagreturn);
551 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
554 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
556 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
563 void nades_GiveBonus(entity player, float score);
565 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
567 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
568 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
569 entity player_team_flag = NULL, tmp_entity;
570 float old_time, new_time;
572 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
573 if(CTF_DIFFTEAM(player, flag)) { return; }
574 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)
577 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
578 if(SAME_TEAM(tmp_entity, player))
580 player_team_flag = tmp_entity;
584 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
586 player.throw_prevtime = time;
587 player.throw_count = 0;
589 // messages and sounds
590 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
591 ctf_CaptureRecord(enemy_flag, player);
592 _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);
596 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
597 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
603 if(enemy_flag.score_capture || flag.score_capture)
604 pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
605 PlayerTeamScore_AddScore(player, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
607 if(enemy_flag.score_team_capture || flag.score_team_capture)
608 capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
609 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, ((capscore) ? capscore : 1));
611 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
612 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
613 if(!old_time || new_time < old_time)
614 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
617 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
618 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
621 if(capturetype == CAPTURE_NORMAL)
623 WaypointSprite_Kill(player.wps_flagcarrier);
624 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
626 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
627 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
631 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
632 ctf_RespawnFlag(enemy_flag);
635 void ctf_Handle_Return(entity flag, entity player)
637 // messages and sounds
638 if(IS_MONSTER(player))
640 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
644 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
645 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
647 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
648 ctf_EventLog("return", flag.team, player);
651 if(IS_PLAYER(player))
653 PlayerTeamScore_AddScore(player, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
654 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
656 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
659 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
663 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
664 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
665 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
669 if(player.flagcarried == flag)
670 WaypointSprite_Kill(player.wps_flagcarrier);
673 ctf_RespawnFlag(flag);
676 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
679 float pickup_dropped_score; // used to calculate dropped pickup score
681 // attach the flag to the player
683 player.flagcarried = flag;
686 setattachment(flag, player.vehicle, "");
687 setorigin(flag, VEHICLE_FLAG_OFFSET);
688 flag.scale = VEHICLE_FLAG_SCALE;
692 setattachment(flag, player, "");
693 setorigin(flag, FLAG_CARRY_OFFSET);
697 set_movetype(flag, MOVETYPE_NONE);
698 flag.takedamage = DAMAGE_NO;
699 flag.solid = SOLID_NOT;
700 flag.angles = '0 0 0';
701 flag.ctf_status = FLAG_CARRY;
705 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
706 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
710 // messages and sounds
711 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
713 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
715 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
716 else if(CTF_DIFFTEAM(player, flag))
717 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
719 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
721 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
724 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); });
727 FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
728 if(CTF_SAMETEAM(flag, it))
729 if(SAME_TEAM(player, it))
730 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
732 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);
735 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
738 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
739 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
744 PlayerTeamScore_AddScore(player, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
745 ctf_EventLog("steal", flag.team, player);
751 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);
752 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);
753 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
754 PlayerTeamScore_AddScore(player, pickup_dropped_score);
755 ctf_EventLog("pickup", flag.team, player);
763 if(pickuptype == PICKUP_BASE)
765 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
766 if((player.speedrunning) && (ctf_captimerecord))
767 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
771 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
774 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
775 ctf_FlagcarrierWaypoints(player);
776 WaypointSprite_Ping(player.wps_flagcarrier);
780 // ===================
781 // Main Flag Functions
782 // ===================
784 void ctf_CheckFlagReturn(entity flag, int returntype)
786 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
788 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
790 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
795 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
797 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
798 case RETURN_SPEEDRUN:
799 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
800 case RETURN_NEEDKILL:
801 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
804 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
806 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
807 ctf_EventLog("returned", flag.team, NULL);
808 ctf_RespawnFlag(flag);
813 bool ctf_Stalemate_Customize(entity this, entity client)
815 // make spectators see what the player would see
816 entity e = WaypointSprite_getviewentity(client);
817 entity wp_owner = this.owner;
820 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
821 if(SAME_TEAM(wp_owner, e)) { return false; }
822 if(!IS_PLAYER(e)) { return false; }
827 void ctf_CheckStalemate()
830 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
833 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
835 // build list of stale flags
836 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
838 if(autocvar_g_ctf_stalemate)
839 if(tmp_entity.ctf_status != FLAG_BASE)
840 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
842 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
843 ctf_staleflaglist = tmp_entity;
845 switch(tmp_entity.team)
847 case NUM_TEAM_1: ++stale_red_flags; break;
848 case NUM_TEAM_2: ++stale_blue_flags; break;
849 case NUM_TEAM_3: ++stale_yellow_flags; break;
850 case NUM_TEAM_4: ++stale_pink_flags; break;
851 default: ++stale_neutral_flags; break;
857 stale_flags = (stale_neutral_flags >= 1);
859 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
861 if(ctf_oneflag && stale_flags == 1)
862 ctf_stalemate = true;
863 else if(stale_flags >= 2)
864 ctf_stalemate = true;
865 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
866 { ctf_stalemate = false; wpforenemy_announced = false; }
867 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
868 { ctf_stalemate = false; wpforenemy_announced = false; }
870 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
873 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
875 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
877 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);
878 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
879 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
883 if (!wpforenemy_announced)
885 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)); });
887 wpforenemy_announced = true;
892 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
894 if(ITEM_DAMAGE_NEEDKILL(deathtype))
896 if(autocvar_g_ctf_flag_return_damage_delay)
897 this.ctf_flagdamaged_byworld = true;
901 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
905 if(autocvar_g_ctf_flag_return_damage)
907 // reduce health and check if it should be returned
908 this.health = this.health - damage;
909 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
914 void ctf_FlagThink(entity this)
919 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
922 if(this == ctf_worldflaglist) // only for the first flag
923 FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
926 if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
927 LOG_TRACE("wtf the flag got squashed?");
928 tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
929 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
930 setsize(this, this.m_mins, this.m_maxs);
934 switch(this.ctf_status)
938 if(autocvar_g_ctf_dropped_capture_radius)
940 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
941 if(tmp_entity.ctf_status == FLAG_DROPPED)
942 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
943 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
944 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
951 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
953 if(autocvar_g_ctf_flag_dropped_floatinwater)
955 vector midpoint = ((this.absmin + this.absmax) * 0.5);
956 if(pointcontents(midpoint) == CONTENT_WATER)
958 this.velocity = this.velocity * 0.5;
960 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
961 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
963 { set_movetype(this, MOVETYPE_FLY); }
965 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
967 if(autocvar_g_ctf_flag_return_dropped)
969 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
972 ctf_CheckFlagReturn(this, RETURN_DROPPED);
976 if(this.ctf_flagdamaged_byworld)
978 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
979 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
982 else if(autocvar_g_ctf_flag_return_time)
984 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
985 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
993 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
996 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
998 CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
999 ImpulseCommands(this.owner);
1001 if(autocvar_g_ctf_stalemate)
1003 if(time >= wpforenemy_nextthink)
1005 ctf_CheckStalemate();
1006 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1009 if(CTF_SAMETEAM(this, this.owner) && this.team)
1011 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1012 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1013 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1014 ctf_Handle_Return(this, this.owner);
1021 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1022 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1023 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1025 if((this.pass_target == NULL)
1026 || (IS_DEAD(this.pass_target))
1027 || (this.pass_target.flagcarried)
1028 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1029 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1030 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1032 // give up, pass failed
1033 ctf_Handle_Drop(this, NULL, DROP_PASS);
1037 // still a viable target, go for it
1038 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1043 default: // this should never happen
1045 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1051 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1054 if(game_stopped) return;
1055 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1057 bool is_not_monster = (!IS_MONSTER(toucher));
1059 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1060 if(ITEM_TOUCH_NEEDKILL())
1062 if(!autocvar_g_ctf_flag_return_damage_delay)
1065 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1067 if(!flag.ctf_flagdamaged_byworld) { return; }
1070 // special touch behaviors
1071 if(STAT(FROZEN, toucher)) { return; }
1072 else if(IS_VEHICLE(toucher))
1074 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1075 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1077 return; // do nothing
1079 else if(IS_MONSTER(toucher))
1081 if(!autocvar_g_ctf_allow_monster_touch)
1082 return; // do nothing
1084 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1086 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1088 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1089 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1090 flag.wait = time + FLAG_TOUCHRATE;
1094 else if(IS_DEAD(toucher)) { return; }
1096 switch(flag.ctf_status)
1102 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1103 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1104 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1105 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1107 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1108 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1109 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)
1111 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1112 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1114 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1115 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1121 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1122 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1123 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1124 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1130 LOG_TRACE("Someone touched a flag even though it was being carried?");
1136 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1138 if(DIFF_TEAM(toucher, flag.pass_sender))
1140 if(ctf_Immediate_Return_Allowed(flag, toucher))
1141 ctf_Handle_Return(flag, toucher);
1142 else if(is_not_monster && (!toucher.flagcarried))
1143 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1145 else if(!toucher.flagcarried)
1146 ctf_Handle_Retrieve(flag, toucher);
1153 .float last_respawn;
1154 void ctf_RespawnFlag(entity flag)
1156 // check for flag respawn being called twice in a row
1157 if(flag.last_respawn > time - 0.5)
1158 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1160 flag.last_respawn = time;
1162 // reset the player (if there is one)
1163 if((flag.owner) && (flag.owner.flagcarried == flag))
1165 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1166 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1167 WaypointSprite_Kill(flag.wps_flagcarrier);
1169 flag.owner.flagcarried = NULL;
1171 if(flag.speedrunning)
1172 ctf_FakeTimeLimit(flag.owner, -1);
1175 if((flag.owner) && (flag.owner.vehicle))
1176 flag.scale = FLAG_SCALE;
1178 if(flag.ctf_status == FLAG_DROPPED)
1179 { WaypointSprite_Kill(flag.wps_flagdropped); }
1182 setattachment(flag, NULL, "");
1183 setorigin(flag, flag.ctf_spawnorigin);
1185 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1186 flag.takedamage = DAMAGE_NO;
1187 flag.health = flag.max_flag_health;
1188 flag.solid = SOLID_TRIGGER;
1189 flag.velocity = '0 0 0';
1190 flag.angles = flag.mangle;
1191 flag.flags = FL_ITEM | FL_NOTARGET;
1193 flag.ctf_status = FLAG_BASE;
1195 flag.pass_distance = 0;
1196 flag.pass_sender = NULL;
1197 flag.pass_target = NULL;
1198 flag.ctf_dropper = NULL;
1199 flag.ctf_pickuptime = 0;
1200 flag.ctf_droptime = 0;
1201 flag.ctf_flagdamaged_byworld = false;
1202 navigation_dynamicgoal_unset(flag);
1204 ctf_CheckStalemate();
1207 void ctf_Reset(entity this)
1209 if(this.owner && IS_PLAYER(this.owner))
1210 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1212 ctf_RespawnFlag(this);
1215 bool ctf_FlagBase_Customize(entity this, entity client)
1217 entity e = WaypointSprite_getviewentity(client);
1218 entity wp_owner = this.owner;
1219 entity flag = e.flagcarried;
1220 if(flag && CTF_SAMETEAM(e, flag))
1222 if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1227 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1230 waypoint_spawnforitem_force(this, this.origin);
1231 navigation_dynamicgoal_init(this, true);
1237 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1238 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1239 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1240 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1241 default: basename = WP_FlagBaseNeutral; break;
1244 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1245 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1246 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1247 setcefc(wp, ctf_FlagBase_Customize);
1249 // captureshield setup
1250 ctf_CaptureShield_Spawn(this);
1255 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1258 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1259 ctf_worldflaglist = flag;
1261 setattachment(flag, NULL, "");
1263 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1264 flag.team = teamnumber;
1265 flag.classname = "item_flag_team";
1266 flag.target = "###item###"; // wut?
1267 flag.flags = FL_ITEM | FL_NOTARGET;
1268 IL_PUSH(g_items, flag);
1269 flag.solid = SOLID_TRIGGER;
1270 flag.takedamage = DAMAGE_NO;
1271 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1272 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1273 flag.health = flag.max_flag_health;
1274 flag.event_damage = ctf_FlagDamage;
1275 flag.pushable = true;
1276 flag.teleportable = TELEPORT_NORMAL;
1277 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1278 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1279 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1280 if(flag.damagedbycontents)
1281 IL_PUSH(g_damagedbycontents, flag);
1282 flag.velocity = '0 0 0';
1283 flag.mangle = flag.angles;
1284 flag.reset = ctf_Reset;
1285 settouch(flag, ctf_FlagTouch);
1286 setthink(flag, ctf_FlagThink);
1287 flag.nextthink = time + FLAG_THINKRATE;
1288 flag.ctf_status = FLAG_BASE;
1290 // crudely force them all to 0
1291 if(autocvar_g_ctf_score_ignore_fields)
1292 flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1294 string teamname = Static_Team_ColorName_Lower(teamnumber);
1296 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1297 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1298 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1299 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1300 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1301 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1305 if(flag.s == "") flag.s = b; \
1306 precache_sound(flag.s);
1308 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1309 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1310 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1311 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1312 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1313 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1314 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1318 precache_model(flag.model);
1321 _setmodel(flag, flag.model); // precision set below
1322 setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
1323 flag.m_mins = flag.mins; // store these for squash checks
1324 flag.m_maxs = flag.maxs;
1325 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1327 if(autocvar_g_ctf_flag_glowtrails)
1331 case NUM_TEAM_1: flag.glow_color = 251; break;
1332 case NUM_TEAM_2: flag.glow_color = 210; break;
1333 case NUM_TEAM_3: flag.glow_color = 110; break;
1334 case NUM_TEAM_4: flag.glow_color = 145; break;
1335 default: flag.glow_color = 254; break;
1337 flag.glow_size = 25;
1338 flag.glow_trail = 1;
1341 flag.effects |= EF_LOWPRECISION;
1342 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1343 if(autocvar_g_ctf_dynamiclights)
1347 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1348 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1349 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1350 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1351 default: flag.effects |= EF_DIMLIGHT; break;
1356 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1358 flag.dropped_origin = flag.origin;
1359 flag.noalign = true;
1360 set_movetype(flag, MOVETYPE_NONE);
1362 else // drop to floor, automatically find a platform and set that as spawn origin
1364 flag.noalign = false;
1366 set_movetype(flag, MOVETYPE_NONE);
1369 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1377 // NOTE: LEGACY CODE, needs to be re-written!
1379 void havocbot_ctf_calculate_middlepoint()
1383 vector fo = '0 0 0';
1386 f = ctf_worldflaglist;
1391 f = f.ctf_worldflagnext;
1397 havocbot_middlepoint = s / n;
1398 havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
1400 havocbot_symmetryaxis_equation = '0 0 0';
1403 // for symmetrical editing of waypoints
1404 entity f1 = ctf_worldflaglist;
1405 entity f2 = f1.ctf_worldflagnext;
1406 float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
1407 float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
1408 havocbot_symmetryaxis_equation.x = m;
1409 havocbot_symmetryaxis_equation.y = q;
1411 // store number of flags in this otherwise unused vector component
1412 havocbot_symmetryaxis_equation.z = n;
1416 entity havocbot_ctf_find_flag(entity bot)
1419 f = ctf_worldflaglist;
1422 if (CTF_SAMETEAM(bot, f))
1424 f = f.ctf_worldflagnext;
1429 entity havocbot_ctf_find_enemy_flag(entity bot)
1432 f = ctf_worldflaglist;
1437 if(CTF_DIFFTEAM(bot, f))
1444 else if(!bot.flagcarried)
1448 else if (CTF_DIFFTEAM(bot, f))
1450 f = f.ctf_worldflagnext;
1455 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1462 FOREACH_CLIENT(IS_PLAYER(it), {
1463 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1466 if(vdist(it.origin - org, <, tc_radius))
1475 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1478 head = ctf_worldflaglist;
1481 if (CTF_SAMETEAM(this, head))
1483 head = head.ctf_worldflagnext;
1486 navigation_routerating(this, head, ratingscale, 10000);
1490 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1493 head = ctf_worldflaglist;
1496 if (CTF_SAMETEAM(this, head))
1498 if (this.flagcarried)
1499 if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
1501 head = head.ctf_worldflagnext; // skip base if it has a different group
1506 head = head.ctf_worldflagnext;
1511 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1514 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1517 head = ctf_worldflaglist;
1522 if(CTF_DIFFTEAM(this, head))
1526 if(this.flagcarried)
1529 else if(!this.flagcarried)
1533 else if(CTF_DIFFTEAM(this, head))
1535 head = head.ctf_worldflagnext;
1538 navigation_routerating(this, head, ratingscale, 10000);
1541 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1543 if (!bot_waypoints_for_items)
1545 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1551 head = havocbot_ctf_find_enemy_flag(this);
1556 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1559 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1563 mf = havocbot_ctf_find_flag(this);
1565 if(mf.ctf_status == FLAG_BASE)
1569 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1572 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1575 head = ctf_worldflaglist;
1578 // flag is out in the field
1579 if(head.ctf_status != FLAG_BASE)
1580 if(head.tag_entity==NULL) // dropped
1584 if(vdist(org - head.origin, <, df_radius))
1585 navigation_routerating(this, head, ratingscale, 10000);
1588 navigation_routerating(this, head, ratingscale, 10000);
1591 head = head.ctf_worldflagnext;
1595 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1597 IL_EACH(g_items, it.bot_pickup,
1599 // gather health and armor only
1601 if (it.health || it.armorvalue)
1602 if (vdist(it.origin - org, <, sradius))
1604 // get the value of the item
1605 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1607 navigation_routerating(this, it, t * ratingscale, 500);
1612 void havocbot_ctf_reset_role(entity this)
1614 float cdefense, cmiddle, coffense;
1622 if (this.flagcarried)
1624 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1628 mf = havocbot_ctf_find_flag(this);
1629 ef = havocbot_ctf_find_enemy_flag(this);
1631 // Retrieve stolen flag
1632 if(mf.ctf_status!=FLAG_BASE)
1634 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1638 // If enemy flag is taken go to the middle to intercept pursuers
1639 if(ef.ctf_status!=FLAG_BASE)
1641 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1645 // if there is only me on the team switch to offense
1647 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
1651 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1655 // Evaluate best position to take
1656 // Count mates on middle position
1657 cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
1659 // Count mates on defense position
1660 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
1662 // Count mates on offense position
1663 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
1665 if(cdefense<=coffense)
1666 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1667 else if(coffense<=cmiddle)
1668 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1670 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1673 void havocbot_role_ctf_carrier(entity this)
1677 havocbot_ctf_reset_role(this);
1681 if (this.flagcarried == NULL)
1683 havocbot_ctf_reset_role(this);
1687 if (this.bot_strategytime < time)
1689 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1691 navigation_goalrating_start(this);
1693 havocbot_goalrating_ctf_enemybase(this, 50000);
1695 havocbot_goalrating_ctf_ourbase(this, 50000);
1698 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1700 navigation_goalrating_end(this);
1702 if (this.goalentity)
1703 this.havocbot_cantfindflag = time + 10;
1704 else if (time > this.havocbot_cantfindflag)
1706 // Can't navigate to my own base, suicide!
1707 // TODO: drop it and wander around
1708 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1714 void havocbot_role_ctf_escort(entity this)
1720 havocbot_ctf_reset_role(this);
1724 if (this.flagcarried)
1726 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1730 // If enemy flag is back on the base switch to previous role
1731 ef = havocbot_ctf_find_enemy_flag(this);
1732 if(ef.ctf_status==FLAG_BASE)
1734 this.havocbot_role = this.havocbot_previous_role;
1735 this.havocbot_role_timeout = 0;
1739 // If the flag carrier reached the base switch to defense
1740 mf = havocbot_ctf_find_flag(this);
1741 if(mf.ctf_status!=FLAG_BASE)
1742 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1744 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1748 // Set the role timeout if necessary
1749 if (!this.havocbot_role_timeout)
1751 this.havocbot_role_timeout = time + random() * 30 + 60;
1754 // If nothing happened just switch to previous role
1755 if (time > this.havocbot_role_timeout)
1757 this.havocbot_role = this.havocbot_previous_role;
1758 this.havocbot_role_timeout = 0;
1762 // Chase the flag carrier
1763 if (this.bot_strategytime < time)
1765 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1766 navigation_goalrating_start(this);
1767 havocbot_goalrating_ctf_enemyflag(this, 30000);
1768 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1769 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1770 navigation_goalrating_end(this);
1774 void havocbot_role_ctf_offense(entity this)
1781 havocbot_ctf_reset_role(this);
1785 if (this.flagcarried)
1787 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1792 mf = havocbot_ctf_find_flag(this);
1793 ef = havocbot_ctf_find_enemy_flag(this);
1796 if(mf.ctf_status!=FLAG_BASE)
1799 pos = mf.tag_entity.origin;
1803 // Try to get it if closer than the enemy base
1804 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1806 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1811 // Escort flag carrier
1812 if(ef.ctf_status!=FLAG_BASE)
1815 pos = ef.tag_entity.origin;
1819 if(vdist(pos - mf.dropped_origin, >, 700))
1821 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1826 // About to fail, switch to middlefield
1829 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1833 // Set the role timeout if necessary
1834 if (!this.havocbot_role_timeout)
1835 this.havocbot_role_timeout = time + 120;
1837 if (time > this.havocbot_role_timeout)
1839 havocbot_ctf_reset_role(this);
1843 if (this.bot_strategytime < time)
1845 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1846 navigation_goalrating_start(this);
1847 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1848 havocbot_goalrating_ctf_enemybase(this, 20000);
1849 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1850 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1851 navigation_goalrating_end(this);
1855 // Retriever (temporary role):
1856 void havocbot_role_ctf_retriever(entity this)
1862 havocbot_ctf_reset_role(this);
1866 if (this.flagcarried)
1868 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1872 // If flag is back on the base switch to previous role
1873 mf = havocbot_ctf_find_flag(this);
1874 if(mf.ctf_status==FLAG_BASE)
1876 if(this.goalcurrent == mf)
1878 navigation_clearroute(this);
1879 this.bot_strategytime = 0;
1881 havocbot_ctf_reset_role(this);
1885 if (!this.havocbot_role_timeout)
1886 this.havocbot_role_timeout = time + 20;
1888 if (time > this.havocbot_role_timeout)
1890 havocbot_ctf_reset_role(this);
1894 if (this.bot_strategytime < time)
1899 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1900 navigation_goalrating_start(this);
1901 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1902 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1903 havocbot_goalrating_ctf_enemybase(this, 30000);
1904 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1905 navigation_goalrating_end(this);
1909 void havocbot_role_ctf_middle(entity this)
1915 havocbot_ctf_reset_role(this);
1919 if (this.flagcarried)
1921 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1925 mf = havocbot_ctf_find_flag(this);
1926 if(mf.ctf_status!=FLAG_BASE)
1928 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1932 if (!this.havocbot_role_timeout)
1933 this.havocbot_role_timeout = time + 10;
1935 if (time > this.havocbot_role_timeout)
1937 havocbot_ctf_reset_role(this);
1941 if (this.bot_strategytime < time)
1945 org = havocbot_middlepoint;
1946 org.z = this.origin.z;
1948 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1949 navigation_goalrating_start(this);
1950 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1951 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1952 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
1953 havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
1954 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1955 havocbot_goalrating_ctf_enemybase(this, 2500);
1956 navigation_goalrating_end(this);
1960 void havocbot_role_ctf_defense(entity this)
1966 havocbot_ctf_reset_role(this);
1970 if (this.flagcarried)
1972 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1976 // If own flag was captured
1977 mf = havocbot_ctf_find_flag(this);
1978 if(mf.ctf_status!=FLAG_BASE)
1980 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1984 if (!this.havocbot_role_timeout)
1985 this.havocbot_role_timeout = time + 30;
1987 if (time > this.havocbot_role_timeout)
1989 havocbot_ctf_reset_role(this);
1992 if (this.bot_strategytime < time)
1994 vector org = mf.dropped_origin;
1996 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1997 navigation_goalrating_start(this);
1999 // if enemies are closer to our base, go there
2000 entity closestplayer = NULL;
2001 float distance, bestdistance = 10000;
2002 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
2003 distance = vlen(org - it.origin);
2004 if(distance<bestdistance)
2007 bestdistance = distance;
2012 if(DIFF_TEAM(closestplayer, this))
2013 if(vdist(org - this.origin, >, 1000))
2014 if(checkpvs(this.origin,closestplayer)||random()<0.5)
2015 havocbot_goalrating_ctf_ourbase(this, 30000);
2017 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
2018 havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
2019 havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
2020 havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
2021 havocbot_goalrating_items(this, 5000, this.origin, 10000);
2022 navigation_goalrating_end(this);
2026 void havocbot_role_ctf_setrole(entity bot, int role)
2028 string s = "(null)";
2031 case HAVOCBOT_CTF_ROLE_CARRIER:
2033 bot.havocbot_role = havocbot_role_ctf_carrier;
2034 bot.havocbot_role_timeout = 0;
2035 bot.havocbot_cantfindflag = time + 10;
2036 bot.bot_strategytime = 0;
2038 case HAVOCBOT_CTF_ROLE_DEFENSE:
2040 bot.havocbot_role = havocbot_role_ctf_defense;
2041 bot.havocbot_role_timeout = 0;
2043 case HAVOCBOT_CTF_ROLE_MIDDLE:
2045 bot.havocbot_role = havocbot_role_ctf_middle;
2046 bot.havocbot_role_timeout = 0;
2048 case HAVOCBOT_CTF_ROLE_OFFENSE:
2050 bot.havocbot_role = havocbot_role_ctf_offense;
2051 bot.havocbot_role_timeout = 0;
2053 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2055 bot.havocbot_previous_role = bot.havocbot_role;
2056 bot.havocbot_role = havocbot_role_ctf_retriever;
2057 bot.havocbot_role_timeout = time + 10;
2058 bot.bot_strategytime = 0;
2060 case HAVOCBOT_CTF_ROLE_ESCORT:
2062 bot.havocbot_previous_role = bot.havocbot_role;
2063 bot.havocbot_role = havocbot_role_ctf_escort;
2064 bot.havocbot_role_timeout = time + 30;
2065 bot.bot_strategytime = 0;
2068 LOG_TRACE(bot.netname, " switched to ", s);
2076 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2078 entity player = M_ARGV(0, entity);
2080 int t = 0, t2 = 0, t3 = 0;
2081 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)
2083 // initially clear items so they can be set as necessary later.
2084 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2085 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2086 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2087 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2088 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2089 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2091 // scan through all the flags and notify the client about them
2092 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2094 if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2095 if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2096 if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2097 if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2098 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; }
2100 switch(flag.ctf_status)
2105 if((flag.owner == player) || (flag.pass_sender == player))
2106 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2108 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2113 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2119 // item for stopping players from capturing the flag too often
2120 if(player.ctf_captureshielded)
2121 player.ctf_flagstatus |= CTF_SHIELDED;
2124 player.ctf_flagstatus |= CTF_STALEMATE;
2126 // update the health of the flag carrier waypointsprite
2127 if(player.wps_flagcarrier)
2128 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2131 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2133 entity frag_attacker = M_ARGV(1, entity);
2134 entity frag_target = M_ARGV(2, entity);
2135 float frag_damage = M_ARGV(4, float);
2136 vector frag_force = M_ARGV(6, vector);
2138 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2140 if(frag_target == frag_attacker) // damage done to yourself
2142 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2143 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2145 else // damage done to everyone else
2147 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2148 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2151 M_ARGV(4, float) = frag_damage;
2152 M_ARGV(6, vector) = frag_force;
2154 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2156 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)))
2157 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2159 frag_target.wps_helpme_time = time;
2160 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2162 // todo: add notification for when flag carrier needs help?
2166 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2168 entity frag_attacker = M_ARGV(1, entity);
2169 entity frag_target = M_ARGV(2, entity);
2171 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2173 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2174 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2177 if(frag_target.flagcarried)
2179 entity tmp_entity = frag_target.flagcarried;
2180 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2181 tmp_entity.ctf_dropper = NULL;
2185 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2187 M_ARGV(2, float) = 0; // frag score
2188 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2191 void ctf_RemovePlayer(entity player)
2193 if(player.flagcarried)
2194 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2196 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2198 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2199 if(flag.pass_target == player) { flag.pass_target = NULL; }
2200 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2204 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2206 entity player = M_ARGV(0, entity);
2208 ctf_RemovePlayer(player);
2211 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2213 entity player = M_ARGV(0, entity);
2215 ctf_RemovePlayer(player);
2218 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2220 if(!autocvar_g_ctf_leaderboard)
2223 entity player = M_ARGV(0, entity);
2225 if(IS_REAL_CLIENT(player))
2227 for(int i = 1; i <= RANKINGS_CNT; ++i)
2229 race_SendRankings(i, 0, 0, MSG_ONE);
2234 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2236 if(!autocvar_g_ctf_leaderboard)
2239 entity player = M_ARGV(0, entity);
2241 if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
2243 if (!player.stored_netname)
2244 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2245 if(player.stored_netname != player.netname)
2247 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2248 strunzone(player.stored_netname);
2249 player.stored_netname = strzone(player.netname);
2254 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2256 entity player = M_ARGV(0, entity);
2258 if(player.flagcarried)
2259 if(!autocvar_g_ctf_portalteleport)
2260 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2263 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2265 if(MUTATOR_RETURNVALUE || game_stopped) return;
2267 entity player = M_ARGV(0, entity);
2269 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2271 // pass the flag to a team mate
2272 if(autocvar_g_ctf_pass)
2274 entity head, closest_target = NULL;
2275 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2277 while(head) // find the closest acceptable target to pass to
2279 if(IS_PLAYER(head) && !IS_DEAD(head))
2280 if(head != player && SAME_TEAM(head, player))
2281 if(!head.speedrunning && !head.vehicle)
2283 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2284 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2285 vector passer_center = CENTER_OR_VIEWOFS(player);
2287 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2289 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2291 if(IS_BOT_CLIENT(head))
2293 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2294 ctf_Handle_Throw(head, player, DROP_PASS);
2298 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2299 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2301 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2304 else if(player.flagcarried && !head.flagcarried)
2308 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2309 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2310 { closest_target = head; }
2312 else { closest_target = head; }
2319 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2322 // throw the flag in front of you
2323 if(autocvar_g_ctf_throw && player.flagcarried)
2325 if(player.throw_count == -1)
2327 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2329 player.throw_prevtime = time;
2330 player.throw_count = 1;
2331 ctf_Handle_Throw(player, NULL, DROP_THROW);
2336 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2342 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2343 else { player.throw_count += 1; }
2344 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2346 player.throw_prevtime = time;
2347 ctf_Handle_Throw(player, NULL, DROP_THROW);
2354 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2356 entity player = M_ARGV(0, entity);
2358 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2360 player.wps_helpme_time = time;
2361 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2363 else // create a normal help me waypointsprite
2365 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2366 WaypointSprite_Ping(player.wps_helpme);
2372 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2374 entity player = M_ARGV(0, entity);
2375 entity veh = M_ARGV(1, entity);
2377 if(player.flagcarried)
2379 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2381 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2385 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2386 setattachment(player.flagcarried, veh, "");
2387 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2388 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2389 //player.flagcarried.angles = '0 0 0';
2395 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2397 entity player = M_ARGV(0, entity);
2399 if(player.flagcarried)
2401 setattachment(player.flagcarried, player, "");
2402 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2403 player.flagcarried.scale = FLAG_SCALE;
2404 player.flagcarried.angles = '0 0 0';
2405 player.flagcarried.nodrawtoclient = NULL;
2410 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2412 entity player = M_ARGV(0, entity);
2414 if(player.flagcarried)
2416 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2417 ctf_RespawnFlag(player.flagcarried);
2422 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2424 entity flag; // temporary entity for the search method
2426 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2428 switch(flag.ctf_status)
2433 // lock the flag, game is over
2434 set_movetype(flag, MOVETYPE_NONE);
2435 flag.takedamage = DAMAGE_NO;
2436 flag.solid = SOLID_NOT;
2437 flag.nextthink = false; // stop thinking
2439 //dprint("stopping the ", flag.netname, " from moving.\n");
2447 // do nothing for these flags
2454 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2456 entity bot = M_ARGV(0, entity);
2458 havocbot_ctf_reset_role(bot);
2462 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2464 //M_ARGV(0, float) = ctf_teams;
2465 M_ARGV(1, string) = "ctf_team";
2469 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2471 entity spectatee = M_ARGV(0, entity);
2472 entity client = M_ARGV(1, entity);
2474 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2477 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2479 int record_page = M_ARGV(0, int);
2480 string ret_string = M_ARGV(1, string);
2482 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2484 if (MapInfo_Get_ByID(i))
2486 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2492 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2493 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2497 M_ARGV(1, string) = ret_string;
2500 bool superspec_Spectate(entity this, entity targ); // TODO
2501 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2502 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2504 entity player = M_ARGV(0, entity);
2505 string cmd_name = M_ARGV(1, string);
2506 int cmd_argc = M_ARGV(2, int);
2508 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2510 if(cmd_name == "followfc")
2522 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2523 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2524 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2525 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2529 FOREACH_CLIENT(IS_PLAYER(it), {
2530 if(it.flagcarried && (it.team == _team || _team == 0))
2533 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2534 continue; // already spectating this fc, try another
2535 return superspec_Spectate(player, it);
2540 superspec_msg("", "", player, "No active flag carrier\n", 1);
2545 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2547 entity frag_target = M_ARGV(0, entity);
2549 if(frag_target.flagcarried)
2550 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2558 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2559 CTF flag for team one (Red).
2561 "angle" Angle the flag will point (minus 90 degrees)...
2562 "model" model to use, note this needs red and blue as skins 0 and 1...
2563 "noise" sound played when flag is picked up...
2564 "noise1" sound played when flag is returned by a teammate...
2565 "noise2" sound played when flag is captured...
2566 "noise3" sound played when flag is lost in the field and respawns itself...
2567 "noise4" sound played when flag is dropped by a player...
2568 "noise5" sound played when flag touches the ground... */
2569 spawnfunc(item_flag_team1)
2571 if(!g_ctf) { delete(this); return; }
2573 ctf_FlagSetup(NUM_TEAM_1, this);
2576 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2577 CTF flag for team two (Blue).
2579 "angle" Angle the flag will point (minus 90 degrees)...
2580 "model" model to use, note this needs red and blue as skins 0 and 1...
2581 "noise" sound played when flag is picked up...
2582 "noise1" sound played when flag is returned by a teammate...
2583 "noise2" sound played when flag is captured...
2584 "noise3" sound played when flag is lost in the field and respawns itself...
2585 "noise4" sound played when flag is dropped by a player...
2586 "noise5" sound played when flag touches the ground... */
2587 spawnfunc(item_flag_team2)
2589 if(!g_ctf) { delete(this); return; }
2591 ctf_FlagSetup(NUM_TEAM_2, this);
2594 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2595 CTF flag for team three (Yellow).
2597 "angle" Angle the flag will point (minus 90 degrees)...
2598 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2599 "noise" sound played when flag is picked up...
2600 "noise1" sound played when flag is returned by a teammate...
2601 "noise2" sound played when flag is captured...
2602 "noise3" sound played when flag is lost in the field and respawns itself...
2603 "noise4" sound played when flag is dropped by a player...
2604 "noise5" sound played when flag touches the ground... */
2605 spawnfunc(item_flag_team3)
2607 if(!g_ctf) { delete(this); return; }
2609 ctf_FlagSetup(NUM_TEAM_3, this);
2612 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2613 CTF flag for team four (Pink).
2615 "angle" Angle the flag will point (minus 90 degrees)...
2616 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2617 "noise" sound played when flag is picked up...
2618 "noise1" sound played when flag is returned by a teammate...
2619 "noise2" sound played when flag is captured...
2620 "noise3" sound played when flag is lost in the field and respawns itself...
2621 "noise4" sound played when flag is dropped by a player...
2622 "noise5" sound played when flag touches the ground... */
2623 spawnfunc(item_flag_team4)
2625 if(!g_ctf) { delete(this); return; }
2627 ctf_FlagSetup(NUM_TEAM_4, this);
2630 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2633 "angle" Angle the flag will point (minus 90 degrees)...
2634 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2635 "noise" sound played when flag is picked up...
2636 "noise1" sound played when flag is returned by a teammate...
2637 "noise2" sound played when flag is captured...
2638 "noise3" sound played when flag is lost in the field and respawns itself...
2639 "noise4" sound played when flag is dropped by a player...
2640 "noise5" sound played when flag touches the ground... */
2641 spawnfunc(item_flag_neutral)
2643 if(!g_ctf) { delete(this); return; }
2644 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2646 ctf_FlagSetup(0, this);
2649 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2650 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2651 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.
2653 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2654 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2657 if(!g_ctf) { delete(this); return; }
2659 this.classname = "ctf_team";
2660 this.team = this.cnt + 1;
2663 // compatibility for quake maps
2664 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2665 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2666 spawnfunc(info_player_team1);
2667 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2668 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2669 spawnfunc(info_player_team2);
2670 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2671 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2673 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2674 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2676 // compatibility for wop maps
2677 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2678 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2679 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2680 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2681 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2682 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2690 void ctf_ScoreRules(int teams)
2692 CheckAllowedTeams(NULL);
2693 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2694 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2695 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2696 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2697 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2698 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2699 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2700 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2701 ScoreRules_basics_end();
2704 // code from here on is just to support maps that don't have flag and team entities
2705 void ctf_SpawnTeam (string teamname, int teamcolor)
2707 entity this = new_pure(ctf_team);
2708 this.netname = teamname;
2709 this.cnt = teamcolor - 1;
2710 this.spawnfunc_checked = true;
2711 this.team = teamcolor;
2714 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2719 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2721 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2722 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2724 switch(tmp_entity.team)
2726 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2727 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2728 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2729 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2731 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2734 havocbot_ctf_calculate_middlepoint();
2736 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2738 ctf_teams = 0; // so set the default red and blue teams
2739 BITSET_ASSIGN(ctf_teams, BIT(0));
2740 BITSET_ASSIGN(ctf_teams, BIT(1));
2743 //ctf_teams = bound(2, ctf_teams, 4);
2745 // if no teams are found, spawn defaults
2746 if(find(NULL, classname, "ctf_team") == NULL)
2748 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2749 if(ctf_teams & BIT(0))
2750 ctf_SpawnTeam("Red", NUM_TEAM_1);
2751 if(ctf_teams & BIT(1))
2752 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2753 if(ctf_teams & BIT(2))
2754 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2755 if(ctf_teams & BIT(3))
2756 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2759 ctf_ScoreRules(ctf_teams);
2762 void ctf_Initialize()
2764 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2766 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2767 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2768 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2770 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);