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