Merge branch 'TimePath/modules'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / steerlib.qc
1 #include "steerlib.qh"
2 #if defined(CSQC)
3 #elif defined(MENUQC)
4 #elif defined(SVQC)
5 #endif
6
7 /**
8     Uniform pull towards a point
9 **/
10 #define steerlib_pull(ent,point) normalize(point - (ent).origin)
11 /*vector steerlib_pull(entity this, vector point)
12 {
13     return normalize(point - this.origin);
14 }*/
15
16 /**
17     Uniform push from a point
18 **/
19 #define steerlib_push(ent,point) normalize(ent.origin - point)
20 /*
21 vector steerlib_push(entity this, vector point)
22 {
23     return normalize(this.origin - point);
24 }
25 */
26 /**
27     Pull toward a point, The further away, the stronger the pull.
28 **/
29 vector steerlib_arrive(entity this, vector point, float maximal_distance)
30 {
31     float distance;
32     vector direction;
33
34     distance = bound(0.001,vlen(this.origin - point),maximal_distance);
35     direction = normalize(point - this.origin);
36     return  direction * (distance / maximal_distance);
37 }
38
39 /**
40     Pull toward a point increasing the pull the closer we get
41 **/
42 vector steerlib_attract(entity this, vector point, float maximal_distance)
43 {
44     float distance;
45     vector direction;
46
47     distance = bound(0.001,vlen(this.origin - point),maximal_distance);
48     direction = normalize(point - this.origin);
49
50     return  direction * (1-(distance / maximal_distance));
51 }
52
53 vector steerlib_attract2(entity this, vector point, float min_influense,float max_distance,float max_influense)
54 {
55     float distance;
56     vector direction;
57     float influense;
58
59     distance  = bound(0.00001,vlen(this.origin - point),max_distance);
60     direction = normalize(point - this.origin);
61
62     influense = 1 - (distance / max_distance);
63     influense = min_influense + (influense * (max_influense - min_influense));
64
65     return  direction * influense;
66 }
67
68 /*
69 vector steerlib_attract2(vector point, float maximal_distance,float min_influense,float max_influense,float distance)
70 {
71     //float distance;
72     vector current_direction;
73     vector target_direction;
74     float i_target,i_current;
75
76     if(!distance)
77         distance = vlen(this.origin - point);
78
79     distance = bound(0.001,distance,maximal_distance);
80
81     target_direction = normalize(point - this.origin);
82     current_direction = normalize(this.velocity);
83
84     i_target = bound(min_influense,(1-(distance / maximal_distance)),max_influense);
85     i_current = 1 - i_target;
86
87     // i_target = bound(min_influense,(1-(distance / maximal_distance)),max_influense);
88
89     string s;
90     s = ftos(i_target);
91     bprint("IT: ",s,"\n");
92     s = ftos(i_current);
93     bprint("IC  : ",s,"\n");
94
95     return  normalize((target_direction * i_target) + (current_direction * i_current));
96 }
97 */
98 /**
99     Move away from a point.
100 **/
101 vector steerlib_repell(entity this, vector point,float maximal_distance)
102 {
103     float distance;
104     vector direction;
105
106     distance = bound(0.001,vlen(this.origin - point),maximal_distance);
107     direction = normalize(this.origin - point);
108
109     return  direction * (1-(distance / maximal_distance));
110 }
111
112 /**
113     Try to keep at ideal_distance away from point
114 **/
115 vector steerlib_standoff(entity this, vector point,float ideal_distance)
116 {
117     float distance;
118     vector direction;
119
120     distance = vlen(this.origin - point);
121
122
123     if(distance < ideal_distance)
124     {
125         direction = normalize(this.origin - point);
126         return direction * (distance / ideal_distance);
127     }
128
129     direction = normalize(point - this.origin);
130     return direction * (ideal_distance / distance);
131
132 }
133
134 /**
135     A random heading in a forward halfcicrle
136
137     use like:
138     this.target = steerlib_wander(256,32,this.target)
139
140     where range is the cicrle radius and tresh is how close we need to be to pick a new heading.
141 **/
142 vector steerlib_wander(entity this, float range, float tresh, vector oldpoint)
143 {
144     vector wander_point;
145     wander_point = v_forward - oldpoint;
146
147     if (vlen(wander_point) > tresh)
148         return oldpoint;
149
150     range = bound(0,range,1);
151
152     wander_point = this.origin + v_forward * 128;
153     wander_point = wander_point + randomvec() * (range * 128) - randomvec() * (range * 128);
154
155     return normalize(wander_point - this.origin);
156 }
157
158 /**
159     Dodge a point. dont work to well.
160 **/
161 vector steerlib_dodge(entity this, vector point, vector dodge_dir, float min_distance)
162 {
163     float distance;
164
165     distance = max(vlen(this.origin - point),min_distance);
166     if (min_distance < distance)
167         return '0 0 0';
168
169     return dodge_dir * (min_distance/distance);
170 }
171
172 /**
173     flocking by .flock_id
174     Group will move towards the unified direction while keeping close to eachother.
175 **/
176 .float flock_id;
177 vector steerlib_flock(entity this, float _radius, float standoff,float separation_force,float flock_force)
178 {
179     entity flock_member;
180     vector push = '0 0 0', pull = '0 0 0';
181     float ccount = 0;
182
183     flock_member = findradius(this.origin, _radius);
184     while(flock_member)
185     {
186         if(flock_member != this)
187         if(flock_member.flock_id == this.flock_id)
188         {
189             ++ccount;
190             push = push + (steerlib_repell(this, flock_member.origin,standoff) * separation_force);
191             pull = pull + (steerlib_arrive(this, flock_member.origin + flock_member.velocity, _radius) * flock_force);
192         }
193         flock_member = flock_member.chain;
194     }
195     return push + (pull* (1 / ccount));
196 }
197
198 /**
199     flocking by .flock_id
200     Group will move towards the unified direction while keeping close to eachother.
201     xy only version (for ground movers).
202 **/
203 vector steerlib_flock2d(entity this, float _radius, float standoff,float separation_force,float flock_force)
204 {
205     entity flock_member;
206     vector push = '0 0 0', pull = '0 0 0';
207     float ccount = 0;
208
209     flock_member = findradius(this.origin,_radius);
210     while(flock_member)
211     {
212         if(flock_member != this)
213         if(flock_member.flock_id == this.flock_id)
214         {
215             ++ccount;
216             push = push + (steerlib_repell(this, flock_member.origin, standoff) * separation_force);
217             pull = pull + (steerlib_arrive(this, flock_member.origin + flock_member.velocity, _radius) * flock_force);
218         }
219         flock_member = flock_member.chain;
220     }
221
222     push.z = 0;
223     pull.z = 0;
224
225     return push + (pull * (1 / ccount));
226 }
227
228 /**
229     All members want to be in the center, and keep away from eachother.
230     The furtehr form the center the more they want to be there.
231
232     This results in a aligned movement (?!) much like flocking.
233 **/
234 vector steerlib_swarm(entity this, float _radius, float standoff,float separation_force,float swarm_force)
235 {
236     entity swarm_member;
237     vector force = '0 0 0', center = '0 0 0';
238     float ccount = 0;
239
240     swarm_member = findradius(this.origin,_radius);
241
242     while(swarm_member)
243     {
244         if(swarm_member.flock_id == this.flock_id)
245         {
246             ++ccount;
247             center = center + swarm_member.origin;
248             force = force + (steerlib_repell(this, swarm_member.origin,standoff) * separation_force);
249         }
250         swarm_member = swarm_member.chain;
251     }
252
253     center = center * (1 / ccount);
254     force = force + (steerlib_arrive(this, center,_radius) * swarm_force);
255
256     return force;
257 }
258
259 /**
260     Steer towards the direction least obstructed.
261     Run four tracelines in a forward funnel, bias each diretion negative if something is found there.
262     You need to call makevectors() (or equivalent) before this function to set v_forward and v_right
263 **/
264 vector steerlib_traceavoid(entity this, float pitch,float length)
265 {
266     vector vup_left,vup_right,vdown_left,vdown_right;
267     float fup_left,fup_right,fdown_left,fdown_right;
268     vector upwish,downwish,leftwish,rightwish;
269     vector v_left,v_down;
270
271
272     v_left = v_right * -1;
273     v_down = v_up * -1;
274
275     vup_left = (v_forward + (v_left * pitch + v_up * pitch)) * length;
276     traceline(this.origin, this.origin +  vup_left,MOVE_NOMONSTERS,this);
277     fup_left = trace_fraction;
278
279     //te_lightning1(NULL,this.origin, trace_endpos);
280
281     vup_right = (v_forward + (v_right * pitch + v_up * pitch)) * length;
282     traceline(this.origin,this.origin + vup_right ,MOVE_NOMONSTERS,this);
283     fup_right = trace_fraction;
284
285     //te_lightning1(NULL,this.origin, trace_endpos);
286
287     vdown_left = (v_forward + (v_left * pitch + v_down * pitch)) * length;
288     traceline(this.origin,this.origin + vdown_left,MOVE_NOMONSTERS,this);
289     fdown_left = trace_fraction;
290
291     //te_lightning1(NULL,this.origin, trace_endpos);
292
293     vdown_right = (v_forward + (v_right * pitch + v_down * pitch)) * length;
294     traceline(this.origin,this.origin + vdown_right,MOVE_NOMONSTERS,this);
295     fdown_right = trace_fraction;
296
297     //te_lightning1(NULL,this.origin, trace_endpos);
298     upwish    = v_up    * (fup_left   + fup_right);
299     downwish  = v_down  * (fdown_left + fdown_right);
300     leftwish  = v_left  * (fup_left   + fdown_left);
301     rightwish = v_right * (fup_right  + fdown_right);
302
303     return (upwish+leftwish+downwish+rightwish) * 0.25;
304
305 }
306
307 /**
308     Steer towards the direction least obstructed.
309     Run tracelines in a forward trident, bias each direction negative if something is found there.
310 **/
311 vector steerlib_traceavoid_flat(entity this, float pitch, float length, vector vofs)
312 {
313     vector vt_left, vt_right,vt_front;
314     float f_left, f_right,f_front;
315     vector leftwish, rightwish,frontwish, v_left;
316
317     v_left = v_right * -1;
318
319
320     vt_front = v_forward * length;
321     traceline(this.origin + vofs, this.origin + vofs + vt_front,MOVE_NOMONSTERS,this);
322     f_front = trace_fraction;
323
324     vt_left = (v_forward + (v_left * pitch)) * length;
325     traceline(this.origin + vofs, this.origin + vofs + vt_left,MOVE_NOMONSTERS,this);
326     f_left = trace_fraction;
327
328     //te_lightning1(NULL,this.origin, trace_endpos);
329
330     vt_right = (v_forward + (v_right * pitch)) * length;
331     traceline(this.origin + vofs, this.origin + vofs + vt_right ,MOVE_NOMONSTERS,this);
332     f_right = trace_fraction;
333
334     //te_lightning1(NULL,this.origin, trace_endpos);
335
336     leftwish  = v_left    * f_left;
337     rightwish = v_right   * f_right;
338     frontwish = v_forward * f_front;
339
340     return normalize(leftwish + rightwish + frontwish);
341 }
342
343 bool beamsweep_badpoint(vector point, bool waterok)
344 {
345     if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
346         return true;
347
348     int pc = pointcontents(point);
349     int pc2 = pointcontents(point - '0 0 1');
350     
351     if(pc == CONTENT_EMPTY && pc2 == CONTENT_SOLID)
352         return false;
353     if(pc == CONTENT_EMPTY && pc2 == CONTENT_WATER && waterok)
354         return false;
355     if(pc == CONTENT_WATER && waterok)
356         return false;
357     return true;
358
359     /*switch(pc)
360     {
361         case CONTENT_SOLID: break;
362         case CONTENT_SLIME: break;
363         case CONTENT_LAVA:  break;
364
365         case CONTENT_SKY:
366             return true;
367
368         case CONTENT_EMPTY:
369             if (pc2 == CONTENT_SOLID)
370                 return 0;
371
372             if (pc2 == CONTENT_WATER)
373                 if(waterok)
374                     return 0;
375
376             break;
377
378         case CONTENT_WATER:
379             if(waterok)
380                 return 0;
381
382             break;
383     }
384
385     return true;*/
386 }
387
388 //#define BEAMSTEER_VISUAL
389 float beamsweep(entity this, vector from, vector dir,float length, float step,float step_up, float step_down)
390 {
391     float i;
392     vector a, b, u, d;
393
394     u = '0 0 1' * step_up;
395     d = '0 0 1' * step_down;
396
397     traceline(from + u, from - d,MOVE_NORMAL,this);
398     if(trace_fraction == 1.0)
399         return 0;
400
401     if(beamsweep_badpoint(trace_endpos,0))
402         return 0;
403
404     a = trace_endpos;
405     for(i = 0; i < length; i += step)
406     {
407
408         b = a + dir * step;
409         tracebox(a + u,'-4 -4 -4','4 4 4', b + u,MOVE_NORMAL,this);
410         if(trace_fraction != 1.0)
411             return i / length;
412
413         traceline(b + u, b - d,MOVE_NORMAL,this);
414         if(trace_fraction == 1.0)
415             return i / length;
416
417         if(beamsweep_badpoint(trace_endpos,0))
418             return i / length;
419 #ifdef BEAMSTEER_VISUAL
420         te_lightning1(NULL,a+u,b+u);
421         te_lightning1(NULL,b+u,b-d);
422 #endif
423         a = trace_endpos;
424     }
425
426     return 1;
427 }
428
429 vector steerlib_beamsteer(entity this, vector dir, float length, float step, float step_up, float step_down)
430 {
431     float bm_forward, bm_right, bm_left,p;
432     vector vr,vl;
433
434     dir.z *= 0.15;
435     vr = vectoangles(dir);
436     //vr_x *= -1;
437
438     tracebox(this.origin + '0 0 1' * step_up, this.mins, this.maxs, ('0 0 1' * step_up) + this.origin +  (dir * length), MOVE_NOMONSTERS, this);
439     if(trace_fraction == 1.0)
440     {
441         //te_lightning1(this,this.origin,this.origin +  (dir * length));
442         return dir;
443     }
444
445     makevectors(vr);
446     bm_forward = beamsweep(this, this.origin, v_forward, length, step, step_up, step_down);
447
448     vr = normalize(v_forward + v_right * 0.125);
449     vl = normalize(v_forward - v_right * 0.125);
450
451     bm_right = beamsweep(this, this.origin, vr, length, step, step_up, step_down);
452     bm_left  = beamsweep(this, this.origin, vl, length, step, step_up, step_down);
453
454
455     p = bm_left + bm_right;
456     if(p == 2)
457     {
458         //te_lightning1(this,this.origin + '0 0 32',this.origin + '0 0 32' + vr * length);
459         //te_lightning1(this.tur_head,this.origin + '0 0 32',this.origin + '0 0 32' + vl * length);
460
461         return v_forward;
462     }
463
464     p = 2 - p;
465
466     vr = normalize(v_forward + v_right * p);
467     vl = normalize(v_forward - v_right * p);
468     bm_right = beamsweep(this, this.origin, vr, length, step, step_up, step_down);
469     bm_left  = beamsweep(this, this.origin, vl, length, step, step_up, step_down);
470
471
472     if(bm_left + bm_right < 0.15)
473     {
474         vr = normalize((v_forward*-1) + v_right * 0.90);
475         vl = normalize((v_forward*-1) - v_right * 0.90);
476
477         bm_right = beamsweep(this, this.origin, vr, length, step, step_up, step_down);
478         bm_left  = beamsweep(this, this.origin, vl, length, step, step_up, step_down);
479     }
480
481     //te_lightning1(this,this.origin + '0 0 32',this.origin + '0 0 32' + vr * length);
482     //te_lightning1(this.tur_head,this.origin + '0 0 32',this.origin + '0 0 32' + vl * length);
483
484     bm_forward *= bm_forward;
485     bm_right   *= bm_right;
486     bm_left    *= bm_left;
487
488     vr = vr * bm_right;
489     vl = vl * bm_left;
490
491     return normalize(vr + vl);
492
493 }
494
495
496 //////////////////////////////////////////////
497 //     Testting                             //
498 // Everything below this point is a mess :D //
499 //////////////////////////////////////////////
500 //#define TLIBS_TETSLIBS
501 #ifdef TLIBS_TETSLIBS
502 void flocker_die(entity this)
503 {
504         Send_Effect(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1);
505
506     this.owner.cnt += 1;
507     this.owner = NULL;
508
509     this.nextthink = time;
510     setthink(this, SUB_Remove);
511 }
512
513
514 void flocker_think(entity this)
515 {
516     vector dodgemove,swarmmove;
517     vector reprellmove,wandermove,newmove;
518
519     this.angles_x = this.angles.x * -1;
520     makevectors(this.angles);
521     this.angles_x = this.angles.x * -1;
522
523     dodgemove   = steerlib_traceavoid(this, 0.35,1000);
524     swarmmove   = steerlib_flock(this, 500,75,700,500);
525     reprellmove = steerlib_repell(this, this.owner.enemy.origin+this.enemy.velocity,2000) * 700;
526
527     if(dodgemove == '0 0 0')
528     {
529         this.pos1 = steerlib_wander(this, 0.5,0.1,this.pos1);
530         wandermove  = this.pos1 * 50;
531     }
532     else
533         this.pos1 = normalize(this.velocity);
534
535     dodgemove = dodgemove * vlen(this.velocity) * 5;
536
537     newmove = swarmmove + reprellmove + wandermove + dodgemove;
538     this.velocity = movelib_inertmove_byspeed(this, newmove,300,0.2,0.9);
539     //this.velocity  = movelib_inertmove(this, dodgemove,0.65);
540
541     this.velocity = movelib_dragvec(this, 0.01,0.6);
542
543     this.angles = vectoangles(this.velocity);
544
545     if(this.health <= 0)
546         flocker_die(this);
547     else
548         this.nextthink = time + 0.1;
549 }
550
551 MODEL(FLOCKER, "models/turrets/rocket.md3");
552
553 void spawn_flocker(entity this)
554 {
555     entity flocker = new(flocker);
556
557     setorigin(flocker, this.origin + '0 0 32');
558     setmodel (flocker, MDL_FLOCKER);
559     setsize (flocker, '-3 -3 -3', '3 3 3');
560
561     flocker.flock_id   = this.flock_id;
562     flocker.owner      = this;
563     setthink(flocker, flocker_think);
564     flocker.nextthink  = time + random() * 5;
565     PROJECTILE_MAKETRIGGER(flocker);
566     set_movetype(flocker, MOVETYPE_BOUNCEMISSILE);
567     flocker.effects    = EF_LOWPRECISION;
568     flocker.velocity   = randomvec() * 300;
569     flocker.angles     = vectoangles(flocker.velocity);
570     flocker.health     = 10;
571     flocker.pos1      = normalize(flocker.velocity + randomvec() * 0.1);
572
573     this.cnt = this.cnt -1;
574
575 }
576
577 void flockerspawn_think(entity this)
578 {
579     if(this.cnt > 0)
580         spawn_flocker(this);
581
582     this.nextthink = time + this.delay;
583
584 }
585
586 void flocker_hunter_think(entity this)
587 {
588     vector dodgemove,attractmove,newmove;
589     entity ee;
590
591     this.angles_x = this.angles.x * -1;
592     makevectors(this.angles);
593     this.angles_x = this.angles.x * -1;
594
595     if(this.enemy)
596     if(vdist(this.enemy.origin - this.origin, <, 64))
597     {
598         ee = this.enemy;
599         ee.health = -1;
600         this.enemy = NULL;
601
602     }
603
604     if(!this.enemy)
605     {
606         FOREACH_ENTITY_FLOAT(flock_id, this.flock_id,
607         {
608             if(it == this.owner || it == ee)
609                 continue;
610
611             if(!this.enemy || vlen2(this.origin - it.origin) > vlen2(this.origin - this.enemy.origin))
612                 this.enemy = it;
613         });
614     }
615
616     if(this.enemy)
617         attractmove = steerlib_attract(this, this.enemy.origin+this.enemy.velocity * 0.1,5000) * 1250;
618     else
619         attractmove = normalize(this.velocity) * 200;
620
621     dodgemove = steerlib_traceavoid(this, 0.35,1500) * vlen(this.velocity);
622
623     newmove = dodgemove + attractmove;
624     this.velocity = movelib_inertmove_byspeed(this, newmove,1250,0.3,0.7);
625     this.velocity = movelib_dragvec(this, 0.01,0.5);
626
627     this.angles = vectoangles(this.velocity);
628     this.nextthink = time + 0.1;
629 }
630
631
632 float globflockcnt;
633 spawnfunc(flockerspawn)
634 {
635     ++globflockcnt;
636
637     if(!this.cnt)      this.cnt = 20;
638     if(!this.delay)    this.delay = 0.25;
639     if(!this.flock_id) this.flock_id = globflockcnt;
640
641     setthink(this, flockerspawn_think);
642     this.nextthink = time + 0.25;
643
644     this.enemy = new(FLock Hunter);
645
646     setmodel(this.enemy, MDL_FLOCKER);
647     setorigin(this.enemy, this.origin + '0 0 768' + (randomvec() * 128));
648
649     this.enemy.scale     = 3;
650     this.enemy.effects   = EF_LOWPRECISION;
651     set_movetype(this.enemy, MOVETYPE_BOUNCEMISSILE);
652     PROJECTILE_MAKETRIGGER(this.enemy);
653     setthink(this.enemy, flocker_hunter_think);
654     this.enemy.nextthink = time + 10;
655     this.enemy.flock_id  = this.flock_id;
656     this.enemy.owner     = this;
657 }
658 #endif
659
660
661