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