]> git.xonotic.org Git - voretournament/voretournament.git/blob - data/qcsrc/server/keyhunt.qc
Get VoreTournament code to compile with gmqcc. To be compiled with the same parameter...
[voretournament/voretournament.git] / data / qcsrc / server / keyhunt.qc
1 #define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext )\r
2 \r
3 // #define KH_PLAYER_USE_ATTACHMENT\r
4 // #define KH_PLAYER_USE_CARRIEDMODEL\r
5 // #define KH_KEY_ATTACHMENT_DEBUG\r
6 \r
7 #ifdef KH_PLAYER_USE_ATTACHMENT\r
8 vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0';\r
9 vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0';\r
10 vector KH_PLAYER_ATTACHMENT = '0 0 0';\r
11 vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0';\r
12 string KH_PLAYER_ATTACHMENT_BONE = "";\r
13 #else\r
14 float KH_KEY_ZSHIFT = 22;\r
15 float KH_KEY_XYDIST = 24;\r
16 float KH_KEY_XYSPEED = 45;\r
17 #endif\r
18 float KH_KEY_WP_ZSHIFT = 20;\r
19 \r
20 vector KH_KEY_MIN = '-10 -10 -46';\r
21 vector KH_KEY_MAX = '10 10 3';\r
22 float KH_KEY_BRIGHTNESS = 2;\r
23 \r
24 string kh_Controller_Waitmsg;\r
25 float kh_no_radar_circles;\r
26 \r
27 // kh_state\r
28 //     bits  0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self\r
29 //     bits  5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self\r
30 //     bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self\r
31 //     bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self\r
32 .float kh_state;\r
33 .float siren_time;  //  time delay the siren\r
34 //.float stuff_time;  //  time delay to stuffcmd a cvar\r
35 \r
36 // sigh GMQCC needs to implement this yet.\r
37 float test[17]\r
38 #ifndef GMQCC\r
39 = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};\r
40 #else\r
41 ;\r
42 #endif\r
43 \r
44 //test[0] = status of dropped keys, test[1 - 16] = player #\r
45 //replace 17 with cvar("maxplayers") or similar !!!!!!!!!\r
46 //for(i = 0; i < maxplayers; ++i)\r
47 //      test[i] = "0";\r
48 \r
49 float kh_Team_ByID(float t)\r
50 {\r
51         if(t == 0) return COLOR_TEAM1;\r
52         if(t == 1) return COLOR_TEAM2;\r
53         if(t == 2) return COLOR_TEAM3;\r
54         if(t == 3) return COLOR_TEAM4;\r
55         return 0;\r
56 }\r
57 \r
58 entity kh_worldkeylist;\r
59 .entity kh_worldkeynext;\r
60 entity kh_controller;\r
61 float kh_tracking_enabled;\r
62 float kh_teams;\r
63 float kh_interferemsg_time, kh_interferemsg_team;\r
64 .entity kh_next, kh_prev; // linked list\r
65 .float kh_droptime;\r
66 .float kh_dropperteam;\r
67 .entity kh_previous_owner;\r
68 .float kh_previous_owner_playerid;\r
69 \r
70 string kh_sound_capture = "kh/capture.wav";\r
71 string kh_sound_destroy = "kh/destroy.wav";\r
72 string kh_sound_drop = "kh/drop.wav";\r
73 string kh_sound_collect = "kh/collect.wav";\r
74 string kh_sound_alarm = "kh/alarm.wav";  // the new siren/alarm\r
75 \r
76 float kh_key_dropped, kh_key_carried;\r
77 \r
78 void kh_Controller_SetThink(float t, string msg, kh_Think_t func)  // runs occasionaly\r
79 {\r
80         kh_Controller_Thinkfunc = func;\r
81         kh_controller.cnt = ceil(t);\r
82         if(kh_Controller_Waitmsg != "")\r
83                 strunzone(kh_Controller_Waitmsg);\r
84         if(msg == "")\r
85                 kh_Controller_Waitmsg = "";\r
86         else\r
87                 kh_Controller_Waitmsg = strzone(msg);\r
88         if(t == 0)\r
89                 kh_controller.nextthink = time; // force\r
90 }\r
91 \r
92 void kh_Controller_Think()  // called a lot\r
93 {\r
94         entity e;\r
95         if(intermission_running)\r
96                 return;\r
97         if(self.cnt > 0)\r
98         {\r
99                 if(kh_Controller_Waitmsg != "")\r
100                 {\r
101                         string s;\r
102                         if(substring(kh_Controller_Waitmsg, strlen(kh_Controller_Waitmsg)-1, 1) == " ")\r
103                                 s = strcat(kh_Controller_Waitmsg, ftos(self.cnt));\r
104                         else\r
105                                 s = kh_Controller_Waitmsg;\r
106 \r
107                         //dprint(s, "\n");\r
108 \r
109                         FOR_EACH_PLAYER(e)\r
110                                 if(clienttype(e) == CLIENTTYPE_REAL)\r
111                                         centerprint_atprio(e, CENTERPRIO_SPAM, s);\r
112                 }\r
113                 self.cnt -= 1;\r
114         }\r
115         else if(self.cnt == 0)\r
116         {\r
117                 self.cnt -= 1;\r
118                 kh_Controller_Thinkfunc();\r
119         }\r
120         self.nextthink = time + 1;\r
121 }\r
122 \r
123 // frags f: take from cvar * f\r
124 // frags 0: no frags\r
125 void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner)  // update the score when a key is captured\r
126 {\r
127         string s;\r
128         if(intermission_running)\r
129                 return;\r
130 \r
131         if(frags_player)\r
132                 UpdateFrags(player, frags_player);\r
133 \r
134         if(key && key.owner && frags_owner)\r
135                 UpdateFrags(key.owner, frags_owner);\r
136 \r
137         if(!cvar("sv_eventlog"))  //output extra info to the console or text file\r
138                 return;\r
139 \r
140         s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player));\r
141 \r
142         if(key && key.owner)\r
143                 s = strcat(s, ":", ftos(key.owner.playerid));\r
144         else\r
145                 s = strcat(s, ":0");\r
146 \r
147         s = strcat(s, ":", ftos(frags_owner), ":");\r
148 \r
149         if(key)\r
150                 s = strcat(s, key.netname);\r
151 \r
152         GameLogEcho(s);\r
153 }\r
154 \r
155 vector kh_AttachedOrigin(entity e)  // runs when a team captures the flag, it can run 2 or 3 times.\r
156 {\r
157         if(e.tag_entity)\r
158         {\r
159                 makevectors(e.tag_entity.angles);\r
160                 return e.tag_entity.origin + e.origin_x * v_forward - e.origin_y * v_right + e.origin_z * v_up;\r
161         }\r
162         else\r
163                 return e.origin;\r
164 }\r
165 \r
166 void 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\r
167 {\r
168 #ifdef KH_PLAYER_USE_ATTACHMENT\r
169         entity first;\r
170         first = key.owner.kh_next;\r
171         if(key == first)\r
172         {\r
173                 setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE);\r
174                 if(key.kh_next)\r
175                 {\r
176                         setattachment(key.kh_next, key, "");\r
177                         setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);\r
178                         setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED);\r
179                         key.kh_next.angles = '0 0 0';\r
180                 }\r
181                 else\r
182                         setorigin(key, KH_PLAYER_ATTACHMENT);\r
183                 key.angles = KH_PLAYER_ATTACHMENT_ANGLES;\r
184         }\r
185         else\r
186         {\r
187                 setattachment(key, key.kh_prev, "");\r
188                 if(key.kh_next)\r
189                         setattachment(key.kh_next, key, "");\r
190                 setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED);\r
191                 setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);\r
192                 key.angles = '0 0 0';\r
193         }\r
194 #else\r
195         setattachment(key, key.owner, "");\r
196         setorigin(key, '0 0 1' * KH_KEY_ZSHIFT);  // fixing x, y in think\r
197         key.angles_y -= key.owner.angles_y;\r
198 #endif\r
199         key.flags = 0;\r
200         key.solid = SOLID_NOT;\r
201         key.movetype = MOVETYPE_NONE;\r
202         key.team = key.owner.team;\r
203         key.nextthink = time;\r
204         key.damageforcescale = 0;\r
205         key.takedamage = DAMAGE_NO;\r
206         key.modelindex = kh_key_carried;\r
207 }\r
208 \r
209 void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured\r
210 {\r
211 #ifdef KH_PLAYER_USE_ATTACHMENT\r
212         entity first;\r
213         first = key.owner.kh_next;\r
214         if(key == first)\r
215         {\r
216                 if(key.kh_next)\r
217                 {\r
218                         setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE);\r
219                         setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);\r
220                         key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES;\r
221                 }\r
222         }\r
223         else\r
224         {\r
225                 if(key.kh_next)\r
226                         setattachment(key.kh_next, key.kh_prev, "");\r
227                 setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);\r
228         }\r
229         // in any case:\r
230         setattachment(key, world, "");\r
231         setorigin(key, key.owner.origin + '0 0 1' * (PL_MIN_z - KH_KEY_MIN_z));\r
232         key.angles = key.owner.angles;\r
233 #else\r
234         setorigin(key, key.owner.origin + key.origin_z * '0 0 1');\r
235         setattachment(key, world, "");\r
236         key.angles_y += key.owner.angles_y;\r
237 #endif\r
238         key.flags = FL_ITEM;\r
239         key.solid = SOLID_TRIGGER;\r
240         key.movetype = MOVETYPE_TOSS;\r
241         key.pain_finished = time + cvar("g_balance_keyhunt_delay_return");\r
242         key.damageforcescale = cvar("g_balance_keyhunt_damageforcescale");\r
243         key.takedamage = DAMAGE_YES;\r
244         // let key.team stay\r
245         key.modelindex = kh_key_dropped;\r
246         key.kh_previous_owner = key.owner;\r
247         key.kh_previous_owner_playerid = key.owner.playerid;\r
248 }\r
249 \r
250 void kh_Key_AssignTo(entity key, entity player)  // runs every time a key is picked up or assigned. Runs prior to kh_key_attach\r
251 {\r
252         entity k;\r
253         float ownerteam0, ownerteam;\r
254         if(key.owner == player)\r
255                 return;\r
256 \r
257         ownerteam0 = kh_Key_AllOwnedByWhichTeam();\r
258 \r
259         if(key.owner)\r
260         {\r
261                 kh_Key_Detach(key);\r
262 \r
263                 // remove from linked list\r
264                 if(key.kh_next)\r
265                         key.kh_next.kh_prev = key.kh_prev;\r
266                 key.kh_prev.kh_next = key.kh_next;\r
267                 key.kh_next = world;\r
268                 key.kh_prev = world;\r
269 \r
270                 if(key.owner.kh_next == world)\r
271                 {\r
272                         // No longer a key carrier\r
273                         if(!kh_no_radar_circles)\r
274                                 WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier);\r
275                         WaypointSprite_DetachCarrier(key.owner);\r
276                 }\r
277         }\r
278 \r
279         key.owner = player;\r
280 \r
281         if(player)\r
282         {\r
283                 // insert into linked list\r
284                 key.kh_next = player.kh_next;\r
285                 key.kh_prev = player;\r
286                 player.kh_next = key;\r
287                 if(key.kh_next)\r
288                         key.kh_next.kh_prev = key;\r
289 \r
290                 float i;\r
291                 i = test[key.owner.playerid];\r
292                         if(key.netname == "^1red key")\r
293                                 i += 1;\r
294                         if(key.netname == "^4blue key")\r
295                                 i += 2;\r
296                         if(key.netname == "^3yellow key")\r
297                                 i += 4;\r
298                         if(key.netname == "^6pink key")\r
299                                 i += 8;\r
300                 test[key.owner.playerid] = i;\r
301 \r
302                 kh_Key_Attach(key);\r
303 \r
304                 if(key.kh_next == world)\r
305                 {\r
306                         // player is now a key carrier\r
307                         WaypointSprite_AttachCarrier("", player);\r
308                         player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player;\r
309                         WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY);\r
310                         if(player.team == COLOR_TEAM1)\r
311                                 WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, "keycarrier-red", "keycarrier-friend", "keycarrier-red");\r
312                         else if(player.team == COLOR_TEAM2)\r
313                                 WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, "keycarrier-blue", "keycarrier-friend", "keycarrier-blue");\r
314                         else if(player.team == COLOR_TEAM3)\r
315                                 WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, "keycarrier-yellow", "keycarrier-friend", "keycarrier-yellow");\r
316                         else if(player.team == COLOR_TEAM4)\r
317                                 WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, "keycarrier-pink", "keycarrier-friend", "keycarrier-pink");\r
318                         WaypointSprite_UpdateRadar(player.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, colormapPaletteColor(player.team - 1, 0));\r
319                         if(!kh_no_radar_circles)\r
320                                 WaypointSprite_Ping(player.waypointsprite_attachedforcarrier);\r
321                 }\r
322         }\r
323 \r
324         // moved that here, also update if there's no player\r
325         kh_update_state();\r
326 \r
327         key.pusher = world;\r
328 \r
329         ownerteam = kh_Key_AllOwnedByWhichTeam();\r
330         if(ownerteam != ownerteam0)\r
331         {\r
332                 if(ownerteam != -1)\r
333                 {\r
334                         kh_interferemsg_time = time + 0.2;\r
335                         kh_interferemsg_team = player.team;\r
336 \r
337                         // audit all key carrier sprites, update them to RUN HERE\r
338                         FOR_EACH_KH_KEY(k)\r
339                         {\r
340                                 if(k.owner)\r
341                                         WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, k.owner.waypointsprite_attachedforcarrier.model1, "keycarrier-finish", k.owner.waypointsprite_attachedforcarrier.model3);\r
342                         }\r
343                 }\r
344                 else\r
345                 {\r
346                         kh_interferemsg_time = 0;\r
347 \r
348                         // audit all key carrier sprites, update them to RUN HERE\r
349                         FOR_EACH_KH_KEY(k)\r
350                         {\r
351                                 if(k.owner)\r
352                                         WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, k.owner.waypointsprite_attachedforcarrier.model1, "keycarrier-friend", k.owner.waypointsprite_attachedforcarrier.model3);\r
353                         }\r
354                 }\r
355         }\r
356 }\r
357 \r
358 void kh_Key_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)\r
359 {\r
360         if(self.owner)\r
361                 return;\r
362         if(vlen(force) <= 0)\r
363                 return;\r
364         if(time > self.pushltime)\r
365                 if(attacker.classname == "player")\r
366                         self.team = attacker.team;\r
367 }\r
368 \r
369 void key_reset()\r
370 {\r
371         kh_Key_AssignTo(self, world);\r
372         kh_Key_Remove(self);\r
373 }\r
374 \r
375 void 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\r
376 {\r
377         entity key;\r
378         key = spawn();\r
379         key.count = i;\r
380         key.classname = STR_ITEM_KH_KEY;\r
381         key.touch = kh_Key_Touch;\r
382         key.think = kh_Key_Think;\r
383         key.nextthink = time;\r
384         key.items = IT_KEY1 | IT_KEY2;\r
385         key.cnt = angle;\r
386         key.angles = '0 360 0' * random();\r
387         key.event_damage = kh_Key_Damage;\r
388         key.takedamage = DAMAGE_YES;\r
389         key.modelindex = kh_key_dropped;\r
390         key.model = "key";\r
391         key.kh_dropperteam = 0;\r
392         key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;\r
393         setsize(key, KH_KEY_MIN, KH_KEY_MAX);\r
394         key.colormod = TeamColor(initial_owner.team) * KH_KEY_BRIGHTNESS;\r
395         key.reset = key_reset;\r
396 \r
397         switch(initial_owner.team)\r
398         {\r
399                 case COLOR_TEAM1:\r
400                         key.netname = "^1red key";\r
401                         break;\r
402                 case COLOR_TEAM2:\r
403                         key.netname = "^4blue key";\r
404                         break;\r
405                 case COLOR_TEAM3:\r
406                         key.netname = "^3yellow key";\r
407                         break;\r
408                 case COLOR_TEAM4:\r
409                         key.netname = "^6pink key";\r
410                         break;\r
411                 default:\r
412                         key.netname = "NETGIER key";\r
413                         break;\r
414         }\r
415 \r
416         // link into key list\r
417         key.kh_worldkeynext = kh_worldkeylist;\r
418         kh_worldkeylist = key;\r
419 \r
420         centerprint(initial_owner, strcat("You are starting with the ", key.netname, "\n"));  // message to player at start of round\r
421 \r
422         WaypointSprite_Spawn("key-dropped", 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, world, key.team, key, waypointsprite_attachedforcarrier, FALSE);\r
423         key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player;\r
424         WaypointSprite_UpdateRadar(key.waypointsprite_attachedforcarrier, RADARICON_FLAG, '0 1 1');\r
425 \r
426         kh_Key_AssignTo(key, initial_owner);\r
427 }\r
428 \r
429 void 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\r
430 {\r
431         entity o;\r
432         o = key.owner;\r
433         kh_Key_AssignTo(key, world);\r
434         if(o) // it was attached\r
435                 WaypointSprite_Kill(key.waypointsprite_attachedforcarrier);\r
436         else // it was dropped\r
437                 WaypointSprite_DetachCarrier(key);\r
438 \r
439         // remove key from key list\r
440         if (kh_worldkeylist == key)\r
441                 kh_worldkeylist = kh_worldkeylist.kh_worldkeynext;\r
442         else\r
443         {\r
444                 o = kh_worldkeylist;\r
445                 while (o)\r
446                 {\r
447                         if (o.kh_worldkeynext == key)\r
448                         {\r
449                                 o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext;\r
450                                 break;\r
451                         }\r
452                         o = o.kh_worldkeynext;\r
453                 }\r
454         }\r
455 \r
456         remove(key);\r
457 \r
458         kh_update_state();\r
459 }\r
460 \r
461 // -1 when no team completely owns all keys yet\r
462 float kh_Key_AllOwnedByWhichTeam()  // constantly called. check to see if all the keys are owned by the same team\r
463 {\r
464         entity key;\r
465         float teem;\r
466         float keys;\r
467 \r
468         teem = -1;\r
469         keys = kh_teams;\r
470         FOR_EACH_KH_KEY(key)\r
471         {\r
472                 if(!key.owner)\r
473                         return -1;\r
474                 if(teem == -1)\r
475                         teem = key.team;\r
476                 else if(teem != key.team)\r
477                         return -1;\r
478                 --keys;\r
479         }\r
480         if(keys != 0)\r
481                 return -1;\r
482         return teem;\r
483 }\r
484 \r
485 void kh_Key_Collect(entity key, entity player)  //a player picks up a dropped key\r
486 {\r
487         sound(player, CHAN_AUTO, kh_sound_collect, VOL_BASE, ATTN_NORM);\r
488 \r
489         if(key.kh_dropperteam != player.team)\r
490         {\r
491                 kh_Scores_Event(player, key, "collect", cvar("g_balance_keyhunt_score_collect"), 0);\r
492                 PlayerScore_Add(player, SP_KH_PICKUPS, 1);\r
493         }\r
494         key.kh_dropperteam = 0;\r
495         bprint(player.netname, "^7 picked up the ", key.netname, "\n");\r
496 \r
497         kh_Key_AssignTo(key, player); // this also updates .kh_state\r
498 }\r
499 \r
500 void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies or gets eaten\r
501 {\r
502         entity key;\r
503         entity mypusher;\r
504         if(player.kh_next)\r
505         {\r
506                 mypusher = world;\r
507                 if(player.pusher)\r
508                         if(time < player.pushltime)\r
509                                 mypusher = player.pusher;\r
510                 while((key = player.kh_next))\r
511                 {\r
512                         kh_Scores_Event(player, key, "losekey", 0, 0);\r
513                         PlayerScore_Add(player, SP_KH_LOSSES, 1);\r
514                         bprint(player.netname, "^7 died and lost the ", key.netname, "\n");\r
515                         kh_Key_AssignTo(key, world);\r
516                         makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());\r
517                         key.velocity = W_CalculateProjectileVelocity(player.velocity, cvar("g_balance_keyhunt_dropvelocity") * v_forward);\r
518                         key.pusher = mypusher;\r
519                         key.pushltime = time + cvar("g_balance_keyhunt_protecttime");\r
520                         if(suicide)\r
521                                 key.kh_dropperteam = player.team;\r
522                 }\r
523                 sound(player, CHAN_AUTO, kh_sound_drop, VOL_BASE, ATTN_NORM);\r
524         }\r
525 }\r
526 \r
527 void kh_Key_Touch()  // runs many, many times when a key has been dropped and can be picked up\r
528 {\r
529         if(intermission_running)\r
530                 return;\r
531 \r
532         if(self.owner) // already carried\r
533                 return;\r
534         if(other.classname != "player")\r
535                 return;\r
536         if(other.deadflag != DEAD_NO)\r
537                 return;\r
538         if(other == self.enemy)\r
539                 if(time < self.kh_droptime + cvar("g_balance_keyhunt_delay_collect"))\r
540                         return;  // you just dropped it!\r
541         kh_Key_Collect(self, other);\r
542 }\r
543 \r
544 void kh_Key_Think()  // runs all the time\r
545 {\r
546         entity head;\r
547         //entity player;  // needed by FOR_EACH_PLAYER\r
548 \r
549         if(intermission_running)\r
550                 return;\r
551 \r
552 #ifdef KH_KEY_ATTACHMENT_DEBUG\r
553         if(self.kh_prev == self.owner)\r
554         {\r
555                 if(cvar_string("_angles") != "")\r
556                 {\r
557                         self.angles = stov(cvar_string("_angles"));\r
558                         setorigin(self, stov(cvar_string("_origin")));\r
559                 }\r
560         }\r
561 #endif\r
562 \r
563         if(self.owner)\r
564         {\r
565 #ifndef KH_PLAYER_USE_ATTACHMENT\r
566                 makevectors('0 1 0' * (self.cnt + mod(time, 360) * KH_KEY_XYSPEED));\r
567                 setorigin(self, v_forward * KH_KEY_XYDIST + '0 0 1' * self.origin_z);\r
568 #endif\r
569 \r
570                 if(self.owner.BUTTON_USE)\r
571                 if(time >= self.owner.kh_droptime + cvar("g_balance_keyhunt_delay_drop"))\r
572                 {\r
573                         self.owner.kh_droptime = time;\r
574                         self.kh_droptime = time;  // prevent collecting this one for some time\r
575                         self.enemy = self.owner;\r
576                         self.pusher = world;\r
577                         kh_Scores_Event(self.owner, self, "dropkey", 0, 0);\r
578                         bprint(self.owner.netname, "^7 dropped the ", self.netname, "\n");\r
579                         sound(self.owner, CHAN_AUTO, kh_sound_drop, VOL_BASE, ATTN_NORM);\r
580                         makevectors(self.owner.v_angle);\r
581                         self.velocity = W_CalculateProjectileVelocity(self.owner.velocity, cvar("g_balance_keyhunt_throwvelocity") * v_forward);\r
582                         kh_Key_AssignTo(self, world);\r
583                         self.pushltime = time + cvar("g_balance_keyhunt_protecttime");\r
584                         self.kh_dropperteam = self.team;\r
585                 }\r
586         }\r
587 \r
588         // if in nodrop or time over, end the round\r
589         if(!self.owner)\r
590                 if(time > self.pain_finished)\r
591                         kh_LoserTeam(self.team, self);\r
592 \r
593         if(self.owner)\r
594         if(kh_Key_AllOwnedByWhichTeam() != -1)\r
595         {\r
596                 if(self.siren_time < time)\r
597                 {\r
598                         sound(self.owner, CHAN_AUTO, kh_sound_alarm, VOL_BASE, ATTN_NORM);  // play a simple alarm\r
599                         self.siren_time = time + 2.5;  // repeat every 2.5 seconds\r
600                 }\r
601 \r
602                 entity key;\r
603                 vector p;\r
604                 p = self.owner.origin;\r
605                 FOR_EACH_KH_KEY(key)\r
606                         if(vlen(key.owner.origin - p) > cvar("g_balance_keyhunt_maxdist"))\r
607                                 goto not_winning;\r
608                 kh_WinnerTeam(self.team);\r
609 :not_winning\r
610         }\r
611 \r
612         if(kh_interferemsg_time && time > kh_interferemsg_time)\r
613         {\r
614                 kh_interferemsg_time = 0;\r
615                 FOR_EACH_PLAYER(head)\r
616                 {\r
617                         if(head.team == kh_interferemsg_team)\r
618                                 if(head.kh_next)\r
619                                         centerprint(head, "All keys are in your team's hands!\n\nMeet the other key carriers ^1NOW^7!");\r
620                                 else\r
621                                         centerprint(head, "All keys are in your team's hands!\n\nHelp the key carriers to meet!");\r
622                         else\r
623                                 centerprint(head, strcat("All keys are in the ", ColoredTeamName(kh_interferemsg_team), "^7's hands!\n\nInterfere ^1NOW^7!"));\r
624                 }\r
625         }\r
626 \r
627         self.nextthink = time + 0.05;\r
628 }\r
629 \r
630 void kh_WinnerTeam(float teem)  // runs when a team wins\r
631 {\r
632         // all key carriers get some points\r
633         vector firstorigin, lastorigin, midpoint;\r
634         float first;\r
635         entity key;\r
636         float score;\r
637         score = (kh_teams - 1) * cvar("g_balance_keyhunt_score_capture");\r
638         DistributeEvenly_Init(score, kh_teams);\r
639         // twice the score for 3 team games, three times the score for 4 team games!\r
640         // note: for a win by destroying the key, this should NOT be applied\r
641         FOR_EACH_KH_KEY(key)\r
642         {\r
643                 float f;\r
644                 f = DistributeEvenly_Get(1);\r
645                 kh_Scores_Event(key.owner, key, "capture", f, 0);\r
646                 PlayerTeamScore_Add(key.owner, SP_KH_CAPS, ST_KH_CAPS, 1);\r
647         }\r
648 \r
649         first = TRUE;\r
650         FOR_EACH_KH_KEY(key)\r
651                 if(key.owner.kh_next == key)\r
652                 {\r
653                         if(!first)\r
654                                 bprint("^7, ");\r
655                         bprint(key.owner.netname);\r
656                         first = FALSE;\r
657                 }\r
658         bprint("^7 captured the keys for the ", ColoredTeamName(teem), "\n");\r
659 \r
660         first = TRUE;\r
661         midpoint = '0 0 0';\r
662         FOR_EACH_KH_KEY(key)\r
663         {\r
664                 vector thisorigin;\r
665 \r
666                 thisorigin = kh_AttachedOrigin(key);\r
667                 //dprint("Key origin: ", vtos(thisorigin), "\n");\r
668                 midpoint += thisorigin;\r
669 \r
670                 if(!first)\r
671                         te_lightning2(world, lastorigin, thisorigin);\r
672                 lastorigin = thisorigin;\r
673                 if(first)\r
674                         firstorigin = thisorigin;\r
675                 first = FALSE;\r
676         }\r
677         if(kh_teams > 2)\r
678         {\r
679                 te_lightning2(world, lastorigin, firstorigin);\r
680         }\r
681         midpoint = midpoint * (1 / kh_teams);\r
682         te_customflash(midpoint, 1000, 1, TeamColor(teem) * 0.5 + '0.5 0.5 0.5');  // make the color >=0.5 in each component\r
683 \r
684         play2all(kh_sound_capture);\r
685         kh_FinishRound();\r
686 }\r
687 \r
688 void kh_LoserTeam(float teem, entity lostkey)  // runs when a player pushes a flag carrier off the map\r
689 {\r
690         entity player, key, attacker;\r
691         float players;\r
692         float keys;\r
693         float f;\r
694 \r
695         attacker = world;\r
696         if(lostkey.pusher)\r
697                 if(lostkey.pusher.team != teem)\r
698                         if(lostkey.pusher.classname == "player")\r
699                                 attacker = lostkey.pusher;\r
700 \r
701         players = keys = 0;\r
702 \r
703         if(attacker)\r
704         {\r
705                 if(lostkey.kh_previous_owner)\r
706                         kh_Scores_Event(lostkey.kh_previous_owner, world, "pushed", 0, -cvar("g_balance_keyhunt_score_push"));\r
707                         // don't actually GIVE him the -nn points, just log\r
708                 kh_Scores_Event(attacker, world, "push", cvar("g_balance_keyhunt_score_push"), 0);\r
709                 PlayerScore_Add(attacker, SP_KH_PUSHES, 1);\r
710                 centerprint(attacker, "Your push is the best!");\r
711                 bprint("The ", ColoredTeamName(teem), "^7 could not take care of the ", lostkey.netname, "^7 when ", attacker.netname, "^7 came\n");\r
712         }\r
713         else\r
714         {\r
715                 float of, fragsleft, i, j, thisteam;\r
716                 of = cvar("g_balance_keyhunt_score_destroyed_ownfactor");\r
717 \r
718                 FOR_EACH_PLAYER(player)\r
719                         if(player.team != teem)\r
720                                 ++players;\r
721 \r
722                 FOR_EACH_KH_KEY(key)\r
723                         if(key.owner && key.team != teem)\r
724                                 ++keys;\r
725 \r
726                 if(lostkey.kh_previous_owner)\r
727                         kh_Scores_Event(lostkey.kh_previous_owner, world, "destroyed", 0, -cvar("g_balance_keyhunt_score_destroyed"));\r
728                         // don't actually GIVE him the -nn points, just log\r
729 \r
730                 if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid)\r
731                         PlayerScore_Add(lostkey.kh_previous_owner, SP_KH_DESTROYS, 1);\r
732 \r
733                 DistributeEvenly_Init(cvar("g_balance_keyhunt_score_destroyed"), keys * of + players);\r
734 \r
735                 FOR_EACH_KH_KEY(key)\r
736                         if(key.owner && key.team != teem)\r
737                         {\r
738                                 f = DistributeEvenly_Get(of);\r
739                                 kh_Scores_Event(key.owner, world, "destroyed_holdingkey", f, 0);\r
740                         }\r
741 \r
742                 fragsleft = DistributeEvenly_Get(players);\r
743 \r
744                 // Now distribute these among all other teams...\r
745                 j = kh_teams - 1;\r
746                 for(i = 0; i < kh_teams; ++i)\r
747                 {\r
748                         thisteam = kh_Team_ByID(i);\r
749                         if(thisteam == teem) // bad boy, no cookie - this WILL happen\r
750                                 continue;\r
751 \r
752                         players = 0;\r
753                         FOR_EACH_PLAYER(player)\r
754                                 if(player.team == thisteam)\r
755                                         ++players;\r
756 \r
757                         DistributeEvenly_Init(fragsleft, j);\r
758                         fragsleft = DistributeEvenly_Get(j - 1);\r
759                         DistributeEvenly_Init(DistributeEvenly_Get(1), players);\r
760 \r
761                         FOR_EACH_PLAYER(player)\r
762                                 if(player.team == thisteam)\r
763                                 {\r
764                                         f = DistributeEvenly_Get(1);\r
765                                         kh_Scores_Event(player, world, "destroyed", f, 0);\r
766                                 }\r
767 \r
768                         --j;\r
769                 }\r
770 \r
771                 bprint("The ", ColoredTeamName(teem), "^7 could not take care of the ", lostkey.netname, "\n");\r
772         }\r
773         play2all(kh_sound_destroy);\r
774         te_tarexplosion(lostkey.origin);\r
775 \r
776         kh_FinishRound();\r
777 }\r
778 \r
779 void kh_FinishRound()  // runs when a team captures the keys\r
780 {\r
781         // prepare next round\r
782         kh_interferemsg_time = 0;\r
783         entity key;\r
784 \r
785         kh_no_radar_circles = TRUE;\r
786         FOR_EACH_KH_KEY(key)\r
787                 kh_Key_Remove(key);\r
788         kh_no_radar_circles = FALSE;\r
789 \r
790         kh_Controller_SetThink(cvar("g_balance_keyhunt_delay_round"), "Round starts in ", kh_StartRound);\r
791 }\r
792 \r
793 string kh_CheckEnoughPlayers()  // checks enough player are present, runs after every completed round\r
794 {\r
795         float i, players, teem;\r
796         entity player;\r
797         string result;\r
798         result = "";\r
799 \r
800         // find a random player per team\r
801         for(i = 0; i < kh_teams; ++i)\r
802         {\r
803                 teem = kh_Team_ByID(i);\r
804                 players = 0;\r
805                 FOR_EACH_PLAYER(player)\r
806                         if(player.deadflag == DEAD_NO)\r
807                                 if(!player.BUTTON_CHAT)\r
808                                         if(player.team == teem)\r
809                                                 ++players;\r
810                 if(players == 0)\r
811                 {\r
812                         if(result != "")\r
813                                 result = strcat(result, ", ");\r
814                         result = strcat(result, ColoredTeamName(teem));\r
815                 }\r
816         }\r
817         return result;\r
818 }\r
819 \r
820 void kh_WaitForPlayers()  // delay start of the round until enough players are present\r
821 {\r
822         string teams_missing;\r
823 \r
824         if(time < game_starttime)\r
825         {\r
826                 kh_Controller_SetThink(game_starttime - time + 0.1, "", kh_WaitForPlayers);\r
827                 return;\r
828         }\r
829 \r
830         teams_missing = kh_CheckEnoughPlayers();\r
831         if(teams_missing == "")\r
832                 kh_Controller_SetThink(cvar("g_balance_keyhunt_delay_round"), "Round starts in ", kh_StartRound);\r
833         else\r
834                 kh_Controller_SetThink(1, strcat("Waiting for players to join...\n\nNeed active players for: ", teams_missing), kh_WaitForPlayers);\r
835 }\r
836 \r
837 void kh_StartRound()  // runs at the start of each round\r
838 {\r
839         string teams_missing;\r
840         float i, players, teem;\r
841         entity player;\r
842 \r
843         if(time < game_starttime)\r
844         {\r
845                 kh_Controller_SetThink(game_starttime - time + 0.1, "", kh_WaitForPlayers);\r
846                 return;\r
847         }\r
848 \r
849         teams_missing = kh_CheckEnoughPlayers();\r
850         if(teams_missing != "")\r
851         {\r
852                 kh_Controller_SetThink(1, strcat("Waiting for players to join...\n\nNeed active players for: ", teams_missing), kh_WaitForPlayers);\r
853                 return;\r
854         }\r
855 \r
856         FOR_EACH_PLAYER(player)\r
857                 if(clienttype(player) == CLIENTTYPE_REAL)\r
858                         centerprint_expire(player, CENTERPRIO_SPAM);\r
859 \r
860         for(i = 0; i < kh_teams; ++i)\r
861         {\r
862                 teem = kh_Team_ByID(i);\r
863                 players = 0;\r
864                 entity my_player;\r
865                 FOR_EACH_PLAYER(player)\r
866                         if(player.deadflag == DEAD_NO)\r
867                                 if(!player.BUTTON_CHAT)\r
868                                         if(player.team == teem)\r
869                                         {\r
870                                                 ++players;\r
871                                                 if(random() * players <= 1)\r
872                                                         my_player = player;\r
873                                         }\r
874                 kh_Key_Spawn(my_player, 360 * i / kh_teams, i);\r
875         }\r
876 \r
877         kh_tracking_enabled = FALSE;\r
878         kh_Controller_SetThink(cvar("g_balance_keyhunt_delay_tracking"), "Scanning frequency range...", kh_EnableTrackingDevice);\r
879 }\r
880 \r
881 void kh_EnableTrackingDevice()  // runs after each round\r
882 {\r
883         entity player;\r
884 \r
885         FOR_EACH_PLAYER(player)\r
886                 if(clienttype(player) == CLIENTTYPE_REAL)\r
887                         centerprint_expire(player, CENTERPRIO_SPAM);\r
888 \r
889         kh_tracking_enabled = TRUE;\r
890 }\r
891 \r
892 float kh_Key_waypointsprite_visible_for_player(entity e) // ??\r
893 {\r
894         if(!kh_tracking_enabled)\r
895                 return FALSE;\r
896         if(!self.owner)\r
897                 return TRUE;\r
898         if(!self.owner.owner)\r
899                 return TRUE;\r
900         return FALSE;  // draw only when key is not owned\r
901 }\r
902 \r
903 float kh_KeyCarrier_waypointsprite_visible_for_player(entity e)  // runs all the time\r
904 {\r
905         if(e.classname != "player" || self.team != e.team)\r
906                 if(!kh_tracking_enabled)\r
907                         return FALSE;\r
908 \r
909         return TRUE;\r
910 }\r
911 \r
912 float kh_HandleFrags(entity attacker, entity targ, float f)  // adds to the player score\r
913 {\r
914         if(attacker == targ)\r
915                 return f;\r
916 \r
917         if(targ.kh_next)\r
918         {\r
919                 if(attacker.team == targ.team)\r
920                 {\r
921                         entity k;\r
922                         float nk;\r
923                         nk = 0;\r
924                         for(k = targ.kh_next; k != world; k = k.kh_next)\r
925                                 ++nk;\r
926                         kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * cvar("g_balance_keyhunt_score_collect"), 0);\r
927                 }\r
928                 else\r
929                 {\r
930                         kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", cvar("g_balance_keyhunt_score_carrierfrag")-1, 0);\r
931                         PlayerScore_Add(attacker, SP_KH_KCKILLS, 1);\r
932                         // the frag gets added later\r
933                 }\r
934         }\r
935 \r
936         return f;\r
937 }\r
938 \r
939 void kh_init()  // sets up th KH environment\r
940 {\r
941         precache_sound(kh_sound_capture);\r
942         precache_sound(kh_sound_destroy);\r
943         precache_sound(kh_sound_drop);\r
944         precache_sound(kh_sound_collect);\r
945         precache_sound(kh_sound_alarm);  // the new siren\r
946 \r
947 #ifdef KH_PLAYER_USE_CARRIEDMODEL\r
948         precache_model("models/keyhunt/key-carried.md3");\r
949 #endif\r
950         precache_model("models/keyhunt/key.md3");\r
951 \r
952         // setup variables\r
953         kh_teams = cvar("g_keyhunt_teams_override");\r
954         if(kh_teams < 2)\r
955                 kh_teams = cvar("g_keyhunt_teams");\r
956         kh_teams = bound(2, kh_teams, 4);\r
957 \r
958         // make a KH entity for controlling the game\r
959         kh_controller = spawn();\r
960         kh_controller.think = kh_Controller_Think;\r
961         kh_Controller_SetThink(0, "", kh_WaitForPlayers);\r
962 \r
963         setmodel(kh_controller, "models/keyhunt/key.md3");\r
964         kh_key_dropped = kh_controller.modelindex;\r
965         /*\r
966         dprint(vtos(kh_controller.mins));\r
967         dprint(vtos(kh_controller.maxs));\r
968         dprint("\n");\r
969         */\r
970 #ifdef KH_PLAYER_USE_CARRIEDMODEL\r
971         setmodel(kh_controller, "models/keyhunt/key-carried.md3");\r
972         kh_key_carried = kh_controller.modelindex;\r
973 #else\r
974         kh_key_carried = kh_key_dropped;\r
975 #endif\r
976 \r
977         kh_controller.model = "";\r
978         kh_controller.modelindex = 0;\r
979 \r
980         addstat(STAT_KH_KEYS, AS_INT, kh_state);\r
981 \r
982         ScoreRules_kh(kh_teams);\r
983 }\r
984 \r
985 void kh_finalize()\r
986 {\r
987         // to be called before intermission\r
988         kh_FinishRound();\r
989         remove(kh_controller);\r
990         kh_controller = world;\r
991 }\r
992 \r
993 void kh_update_state()\r
994 {\r
995         entity player;\r
996         entity key;\r
997         float s;\r
998         float f;\r
999 \r
1000         s = 0;\r
1001         FOR_EACH_KH_KEY(key)\r
1002         {\r
1003                 if(key.owner)\r
1004                         f = key.team;\r
1005                 else\r
1006                         f = 30;\r
1007                 s |= pow(32, key.count) * f;\r
1008         }\r
1009 \r
1010         FOR_EACH_CLIENT(player)\r
1011         {\r
1012                 player.kh_state = s;\r
1013         }\r
1014 \r
1015         FOR_EACH_KH_KEY(key)\r
1016         {\r
1017                 if(key.owner)\r
1018                         key.owner.kh_state |= pow(32, key.count) * 31;\r
1019         }\r
1020         //print(ftos((nextent(world)).kh_state), "\n");\r
1021 }\r