]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/runematch.qc
Merge branch 'master' into terencehill/cmd_fixes
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / runematch.qc
1 float rune_numspawns;
2
3 float   RUNE_FIRST      = 1;
4 float   RUNE_STRENGTH   = 1;
5 float   RUNE_DEFENSE    = 2;
6 float   RUNE_REGEN      = 4;
7 float   RUNE_SPEED      = 8;
8 float   RUNE_VAMPIRE    = 16;
9 float   RUNE_LAST       = 16;
10
11 float   CURSE_FIRST     = 8192;
12 float   CURSE_WEAK      = 8192;
13 float   CURSE_VULNER    = 16384;
14 float   CURSE_VENOM     = 32768;
15 float   CURSE_SLOW      = 65536;
16 float   CURSE_EMPATHY   = 131072;
17 float   CURSE_LAST      = 131072;
18
19 float RUNE_COUNT = 5;
20
21 /* rune ideas:
22
23         Doom/Death
24         Rune: When you damage enemies, you have a slight chance of instant-killing them (porportional to damage dealt / their health)
25         Curse: When you are damaged, you have a chance of being instant-killed
26
27         Vengence/Slothful
28         Rune: The lower your health below 100, the more damage you deal (does not decrease your damage if you're above 100)
29         Curse: The higher your health (up to 100), the less damage you deal (at 100 hp deal 1/5th damage)
30
31 */
32
33 /*QUAKED spawnfunc_runematch_spawn_point (1 0 0) (-16 -16 -24) (16 16 24)
34 spawn point for runes in runematch
35 */
36
37 void spawnfunc_runematch_spawn_point()
38 {
39         if(!g_runematch || !autocvar_g_runematch_fixedspawns)
40         {
41                 remove(self);
42                 return;
43         }
44
45         setsize(self, '0 0 -35', '0 0 0');
46         droptofloor();
47         ++rune_numspawns;
48 }
49
50 // only used if using rune spawns at all
51 entity rune_find_spawnpoint()
52 {
53         entity e;
54
55         if(rune_numspawns < RUNE_COUNT)
56                 return world;
57
58         RandomSelection_Init();
59
60         for(e = world; (e = find(e, classname, "runematch_spawn_point")); )
61                 if(e.owner == world)
62                         RandomSelection_Add(e, 0, string_null, e.cnt, 0);
63
64         return RandomSelection_chosen_ent;
65 }
66
67 float rune_spawn_somewhere(entity e)
68 {
69         entity spot;
70         spot = rune_find_spawnpoint();
71         if(spot)
72         {
73                 spot.owner = e;
74                 setorigin(e, spot.origin);
75
76                 e.owner = spot;
77                 spot.owner = e;
78
79                 return TRUE;
80         }
81         else
82         {
83                 if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
84                 {
85                         // great
86                         makevectors(self.angles),
87                         self.velocity = v_forward * 250;
88                         self.angles = '0 0 0';
89                         return TRUE;
90                 }
91                 else
92                 {
93                         // sorry, can't spawn, better luck next frame
94                         return FALSE;
95                 }
96         }
97 }
98
99 void rune_unmark_spot(entity e)
100 {
101         if(e.owner.classname == "runematch_spawn_point")
102         {
103                 e.owner.owner = world;
104                 e.owner = world;
105         }
106 }
107
108 string RuneName(float r)
109 {
110         if(r == RUNE_STRENGTH)
111                 return "^1Strength^7";
112         if(r == RUNE_DEFENSE)
113                 return "^4Defense^7";
114         if(r == RUNE_REGEN)
115                 return "^2Vitality^7";
116         if(r == RUNE_SPEED)
117                 return "^3Speed^7";
118         if(r == RUNE_VAMPIRE)
119                 return "^6Vampire^7";
120
121         if(r == CURSE_WEAK)
122                 return "^1Weakness^7";
123         if(r == CURSE_VULNER)
124                 return "^4Vulnerability^7";
125         if(r == CURSE_VENOM)
126                 return "^2Venom^7";
127         if(r == CURSE_SLOW)
128                 return "^3Slow^7";
129         if(r == CURSE_EMPATHY)
130                 return "^6Empathy^7";
131         return strcat("^8[unnamed", ftos(r), "]^7");
132 }
133
134 vector RuneColormod(float r)
135 {
136         vector _color = '255 0 255';
137
138         if(r == RUNE_STRENGTH)
139                 _color = '255 0 0';
140         if(r == RUNE_DEFENSE)
141                 _color = '0 0 255';//'0 102 255';//
142         if(r == RUNE_REGEN)
143                 _color = '0 204 0';//'0 255 0';
144         if(r == RUNE_SPEED)
145                 _color = 0.35*'185 185 0';//255 230 0';//'255 255 0';
146         if(r == RUNE_VAMPIRE)
147                 _color = '64 0 128';//'108 0 217';//'128 0 255';//'179 0 204';//
148
149         if(r == CURSE_WEAK)
150                 _color = '255 0 0';
151         if(r == CURSE_VULNER)
152                 _color = '0 0 255';//'0 102 255';//
153         if(r == CURSE_VENOM)
154                 _color = '0 204 0';//'0 255 0';
155         if(r == CURSE_SLOW)
156                 _color = 0.5*'185 185 0';//'255 255 0';
157         if(r == CURSE_EMPATHY)
158                 _color = '179 0 204';//'128 0 255';
159
160         return _color * (1 / 255) * autocvar_g_runematch_rune_color_strength;
161 }
162
163 void rune_respawn();
164
165 void RuneCarriedThink()
166 {
167         float rcount, rnum;
168         vector ang;
169         entity rune;
170
171         if(self.owner.classname != "player" || time < game_starttime)
172         {
173                 rune_respawn();
174                 return;
175         }
176
177         self.nextthink = time + 0.1;
178
179         // count runes my owner holds
180         rcount = 0;
181         rune = find(world, classname, "rune");
182         rnum = -1;
183         while(rune)
184         {
185                 if(rune.owner == self.owner)
186                         rcount = rcount + 1;
187                 if(rune == self)
188                         rnum = rcount;
189                 rune = find(rune, classname, "rune");
190         }
191
192         ang_y = rnum*(360 / rcount) + mod(time, 360)*45;//180;
193
194         makevectors(ang);
195
196         setorigin(self, v_forward*32);
197 }
198
199 void rune_touch()
200 {
201         if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
202         {
203                 self.think = rune_respawn;
204                 self.nextthink = time;
205                 return;
206         }
207
208         if(other.classname != "player" || other.health < 1)
209                 return;
210         if(self.wait > time)
211                 return; // "notouch" time isn't finished
212
213         // detach from the spawn point you're on
214         rune_unmark_spot(self);
215
216         self.owner = other;
217         self.enemy.owner = other;
218         setattachment(self, other, "");
219
220         other.runes = other.runes | self.runes | self.enemy.runes;
221
222         //self.think = SUB_Null;
223         //self.nextthink = 0;
224         self.think = RuneCarriedThink;
225         self.nextthink = time;
226         self.touch = SUB_Null;
227
228         self.solid = SOLID_NOT;
229         setorigin(self, self.origin);
230
231         //sprint(other, strcat("^3You have picked up ",
232         //      RuneName(self.runes & (RUNE_LAST*2-1)), " and "));
233         //sprint(other, strcat(RuneName(self.enemy.runes & (CURSE_WEAK | CURSE_VULNER | CURSE_VENOM | CURSE_SLOW | CURSE_EMPATHY)), "\n"));
234
235         bprint("^3", other.netname, "^7 has picked up ",
236                 RuneName(self.runes & (RUNE_LAST*2-1)), "^7 and ");
237         bprint(RuneName(self.enemy.runes & (CURSE_WEAK | CURSE_VULNER | CURSE_VENOM | CURSE_SLOW | CURSE_EMPATHY)), "\n");
238 }
239
240 void rune_respawn()
241 {
242         rune_unmark_spot(self);
243         if(rune_spawn_somewhere(self))
244         {
245                 self.solid = SOLID_TRIGGER;
246                 self.touch = rune_touch;
247                 self.think = rune_respawn;
248                 self.nextthink = time + autocvar_g_runematch_shuffletime;//30 + random()*5; // fixme: cvar
249         }
250         else
251         {
252                 // try again later
253                 self.think = rune_respawn;
254                 self.nextthink = time;
255         }
256 }
257
258 entity FindRune(entity own, string clname, float r)
259 {
260         entity rune;
261         float _count, c;
262
263         c = _count = 0;
264         rune = world;
265
266         do
267         {
268                 rune = find(rune, classname, clname);
269                 if(!rune)
270                         rune = find(rune, classname, clname);
271                 if(!rune)
272                         break;
273                 if(rune.owner == own)
274                 {
275                         _count = _count + 1;
276                         if(_count >= r)
277                                 return rune;
278                         if(r <= 1)
279                                 return rune;
280                 }
281                 c = c + 1;
282         }while(c < 30);
283         return world;
284 }
285
286
287 void DropRune(entity pl, entity e)
288 {
289         //entity pl;
290
291         //pl = e.owner;
292         // detach from player
293         setattachment(e, world, "");
294         e.owner = world;
295         e.enemy.owner = world;
296         // don't instantly touch player again
297         e.wait = time + 1; // "notouch" time
298         e.movetype = MOVETYPE_TOSS;
299         e.solid = SOLID_TRIGGER;
300         // reposition itself if not picked up soon
301         e.think = rune_respawn;
302         e.nextthink = time + autocvar_g_runematch_respawntime;//15 + random()*5; // fixme: cvar
303         e.touch = rune_touch;
304
305         pl.runes = pl.runes - (pl.runes & (e.runes | e.enemy.runes));
306
307         // toss from player
308         setorigin(e, pl.origin + '0 0 10');
309         e.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
310
311
312         bprint("^3", pl.netname, "^7 has lost ",
313                 RuneName(e.runes & (RUNE_LAST*2-1)), "^7 and ");
314         bprint(RuneName(e.enemy.runes & (CURSE_WEAK | CURSE_VULNER | CURSE_VENOM | CURSE_SLOW | CURSE_EMPATHY)), "\n");
315 }
316
317 float RuneMatchesCurse(float r, float c)
318 {
319         float cr;
320         if(r & RUNE_STRENGTH)
321                 cr = CURSE_WEAK;
322         else if(r & RUNE_DEFENSE)
323                 cr = CURSE_VULNER;
324         else if(r & RUNE_REGEN)
325                 cr = CURSE_VENOM;
326         else if(r & RUNE_SPEED)
327                 cr = CURSE_SLOW;
328         else if(r & RUNE_VAMPIRE)
329                 cr = CURSE_EMPATHY;
330         else return FALSE; // fixme: error?
331
332         if(c & cr)
333                 return TRUE;
334         return FALSE;
335 }
336
337 // player died, drop runes
338 // each rune should pair up with a random curse and then be tossed from the player
339 void DropAllRunes(entity pl)
340 {
341         entity rune, curse;
342         float rcount, ccount, r, c, rand, prevent_same, numtodrop, tries;
343
344         entity curse1, rune1, curse2, rune2;
345
346         rune = curse = world;
347         rcount = ccount = r = c = 0;
348         rune = find(rune, classname, "rune");
349         while(rune)
350         {
351                 if(rune.owner == pl)
352                         rcount = rcount + 1;
353                 rune = find(rune, classname, "rune");
354         }
355         curse = find(curse, classname, "curse");
356         while(curse)
357         {
358                 if(curse.owner == pl)
359                         ccount = ccount + 1;
360                 curse = find(curse, classname, "curse");
361         }
362
363         numtodrop = autocvar_g_runematch_drop_runes_max;
364         prevent_same = !autocvar_g_runematch_allow_same;
365
366         rune = curse = world;
367         do
368         {
369                 rune = find(rune, classname, "rune");
370                 if(!rune)
371                         break;
372                 if(rune.owner != pl)
373                         continue;
374
375
376                 // find a random curse
377                 tries = 15;
378                 if(ccount > 1 && prevent_same)
379                 {
380                         // avoid pairing runes and curses that match each other
381                         do{
382                                 rand = floor(random()*ccount) + 1;
383                                 curse = FindRune(pl, "curse", rand);
384                                 tries = tries - 1;
385                         }while(RuneMatchesCurse(rune.runes, curse.runes) && tries > 0);
386                         if(tries <= 0)
387                         {
388                                 bprint("warning: couldn't prevent same rune\n");
389                         }
390                 }
391                 else
392                 {
393                                 rand = floor(random()*ccount) + 1;
394                                 curse = FindRune(pl, "curse", rand);
395                 }
396
397                 if(!curse)
398                         error("Couldn't fine curse to bind rune to\n");
399
400                 // pair rune and curse
401
402                 rune1 = rune;
403                 curse1 = curse;
404                 rune2 = curse1.enemy;
405                 curse2 = rune1.enemy;
406
407                 if(rune1 != rune2) // not already attached to each other
408                 {
409                         rune1.enemy = curse1;
410                         curse1.enemy = rune1;
411                         setattachment(curse1, rune1, "");
412                         rune2.enemy = curse2;
413                         curse2.enemy = rune2;
414                         setattachment(curse2, rune2, "");
415                         //DropRune(pl, rune2);
416                         //ccount = ccount - 1;
417                         //rcount = rcount - 1;
418                 }
419                 DropRune(pl, rune1);
420
421                 if(numtodrop <=0)
422                 {
423                         rune1.think = rune_respawn;
424                         rune1.nextthink = time;
425                 }
426
427                 numtodrop = numtodrop - 1;
428
429                 ccount = ccount - 1;
430                 rcount = rcount - 1;
431
432         }while(rune);
433 }
434
435 void rune_reset()
436 {
437         if(self.owner)
438                 if(self.owner.classname != "runematch_spawn_point")
439                         DropAllRunes(self.owner);
440         rune_respawn();
441 }
442
443 void spawn_runes()
444 {
445         float rn, cs, runes_used, curses_used, prevent_same, numrunes;
446         entity e;
447
448         if(self)
449                 remove(self);
450
451         // fixme: instead of placing them all now, why not
452         // simply create them all and let them call rune_respawn() as their think?
453
454         runes_used  = 0;
455         curses_used = 0;
456
457         prevent_same = !autocvar_g_runematch_allow_same;
458         numrunes = RUNE_COUNT;
459
460         while(numrunes > 0)
461         {
462                 RandomSelection_Init();
463                 for(rn = RUNE_FIRST; rn <= RUNE_LAST; rn *= 2)
464                         if not(runes_used & rn)
465                                 RandomSelection_Add(world, rn, string_null, 1, 1);
466                 rn = RandomSelection_chosen_float;
467
468                 RandomSelection_Init();
469                 for(cs = CURSE_FIRST; cs <= CURSE_LAST; cs *= 2)
470                         if not(curses_used & cs)
471                                 if not(prevent_same && cs == RuneMatchesCurse(rn, cs))
472                                         RandomSelection_Add(world, cs, string_null, 1, 1);
473                 cs = RandomSelection_chosen_float;
474
475                 if(!rn || !cs)
476                         error("No rune/curse left");
477
478                 runes_used |= rn;
479                 curses_used |= cs;
480
481                 e = spawn();
482                 e.runes = rn;
483                 e.classname = "rune";
484                 e.touch = rune_touch;
485                 e.think = rune_respawn;
486                 e.nextthink = time;
487                 e.movetype = MOVETYPE_TOSS;
488                 e.solid = SOLID_TRIGGER;
489                 e.flags = FL_ITEM;
490                 e.reset = rune_reset;
491                 setmodel(e, "models/runematch/rune.mdl"); // precision set below
492                 setsize(e, '0 0 -35', '0 0 0');
493
494                 e.enemy = spawn();
495                 e.enemy.enemy = e;
496                 e.enemy.classname = "curse";
497                 e.enemy.runes = cs;
498                 //e.enemy.avelocity = '300 500 200';
499                 setmodel(e.enemy, "models/runematch/curse.mdl"); // precision set below
500                 setorigin(e, '0 0 0');
501                 setattachment(e.enemy, e, "");
502
503                 e.colormod = RuneColormod(rn);
504                 e.enemy.colormod = RuneColormod(cs);
505
506                 e.alpha = e.enemy.alpha = autocvar_g_runematch_rune_alpha;//0.78;
507                 e.effects = e.enemy.effects = autocvar_g_runematch_rune_effects | EF_LOWPRECISION;//EF_ADDITIVE;// | EF_FULLBRIGHT;
508
509                 //e.glow_size = e.enemy.glow_size = cvar("g_runematch_rune_glow_size");
510                 //e.glow_color = e.enemy.glow_color = cvar("g_runematch_rune_glow_color");
511
512                 //rn = RUNE_FIRST;
513                 //cs = CURSE_FIRST;
514
515                 numrunes = numrunes - 1;
516         }
517 }
518
519 void runematch_init()
520 {
521         if(!g_runematch)
522                 return;
523
524         entity e;
525         e = spawn();
526         e.think = spawn_runes;
527         e.nextthink = time + 0.1;
528 }
529
530
531 float runematch_point_time;
532
533 // give points to players who are holding runes
534 void RuneMatchGivePoints()
535 {
536         entity rune;
537
538         if(!g_runematch || !autocvar_g_runematch_pointamt)
539                 return;
540
541         if(gameover)
542                 return;
543
544         if(runematch_point_time > time)
545                 return;
546
547         runematch_point_time = time + autocvar_g_runematch_pointrate;
548
549         rune = world;
550         do
551         {
552                 rune = find(rune, classname, "rune");
553                 if(!rune)
554                         return;
555
556                 if(rune.owner.classname == "player")
557                 {
558                         UpdateFrags(rune.owner, autocvar_g_runematch_pointamt);
559                 }
560         }while(rune);
561 }
562
563 float RunematchHandleFrags(entity attacker, entity targ, float f)
564 {
565         entity head;
566         float arunes, trunes, newfrags;
567
568         if(f <= 0)
569                 return f;
570         if(attacker == targ)
571                 return f;
572
573         arunes = trunes = 0;
574
575         head = find(world, classname, "rune");
576         while(head)
577         {
578                 if(head.owner == attacker)
579                 {
580                         arunes = arunes + 1;
581                 }
582                 else if(head.owner == targ)
583                 {
584                         trunes = trunes + 1;
585                 }
586
587                 head = find(head, classname, "rune");
588         }
589
590         if(!arunes && !trunes)
591                 return f - 1 + autocvar_g_runematch_frags_norune; // don't give points to players when no runes are involved.
592
593         newfrags = 0;
594         if(arunes)
595         {       // got a kill while holding runes
596                 newfrags = newfrags + autocvar_g_runematch_frags_killedby_runeholder;//5;
597         }
598         if(trunes)
599         {       // killed an enemy holding runes
600                 newfrags = newfrags + autocvar_g_runematch_frags_killed_runeholder;//5;
601         }
602         if(newfrags)
603                 f = f - 1 + newfrags;
604
605         return f;
606 }