]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/keyhunt.qc
make friend sprites work again
[divverent/nexuiz.git] / data / qcsrc / server / keyhunt.qc
1 string STR_ITEM_KH_KEY = "item_kh_key";
2 #define FOR_EACH_KH_KEY(v) for(v = world; (v = find(v, classname, STR_ITEM_KH_KEY)); )
3
4 typedef void(void) kh_Think_t;
5 var kh_Think_t kh_Controller_Thinkfunc;
6 string kh_Controller_Waitmsg;
7
8 float kh_Team_ByID(float t)
9 {
10         if(t == 0) return COLOR_TEAM1;
11         if(t == 1) return COLOR_TEAM2;
12         if(t == 2) return COLOR_TEAM3;
13         if(t == 3) return COLOR_TEAM4;
14         return 0;
15 }
16
17 entity kh_controller;
18 float kh_tracking_enabled;
19 float kh_teams;
20 .entity kh_next, kh_prev; // linked list
21 .float kh_droptime;
22
23 float kh_sprite_dropped, kh_sprite_finish, kh_sprite_red, kh_sprite_blue, kh_sprite_pink, kh_sprite_yellow, kh_sprite_friend;
24
25 float kh_GetCarrierSprite(float t, float e)
26 {
27         if(t == e)           return kh_sprite_friend;
28         if(t == COLOR_TEAM1) return kh_sprite_red;
29         if(t == COLOR_TEAM2) return kh_sprite_blue;
30         if(t == COLOR_TEAM3) return kh_sprite_pink;
31         if(t == COLOR_TEAM4) return kh_sprite_yellow;
32         return 0;
33 }
34
35 void kh_Controller_SetThink(float t, string msg, kh_Think_t func)
36 {
37         kh_Controller_Thinkfunc = func;
38         kh_controller.cnt = t;
39         if(kh_Controller_Waitmsg != "")
40                 strunzone(kh_Controller_Waitmsg);
41         if(msg == "")
42                 kh_Controller_Waitmsg = "";
43         else
44                 kh_Controller_Waitmsg = strzone(msg);
45         if(t == 0)
46                 kh_controller.nextthink = time; // force
47 }
48
49 void kh_Controller_Think()
50 {
51         entity e;
52         if(self.cnt > 0)
53         {
54                 if(kh_Controller_Waitmsg != "")
55                 {
56                         string s;
57                         if(substring(kh_Controller_Waitmsg, strlen(kh_Controller_Waitmsg)-1, 1) == " ")
58                                 s = strcat(kh_Controller_Waitmsg, ftos(self.cnt));
59                         else
60                                 s = kh_Controller_Waitmsg;
61
62                         dprint(s, "\n");
63
64                         FOR_EACH_PLAYER(e)
65                                 if(clienttype(e) == CLIENTTYPE_REAL)
66                                         centerprint_atprio(e, CENTERPRIO_SPAM, s);
67                 }
68                 self.cnt -= 1;
69         }
70         else if(self.cnt == 0)
71         {
72                 FOR_EACH_PLAYER(e)
73                         if(clienttype(e) == CLIENTTYPE_REAL)
74                                 centerprint_expire(e, CENTERPRIO_SPAM);
75                 self.cnt -= 1;
76                 kh_Controller_Thinkfunc();
77         }
78         self.nextthink = time + 1;
79 }
80
81 void kh_Log()
82 {
83         string s;
84         if(!cvar("sv_eventlog"))
85                 return;
86         // TODO ...
87         GameLogEcho(s, FALSE);
88 }
89
90 // frags f: take from cvar * f
91 // frags 0: no frags
92 void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner)
93 {
94         float basefrags;
95         basefrags = cvar(strcat("g_balance_keyhunt_score_", what));
96
97         if(frags_player)
98                 player.frags = player.frags + floor(0.5 + basefrags * frags_player);
99         if(frags_owner)
100                 key.owner.frags = key.owner.frags + floor(0.5 + basefrags * frags_owner);
101 }
102
103 void kh_Key_Attach(entity key, float wpchange)
104 {
105         setattachment(key, key.owner, "");
106         setorigin(key, '0 0 0'); // fixed later in think
107         key.angles = key.angles - '0 1 0' * key.owner.angles_y;
108         key.flags = 0;
109         key.solid = SOLID_NOT;
110         key.movetype = MOVETYPE_NONE;
111         key.team = key.owner.team;
112         key.nextthink = time;
113 }
114
115 void kh_Key_Detach(entity key, float wpchange)
116 {
117         setattachment(key, world, "");
118         makevectors(key.owner.angles);
119         setorigin(key, key.owner.origin + key.origin_x * v_forward - key.origin_y * v_right + key.origin_z * v_up);
120         key.angles_y = key.owner.angles_y;
121         key.aiment = world;
122         key.flags = FL_ITEM;
123         key.solid = SOLID_TRIGGER;
124         key.movetype = MOVETYPE_TOSS;
125         key.pain_finished = time + cvar("g_balance_keyhunt_delay_return");
126         // let key.team stay
127 }
128
129 void kh_Key_AssignTo(entity key, entity player, float wpchange)
130 {
131         if(key.owner == player)
132                 return;
133
134         if(key.owner)
135         {
136                 kh_Key_Detach(key, wpchange);
137
138                 // remove from linked list
139                 if(key.kh_next)
140                         key.kh_next.kh_prev = key.kh_prev;
141                 key.kh_prev.kh_next = key.kh_next;
142                 key.kh_next = world;
143                 key.kh_prev = world;
144
145                 if(key.owner.kh_next == world)
146                 {
147                         // No longer a key carrier
148                         if(wpchange)
149                                 WaypointSprite_Kill(key.owner.waypointsprite_attachedforcarrier);
150                         else
151                                 WaypointSprite_DetachCarrier(key.owner);
152                 }
153         }
154
155         key.owner = player;
156
157         if(player)
158         {
159                 // insert into linked list
160                 key.kh_next = player.kh_next;
161                 key.kh_prev = player;
162                 player.kh_next = key;
163                 if(key.kh_next)
164                         key.kh_next.kh_prev = key;
165
166                 kh_Key_Attach(key, wpchange);
167
168                 if(key.kh_next == world)
169                 {
170                         // player is now a key carrier
171                         WaypointSprite_AttachCarrier("", player);
172                         player.waypointsprite_attachedforcarrier.waypointsprite_for_player = kh_KeyCarrier_waypointsprite_for_player;
173                         player.waypointsprite_attachedforcarrier.team = player.team;
174                 }
175         }
176
177         key.pusher = world;
178 }
179
180 void kh_Key_Spawn(entity initial_owner, float angle)
181 {
182         entity key;
183         key = spawn();
184         key.classname = STR_ITEM_KH_KEY;
185         key.touch = kh_Key_Touch;
186         key.think = kh_Key_Think;
187         key.nextthink = time;
188         key.items = IT_KEY1 | IT_KEY2;
189         key.cnt = angle;
190         key.angles = '0 360 0' * random();
191         setmodel(key, "models/keyhunt/key3.md3");
192         setsize(key, '0 0 -24', '0 0 25');
193
194         switch(initial_owner.team)
195         {
196                 case COLOR_TEAM1:
197                         key.netname = "^1red key";
198                         key.colormod = '1.73 0.10 0.10';
199                         break;
200                 case COLOR_TEAM2:
201                         key.netname = "^4blue key";
202                         key.colormod = '0.10 0.10 1.73';
203                         break;
204                 case COLOR_TEAM3:
205                         key.netname = "^6pink key";
206                         key.colormod = '1.22 0.10 1.22';
207                         break;
208                 case COLOR_TEAM4:
209                         key.netname = "^3yellow key";
210                         key.colormod = '1.22 1.22 0.10';
211                         break;
212                 default:
213                         key.netname = "NETGIER key";
214                         key.colormod = '1.00 1.00 1.00';
215                         break;
216         }
217
218         sprint(initial_owner, strcat("You got the ^2", key.netname, "\n"));
219
220         WaypointSprite_AttachCarrier("", key);
221         key.waypointsprite_attachedforcarrier.waypointsprite_for_player = kh_Key_waypointsprite_for_player;
222
223         kh_Key_AssignTo(key, initial_owner, TRUE);
224 }
225
226 void kh_Key_Remove(entity key)
227 {
228         entity o;
229         o = key.owner;
230         kh_Key_AssignTo(key, world, FALSE);
231         if(o) // it was attached
232                 WaypointSprite_Kill(key.waypointsprite_attachedforcarrier);
233         else // it was dropped
234                 WaypointSprite_DetachCarrier(key);
235
236         remove(key);
237 }
238
239 void kh_Key_Collect(entity key, entity player)
240 {
241         entity head;
242
243         kh_Scores_Event(player, key, "collect", 1, 0);
244         bprint(player.netname, "^7 collected the ", key.netname, "\n");
245         kh_Key_AssignTo(key, player, TRUE);
246
247         FOR_EACH_KH_KEY(key)
248                 if(!key.owner || key.team != player.team)
249                         goto notallowned;
250         FOR_EACH_PLAYER(head)
251         {
252                 if(head.team == player.team)
253                         if(head.kh_next)
254                                 centerprint(head, "All keys are in your team's hands!\n\nMeet the other key carriers ^1NOW^7!\n");
255                         else
256                                 centerprint(head, "All keys are in your team's hands!\n\nHelp the key carriers to meet!\n");
257                 else
258                         centerprint(head, "All keys are in the enemy's hands!\n\nInterfere ^1NOW^7!\n");
259         }
260 :notallowned
261 }
262
263 void kh_Key_DropAll(entity player)
264 {
265         entity key;
266         entity mypusher;
267         mypusher = world;
268         if(player.pusher)
269                 if(time < player.pushltime)
270                         mypusher = player.pusher;
271         while((key = player.kh_next))
272         {
273                 kh_Scores_Event(player, key, "losekey", 0, 0);
274                 bprint(player.netname, "^7 lost the ", key.netname, "\n");
275                 kh_Key_AssignTo(key, world, TRUE);
276                 key.pusher = player.pusher;
277                 key.pushltime = player.pushltime;
278         }
279 }
280
281 void kh_Key_Touch()
282 {
283         if(self.owner) // already carried
284                 return;
285         if(other.classname != "player")
286                 return;
287         if(other.deadflag != DEAD_NO)
288                 return;
289         if(other == self.enemy)
290                 if(time < self.kh_droptime + cvar("g_balance_keyhunt_delay_collect"))
291                         return; // you just dropped it!
292         kh_Key_Collect(self, other);
293 }
294
295 void kh_Key_Think()
296 {
297         self.angles_y = math_mod(self.angles_y + 0.05 * 135, 360);
298
299         if(self.owner)
300         {
301                 makevectors('0 1 0' * (self.cnt + math_mod(time, 360) * 45));
302                 setorigin(self, v_forward * 16);
303
304                 if(self.owner.buttonuse)
305                 if(time >= self.owner.kh_droptime + cvar("g_balance_keyhunt_delay_drop"))
306                 {
307                         self.owner.kh_droptime = time;
308                         self.kh_droptime = time; // prevent collecting this one for some time
309                         self.enemy = self.owner;
310                         self.pusher = world;
311                         kh_Scores_Event(self.owner, self, "dropkey", 0, 0);
312                         bprint(self.owner.netname, "^7 dropped the ", self.netname, "\n");
313                         kh_Key_AssignTo(self, world, TRUE);
314                 }
315         }
316
317         // if in nodrop or time over, end the round
318         if(!self.owner)
319                 if(time > self.pain_finished)
320                         kh_LoserTeam(self.team, self);
321         
322         if(self.owner)
323         {
324                 entity key;
325                 vector p;
326                 float teem;
327                 teem = self.team;
328                 p = self.owner.origin;
329                 FOR_EACH_KH_KEY(key)
330                 {
331                         if(key.owner == self.owner)
332                                 continue;
333                         if(key.owner)
334                         if(key.team == teem)
335                         if(vlen(key.owner.origin - p) < cvar("g_balance_keyhunt_maxdist"))
336                                 continue;
337                         goto not_winning;
338                 }
339                 kh_WinnerTeam(teem);
340 :not_winning
341         }
342
343         self.nextthink = time + 0.05;
344 }
345
346 void kh_WinnerTeam(float teem)
347 {
348         // all key carriers get some points
349         entity key;
350         float first;
351         float score;
352         score = 1.0 / kh_teams;
353         first = TRUE;
354         FOR_EACH_KH_KEY(key)
355         {
356                 kh_Scores_Event(key.owner, key, "capture", score, 0);
357                 if(key.owner.kh_next == key)
358                 {
359                         if(!first)
360                                 bprint("^7, ");
361                         bprint(key.owner.netname);
362                         first = FALSE;
363                 }
364         }
365         bprint("^7 captured the keys for the ", ColoredTeamName(teem), "\n");
366         kh_FinishRound();
367 }
368
369 void kh_LoserTeam(float teem, entity lostkey)
370 {
371         entity player, key, attacker;
372         float players;
373         float keys;
374         float score;
375
376         attacker = world;
377         if(lostkey.pusher)
378                 if(player.pusher.team != player.team)
379                         if(player.pusher.classname == "player")
380                                 attacker = lostkey.pusher;
381
382         players = keys = 0;
383
384         if(attacker)
385         {
386                 kh_Scores_Event(attacker, world, "push", 1, 0);
387                 centerprint(attacker, "Your push is the best!\n\n\n");
388                 bprint("The ", ColoredTeamName(teem), "^7 could not take care of the ", lostkey.netname, "\n");
389         }
390         else
391         {
392                 FOR_EACH_PLAYER(player)
393                         if(player.team != teem)
394                                 ++players;
395                 
396                 FOR_EACH_KH_KEY(key)
397                         if(key.owner && key.team != teem)
398                                 ++keys;
399
400                 score = 1.0 / (keys * cvar("g_balance_keyhunt_score_destroyed_ownfactor") + players);
401
402                 FOR_EACH_PLAYER(player)
403                         if(player.team != teem)
404                                 kh_Scores_Event(player, world, "destroyed", score, 0);
405
406                 FOR_EACH_KH_KEY(key)
407                         if(key.owner && key.team != teem)
408                                 kh_Scores_Event(key.owner, world, "destroyed", score * cvar("g_balance_keyhunt_score_destroyed_ownfactor"), 0);
409
410                 bprint("The ", ColoredTeamName(teem), "^7 could not take care of the ", lostkey.netname, "\n");
411         }
412
413         kh_FinishRound();
414 }
415
416 void kh_FinishRound()
417 {
418         // prepare next round
419         entity key;
420         FOR_EACH_KH_KEY(key)
421                 kh_Key_Remove(key);
422
423         kh_Controller_SetThink(cvar("g_balance_keyhunt_delay_round"), "Round starts in ", kh_StartRound);
424 }
425
426 float kh_EnoughPlayers()
427 {
428         float i, players, teem;
429         entity player;
430         
431         // find a random player per team
432         for(i = 0; i < kh_teams; ++i)
433         {
434                 teem = kh_Team_ByID(i);
435                 players = 0;
436                 FOR_EACH_PLAYER(player)
437                         if(player.deadflag == DEAD_NO)
438                                 if(player.team == teem)
439                                         ++players;
440                 if(players == 0)
441                         return FALSE;
442         }
443         return TRUE;
444 }
445
446 void kh_WaitForPlayers()
447 {
448         if(kh_EnoughPlayers())
449                 kh_Controller_SetThink(cvar("g_balance_keyhunt_delay_round"), "Round starts in ", kh_StartRound);
450         else
451                 kh_Controller_SetThink(1, "Waiting for players to join...", kh_WaitForPlayers);
452 }
453
454 void kh_StartRound()
455 {
456         float i, players, teem;
457         entity player;
458
459         if(!kh_EnoughPlayers())
460         {
461                 kh_Controller_SetThink(1, "Waiting for players to join...", kh_WaitForPlayers);
462                 return;
463         }
464
465         for(i = 0; i < kh_teams; ++i)
466         {
467                 teem = kh_Team_ByID(i);
468                 players = 0;
469                 entity my_player;
470                 FOR_EACH_PLAYER(player)
471                         if(player.deadflag == DEAD_NO)
472                                 if(player.team == teem)
473                                 {
474                                         ++players;
475                                         if(random() * players <= 1)
476                                                 my_player = player;
477                                 }
478                 kh_Key_Spawn(my_player, 360 * i / kh_teams);
479         }
480
481         kh_tracking_enabled = FALSE;
482         kh_Controller_SetThink(cvar("g_balance_keyhunt_delay_tracking"), "Scanning frequency range...", kh_EnableTrackingDevice);
483 }
484
485 void kh_setstatus()
486 {
487         if(kh_teams)
488         {
489                 float kh_KEY;
490                 kh_KEY = (IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST); // the one impossible combination
491                 if(self.kh_next)
492                         self.items = self.items | kh_KEY;
493                 else
494                         self.items = self.items - (self.items & kh_KEY);
495         }
496 }
497
498 void kh_EnableTrackingDevice()
499 {
500         kh_tracking_enabled = TRUE;
501 }
502
503 float kh_Key_waypointsprite_for_player(entity e)
504 {
505         if(!kh_tracking_enabled)
506                 return 0;
507         if(!self.owner)
508                 return kh_sprite_dropped;
509         if(!self.owner.owner)
510                 return kh_sprite_dropped;
511         return 0; // draw only when key is not owned
512 }
513
514 float kh_KeyCarrier_waypointsprite_for_player(entity e)
515 {
516         entity key;
517         
518         if(e.classname != "player" || self.team != e.team)
519                 if(!kh_tracking_enabled)
520                         return 0;
521
522         // e is no key carrier: simple case...
523         if(!e.kh_next)
524                 return kh_GetCarrierSprite(self.team, e.team);
525         
526         // e is a key carrier: if any key is dropped or owned by another team, show
527         // the carrier sprite; otherwise show run here
528         FOR_EACH_KH_KEY(key)
529                 if(!key.owner || key.team != e.team)
530                         return kh_GetCarrierSprite(self.team, e.team);
531
532         return kh_sprite_finish;
533 }
534
535 float kh_HandleFrags(entity attacker, entity targ, float f)
536 {
537         float newfrags;
538
539         if(f <= 0)
540                 return f;
541         if(attacker == targ)
542                 return f;
543
544         if(targ.kh_next)
545                 f = f - 1 + cvar("g_balance_keyhunt_score_carrierfrag");
546
547         if(newfrags)
548                 f = f - 1 + newfrags;
549         return f;
550 }
551
552 void kh_init()
553 {
554         precache_model("models/sprites/key-dropped.sp2");
555         precache_model("models/sprites/keycarrier-finish.sp2");
556         precache_model("models/sprites/keycarrier-friend.sp2");
557         precache_model("models/sprites/keycarrier-red.sp2");
558         precache_model("models/sprites/keycarrier-blue.sp2");
559         precache_model("models/sprites/keycarrier-pink.sp2");
560         precache_model("models/sprites/keycarrier-yellow.sp2");
561         precache_model("models/keyhunt/key3.md3");
562
563         // setup variables
564         kh_teams = cvar("g_keyhunt_teams_override");
565         if(kh_teams < 2)
566                 kh_teams = cvar("g_keyhunt_teams");
567         if(kh_teams < 2)
568                 kh_teams = 2;
569
570         // make a KH entity for controlling the game
571         kh_controller = spawn();
572         kh_controller.think = kh_Controller_Think;
573         kh_Controller_SetThink(0, "", kh_WaitForPlayers);
574
575         setmodel(kh_controller, "models/sprites/key-dropped.sp2");
576         kh_sprite_dropped = kh_controller.modelindex;
577         setmodel(kh_controller, "models/sprites/keycarrier-finish.sp2");
578         kh_sprite_finish = kh_controller.modelindex;
579         setmodel(kh_controller, "models/sprites/keycarrier-friend.sp2");
580         kh_sprite_friend = kh_controller.modelindex;
581         setmodel(kh_controller, "models/sprites/keycarrier-red.sp2");
582         kh_sprite_red = kh_controller.modelindex;
583         setmodel(kh_controller, "models/sprites/keycarrier-blue.sp2");
584         kh_sprite_blue = kh_controller.modelindex;
585         setmodel(kh_controller, "models/sprites/keycarrier-pink.sp2");
586         kh_sprite_pink = kh_controller.modelindex;
587         setmodel(kh_controller, "models/sprites/keycarrier-yellow.sp2");
588         kh_sprite_yellow = kh_controller.modelindex;
589         setmodel(kh_controller, "");
590 }
591
592 void kh_finalize()
593 {
594         // to be called before intermission
595         kh_FinishRound();
596         remove(kh_controller);
597         kh_controller = world;
598 }