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((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
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;
210 if(current_height) // make sure we can actually do this arcing path
211 {
212 targpos = (to + ('0 0 1' * 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);
218 targpos = (trace_endpos + eZ * FLAG_PASS_ARC_OFFSET_Z);
219 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
220 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
221 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
222 }
223 }
224 else { targpos = to; }
225
226 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
227
228 vector desired_direction = normalize(targpos - from);
229 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
230 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
231}
232
233bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
234{
236 {
237 // directional tracing only
238 float spreadlimit;
239 makevectors(passer_angle);
240
241 // find the closest point on the enemy to the center of the attack
242 float h; // hypotenuse, which is the distance between attacker to head
243 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
244
245 h = vlen(head_center - passer_center);
246 a = h * (normalize(head_center - passer_center) * v_forward);
247
248 vector nearest_on_line = (passer_center + a * v_forward);
249 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
250
251 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
252 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
253
254 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
255 { return true; }
256 else
257 { return false; }
258 }
259 else { return true; }
260}
261
262
263// =======================
264// CaptureShield Functions
265// =======================
266
268{
269 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
270 int players_worseeq, players_total;
271
273 return false;
274
275 s = GameRules_scoring_add(p, CTF_CAPS, 0);
276 s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
277 s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
278 s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
279
280 sr = ((s - s2) + (s3 + s4));
281
283 return false;
284
285 players_total = players_worseeq = 0;
287 if(DIFF_TEAM(it, p))
288 continue;
289 se = GameRules_scoring_add(it, CTF_CAPS, 0);
290 se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
291 se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
292 se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0);
293
294 ser = ((se - se2) + (se3 + se4));
295
296 if(ser <= sr)
297 ++players_worseeq;
298 ++players_total;
299 });
300
301 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
302 // use this rule here
303
304 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
305 return false;
306
307 return true;
308}
309
310void ctf_CaptureShield_Update(entity player, bool wanted_status)
311{
312 bool updated_status = ctf_CaptureShield_CheckStatus(player);
313 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
314 {
315 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
316 player.ctf_captureshielded = updated_status;
317 }
318}
319
321{
322 if(!client.ctf_captureshielded) { return false; }
323 if(CTF_SAMETEAM(this, client)) { return false; }
324
325 return true;
326}
327
329{
330 if(!toucher.ctf_captureshielded) { return; }
331 if(CTF_SAMETEAM(this, toucher)) { return; }
332
333 vector mymid = (this.absmin + this.absmax) * 0.5;
334 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
335
336 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
337 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
338}
339
341{
342 entity shield = new(ctf_captureshield);
343
344 shield.enemy = flag;
345 shield.team = flag.team;
348 shield.effects = EF_ADDITIVE;
350 shield.solid = SOLID_TRIGGER;
351 shield.avelocity = '7 0 11';
352 shield.scale = 0.5;
353
354 setorigin(shield, flag.origin);
355 setmodel(shield, MDL_CTF_SHIELD);
356 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
357}
358
359
360// ====================
361// Drop/Pass/Throw Code
362// ====================
363
364void ctf_Handle_Drop(entity flag, entity player, int droptype)
365{
366 // declarations
367 player = (player ? player : flag.pass_sender);
368
369 // main
371 flag.takedamage = DAMAGE_YES;
372 flag.angles = '0 0 0';
373
375 SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
376
377 flag.ctf_droptime = time;
378 flag.ctf_landtime = 0;
379 flag.ctf_dropper = player;
380 flag.ctf_status = FLAG_DROPPED;
381
382 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
383 if(flag.damagedbycontents)
385
386 // messages and sounds
387 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
388 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
389 ctf_EventLog("dropped", player.team, player);
390
391 // scoring
392 GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
393 GameRules_scoring_add(player, CTF_DROPS, 1);
394
395 // waypoints
397 entity base_wp;
398 switch (flag.team)
399 {
400 case NUM_TEAM_1: base_wp = WP_FlagDroppedRed; break;
401 case NUM_TEAM_2: base_wp = WP_FlagDroppedBlue; break;
402 case NUM_TEAM_3: base_wp = WP_FlagDroppedYellow; break;
403 case NUM_TEAM_4: base_wp = WP_FlagDroppedPink; break;
404 default: base_wp = WP_FlagDroppedNeutral; break;
405 }
406 int wp_team = ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team);
407 entity wp = WaypointSprite_Spawn(base_wp, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL,
408 wp_team, flag, wps_flagdropped, true, RADARICON_FLAG);
409 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
410 }
411
413 {
414 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_health);
415 WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResource(flag, RES_HEALTH));
416 }
417
418 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
419
420 if(droptype == DROP_PASS)
421 {
422 flag.pass_distance = 0;
423 flag.pass_sender = NULL;
424 flag.pass_target = NULL;
425 }
426}
427
429{
430 entity sender = flag.pass_sender;
431
432 // transfer flag to player
433 flag.owner = player;
434 flag.owner.flagcarried = flag;
435 GameRules_scoring_vip(player, true);
436
437 // reset flag
438 flag.solid = SOLID_NOT; // before setorigin to prevent area grid linking
439 if(player.vehicle)
440 {
441 setattachment(flag, player.vehicle, "");
442 setorigin(flag, VEHICLE_FLAG_OFFSET);
443 flag.scale = VEHICLE_FLAG_SCALE;
444 }
445 else
446 {
447 setattachment(flag, player, "");
448 setorigin(flag, FLAG_CARRY_OFFSET);
449 }
451 flag.takedamage = DAMAGE_NO;
452 flag.angles = '0 0 0';
453 flag.ctf_status = FLAG_CARRY;
454
455 // messages and sounds
456 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
457 ctf_EventLog("receive", flag.team, player);
458
460 if(it == sender)
461 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
462 else if(it == player)
463 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
464 else if(SAME_TEAM(it, sender))
465 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
466 });
467
468 // create new waypoint
470
471 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
472 player.throw_antispam = sender.throw_antispam;
473
474 flag.pass_distance = 0;
475 flag.pass_sender = NULL;
476 flag.pass_target = NULL;
477}
478
479void ctf_Handle_Throw(entity player, entity receiver, int droptype)
480{
481 entity flag = player.flagcarried;
482 vector targ_origin, flag_velocity;
483
484 if(!flag) { return; }
485 if((droptype == DROP_PASS) && !receiver) { return; }
486
487 if(flag.speedrunning || flag.classname == "phantomflag")
488 {
489 // ensure old waypoints are removed before resetting the flag
490 WaypointSprite_Kill(player.wps_flagcarrier);
491
492 if(player.wps_enemyflagcarrier)
493 WaypointSprite_Kill(player.wps_enemyflagcarrier);
494
495 if(player.wps_flagreturn)
496 WaypointSprite_Kill(player.wps_flagreturn);
497 ctf_RespawnFlag(flag);
498 return;
499 }
500
501 // reset the flag
502 setattachment(flag, NULL, "");
503 tracebox(player.origin + vec3(0, 0, player.mins.z - flag.m_mins.z), // same bbox floor height as player
504 flag.m_mins, flag.m_maxs, player.origin + FLAG_DROP_OFFSET, MOVE_NOMONSTERS, flag);
505 flag.solid = SOLID_TRIGGER; // before setorigin to ensure area grid linking
506 setorigin(flag, trace_endpos);
507 if (trace_startsolid && !nudgeoutofsolid_OrFallback(flag)) // TODO: trace_allsolid would perform better but isn't 100% reliable yet
508 {
509 // the flag's bbox doesn't fit but we can assume the player's current bbox does
510 tracebox(player.origin, player.mins, player.maxs, player.origin + FLAG_DROP_OFFSET, MOVE_NOMONSTERS, flag);
511 flag.origin = trace_endpos;
512 setsize(flag, player.mins, player.maxs); // this allows physics to move the flag somewhere its think func can resize it
513 }
514 flag.owner.flagcarried = NULL;
515 GameRules_scoring_vip(flag.owner, false);
516 flag.owner = NULL;
517 flag.ctf_dropper = player;
518 flag.ctf_droptime = time;
519 flag.ctf_landtime = 0;
520
521 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
522
523 switch(droptype)
524 {
525 case DROP_PASS:
526 {
527 // warpzone support:
528 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
529 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
530 WarpZone_RefSys_Copy(flag, receiver);
531 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 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((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis
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 if(MUTATOR_RETURNVALUE || game_stopped) return;
2431
2432 entity player = M_ARGV(0, entity);
2433
2434 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2435 {
2436 // pass the flag to a teammate
2437 if(autocvar_g_ctf_pass && player.flagcarried.classname != "phantomflag")
2438 {
2439 entity head, closest_target = NULL;
2440 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2441
2442 while(head) // find the closest acceptable target to pass to
2443 {
2444 if(IS_PLAYER(head) && !IS_DEAD(head) && !IS_INDEPENDENT_PLAYER(head))
2445 if(head != player && SAME_TEAM(head, player))
2446 if(!head.speedrunning && !head.vehicle)
2447 {
2448 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc)
2449 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2450 vector passer_center = CENTER_OR_VIEWOFS(player);
2451
2452 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2453 {
2454 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried && head.flagcarried.classname != "phantomflag")
2455 {
2456 if(IS_BOT_CLIENT(head))
2457 {
2458 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2459 ctf_Handle_Throw(head, player, DROP_PASS);
2460 }
2461 else
2462 {
2463 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2464 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2465 }
2466 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2467 return true;
2468 }
2469 else if(player.flagcarried && !head.flagcarried)
2470 {
2471 if(closest_target)
2472 {
2473 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2474 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2475 { closest_target = head; }
2476 }
2477 else { closest_target = head; }
2478 }
2479 }
2480 }
2481 head = head.chain;
2482 }
2483
2484 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2485 }
2486
2487 // throw the flag in front of you
2488 if(autocvar_g_ctf_throw && player.flagcarried)
2489 {
2490 if(player.throw_count == -1)
2491 {
2492 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2493 {
2494 player.throw_prevtime = time;
2495 player.throw_count = 1;
2497 return true;
2498 }
2499 else
2500 {
2501 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2502 return false;
2503 }
2504 }
2505 else
2506 {
2507 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2508 else { ++player.throw_count; }
2509 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2510
2511 player.throw_prevtime = time;
2513 return true;
2514 }
2515 }
2516 }
2517}
2518
2519MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2520{
2521 entity player = M_ARGV(0, entity);
2522
2523 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2524 {
2525 player.wps_helpme_time = time;
2526 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2527 }
2528 else // create a normal help me waypointsprite
2529 {
2530 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2531 WaypointSprite_Ping(player.wps_helpme);
2532 }
2533
2534 return true;
2535}
2536
2537MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2538{
2539 entity player = M_ARGV(0, entity);
2540 entity veh = M_ARGV(1, entity);
2541
2542 if(player.flagcarried)
2543 {
2545 {
2547 }
2548 else
2549 {
2550 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2551 setattachment(player.flagcarried, veh, "");
2552 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2553 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2554 //player.flagcarried.angles = '0 0 0';
2555 }
2556 return true;
2557 }
2558}
2559
2560MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2561{
2562 entity player = M_ARGV(0, entity);
2563
2564 if(player.flagcarried)
2565 {
2566 setattachment(player.flagcarried, player, "");
2567 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2568 player.flagcarried.scale = FLAG_SCALE;
2569 player.flagcarried.angles = '0 0 0';
2570 player.flagcarried.nodrawtoclient = NULL;
2571 return true;
2572 }
2573}
2574
2575MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2576{
2577 entity player = M_ARGV(0, entity);
2578
2579 if(player.flagcarried)
2580 {
2581 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2582 ctf_RespawnFlag(player.flagcarried);
2583 return true;
2584 }
2585}
2586
2588{
2589 IL_EACH(g_flags, true,
2590 {
2591 switch(it.ctf_status)
2592 {
2593 case FLAG_DROPPED:
2594 case FLAG_PASSING:
2595 {
2596 // lock the flag, game is over
2598 it.takedamage = DAMAGE_NO;
2599 it.solid = SOLID_NOT;
2600 it.nextthink = false; // stop thinking
2601
2602 //dprint("stopping the ", flag.netname, " from moving.\n");
2603 break;
2604 }
2605
2606 default:
2607 case FLAG_BASE:
2608 case FLAG_CARRY:
2609 {
2610 // do nothing for these flags
2611 break;
2612 }
2613 }
2614 });
2615}
2616
2617MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2618{
2619 entity bot = M_ARGV(0, entity);
2620
2622 return true;
2623}
2624
2625MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2626{
2627 int record_page = M_ARGV(0, int);
2628 string ret_string = M_ARGV(1, string);
2629
2630 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2631 {
2632 if (MapInfo_Get_ByID(i))
2633 {
2634 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2635
2636 if(!r)
2637 continue;
2638
2639 // TODO: uid2name
2640 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2641 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2642 }
2643 }
2644
2645 M_ARGV(1, string) = ret_string;
2646}
2647
2648bool superspec_Spectate(entity this, entity targ); // TODO
2649void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2651{
2652 entity player = M_ARGV(0, entity);
2653 string cmd_name = M_ARGV(1, string);
2654 int cmd_argc = M_ARGV(2, int);
2655
2656 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2657
2658 if(cmd_name == "followfc")
2659 {
2660 if(!g_ctf)
2661 return true;
2662
2663 int _team = 0;
2664 bool found = false;
2665
2666 if(cmd_argc == 2)
2667 {
2668 switch(argv(1))
2669 {
2670 case "red": if(teamplay_bitmask & BIT(0)) _team = NUM_TEAM_1; break;
2671 case "blue": if(teamplay_bitmask & BIT(1)) _team = NUM_TEAM_2; break;
2672 case "yellow": if(teamplay_bitmask & BIT(2)) _team = NUM_TEAM_3; break;
2673 case "pink": if(teamplay_bitmask & BIT(3)) _team = NUM_TEAM_4; break;
2674 }
2675 }
2676
2678 if(it.flagcarried && (it.team == _team || _team == 0))
2679 {
2680 found = true;
2681 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2682 continue; // already spectating this fc, try another
2683 return superspec_Spectate(player, it);
2684 }
2685 });
2686
2687 if(!found)
2688 superspec_msg("", "", player, "No active flag carrier\n", 1);
2689 return true;
2690 }
2691}
2692
2693MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2694{
2696
2697 if(frag_target.flagcarried)
2699}
2700
2701MUTATOR_HOOKFUNCTION(ctf, LogDeath_AppendItemCodes)
2702{
2703 entity player = M_ARGV(0, entity);
2704 if(player.flagcarried)
2705 M_ARGV(1, string) = strcat(M_ARGV(1, string), "F"); // item codes
2706}
2707
2708MUTATOR_HOOKFUNCTION(ctf, PreferPlayerScore_Clear)
2709{
2710 return true;
2711}
2712
2713
2714// ==========
2715// Spawnfuncs
2716// ==========
2717
2718/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2719CTF flag for team one (Red).
2720Keys:
2721"angle" Angle the flag will point (minus 90 degrees)...
2722"model" model to use, note this needs red and blue as skins 0 and 1...
2723"noise" sound played when flag is picked up...
2724"noise1" sound played when flag is returned by a teammate...
2725"noise2" sound played when flag is captured...
2726"noise3" sound played when flag is lost in the field and respawns itself...
2727"noise4" sound played when flag is dropped by a player...
2728"noise5" sound played when flag touches the ground... */
2729spawnfunc(item_flag_team1)
2730{
2731 if(!g_ctf) { delete(this); return; }
2732
2734}
2735
2736/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2737CTF flag for team two (Blue).
2738Keys:
2739"angle" Angle the flag will point (minus 90 degrees)...
2740"model" model to use, note this needs red and blue as skins 0 and 1...
2741"noise" sound played when flag is picked up...
2742"noise1" sound played when flag is returned by a teammate...
2743"noise2" sound played when flag is captured...
2744"noise3" sound played when flag is lost in the field and respawns itself...
2745"noise4" sound played when flag is dropped by a player...
2746"noise5" sound played when flag touches the ground... */
2747spawnfunc(item_flag_team2)
2748{
2749 if(!g_ctf) { delete(this); return; }
2750
2752}
2753
2754/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2755CTF flag for team three (Yellow).
2756Keys:
2757"angle" Angle the flag will point (minus 90 degrees)...
2758"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2759"noise" sound played when flag is picked up...
2760"noise1" sound played when flag is returned by a teammate...
2761"noise2" sound played when flag is captured...
2762"noise3" sound played when flag is lost in the field and respawns itself...
2763"noise4" sound played when flag is dropped by a player...
2764"noise5" sound played when flag touches the ground... */
2765spawnfunc(item_flag_team3)
2766{
2767 if(!g_ctf) { delete(this); return; }
2768
2770}
2771
2772/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2773CTF flag for team four (Pink).
2774Keys:
2775"angle" Angle the flag will point (minus 90 degrees)...
2776"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2777"noise" sound played when flag is picked up...
2778"noise1" sound played when flag is returned by a teammate...
2779"noise2" sound played when flag is captured...
2780"noise3" sound played when flag is lost in the field and respawns itself...
2781"noise4" sound played when flag is dropped by a player...
2782"noise5" sound played when flag touches the ground... */
2783spawnfunc(item_flag_team4)
2784{
2785 if(!g_ctf) { delete(this); return; }
2786
2788}
2789
2790/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2791CTF flag (Neutral).
2792Keys:
2793"angle" Angle the flag will point (minus 90 degrees)...
2794"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2795"noise" sound played when flag is picked up...
2796"noise1" sound played when flag is returned by a teammate...
2797"noise2" sound played when flag is captured...
2798"noise3" sound played when flag is lost in the field and respawns itself...
2799"noise4" sound played when flag is dropped by a player...
2800"noise5" sound played when flag touches the ground... */
2801spawnfunc(item_flag_neutral)
2802{
2803 if(!g_ctf) { delete(this); return; }
2804 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2805
2806 ctf_FlagSetup(0, this);
2807}
2808
2809/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2810Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2811Note: 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.
2812Keys:
2813"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2814"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2815spawnfunc(ctf_team)
2816{
2817 if(!g_ctf) { delete(this); return; }
2818
2819 this.team = this.cnt + 1;
2820}
2821
2822// compatibility for quake maps
2823spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2824spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2825spawnfunc(info_player_team1);
2826spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2827spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2828spawnfunc(info_player_team2);
2829spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2830spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2831
2832spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2833spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2834
2835// compatibility for wop maps
2836spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2837spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2838spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2839spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2840spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2841spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2842
2843
2844// ==============
2845// Initialization
2846// ==============
2847
2848// scoreboard setup
2850{
2852 field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2853 field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2854 field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2855 field(SP_CTF_PICKUPS, "pickups", 0);
2856 field(SP_CTF_FCKILLS, "fckills", 0);
2857 field(SP_CTF_RETURNS, "returns", 0);
2858 field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2859 });
2860}
2861
2862void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2863{
2864 int ctf_teams = 0;
2865
2866 IL_EACH(g_flags, true,
2867 {
2868 //if(it.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2869 //if(it.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2870
2871 switch(it.team)
2872 {
2873 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2874 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2875 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2876 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2877 }
2878 if(it.team == 0) { ctf_oneflag = true; }
2879 });
2880
2882
2883 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2884 {
2885 ctf_teams = 0; // so set the default red and blue teams
2886 BITSET_ASSIGN(ctf_teams, BIT(0));
2887 BITSET_ASSIGN(ctf_teams, BIT(1));
2888 }
2889
2890 //ctf_teams = bound(2, ctf_teams, 4);
2891
2892 // if no teams are found, spawn defaults
2893 Team_MapEnts_FindOrSpawn("ctf_team", ctf_teams);
2894
2895 ctf_ScoreRules(ctf_teams);
2896}
2897
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:1389
#define CTF_RECORD
Definition util.qh:99
#define TIME_ENCODE(t)
Definition util.qh:100
const int INITPRIO_SETLOCATION
Definition constants.qh:98
const int INITPRIO_GAMETYPE
Definition constants.qh:94
const int FL_NOTARGET
Definition constants.qh:76
const int FL_ITEM
Definition constants.qh:77
const float MOVE_NOMONSTERS
float trace_dphitcontents
entity trace_ent
const float SOLID_TRIGGER
float DPCONTENTS_SOLID
float RAD2DEG
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:483
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:193
entity EFFECT_PASS(int teamid)
Definition all.inc:177
entity EFFECT_FLAG_TOUCH(int teamid)
Definition all.inc:161
void Send_Effect_(string eff_name, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:125
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_FindRadius(vector org, float rad, bool needlineofsight)
Definition common.qc:660
void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
Definition common.qc:328
void WarpZone_TrailParticles(entity own, float eff, vector org, vector end)
Definition common.qc:451
vector WarpZone_UnTransformOrigin(entity wz, vector v)
Definition common.qc:519
void WarpZone_RefSys_AddInverse(entity me, entity wz)
Definition common.qc:717
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Definition common.qc:739
void WarpZone_RefSys_Copy(entity me, entity from)
Definition common.qc:771
#define BITSET_ASSIGN(a, b)
Definition common.qh:107
#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:475
f1
Definition all.inc:567
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:1767
void PlayerUseKey(entity this)
Definition client.qc:2620
#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:310
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:340
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:364
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:445
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:233
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:320
int autocvar_g_ctf_score_penalty_drop
Definition sv_ctf.qc:79
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:328
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:2898
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:267
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:479
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:2849
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:428
void ctf_DelayedInit(entity this)
Definition sv_ctf.qc:2862
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:174
#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 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:2427
void InitializeEntity(entity e, void(entity this) func, int order)
Definition world.qc:2229
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