Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
sv_keyhunt.qc
Go to the documentation of this file.
1#include "sv_keyhunt.qh"
2
5#include <server/gamelog.qh>
6#include <server/damage.qh>
9
20
25
33
34//int autocvar_g_keyhunt_teams;
36
37// #define KH_PLAYER_USE_ATTACHMENT
38// TODO? no model exists for this
39// #define KH_PLAYER_USE_CARRIEDMODEL
40
41#ifdef KH_PLAYER_USE_ATTACHMENT
42const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0';
43const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0';
44const vector KH_PLAYER_ATTACHMENT = '0 0 0';
45const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0';
46const string KH_PLAYER_ATTACHMENT_BONE = "";
47#else
48const float KH_KEY_ZSHIFT = 22;
49const float KH_KEY_XYDIST = 24;
50const float KH_KEY_XYSPEED = 45;
51#endif
52const float KH_KEY_WP_ZSHIFT = 20;
53
54const vector KH_KEY_MIN = '-25 -25 -46'; // 0.8.6 used '-10 -10 -46' with sv_legacy_bbox_expand 1 and FL_ITEM
55const vector KH_KEY_MAX = '25 25 4'; // 0.8.6 used '10 10 3' with sv_legacy_bbox_expand 1 and FL_ITEM
56const float KH_KEY_BRIGHTNESS = 2;
57
59
60// kh_state
61// bits 0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self
62// bits 5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self
63// bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self
64// bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self
65.float siren_time; // time delay the siren
66
67int kh_Team_ByID(int t)
68{
69 if(t == 0) return NUM_TEAM_1;
70 if(t == 1) return NUM_TEAM_2;
71 if(t == 2) return NUM_TEAM_3;
72 if(t == 3) return NUM_TEAM_4;
73 return 0;
74}
75
80.entity kh_next, kh_prev; // linked list
83.entity previous_owner; // also used on ka balls
85
87
89
90const int ST_KH_CAPS = 1;
92{
94 field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
95 field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
96 field(SP_KH_PUSHES, "pushes", 0);
97 field(SP_KH_DESTRUCTIONS, "destructions", SFL_LOWER_IS_BETTER);
98 field(SP_KH_PICKUPS, "pickups", 0);
99 field(SP_KH_KCKILLS, "kckills", 0);
100 field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER);
101 });
102}
103
104bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs all the time
105{
106 if(view.kh_next && IS_SPEC(player))
107 return false; // we don't want spectators of key carriers to see the attached waypoint on the top of their screen
108 if(IS_SPEC(player) || warmup_stage || SAME_TEAM(player, this.owner))
109 return true;
110 if(IS_INVISIBLE(this.owner))
111 return false; // hide the waypointsprite if the owner is invisible
112
113 return kh_tracking_enabled;
114}
115
117{
118 // always show the dropped key waypoint while in warmup or spectating
119 if(this.owner && !this.owner.owner && (IS_SPEC(player) || warmup_stage))
120 return true;
122 return false;
123
124 return !this.owner || !this.owner.owner; // draw only when key is not owned
125}
126
128{
129 entity key;
130 int f;
131 int s = 0;
132 FOR_EACH_KH_KEY(key)
133 {
134 if(key.owner)
135 f = key.team;
136 else
137 f = 30;
138 s |= (32 ** key.count) * f;
139 }
140
141 FOREACH_CLIENT(true, { STAT(OBJECTIVE_STATUS, it) = s; });
142
143 FOR_EACH_KH_KEY(key)
144 {
145 if(key.owner)
146 STAT(OBJECTIVE_STATUS, key.owner) |= (32 ** key.count) * 31;
147 }
148 //print(ftos((nextent(NULL)).kh_state), "\n");
149}
150
151
152
153
155void kh_Controller_SetThink(float t, kh_Think_t func) // runs occasionaly
156{
158 kh_controller.cnt = ceil(t);
159 if(t == 0)
160 kh_controller.nextthink = time; // force
161}
162void kh_WaitForPlayers();
163void kh_Controller_Think(entity this) // called a lot
164{
165 if(game_stopped)
166 return;
167 if(this.cnt > 0)
168 {
169 if(getthink(this) != kh_WaitForPlayers)
170 this.cnt -= 1;
171 }
172 else if(this.cnt == 0)
173 {
174 this.cnt -= 1;
176 }
177 this.nextthink = time + 1;
178}
179
180// frags f: take from cvar * f
181// frags 0: no frags
182// field where the decimal part of SCORE is accumulated (shared with other gametypes)
184void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner) // update the score when a key is captured
185{
186 string s;
187 if(game_stopped)
188 return;
189
190 if(frags_player)
191 GameRules_scoring_add_team_float2int(player, SCORE, frags_player, float2int_decimal_fld, 1);
192
193 if(key && key.owner && frags_owner)
194 GameRules_scoring_add_team_float2int(key.owner, SCORE, frags_owner, float2int_decimal_fld, 1);
195
196 if(!autocvar_sv_eventlog) //output extra info to the console or text file
197 return;
198
199 s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player));
200
201 if(key && key.owner)
202 s = strcat(s, ":", ftos(key.owner.playerid));
203 else
204 s = strcat(s, ":0");
205
206 s = strcat(s, ":", ftos(frags_owner), ":");
207
208 if(key)
209 s = strcat(s, key.netname);
210
211 GameLogEcho(s);
212}
213
214vector kh_AttachedOrigin(entity e) // runs when a team captures the flag, it can run 2 or 3 times.
215{
216 if(e.tag_entity)
217 {
218 makevectors(e.tag_entity.angles);
219 return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up;
220 }
221 else
222 return e.origin;
223}
224
225void kh_Key_Attach(entity key) // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round
226{
227 key.solid = SOLID_NOT; // before setorigin to prevent area grid linking
228#ifdef KH_PLAYER_USE_ATTACHMENT
229 entity first = key.owner.kh_next;
230 if(key == first)
231 {
232 setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE);
233 if(key.kh_next)
234 {
235 setattachment(key.kh_next, key, "");
236 setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
237 setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
238 key.kh_next.angles = '0 0 0';
239 }
240 else
241 setorigin(key, KH_PLAYER_ATTACHMENT);
242 key.angles = KH_PLAYER_ATTACHMENT_ANGLES;
243 }
244 else
245 {
246 setattachment(key, key.kh_prev, "");
247 if(key.kh_next)
248 setattachment(key.kh_next, key, "");
249 setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
250 setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
251 key.angles = '0 0 0';
252 }
253#else
254 setattachment(key, key.owner, "");
255 setorigin(key, '0 0 1' * KH_KEY_ZSHIFT); // fixing x, y in think
256 key.angles_y -= key.owner.angles.y;
257#endif
258 key.flags = 0;
259 if(IL_CONTAINS(g_items, key))
260 IL_REMOVE(g_items, key);
262 key.team = key.owner.team;
263 key.nextthink = time;
264 key.damageforcescale = 0;
265 key.takedamage = DAMAGE_NO;
266 key.modelindex = kh_key_carried;
268}
269
270void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured
271{
272 key.solid = SOLID_TRIGGER; // before setorigin to ensure area grid linking
273#ifdef KH_PLAYER_USE_ATTACHMENT
274 entity first = key.owner.kh_next;
275 if(key == first)
276 {
277 if(key.kh_next)
278 {
279 setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE);
280 setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
281 key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES;
282 }
283 }
284 else
285 {
286 if(key.kh_next)
287 setattachment(key.kh_next, key.kh_prev, "");
288 setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
289 }
290 // in any case:
291 setattachment(key, NULL, "");
292 setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z));
293 key.angles = key.owner.angles;
294#else
295 setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
296 setattachment(key, NULL, "");
297 key.angles_y += key.owner.angles.y;
298#endif
299 key.flags = FL_ITEM;
300 if(!IL_CONTAINS(g_items, key))
301 IL_PUSH(g_items, key);
303 nudgeoutofsolid_OrFallback(key); // a key has a bigger bbox than a player
304 key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return;
305 key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale;
306 key.takedamage = DAMAGE_YES;
307 // let key.team stay
308 key.modelindex = kh_key_dropped;
309 navigation_dynamicgoal_set(key, key.owner);
310 key.previous_owner = key.owner;
311 key.kh_previous_owner_playerid = key.owner.playerid;
312}
313
314void kh_Key_AssignTo(entity key, entity player) // runs every time a key is picked up or assigned. Runs prior to kh_key_attach
315{
316 if(key.owner == player)
317 return;
318
319 int ownerteam0 = kh_Key_AllOwnedByWhichTeam();
320
321 if(key.owner)
322 {
323 kh_Key_Detach(key);
324
325 // remove from linked list
326 if(key.kh_next)
327 key.kh_next.kh_prev = key.kh_prev;
328 key.kh_prev.kh_next = key.kh_next;
329 key.kh_next = NULL;
330 key.kh_prev = NULL;
331
332 if(key.owner.kh_next == NULL)
333 {
334 // No longer a key carrier
336 WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier);
338 }
339 }
340
341 key.owner = player;
342
343 if(player)
344 {
345 // insert into linked list
346 key.kh_next = player.kh_next;
347 key.kh_prev = player;
348 player.kh_next = key;
349 if(key.kh_next)
350 key.kh_next.kh_prev = key;
351
352 kh_Key_Attach(key);
353
354 if(key.kh_next == NULL)
355 {
356 // player is now a key carrier
357 entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER);
358 wp.colormod = colormapPaletteColor(player.team - 1, 0);
359 player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player;
360 WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY);
361 if(player.team == NUM_TEAM_1)
362 WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed);
363 else if(player.team == NUM_TEAM_2)
364 WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue);
365 else if(player.team == NUM_TEAM_3)
366 WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow);
367 else if(player.team == NUM_TEAM_4)
368 WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink);
370 WaypointSprite_Ping(player.waypointsprite_attachedforcarrier);
371 }
372 }
373
374 // moved that here, also update if there's no player
376
377 key.pusher = NULL;
378
379 int ownerteam = kh_Key_AllOwnedByWhichTeam();
380 if(ownerteam != ownerteam0)
381 {
382 entity k;
383 if(ownerteam != -1)
384 {
386 kh_interferemsg_team = player.team;
387
388 // audit all key carrier sprites, update them to "Run here"
390 {
391 if (!k.owner) continue;
392 entity first = WP_Null;
393 FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
394 entity third = WP_Null;
395 FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
396 WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third);
397 }
398 }
399 else
400 {
402
403 // audit all key carrier sprites, update them to "Key Carrier"
405 {
406 if (!k.owner) continue;
407 entity first = WP_Null;
408 FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
409 entity third = WP_Null;
410 FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
411 WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
412 }
413 }
414 }
415}
416
417void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
418{
419 if(this.owner)
420 return;
421 if(ITEM_DAMAGE_NEEDKILL(deathtype))
422 {
424 return;
425 }
426 if(force == '0 0 0')
427 return;
428 if(time > this.pushltime)
429 if(IS_PLAYER(attacker))
430 this.team = attacker.team;
431}
432
433void kh_Key_Collect(entity key, entity player) //a player picks up a dropped key
434{
435 sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM);
436
437 if(key.kh_dropperteam != player.team)
438 {
440 GameRules_scoring_add(player, KH_PICKUPS, 1);
441 }
442 key.kh_dropperteam = 0;
443 int realteam = kh_Team_ByID(key.count);
444 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname);
445
446 kh_Key_AssignTo(key, player); // this also updates .kh_state
447}
448
449void kh_Key_Touch(entity this, entity toucher) // runs many, many times when a key has been dropped and can be picked up
450{
451 if(game_stopped)
452 return;
453
454 if(this.owner) // already carried
455 return;
456
458 {
460 return;
461 }
462
463 if (!IS_PLAYER(toucher))
464 return;
465 if(IS_DEAD(toucher))
466 return;
468 return;
469 if(toucher == this.enemy)
471 return; // you just dropped it!
472 kh_Key_Collect(this, toucher);
473}
474
475void kh_Key_Remove(entity key) // runs after when all the keys have been collected or when a key has been dropped for more than X seconds
476{
477 entity o = key.owner;
478 kh_Key_AssignTo(key, NULL);
479 if(o) // it was attached
480 WaypointSprite_Kill(key.waypointsprite_attachedforcarrier);
481 else // it was dropped
483
484 // remove key from key list
485 if (kh_worldkeylist == key)
486 kh_worldkeylist = kh_worldkeylist.kh_worldkeynext;
487 else
488 {
489 o = kh_worldkeylist;
490 while (o)
491 {
492 if (o.kh_worldkeynext == key)
493 {
494 o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext;
495 break;
496 }
497 o = o.kh_worldkeynext;
498 }
499 }
500
501 delete(key);
502
504}
505
506void kh_FinishRound() // runs when a team captures the keys
507{
508 // prepare next round
510 entity key;
511
512 kh_no_radar_circles = true;
513 FOR_EACH_KH_KEY(key)
514 kh_Key_Remove(key);
515 kh_no_radar_circles = false;
516
517 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
519}
520
521void nades_GiveBonus(entity player, float score);
522
523void kh_WinnerTeam(int winner_team) // runs when a team wins
524{
525 // all key carriers get some points
526 entity key;
529 // twice the score for 3 team games, three times the score for 4 team games!
530 // note: for a win by destroying the key, this should NOT be applied
531 FOR_EACH_KH_KEY(key)
532 {
533 float f = DistributeEvenly_Get(1);
534 kh_Scores_Event(key.owner, key, "capture", f, 0);
535 GameRules_scoring_add_team(key.owner, KH_CAPS, 1);
537 }
538
539 bool first = true;
540 string keyowner = "";
541 FOR_EACH_KH_KEY(key)
542 if(key.owner.kh_next == key)
543 {
544 if(!first)
545 keyowner = strcat(keyowner, ", ");
546 keyowner = key.owner.netname;
547 first = false;
548 }
549
550 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
551 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner);
552
553 first = true;
554 vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0';
555 FOR_EACH_KH_KEY(key)
556 {
557 vector thisorigin = kh_AttachedOrigin(key);
558 //dprint("Key origin: ", vtos(thisorigin), "\n");
559 midpoint += thisorigin;
560
561 if(!first)
562 {
563 // TODO: this effect has been replaced due to a possible crash it causes
564 // see https://gitlab.com/xonotic/darkplaces/issues/123
565 //te_lightning2(NULL, lastorigin, thisorigin);
566 Send_Effect(EFFECT_TR_NEXUIZPLASMA, lastorigin, thisorigin, 1);
567 }
568 lastorigin = thisorigin;
569 if(first)
570 firstorigin = thisorigin;
571 first = false;
572 }
573 if(NumTeams(kh_teams) > 2)
574 {
575 //te_lightning2(NULL, lastorigin, firstorigin); // TODO see above
576 Send_Effect(EFFECT_TR_NEXUIZPLASMA, lastorigin, firstorigin, 1);
577 }
578 midpoint = midpoint * (1 / NumTeams(kh_teams));
579 te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 0.5 + '0.5 0.5 0.5'); // make the color >=0.5 in each component
580
581 play2all(SND(KH_CAPTURE));
583}
584
585void kh_LoserTeam(int loser_team, entity lostkey) // runs when a player pushes a flag carrier off the map
586{
587 float f;
588 entity attacker = NULL;
589 if(lostkey.pusher)
590 if(lostkey.pusher.team != loser_team)
591 if(IS_PLAYER(lostkey.pusher))
592 attacker = lostkey.pusher;
593
594 if(attacker)
595 {
596 if(lostkey.previous_owner)
597 kh_Scores_Event(lostkey.previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
598 // don't actually GIVE them the -nn points, just log
600 GameRules_scoring_add(attacker, KH_PUSHES, 1);
601 //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
602 }
603 else
604 {
605 int players = 0;
607
608 FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; });
609
610 entity key;
611 int keys = 0;
612 FOR_EACH_KH_KEY(key)
613 if(key.owner && key.team != loser_team)
614 ++keys;
615
616 if(lostkey.previous_owner)
617 kh_Scores_Event(lostkey.previous_owner, NULL, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed);
618 // don't actually GIVE them the -nn points, just log
619
620 if(lostkey.previous_owner.playerid == lostkey.kh_previous_owner_playerid)
621 GameRules_scoring_add(lostkey.previous_owner, KH_DESTRUCTIONS, 1);
622
624
625 FOR_EACH_KH_KEY(key)
626 if(key.owner && key.team != loser_team)
627 {
628 f = DistributeEvenly_Get(of);
629 kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0);
630 }
631
632 int fragsleft = DistributeEvenly_Get(players);
633
634 // Now distribute these among all other teams...
635 int j = NumTeams(kh_teams) - 1;
636 for(int i = 0; i < NumTeams(kh_teams); ++i)
637 {
638 int thisteam = kh_Team_ByID(i);
639 if(thisteam == loser_team) // bad boy, no cookie - this WILL happen
640 continue;
641
642 players = 0;
643 FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; });
644
645 DistributeEvenly_Init(fragsleft, j);
646 fragsleft = DistributeEvenly_Get(j - 1);
648
649 FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, {
650 f = DistributeEvenly_Get(1);
651 kh_Scores_Event(it, NULL, "destroyed", f, 0);
652 });
653
654 --j;
655 }
656 }
657
658 int realteam = kh_Team_ByID(lostkey.count);
659 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS));
660 if(attacker)
661 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.previous_owner.netname);
662 else
663 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.previous_owner.netname);
664
665 play2all(SND(KH_DESTROY));
666 te_tarexplosion(lostkey.origin);
667
669}
670
671void kh_Key_Think(entity this) // runs all the time
672{
673 if(game_stopped)
674 return;
675
676 if(this.owner)
677 {
678#ifndef KH_PLAYER_USE_ATTACHMENT
679 makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED));
680 setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z);
681#endif
682 }
683
684 // if in nodrop or time over, end the round
685 if(!this.owner)
686 if(time > this.pain_finished)
687 kh_LoserTeam(this.team, this);
688
689 if(this.owner)
691 {
692 if(this.siren_time < time)
693 {
694 sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM); // play a simple alarm
695 this.siren_time = time + 2.5; // repeat every 2.5 seconds
696 }
697
698 entity key;
699 vector p = this.owner.origin;
700 FOR_EACH_KH_KEY(key)
701 if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist))
702 goto not_winning;
703 kh_WinnerTeam(this.team);
704LABEL(not_winning)
705 }
706
708 {
711 if(it.team == kh_interferemsg_team)
712 {
713 if(it.kh_next)
714 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET);
715 else
716 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP);
717 }
718 else
719 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE));
720 });
721 }
722
723 this.nextthink = time + 0.05;
724}
725
727{
728 kh_Key_AssignTo(this, NULL);
729 kh_Key_Remove(this);
730}
731
732void kh_Key_Spawn(entity initial_owner, float _angle, float i) // runs every time a new flag is created, ie after all the keys have been collected
733{
734 entity key = new(item_kh_key);
735 key.count = i;
738 key.nextthink = time;
739 key.cnt = _angle;
740 key.angles = '0 360 0' * random();
741 key.event_damage = kh_Key_Damage;
742 key.takedamage = DAMAGE_YES;
745 key.modelindex = kh_key_dropped;
746 key.model = "key";
747 key.kh_dropperteam = 0;
749 setsize(key, KH_KEY_MIN, KH_KEY_MAX);
750 key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS;
751 key.reset = key_reset;
752 navigation_dynamicgoal_init(key, false);
753
754 switch(initial_owner.team)
755 {
756 case NUM_TEAM_1:
757 key.netname = "^1red key";
758 break;
759 case NUM_TEAM_2:
760 key.netname = "^4blue key";
761 break;
762 case NUM_TEAM_3:
763 key.netname = "^3yellow key";
764 break;
765 case NUM_TEAM_4:
766 key.netname = "^6pink key";
767 break;
768 default:
769 key.netname = "NETGIER key";
770 break;
771 }
772
773 // link into key list
774 key.kh_worldkeynext = kh_worldkeylist;
775 kh_worldkeylist = key;
776
777 Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM(initial_owner.team, CENTER_KEYHUNT_START));
778
779 WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG);
780 key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player;
781
782 kh_Key_AssignTo(key, initial_owner);
783}
784
785// -1 when no team completely owns all keys yet
786int kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team
787{
788 entity key;
789 int teem = -1;
790 int keys = NumTeams(kh_teams);
791 FOR_EACH_KH_KEY(key)
792 {
793 if(!key.owner)
794 return -1;
795 if(teem == -1)
796 teem = key.team;
797 else if(teem != key.team)
798 return -1;
799 --keys;
800 }
801 if(keys != 0)
802 return -1;
803 return teem;
804}
805
807{
808 // prevent collecting this one for some time
809 entity player = key.owner;
810
811 key.kh_droptime = time;
812 key.enemy = player;
813
814 kh_Scores_Event(player, key, "dropkey", 0, 0);
815 GameRules_scoring_add(player, KH_LOSSES, 1);
816 int realteam = kh_Team_ByID(key.count);
817 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname);
818
819 kh_Key_AssignTo(key, NULL);
820 makevectors(player.v_angle);
821 key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
822 key.pusher = NULL;
824 key.kh_dropperteam = key.team;
825
826 sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
827}
828
829void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies
830{
831 if(player.kh_next)
832 {
833 entity mypusher = NULL;
834 if(player.pusher)
835 if(time < player.pushltime)
836 mypusher = player.pusher;
837
838 entity key;
839 while((key = player.kh_next))
840 {
841 kh_Scores_Event(player, key, "losekey", 0, 0);
842 GameRules_scoring_add(player, KH_LOSSES, 1);
843 int realteam = kh_Team_ByID(key.count);
844 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname);
845 kh_Key_AssignTo(key, NULL);
846 makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
847 key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false);
848 key.pusher = mypusher;
850 if(suicide)
851 key.kh_dropperteam = player.team;
852 }
853 sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
854 }
855}
856
858{
859 int missing_teams = 0;
860 for(int i = 0; i < NumTeams(kh_teams); ++i)
861 {
862 int teem = kh_Team_ByID(i);
863 int players = 0;
865 if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
866 ++players;
867 });
868 if (!players)
869 missing_teams |= BIT(i);
870 }
871 return missing_teams;
872}
873
874void kh_WaitForPlayers() // delay start of the round until enough players are present
875{
876 static int prev_missing_teams_mask;
878 if(time < game_starttime)
879 {
881 return;
882 }
883
885 {
886 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
888 }
889 else
890 {
891 if(player_count != 0)
892 {
893 if(prev_missing_teams_mask != missing_teams_mask)
894 prev_missing_teams_mask = missing_teams_mask;
895 }
897 }
898}
899
900void kh_EnableTrackingDevice() // runs after each round
901{
902 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
903 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
904
905 kh_tracking_enabled = true;
906}
907
908void kh_StartRound() // runs at the start of each round
909{
910 if(time < game_starttime)
911 {
913 return;
914 }
915
917 {
919 return;
920 }
921
922 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
923 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
924
925 for(int i = 0; i < NumTeams(kh_teams); ++i)
926 {
927 int teem = kh_Team_ByID(i);
928 int players = 0;
929 entity my_player = NULL;
931 if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
932 {
933 ++players;
934 if(random() * players <= 1)
935 my_player = it;
936 }
937 });
938 kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i);
939 }
940
941 kh_tracking_enabled = false;
943 {
944 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking);
946 }
947}
948
949float kh_HandleFrags(entity attacker, entity targ, float f) // adds to the player score
950{
951 if(attacker == targ)
952 return f;
953
954 if(targ.kh_next)
955 {
956 if(attacker.team == targ.team)
957 {
958 int nk = 0;
959 for(entity k = targ.kh_next; k != NULL; k = k.kh_next)
960 ++nk;
961 kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0);
962 }
963 else
964 {
965 kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0);
966 GameRules_scoring_add(attacker, KH_KCKILLS, 1);
967 // the frag gets added later
968 }
969 }
970
971 return f;
972}
973
974void kh_Initialize() // sets up th KH environment
975{
976 // setup variables
978 if(kh_teams < 2)
979 kh_teams = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame
980 kh_teams = BITS(bound(2, kh_teams, 4));
981
982 // use a temp entity to avoid linking kh_controller to the world with setmodel
983 entity tmp_ent = spawn();
984 setmodel(tmp_ent, MDL_KH_KEY);
985 kh_key_dropped = tmp_ent.modelindex;
986
987#ifdef KH_PLAYER_USE_CARRIEDMODEL
988 setmodel(tmp_ent, MDL_KH_KEY_CARRIED);
989 kh_key_carried = tmp_ent.modelindex;
990#else
992#endif
993
994 delete(tmp_ent);
995
996 // make a KH entity for controlling the game
1000
1002}
1003
1005{
1006 // to be called before intermission
1008 delete(kh_controller);
1010}
1011
1012// legacy bot role
1013
1018
1019
1021{
1022 entity key_wp = e.waypointsprite_attachedforcarrier;
1023 if (!key_wp)
1024 if (!key_wp.waypointsprite_visible_for_player(key_wp, this, WaypointSprite_getviewentity(this)))
1025 { // has no waypoint, or waypoint not visible
1026 if (!checkpvs(this.origin + this.view_ofs, e)) // key cannot be seen
1027 return false;
1028 }
1029 return true;
1030}
1031
1032void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
1033{
1034 for (entity head = kh_worldkeylist; head; head = head.kh_worldkeynext)
1035 {
1036 if (head.owner)
1037 {
1038 if (head.owner == this || !kh_waypointsprite_visible_for_bot(this, head.owner))
1039 continue;
1040 if (SAME_TEAM(head, this))
1041 navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000);
1042 else
1043 navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000);
1044 }
1045 else
1046 {
1047 if (!kh_waypointsprite_visible_for_bot(this, head))
1048 continue;
1049 navigation_routerating(this, head, ratingscale_dropped * 10000, 100000);
1050 }
1051 }
1052
1053 havocbot_goalrating_items(this, 80000, this.origin, 10000);
1054}
1055
1057{
1058 if(IS_DEAD(this))
1059 return;
1060
1061 if (!(this.kh_next))
1062 {
1063 LOG_TRACE("changing role to freelancer");
1064 this.havocbot_role = havocbot_role_kh_freelancer;
1065 this.havocbot_role_timeout = 0;
1066 return;
1067 }
1068
1070 {
1072
1073 if(kh_Key_AllOwnedByWhichTeam() == this.team)
1074 havocbot_goalrating_kh(this, 10, 0.1, 0.05); // bring home
1075 else
1076 havocbot_goalrating_kh(this, 4, 4, 0.5); // play defensively
1077
1079
1081 }
1082}
1083
1085{
1086 if(IS_DEAD(this))
1087 return;
1088
1089 if (this.kh_next)
1090 {
1091 LOG_TRACE("changing role to carrier");
1092 this.havocbot_role = havocbot_role_kh_carrier;
1093 this.havocbot_role_timeout = 0;
1094 return;
1095 }
1096
1097 if (!this.havocbot_role_timeout)
1098 this.havocbot_role_timeout = time + random() * 10 + 20;
1099 if (time > this.havocbot_role_timeout)
1100 {
1101 LOG_TRACE("changing role to freelancer");
1102 this.havocbot_role = havocbot_role_kh_freelancer;
1103 this.havocbot_role_timeout = 0;
1104 return;
1105 }
1106
1108 {
1109 float key_owner_team;
1111
1112 key_owner_team = kh_Key_AllOwnedByWhichTeam();
1113 if(key_owner_team == this.team)
1114 havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend key carriers
1115 else if(key_owner_team == -1)
1116 havocbot_goalrating_kh(this, 4, 1, 0.05); // play defensively
1117 else
1118 havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
1119
1121
1123 }
1124}
1125
1127{
1128 if(IS_DEAD(this))
1129 return;
1130
1131 if (this.kh_next)
1132 {
1133 LOG_TRACE("changing role to carrier");
1134 this.havocbot_role = havocbot_role_kh_carrier;
1135 this.havocbot_role_timeout = 0;
1136 return;
1137 }
1138
1139 if (!this.havocbot_role_timeout)
1140 this.havocbot_role_timeout = time + random() * 10 + 20;
1141 if (time > this.havocbot_role_timeout)
1142 {
1143 LOG_TRACE("changing role to freelancer");
1144 this.havocbot_role = havocbot_role_kh_freelancer;
1145 this.havocbot_role_timeout = 0;
1146 return;
1147 }
1148
1150 {
1151 float key_owner_team;
1152
1154
1155 key_owner_team = kh_Key_AllOwnedByWhichTeam();
1156 if(key_owner_team == this.team)
1157 havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
1158 else if(key_owner_team == -1)
1159 havocbot_goalrating_kh(this, 0.1, 1, 2); // play offensively
1160 else
1161 havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK! EMERGENCY!
1162
1164
1166 }
1167}
1168
1170{
1171 if(IS_DEAD(this))
1172 return;
1173
1174 if (this.kh_next)
1175 {
1176 LOG_TRACE("changing role to carrier");
1177 this.havocbot_role = havocbot_role_kh_carrier;
1178 this.havocbot_role_timeout = 0;
1179 return;
1180 }
1181
1182 if (!this.havocbot_role_timeout)
1183 this.havocbot_role_timeout = time + random() * 10 + 10;
1184 if (time > this.havocbot_role_timeout)
1185 {
1186 if (random() < 0.5)
1187 {
1188 LOG_TRACE("changing role to offense");
1189 this.havocbot_role = havocbot_role_kh_offense;
1190 }
1191 else
1192 {
1193 LOG_TRACE("changing role to defense");
1194 this.havocbot_role = havocbot_role_kh_defense;
1195 }
1196 this.havocbot_role_timeout = 0;
1197 return;
1198 }
1199
1201 {
1203
1204 int key_owner_team = kh_Key_AllOwnedByWhichTeam();
1205 if(key_owner_team == this.team)
1206 havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
1207 else if(key_owner_team == -1)
1208 havocbot_goalrating_kh(this, 1, 10, 2); // prefer dropped keys
1209 else
1210 havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
1211
1213
1215 }
1216}
1217
1218
1219// register this as a mutator
1220
1221MUTATOR_HOOKFUNCTION(kh, Damage_Calculate) // for changing damage and force values that are applied to players
1222{
1223 entity frag_attacker = M_ARGV(1, entity);
1225
1226 // as a gametype rule, only apply scaling to player versus player combat
1227 if (!IS_PLAYER(frag_attacker) || !IS_PLAYER(frag_target))
1228 return;
1229
1230 if (frag_attacker.kh_next != NULL) // if the attacker is a key carrier
1231 {
1232 if (frag_target == frag_attacker) // damage done to themselves
1233 {
1236 }
1237 else if (frag_target.kh_next != NULL) // damage done to other key carriers
1238 {
1241 }
1242 else // damage done to noncarriers
1243 {
1246 }
1247 }
1248 else
1249 {
1250 if (frag_target == frag_attacker) // damage done to themselves
1251 {
1254 }
1255 else if (frag_target.kh_next != NULL) // damage done to key carriers
1256 {
1259 }
1260 else // damage done to other noncarriers
1261 {
1264 }
1265 }
1266}
1267
1269{
1270 entity player = M_ARGV(0, entity);
1271
1272 kh_Key_DropAll(player, true);
1273}
1274
1275MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
1276{
1277 entity player = M_ARGV(0, entity);
1278
1279 kh_Key_DropAll(player, true);
1280}
1281
1282MUTATOR_HOOKFUNCTION(kh, PlayerDies)
1283{
1284 entity frag_attacker = M_ARGV(1, entity);
1286
1287 if(frag_target == frag_attacker)
1289 else if(IS_PLAYER(frag_attacker))
1291 else
1293}
1294
1295MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
1296{
1297 entity frag_attacker = M_ARGV(0, entity);
1299 float frag_score = M_ARGV(2, float);
1300 M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score);
1301}
1302
1303MUTATOR_HOOKFUNCTION(kh, MatchEnd)
1304{
1305 kh_finalize();
1306}
1307
1309{
1310 M_ARGV(0, float) = kh_teams;
1311 return true;
1312}
1313
1315{
1316 entity player = M_ARGV(0, entity);
1317
1318 if(MUTATOR_RETURNVALUE == 0)
1319 {
1320 entity k = player.kh_next;
1321 if(k)
1322 {
1323 kh_Key_DropOne(k);
1324 return true;
1325 }
1326 }
1327}
1328
1329MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
1330{
1331 entity bot = M_ARGV(0, entity);
1332
1333 if(IS_DEAD(bot))
1334 return true;
1335
1336 float r = random() * 3;
1337 if (r < 1)
1338 bot.havocbot_role = havocbot_role_kh_offense;
1339 else if (r < 2)
1340 bot.havocbot_role = havocbot_role_kh_defense;
1341 else
1342 bot.havocbot_role = havocbot_role_kh_freelancer;
1343
1344 return true;
1345}
1346
1347MUTATOR_HOOKFUNCTION(kh, LogDeath_AppendItemCodes)
1348{
1349 entity player = M_ARGV(0, entity);
1350 if(player.kh_next)
1351 M_ARGV(1, string) = strcat(M_ARGV(1, string), "K"); // item codes
1352}
1353
1354MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
1355{
1357
1359}
1360
1361MUTATOR_HOOKFUNCTION(kh, reset_map_global)
1362{
1363 kh_WaitForPlayers(); // takes care of killing the "missing teams" message
1364}
void navigation_goalrating_start(entity this)
void navigation_goalrating_timeout_set(entity this)
Definition navigation.qc:20
bool navigation_goalrating_timeout(entity this)
Definition navigation.qc:44
void navigation_dynamicgoal_init(entity this, bool initially_static)
Definition navigation.qc:77
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_end(entity this)
float havocbot_role_timeout
Definition api.qh:46
int player_count
Definition api.qh:103
void navigation_dynamicgoal_unset(entity this)
Definition navigation.qc:96
const int CBC_ORDER_FIRST
Definition base.qh:10
#define MUTATOR_HOOKFUNCTION(...)
Definition base.qh:335
const int CBC_ORDER_EXCLUSIVE
Definition base.qh:12
#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 BITS(n)
Definition bits.qh:9
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float pain_finished
float cnt
Definition powerups.qc:24
entity players
Definition main.qh:57
entity owner
Definition main.qh:87
bool warmup_stage
Definition main.qh:120
int team
Definition main.qh:188
entity teams
Definition main.qh:58
#define colormapPaletteColor(c, isPants)
Definition color.qh:5
#define setmodel(this, m)
Definition model.qh:26
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_DEAD(s)
Definition player.qh:245
#define PHYS_INPUT_BUTTON_CHAT(s)
Definition player.qh:159
#define IS_PLAYER(s)
Definition player.qh:243
const int SFL_LOWER_IS_BETTER
Lower scores are better (e.g.
Definition scores.qh:102
const int SFL_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
int missing_teams_mask
Definition stats.qh:85
float game_starttime
Definition stats.qh:82
float game_stopped
Definition stats.qh:81
#define LABEL(id)
Definition compiler.qh:34
const int FL_ITEM
Definition constants.qh:77
vector v_up
float DPCONTENTS_BOTCLIP
const float SOLID_TRIGGER
float DPCONTENTS_SOLID
float DPCONTENTS_BODY
const float SOLID_NOT
float DPCONTENTS_PLAYERCLIP
float time
vector v_right
float checkpvs(vector viewpos, entity viewee)
float nextthink
vector v_forward
vector origin
#define spawn
void Send_Effect(entity eff, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:124
void GameLogEcho(string s)
Definition gamelog.qc:15
bool autocvar_sv_eventlog
Definition gamelog.qh:3
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 FOREACH(list, cond, body)
Definition iter.qh:19
float pushltime
Definition jumppads.qh:21
#define ClientDisconnect
Definition _all.inc:242
#define STAT(...)
Definition stats.qh:82
#define LOG_TRACE(...)
Definition log.qh:76
float ceil(float f)
float bound(float min, float value, float max)
float cvar(string name)
float random(void)
string ftos(float f)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_NONE
Definition movetypes.qh:129
const int MOVETYPE_TOSS
Definition movetypes.qh:135
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
void Kill_Notification(NOTIF broadcast, entity client, MSG net_type, CPID net_cpid)
Definition all.qc:1537
#define APP_TEAM_NUM(num, prefix)
Definition all.qh:84
#define new_pure(class)
purely logical entities (not linked to the area grid)
Definition oo.qh:67
#define NULL
Definition post.qh:14
#define makevectors
Definition post.qh:21
vector view_ofs
Definition progsdefs.qc:151
ERASEABLE void DistributeEvenly_Init(float amount, float totalweight)
Definition random.qc:42
ERASEABLE float DistributeEvenly_Get(float weight)
Definition random.qc:52
int NumTeams(int teams)
#define setthink(e, f)
#define getthink(e)
vector
Definition self.qh:92
entity entity toucher
Definition self.qh:72
void
Definition self.qh:72
#define settouch(e, f)
Definition self.qh:73
void PlayerUseKey(entity this)
Definition client.qc:2584
#define IS_INDEPENDENT_PLAYER(e)
Definition client.qh:312
IntrusiveList g_items
Definition items.qh:125
#define ITEM_TOUCH_NEEDKILL()
Definition items.qh:128
#define ITEM_DAMAGE_NEEDKILL(dt)
Definition items.qh:129
const int CH_TRIGGER
Definition sound.qh:12
const float VOL_BASE
Definition sound.qh:36
const float ATTEN_NORM
Definition sound.qh:30
#define sound(e, c, s, v, a)
Definition sound.qh:52
void play2all(string samp)
Definition all.qc:142
#define SND(id)
Definition all.qh:35
const int DAMAGE_YES
Definition subs.qh:80
const int DAMAGE_NO
Definition subs.qh:79
float float2int_decimal_fld
entity frag_target
Definition sv_ctf.qc:2321
entity enemy
Definition sv_ctf.qh:153
entity previous_owner
void kh_EnableTrackingDevice()
entity kh_next
Definition sv_keyhunt.qc:80
int kh_Team_ByID(int t)
Definition sv_keyhunt.qc:67
void havocbot_role_kh_offense(entity this)
int kh_GetMissingTeams()
void key_reset(entity this)
void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
void kh_Key_Spawn(entity initial_owner, float _angle, float i)
int kh_interferemsg_team
Definition sv_keyhunt.qc:78
int kh_Key_AllOwnedByWhichTeam()
bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view)
const float KH_KEY_ZSHIFT
Definition sv_keyhunt.qc:48
void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner)
int autocvar_g_balance_keyhunt_score_destroyed
Definition sv_keyhunt.qc:29
void havocbot_role_kh_carrier(entity this)
void kh_ScoreRules(int teams)
Definition sv_keyhunt.qc:91
void kh_LoserTeam(int loser_team, entity lostkey)
void kh_Key_DropOne(entity key)
float autocvar_g_balance_keyhunt_delay_collect
Definition sv_keyhunt.qc:11
int autocvar_g_balance_keyhunt_score_collect
Definition sv_keyhunt.qc:28
void kh_Key_Think(entity this)
float autocvar_g_balance_keyhunt_delay_tracking
Definition sv_keyhunt.qc:15
int autocvar_g_keyhunt_teams_override
Definition sv_keyhunt.qc:35
void kh_Controller_Think(entity this)
const float KH_KEY_BRIGHTNESS
Definition sv_keyhunt.qc:56
int kh_key_dropped
Definition sv_keyhunt.qc:86
bool kh_no_radar_circles
Definition sv_keyhunt.qc:58
const vector KH_KEY_MAX
Definition sv_keyhunt.qc:55
float kh_droptime
Definition sv_keyhunt.qc:81
int kh_previous_owner_playerid
Definition sv_keyhunt.qc:84
float autocvar_g_balance_keyhunt_delay_return
Definition sv_keyhunt.qc:13
void kh_Controller_SetThink(float t, kh_Think_t func)
void kh_Key_AssignTo(entity key, entity player)
float autocvar_g_balance_keyhunt_protecttime
Definition sv_keyhunt.qc:19
float autocvar_g_balance_keyhunt_return_when_unreachable
Definition sv_keyhunt.qc:16
void kh_Key_Detach(entity key)
float autocvar_g_balance_keyhunt_delay_damage_return
Definition sv_keyhunt.qc:12
void nades_GiveBonus(entity player, float score)
Definition sv_nades.qc:434
float kh_interferemsg_time
Definition sv_keyhunt.qc:79
const float KH_KEY_WP_ZSHIFT
Definition sv_keyhunt.qc:52
entity kh_prev
Definition sv_keyhunt.qc:80
int kh_dropperteam
Definition sv_keyhunt.qc:82
int kh_key_carried
Definition sv_keyhunt.qc:86
void kh_Key_Remove(entity key)
void kh_finalize()
const float KH_KEY_XYSPEED
Definition sv_keyhunt.qc:50
vector autocvar_g_balance_keyhunt_noncarrier_force
Definition sv_keyhunt.qc:24
void kh_Key_Collect(entity key, entity player)
bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view)
float autocvar_g_balance_keyhunt_delay_round
Definition sv_keyhunt.qc:14
void kh_Key_Touch(entity this, entity toucher)
const int ST_KH_CAPS
Definition sv_keyhunt.qc:90
float siren_time
Definition sv_keyhunt.qc:65
var kh_Think_t kh_Controller_Thinkfunc
vector autocvar_g_balance_keyhunt_noncarrier_damage
Definition sv_keyhunt.qc:23
vector autocvar_g_balance_keyhunt_carrier_damage
Definition sv_keyhunt.qc:21
void havocbot_role_kh_freelancer(entity this)
vector kh_AttachedOrigin(entity e)
void kh_Initialize()
bool kh_waypointsprite_visible_for_bot(entity this, entity e)
void kh_Key_Attach(entity key)
float autocvar_g_balance_keyhunt_damageforcescale
Definition sv_keyhunt.qc:10
float autocvar_g_balance_keyhunt_maxdist
Definition sv_keyhunt.qc:18
void kh_WaitForPlayers()
void kh_StartRound()
float autocvar_g_balance_keyhunt_dropvelocity
Definition sv_keyhunt.qc:17
void kh_Key_DropAll(entity player, float suicide)
vector autocvar_g_balance_keyhunt_carrier_force
Definition sv_keyhunt.qc:22
int kh_teams
Definition sv_keyhunt.qc:77
void kh_WinnerTeam(int winner_team)
float kh_HandleFrags(entity attacker, entity targ, float f)
float autocvar_g_balance_keyhunt_throwvelocity
Definition sv_keyhunt.qc:32
entity kh_controller
Definition sv_keyhunt.qc:76
int autocvar_g_balance_keyhunt_score_capture
Definition sv_keyhunt.qc:26
void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
void havocbot_role_kh_defense(entity this)
int autocvar_g_balance_keyhunt_score_carrierfrag
Definition sv_keyhunt.qc:27
void kh_update_state()
const vector KH_KEY_MIN
Definition sv_keyhunt.qc:54
int autocvar_g_balance_keyhunt_score_push
Definition sv_keyhunt.qc:31
int autocvar_g_balance_keyhunt_score_destroyed_ownfactor
Definition sv_keyhunt.qc:30
void kh_FinishRound()
const float KH_KEY_XYDIST
Definition sv_keyhunt.qc:49
entity kh_worldkeylist
Definition sv_keyhunt.qh:24
void() kh_Think_t
Definition sv_keyhunt.qh:35
bool kh_tracking_enabled
Definition sv_keyhunt.qh:32
#define FOR_EACH_KH_KEY(v)
Definition sv_keyhunt.qh:27
int autocvar_g_nades_bonus_score_high
Definition sv_nades.qh:38
void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius)
Definition roles.qc:106
#define GameRules_scoring_add_team_float2int(client, fld, value, float_field, score_factor)
Definition sv_rules.qh:87
#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
vector Team_ColorRGB(int teamid)
Definition teams.qh:76
const int NUM_TEAM_3
Definition teams.qh:15
const int NUM_TEAM_1
Definition teams.qh:13
vector W_CalculateProjectileVelocity(entity actor, vector pvelocity, vector mvelocity, float forceAbsolute)
Definition tracing.qc:176
#define IS_INVISIBLE(v)
Definition utils.qh:27
#define IS_SPEC(v)
Definition utils.qh:10
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:50
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
void WaypointSprite_Kill(entity wp)
void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
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)
entity WaypointSprite_AttachCarrier(entity spr, entity carrier, entity icon)
void WaypointSprite_DetachCarrier(entity carrier)
void WaypointSprite_UpdateRule(entity e, float t, float r)
entity waypointsprite_attachedforcarrier
const int SPRITERULE_TEAMPLAY