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