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