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 *= 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 = ctf_worldflaglist;
1574 while (head)
1575 {
1576 if (CTF_SAMETEAM(this, head))
1577 break;
1578 head = head.ctf_worldflagnext;
1579 }
1580 if (head)
1581 navigation_routerating(this, head, ratingscale, 10000);
1582}
1583#endif
1584
1585void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1586{
1587 entity chosen = NULL;
1588 IL_EACH(g_flags, CTF_SAMETEAM(this, it),
1589 {
1590 if(this.flagcarried)
1591 if((this.flagcarried.cnt || it.cnt) && this.flagcarried.cnt != it.cnt)
1592 {
1593 // skip base if it has a different group
1594 continue;
1595 }
1596
1597 chosen = it;
1598 break;
1599 });
1600
1601 navigation_routerating(this, chosen.bot_basewaypoint, ratingscale, 10000);
1602}
1603
1604void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1605{
1606 entity chosen = NULL;
1607 IL_EACH(g_flags, true,
1608 {
1609 if(ctf_oneflag)
1610 {
1611 if(CTF_DIFFTEAM(this, it))
1612 {
1613 if(it.team)
1614 {
1615 if(this.flagcarried)
1616 {
1617 chosen = it;
1618 break;
1619 }
1620 }
1621 else if(!this.flagcarried)
1622 {
1623 chosen = it;
1624 break;
1625 }
1626 }
1627 }
1628 else if(CTF_DIFFTEAM(this, it))
1629 {
1630 chosen = it;
1631 break;
1632 }
1633 });
1634
1635 if (chosen)
1636 {
1637 if (chosen.ctf_status == FLAG_CARRY)
1638 {
1639 // adjust rating of our flag carrier depending on their health
1640 chosen = chosen.tag_entity;
1641 float f = bound(0, (GetResource(chosen, RES_HEALTH) + GetResource(chosen, RES_ARMOR)) / 100, 2) - 1;
1642 ratingscale += ratingscale * f * 0.1;
1643 }
1644 navigation_routerating(this, chosen, ratingscale, 10000);
1645 }
1646}
1647
1648void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1649{
1650 // disabled because we always spawn waypoints for flags with waypoint_spawnforitem_force
1651 /*
1652 if (!bot_waypoints_for_items)
1653 {
1654 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1655 return;
1656 }
1657 */
1659
1660 if (!head)
1661 return;
1662
1663 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1664}
1665
1667{
1668 entity mf = havocbot_ctf_find_flag(this);
1669
1670 if(mf.ctf_status == FLAG_BASE)
1671 return;
1672
1673 if(mf.tag_entity)
1674 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1675}
1676
1677void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1678{
1679 IL_EACH(g_flags, true,
1680 {
1681 // flag is out in the field
1682 if(it.ctf_status != FLAG_BASE)
1683 if(it.tag_entity==NULL) // dropped
1684 {
1685 if(df_radius)
1686 {
1687 if(vdist(org - it.origin, <, df_radius))
1688 navigation_routerating(this, it, ratingscale, 10000);
1689 }
1690 else
1691 navigation_routerating(this, it, ratingscale, 10000);
1692 }
1693 });
1694}
1695
1697{
1698 float cdefense, cmiddle, coffense;
1699 entity mf, ef;
1700
1701 if(IS_DEAD(this))
1702 return;
1703
1704 // Check ctf flags
1705 if (this.flagcarried)
1706 {
1708 return;
1709 }
1710
1711 mf = havocbot_ctf_find_flag(this);
1713
1714 // Retrieve stolen flag
1715 if(mf.ctf_status!=FLAG_BASE)
1716 {
1718 return;
1719 }
1720
1721 // If enemy flag is taken go to the middle to intercept pursuers
1722 if(ef.ctf_status!=FLAG_BASE)
1723 {
1725 return;
1726 }
1727
1728 // if there is no one else on the team switch to offense
1729 int count = 0;
1730 // don't check if this bot is a player since it isn't true when the bot is added to the server
1731 FOREACH_CLIENT(it != this && IS_PLAYER(it) && SAME_TEAM(it, this), { ++count; });
1732
1733 if (count == 0)
1734 {
1736 return;
1737 }
1738 else if (time < CS(this).jointime + 1)
1739 {
1740 // if bots spawn all at once set good default roles
1741 if (count == 1)
1742 {
1744 return;
1745 }
1746 else if (count == 2)
1747 {
1749 return;
1750 }
1751 }
1752
1753 // Evaluate best position to take
1754 // Count mates on middle position
1756
1757 // Count mates on defense position
1758 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
1759
1760 // Count mates on offense position
1761 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
1762
1763 if(cdefense<=coffense)
1765 else if(coffense<=cmiddle)
1767 else
1769
1770 // if bots spawn all at once assign them a more appropriated role after a while
1771 if (time < CS(this).jointime + 1 && count > 2)
1772 this.havocbot_role_timeout = time + 10 + random() * 10;
1773}
1774
1776{
1777 if (item.classname != "waypoint")
1778 return false;
1779
1780 IL_EACH(g_flags, item == it.bot_basewaypoint,
1781 {
1782 return true;
1783 });
1784 return false;
1785}
1786
1788{
1789 if(IS_DEAD(this))
1790 {
1792 return;
1793 }
1794
1795 if (this.flagcarried == NULL)
1796 {
1798 return;
1799 }
1800
1802 {
1804
1805 // role: carrier
1806 entity mf = havocbot_ctf_find_flag(this);
1807 vector base_org = mf.dropped_origin;
1808 float base_rating = (mf.ctf_status == FLAG_BASE) ? 10000 : (vdist(this.origin - base_org, >, 100) ? 2000 : 1000);
1809 if(ctf_oneflag)
1810 havocbot_goalrating_ctf_enemybase(this, base_rating);
1811 else
1812 havocbot_goalrating_ctf_ourbase(this, base_rating);
1813
1814 // start collecting items very close to the bot but only inside of own base radius
1815 if (vdist(this.origin - base_org, <, havocbot_middlepoint_radius))
1816 havocbot_goalrating_items(this, 15000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
1817
1818 havocbot_goalrating_items(this, 10000, base_org, havocbot_middlepoint_radius * 0.5);
1819
1821
1823
1824 entity goal = this.goalentity;
1825 if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
1826 this.goalentity_lock_timeout = time + ((this.enemy) ? 2 : 3);
1827
1828 if (goal)
1829 this.havocbot_cantfindflag = time + 10;
1830 else if (time > this.havocbot_cantfindflag)
1831 {
1832 // Can't navigate to my own base, suicide!
1833 // TODO: drop it and wander around
1834 Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
1835 return;
1836 }
1837 }
1838}
1839
1841{
1842 entity mf, ef;
1843
1844 if(IS_DEAD(this))
1845 {
1847 return;
1848 }
1849
1850 if (this.flagcarried)
1851 {
1853 return;
1854 }
1855
1856 // If enemy flag is back on the base switch to previous role
1858 if(ef.ctf_status==FLAG_BASE)
1859 {
1860 this.havocbot_role = this.havocbot_previous_role;
1861 this.havocbot_role_timeout = 0;
1862 return;
1863 }
1864 if (ef.ctf_status == FLAG_DROPPED)
1865 {
1867 return;
1868 }
1869
1870 // If the flag carrier reached the base switch to defense
1871 mf = havocbot_ctf_find_flag(this);
1872 if (mf.ctf_status != FLAG_BASE && vdist(ef.origin - mf.dropped_origin, <, 900))
1873 {
1875 return;
1876 }
1877
1878 // Set the role timeout if necessary
1879 if (!this.havocbot_role_timeout)
1880 {
1881 this.havocbot_role_timeout = time + random() * 30 + 60;
1882 }
1883
1884 // If nothing happened just switch to previous role
1885 if (time > this.havocbot_role_timeout)
1886 {
1887 this.havocbot_role = this.havocbot_previous_role;
1888 this.havocbot_role_timeout = 0;
1889 return;
1890 }
1891
1892 // Chase the flag carrier
1894 {
1896
1897 // role: escort
1900 havocbot_goalrating_items(this, 21000, this.origin, 10000);
1901
1903
1905 }
1906}
1907
1909{
1910 entity mf, ef;
1911 vector pos;
1912
1913 if(IS_DEAD(this))
1914 {
1916 return;
1917 }
1918
1919 if (this.flagcarried)
1920 {
1922 return;
1923 }
1924
1925 // Check flags
1926 mf = havocbot_ctf_find_flag(this);
1928
1929 // Own flag stolen
1930 if(mf.ctf_status!=FLAG_BASE)
1931 {
1932 if(mf.tag_entity)
1933 pos = mf.tag_entity.origin;
1934 else
1935 pos = mf.origin;
1936
1937 // Try to get it if closer than the enemy base
1938 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1939 {
1941 return;
1942 }
1943 }
1944
1945 // Escort flag carrier
1946 if(ef.ctf_status!=FLAG_BASE)
1947 {
1948 if(ef.tag_entity)
1949 pos = ef.tag_entity.origin;
1950 else
1951 pos = ef.origin;
1952
1953 if(vdist(pos - mf.dropped_origin, >, 700))
1954 {
1956 return;
1957 }
1958 }
1959
1960 // Set the role timeout if necessary
1961 if (!this.havocbot_role_timeout)
1962 this.havocbot_role_timeout = time + 120;
1963
1964 if (time > this.havocbot_role_timeout)
1965 {
1967 return;
1968 }
1969
1971 {
1973
1974 // role: offense
1977 havocbot_goalrating_items(this, 22000, this.origin, 10000);
1978
1980
1982 }
1983}
1984
1985// Retriever (temporary role):
1987{
1988 entity mf;
1989
1990 if(IS_DEAD(this))
1991 {
1993 return;
1994 }
1995
1996 if (this.flagcarried)
1997 {
1999 return;
2000 }
2001
2002 // If flag is back on the base switch to previous role
2003 mf = havocbot_ctf_find_flag(this);
2004 if(mf.ctf_status==FLAG_BASE)
2005 {
2006 if (mf.enemy == this) // did this bot return the flag?
2009 return;
2010 }
2011
2012 if (!this.havocbot_role_timeout)
2013 this.havocbot_role_timeout = time + 20;
2014
2015 if (time > this.havocbot_role_timeout)
2016 {
2018 return;
2019 }
2020
2022 {
2023 const float RT_RADIUS = 10000;
2024
2026
2027 // role: retriever
2029 havocbot_goalrating_ctf_droppedflags(this, 12000, this.origin, RT_RADIUS);
2032 vector enemy_base_org = ef.dropped_origin;
2033 // start collecting items very close to the bot but only inside of enemy base radius
2034 if (vdist(this.origin - enemy_base_org, <, havocbot_middlepoint_radius))
2035 havocbot_goalrating_items(this, 27000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
2037
2039
2041 }
2042}
2043
2045{
2046 entity mf;
2047
2048 if(IS_DEAD(this))
2049 {
2051 return;
2052 }
2053
2054 if (this.flagcarried)
2055 {
2057 return;
2058 }
2059
2060 mf = havocbot_ctf_find_flag(this);
2061 if(mf.ctf_status!=FLAG_BASE)
2062 {
2064 return;
2065 }
2066
2067 if (!this.havocbot_role_timeout)
2068 this.havocbot_role_timeout = time + 10;
2069
2070 if (time > this.havocbot_role_timeout)
2071 {
2073 return;
2074 }
2075
2077 {
2079 org.z = this.origin.z;
2080
2082
2083 // role: middle
2085 havocbot_goalrating_ctf_droppedflags(this, 9000, this.origin, 10000);
2088 havocbot_goalrating_items(this, 18000, this.origin, 10000);
2090
2092
2093 entity goal = this.goalentity;
2094 if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
2095 this.goalentity_lock_timeout = time + 2;
2096
2098 }
2099}
2100
2102{
2103 entity mf;
2104
2105 if(IS_DEAD(this))
2106 {
2108 return;
2109 }
2110
2111 if (this.flagcarried)
2112 {
2114 return;
2115 }
2116
2117 // If own flag was captured
2118 mf = havocbot_ctf_find_flag(this);
2119 if(mf.ctf_status!=FLAG_BASE)
2120 {
2122 return;
2123 }
2124
2125 if (!this.havocbot_role_timeout)
2126 this.havocbot_role_timeout = time + 30;
2127
2128 if (time > this.havocbot_role_timeout)
2129 {
2131 return;
2132 }
2134 {
2135 vector org = mf.dropped_origin;
2136
2138
2139 // if enemies are closer to our base, go there
2140 entity closestplayer = NULL;
2141 float distance, bestdistance = 10000;
2142 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
2143 distance = vlen(org - it.origin);
2144 if(distance<bestdistance)
2145 {
2146 closestplayer = it;
2147 bestdistance = distance;
2148 }
2149 });
2150
2151 // role: defense
2152 if(closestplayer)
2153 if(DIFF_TEAM(closestplayer, this))
2154 if(vdist(org - this.origin, >, 1000))
2155 if(checkpvs(this.origin,closestplayer)||random()<0.5)
2157
2162 havocbot_goalrating_items(this, 18000, this.origin, 10000);
2163
2165
2167 }
2168}
2169
2171{
2172 string s = "(null)";
2173 switch(role)
2174 {
2176 s = "carrier";
2177 bot.havocbot_role = havocbot_role_ctf_carrier;
2178 bot.havocbot_role_timeout = 0;
2179 bot.havocbot_cantfindflag = time + 10;
2180 if (bot.havocbot_previous_role != bot.havocbot_role)
2182 break;
2184 s = "defense";
2185 bot.havocbot_role = havocbot_role_ctf_defense;
2186 bot.havocbot_role_timeout = 0;
2187 break;
2189 s = "middle";
2190 bot.havocbot_role = havocbot_role_ctf_middle;
2191 bot.havocbot_role_timeout = 0;
2192 break;
2194 s = "offense";
2195 bot.havocbot_role = havocbot_role_ctf_offense;
2196 bot.havocbot_role_timeout = 0;
2197 break;
2199 s = "retriever";
2200 bot.havocbot_previous_role = bot.havocbot_role;
2201 bot.havocbot_role = havocbot_role_ctf_retriever;
2202 bot.havocbot_role_timeout = time + 10;
2203 if (bot.havocbot_previous_role != bot.havocbot_role)
2205 break;
2207 s = "escort";
2208 bot.havocbot_previous_role = bot.havocbot_role;
2209 bot.havocbot_role = havocbot_role_ctf_escort;
2210 bot.havocbot_role_timeout = time + 30;
2211 if (bot.havocbot_previous_role != bot.havocbot_role)
2213 break;
2214 }
2215 LOG_TRACE(bot.netname, " switched to ", s);
2216}
2217
2218
2219// ==============
2220// Hook Functions
2221// ==============
2222
2224{
2225 entity player = M_ARGV(0, entity);
2226
2227 // initially clear items so they can be set as necessary later.
2228 STAT(OBJECTIVE_STATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2234
2235 // do an iteration for each team (plus neutral) and get the flag for that team
2236 // this allows selection of the "best" flag for each team (e.g. the carried one)
2237 for(int j = 0; j <= AVAILABLE_TEAMS; ++j)
2238 {
2239 int teamnumber = (j > 0) ? Team_IndexToTeam(j) : 0;
2240 entity bestflag = NULL;
2241 int bestprio = 0;
2242
2243 IL_EACH(g_flags, it.team == teamnumber,
2244 {
2245 // if there's a neutral flag in the map, enable oneflag display
2246 if(it.team == 0)
2247 STAT(OBJECTIVE_STATUS, player) |= CTF_FLAG_NEUTRAL;
2248
2249 // use a priority system to decide the preferred flag
2250 int prio = 0;
2251 if(it.owner == player || it.pass_sender == player)
2252 prio = 3;
2253 else if(it.ctf_status == FLAG_DROPPED)
2254 prio = 2;
2255 else if(it.ctf_status != FLAG_BASE)
2256 prio = 1;
2257
2258 if(!bestflag || prio > bestprio)
2259 {
2260 bestflag = it;
2261 bestprio = prio;
2262 }
2263 });
2264
2265 if(bestflag)
2266 {
2267 int t = 0, t2 = 0, t3 = 0;
2268 switch(bestflag.team)
2269 {
2275 }
2276
2277 switch(bestflag.ctf_status)
2278 {
2279 case FLAG_PASSING:
2280 case FLAG_CARRY:
2281 {
2282 if((bestflag.owner == player) || (bestflag.pass_sender == player))
2283 STAT(OBJECTIVE_STATUS, player) |= t; // carrying: player is currently carrying the flag
2284 else
2285 STAT(OBJECTIVE_STATUS, player) |= t2; // taken: someone else is carrying the flag
2286 break;
2287 }
2288 case FLAG_DROPPED:
2289 {
2290 STAT(OBJECTIVE_STATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
2291 break;
2292 }
2293 }
2294 }
2295 }
2296
2297 // item for stopping players from capturing the flag too often
2298 if(player.ctf_captureshielded)
2299 STAT(OBJECTIVE_STATUS, player) |= CTF_SHIELDED;
2300
2301 if(ctf_stalemate)
2302 STAT(OBJECTIVE_STATUS, player) |= CTF_STALEMATE;
2303
2304 ctf_CaptureShield_Update(player, 1);
2305
2306 // update the health of the flag carrier waypointsprite
2307 if(player.wps_flagcarrier)
2308 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);
2309}
2310
2311MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in damage.qc
2312{
2313 entity frag_attacker = M_ARGV(1, entity);
2315 float frag_damage = M_ARGV(4, float);
2317
2318 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2319 {
2320 if(frag_target == frag_attacker) // damage done to yourself
2321 {
2324 }
2325 else // damage done to everyone else
2326 {
2329 }
2330
2331 M_ARGV(4, float) = frag_damage;
2332 M_ARGV(6, vector) = frag_force;
2333 }
2334 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2335 {
2338 {
2339 frag_target.wps_helpme_time = time;
2340 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2341 }
2342 // todo: add notification for when flag carrier needs help?
2343 }
2344}
2345
2346MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2347{
2348 entity frag_attacker = M_ARGV(1, entity);
2350
2351 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2352 {
2354 GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
2355 }
2356
2357 if(frag_target.flagcarried)
2358 {
2359 entity tmp_entity = frag_target.flagcarried;
2361 tmp_entity.ctf_dropper = NULL;
2362 }
2363}
2364
2365MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2366{
2367 M_ARGV(2, float) = 0; // frag score
2368 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2369}
2370
2372{
2373 if(player.flagcarried)
2374 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2375
2376 IL_EACH(g_flags, true,
2377 {
2378 if(it.pass_sender == player) { it.pass_sender = NULL; }
2379 if(it.pass_target == player) { it.pass_target = NULL; }
2380 if(it.ctf_dropper == player) { it.ctf_dropper = NULL; }
2381 });
2382}
2383
2384MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2385{
2386 entity player = M_ARGV(0, entity);
2387
2388 ctf_RemovePlayer(player);
2389}
2390
2392{
2393 entity player = M_ARGV(0, entity);
2394
2395 ctf_RemovePlayer(player);
2396}
2397
2399{
2401 return;
2402
2403 entity player = M_ARGV(0, entity);
2404
2405 race_SendAll(player, true);
2406}
2407
2409{
2411 return;
2412
2413 entity player = M_ARGV(0, entity);
2414
2415 race_checkAndWriteName(player);
2416}
2417
2418MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2419{
2420 entity player = M_ARGV(0, entity);
2421
2422 if(player.flagcarried)
2424 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2425}
2426
2428{
2429 if(MUTATOR_RETURNVALUE || game_stopped) return;
2430
2431 entity player = M_ARGV(0, entity);
2432
2433 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2434 {
2435 // pass the flag to a teammate
2436 if(autocvar_g_ctf_pass && player.flagcarried.classname != "phantomflag")
2437 {
2438 entity head, closest_target = NULL;
2439 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2440
2441 while(head) // find the closest acceptable target to pass to
2442 {
2443 if(IS_PLAYER(head) && !IS_DEAD(head) && !IS_INDEPENDENT_PLAYER(head))
2444 if(head != player && SAME_TEAM(head, player))
2445 if(!head.speedrunning && !head.vehicle)
2446 {
2447 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc)
2448 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2449 vector passer_center = CENTER_OR_VIEWOFS(player);
2450
2451 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2452 {
2453 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried && head.flagcarried.classname != "phantomflag")
2454 {
2455 if(IS_BOT_CLIENT(head))
2456 {
2457 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2458 ctf_Handle_Throw(head, player, DROP_PASS);
2459 }
2460 else
2461 {
2462 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2463 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2464 }
2465 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2466 return true;
2467 }
2468 else if(player.flagcarried && !head.flagcarried)
2469 {
2470 if(closest_target)
2471 {
2472 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2473 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2474 { closest_target = head; }
2475 }
2476 else { closest_target = head; }
2477 }
2478 }
2479 }
2480 head = head.chain;
2481 }
2482
2483 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2484 }
2485
2486 // throw the flag in front of you
2487 if(autocvar_g_ctf_throw && player.flagcarried)
2488 {
2489 if(player.throw_count == -1)
2490 {
2491 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2492 {
2493 player.throw_prevtime = time;
2494 player.throw_count = 1;
2496 return true;
2497 }
2498 else
2499 {
2500 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2501 return false;
2502 }
2503 }
2504 else
2505 {
2506 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2507 else { ++player.throw_count; }
2508 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2509
2510 player.throw_prevtime = time;
2512 return true;
2513 }
2514 }
2515 }
2516}
2517
2518MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2519{
2520 entity player = M_ARGV(0, entity);
2521
2522 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2523 {
2524 player.wps_helpme_time = time;
2525 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2526 }
2527 else // create a normal help me waypointsprite
2528 {
2529 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2530 WaypointSprite_Ping(player.wps_helpme);
2531 }
2532
2533 return true;
2534}
2535
2536MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2537{
2538 entity player = M_ARGV(0, entity);
2539 entity veh = M_ARGV(1, entity);
2540
2541 if(player.flagcarried)
2542 {
2544 {
2546 }
2547 else
2548 {
2549 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2550 setattachment(player.flagcarried, veh, "");
2551 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2552 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2553 //player.flagcarried.angles = '0 0 0';
2554 }
2555 return true;
2556 }
2557}
2558
2559MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2560{
2561 entity player = M_ARGV(0, entity);
2562
2563 if(player.flagcarried)
2564 {
2565 setattachment(player.flagcarried, player, "");
2566 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2567 player.flagcarried.scale = FLAG_SCALE;
2568 player.flagcarried.angles = '0 0 0';
2569 player.flagcarried.nodrawtoclient = NULL;
2570 return true;
2571 }
2572}
2573
2574MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2575{
2576 entity player = M_ARGV(0, entity);
2577
2578 if(player.flagcarried)
2579 {
2580 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2581 ctf_RespawnFlag(player.flagcarried);
2582 return true;
2583 }
2584}
2585
2587{
2588 IL_EACH(g_flags, true,
2589 {
2590 switch(it.ctf_status)
2591 {
2592 case FLAG_DROPPED:
2593 case FLAG_PASSING:
2594 {
2595 // lock the flag, game is over
2597 it.takedamage = DAMAGE_NO;
2598 it.solid = SOLID_NOT;
2599 it.nextthink = false; // stop thinking
2600
2601 //dprint("stopping the ", flag.netname, " from moving.\n");
2602 break;
2603 }
2604
2605 default:
2606 case FLAG_BASE:
2607 case FLAG_CARRY:
2608 {
2609 // do nothing for these flags
2610 break;
2611 }
2612 }
2613 });
2614}
2615
2616MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2617{
2618 entity bot = M_ARGV(0, entity);
2619
2621 return true;
2622}
2623
2625{
2626 M_ARGV(1, string) = "ctf_team";
2627}
2628
2629MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2630{
2631 int record_page = M_ARGV(0, int);
2632 string ret_string = M_ARGV(1, string);
2633
2634 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2635 {
2636 if (MapInfo_Get_ByID(i))
2637 {
2638 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2639
2640 if(!r)
2641 continue;
2642
2643 // TODO: uid2name
2644 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2645 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2646 }
2647 }
2648
2649 M_ARGV(1, string) = ret_string;
2650}
2651
2652bool superspec_Spectate(entity this, entity targ); // TODO
2653void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2655{
2656 entity player = M_ARGV(0, entity);
2657 string cmd_name = M_ARGV(1, string);
2658 int cmd_argc = M_ARGV(2, int);
2659
2660 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2661
2662 if(cmd_name == "followfc")
2663 {
2664 if(!g_ctf)
2665 return true;
2666
2667 int _team = 0;
2668 bool found = false;
2669
2670 if(cmd_argc == 2)
2671 {
2672 switch(argv(1))
2673 {
2674 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2675 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2676 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2677 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2678 }
2679 }
2680
2682 if(it.flagcarried && (it.team == _team || _team == 0))
2683 {
2684 found = true;
2685 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2686 continue; // already spectating this fc, try another
2687 return superspec_Spectate(player, it);
2688 }
2689 });
2690
2691 if(!found)
2692 superspec_msg("", "", player, "No active flag carrier\n", 1);
2693 return true;
2694 }
2695}
2696
2697MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2698{
2700
2701 if(frag_target.flagcarried)
2703}
2704
2705MUTATOR_HOOKFUNCTION(ctf, LogDeath_AppendItemCodes)
2706{
2707 entity player = M_ARGV(0, entity);
2708 if(player.flagcarried)
2709 M_ARGV(1, string) = strcat(M_ARGV(1, string), "F"); // item codes
2710}
2711
2712MUTATOR_HOOKFUNCTION(ctf, PreferPlayerScore_Clear)
2713{
2714 return true;
2715}
2716
2717
2718// ==========
2719// Spawnfuncs
2720// ==========
2721
2722/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2723CTF flag for team one (Red).
2724Keys:
2725"angle" Angle the flag will point (minus 90 degrees)...
2726"model" model to use, note this needs red and blue as skins 0 and 1...
2727"noise" sound played when flag is picked up...
2728"noise1" sound played when flag is returned by a teammate...
2729"noise2" sound played when flag is captured...
2730"noise3" sound played when flag is lost in the field and respawns itself...
2731"noise4" sound played when flag is dropped by a player...
2732"noise5" sound played when flag touches the ground... */
2733spawnfunc(item_flag_team1)
2734{
2735 if(!g_ctf) { delete(this); return; }
2736
2738}
2739
2740/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2741CTF flag for team two (Blue).
2742Keys:
2743"angle" Angle the flag will point (minus 90 degrees)...
2744"model" model to use, note this needs red and blue as skins 0 and 1...
2745"noise" sound played when flag is picked up...
2746"noise1" sound played when flag is returned by a teammate...
2747"noise2" sound played when flag is captured...
2748"noise3" sound played when flag is lost in the field and respawns itself...
2749"noise4" sound played when flag is dropped by a player...
2750"noise5" sound played when flag touches the ground... */
2751spawnfunc(item_flag_team2)
2752{
2753 if(!g_ctf) { delete(this); return; }
2754
2756}
2757
2758/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2759CTF flag for team three (Yellow).
2760Keys:
2761"angle" Angle the flag will point (minus 90 degrees)...
2762"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2763"noise" sound played when flag is picked up...
2764"noise1" sound played when flag is returned by a teammate...
2765"noise2" sound played when flag is captured...
2766"noise3" sound played when flag is lost in the field and respawns itself...
2767"noise4" sound played when flag is dropped by a player...
2768"noise5" sound played when flag touches the ground... */
2769spawnfunc(item_flag_team3)
2770{
2771 if(!g_ctf) { delete(this); return; }
2772
2774}
2775
2776/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2777CTF flag for team four (Pink).
2778Keys:
2779"angle" Angle the flag will point (minus 90 degrees)...
2780"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2781"noise" sound played when flag is picked up...
2782"noise1" sound played when flag is returned by a teammate...
2783"noise2" sound played when flag is captured...
2784"noise3" sound played when flag is lost in the field and respawns itself...
2785"noise4" sound played when flag is dropped by a player...
2786"noise5" sound played when flag touches the ground... */
2787spawnfunc(item_flag_team4)
2788{
2789 if(!g_ctf) { delete(this); return; }
2790
2792}
2793
2794/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2795CTF flag (Neutral).
2796Keys:
2797"angle" Angle the flag will point (minus 90 degrees)...
2798"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2799"noise" sound played when flag is picked up...
2800"noise1" sound played when flag is returned by a teammate...
2801"noise2" sound played when flag is captured...
2802"noise3" sound played when flag is lost in the field and respawns itself...
2803"noise4" sound played when flag is dropped by a player...
2804"noise5" sound played when flag touches the ground... */
2805spawnfunc(item_flag_neutral)
2806{
2807 if(!g_ctf) { delete(this); return; }
2808 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2809
2810 ctf_FlagSetup(0, this);
2811}
2812
2813/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2814Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2815Note: 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.
2816Keys:
2817"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2818"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2819spawnfunc(ctf_team)
2820{
2821 if(!g_ctf) { delete(this); return; }
2822
2823 this.team = this.cnt + 1;
2824}
2825
2826// compatibility for quake maps
2827spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2828spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2829spawnfunc(info_player_team1);
2830spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2831spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2832spawnfunc(info_player_team2);
2833spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2834spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2835
2836spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2837spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2838
2839// compatibility for wop maps
2840spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2841spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2842spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2843spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2844spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2845spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2846
2847
2848// ==============
2849// Initialization
2850// ==============
2851
2852// scoreboard setup
2854{
2856 field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2857 field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2858 field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2859 field(SP_CTF_PICKUPS, "pickups", 0);
2860 field(SP_CTF_FCKILLS, "fckills", 0);
2861 field(SP_CTF_RETURNS, "returns", 0);
2862 field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2863 });
2864}
2865
2866// code from here on is just to support maps that don't have flag and team entities
2867void ctf_SpawnTeam (string teamname, int teamcolor)
2868{
2869 entity this = new_pure(ctf_team);
2870 this.netname = teamname;
2871 this.cnt = teamcolor - 1;
2872 this.spawnfunc_checked = true;
2873 this.team = teamcolor;
2874}
2875
2876void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2877{
2878 ctf_teams = 0;
2879
2880 IL_EACH(g_flags, true,
2881 {
2882 //if(it.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2883 //if(it.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2884
2885 switch(it.team)
2886 {
2887 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2888 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2889 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2890 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2891 }
2892 if(it.team == 0) { ctf_oneflag = true; }
2893 });
2894
2896
2897 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2898 {
2899 ctf_teams = 0; // so set the default red and blue teams
2902 }
2903
2904 //ctf_teams = bound(2, ctf_teams, 4);
2905
2906 // if no teams are found, spawn defaults
2907 if(find(NULL, classname, "ctf_team") == NULL)
2908 {
2909 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2910 if(ctf_teams & BIT(0))
2911 ctf_SpawnTeam("Red", NUM_TEAM_1);
2912 if(ctf_teams & BIT(1))
2913 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2914 if(ctf_teams & BIT(2))
2915 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2916 if(ctf_teams & BIT(3))
2917 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2918 }
2919
2921}
2922
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:244
#define IS_PLAYER(s)
Definition player.qh:242
const int SFL_LOWER_IS_BETTER
Lower scores are better (e.g.
Definition scores.qh:102
const int SFL_TIME
Display as mm:ss.s, value is stored as 10ths of a second (AND 0 is the worst possible value!...
Definition scores.qh:122
const int ST_SCORE
Definition scores.qh:158
const int SFL_SORT_PRIO_SECONDARY
Scoring priority (NOTE: PRIMARY is used for fraglimit) NOTE: SFL_SORT_PRIO_SECONDARY value must be lo...
Definition scores.qh:133
const int SFL_SORT_PRIO_PRIMARY
Definition scores.qh:134
#define autocvar_timelimit
Definition stats.qh:92
bool autocvar_g_ctf_leaderboard
Definition stats.qh:148
float game_stopped
Definition stats.qh:81
vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
Definition util.qc:1389
#define CTF_RECORD
Definition util.qh:99
#define TIME_ENCODE(t)
Definition util.qh:100
const int INITPRIO_SETLOCATION
Definition constants.qh:98
const int INITPRIO_GAMETYPE
Definition constants.qh:94
const int FL_NOTARGET
Definition constants.qh:76
const int FL_ITEM
Definition constants.qh:77
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:57
const int CTF_PINK_FLAG_CARRYING
Definition ctf.qh:52
const int CTF_YELLOW_FLAG_TAKEN
Definition ctf.qh:47
const int CTF_YELLOW_FLAG_CARRYING
Definition ctf.qh:49
const int CTF_PINK_FLAG_TAKEN
Definition ctf.qh:50
const int CTF_RED_FLAG_LOST
Definition ctf.qh:42
const int CTF_NEUTRAL_FLAG_LOST
Definition ctf.qh:54
const int CTF_YELLOW_FLAG_LOST
Definition ctf.qh:48
const int CTF_RED_FLAG_CARRYING
Definition ctf.qh:43
const int CTF_RED_FLAG_TAKEN
Definition ctf.qh:41
const int CTF_FLAG_NEUTRAL
Definition ctf.qh:56
const int CTF_STALEMATE
Definition ctf.qh:58
#define g_ctf
Definition ctf.qh:38
const int CTF_BLUE_FLAG_LOST
Definition ctf.qh:45
const int CTF_BLUE_FLAG_TAKEN
Definition ctf.qh:44
const int CTF_PINK_FLAG_LOST
Definition ctf.qh:51
const int CTF_NEUTRAL_FLAG_CARRYING
Definition ctf.qh:55
const int CTF_BLUE_FLAG_CARRYING
Definition ctf.qh:46
const int CTF_NEUTRAL_FLAG_TAKEN
Definition ctf.qh:53
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition damage.qc:493
float autocvar_g_balance_armor_blockpercent
Definition damage.qh:21
IntrusiveList g_damagedbycontents
Definition damage.qh:143
#define DMG_NOWEP
Definition damage.qh:104
float EF_LOWPRECISION
entity EFFECT_CAP(int teamid)
Definition all.inc:193
entity EFFECT_PASS(int teamid)
Definition all.inc:177
entity EFFECT_FLAG_TOUCH(int teamid)
Definition all.inc:161
void Send_Effect_(string eff_name, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:125
RES_ARMOR
Definition ent_cs.qc: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:684
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:544
void WarpZone_RefSys_AddInverse(entity me, entity wz)
Definition common.qc:741
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Definition common.qc:763
void WarpZone_RefSys_Copy(entity me, entity from)
Definition common.qc:795
#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:274
float MapInfo_count
Definition mapinfo.qh:166
string MapInfo_Map_bspname
Definition mapinfo.qh:6
entity goalentity
Definition viewloc.qh:16
float tanh(float e)
Definition mathlib.qc:69
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:366
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:471
f1
Definition all.inc:563
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:108
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:1762
void PlayerUseKey(entity this)
Definition client.qc:2585
#define IS_INDEPENDENT_PLAYER(e)
Definition client.qh:312
float jointime
Definition client.qh:66
void ImpulseCommands(entity this)
Definition impulse.qc:371
IntrusiveList g_items
Definition items.qh:119
#define ITEM_TOUCH_NEEDKILL()
Definition items.qh:122
#define ITEM_DAMAGE_NEEDKILL(dt)
Definition items.qh:123
bool noalign
Definition items.qh:37
void race_SendAll(entity player, bool only_rankings)
Definition race.qc:332
void write_recordmarker(entity pl, float tstart, float dt)
Definition race.qc:57
void race_checkAndWriteName(entity player)
Definition race.qc:133
void race_setTime(string map, float t, string myuid, string mynetname, entity e, bool showmessage)
Definition race.qc:373
const int CH_TRIGGER
Definition sound.qh:12
const float VOL_BASE
Definition sound.qh:36
const float ATTEN_NONE
Definition sound.qh:27
#define _sound(e, c, s, v, a)
Definition sound.qh:43
const float ATTEN_NORM
Definition sound.qh:30
#define SND(id)
Definition all.qh:35
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:2867
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:2044
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:1775
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:1666
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:445
void havocbot_role_ctf_carrier(entity this)
Definition sv_ctf.qc:1787
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:1840
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:1908
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:1677
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:2371
void havocbot_role_ctf_defense(entity this)
Definition sv_ctf.qc:2101
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:2923
int autocvar_g_ctf_score_kill
Definition sv_ctf.qc:78
void havocbot_role_ctf_retriever(entity this)
Definition sv_ctf.qc:1986
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:2170
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:2315
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:1585
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:1648
vector frag_force
Definition sv_ctf.qc:2316
void ctf_CheckStalemate()
Definition sv_ctf.qc:902
entity frag_target
Definition sv_ctf.qc:2314
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:2853
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:2876
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:1604
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:1696
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:174
#define IS_INVISIBLE(v)
Definition utils.qh:29
#define IS_SPEC(v)
Definition utils.qh:10
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define IS_MONSTER(v)
Definition utils.qh:23
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:52
#define IS_VEHICLE(v)
Definition utils.qh:24
#define CENTER_OR_VIEWOFS(ent)
Definition utils.qh:31
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15
#define vlen2(v)
Definition vector.qh:4
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
ERASEABLE vector vrint(vector v)
Definition vector.qh:216
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:2428
void InitializeEntity(entity e, void(entity this) func, int order)
Definition world.qc:2230
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