Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
sv_ctf.qc
Go to the documentation of this file.
1#include "sv_ctf.qh"
2
10#include <server/client.qh>
11#include <server/gamelog.qh>
13#include <server/damage.qh>
14#include <server/world.qh>
15#include <server/items/items.qh>
16#include <server/race.qh>
17#include <server/teamplay.qh>
18
20
72//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
94
95void ctf_FakeTimeLimit(entity e, float t)
96{
97 msg_entity = e;
98 WriteByte(MSG_ONE, 3); // svc_updatestat
99 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
100 if(t < 0)
102 else
103 WriteCoord(MSG_ONE, (t + 1) / 60);
104}
105
106void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
107{
109 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
110 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
111}
112
114{
115 float cap_record = ctf_captimerecord;
116 float cap_time = (time - flag.ctf_pickuptime);
117 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
118 bool valid_record = (cap_time > 0.01);
119
120 // notify about shit
121 if(ctf_oneflag)
122 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
123 else if(!ctf_captimerecord || !valid_record)
124 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
125 else if(cap_time < cap_record)
126 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));
127 else
128 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));
129
130 // write that shit in the database
131 if(!ctf_oneflag && valid_record) // but not in 1-flag mode
132 if((!ctf_captimerecord) || (cap_time < cap_record))
133 {
134 ctf_captimerecord = cap_time;
135 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
136 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
137 write_recordmarker(player, flag.ctf_pickuptime, cap_time);
138 }
139
140 if(autocvar_g_ctf_leaderboard && !ctf_oneflag && valid_record)
141 race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
142}
143
145{
146 int num_perteam = 0;
147 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
148
149 // automatically return if there's only 1 player on the team
150 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
151 && flag.team);
152}
153
155{
156 // only to the carrier
157 return boolean(client == this.owner);
158}
159
161{
162 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
164 WaypointSprite_UpdateHealth(player.wps_flagcarrier, healtharmor_maxdamage(GetResource(player, RES_HEALTH), GetResource(player, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
165 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
166
167 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
168 {
169 if(!player.wps_enemyflagcarrier)
170 {
171 entity base_wp = WP_FlagCarrier;
172 if (!ctf_oneflag)
173 {
174 switch (player.team)
175 {
176 case NUM_TEAM_1: base_wp = WP_FlagCarrierEnemyRed; break;
177 case NUM_TEAM_2: base_wp = WP_FlagCarrierEnemyBlue; break;
178 case NUM_TEAM_3: base_wp = WP_FlagCarrierEnemyYellow; break;
179 case NUM_TEAM_4: base_wp = WP_FlagCarrierEnemyPink; break;
180 default: base_wp = WP_FlagCarrierEnemyNeutral; break;
181 }
182 }
183 entity wp = WaypointSprite_Spawn(base_wp, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL,
184 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
185 wp.colormod = WPCOLOR_ENEMYFC(player.team);
187
188 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
189 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
190 }
191
192 if(!player.wps_flagreturn)
193 {
194 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
195 owp.colormod = '0 0.8 0.8';
196 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
198 }
199 }
200}
201
202void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
203{
204 float current_distance = vlen(vec2(to.x - from.x, to.y - from.y)); // for the sake of this check, exclude Z axis
205 float initial_height = min(autocvar_g_ctf_pass_arc_max, flag.pass_distance * tanh(autocvar_g_ctf_pass_arc));
206 float current_height = initial_height * min(1, current_distance / flag.pass_distance);
207 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
208
209 vector targpos = to;
210 if (current_height) // make sure we can actually do this arcing path
211 {
212 targpos.z += current_height;
213 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
214 if (trace_fraction < 1)
215 {
216 //print("normal arc line failed, trying to find new pos...");
217 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
219 targpos.z += FLAG_PASS_ARC_OFFSET_Z;
220 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
221 if (trace_fraction < 1)
222 { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
223 /*else
224 print(" ^3SUCCESS^7, using new arc line.\n");*/
225 }
226 }
227
228 //flag.angles = normalize(eY * (to.y - from.y));
229
230 vector desired_direction = normalize(targpos - from);
231 flag.velocity = (turnrate)
232 ? normalize(normalize(flag.velocity) + desired_direction * autocvar_g_ctf_pass_turnrate) * autocvar_g_ctf_pass_velocity
233 : desired_direction * autocvar_g_ctf_pass_velocity;
234}
235
236bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
237{
239 {
240 // directional tracing only
241 float spreadlimit;
242 makevectors(passer_angle);
243
244 // find the closest point on the enemy to the center of the attack
245 vector dist = head_center - passer_center;
246 float h; // hypotenuse, which is the distance between attacker to head
247 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
248 h = vlen(dist);
249 a = dist * v_forward;
250
251 vector nearest_on_line = passer_center + a * v_forward;
252 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
253
254 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, a / autocvar_g_ctf_pass_radius) : 1);
255 spreadlimit = autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit;
256
257 return (spreadlimit && distance_from_line <= spreadlimit
258 && vdist(dist * (1 / h) - v_forward, <=, 90 * DEG2RAD));
259 }
260 return true;
261}
262
263
264// =======================
265// CaptureShield Functions
266// =======================
267
269{
270 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
271 int players_worseeq, players_total;
272
274 return false;
275
276 s = GameRules_scoring_add(p, CTF_CAPS, 0);
277 s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
278 s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
279 s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
280
281 sr = ((s - s2) + (s3 + s4));
282
284 return false;
285
286 players_total = players_worseeq = 0;
288 if(DIFF_TEAM(it, p))
289 continue;
290 se = GameRules_scoring_add(it, CTF_CAPS, 0);
291 se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
292 se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
293 se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0);
294
295 ser = ((se - se2) + (se3 + se4));
296
297 if(ser <= sr)
298 ++players_worseeq;
299 ++players_total;
300 });
301
302 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
303 // use this rule here
304
305 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
306 return false;
307
308 return true;
309}
310
311void ctf_CaptureShield_Update(entity player, bool wanted_status)
312{
313 bool updated_status = ctf_CaptureShield_CheckStatus(player);
314 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
315 {
316 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
317 player.ctf_captureshielded = updated_status;
318 }
319}
320
322{
323 if(!client.ctf_captureshielded) { return false; }
324 if(CTF_SAMETEAM(this, client)) { return false; }
325
326 return true;
327}
328
330{
331 if(!toucher.ctf_captureshielded) { return; }
332 if(CTF_SAMETEAM(this, toucher)) { return; }
333
334 vector mymid = (this.absmin + this.absmax) * 0.5;
335 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
336
337 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
338 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
339}
340
342{
343 entity shield = new(ctf_captureshield);
344
345 shield.enemy = flag;
346 shield.team = flag.team;
349 shield.effects = EF_ADDITIVE;
351 shield.solid = SOLID_TRIGGER;
352 shield.avelocity = '7 0 11';
353 shield.scale = 0.5;
354
355 setorigin(shield, flag.origin);
356 setmodel(shield, MDL_CTF_SHIELD);
357 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
358}
359
360
361// ====================
362// Drop/Pass/Throw Code
363// ====================
364
365void ctf_Handle_Drop(entity flag, entity player, int droptype)
366{
367 // declarations
368 player = (player ? player : flag.pass_sender);
369
370 // main
372 flag.takedamage = DAMAGE_YES;
373 flag.angles = '0 0 0';
374
376 SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
377
378 flag.ctf_droptime = time;
379 flag.ctf_landtime = 0;
380 flag.ctf_dropper = player;
381 flag.ctf_status = FLAG_DROPPED;
382
383 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
384 if(flag.damagedbycontents)
386
387 // messages and sounds
388 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
389 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
390 ctf_EventLog("dropped", player.team, player);
391
392 // scoring
393 GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
394 GameRules_scoring_add(player, CTF_DROPS, 1);
395
396 // waypoints
398 entity base_wp;
399 switch (flag.team)
400 {
401 case NUM_TEAM_1: base_wp = WP_FlagDroppedRed; break;
402 case NUM_TEAM_2: base_wp = WP_FlagDroppedBlue; break;
403 case NUM_TEAM_3: base_wp = WP_FlagDroppedYellow; break;
404 case NUM_TEAM_4: base_wp = WP_FlagDroppedPink; break;
405 default: base_wp = WP_FlagDroppedNeutral; break;
406 }
407 int wp_team = ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team);
408 entity wp = WaypointSprite_Spawn(base_wp, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL,
409 wp_team, flag, wps_flagdropped, true, RADARICON_FLAG);
410 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
411 }
412
414 {
415 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_health);
416 WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResource(flag, RES_HEALTH));
417 }
418
419 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
420
421 if(droptype == DROP_PASS)
422 {
423 flag.pass_distance = 0;
424 flag.pass_sender = NULL;
425 flag.pass_target = NULL;
426 }
427}
428
430{
431 entity sender = flag.pass_sender;
432
433 // transfer flag to player
434 flag.owner = player;
435 flag.owner.flagcarried = flag;
436 GameRules_scoring_vip(player, true);
437
438 // reset flag
439 flag.solid = SOLID_NOT; // before setorigin to prevent area grid linking
440 if(player.vehicle)
441 {
442 setattachment(flag, player.vehicle, "");
443 setorigin(flag, VEHICLE_FLAG_OFFSET);
444 flag.scale = VEHICLE_FLAG_SCALE;
445 }
446 else
447 {
448 setattachment(flag, player, "");
449 setorigin(flag, FLAG_CARRY_OFFSET);
450 }
452 flag.takedamage = DAMAGE_NO;
453 flag.angles = '0 0 0';
454 flag.ctf_status = FLAG_CARRY;
455
456 // messages and sounds
457 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
458 ctf_EventLog("receive", flag.team, player);
459
461 if(it == sender)
462 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
463 else if(it == player)
464 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
465 else if(SAME_TEAM(it, sender))
466 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
467 });
468
469 // create new waypoint
471
472 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
473 player.throw_antispam = sender.throw_antispam;
474
475 flag.pass_distance = 0;
476 flag.pass_sender = NULL;
477 flag.pass_target = NULL;
478}
479
480void ctf_Handle_Throw(entity player, entity receiver, int droptype)
481{
482 entity flag = player.flagcarried;
483 vector targ_origin, flag_velocity;
484
485 if(!flag) { return; }
486 if((droptype == DROP_PASS) && !receiver) { return; }
487
488 if(flag.speedrunning || flag.classname == "phantomflag")
489 {
490 // ensure old waypoints are removed before resetting the flag
491 WaypointSprite_Kill(player.wps_flagcarrier);
492
493 if(player.wps_enemyflagcarrier)
494 WaypointSprite_Kill(player.wps_enemyflagcarrier);
495
496 if(player.wps_flagreturn)
497 WaypointSprite_Kill(player.wps_flagreturn);
498 ctf_RespawnFlag(flag);
499 return;
500 }
501
502 // reset the flag
503 setattachment(flag, NULL, "");
504 tracebox(player.origin + vec3(0, 0, player.mins.z - flag.m_mins.z), // same bbox floor height as player
505 flag.m_mins, flag.m_maxs, player.origin + FLAG_DROP_OFFSET, MOVE_NOMONSTERS, flag);
506 flag.solid = SOLID_TRIGGER; // before setorigin to ensure area grid linking
507 setorigin(flag, trace_endpos);
508 if (trace_startsolid && !nudgeoutofsolid_OrFallback(flag)) // TODO: trace_allsolid would perform better but isn't 100% reliable yet
509 {
510 // the flag's bbox doesn't fit but we can assume the player's current bbox does
511 tracebox(player.origin, player.mins, player.maxs, player.origin + FLAG_DROP_OFFSET, MOVE_NOMONSTERS, flag);
512 flag.origin = trace_endpos;
513 setsize(flag, player.mins, player.maxs); // this allows physics to move the flag somewhere its think func can resize it
514 }
515 flag.owner.flagcarried = NULL;
516 GameRules_scoring_vip(flag.owner, false);
517 flag.owner = NULL;
518 flag.ctf_dropper = player;
519 flag.ctf_droptime = time;
520 flag.ctf_landtime = 0;
521
522 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
523
524 switch(droptype)
525 {
526 case DROP_PASS:
527 {
528 // warpzone support:
529 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
530 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
531 WarpZone_RefSys_Copy(flag, receiver);
532 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, 0.5 * (receiver.absmin + receiver.absmax)); // this is target origin as seen by the flag
533
534 flag.pass_distance = vlen(vec2(targ_origin.x - player.origin.x, targ_origin.y - player.origin.y)); // for the sake of this check, exclude Z axis
535 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
536
537 // main
539 flag.takedamage = DAMAGE_NO;
540 flag.pass_sender = player;
541 flag.pass_target = receiver;
542 flag.ctf_status = FLAG_PASSING;
543
544 // other
545 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
546 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
547 ctf_EventLog("pass", flag.team, player);
548 break;
549 }
550
551 case DROP_THROW:
552 {
553 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'));
554
556 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
557 ctf_Handle_Drop(flag, player, droptype);
558 navigation_dynamicgoal_set(flag, player);
559 break;
560 }
561
562 case DROP_RESET:
563 {
564 flag.velocity = '0 0 0'; // do nothing
565 break;
566 }
567
568 default:
569 case DROP_NORMAL:
570 {
571 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);
572 ctf_Handle_Drop(flag, player, droptype);
573 navigation_dynamicgoal_set(flag, player);
574 break;
575 }
576 }
577
578 // kill old waypointsprite
579 WaypointSprite_Ping(player.wps_flagcarrier);
580 WaypointSprite_Kill(player.wps_flagcarrier);
581
582 if(player.wps_enemyflagcarrier)
583 WaypointSprite_Kill(player.wps_enemyflagcarrier);
584
585 if(player.wps_flagreturn)
586 WaypointSprite_Kill(player.wps_flagreturn);
587
588 // captureshield
589 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
590}
591
592#if 0
593void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
594{
595 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
596}
597#endif
598
599// ==============
600// Event Handlers
601// ==============
602
603void nades_GiveBonus(entity player, float score);
604
605void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
606{
607 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
608 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
609 entity player_team_flag = NULL, tmp_entity;
610 float old_time, new_time;
611
612 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
613 if(CTF_DIFFTEAM(player, flag)) { return; }
614 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)
615
616 if (toucher.goalentity == flag.bot_basewaypoint)
617 toucher.goalentity_lock_timeout = 0;
618
619 if(ctf_oneflag)
620 {
621 IL_EACH(g_flags, SAME_TEAM(it, player),
622 {
623 player_team_flag = it;
624 break;
625 });
626 }
627
629
630 player.throw_prevtime = time;
631 player.throw_count = 0;
632
633 // messages and sounds
634 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
635 ctf_CaptureRecord(enemy_flag, player);
636 _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);
637
638 switch(capturetype)
639 {
640 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
641 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
642 default: break;
643 }
644
645 // scoring
646 float pscore = 0;
647 if(enemy_flag.score_capture || flag.score_capture)
648 pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
649 GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
650 float capscore = 0;
651 if(enemy_flag.score_team_capture || flag.score_team_capture)
652 capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
653 GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
654
655 old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
656 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
657 if(!old_time || new_time < old_time)
658 GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time);
659
660 // effects
661 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
662#if 0
663 shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
664#endif
665
666 // other
667 if(capturetype == CAPTURE_NORMAL)
668 {
669 WaypointSprite_Kill(player.wps_flagcarrier);
670 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
671
672 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
673 { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
674 }
675
676 flag.enemy = toucher;
677
678 // reset the flag
679 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
680 ctf_RespawnFlag(enemy_flag);
681}
682
684{
685 // messages and sounds
686 if(IS_MONSTER(player))
687 {
688 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.m_name);
689 }
690 else if(flag.team)
691 {
692 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
693 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
694 }
695 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
696 ctf_EventLog("return", flag.team, player);
697
698 // scoring
699 if(IS_PLAYER(player))
700 {
701 GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
702 GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns
703
705 }
706
707 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
708
709 if(flag.ctf_dropper)
710 {
711 GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
712 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
713 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
714 }
715
716 // other
717 if(player.flagcarried == flag)
718 WaypointSprite_Kill(player.wps_flagcarrier);
719
720 flag.enemy = player;
721
722 // reset the flag
723 ctf_RespawnFlag(flag);
724}
725
726void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
727{
728 // declarations
729 float pickup_dropped_score; // used to calculate dropped pickup score
730
731 if(autocvar_g_ctf_flag_stay && pickuptype == PICKUP_BASE)
732 {
733 entity newflag = spawn();
734 copyentity_qc(flag, newflag);
735 newflag.classname = "phantomflag"; // identifier for other code
736 IL_PUSH(g_moveables, newflag); // cleared by copyentity_qc
737 IL_PUSH(g_flags, newflag);
738 // uncomment for "ghostly" phantom flag appearance (may look bad)
739 //newflag.effects |= EF_ADDITIVE;
740 flag = newflag;
741 }
742
743 // attach the flag to the player
744 flag.owner = player;
745 player.flagcarried = flag;
746 GameRules_scoring_vip(player, true);
747 flag.solid = SOLID_NOT; // before setorigin to prevent area grid linking
748 if(player.vehicle)
749 {
750 setattachment(flag, player.vehicle, "");
751 setorigin(flag, VEHICLE_FLAG_OFFSET);
752 flag.scale = VEHICLE_FLAG_SCALE;
753 }
754 else
755 {
756 setattachment(flag, player, "");
757 setorigin(flag, FLAG_CARRY_OFFSET);
758 }
759
760 // flag setup
762 flag.takedamage = DAMAGE_NO;
763 flag.angles = '0 0 0';
764 flag.ctf_status = FLAG_CARRY;
765
766 flag.damagedbycontents = false;
769
771
772 switch(pickuptype)
773 {
774 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
775 case PICKUP_DROPPED: SetResourceExplicit(flag, RES_HEALTH, flag.max_health); break; // reset health/return timelimit
776 default: break;
777 }
778
779 // messages and sounds
780 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
781 if(ctf_stalemate)
782 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
783 if(!flag.team)
784 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
785 else if(CTF_DIFFTEAM(player, flag))
786 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
787 else
788 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
789
790 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
791
792 if(!flag.team)
793 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); });
794
795 if(flag.team)
796 FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
797 if(CTF_SAMETEAM(flag, it))
798 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);
799 else if(DIFF_TEAM(player, it))
800 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_ENEMY_OTHER), Team_ColorCode(player.team), player.netname);
801 });
802
803 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
804
805 // scoring
806 GameRules_scoring_add(player, CTF_PICKUPS, 1);
808 switch(pickuptype)
809 {
810 case PICKUP_BASE:
811 {
812 GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
813 ctf_EventLog("steal", flag.team, player);
814 break;
815 }
816
817 case PICKUP_DROPPED:
818 {
819 pickup_dropped_score = (autocvar_g_ctf_flag_return_time > 0 ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
820 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);
821 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
822 GameRules_scoring_add_team(player, SCORE, pickup_dropped_score);
823 ctf_EventLog("pickup", flag.team, player);
824 break;
825 }
826
827 default: break;
828 }
829
830 // speedrunning
831 if(pickuptype == PICKUP_BASE)
832 {
833 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
834 if((player.speedrunning) && (ctf_captimerecord))
836 }
837
838 // effects
839 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
840
841 // waypoints
842 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
844 WaypointSprite_Ping(player.wps_flagcarrier);
845}
846
847
848// ===================
849// Main Flag Functions
850// ===================
851
852void ctf_CheckFlagReturn(entity flag, int returntype)
853{
854 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
855 {
856 if (flag.wps_flagdropped
858 {
859 WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResource(flag, RES_HEALTH));
860 }
861
862 if((GetResource(flag, RES_HEALTH) <= 0)
864 {
865 switch(returntype)
866 {
867 case RETURN_DROPPED:
868 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
869 case RETURN_DAMAGE:
870 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
871 case RETURN_SPEEDRUN:
872 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
873 case RETURN_NEEDKILL:
874 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
875 default:
876 case RETURN_TIMEOUT:
877 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
878 }
879 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
880 ctf_EventLog("returned", flag.team, NULL);
881 flag.enemy = NULL;
882 ctf_RespawnFlag(flag);
883 }
884 }
885}
886
888{
889 // make spectators see what the player would see
891 entity wp_owner = this.owner;
892
893 // team waypoints
894 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
895 if(SAME_TEAM(wp_owner, e) || !IS_PLAYER(e))
896 return false;
897 if(IS_INVISIBLE(wp_owner))
898 return false; // hide the waypointsprite if the owner is invisible
899
900 return true;
901}
902
904{
905 // declarations
906 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
907 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
908
909 // build list of stale flags
910 IL_EACH(g_flags, true,
911 {
913 if(it.ctf_status != FLAG_BASE)
914 if(time >= it.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !it.team) // instant stalemate in oneflag
915 {
916 it.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
917 ctf_staleflaglist = it;
918
919 switch(it.team)
920 {
921 case NUM_TEAM_1: ++stale_red_flags; break;
922 case NUM_TEAM_2: ++stale_blue_flags; break;
923 case NUM_TEAM_3: ++stale_yellow_flags; break;
924 case NUM_TEAM_4: ++stale_pink_flags; break;
925 default: ++stale_neutral_flags; break;
926 }
927 }
928 });
929
930 if(ctf_oneflag)
931 stale_flags = (stale_neutral_flags >= 1);
932 else
933 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
934
935 if(ctf_oneflag && stale_flags == 1)
936 ctf_stalemate = true;
937 else if(stale_flags >= 2)
938 ctf_stalemate = true;
939 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
940 { ctf_stalemate = false; wpforenemy_announced = false; }
941 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
942 { ctf_stalemate = false; wpforenemy_announced = false; }
943
944 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
945 if(ctf_stalemate)
946 {
947 for(entity tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
948 {
949 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
950 {
951 entity base_wp = WP_FlagCarrier;
952 if (!ctf_oneflag)
953 {
954 switch (tmp_entity.owner.team)
955 {
956 case NUM_TEAM_1: base_wp = WP_FlagCarrierEnemyRed; break;
957 case NUM_TEAM_2: base_wp = WP_FlagCarrierEnemyBlue; break;
958 case NUM_TEAM_3: base_wp = WP_FlagCarrierEnemyYellow; break;
959 case NUM_TEAM_4: base_wp = WP_FlagCarrierEnemyPink; break;
960 default: base_wp = WP_FlagCarrierEnemyNeutral; break;
961 }
962 }
963 entity wp = WaypointSprite_Spawn(base_wp, 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, NULL,
964 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
965 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
966 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
967 }
968 }
969
971 {
972 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)); });
973
975 }
976 }
977}
978
979void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
980{
981 if(ITEM_DAMAGE_NEEDKILL(deathtype))
982 {
984 this.ctf_flagdamaged_byworld = true;
985 else
986 {
987 SetResourceExplicit(this, RES_HEALTH, 0);
989 }
990 return;
991 }
993 {
994 // reduce health and check if it should be returned
995 TakeResource(this, RES_HEALTH, damage);
997 return;
998 }
999}
1000
1002{
1003 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
1004
1005 // sanity checks
1006 if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
1007 tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
1008 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
1009 setsize(this, this.m_mins, this.m_maxs);
1010 }
1011
1012 // main think method
1013 switch(this.ctf_status)
1014 {
1015 case FLAG_BASE:
1016 {
1018 {
1019 IL_EACH(g_flags, true,
1020 {
1021 if(it.ctf_status == FLAG_DROPPED)
1022 if(vdist(this.origin - it.origin, <, autocvar_g_ctf_dropped_capture_radius))
1023 if((this.noalign || it.ctf_landtime) && time > ((this.noalign) ? it.ctf_droptime : it.ctf_landtime) + autocvar_g_ctf_dropped_capture_delay)
1025 });
1026 }
1027 return;
1028 }
1029
1030 case FLAG_DROPPED:
1031 {
1032 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
1033 if(IS_ONGROUND(this) && !this.ctf_landtime)
1034 this.ctf_landtime = time; // landtime is reset when thrown, and we don't want to restart the timer if the flag is pushed
1035
1037 {
1038 vector midpoint = ((this.absmin + this.absmax) * 0.5);
1039 if(pointcontents(midpoint) == CONTENT_WATER)
1040 {
1041 this.velocity *= 0.5;
1042
1043 if (pointcontents(midpoint + eZ * FLAG_FLOAT_OFFSET_Z) == CONTENT_WATER)
1044 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
1045 else
1046 { set_movetype(this, MOVETYPE_FLY); }
1047 }
1048 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
1049 }
1051 {
1053 {
1054 SetResourceExplicit(this, RES_HEALTH, 0);
1056 return;
1057 }
1058 }
1060 {
1063 return;
1064 }
1066 {
1069 return;
1070 }
1071 return;
1072 }
1073
1074 case FLAG_CARRY:
1075 {
1077 {
1078 SetResourceExplicit(this, RES_HEALTH, 0);
1080
1081 CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
1082 ImpulseCommands(this.owner);
1083 }
1085 {
1087 {
1089 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1090 }
1091 }
1092 if(CTF_SAMETEAM(this, this.owner) && this.team)
1093 {
1094 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1096 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1097 ctf_Handle_Return(this, this.owner);
1098 }
1099 return;
1100 }
1101
1102 case FLAG_PASSING:
1103 {
1104 vector targ_origin = (this.pass_target.absmin + this.pass_target.absmax) * 0.5;
1105 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1106 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1107
1108 if((this.pass_target == NULL)
1109 || (IS_DEAD(this.pass_target))
1110 || (this.pass_target.flagcarried)
1111 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1112 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1114 {
1115 // give up, pass failed
1117 }
1118 else
1119 {
1120 // still a viable target, go for it
1121 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1122 }
1123 return;
1124 }
1125
1126 default: // this should never happen
1127 {
1128 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1129 return;
1130 }
1131 }
1132}
1133
1134METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1135{
1136 return = false;
1137 if(game_stopped) return;
1139
1140 bool is_not_monster = (!IS_MONSTER(toucher));
1141
1142 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1144 {
1146 {
1147 SetResourceExplicit(flag, RES_HEALTH, 0);
1149 }
1150 if(!flag.ctf_flagdamaged_byworld) { return; }
1151 }
1152
1153 // special touch behaviors
1154 // TODO: mutator hook to prevent picking up objectives
1155 if(IS_INDEPENDENT_PLAYER(toucher)) { return; }
1156 else if(IS_VEHICLE(toucher))
1157 {
1159 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1160 else
1161 return; // do nothing
1162 }
1163 else if(IS_MONSTER(toucher))
1164 {
1166 return; // do nothing
1167 }
1168 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1169 {
1170 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1171 {
1172 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1173 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1174 flag.wait = time + FLAG_TOUCHRATE;
1175 }
1176 return;
1177 }
1178 else if(IS_DEAD(toucher)) { return; }
1179
1180 switch(flag.ctf_status)
1181 {
1182 case FLAG_BASE:
1183 {
1184 if(ctf_oneflag)
1185 {
1186 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && flag.team && !toucher.flagcarried.team && is_not_monster)
1187 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1188 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1189 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1190 }
1191 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1192 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to their base
1193 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)
1194 {
1195 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1196 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1197 }
1198 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1199 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1200 break;
1201 }
1202
1203 case FLAG_DROPPED:
1204 {
1206 ctf_Handle_Return(flag, toucher); // toucher just returned their own flag
1207 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1208 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1209 break;
1210 }
1211
1212 case FLAG_CARRY:
1213 {
1214 LOG_TRACE("Someone touched a flag even though it was being carried?");
1215 break;
1216 }
1217
1218 case FLAG_PASSING:
1219 {
1220 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1221 {
1222 if(DIFF_TEAM(toucher, flag.pass_sender))
1223 {
1226 else if(is_not_monster && (!toucher.flagcarried))
1228 }
1229 else if(!toucher.flagcarried)
1231 }
1232 break;
1233 }
1234 }
1235}
1236
1238{
1239 flag.watertype = CONTENT_EMPTY; // TODO: it is unclear why this workaround is needed, likely many other potential breakage points!!
1240
1241 // reset the player (if there is one)
1242 if((flag.owner) && (flag.owner.flagcarried == flag))
1243 {
1244 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1245 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1246 WaypointSprite_Kill(flag.wps_flagcarrier);
1247
1248 flag.owner.flagcarried = NULL;
1249 GameRules_scoring_vip(flag.owner, false);
1250
1251 if(flag.speedrunning)
1252 ctf_FakeTimeLimit(flag.owner, -1);
1253 }
1254
1255 if((flag.owner) && (flag.owner.vehicle))
1256 flag.scale = FLAG_SCALE;
1257
1258 if(flag.ctf_status == FLAG_DROPPED)
1259 { WaypointSprite_Kill(flag.wps_flagdropped); }
1260
1261 // reset the flag
1262 setattachment(flag, NULL, "");
1263 flag.solid = SOLID_TRIGGER; // before setorigin to ensure area grid linking
1264 setorigin(flag, flag.ctf_spawnorigin);
1265
1266 //set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS)); // would be desired, except maps that want floating flags have it set to fall!
1267 set_movetype(flag, MOVETYPE_NONE); // match the initial setup handling (flag doesn't move when spawned)
1268 flag.takedamage = DAMAGE_NO;
1269 SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
1270 flag.velocity = '0 0 0';
1271 flag.angles = flag.mangle;
1272 flag.flags = FL_ITEM | FL_NOTARGET;
1273
1274 flag.damagedbycontents = false;
1277
1279
1280 flag.ctf_status = FLAG_BASE;
1281 flag.owner = NULL;
1282 flag.pass_distance = 0;
1283 flag.pass_sender = NULL;
1284 flag.pass_target = NULL;
1285 flag.ctf_dropper = NULL;
1286 flag.ctf_pickuptime = 0;
1287 flag.ctf_droptime = 0;
1288 flag.ctf_landtime = 0;
1289 flag.ctf_flagdamaged_byworld = false;
1291
1293
1294 if(flag.classname == "phantomflag")
1295 delete(flag);
1296}
1297
1299{
1300 if(this.owner && IS_PLAYER(this.owner))
1302
1303 this.enemy = NULL;
1304 ctf_RespawnFlag(this);
1305}
1306
1308{
1310 entity wp_owner = this.owner;
1311 entity flag = e.flagcarried;
1312 if(flag && CTF_SAMETEAM(e, flag))
1313 return false;
1314 if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1315 return false;
1316 return true;
1317}
1318
1319void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1320{
1321 // bot waypoints
1323 navigation_dynamicgoal_init(this, true);
1324
1325 // waypointsprites
1326 entity basename;
1327 switch (this.team)
1328 {
1329 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1330 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1331 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1332 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1333 default: basename = WP_FlagBaseNeutral; break;
1334 }
1335
1337 {
1338 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1339 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1341 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1343 }
1344
1345 // captureshield setup
1347}
1348
1350
1351void ctf_FlagSetup(int teamnum, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1352{
1353 // main setup
1354 IL_PUSH(g_flags, flag);
1355
1356 setattachment(flag, NULL, "");
1357
1358 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnum), Team_ColorName_Upper(teamnum)));
1359 flag.team = teamnum;
1360 flag.classname = "item_flag_team";
1361 flag.target = "###item###"; // for finding the nearest item using findnearest
1362 flag.flags = FL_ITEM | FL_NOTARGET;
1363 IL_PUSH(g_items, flag);
1364 flag.solid = SOLID_TRIGGER;
1365 flag.takedamage = DAMAGE_NO;
1366 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1367
1369 SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
1370
1371 flag.event_damage = ctf_FlagDamage;
1372 flag.pushable = true;
1373 flag.teleportable = TELEPORT_NORMAL;
1374 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1375 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1376 flag.damagedbycontents = false; // can be damaged by contents only when dropped
1377
1378 flag.velocity = '0 0 0';
1379 flag.mangle = flag.angles;
1380 flag.reset = ctf_Reset;
1381 settouch(flag, ctf_FlagTouch);
1382 setthink(flag, ctf_FlagThink);
1383 flag.nextthink = time + FLAG_THINKRATE;
1384 flag.ctf_status = FLAG_BASE;
1385
1386 // set correct team colors
1387 flag.glowmod = Team_ColorRGB(teamnum);
1388 flag.colormap = (teamnum) ? (teamnum - 1) * 0x11 : 0x00;
1389 flag.colormap |= BIT(10); // RENDER_COLORMAPPED
1390
1391 // crudely force them all to 0
1393 flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1394
1395 string teamname = Static_Team_ColorName_Lower(teamnum);
1396 // appearence
1397 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1398 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1399 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1400 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnum).eent_eff_name; }
1401 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnum).eent_eff_name; }
1402 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnum).eent_eff_name; }
1403
1404 // sounds
1405#define X(s,b) \
1406 if(flag.s == "") flag.s = b; \
1407 precache_sound(flag.s);
1408
1409 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnum))))
1410 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnum))))
1411 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnum))))
1412 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnum))))
1413 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1414 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1415 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1416#undef X
1417
1418 // precache
1419 precache_model(flag.model);
1420
1421 // appearence
1422 _setmodel(flag, flag.model); // precision set below
1423 setsize(flag, vrint(CTF_FLAG.m_mins * flag.scale), vrint(CTF_FLAG.m_maxs * flag.scale));
1424 flag.m_mins = flag.mins; // store these for squash checks
1425 flag.m_maxs = flag.maxs;
1426 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1427
1429 {
1430 switch(teamnum)
1431 {
1432 case NUM_TEAM_1: flag.glow_color = 251; break;
1433 case NUM_TEAM_2: flag.glow_color = 210; break;
1434 case NUM_TEAM_3: flag.glow_color = 110; break;
1435 case NUM_TEAM_4: flag.glow_color = 145; break;
1436 default: flag.glow_color = 254; break;
1437 }
1438 flag.glow_size = 25;
1439 flag.glow_trail = 1;
1440 }
1441
1442 flag.effects |= EF_LOWPRECISION;
1443 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1445 {
1446 switch(teamnum)
1447 {
1448 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1449 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1450 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1451 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1452 default: flag.effects |= EF_DIMLIGHT; break;
1453 }
1454 }
1455
1456 // flag placement
1457 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1458 {
1459 flag.dropped_origin = flag.origin;
1460 flag.noalign = true;
1462 }
1463 else // drop to floor, automatically find a platform and set that as spawn origin
1464 {
1465 flag.noalign = false;
1468 }
1469
1471}
1472
1473
1474// ================
1475// Bot player logic
1476// ================
1477
1478// NOTE: LEGACY CODE, needs to be re-written!
1479
1481{
1482 vector s = '0 0 0';
1483 vector fo = '0 0 0';
1484 int n = 0;
1485
1486 entity f1 = NULL, f2 = NULL;
1487
1488 IL_EACH(g_flags, it.classname != "phantomflag",
1489 {
1490 // save base flags incase symmetry is checked
1491 if(!f1 && it.team == NUM_TEAM_1)
1492 f1 = it;
1493 else if(!f2 && it.team == NUM_TEAM_2)
1494 f2 = it;
1495 fo = it.origin;
1496 s += fo;
1497 ++n;
1498 });
1499 if(!n)
1500 return;
1501
1502 // TODO: Consider changing this back to `s / n` after https://github.com/graphitemaster/gmqcc/issues/210.
1503 havocbot_middlepoint = s * (1 / n);
1505
1508 if(n == 2)
1509 {
1510 // for symmetrical editing of waypoints
1511 float m = -(f1.origin.y - f2.origin.y) / (max(f1.origin.x - f2.origin.x, FLOAT_EPSILON));
1512 float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
1515 }
1517}
1518
1519
1521{
1522 IL_EACH(g_flags, CTF_SAMETEAM(bot, it),
1523 {
1524 return it;
1525 });
1526 return NULL;
1527}
1528
1530{
1531 IL_EACH(g_flags, true,
1532 {
1533 if(ctf_oneflag)
1534 {
1535 if(CTF_DIFFTEAM(bot, it))
1536 {
1537 if(it.team)
1538 {
1539 if(bot.flagcarried)
1540 return it;
1541 }
1542 else if(!bot.flagcarried)
1543 return it;
1544 }
1545 }
1546 else if (CTF_DIFFTEAM(bot, it))
1547 return it;
1548 });
1549 return NULL;
1550}
1551
1552int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1553{
1554 if (!teamplay)
1555 return 0;
1556
1557 int c = 0;
1558
1560 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1561 continue;
1562
1563 if(vdist(it.origin - org, <, tc_radius))
1564 ++c;
1565 });
1566
1567 return c;
1568}
1569
1570// unused
1571#if 0
1572void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1573{
1574 entity head = ctf_worldflaglist;
1575 while (head)
1576 {
1577 if (CTF_SAMETEAM(this, head))
1578 break;
1579 head = head.ctf_worldflagnext;
1580 }
1581 if (head)
1582 navigation_routerating(this, head, ratingscale, 10000);
1583}
1584#endif
1585
1586void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1587{
1588 entity chosen = NULL;
1589 IL_EACH(g_flags, CTF_SAMETEAM(this, it),
1590 {
1591 if(this.flagcarried)
1592 if((this.flagcarried.cnt || it.cnt) && this.flagcarried.cnt != it.cnt)
1593 {
1594 // skip base if it has a different group
1595 continue;
1596 }
1597
1598 chosen = it;
1599 break;
1600 });
1601
1602 navigation_routerating(this, chosen.bot_basewaypoint, ratingscale, 10000);
1603}
1604
1605void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1606{
1607 entity chosen = NULL;
1608 IL_EACH(g_flags, true,
1609 {
1610 if(ctf_oneflag)
1611 {
1612 if(CTF_DIFFTEAM(this, it))
1613 {
1614 if(it.team)
1615 {
1616 if(this.flagcarried)
1617 {
1618 chosen = it;
1619 break;
1620 }
1621 }
1622 else if(!this.flagcarried)
1623 {
1624 chosen = it;
1625 break;
1626 }
1627 }
1628 }
1629 else if(CTF_DIFFTEAM(this, it))
1630 {
1631 chosen = it;
1632 break;
1633 }
1634 });
1635
1636 if (chosen)
1637 {
1638 if (chosen.ctf_status == FLAG_CARRY)
1639 {
1640 // adjust rating of our flag carrier depending on their health
1641 chosen = chosen.tag_entity;
1642 float f = bound(0, (GetResource(chosen, RES_HEALTH) + GetResource(chosen, RES_ARMOR)) / 100, 2) - 1;
1643 ratingscale += ratingscale * f * 0.1;
1644 }
1645 navigation_routerating(this, chosen, ratingscale, 10000);
1646 }
1647}
1648
1649void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1650{
1651 // disabled because we always spawn waypoints for flags with waypoint_spawnforitem_force
1652 /*
1653 if (!bot_waypoints_for_items)
1654 {
1655 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1656 return;
1657 }
1658 */
1660
1661 if (!head)
1662 return;
1663
1664 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1665}
1666
1668{
1669 entity mf = havocbot_ctf_find_flag(this);
1670
1671 if(mf.ctf_status == FLAG_BASE)
1672 return;
1673
1674 if(mf.tag_entity)
1675 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1676}
1677
1678void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1679{
1680 IL_EACH(g_flags, true,
1681 {
1682 // flag is out in the field
1683 if(it.ctf_status != FLAG_BASE)
1684 if(it.tag_entity==NULL) // dropped
1685 {
1686 if(df_radius)
1687 {
1688 if(vdist(org - it.origin, <, df_radius))
1689 navigation_routerating(this, it, ratingscale, 10000);
1690 }
1691 else
1692 navigation_routerating(this, it, ratingscale, 10000);
1693 }
1694 });
1695}
1696
1698{
1699 float cdefense, cmiddle, coffense;
1700 entity mf, ef;
1701
1702 if(IS_DEAD(this))
1703 return;
1704
1705 // Check ctf flags
1706 if (this.flagcarried)
1707 {
1709 return;
1710 }
1711
1712 mf = havocbot_ctf_find_flag(this);
1714
1715 // Retrieve stolen flag
1716 if(mf.ctf_status!=FLAG_BASE)
1717 {
1719 return;
1720 }
1721
1722 // If enemy flag is taken go to the middle to intercept pursuers
1723 if(ef.ctf_status!=FLAG_BASE)
1724 {
1726 return;
1727 }
1728
1729 // if there is no one else on the team switch to offense
1730 int count = 0;
1731 // don't check if this bot is a player since it isn't true when the bot is added to the server
1732 FOREACH_CLIENT(it != this && IS_PLAYER(it) && SAME_TEAM(it, this), { ++count; });
1733
1734 if (count == 0)
1735 {
1737 return;
1738 }
1739 else if (time < CS(this).jointime + 1)
1740 {
1741 // if bots spawn all at once set good default roles
1742 if (count == 1)
1743 {
1745 return;
1746 }
1747 else if (count == 2)
1748 {
1750 return;
1751 }
1752 }
1753
1754 // Evaluate best position to take
1755 // Count mates on middle position
1757
1758 // Count mates on defense position
1759 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
1760
1761 // Count mates on offense position
1762 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
1763
1764 if(cdefense<=coffense)
1766 else if(coffense<=cmiddle)
1768 else
1770
1771 // if bots spawn all at once assign them a more appropriated role after a while
1772 if (time < CS(this).jointime + 1 && count > 2)
1773 this.havocbot_role_timeout = time + 10 + random() * 10;
1774}
1775
1777{
1778 if (item.classname != "waypoint")
1779 return false;
1780
1781 IL_EACH(g_flags, item == it.bot_basewaypoint,
1782 {
1783 return true;
1784 });
1785 return false;
1786}
1787
1789{
1790 if(IS_DEAD(this))
1791 {
1793 return;
1794 }
1795
1796 if (this.flagcarried == NULL)
1797 {
1799 return;
1800 }
1801
1803 {
1805
1806 // role: carrier
1807 entity mf = havocbot_ctf_find_flag(this);
1808 vector base_org = mf.dropped_origin;
1809 float base_rating = (mf.ctf_status == FLAG_BASE) ? 10000 : (vdist(this.origin - base_org, >, 100) ? 2000 : 1000);
1810 if(ctf_oneflag)
1811 havocbot_goalrating_ctf_enemybase(this, base_rating);
1812 else
1813 havocbot_goalrating_ctf_ourbase(this, base_rating);
1814
1815 // start collecting items very close to the bot but only inside of own base radius
1816 if (vdist(this.origin - base_org, <, havocbot_middlepoint_radius))
1817 havocbot_goalrating_items(this, 15000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
1818
1819 havocbot_goalrating_items(this, 10000, base_org, havocbot_middlepoint_radius * 0.5);
1820
1822
1824
1825 entity goal = this.goalentity;
1826 if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
1827 this.goalentity_lock_timeout = time + ((this.enemy) ? 2 : 3);
1828
1829 if (goal)
1830 this.havocbot_cantfindflag = time + 10;
1831 else if (time > this.havocbot_cantfindflag)
1832 {
1833 // Can't navigate to my own base, suicide!
1834 // TODO: drop it and wander around
1835 Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
1836 return;
1837 }
1838 }
1839}
1840
1842{
1843 entity mf, ef;
1844
1845 if(IS_DEAD(this))
1846 {
1848 return;
1849 }
1850
1851 if (this.flagcarried)
1852 {
1854 return;
1855 }
1856
1857 // If enemy flag is back on the base switch to previous role
1859 if(ef.ctf_status==FLAG_BASE)
1860 {
1861 this.havocbot_role = this.havocbot_previous_role;
1862 this.havocbot_role_timeout = 0;
1863 return;
1864 }
1865 if (ef.ctf_status == FLAG_DROPPED)
1866 {
1868 return;
1869 }
1870
1871 // If the flag carrier reached the base switch to defense
1872 mf = havocbot_ctf_find_flag(this);
1873 if (mf.ctf_status != FLAG_BASE && vdist(ef.origin - mf.dropped_origin, <, 900))
1874 {
1876 return;
1877 }
1878
1879 // Set the role timeout if necessary
1880 if (!this.havocbot_role_timeout)
1881 {
1882 this.havocbot_role_timeout = time + random() * 30 + 60;
1883 }
1884
1885 // If nothing happened just switch to previous role
1886 if (time > this.havocbot_role_timeout)
1887 {
1888 this.havocbot_role = this.havocbot_previous_role;
1889 this.havocbot_role_timeout = 0;
1890 return;
1891 }
1892
1893 // Chase the flag carrier
1895 {
1897
1898 // role: escort
1901 havocbot_goalrating_items(this, 21000, this.origin, 10000);
1902
1904
1906 }
1907}
1908
1910{
1911 entity mf, ef;
1912 vector pos;
1913
1914 if(IS_DEAD(this))
1915 {
1917 return;
1918 }
1919
1920 if (this.flagcarried)
1921 {
1923 return;
1924 }
1925
1926 // Check flags
1927 mf = havocbot_ctf_find_flag(this);
1929
1930 // Own flag stolen
1931 if(mf.ctf_status!=FLAG_BASE)
1932 {
1933 if(mf.tag_entity)
1934 pos = mf.tag_entity.origin;
1935 else
1936 pos = mf.origin;
1937
1938 // Try to get it if closer than the enemy base
1939 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1940 {
1942 return;
1943 }
1944 }
1945
1946 // Escort flag carrier
1947 if(ef.ctf_status!=FLAG_BASE)
1948 {
1949 if(ef.tag_entity)
1950 pos = ef.tag_entity.origin;
1951 else
1952 pos = ef.origin;
1953
1954 if(vdist(pos - mf.dropped_origin, >, 700))
1955 {
1957 return;
1958 }
1959 }
1960
1961 // Set the role timeout if necessary
1962 if (!this.havocbot_role_timeout)
1963 this.havocbot_role_timeout = time + 120;
1964
1965 if (time > this.havocbot_role_timeout)
1966 {
1968 return;
1969 }
1970
1972 {
1974
1975 // role: offense
1978 havocbot_goalrating_items(this, 22000, this.origin, 10000);
1979
1981
1983 }
1984}
1985
1986// Retriever (temporary role):
1988{
1989 entity mf;
1990
1991 if(IS_DEAD(this))
1992 {
1994 return;
1995 }
1996
1997 if (this.flagcarried)
1998 {
2000 return;
2001 }
2002
2003 // If flag is back on the base switch to previous role
2004 mf = havocbot_ctf_find_flag(this);
2005 if(mf.ctf_status==FLAG_BASE)
2006 {
2007 if (mf.enemy == this) // did this bot return the flag?
2010 return;
2011 }
2012
2013 if (!this.havocbot_role_timeout)
2014 this.havocbot_role_timeout = time + 20;
2015
2016 if (time > this.havocbot_role_timeout)
2017 {
2019 return;
2020 }
2021
2023 {
2024 const float RT_RADIUS = 10000;
2025
2027
2028 // role: retriever
2030 havocbot_goalrating_ctf_droppedflags(this, 12000, this.origin, RT_RADIUS);
2033 vector enemy_base_org = ef.dropped_origin;
2034 // start collecting items very close to the bot but only inside of enemy base radius
2035 if (vdist(this.origin - enemy_base_org, <, havocbot_middlepoint_radius))
2036 havocbot_goalrating_items(this, 27000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
2038
2040
2042 }
2043}
2044
2046{
2047 entity mf;
2048
2049 if(IS_DEAD(this))
2050 {
2052 return;
2053 }
2054
2055 if (this.flagcarried)
2056 {
2058 return;
2059 }
2060
2061 mf = havocbot_ctf_find_flag(this);
2062 if(mf.ctf_status!=FLAG_BASE)
2063 {
2065 return;
2066 }
2067
2068 if (!this.havocbot_role_timeout)
2069 this.havocbot_role_timeout = time + 10;
2070
2071 if (time > this.havocbot_role_timeout)
2072 {
2074 return;
2075 }
2076
2078 {
2080 org.z = this.origin.z;
2081
2083
2084 // role: middle
2086 havocbot_goalrating_ctf_droppedflags(this, 9000, this.origin, 10000);
2089 havocbot_goalrating_items(this, 18000, this.origin, 10000);
2091
2093
2094 entity goal = this.goalentity;
2095 if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
2096 this.goalentity_lock_timeout = time + 2;
2097
2099 }
2100}
2101
2103{
2104 entity mf;
2105
2106 if(IS_DEAD(this))
2107 {
2109 return;
2110 }
2111
2112 if (this.flagcarried)
2113 {
2115 return;
2116 }
2117
2118 // If own flag was captured
2119 mf = havocbot_ctf_find_flag(this);
2120 if(mf.ctf_status!=FLAG_BASE)
2121 {
2123 return;
2124 }
2125
2126 if (!this.havocbot_role_timeout)
2127 this.havocbot_role_timeout = time + 30;
2128
2129 if (time > this.havocbot_role_timeout)
2130 {
2132 return;
2133 }
2135 {
2136 vector org = mf.dropped_origin;
2137
2139
2140 // if enemies are closer to our base, go there
2141 entity closestplayer = NULL;
2142 float distance, bestdistance = 10000;
2143 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
2144 distance = vlen(org - it.origin);
2145 if(distance<bestdistance)
2146 {
2147 closestplayer = it;
2148 bestdistance = distance;
2149 }
2150 });
2151
2152 // role: defense
2153 if(closestplayer)
2154 if(DIFF_TEAM(closestplayer, this))
2155 if(vdist(org - this.origin, >, 1000))
2156 if(checkpvs(this.origin,closestplayer)||random()<0.5)
2158
2163 havocbot_goalrating_items(this, 18000, this.origin, 10000);
2164
2166
2168 }
2169}
2170
2172{
2173 string s = "(null)";
2174 switch(role)
2175 {
2177 s = "carrier";
2178 bot.havocbot_role = havocbot_role_ctf_carrier;
2179 bot.havocbot_role_timeout = 0;
2180 bot.havocbot_cantfindflag = time + 10;
2181 if (bot.havocbot_previous_role != bot.havocbot_role)
2183 break;
2185 s = "defense";
2186 bot.havocbot_role = havocbot_role_ctf_defense;
2187 bot.havocbot_role_timeout = 0;
2188 break;
2190 s = "middle";
2191 bot.havocbot_role = havocbot_role_ctf_middle;
2192 bot.havocbot_role_timeout = 0;
2193 break;
2195 s = "offense";
2196 bot.havocbot_role = havocbot_role_ctf_offense;
2197 bot.havocbot_role_timeout = 0;
2198 break;
2200 s = "retriever";
2201 bot.havocbot_previous_role = bot.havocbot_role;
2202 bot.havocbot_role = havocbot_role_ctf_retriever;
2203 bot.havocbot_role_timeout = time + 10;
2204 if (bot.havocbot_previous_role != bot.havocbot_role)
2206 break;
2208 s = "escort";
2209 bot.havocbot_previous_role = bot.havocbot_role;
2210 bot.havocbot_role = havocbot_role_ctf_escort;
2211 bot.havocbot_role_timeout = time + 30;
2212 if (bot.havocbot_previous_role != bot.havocbot_role)
2214 break;
2215 }
2216 LOG_TRACE(bot.netname, " switched to ", s);
2217}
2218
2219
2220// ==============
2221// Hook Functions
2222// ==============
2223
2225{
2226 entity player = M_ARGV(0, entity);
2227
2228 // initially clear items so they can be set as necessary later.
2229 STAT(OBJECTIVE_STATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2235
2236 // do an iteration for each team (plus neutral) and get the flag for that team
2237 // this allows selection of the "best" flag for each team (e.g. the carried one)
2238 for(int j = 0; j <= AVAILABLE_TEAMS; ++j)
2239 {
2240 int teamnumber = (j > 0) ? Team_IndexToTeam(j) : 0;
2241 entity bestflag = NULL;
2242 int bestprio = 0;
2243
2244 IL_EACH(g_flags, it.team == teamnumber,
2245 {
2246 // if there's a neutral flag in the map, enable oneflag display
2247 if(it.team == 0)
2248 STAT(OBJECTIVE_STATUS, player) |= CTF_FLAG_NEUTRAL;
2249
2250 // use a priority system to decide the preferred flag
2251 int prio = 0;
2252 if(it.owner == player || it.pass_sender == player)
2253 prio = 3;
2254 else if(it.ctf_status == FLAG_DROPPED)
2255 prio = 2;
2256 else if(it.ctf_status != FLAG_BASE)
2257 prio = 1;
2258
2259 if(!bestflag || prio > bestprio)
2260 {
2261 bestflag = it;
2262 bestprio = prio;
2263 }
2264 });
2265
2266 if(bestflag)
2267 {
2268 int t = 0, t2 = 0, t3 = 0;
2269 switch(bestflag.team)
2270 {
2276 }
2277
2278 switch(bestflag.ctf_status)
2279 {
2280 case FLAG_PASSING:
2281 case FLAG_CARRY:
2282 {
2283 if((bestflag.owner == player) || (bestflag.pass_sender == player))
2284 STAT(OBJECTIVE_STATUS, player) |= t; // carrying: player is currently carrying the flag
2285 else
2286 STAT(OBJECTIVE_STATUS, player) |= t2; // taken: someone else is carrying the flag
2287 break;
2288 }
2289 case FLAG_DROPPED:
2290 {
2291 STAT(OBJECTIVE_STATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
2292 break;
2293 }
2294 }
2295 }
2296 }
2297
2298 // item for stopping players from capturing the flag too often
2299 if(player.ctf_captureshielded)
2300 STAT(OBJECTIVE_STATUS, player) |= CTF_SHIELDED;
2301
2302 if(ctf_stalemate)
2303 STAT(OBJECTIVE_STATUS, player) |= CTF_STALEMATE;
2304
2305 ctf_CaptureShield_Update(player, 1);
2306
2307 // update the health of the flag carrier waypointsprite
2308 if(player.wps_flagcarrier)
2309 WaypointSprite_UpdateHealth(player.wps_flagcarrier, healtharmor_maxdamage(GetResource(player, RES_HEALTH), GetResource(player, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
2310}
2311
2312MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in damage.qc
2313{
2314 entity frag_attacker = M_ARGV(1, entity);
2316 float frag_damage = M_ARGV(4, float);
2318
2319 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2320 {
2321 if(frag_target == frag_attacker) // damage done to yourself
2322 {
2325 }
2326 else // damage done to everyone else
2327 {
2330 }
2331
2332 M_ARGV(4, float) = frag_damage;
2333 M_ARGV(6, vector) = frag_force;
2334 }
2335 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2336 {
2339 {
2340 frag_target.wps_helpme_time = time;
2341 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2342 }
2343 // todo: add notification for when flag carrier needs help?
2344 }
2345}
2346
2347MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2348{
2349 entity frag_attacker = M_ARGV(1, entity);
2351
2352 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2353 {
2355 GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
2356 }
2357
2358 if(frag_target.flagcarried)
2359 {
2360 entity tmp_entity = frag_target.flagcarried;
2362 tmp_entity.ctf_dropper = NULL;
2363 }
2364}
2365
2366MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2367{
2368 M_ARGV(2, float) = 0; // frag score
2369 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2370}
2371
2373{
2374 if(player.flagcarried)
2375 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2376
2377 IL_EACH(g_flags, true,
2378 {
2379 if(it.pass_sender == player) { it.pass_sender = NULL; }
2380 if(it.pass_target == player) { it.pass_target = NULL; }
2381 if(it.ctf_dropper == player) { it.ctf_dropper = NULL; }
2382 });
2383}
2384
2385MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2386{
2387 entity player = M_ARGV(0, entity);
2388
2389 ctf_RemovePlayer(player);
2390}
2391
2393{
2394 entity player = M_ARGV(0, entity);
2395
2396 ctf_RemovePlayer(player);
2397}
2398
2400{
2402 return;
2403
2404 entity player = M_ARGV(0, entity);
2405
2406 race_SendAll(player, true);
2407}
2408
2410{
2412 return;
2413
2414 entity player = M_ARGV(0, entity);
2415
2416 race_checkAndWriteName(player);
2417}
2418
2419MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2420{
2421 entity player = M_ARGV(0, entity);
2422
2423 if(player.flagcarried)
2425 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2426}
2427
2429{
2430 return (IS_PLAYER(e) && !IS_DEAD(e) && !IS_INDEPENDENT_PLAYER(e)
2432 && !e.speedrunning && !e.vehicle);
2433}
2435{
2436 if(MUTATOR_RETURNVALUE || game_stopped) return;
2437
2438 entity player = M_ARGV(0, entity);
2439
2440 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2441 {
2442 // pass the flag to a teammate
2443 if(autocvar_g_ctf_pass && player.flagcarried.classname != "phantomflag")
2444 {
2447 entity closest = NULL;
2448 float head_dist2, closest_dist2 = 0;
2449 for (; head; head = head.chain) // find the closest acceptable target to pass to
2450 {
2451 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc)
2452 vector head_center = WarpZone_RefSys_TransformOrigin(head, player, CENTER_OR_VIEWOFS(head));
2453 vector passer_center = CENTER_OR_VIEWOFS(player);
2454 vector nearest_to_passer = WarpZone_RefSys_TransformOrigin(head, player, head.WarpZone_findradius_nearest);
2455
2456 if (ctf_CheckPassDirection(head_center, passer_center, player.v_angle, nearest_to_passer))
2457 {
2458 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried && head.flagcarried.classname != "phantomflag")
2459 {
2460 if(IS_BOT_CLIENT(head))
2461 {
2462 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2463 ctf_Handle_Throw(head, player, DROP_PASS);
2464 }
2465 else
2466 {
2467 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2468 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2469 }
2470 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2471 return true;
2472 }
2473 else if(player.flagcarried && !head.flagcarried)
2474 {
2475 head_dist2 = vlen2(passer_center - head_center);
2476 if (!closest || head_dist2 < closest_dist2) // new closest
2477 {
2478 closest_dist2 = head_dist2;
2479 closest = head;
2480 }
2481 }
2482 }
2483 }
2484
2485 if (closest)
2486 {
2487 ctf_Handle_Throw(player, closest, DROP_PASS);
2488 return true;
2489 }
2490 }
2491
2492 // throw the flag in front of you
2493 if(autocvar_g_ctf_throw && player.flagcarried)
2494 {
2495 if(player.throw_count == -1)
2496 {
2497 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2498 {
2499 player.throw_prevtime = time;
2500 player.throw_count = 1;
2502 return true;
2503 }
2504 else
2505 {
2506 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2507 return false;
2508 }
2509 }
2510 else
2511 {
2512 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2513 else { ++player.throw_count; }
2514 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2515
2516 player.throw_prevtime = time;
2518 return true;
2519 }
2520 }
2521 }
2522}
2523
2524MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2525{
2526 entity player = M_ARGV(0, entity);
2527
2528 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2529 {
2530 player.wps_helpme_time = time;
2531 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2532 }
2533 else // create a normal help me waypointsprite
2534 {
2535 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2536 WaypointSprite_Ping(player.wps_helpme);
2537 }
2538
2539 return true;
2540}
2541
2542MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2543{
2544 entity player = M_ARGV(0, entity);
2545 entity veh = M_ARGV(1, entity);
2546
2547 if(player.flagcarried)
2548 {
2550 {
2552 }
2553 else
2554 {
2555 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2556 setattachment(player.flagcarried, veh, "");
2557 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2558 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2559 //player.flagcarried.angles = '0 0 0';
2560 }
2561 return true;
2562 }
2563}
2564
2565MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2566{
2567 entity player = M_ARGV(0, entity);
2568
2569 if(player.flagcarried)
2570 {
2571 setattachment(player.flagcarried, player, "");
2572 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2573 player.flagcarried.scale = FLAG_SCALE;
2574 player.flagcarried.angles = '0 0 0';
2575 player.flagcarried.nodrawtoclient = NULL;
2576 return true;
2577 }
2578}
2579
2580MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2581{
2582 entity player = M_ARGV(0, entity);
2583
2584 if(player.flagcarried)
2585 {
2586 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2587 ctf_RespawnFlag(player.flagcarried);
2588 return true;
2589 }
2590}
2591
2593{
2594 IL_EACH(g_flags, true,
2595 {
2596 switch(it.ctf_status)
2597 {
2598 case FLAG_DROPPED:
2599 case FLAG_PASSING:
2600 {
2601 // lock the flag, game is over
2603 it.takedamage = DAMAGE_NO;
2604 it.solid = SOLID_NOT;
2605 it.nextthink = false; // stop thinking
2606
2607 //dprint("stopping the ", flag.netname, " from moving.\n");
2608 break;
2609 }
2610
2611 default:
2612 case FLAG_BASE:
2613 case FLAG_CARRY:
2614 {
2615 // do nothing for these flags
2616 break;
2617 }
2618 }
2619 });
2620}
2621
2622MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2623{
2624 entity bot = M_ARGV(0, entity);
2625
2627 return true;
2628}
2629
2630MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2631{
2632 int record_page = M_ARGV(0, int);
2633 string ret_string = M_ARGV(1, string);
2634
2635 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2636 {
2637 if (MapInfo_Get_ByID(i))
2638 {
2639 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2640
2641 if(!r)
2642 continue;
2643
2644 // TODO: uid2name
2645 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2646 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2647 }
2648 }
2649
2650 M_ARGV(1, string) = ret_string;
2651}
2652
2653bool superspec_Spectate(entity this, entity targ); // TODO
2654void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2656{
2657 entity player = M_ARGV(0, entity);
2658 string cmd_name = M_ARGV(1, string);
2659 int cmd_argc = M_ARGV(2, int);
2660
2661 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2662
2663 if(cmd_name == "followfc")
2664 {
2665 if(!g_ctf)
2666 return true;
2667
2668 int _team = 0;
2669 bool found = false;
2670
2671 if(cmd_argc == 2)
2672 {
2673 switch(argv(1))
2674 {
2675 case "red": if(teamplay_bitmask & BIT(0)) _team = NUM_TEAM_1; break;
2676 case "blue": if(teamplay_bitmask & BIT(1)) _team = NUM_TEAM_2; break;
2677 case "yellow": if(teamplay_bitmask & BIT(2)) _team = NUM_TEAM_3; break;
2678 case "pink": if(teamplay_bitmask & BIT(3)) _team = NUM_TEAM_4; break;
2679 }
2680 }
2681
2683 if(it.flagcarried && (it.team == _team || _team == 0))
2684 {
2685 found = true;
2686 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2687 continue; // already spectating this fc, try another
2688 return superspec_Spectate(player, it);
2689 }
2690 });
2691
2692 if(!found)
2693 superspec_msg("", "", player, "No active flag carrier\n", 1);
2694 return true;
2695 }
2696}
2697
2698MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2699{
2701
2702 if(frag_target.flagcarried)
2704}
2705
2706MUTATOR_HOOKFUNCTION(ctf, LogDeath_AppendItemCodes)
2707{
2708 entity player = M_ARGV(0, entity);
2709 if(player.flagcarried)
2710 M_ARGV(1, string) = strcat(M_ARGV(1, string), "F"); // item codes
2711}
2712
2713MUTATOR_HOOKFUNCTION(ctf, PreferPlayerScore_Clear)
2714{
2715 return true;
2716}
2717
2718
2719// ==========
2720// Spawnfuncs
2721// ==========
2722
2723/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2724CTF flag for team one (Red).
2725Keys:
2726"angle" Angle the flag will point (minus 90 degrees)...
2727"model" model to use, note this needs red and blue as skins 0 and 1...
2728"noise" sound played when flag is picked up...
2729"noise1" sound played when flag is returned by a teammate...
2730"noise2" sound played when flag is captured...
2731"noise3" sound played when flag is lost in the field and respawns itself...
2732"noise4" sound played when flag is dropped by a player...
2733"noise5" sound played when flag touches the ground... */
2734spawnfunc(item_flag_team1)
2735{
2736 if(!g_ctf) { delete(this); return; }
2737
2739}
2740
2741/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2742CTF flag for team two (Blue).
2743Keys:
2744"angle" Angle the flag will point (minus 90 degrees)...
2745"model" model to use, note this needs red and blue as skins 0 and 1...
2746"noise" sound played when flag is picked up...
2747"noise1" sound played when flag is returned by a teammate...
2748"noise2" sound played when flag is captured...
2749"noise3" sound played when flag is lost in the field and respawns itself...
2750"noise4" sound played when flag is dropped by a player...
2751"noise5" sound played when flag touches the ground... */
2752spawnfunc(item_flag_team2)
2753{
2754 if(!g_ctf) { delete(this); return; }
2755
2757}
2758
2759/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2760CTF flag for team three (Yellow).
2761Keys:
2762"angle" Angle the flag will point (minus 90 degrees)...
2763"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2764"noise" sound played when flag is picked up...
2765"noise1" sound played when flag is returned by a teammate...
2766"noise2" sound played when flag is captured...
2767"noise3" sound played when flag is lost in the field and respawns itself...
2768"noise4" sound played when flag is dropped by a player...
2769"noise5" sound played when flag touches the ground... */
2770spawnfunc(item_flag_team3)
2771{
2772 if(!g_ctf) { delete(this); return; }
2773
2775}
2776
2777/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2778CTF flag for team four (Pink).
2779Keys:
2780"angle" Angle the flag will point (minus 90 degrees)...
2781"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2782"noise" sound played when flag is picked up...
2783"noise1" sound played when flag is returned by a teammate...
2784"noise2" sound played when flag is captured...
2785"noise3" sound played when flag is lost in the field and respawns itself...
2786"noise4" sound played when flag is dropped by a player...
2787"noise5" sound played when flag touches the ground... */
2788spawnfunc(item_flag_team4)
2789{
2790 if(!g_ctf) { delete(this); return; }
2791
2793}
2794
2795/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2796CTF flag (Neutral).
2797Keys:
2798"angle" Angle the flag will point (minus 90 degrees)...
2799"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2800"noise" sound played when flag is picked up...
2801"noise1" sound played when flag is returned by a teammate...
2802"noise2" sound played when flag is captured...
2803"noise3" sound played when flag is lost in the field and respawns itself...
2804"noise4" sound played when flag is dropped by a player...
2805"noise5" sound played when flag touches the ground... */
2806spawnfunc(item_flag_neutral)
2807{
2808 if(!g_ctf) { delete(this); return; }
2809 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2810
2811 ctf_FlagSetup(0, this);
2812}
2813
2814/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2815Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2816Note: 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.
2817Keys:
2818"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2819"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2820spawnfunc(ctf_team)
2821{
2822 if(!g_ctf) { delete(this); return; }
2823
2824 this.team = this.cnt + 1;
2825}
2826
2827// compatibility for quake maps
2828spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2829spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2830spawnfunc(info_player_team1);
2831spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2832spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2833spawnfunc(info_player_team2);
2834spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2835spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2836
2837spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2838spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2839
2840// compatibility for wop maps
2841spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2842spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2843spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2844spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2845spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2846spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2847
2848
2849// ==============
2850// Initialization
2851// ==============
2852
2853// scoreboard setup
2855{
2857 field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2858 field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2859 field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2860 field(SP_CTF_PICKUPS, "pickups", 0);
2861 field(SP_CTF_FCKILLS, "fckills", 0);
2862 field(SP_CTF_RETURNS, "returns", 0);
2863 field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2864 });
2865}
2866
2867void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2868{
2869 int ctf_teams = 0;
2870
2871 IL_EACH(g_flags, true,
2872 {
2873 //if(it.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2874 //if(it.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2875
2876 switch(it.team)
2877 {
2878 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2879 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2880 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2881 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2882 }
2883 if(it.team == 0) { ctf_oneflag = true; }
2884 });
2885
2887
2888 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2889 {
2890 ctf_teams = 0; // so set the default red and blue teams
2891 BITSET_ASSIGN(ctf_teams, BIT(0));
2892 BITSET_ASSIGN(ctf_teams, BIT(1));
2893 }
2894
2895 //ctf_teams = bound(2, ctf_teams, 4);
2896
2897 // if no teams are found, spawn defaults
2898 Team_MapEnts_FindOrSpawn("ctf_team", ctf_teams);
2899
2900 ctf_ScoreRules(ctf_teams);
2901}
2902
void navigation_goalrating_start(entity this)
void navigation_goalrating_timeout_force(entity this)
Definition navigation.qc:29
float havocbot_symmetry_axis_q
Definition api.qh:94
void navigation_goalrating_timeout_set(entity this)
Definition navigation.qc:20
float havocbot_symmetry_axis_m
Definition api.qh:93
vector havocbot_middlepoint
Definition api.qh:91
bool navigation_goalrating_timeout(entity this)
Definition navigation.qc:44
void navigation_dynamicgoal_init(entity this, bool initially_static)
Definition navigation.qc:77
float goalentity_lock_timeout
Definition api.qh:97
float havocbot_symmetry_origin_order
Definition api.qh:95
void navigation_routerating(entity this, entity e, float f, float rangebias)
void navigation_dynamicgoal_set(entity this, entity dropper)
Definition navigation.qc:87
void navigation_goalrating_timeout_expire(entity this, float seconds)
Definition navigation.qc:36
void navigation_goalrating_end(entity this)
float havocbot_role_timeout
Definition api.qh:46
void waypoint_spawnforitem_force(entity e, vector org)
void navigation_dynamicgoal_unset(entity this)
Definition navigation.qc:96
float havocbot_middlepoint_radius
Definition api.qh:92
#define MUTATOR_HOOKFUNCTION(...)
Definition base.qh:335
#define MUTATOR_RETURNVALUE
Definition base.qh:328
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
#define boolean(value)
Definition bool.qh:9
bool speedrunning
Definition cheats.qh:22
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float max_health
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
void TakeResource(entity receiver, Resource res_type, float amount)
Takes an entity some resource.
Definition sv_ctf.qh:37
float cnt
Definition powerups.qc:24
float count
Definition powerups.qc:22
vector m_mins
Definition items.qc:18
bool pushable
Definition items.qc:14
vector m_maxs
Definition items.qc:18
entity owner
Definition main.qh:87
int team
Definition main.qh:188
entity teams
Definition main.qh:58
#define colormapPaletteColor(c, isPants)
Definition color.qh:5
#define setmodel(this, m)
Definition model.qh:26
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_DEAD(s)
Definition player.qh:244
#define IS_PLAYER(s)
Definition player.qh:242
const int SFL_LOWER_IS_BETTER
Lower scores are better (e.g.
Definition scores.qh:102
const int SFL_TIME
Display as mm:ss.s, value is stored as 10ths of a second (AND 0 is the worst possible value!...
Definition scores.qh:122
const int ST_SCORE
Definition scores.qh:158
const int SFL_SORT_PRIO_SECONDARY
Scoring priority (NOTE: PRIMARY is used for fraglimit) NOTE: SFL_SORT_PRIO_SECONDARY value must be lo...
Definition scores.qh:133
const int SFL_SORT_PRIO_PRIMARY
Definition scores.qh:134
#define autocvar_timelimit
Definition stats.qh:92
bool autocvar_g_ctf_leaderboard
Definition stats.qh:148
float game_stopped
Definition stats.qh:81
vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
Definition util.qc:1401
#define CTF_RECORD
Definition util.qh:101
#define TIME_ENCODE(t)
Definition util.qh:102
const int INITPRIO_SETLOCATION
Definition constants.qh:91
const int INITPRIO_GAMETYPE
Definition constants.qh:87
const int FL_NOTARGET
Definition constants.qh:68
const int FL_ITEM
Definition constants.qh:69
const float MOVE_NOMONSTERS
float trace_dphitcontents
entity trace_ent
const float SOLID_TRIGGER
float DEG2RAD
float DPCONTENTS_SOLID
vector mins
const float EF_RED
vector velocity
const float EF_ADDITIVE
const float CONTENT_WATER
const float EF_FULLBRIGHT
const float SOLID_NOT
float DPCONTENTS_PLAYERCLIP
float time
vector trace_endpos
float checkpvs(vector viewpos, entity viewee)
float trace_startsolid
vector maxs
float DPCONTENTS_MONSTERCLIP
float nextthink
const float EF_BLUE
vector absmax
vector v_forward
vector origin
float trace_fraction
vector absmin
const float CONTENT_EMPTY
#define spawn
const int EF_DIMLIGHT
const int CTF_SHIELDED
Definition ctf.qh:57
const int CTF_PINK_FLAG_CARRYING
Definition ctf.qh:52
const int CTF_YELLOW_FLAG_TAKEN
Definition ctf.qh:47
const int CTF_YELLOW_FLAG_CARRYING
Definition ctf.qh:49
const int CTF_PINK_FLAG_TAKEN
Definition ctf.qh:50
const int CTF_RED_FLAG_LOST
Definition ctf.qh:42
const int CTF_NEUTRAL_FLAG_LOST
Definition ctf.qh:54
const int CTF_YELLOW_FLAG_LOST
Definition ctf.qh:48
const int CTF_RED_FLAG_CARRYING
Definition ctf.qh:43
const int CTF_RED_FLAG_TAKEN
Definition ctf.qh:41
const int CTF_FLAG_NEUTRAL
Definition ctf.qh:56
const int CTF_STALEMATE
Definition ctf.qh:58
#define g_ctf
Definition ctf.qh:38
const int CTF_BLUE_FLAG_LOST
Definition ctf.qh:45
const int CTF_BLUE_FLAG_TAKEN
Definition ctf.qh:44
const int CTF_PINK_FLAG_LOST
Definition ctf.qh:51
const int CTF_NEUTRAL_FLAG_CARRYING
Definition ctf.qh:55
const int CTF_BLUE_FLAG_CARRYING
Definition ctf.qh:46
const int CTF_NEUTRAL_FLAG_TAKEN
Definition ctf.qh:53
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition damage.qc:484
float autocvar_g_balance_armor_blockpercent
Definition damage.qh:21
IntrusiveList g_damagedbycontents
Definition damage.qh:143
#define DMG_NOWEP
Definition damage.qh:104
float EF_LOWPRECISION
entity EFFECT_CAP(int teamid)
Definition all.inc:207
entity EFFECT_PASS(int teamid)
Definition all.inc:191
entity EFFECT_FLAG_TOUCH(int teamid)
Definition all.inc:175
void Send_Effect_(string eff_name, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:160
RES_ARMOR
Definition ent_cs.qc:155
ent angles
Definition ent_cs.qc:146
WriteByte(chan, ent.angles.y/DEC_FACTOR)
SetResourceExplicit(ent, RES_ARMOR, ReadByte() *DEC_FACTOR)) ENTCS_PROP(NAME
const float FLOAT_EPSILON
Definition float.qh:4
void GameLogEcho(string s)
Definition gamelog.qc:15
bool autocvar_sv_eventlog
Definition gamelog.qh:3
string GetMapname()
ERASEABLE void IL_REMOVE(IntrusiveList this, entity it)
Remove any element, anywhere in the list.
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
ERASEABLE bool IL_CONTAINS(IntrusiveList this, entity it)
#define IL_EACH(this, cond, body)
#define ClientConnect
Definition _all.inc:242
#define PlayerPreThink
Definition _all.inc:258
#define SV_ParseClientCommand
Definition _all.inc:289
#define ClientDisconnect
Definition _all.inc:246
#define STAT(...)
Definition stats.qh:94
entity WarpZone_SearchInRadius(vector org, float rad, float nomonsters, WarpZone_FindRadius_cond_callback_t cb)
Modified findradius aware of warpzones, that only includes entities that meet the "cb" criteria.
Definition common.qc:680
vector WarpZone_UnTransformOrigin(entity wz, vector org)
Transforms origin org backwards through warpzone wz, as the inverse of WarpZone_TransformOrigin.
Definition common.qc:526
void WarpZone_TrailParticles(entity own, float eff, vector org, vector end)
Definition common.qc:446
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Transform origin org in from's reference into to's reference.
Definition common.qc:787
void WarpZone_RefSys_Copy(entity me, entity from)
Copies the warpzone reference of from onto me.
Definition common.qc:824
#define WarpZone_TraceLine(org, end, nomonsters, forent)
Definition common.qh:51
#define BITSET_ASSIGN(a, b)
Definition common.qh:122
noref entity WarpZone_SearchInRadius_otherent
Free to use in the callback.
Definition common.qh:71
entity WarpZone_trace_transform
Transform accumulator during a trace.
Definition common.qh:40
#define LOG_TRACE(...)
Definition log.qh:74
ERASEABLE string db_get(int db, string key)
Definition map.qh:93
ERASEABLE void db_put(int db, string key, string value)
Definition map.qh:103
bool MapInfo_Get_ByID(int i)
Definition mapinfo.qc:274
float MapInfo_count
Definition mapinfo.qh:166
string MapInfo_Map_bspname
Definition mapinfo.qh:6
entity goalentity
Definition viewloc.qh:16
float tanh(float e)
Definition mathlib.qc:65
string cmd_name
Definition events.qh:12
int cmd_argc
Definition events.qh:13
float MSG_ONE
Definition menudefs.qc:56
float stof(string val,...)
float bound(float min, float value, float max)
float cvar(string name)
float random(void)
const string cvar_string(string name)
float vlen(vector v)
void WriteCoord(float data, float dest, float desto)
float min(float f,...)
float rint(float f)
vector normalize(vector v)
string ftos(float f)
float floor(float f)
string strzone(string s)
string argv(float n)
float max(float f,...)
void modeleffect_spawn(string m, float s, float f, vector o, vector v, vector ang, vector angv, float s0, float s2, float a, float t1, float t2)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_NONE
Definition movetypes.qh:133
float move_movetype
Definition movetypes.qh:80
const int MOVETYPE_NOCLIP
Definition movetypes.qh:141
const int MOVETYPE_FLY
Definition movetypes.qh:138
const int MOVETYPE_TOSS
Definition movetypes.qh:139
#define IS_ONGROUND(s)
Definition movetypes.qh:16
@ STATUSEFFECT_REMOVE_CLEAR
Effect is being forcibly removed without calling any additional mechanics.
Definition all.qh:30
spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2 f1points f2
Definition all.inc:370
spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2 f1points s1 s2
Definition all.inc:477
f1
Definition all.inc:569
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1500
#define APP_NUM(num, prefix)
Definition all.qh:89
#define APP_TEAM_NUM(num, prefix)
Definition all.qh:88
#define NEW(cname,...)
Definition oo.qh:120
#define METHOD(cname, name, prototype)
Definition oo.qh:274
void copyentity_qc(entity src, entity dst)
Definition oo.qh:88
#define NULL
Definition post.qh:14
#define makevectors
Definition post.qh:21
entity msg_entity
Definition progsdefs.qc:63
#define crandom()
Returns a random number between -1.0 and 1.0.
Definition random.qh:32
float TeamScore_AddToTeam(int t, float scorefield, float score)
Adds a score to the given team.
Definition scores.qc:108
int NumTeams(int teams)
#define AVAILABLE_TEAMS
Number of teams that exist currently.
#define setthink(e, f)
vector
Definition self.qh:96
#define setcefc(e, f)
vector org
Definition self.qh:96
entity entity toucher
Definition self.qh:76
#define settouch(e, f)
Definition self.qh:77
void GetPressedKeys(entity this)
Definition client.qc:1768
void PlayerUseKey(entity this)
Definition client.qc:2636
#define IS_INDEPENDENT_PLAYER(e)
Definition client.qh:312
float jointime
Definition client.qh:66
void ImpulseCommands(entity this)
Definition impulse.qc:371
IntrusiveList g_items
Definition items.qh:119
#define ITEM_TOUCH_NEEDKILL()
Definition items.qh:122
#define ITEM_DAMAGE_NEEDKILL(dt)
Definition items.qh:123
bool noalign
Definition items.qh:37
void race_SendAll(entity player, bool only_rankings)
Definition race.qc:332
void write_recordmarker(entity pl, float tstart, float dt)
Definition race.qc:57
void race_checkAndWriteName(entity player)
Definition race.qc:133
void race_setTime(string map, float t, string myuid, string mynetname, entity e, bool showmessage)
Definition race.qc:373
const int CH_TRIGGER
Definition sound.qh:12
const float VOL_BASE
Definition sound.qh:36
const float ATTEN_NONE
Definition sound.qh:27
#define _sound(e, c, s, v, a)
Definition sound.qh:43
const float ATTEN_NORM
Definition sound.qh:30
#define SND(id)
Definition all.qh:35
#define spawnfunc(id)
Definition spawnfunc.qh:107
ClientState CS(Client this)
Definition state.qh:47
void StatusEffects_removeall(entity actor, int removal_type)
bool StatusEffects_active(StatusEffect this, entity actor)
ERASEABLE string ftos_decimals(float number, int decimals)
Converts a number to a string with the indicated number of decimals.
Definition string.qh:476
const int DAMAGE_YES
Definition subs.qh:80
const int DAMAGE_NO
Definition subs.qh:79
bool autocvar_g_ctf_flag_dropped_waypoint
Definition sv_ctf.qc:51
void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
Definition sv_ctf.qc:202
entity havocbot_ctf_find_enemy_flag(entity bot)
Definition sv_ctf.qc:1529
bool autocvar_g_ctf_oneflag_reverse
Definition sv_ctf.qc:35
float autocvar_g_ctf_throw_velocity_forward
Definition sv_ctf.qc:31
float autocvar_g_ctf_flagcarrier_forcefactor
Definition sv_ctf.qc:71
bool autocvar_g_ctf_flag_glowtrails
Definition sv_ctf.qc:53
int autocvar_g_ctf_shield_min_negscore
Definition sv_ctf.qc:87
void ctf_RespawnFlag(entity flag)
Definition sv_ctf.qc:1237
int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
Definition sv_ctf.qc:1552
void ctf_CaptureShield_Update(entity player, bool wanted_status)
Definition sv_ctf.qc:311
void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
Definition sv_ctf.qc:605
void ctf_EventLog(string mode, int flagteam, entity actor)
Definition sv_ctf.qc:106
void ctf_CaptureShield_Spawn(entity flag)
Definition sv_ctf.qc:341
float autocvar_g_ctf_pass_arc_max
Definition sv_ctf.qc:39
float autocvar_g_ctf_pass_timelimit
Definition sv_ctf.qc:46
void havocbot_role_ctf_middle(entity this)
Definition sv_ctf.qc:2045
int autocvar_g_ctf_score_pickup_dropped_late
Definition sv_ctf.qc:83
bool autocvar_g_ctf_flag_return_when_unreachable
Definition sv_ctf.qc:59
void ctf_FlagSetup(int teamnum, entity flag)
Definition sv_ctf.qc:1351
void ctf_CheckFlagReturn(entity flag, int returntype)
Definition sv_ctf.qc:852
void ctf_Handle_Drop(entity flag, entity player, int droptype)
Definition sv_ctf.qc:365
float autocvar_g_ctf_stalemate_time
Definition sv_ctf.qc:90
float autocvar_g_ctf_pass_radius
Definition sv_ctf.qc:42
void havocbot_ctf_calculate_middlepoint()
Definition sv_ctf.qc:1480
float autocvar_g_ctf_pass_turnrate
Definition sv_ctf.qc:45
bool autocvar_g_ctf_ignore_frags
Definition sv_ctf.qc:74
int autocvar_g_ctf_throw_punish_count
Definition sv_ctf.qc:27
bool autocvar_g_ctf_fullbrightflags
Definition sv_ctf.qc:73
int autocvar_g_ctf_score_pickup_dropped_early
Definition sv_ctf.qc:82
float autocvar_g_ctf_dropped_capture_delay
Definition sv_ctf.qc:92
bool autocvar_g_ctf_score_ignore_fields
Definition sv_ctf.qc:75
#define X(s, b)
bool autocvar_g_ctf_portalteleport
Definition sv_ctf.qc:36
bool autocvar_g_ctf_allow_vehicle_carry
Definition sv_ctf.qc:21
int autocvar_g_ctf_score_return
Definition sv_ctf.qc:84
bool autocvar_g_ctf_flag_return_carrying
Definition sv_ctf.qc:56
float autocvar_g_ctf_flag_return_damage_delay
Definition sv_ctf.qc:61
float autocvar_g_ctf_throw_punish_time
Definition sv_ctf.qc:29
bool havocbot_ctf_is_basewaypoint(entity item)
Definition sv_ctf.qc:1776
float autocvar_g_ctf_flag_waypoint_maxdistance
Definition sv_ctf.qc:65
void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
Definition sv_ctf.qc:1667
int autocvar_g_ctf_score_capture
Definition sv_ctf.qc:76
float autocvar_g_ctf_throw_velocity_up
Definition sv_ctf.qc:32
float autocvar_g_ctf_flagcarrier_damagefactor
Definition sv_ctf.qc:70
float autocvar_g_ctf_shield_max_ratio
Definition sv_ctf.qc:86
void ctf_FlagcarrierWaypoints(entity player)
Definition sv_ctf.qc:160
void nades_GiveBonus(entity player, float score)
Definition sv_nades.qc:423
void havocbot_role_ctf_carrier(entity this)
Definition sv_ctf.qc:1788
void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition sv_ctf.qc:979
float autocvar_g_ctf_pass_directional_min
Definition sv_ctf.qc:41
void havocbot_role_ctf_escort(entity this)
Definition sv_ctf.qc:1841
float autocvar_g_ctf_flag_damageforcescale
Definition sv_ctf.qc:50
float autocvar_g_ctf_pass_wait
Definition sv_ctf.qc:43
bool ctf_Stalemate_Customize(entity this, entity client)
Definition sv_ctf.qc:887
float autocvar_g_ctf_flagcarrier_selfdamagefactor
Definition sv_ctf.qc:68
void ctf_Handle_Return(entity flag, entity player)
Definition sv_ctf.qc:683
int autocvar_g_ctf_stalemate_endcondition
Definition sv_ctf.qc:89
entity havocbot_ctf_find_flag(entity bot)
Definition sv_ctf.qc:1520
void havocbot_role_ctf_offense(entity this)
Definition sv_ctf.qc:1909
float autocvar_g_ctf_shield_force
Definition sv_ctf.qc:85
float autocvar_g_ctf_drop_velocity_up
Definition sv_ctf.qc:33
bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
Definition sv_ctf.qc:236
float autocvar_g_ctf_flag_return_damage
Definition sv_ctf.qc:60
void ctf_FlagThink(entity this)
Definition sv_ctf.qc:1001
bool ctf_CaptureShield_Customize(entity this, entity client)
Definition sv_ctf.qc:321
int autocvar_g_ctf_score_penalty_drop
Definition sv_ctf.qc:79
bool ctf_Pass_cond(entity e)
Definition sv_ctf.qc:2428
void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
Definition sv_ctf.qc:1678
float autocvar_g_ctf_flagcarrier_auto_helpme_damage
Definition sv_ctf.qc:66
float autocvar_g_ctf_flag_return_time
Definition sv_ctf.qc:58
bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
Definition sv_ctf.qc:144
int autocvar_g_ctf_score_penalty_returned
Definition sv_ctf.qc:80
void ctf_FakeTimeLimit(entity e, float t)
Definition sv_ctf.qc:95
float autocvar_g_ctf_flagcarrier_selfforcefactor
Definition sv_ctf.qc:69
float autocvar_g_ctf_throw_strengthmultiplier
Definition sv_ctf.qc:30
bool autocvar_g_ctf_flag_dropped_floatinwater
Definition sv_ctf.qc:52
float autocvar_g_ctf_throw_angle_min
Definition sv_ctf.qc:26
void ctf_CaptureShield_Touch(entity this, entity toucher)
Definition sv_ctf.qc:329
void ctf_RemovePlayer(entity player)
Definition sv_ctf.qc:2372
void havocbot_role_ctf_defense(entity this)
Definition sv_ctf.qc:2102
bool autocvar_g_ctf_flag_waypoint
Definition sv_ctf.qc:64
float autocvar_g_ctf_flag_return_dropped
Definition sv_ctf.qc:62
float autocvar_g_ctf_drop_velocity_side
Definition sv_ctf.qc:34
void ctf_Initialize()
Definition sv_ctf.qc:2903
int autocvar_g_ctf_score_kill
Definition sv_ctf.qc:78
void havocbot_role_ctf_retriever(entity this)
Definition sv_ctf.qc:1987
int autocvar_g_ctf_score_pickup_base
Definition sv_ctf.qc:81
float autocvar_g_ctf_flag_health
Definition sv_ctf.qc:54
float autocvar_g_ctf_flag_return_carried_radius
Definition sv_ctf.qc:57
float autocvar_g_ctf_flag_collect_delay
Definition sv_ctf.qc:49
float autocvar_g_ctf_pass_velocity
Definition sv_ctf.qc:47
bool autocvar_g_ctf_pass_request
Definition sv_ctf.qc:44
void ctf_CaptureRecord(entity flag, entity player)
Definition sv_ctf.qc:113
bool ctf_CaptureShield_CheckStatus(entity p)
Definition sv_ctf.qc:268
bool autocvar_g_ctf_allow_monster_touch
Definition sv_ctf.qc:23
bool autocvar_g_ctf_stalemate
Definition sv_ctf.qc:88
bool autocvar_g_ctf_throw
Definition sv_ctf.qc:24
bool autocvar_g_ctf_dynamiclights
Definition sv_ctf.qc:48
void havocbot_role_ctf_setrole(entity bot, int role)
Definition sv_ctf.qc:2171
void ctf_Handle_Throw(entity player, entity receiver, int droptype)
Definition sv_ctf.qc:480
float autocvar_g_ctf_pass_arc
Definition sv_ctf.qc:38
float frag_damage
Definition sv_ctf.qc:2316
void ctf_DelayedFlagSetup(entity this)
Definition sv_ctf.qc:1319
bool ctf_FlagBase_Customize(entity this, entity client)
Definition sv_ctf.qc:1307
void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
Definition sv_ctf.qc:1586
void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel)
void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
Definition sv_ctf.qc:1649
vector frag_force
Definition sv_ctf.qc:2317
void ctf_CheckStalemate()
Definition sv_ctf.qc:903
entity frag_target
Definition sv_ctf.qc:2315
bool autocvar_g_ctf_pass
Definition sv_ctf.qc:37
int autocvar_g_ctf_score_capture_assist
Definition sv_ctf.qc:77
bool autocvar_g_ctf_flag_stay
Definition sv_ctf.qc:63
bool autocvar_g_ctf_allow_vehicle_touch
Definition sv_ctf.qc:22
void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
Definition sv_ctf.qc:726
void ctf_ScoreRules(int teams)
Definition sv_ctf.qc:2854
float autocvar_g_ctf_throw_angle_max
Definition sv_ctf.qc:25
bool ctf_Return_Customize(entity this, entity client)
Definition sv_ctf.qc:154
float autocvar_g_ctf_throw_punish_delay
Definition sv_ctf.qc:28
void ctf_Handle_Retrieve(entity flag, entity player)
Definition sv_ctf.qc:429
void ctf_DelayedInit(entity this)
Definition sv_ctf.qc:2867
float autocvar_g_ctf_dropped_capture_radius
Definition sv_ctf.qc:93
float autocvar_g_ctf_pass_directional_max
Definition sv_ctf.qc:40
void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
Definition sv_ctf.qc:1605
bool superspec_Spectate(entity this, entity targ)
bool autocvar_g_ctf_flag_return
Definition sv_ctf.qc:55
bool autocvar_g_ctf_reverse
Definition sv_ctf.qc:91
void ctf_Reset(entity this)
Definition sv_ctf.qc:1298
void havocbot_ctf_reset_role(entity this)
Definition sv_ctf.qc:1697
float autocvar_g_ctf_flagcarrier_auto_helpme_time
Definition sv_ctf.qc:67
const int PICKUP_BASE
Definition sv_ctf.qh:125
entity wps_flagreturn
Definition sv_ctf.qh:108
const int HAVOCBOT_CTF_ROLE_RETRIEVER
Definition sv_ctf.qh:177
float ctf_captureshield_min_negscore
Definition sv_ctf.qh:164
float wpforenemy_nextthink
Definition sv_ctf.qh:112
const int DROP_NORMAL
Definition sv_ctf.qh:120
IntrusiveList g_flags
Definition sv_ctf.qh:12
float ctf_landtime
Definition sv_ctf.qh:147
const int RETURN_DAMAGE
Definition sv_ctf.qh:133
entity wps_enemyflagcarrier
Definition sv_ctf.qh:109
const int FLAG_FLOAT_OFFSET_Z
Definition sv_ctf.qh:61
entity wps_flagbase
Definition sv_ctf.qh:105
const int CAPTURE_DROPPED
Definition sv_ctf.qh:129
const int DROP_RESET
Definition sv_ctf.qh:123
bool ctf_stalemate
Definition sv_ctf.qh:143
#define ctf_spawnorigin
Definition sv_ctf.qh:142
const float FLAG_TOUCHRATE
Definition sv_ctf.qh:54
const vector FLAG_WAYPOINT_OFFSET
Definition sv_ctf.qh:60
const vector VEHICLE_FLAG_OFFSET
Definition sv_ctf.qh:64
const int HAVOCBOT_CTF_ROLE_DEFENSE
Definition sv_ctf.qh:173
#define snd_flag_returned
Definition sv_ctf.qh:77
const int HAVOCBOT_CTF_ROLE_MIDDLE
Definition sv_ctf.qh:174
const float FLAG_SCALE
Definition sv_ctf.qh:51
void ctf_FlagTouch(entity this, entity toucher)
Definition sv_ctf.qh:67
#define CTF_SAMETEAM(a, b)
Definition sv_ctf.qh:185
int ctf_status
Definition sv_ctf.qh:148
bool havocbot_cantfindflag
Definition sv_ctf.qh:180
string snd_flag_touch
Definition sv_ctf.qh:81
const int DROP_PASS
Definition sv_ctf.qh:122
const float WPFE_THINKRATE
Definition sv_ctf.qh:55
entity wps_flagcarrier
Definition sv_ctf.qh:106
#define snd_flag_capture
Definition sv_ctf.qh:78
entity pass_target
Definition sv_ctf.qh:157
#define WPCOLOR_FLAGCARRIER(t)
Definition sv_ctf.qh:71
#define snd_flag_respawn
Definition sv_ctf.qh:79
#define FLAG_SPAWN_OFFSET
Definition sv_ctf.qh:59
float ctf_captureshield_force
Definition sv_ctf.qh:166
const int FLAG_BASE
Definition sv_ctf.qh:115
float ctf_pickuptime
Definition sv_ctf.qh:145
entity wps_flagdropped
Definition sv_ctf.qh:107
const int HAVOCBOT_CTF_ROLE_ESCORT
Definition sv_ctf.qh:178
const int FLAG_DROPPED
Definition sv_ctf.qh:116
const int FLAG_PASSING
Definition sv_ctf.qh:118
entity flagcarried
Definition sv_ctf.qh:93
entity enemy
Definition sv_ctf.qh:152
Flag CTF_FLAG
Definition sv_ctf.qh:47
const int PICKUP_DROPPED
Definition sv_ctf.qh:126
entity wps_helpme
Definition sv_ctf.qh:104
const int ST_CTF_CAPS
Definition sv_ctf.qh:33
string snd_flag_pass
Definition sv_ctf.qh:82
bool ctf_flagdamaged_byworld
Definition sv_ctf.qh:151
const float FLAG_THINKRATE
Definition sv_ctf.qh:53
const int HAVOCBOT_CTF_ROLE_OFFENSE
Definition sv_ctf.qh:175
bool ctf_oneflag
Definition sv_ctf.qh:169
const int RETURN_SPEEDRUN
Definition sv_ctf.qh:134
bool wpforenemy_announced
Definition sv_ctf.qh:111
const int RETURN_DROPPED
Definition sv_ctf.qh:132
const int CAPTURE_NORMAL
Definition sv_ctf.qh:128
float ctf_captimerecord
Definition sv_ctf.qh:144
const int RETURN_NEEDKILL
Definition sv_ctf.qh:135
const float VEHICLE_FLAG_SCALE
Definition sv_ctf.qh:65
#define WPCOLOR_ENEMYFC(t)
Definition sv_ctf.qh:70
const vector FLAG_CARRY_OFFSET
Definition sv_ctf.qh:58
#define CTF_DIFFTEAM(a, b)
Definition sv_ctf.qh:186
const int DROP_THROW
Definition sv_ctf.qh:121
const int RETURN_TIMEOUT
Definition sv_ctf.qh:131
const int HAVOCBOT_CTF_ROLE_CARRIER
Definition sv_ctf.qh:176
#define snd_flag_taken
Definition sv_ctf.qh:76
#define WPCOLOR_DROPPEDFLAG(t)
Definition sv_ctf.qh:73
const vector FLAG_DROP_OFFSET
Definition sv_ctf.qh:57
float ctf_droptime
Definition sv_ctf.qh:146
string snd_flag_dropped
Definition sv_ctf.qh:80
const int FLAG_CARRY
Definition sv_ctf.qh:117
const int FLAG_PASS_ARC_OFFSET_Z
Definition sv_ctf.qh:62
float ctf_captureshield_max_ratio
Definition sv_ctf.qh:165
int autocvar_g_nades_bonus_score_high
Definition sv_nades.qh:38
int autocvar_g_nades_bonus_score_minor
Definition sv_nades.qh:36
int autocvar_g_nades_bonus_score_medium
Definition sv_nades.qh:39
void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius)
Definition roles.qc:106
void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org, float sradius)
Definition roles.qc:176
#define GameRules_scoring_vip(player, value)
Definition sv_rules.qh:78
#define GameRules_scoring_add(client, fld, value)
Definition sv_rules.qh:85
#define GameRules_scoring_add_team(client, fld, value)
Definition sv_rules.qh:89
#define GameRules_scoring(teams, spprio, stprio, fields)
Definition sv_rules.qh:58
int Team_MapEnts_FindOrSpawn(string ent_classname, int defaultmask)
Finds any team map entities and returns their bitmask, else spawns them.
Definition teamplay.qc:56
int teamplay_bitmask
The set of currently available teams (AVAILABLE_TEAMS is the number of them).
Definition teamplay.qh:18
#define SAME_TEAM(a, b)
Definition teams.qh:241
const int NUM_TEAM_2
Definition teams.qh:14
const int NUM_TEAM_4
Definition teams.qh:16
#define Static_Team_ColorName_Lower(teamid)
Definition teams.qh:226
vector Team_ColorRGB(int teamid)
Definition teams.qh:76
const int NUM_TEAM_3
Definition teams.qh:15
#define Team_ColorName_Upper(teamid)
Definition teams.qh:223
int Team_IndexToTeam(int index)
Converts team index into team value.
Definition teams.qh:169
bool teamplay
Definition teams.qh:59
#define DIFF_TEAM(a, b)
Definition teams.qh:242
string Team_ColorCode(int teamid)
Definition teams.qh:63
const int NUM_TEAM_1
Definition teams.qh:13
const int TELEPORT_NORMAL
vector W_CalculateProjectileVelocity(entity actor, vector pvelocity, vector mvelocity, float forceAbsolute)
Definition tracing.qc:177
#define IS_INVISIBLE(v)
Definition utils.qh:29
#define IS_SPEC(v)
Definition utils.qh:10
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define IS_MONSTER(v)
Definition utils.qh:23
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:52
#define IS_VEHICLE(v)
Definition utils.qh:24
#define CENTER_OR_VIEWOFS(ent)
Definition utils.qh:31
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15
#define vlen2(v)
Definition vector.qh:4
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt().
Definition vector.qh:8
ERASEABLE vector vrint(vector v)
Definition vector.qh:224
const vector eZ
Definition vector.qh:45
#define vec2(...)
Definition vector.qh:95
#define vec3(_x, _y, _z)
Definition vector.qh:100
void WaypointSprite_HelpMePing(entity e)
void WaypointSprite_Kill(entity wp)
void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
void WaypointSprite_UpdateMaxHealth(entity e, float f)
void WaypointSprite_Ping(entity e)
entity WaypointSprite_Spawn(entity spr, float _lifetime, float maxdistance, entity ref, vector ofs, entity showto, float t, entity own,.entity ownfield, float hideable, entity icon)
entity WaypointSprite_getviewentity(entity e)
void WaypointSprite_UpdateHealth(entity e, float f)
entity WaypointSprite_SpawnFixed(entity spr, vector ofs, entity own,.entity ownfield, entity icon)
float waypointsprite_limitedrange
float waypointsprite_deployed_lifetime
void DropToFloor_QC_DelayedInit(entity this)
Definition world.qc:2402
void InitializeEntity(entity e, void(entity this) func, int order)
Definition world.qc:2204
string record_type
Definition world.qh:55
IntrusiveList g_moveables
Definition world.qh:157
float start_armorvalue
Definition world.qh:97
float start_health
Definition world.qh:96
float ServerProgsDB
Definition world.qh:128