1 #include "gamemode_ctf.qh"
6 REGISTER_MUTATOR(ctf, false)
10 if (time > 1) // game loads at time 1
11 error("This is a game type and it cannot be added at runtime.");
15 SetLimits(autocvar_capturelimit_override, autocvar_captureleadlimit_override, autocvar_timelimit_override, -1);
16 have_team_spawns = -1; // request team spawns
19 MUTATOR_ONROLLBACK_OR_REMOVE
21 // we actually cannot roll back ctf_Initialize here
22 // BUT: we don't need to! If this gets called, adding always
28 LOG_INFO("This is a game type and it cannot be removed at runtime.");
37 #include <common/vehicles/all.qh>
38 #include <server/teamplay.qh>
41 #include <lib/warpzone/common.qh>
43 bool autocvar_g_ctf_allow_vehicle_carry;
44 bool autocvar_g_ctf_allow_vehicle_touch;
45 bool autocvar_g_ctf_allow_monster_touch;
46 bool autocvar_g_ctf_throw;
47 float autocvar_g_ctf_throw_angle_max;
48 float autocvar_g_ctf_throw_angle_min;
49 int autocvar_g_ctf_throw_punish_count;
50 float autocvar_g_ctf_throw_punish_delay;
51 float autocvar_g_ctf_throw_punish_time;
52 float autocvar_g_ctf_throw_strengthmultiplier;
53 float autocvar_g_ctf_throw_velocity_forward;
54 float autocvar_g_ctf_throw_velocity_up;
55 float autocvar_g_ctf_drop_velocity_up;
56 float autocvar_g_ctf_drop_velocity_side;
57 bool autocvar_g_ctf_oneflag_reverse;
58 bool autocvar_g_ctf_portalteleport;
59 bool autocvar_g_ctf_pass;
60 float autocvar_g_ctf_pass_arc;
61 float autocvar_g_ctf_pass_arc_max;
62 float autocvar_g_ctf_pass_directional_max;
63 float autocvar_g_ctf_pass_directional_min;
64 float autocvar_g_ctf_pass_radius;
65 float autocvar_g_ctf_pass_wait;
66 bool autocvar_g_ctf_pass_request;
67 float autocvar_g_ctf_pass_turnrate;
68 float autocvar_g_ctf_pass_timelimit;
69 float autocvar_g_ctf_pass_velocity;
70 bool autocvar_g_ctf_dynamiclights;
71 float autocvar_g_ctf_flag_collect_delay;
72 float autocvar_g_ctf_flag_damageforcescale;
73 bool autocvar_g_ctf_flag_dropped_waypoint;
74 bool autocvar_g_ctf_flag_dropped_floatinwater;
75 bool autocvar_g_ctf_flag_glowtrails;
76 int autocvar_g_ctf_flag_health;
77 bool autocvar_g_ctf_flag_return;
78 bool autocvar_g_ctf_flag_return_carrying;
79 float autocvar_g_ctf_flag_return_carried_radius;
80 float autocvar_g_ctf_flag_return_time;
81 bool autocvar_g_ctf_flag_return_when_unreachable;
82 float autocvar_g_ctf_flag_return_damage;
83 float autocvar_g_ctf_flag_return_damage_delay;
84 float autocvar_g_ctf_flag_return_dropped;
85 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
86 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
87 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
88 float autocvar_g_ctf_flagcarrier_selfforcefactor;
89 float autocvar_g_ctf_flagcarrier_damagefactor;
90 float autocvar_g_ctf_flagcarrier_forcefactor;
91 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
92 bool autocvar_g_ctf_fullbrightflags;
93 bool autocvar_g_ctf_ignore_frags;
94 bool autocvar_g_ctf_score_ignore_fields;
95 int autocvar_g_ctf_score_capture;
96 int autocvar_g_ctf_score_capture_assist;
97 int autocvar_g_ctf_score_kill;
98 int autocvar_g_ctf_score_penalty_drop;
99 int autocvar_g_ctf_score_penalty_returned;
100 int autocvar_g_ctf_score_pickup_base;
101 int autocvar_g_ctf_score_pickup_dropped_early;
102 int autocvar_g_ctf_score_pickup_dropped_late;
103 int autocvar_g_ctf_score_return;
104 float autocvar_g_ctf_shield_force;
105 float autocvar_g_ctf_shield_max_ratio;
106 int autocvar_g_ctf_shield_min_negscore;
107 bool autocvar_g_ctf_stalemate;
108 int autocvar_g_ctf_stalemate_endcondition;
109 float autocvar_g_ctf_stalemate_time;
110 bool autocvar_g_ctf_reverse;
111 float autocvar_g_ctf_dropped_capture_delay;
112 float autocvar_g_ctf_dropped_capture_radius;
114 void ctf_FakeTimeLimit(entity e, float t)
117 WriteByte(MSG_ONE, 3); // svc_updatestat
118 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
120 WriteCoord(MSG_ONE, autocvar_timelimit);
122 WriteCoord(MSG_ONE, (t + 1) / 60);
125 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
127 if(autocvar_sv_eventlog)
128 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
129 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
132 void ctf_CaptureRecord(entity flag, entity player)
134 float cap_record = ctf_captimerecord;
135 float cap_time = (time - flag.ctf_pickuptime);
136 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
140 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
141 else if(!ctf_captimerecord)
142 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, (cap_time * 100));
143 else if(cap_time < cap_record)
144 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100));
146 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100));
148 // write that shit in the database
149 if(!ctf_oneflag) // but not in 1-flag mode
150 if((!ctf_captimerecord) || (cap_time < cap_record))
152 ctf_captimerecord = cap_time;
153 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
154 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
155 write_recordmarker(player, (time - cap_time), cap_time);
158 if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
159 race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
162 bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
165 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
167 // automatically return if there's only 1 player on the team
168 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
172 bool ctf_Return_Customize(entity this, entity client)
174 // only to the carrier
175 return boolean(client == this.owner);
178 void ctf_FlagcarrierWaypoints(entity player)
180 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
181 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
182 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
183 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
185 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
187 if(!player.wps_enemyflagcarrier)
189 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
190 wp.colormod = WPCOLOR_ENEMYFC(player.team);
191 setcefc(wp, ctf_Stalemate_Customize);
193 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
194 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
197 if(!player.wps_flagreturn)
199 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
200 owp.colormod = '0 0.8 0.8';
201 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
202 setcefc(owp, ctf_Return_Customize);
207 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
209 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
210 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
211 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
212 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
215 if(current_height) // make sure we can actually do this arcing path
217 targpos = (to + ('0 0 1' * current_height));
218 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
219 if(trace_fraction < 1)
221 //print("normal arc line failed, trying to find new pos...");
222 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
223 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
224 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
225 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
226 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
229 else { targpos = to; }
231 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
233 vector desired_direction = normalize(targpos - from);
234 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
235 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
238 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
240 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
242 // directional tracing only
244 makevectors(passer_angle);
246 // find the closest point on the enemy to the center of the attack
247 float h; // hypotenuse, which is the distance between attacker to head
248 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
250 h = vlen(head_center - passer_center);
251 a = h * (normalize(head_center - passer_center) * v_forward);
253 vector nearest_on_line = (passer_center + a * v_forward);
254 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
256 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
257 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
259 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
264 else { return true; }
268 // =======================
269 // CaptureShield Functions
270 // =======================
272 bool ctf_CaptureShield_CheckStatus(entity p)
274 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
275 int players_worseeq, players_total;
277 if(ctf_captureshield_max_ratio <= 0)
280 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
281 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
282 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
283 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
285 sr = ((s - s2) + (s3 + s4));
287 if(sr >= -ctf_captureshield_min_negscore)
290 players_total = players_worseeq = 0;
291 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
294 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
295 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
296 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
297 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
299 ser = ((se - se2) + (se3 + se4));
306 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
307 // use this rule here
309 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
315 void ctf_CaptureShield_Update(entity player, bool wanted_status)
317 bool updated_status = ctf_CaptureShield_CheckStatus(player);
318 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
320 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
321 player.ctf_captureshielded = updated_status;
325 bool ctf_CaptureShield_Customize(entity this, entity client)
327 if(!client.ctf_captureshielded) { return false; }
328 if(CTF_SAMETEAM(this, client)) { return false; }
333 void ctf_CaptureShield_Touch(entity this, entity toucher)
335 if(!toucher.ctf_captureshielded) { return; }
336 if(CTF_SAMETEAM(this, toucher)) { return; }
338 vector mymid = (this.absmin + this.absmax) * 0.5;
339 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
341 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
342 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
345 void ctf_CaptureShield_Spawn(entity flag)
347 entity shield = new(ctf_captureshield);
350 shield.team = flag.team;
351 settouch(shield, ctf_CaptureShield_Touch);
352 setcefc(shield, ctf_CaptureShield_Customize);
353 shield.effects = EF_ADDITIVE;
354 set_movetype(shield, MOVETYPE_NOCLIP);
355 shield.solid = SOLID_TRIGGER;
356 shield.avelocity = '7 0 11';
359 setorigin(shield, flag.origin);
360 setmodel(shield, MDL_CTF_SHIELD);
361 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
365 // ====================
366 // Drop/Pass/Throw Code
367 // ====================
369 void ctf_Handle_Drop(entity flag, entity player, int droptype)
372 player = (player ? player : flag.pass_sender);
375 set_movetype(flag, MOVETYPE_TOSS);
376 flag.takedamage = DAMAGE_YES;
377 flag.angles = '0 0 0';
378 flag.health = flag.max_flag_health;
379 flag.ctf_droptime = time;
380 flag.ctf_dropper = player;
381 flag.ctf_status = FLAG_DROPPED;
383 // messages and sounds
384 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
385 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
386 ctf_EventLog("dropped", player.team, player);
389 PlayerTeamScore_AddScore(player, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
390 PlayerScore_Add(player, SP_CTF_DROPS, 1);
393 if(autocvar_g_ctf_flag_dropped_waypoint) {
394 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);
395 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
398 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
400 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
401 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
404 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
406 if(droptype == DROP_PASS)
408 flag.pass_distance = 0;
409 flag.pass_sender = NULL;
410 flag.pass_target = NULL;
414 void ctf_Handle_Retrieve(entity flag, entity player)
416 entity sender = flag.pass_sender;
418 // transfer flag to player
420 flag.owner.flagcarried = flag;
425 setattachment(flag, player.vehicle, "");
426 setorigin(flag, VEHICLE_FLAG_OFFSET);
427 flag.scale = VEHICLE_FLAG_SCALE;
431 setattachment(flag, player, "");
432 setorigin(flag, FLAG_CARRY_OFFSET);
434 set_movetype(flag, MOVETYPE_NONE);
435 flag.takedamage = DAMAGE_NO;
436 flag.solid = SOLID_NOT;
437 flag.angles = '0 0 0';
438 flag.ctf_status = FLAG_CARRY;
440 // messages and sounds
441 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
442 ctf_EventLog("receive", flag.team, player);
444 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
446 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
447 else if(it == player)
448 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
449 else if(SAME_TEAM(it, sender))
450 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
453 // create new waypoint
454 ctf_FlagcarrierWaypoints(player);
456 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
457 player.throw_antispam = sender.throw_antispam;
459 flag.pass_distance = 0;
460 flag.pass_sender = NULL;
461 flag.pass_target = NULL;
464 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
466 entity flag = player.flagcarried;
467 vector targ_origin, flag_velocity;
469 if(!flag) { return; }
470 if((droptype == DROP_PASS) && !receiver) { return; }
472 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
475 setattachment(flag, NULL, "");
476 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
477 flag.owner.flagcarried = NULL;
479 flag.solid = SOLID_TRIGGER;
480 flag.ctf_dropper = player;
481 flag.ctf_droptime = time;
482 navigation_dynamicgoal_set(flag);
484 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
491 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
492 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
493 WarpZone_RefSys_Copy(flag, receiver);
494 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
495 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
497 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
498 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
501 set_movetype(flag, MOVETYPE_FLY);
502 flag.takedamage = DAMAGE_NO;
503 flag.pass_sender = player;
504 flag.pass_target = receiver;
505 flag.ctf_status = FLAG_PASSING;
508 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
509 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
510 ctf_EventLog("pass", flag.team, player);
516 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'));
518 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)));
519 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
520 ctf_Handle_Drop(flag, player, droptype);
526 flag.velocity = '0 0 0'; // do nothing
533 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);
534 ctf_Handle_Drop(flag, player, droptype);
539 // kill old waypointsprite
540 WaypointSprite_Ping(player.wps_flagcarrier);
541 WaypointSprite_Kill(player.wps_flagcarrier);
543 if(player.wps_enemyflagcarrier)
544 WaypointSprite_Kill(player.wps_enemyflagcarrier);
546 if(player.wps_flagreturn)
547 WaypointSprite_Kill(player.wps_flagreturn);
550 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
553 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
555 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
562 void nades_GiveBonus(entity player, float score);
564 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
566 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
567 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
568 entity player_team_flag = NULL, tmp_entity;
569 float old_time, new_time;
571 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
572 if(CTF_DIFFTEAM(player, flag)) { return; }
573 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)
576 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
577 if(SAME_TEAM(tmp_entity, player))
579 player_team_flag = tmp_entity;
583 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
585 player.throw_prevtime = time;
586 player.throw_count = 0;
588 // messages and sounds
589 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
590 ctf_CaptureRecord(enemy_flag, player);
591 _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);
595 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
596 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
602 if(enemy_flag.score_capture || flag.score_capture)
603 pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
604 PlayerTeamScore_AddScore(player, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
606 if(enemy_flag.score_team_capture || flag.score_team_capture)
607 capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
608 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, ((capscore) ? capscore : 1));
610 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
611 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
612 if(!old_time || new_time < old_time)
613 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
616 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
617 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
620 if(capturetype == CAPTURE_NORMAL)
622 WaypointSprite_Kill(player.wps_flagcarrier);
623 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
625 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
626 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
630 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
631 ctf_RespawnFlag(enemy_flag);
634 void ctf_Handle_Return(entity flag, entity player)
636 // messages and sounds
637 if(IS_MONSTER(player))
639 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
643 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
644 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
646 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
647 ctf_EventLog("return", flag.team, player);
650 if(IS_PLAYER(player))
652 PlayerTeamScore_AddScore(player, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
653 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
655 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
658 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
662 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
663 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
664 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
668 if(player.flagcarried == flag)
669 WaypointSprite_Kill(player.wps_flagcarrier);
672 ctf_RespawnFlag(flag);
675 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
678 float pickup_dropped_score; // used to calculate dropped pickup score
680 // attach the flag to the player
682 player.flagcarried = flag;
685 setattachment(flag, player.vehicle, "");
686 setorigin(flag, VEHICLE_FLAG_OFFSET);
687 flag.scale = VEHICLE_FLAG_SCALE;
691 setattachment(flag, player, "");
692 setorigin(flag, FLAG_CARRY_OFFSET);
696 set_movetype(flag, MOVETYPE_NONE);
697 flag.takedamage = DAMAGE_NO;
698 flag.solid = SOLID_NOT;
699 flag.angles = '0 0 0';
700 flag.ctf_status = FLAG_CARRY;
704 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
705 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
709 // messages and sounds
710 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
712 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
714 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
715 else if(CTF_DIFFTEAM(player, flag))
716 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
718 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
720 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
723 FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), LAMBDA(Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname)));
726 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
727 if(CTF_SAMETEAM(flag, it))
728 if(SAME_TEAM(player, it))
729 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
731 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);
734 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
737 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
738 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
743 PlayerTeamScore_AddScore(player, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
744 ctf_EventLog("steal", flag.team, player);
750 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);
751 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);
752 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
753 PlayerTeamScore_AddScore(player, pickup_dropped_score);
754 ctf_EventLog("pickup", flag.team, player);
762 if(pickuptype == PICKUP_BASE)
764 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
765 if((player.speedrunning) && (ctf_captimerecord))
766 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
770 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
773 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
774 ctf_FlagcarrierWaypoints(player);
775 WaypointSprite_Ping(player.wps_flagcarrier);
779 // ===================
780 // Main Flag Functions
781 // ===================
783 void ctf_CheckFlagReturn(entity flag, int returntype)
785 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
787 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
789 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
794 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
796 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
797 case RETURN_SPEEDRUN:
798 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), ctf_captimerecord); break;
799 case RETURN_NEEDKILL:
800 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
803 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
805 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
806 ctf_EventLog("returned", flag.team, NULL);
807 ctf_RespawnFlag(flag);
812 bool ctf_Stalemate_Customize(entity this, entity client)
814 // make spectators see what the player would see
815 entity e = WaypointSprite_getviewentity(client);
816 entity wp_owner = this.owner;
819 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
820 if(SAME_TEAM(wp_owner, e)) { return false; }
821 if(!IS_PLAYER(e)) { return false; }
826 void ctf_CheckStalemate()
829 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
832 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
834 // build list of stale flags
835 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
837 if(autocvar_g_ctf_stalemate)
838 if(tmp_entity.ctf_status != FLAG_BASE)
839 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
841 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
842 ctf_staleflaglist = tmp_entity;
844 switch(tmp_entity.team)
846 case NUM_TEAM_1: ++stale_red_flags; break;
847 case NUM_TEAM_2: ++stale_blue_flags; break;
848 case NUM_TEAM_3: ++stale_yellow_flags; break;
849 case NUM_TEAM_4: ++stale_pink_flags; break;
850 default: ++stale_neutral_flags; break;
856 stale_flags = (stale_neutral_flags >= 1);
858 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
860 if(ctf_oneflag && stale_flags == 1)
861 ctf_stalemate = true;
862 else if(stale_flags >= 2)
863 ctf_stalemate = true;
864 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
865 { ctf_stalemate = false; wpforenemy_announced = false; }
866 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
867 { ctf_stalemate = false; wpforenemy_announced = false; }
869 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
872 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
874 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
876 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);
877 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
878 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
882 if (!wpforenemy_announced)
884 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER))));
886 wpforenemy_announced = true;
891 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
893 if(ITEM_DAMAGE_NEEDKILL(deathtype))
895 if(autocvar_g_ctf_flag_return_damage_delay)
896 this.ctf_flagdamaged_byworld = true;
900 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
904 if(autocvar_g_ctf_flag_return_damage)
906 // reduce health and check if it should be returned
907 this.health = this.health - damage;
908 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
913 void ctf_FlagThink(entity this)
918 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
921 if(this == ctf_worldflaglist) // only for the first flag
922 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
925 if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
926 LOG_TRACE("wtf the flag got squashed?");
927 tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
928 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
929 setsize(this, this.m_mins, this.m_maxs);
933 switch(this.ctf_status)
937 if(autocvar_g_ctf_dropped_capture_radius)
939 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
940 if(tmp_entity.ctf_status == FLAG_DROPPED)
941 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
942 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
943 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
950 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
952 if(autocvar_g_ctf_flag_dropped_floatinwater)
954 vector midpoint = ((this.absmin + this.absmax) * 0.5);
955 if(pointcontents(midpoint) == CONTENT_WATER)
957 this.velocity = this.velocity * 0.5;
959 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
960 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
962 { set_movetype(this, MOVETYPE_FLY); }
964 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
966 if(autocvar_g_ctf_flag_return_dropped)
968 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
971 ctf_CheckFlagReturn(this, RETURN_DROPPED);
975 if(this.ctf_flagdamaged_byworld)
977 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
978 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
981 else if(autocvar_g_ctf_flag_return_time)
983 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
984 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
992 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
995 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
997 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
998 ImpulseCommands(this.owner);
1000 if(autocvar_g_ctf_stalemate)
1002 if(time >= wpforenemy_nextthink)
1004 ctf_CheckStalemate();
1005 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1008 if(CTF_SAMETEAM(this, this.owner) && this.team)
1010 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1011 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1012 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1013 ctf_Handle_Return(this, this.owner);
1020 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1021 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1022 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1024 if((this.pass_target == NULL)
1025 || (IS_DEAD(this.pass_target))
1026 || (this.pass_target.flagcarried)
1027 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1028 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1029 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1031 // give up, pass failed
1032 ctf_Handle_Drop(this, NULL, DROP_PASS);
1036 // still a viable target, go for it
1037 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1042 default: // this should never happen
1044 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1050 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1053 if(game_stopped) return;
1054 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1056 bool is_not_monster = (!IS_MONSTER(toucher));
1058 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1059 if(ITEM_TOUCH_NEEDKILL())
1061 if(!autocvar_g_ctf_flag_return_damage_delay)
1064 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1066 if(!flag.ctf_flagdamaged_byworld) { return; }
1069 // special touch behaviors
1070 if(STAT(FROZEN, toucher)) { return; }
1071 else if(IS_VEHICLE(toucher))
1073 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1074 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1076 return; // do nothing
1078 else if(IS_MONSTER(toucher))
1080 if(!autocvar_g_ctf_allow_monster_touch)
1081 return; // do nothing
1083 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1085 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1087 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1088 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1089 flag.wait = time + FLAG_TOUCHRATE;
1093 else if(IS_DEAD(toucher)) { return; }
1095 switch(flag.ctf_status)
1101 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1102 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1103 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1104 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1106 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1107 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1108 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)
1110 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1111 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1113 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1114 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1120 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1121 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1122 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1123 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1129 LOG_TRACE("Someone touched a flag even though it was being carried?");
1135 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1137 if(DIFF_TEAM(toucher, flag.pass_sender))
1139 if(ctf_Immediate_Return_Allowed(flag, toucher))
1140 ctf_Handle_Return(flag, toucher);
1141 else if(is_not_monster && (!toucher.flagcarried))
1142 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1144 else if(!toucher.flagcarried)
1145 ctf_Handle_Retrieve(flag, toucher);
1152 .float last_respawn;
1153 void ctf_RespawnFlag(entity flag)
1155 // check for flag respawn being called twice in a row
1156 if(flag.last_respawn > time - 0.5)
1157 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1159 flag.last_respawn = time;
1161 // reset the player (if there is one)
1162 if((flag.owner) && (flag.owner.flagcarried == flag))
1164 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1165 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1166 WaypointSprite_Kill(flag.wps_flagcarrier);
1168 flag.owner.flagcarried = NULL;
1170 if(flag.speedrunning)
1171 ctf_FakeTimeLimit(flag.owner, -1);
1174 if((flag.owner) && (flag.owner.vehicle))
1175 flag.scale = FLAG_SCALE;
1177 if(flag.ctf_status == FLAG_DROPPED)
1178 { WaypointSprite_Kill(flag.wps_flagdropped); }
1181 setattachment(flag, NULL, "");
1182 setorigin(flag, flag.ctf_spawnorigin);
1184 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1185 flag.takedamage = DAMAGE_NO;
1186 flag.health = flag.max_flag_health;
1187 flag.solid = SOLID_TRIGGER;
1188 flag.velocity = '0 0 0';
1189 flag.angles = flag.mangle;
1190 flag.flags = FL_ITEM | FL_NOTARGET;
1192 flag.ctf_status = FLAG_BASE;
1194 flag.pass_distance = 0;
1195 flag.pass_sender = NULL;
1196 flag.pass_target = NULL;
1197 flag.ctf_dropper = NULL;
1198 flag.ctf_pickuptime = 0;
1199 flag.ctf_droptime = 0;
1200 flag.ctf_flagdamaged_byworld = false;
1201 navigation_dynamicgoal_unset(flag);
1203 ctf_CheckStalemate();
1206 void ctf_Reset(entity this)
1208 if(this.owner && IS_PLAYER(this.owner))
1209 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1211 ctf_RespawnFlag(this);
1214 bool ctf_FlagBase_Customize(entity this, entity client)
1216 entity e = WaypointSprite_getviewentity(client);
1217 entity wp_owner = this.owner;
1218 entity flag = e.flagcarried;
1219 if(flag && CTF_SAMETEAM(e, flag))
1221 if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1226 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1229 waypoint_spawnforitem_force(this, this.origin);
1230 navigation_dynamicgoal_init(this, true);
1236 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1237 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1238 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1239 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1240 default: basename = WP_FlagBaseNeutral; break;
1243 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1244 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1245 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1246 setcefc(wp, ctf_FlagBase_Customize);
1248 // captureshield setup
1249 ctf_CaptureShield_Spawn(this);
1254 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1257 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1258 ctf_worldflaglist = flag;
1260 setattachment(flag, NULL, "");
1262 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1263 flag.team = teamnumber;
1264 flag.classname = "item_flag_team";
1265 flag.target = "###item###"; // wut?
1266 flag.flags = FL_ITEM | FL_NOTARGET;
1267 IL_PUSH(g_items, flag);
1268 flag.solid = SOLID_TRIGGER;
1269 flag.takedamage = DAMAGE_NO;
1270 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1271 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1272 flag.health = flag.max_flag_health;
1273 flag.event_damage = ctf_FlagDamage;
1274 flag.pushable = true;
1275 flag.teleportable = TELEPORT_NORMAL;
1276 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1277 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1278 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1279 if(flag.damagedbycontents)
1280 IL_PUSH(g_damagedbycontents, flag);
1281 flag.velocity = '0 0 0';
1282 flag.mangle = flag.angles;
1283 flag.reset = ctf_Reset;
1284 settouch(flag, ctf_FlagTouch);
1285 setthink(flag, ctf_FlagThink);
1286 flag.nextthink = time + FLAG_THINKRATE;
1287 flag.ctf_status = FLAG_BASE;
1289 // crudely force them all to 0
1290 if(autocvar_g_ctf_score_ignore_fields)
1291 flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1293 string teamname = Static_Team_ColorName_Lower(teamnumber);
1295 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1296 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1297 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1298 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1299 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1300 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1304 if(flag.s == "") flag.s = b; \
1305 precache_sound(flag.s);
1307 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1308 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1309 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1310 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1311 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1312 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1313 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1317 precache_model(flag.model);
1320 _setmodel(flag, flag.model); // precision set below
1321 setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
1322 flag.m_mins = flag.mins; // store these for squash checks
1323 flag.m_maxs = flag.maxs;
1324 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1326 if(autocvar_g_ctf_flag_glowtrails)
1330 case NUM_TEAM_1: flag.glow_color = 251; break;
1331 case NUM_TEAM_2: flag.glow_color = 210; break;
1332 case NUM_TEAM_3: flag.glow_color = 110; break;
1333 case NUM_TEAM_4: flag.glow_color = 145; break;
1334 default: flag.glow_color = 254; break;
1336 flag.glow_size = 25;
1337 flag.glow_trail = 1;
1340 flag.effects |= EF_LOWPRECISION;
1341 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1342 if(autocvar_g_ctf_dynamiclights)
1346 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1347 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1348 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1349 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1350 default: flag.effects |= EF_DIMLIGHT; break;
1355 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1357 flag.dropped_origin = flag.origin;
1358 flag.noalign = true;
1359 set_movetype(flag, MOVETYPE_NONE);
1361 else // drop to floor, automatically find a platform and set that as spawn origin
1363 flag.noalign = false;
1365 set_movetype(flag, MOVETYPE_NONE);
1368 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1376 // NOTE: LEGACY CODE, needs to be re-written!
1378 void havocbot_ctf_calculate_middlepoint()
1382 vector fo = '0 0 0';
1385 f = ctf_worldflaglist;
1390 f = f.ctf_worldflagnext;
1396 havocbot_middlepoint = s / n;
1397 havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
1399 havocbot_symmetryaxis_equation = '0 0 0';
1402 // for symmetrical editing of waypoints
1403 entity f1 = ctf_worldflaglist;
1404 entity f2 = f1.ctf_worldflagnext;
1405 float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
1406 float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
1407 havocbot_symmetryaxis_equation.x = m;
1408 havocbot_symmetryaxis_equation.y = q;
1410 // store number of flags in this otherwise unused vector component
1411 havocbot_symmetryaxis_equation.z = n;
1415 entity havocbot_ctf_find_flag(entity bot)
1418 f = ctf_worldflaglist;
1421 if (CTF_SAMETEAM(bot, f))
1423 f = f.ctf_worldflagnext;
1428 entity havocbot_ctf_find_enemy_flag(entity bot)
1431 f = ctf_worldflaglist;
1436 if(CTF_DIFFTEAM(bot, f))
1443 else if(!bot.flagcarried)
1447 else if (CTF_DIFFTEAM(bot, f))
1449 f = f.ctf_worldflagnext;
1454 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1461 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1462 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1465 if(vdist(it.origin - org, <, tc_radius))
1474 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1477 head = ctf_worldflaglist;
1480 if (CTF_SAMETEAM(this, head))
1482 head = head.ctf_worldflagnext;
1485 navigation_routerating(this, head, ratingscale, 10000);
1489 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1492 head = ctf_worldflaglist;
1495 if (CTF_SAMETEAM(this, head))
1497 if (this.flagcarried)
1498 if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
1500 head = head.ctf_worldflagnext; // skip base if it has a different group
1505 head = head.ctf_worldflagnext;
1510 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1513 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1516 head = ctf_worldflaglist;
1521 if(CTF_DIFFTEAM(this, head))
1525 if(this.flagcarried)
1528 else if(!this.flagcarried)
1532 else if(CTF_DIFFTEAM(this, head))
1534 head = head.ctf_worldflagnext;
1537 navigation_routerating(this, head, ratingscale, 10000);
1540 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1542 if (!bot_waypoints_for_items)
1544 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1550 head = havocbot_ctf_find_enemy_flag(this);
1555 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1558 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1562 mf = havocbot_ctf_find_flag(this);
1564 if(mf.ctf_status == FLAG_BASE)
1568 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1571 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1574 head = ctf_worldflaglist;
1577 // flag is out in the field
1578 if(head.ctf_status != FLAG_BASE)
1579 if(head.tag_entity==NULL) // dropped
1583 if(vdist(org - head.origin, <, df_radius))
1584 navigation_routerating(this, head, ratingscale, 10000);
1587 navigation_routerating(this, head, ratingscale, 10000);
1590 head = head.ctf_worldflagnext;
1594 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1596 IL_EACH(g_items, it.bot_pickup,
1598 // gather health and armor only
1600 if (it.health || it.armorvalue)
1601 if (vdist(it.origin - org, <, sradius))
1603 // get the value of the item
1604 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1606 navigation_routerating(this, it, t * ratingscale, 500);
1611 void havocbot_ctf_reset_role(entity this)
1613 float cdefense, cmiddle, coffense;
1621 if (this.flagcarried)
1623 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1627 mf = havocbot_ctf_find_flag(this);
1628 ef = havocbot_ctf_find_enemy_flag(this);
1630 // Retrieve stolen flag
1631 if(mf.ctf_status!=FLAG_BASE)
1633 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1637 // If enemy flag is taken go to the middle to intercept pursuers
1638 if(ef.ctf_status!=FLAG_BASE)
1640 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1644 // if there is only me on the team switch to offense
1646 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1650 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1654 // Evaluate best position to take
1655 // Count mates on middle position
1656 cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
1658 // Count mates on defense position
1659 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
1661 // Count mates on offense position
1662 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
1664 if(cdefense<=coffense)
1665 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1666 else if(coffense<=cmiddle)
1667 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1669 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1672 void havocbot_role_ctf_carrier(entity this)
1676 havocbot_ctf_reset_role(this);
1680 if (this.flagcarried == NULL)
1682 havocbot_ctf_reset_role(this);
1686 if (this.bot_strategytime < time)
1688 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1690 navigation_goalrating_start(this);
1692 havocbot_goalrating_ctf_enemybase(this, 50000);
1694 havocbot_goalrating_ctf_ourbase(this, 50000);
1697 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1699 navigation_goalrating_end(this);
1701 if (this.goalentity)
1702 this.havocbot_cantfindflag = time + 10;
1703 else if (time > this.havocbot_cantfindflag)
1705 // Can't navigate to my own base, suicide!
1706 // TODO: drop it and wander around
1707 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1713 void havocbot_role_ctf_escort(entity this)
1719 havocbot_ctf_reset_role(this);
1723 if (this.flagcarried)
1725 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1729 // If enemy flag is back on the base switch to previous role
1730 ef = havocbot_ctf_find_enemy_flag(this);
1731 if(ef.ctf_status==FLAG_BASE)
1733 this.havocbot_role = this.havocbot_previous_role;
1734 this.havocbot_role_timeout = 0;
1738 // If the flag carrier reached the base switch to defense
1739 mf = havocbot_ctf_find_flag(this);
1740 if(mf.ctf_status!=FLAG_BASE)
1741 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1743 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1747 // Set the role timeout if necessary
1748 if (!this.havocbot_role_timeout)
1750 this.havocbot_role_timeout = time + random() * 30 + 60;
1753 // If nothing happened just switch to previous role
1754 if (time > this.havocbot_role_timeout)
1756 this.havocbot_role = this.havocbot_previous_role;
1757 this.havocbot_role_timeout = 0;
1761 // Chase the flag carrier
1762 if (this.bot_strategytime < time)
1764 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1765 navigation_goalrating_start(this);
1766 havocbot_goalrating_ctf_enemyflag(this, 30000);
1767 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1768 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1769 navigation_goalrating_end(this);
1773 void havocbot_role_ctf_offense(entity this)
1780 havocbot_ctf_reset_role(this);
1784 if (this.flagcarried)
1786 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1791 mf = havocbot_ctf_find_flag(this);
1792 ef = havocbot_ctf_find_enemy_flag(this);
1795 if(mf.ctf_status!=FLAG_BASE)
1798 pos = mf.tag_entity.origin;
1802 // Try to get it if closer than the enemy base
1803 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1805 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1810 // Escort flag carrier
1811 if(ef.ctf_status!=FLAG_BASE)
1814 pos = ef.tag_entity.origin;
1818 if(vdist(pos - mf.dropped_origin, >, 700))
1820 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1825 // About to fail, switch to middlefield
1828 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1832 // Set the role timeout if necessary
1833 if (!this.havocbot_role_timeout)
1834 this.havocbot_role_timeout = time + 120;
1836 if (time > this.havocbot_role_timeout)
1838 havocbot_ctf_reset_role(this);
1842 if (this.bot_strategytime < time)
1844 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1845 navigation_goalrating_start(this);
1846 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1847 havocbot_goalrating_ctf_enemybase(this, 20000);
1848 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1849 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1850 navigation_goalrating_end(this);
1854 // Retriever (temporary role):
1855 void havocbot_role_ctf_retriever(entity this)
1861 havocbot_ctf_reset_role(this);
1865 if (this.flagcarried)
1867 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1871 // If flag is back on the base switch to previous role
1872 mf = havocbot_ctf_find_flag(this);
1873 if(mf.ctf_status==FLAG_BASE)
1875 if(this.goalcurrent == mf)
1877 navigation_clearroute(this);
1878 this.bot_strategytime = 0;
1880 havocbot_ctf_reset_role(this);
1884 if (!this.havocbot_role_timeout)
1885 this.havocbot_role_timeout = time + 20;
1887 if (time > this.havocbot_role_timeout)
1889 havocbot_ctf_reset_role(this);
1893 if (this.bot_strategytime < time)
1898 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1899 navigation_goalrating_start(this);
1900 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1901 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1902 havocbot_goalrating_ctf_enemybase(this, 30000);
1903 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1904 navigation_goalrating_end(this);
1908 void havocbot_role_ctf_middle(entity this)
1914 havocbot_ctf_reset_role(this);
1918 if (this.flagcarried)
1920 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1924 mf = havocbot_ctf_find_flag(this);
1925 if(mf.ctf_status!=FLAG_BASE)
1927 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1931 if (!this.havocbot_role_timeout)
1932 this.havocbot_role_timeout = time + 10;
1934 if (time > this.havocbot_role_timeout)
1936 havocbot_ctf_reset_role(this);
1940 if (this.bot_strategytime < time)
1944 org = havocbot_middlepoint;
1945 org.z = this.origin.z;
1947 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1948 navigation_goalrating_start(this);
1949 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1950 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1951 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
1952 havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
1953 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1954 havocbot_goalrating_ctf_enemybase(this, 2500);
1955 navigation_goalrating_end(this);
1959 void havocbot_role_ctf_defense(entity this)
1965 havocbot_ctf_reset_role(this);
1969 if (this.flagcarried)
1971 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1975 // If own flag was captured
1976 mf = havocbot_ctf_find_flag(this);
1977 if(mf.ctf_status!=FLAG_BASE)
1979 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1983 if (!this.havocbot_role_timeout)
1984 this.havocbot_role_timeout = time + 30;
1986 if (time > this.havocbot_role_timeout)
1988 havocbot_ctf_reset_role(this);
1991 if (this.bot_strategytime < time)
1993 vector org = mf.dropped_origin;
1995 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1996 navigation_goalrating_start(this);
1998 // if enemies are closer to our base, go there
1999 entity closestplayer = NULL;
2000 float distance, bestdistance = 10000;
2001 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
2002 distance = vlen(org - it.origin);
2003 if(distance<bestdistance)
2006 bestdistance = distance;
2011 if(DIFF_TEAM(closestplayer, this))
2012 if(vdist(org - this.origin, >, 1000))
2013 if(checkpvs(this.origin,closestplayer)||random()<0.5)
2014 havocbot_goalrating_ctf_ourbase(this, 30000);
2016 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
2017 havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
2018 havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
2019 havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
2020 havocbot_goalrating_items(this, 5000, this.origin, 10000);
2021 navigation_goalrating_end(this);
2025 void havocbot_role_ctf_setrole(entity bot, int role)
2027 string s = "(null)";
2030 case HAVOCBOT_CTF_ROLE_CARRIER:
2032 bot.havocbot_role = havocbot_role_ctf_carrier;
2033 bot.havocbot_role_timeout = 0;
2034 bot.havocbot_cantfindflag = time + 10;
2035 bot.bot_strategytime = 0;
2037 case HAVOCBOT_CTF_ROLE_DEFENSE:
2039 bot.havocbot_role = havocbot_role_ctf_defense;
2040 bot.havocbot_role_timeout = 0;
2042 case HAVOCBOT_CTF_ROLE_MIDDLE:
2044 bot.havocbot_role = havocbot_role_ctf_middle;
2045 bot.havocbot_role_timeout = 0;
2047 case HAVOCBOT_CTF_ROLE_OFFENSE:
2049 bot.havocbot_role = havocbot_role_ctf_offense;
2050 bot.havocbot_role_timeout = 0;
2052 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2054 bot.havocbot_previous_role = bot.havocbot_role;
2055 bot.havocbot_role = havocbot_role_ctf_retriever;
2056 bot.havocbot_role_timeout = time + 10;
2057 bot.bot_strategytime = 0;
2059 case HAVOCBOT_CTF_ROLE_ESCORT:
2061 bot.havocbot_previous_role = bot.havocbot_role;
2062 bot.havocbot_role = havocbot_role_ctf_escort;
2063 bot.havocbot_role_timeout = time + 30;
2064 bot.bot_strategytime = 0;
2067 LOG_TRACE(bot.netname, " switched to ", s);
2075 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2077 entity player = M_ARGV(0, entity);
2079 int t = 0, t2 = 0, t3 = 0;
2080 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)
2082 // initially clear items so they can be set as necessary later.
2083 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2084 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2085 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2086 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2087 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2088 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2090 // scan through all the flags and notify the client about them
2091 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2093 if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2094 if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2095 if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2096 if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2097 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; }
2099 switch(flag.ctf_status)
2104 if((flag.owner == player) || (flag.pass_sender == player))
2105 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2107 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2112 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2118 // item for stopping players from capturing the flag too often
2119 if(player.ctf_captureshielded)
2120 player.ctf_flagstatus |= CTF_SHIELDED;
2123 player.ctf_flagstatus |= CTF_STALEMATE;
2125 // update the health of the flag carrier waypointsprite
2126 if(player.wps_flagcarrier)
2127 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2130 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2132 entity frag_attacker = M_ARGV(1, entity);
2133 entity frag_target = M_ARGV(2, entity);
2134 float frag_damage = M_ARGV(4, float);
2135 vector frag_force = M_ARGV(6, vector);
2137 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2139 if(frag_target == frag_attacker) // damage done to yourself
2141 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2142 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2144 else // damage done to everyone else
2146 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2147 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2150 M_ARGV(4, float) = frag_damage;
2151 M_ARGV(6, vector) = frag_force;
2153 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2155 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)))
2156 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2158 frag_target.wps_helpme_time = time;
2159 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2161 // todo: add notification for when flag carrier needs help?
2165 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2167 entity frag_attacker = M_ARGV(1, entity);
2168 entity frag_target = M_ARGV(2, entity);
2170 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2172 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2173 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2176 if(frag_target.flagcarried)
2178 entity tmp_entity = frag_target.flagcarried;
2179 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2180 tmp_entity.ctf_dropper = NULL;
2184 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2186 M_ARGV(2, float) = 0; // frag score
2187 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2190 void ctf_RemovePlayer(entity player)
2192 if(player.flagcarried)
2193 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2195 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2197 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2198 if(flag.pass_target == player) { flag.pass_target = NULL; }
2199 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2203 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2205 entity player = M_ARGV(0, entity);
2207 ctf_RemovePlayer(player);
2210 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2212 entity player = M_ARGV(0, entity);
2214 ctf_RemovePlayer(player);
2217 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2219 if(!autocvar_g_ctf_leaderboard)
2222 entity player = M_ARGV(0, entity);
2224 if(IS_REAL_CLIENT(player))
2226 for(int i = 1; i <= RANKINGS_CNT; ++i)
2228 race_SendRankings(i, 0, 0, MSG_ONE);
2233 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2235 if(!autocvar_g_ctf_leaderboard)
2238 entity player = M_ARGV(0, entity);
2240 if(player.cvar_cl_allow_uidtracking == 1 && player.cvar_cl_allow_uid2name == 1)
2242 if (!player.stored_netname)
2243 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2244 if(player.stored_netname != player.netname)
2246 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2247 strunzone(player.stored_netname);
2248 player.stored_netname = strzone(player.netname);
2253 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2255 entity player = M_ARGV(0, entity);
2257 if(player.flagcarried)
2258 if(!autocvar_g_ctf_portalteleport)
2259 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2262 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2264 if(MUTATOR_RETURNVALUE || game_stopped) return;
2266 entity player = M_ARGV(0, entity);
2268 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2270 // pass the flag to a team mate
2271 if(autocvar_g_ctf_pass)
2273 entity head, closest_target = NULL;
2274 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2276 while(head) // find the closest acceptable target to pass to
2278 if(IS_PLAYER(head) && !IS_DEAD(head))
2279 if(head != player && SAME_TEAM(head, player))
2280 if(!head.speedrunning && !head.vehicle)
2282 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2283 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2284 vector passer_center = CENTER_OR_VIEWOFS(player);
2286 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2288 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2290 if(IS_BOT_CLIENT(head))
2292 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2293 ctf_Handle_Throw(head, player, DROP_PASS);
2297 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2298 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2300 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2303 else if(player.flagcarried && !head.flagcarried)
2307 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2308 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2309 { closest_target = head; }
2311 else { closest_target = head; }
2318 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2321 // throw the flag in front of you
2322 if(autocvar_g_ctf_throw && player.flagcarried)
2324 if(player.throw_count == -1)
2326 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2328 player.throw_prevtime = time;
2329 player.throw_count = 1;
2330 ctf_Handle_Throw(player, NULL, DROP_THROW);
2335 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2341 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2342 else { player.throw_count += 1; }
2343 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2345 player.throw_prevtime = time;
2346 ctf_Handle_Throw(player, NULL, DROP_THROW);
2353 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2355 entity player = M_ARGV(0, entity);
2357 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2359 player.wps_helpme_time = time;
2360 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2362 else // create a normal help me waypointsprite
2364 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2365 WaypointSprite_Ping(player.wps_helpme);
2371 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2373 entity player = M_ARGV(0, entity);
2374 entity veh = M_ARGV(1, entity);
2376 if(player.flagcarried)
2378 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2380 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2384 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2385 setattachment(player.flagcarried, veh, "");
2386 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2387 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2388 //player.flagcarried.angles = '0 0 0';
2394 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2396 entity player = M_ARGV(0, entity);
2398 if(player.flagcarried)
2400 setattachment(player.flagcarried, player, "");
2401 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2402 player.flagcarried.scale = FLAG_SCALE;
2403 player.flagcarried.angles = '0 0 0';
2404 player.flagcarried.nodrawtoclient = NULL;
2409 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2411 entity player = M_ARGV(0, entity);
2413 if(player.flagcarried)
2415 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2416 ctf_RespawnFlag(player.flagcarried);
2421 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2423 entity flag; // temporary entity for the search method
2425 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2427 switch(flag.ctf_status)
2432 // lock the flag, game is over
2433 set_movetype(flag, MOVETYPE_NONE);
2434 flag.takedamage = DAMAGE_NO;
2435 flag.solid = SOLID_NOT;
2436 flag.nextthink = false; // stop thinking
2438 //dprint("stopping the ", flag.netname, " from moving.\n");
2446 // do nothing for these flags
2453 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2455 entity bot = M_ARGV(0, entity);
2457 havocbot_ctf_reset_role(bot);
2461 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2463 //M_ARGV(0, float) = ctf_teams;
2464 M_ARGV(1, string) = "ctf_team";
2468 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2470 entity spectatee = M_ARGV(0, entity);
2471 entity client = M_ARGV(1, entity);
2473 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2476 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2478 int record_page = M_ARGV(0, int);
2479 string ret_string = M_ARGV(1, string);
2481 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2483 if (MapInfo_Get_ByID(i))
2485 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2491 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2492 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2496 M_ARGV(1, string) = ret_string;
2499 bool superspec_Spectate(entity this, entity targ); // TODO
2500 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2501 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2503 entity player = M_ARGV(0, entity);
2504 string cmd_name = M_ARGV(1, string);
2505 int cmd_argc = M_ARGV(2, int);
2507 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2509 if(cmd_name == "followfc")
2521 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2522 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2523 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2524 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2528 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2529 if(it.flagcarried && (it.team == _team || _team == 0))
2532 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2533 continue; // already spectating this fc, try another
2534 return superspec_Spectate(player, it);
2539 superspec_msg("", "", player, "No active flag carrier\n", 1);
2544 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2546 entity frag_target = M_ARGV(0, entity);
2548 if(frag_target.flagcarried)
2549 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2557 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2558 CTF flag for team one (Red).
2560 "angle" Angle the flag will point (minus 90 degrees)...
2561 "model" model to use, note this needs red and blue as skins 0 and 1...
2562 "noise" sound played when flag is picked up...
2563 "noise1" sound played when flag is returned by a teammate...
2564 "noise2" sound played when flag is captured...
2565 "noise3" sound played when flag is lost in the field and respawns itself...
2566 "noise4" sound played when flag is dropped by a player...
2567 "noise5" sound played when flag touches the ground... */
2568 spawnfunc(item_flag_team1)
2570 if(!g_ctf) { delete(this); return; }
2572 ctf_FlagSetup(NUM_TEAM_1, this);
2575 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2576 CTF flag for team two (Blue).
2578 "angle" Angle the flag will point (minus 90 degrees)...
2579 "model" model to use, note this needs red and blue as skins 0 and 1...
2580 "noise" sound played when flag is picked up...
2581 "noise1" sound played when flag is returned by a teammate...
2582 "noise2" sound played when flag is captured...
2583 "noise3" sound played when flag is lost in the field and respawns itself...
2584 "noise4" sound played when flag is dropped by a player...
2585 "noise5" sound played when flag touches the ground... */
2586 spawnfunc(item_flag_team2)
2588 if(!g_ctf) { delete(this); return; }
2590 ctf_FlagSetup(NUM_TEAM_2, this);
2593 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2594 CTF flag for team three (Yellow).
2596 "angle" Angle the flag will point (minus 90 degrees)...
2597 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2598 "noise" sound played when flag is picked up...
2599 "noise1" sound played when flag is returned by a teammate...
2600 "noise2" sound played when flag is captured...
2601 "noise3" sound played when flag is lost in the field and respawns itself...
2602 "noise4" sound played when flag is dropped by a player...
2603 "noise5" sound played when flag touches the ground... */
2604 spawnfunc(item_flag_team3)
2606 if(!g_ctf) { delete(this); return; }
2608 ctf_FlagSetup(NUM_TEAM_3, this);
2611 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2612 CTF flag for team four (Pink).
2614 "angle" Angle the flag will point (minus 90 degrees)...
2615 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2616 "noise" sound played when flag is picked up...
2617 "noise1" sound played when flag is returned by a teammate...
2618 "noise2" sound played when flag is captured...
2619 "noise3" sound played when flag is lost in the field and respawns itself...
2620 "noise4" sound played when flag is dropped by a player...
2621 "noise5" sound played when flag touches the ground... */
2622 spawnfunc(item_flag_team4)
2624 if(!g_ctf) { delete(this); return; }
2626 ctf_FlagSetup(NUM_TEAM_4, this);
2629 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2632 "angle" Angle the flag will point (minus 90 degrees)...
2633 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2634 "noise" sound played when flag is picked up...
2635 "noise1" sound played when flag is returned by a teammate...
2636 "noise2" sound played when flag is captured...
2637 "noise3" sound played when flag is lost in the field and respawns itself...
2638 "noise4" sound played when flag is dropped by a player...
2639 "noise5" sound played when flag touches the ground... */
2640 spawnfunc(item_flag_neutral)
2642 if(!g_ctf) { delete(this); return; }
2643 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2645 ctf_FlagSetup(0, this);
2648 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2649 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2650 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.
2652 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2653 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2656 if(!g_ctf) { delete(this); return; }
2658 this.classname = "ctf_team";
2659 this.team = this.cnt + 1;
2662 // compatibility for quake maps
2663 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2664 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2665 spawnfunc(info_player_team1);
2666 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2667 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2668 spawnfunc(info_player_team2);
2669 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2670 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2672 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2673 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2675 // compatibility for wop maps
2676 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2677 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2678 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2679 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2680 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2681 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2689 void ctf_ScoreRules(int teams)
2691 CheckAllowedTeams(NULL);
2692 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2693 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2694 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2695 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2696 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2697 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2698 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2699 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2700 ScoreRules_basics_end();
2703 // code from here on is just to support maps that don't have flag and team entities
2704 void ctf_SpawnTeam (string teamname, int teamcolor)
2706 entity this = new_pure(ctf_team);
2707 this.netname = teamname;
2708 this.cnt = teamcolor - 1;
2709 this.spawnfunc_checked = true;
2710 this.team = teamcolor;
2713 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2718 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2720 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2721 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2723 switch(tmp_entity.team)
2725 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2726 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2727 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2728 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2730 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2733 havocbot_ctf_calculate_middlepoint();
2735 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2737 ctf_teams = 0; // so set the default red and blue teams
2738 BITSET_ASSIGN(ctf_teams, BIT(0));
2739 BITSET_ASSIGN(ctf_teams, BIT(1));
2742 //ctf_teams = bound(2, ctf_teams, 4);
2744 // if no teams are found, spawn defaults
2745 if(find(NULL, classname, "ctf_team") == NULL)
2747 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2748 if(ctf_teams & BIT(0))
2749 ctf_SpawnTeam("Red", NUM_TEAM_1);
2750 if(ctf_teams & BIT(1))
2751 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2752 if(ctf_teams & BIT(2))
2753 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2754 if(ctf_teams & BIT(3))
2755 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2758 ctf_ScoreRules(ctf_teams);
2761 void ctf_Initialize()
2763 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2765 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2766 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2767 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2769 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);