Revert "Merge branch 'TimePath/bot_api' into 'master'\r"
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / steerlib.qc
1 #if defined(CSQC)
2 #elif defined(MENUQC)
3 #elif defined(SVQC)
4     #include "../dpdefs/progsdefs.qh"
5     #include "../dpdefs/dpextensions.qh"
6 #endif
7
8 /**
9     Uniform pull towards a point
10 **/
11 vector steerlib_pull(vector point)
12 {SELFPARAM();
13     return normalize(point - self.origin);
14 }
15
16 /**
17     Uniform push from a point
18 **/
19 #define steerlib_push(point) normalize(self.origin - point)
20 /*
21 vector steerlib_push(vector point)
22 {
23     return normalize(self.origin - point);
24 }
25 */
26 /**
27     Pull toward a point, The further away, the stronger the pull.
28 **/
29 vector steerlib_arrive(vector point,float maximal_distance)
30 {SELFPARAM();
31     float distance;
32     vector direction;
33
34     distance = bound(0.001,vlen(self.origin - point),maximal_distance);
35     direction = normalize(point - self.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(vector point, float maximal_distance)
43 {SELFPARAM();
44     float distance;
45     vector direction;
46
47     distance = bound(0.001,vlen(self.origin - point),maximal_distance);
48     direction = normalize(point - self.origin);
49
50     return  direction * (1-(distance / maximal_distance));
51 }
52
53 vector steerlib_attract2(vector point, float min_influense,float max_distance,float max_influense)
54 {SELFPARAM();
55     float distance;
56     vector direction;
57     float influense;
58
59     distance  = bound(0.00001,vlen(self.origin - point),max_distance);
60     direction = normalize(point - self.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(self.origin - point);
78
79     distance = bound(0.001,distance,maximal_distance);
80
81     target_direction = normalize(point - self.origin);
82     current_direction = normalize(self.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(vector point,float maximal_distance)
102 {SELFPARAM();
103     float distance;
104     vector direction;
105
106     distance = bound(0.001,vlen(self.origin - point),maximal_distance);
107     direction = normalize(self.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(vector point,float ideal_distance)
116 {SELFPARAM();
117     float distance;
118     vector direction;
119
120     distance = vlen(self.origin - point);
121
122
123     if(distance < ideal_distance)
124     {
125         direction = normalize(self.origin - point);
126         return direction * (distance / ideal_distance);
127     }
128
129     direction = normalize(point - self.origin);
130     return direction * (ideal_distance / distance);
131
132 }
133
134 /**
135     A random heading in a forward halfcicrle
136
137     use like:
138     self.target = steerlib_wander(256,32,self.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(float range,float tresh,vector oldpoint)
143 {SELFPARAM();
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 = self.origin + v_forward * 128;
153     wander_point = wander_point + randomvec() * (range * 128) - randomvec() * (range * 128);
154
155     return normalize(wander_point - self.origin);
156 }
157
158 /**
159     Dodge a point. dont work to well.
160 **/
161 vector steerlib_dodge(vector point,vector dodge_dir,float min_distance)
162 {SELFPARAM();
163     float distance;
164
165     distance = max(vlen(self.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(float _radius, float standoff,float separation_force,float flock_force)
178 {SELFPARAM();
179     entity flock_member;
180     vector push = '0 0 0', pull = '0 0 0';
181     float ccount = 0;
182
183     flock_member = findradius(self.origin, _radius);
184     while(flock_member)
185     {
186         if(flock_member != self)
187         if(flock_member.flock_id == self.flock_id)
188         {
189             ++ccount;
190             push = push + (steerlib_repell(flock_member.origin,standoff) * separation_force);
191             pull = pull + (steerlib_arrive(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(float _radius, float standoff,float separation_force,float flock_force)
204 {SELFPARAM();
205     entity flock_member;
206     vector push = '0 0 0', pull = '0 0 0';
207     float ccount = 0;
208
209     flock_member = findradius(self.origin,_radius);
210     while(flock_member)
211     {
212         if(flock_member != self)
213         if(flock_member.flock_id == self.flock_id)
214         {
215             ++ccount;
216             push = push + (steerlib_repell(flock_member.origin, standoff) * separation_force);
217             pull = pull + (steerlib_arrive(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(float _radius, float standoff,float separation_force,float swarm_force)
235 {SELFPARAM();
236     entity swarm_member;
237     vector force = '0 0 0', center = '0 0 0';
238     float ccount = 0;
239
240     swarm_member = findradius(self.origin,_radius);
241
242     while(swarm_member)
243     {
244         if(swarm_member.flock_id == self.flock_id)
245         {
246             ++ccount;
247             center = center + swarm_member.origin;
248             force = force + (steerlib_repell(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(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(float pitch,float length)
265 {SELFPARAM();
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(self.origin, self.origin +  vup_left,MOVE_NOMONSTERS,self);
277     fup_left = trace_fraction;
278
279     //te_lightning1(world,self.origin, trace_endpos);
280
281     vup_right = (v_forward + (v_right * pitch + v_up * pitch)) * length;
282     traceline(self.origin,self.origin + vup_right ,MOVE_NOMONSTERS,self);
283     fup_right = trace_fraction;
284
285     //te_lightning1(world,self.origin, trace_endpos);
286
287     vdown_left = (v_forward + (v_left * pitch + v_down * pitch)) * length;
288     traceline(self.origin,self.origin + vdown_left,MOVE_NOMONSTERS,self);
289     fdown_left = trace_fraction;
290
291     //te_lightning1(world,self.origin, trace_endpos);
292
293     vdown_right = (v_forward + (v_right * pitch + v_down * pitch)) * length;
294     traceline(self.origin,self.origin + vdown_right,MOVE_NOMONSTERS,self);
295     fdown_right = trace_fraction;
296
297     //te_lightning1(world,self.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(float pitch, float length, vector vofs)
312 {SELFPARAM();
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(self.origin + vofs, self.origin + vofs + vt_front,MOVE_NOMONSTERS,self);
322     f_front = trace_fraction;
323
324     vt_left = (v_forward + (v_left * pitch)) * length;
325     traceline(self.origin + vofs, self.origin + vofs + vt_left,MOVE_NOMONSTERS,self);
326     f_left = trace_fraction;
327
328     //te_lightning1(world,self.origin, trace_endpos);
329
330     vt_right = (v_forward + (v_right * pitch)) * length;
331     traceline(self.origin + vofs, self.origin + vofs + vt_right ,MOVE_NOMONSTERS,self);
332     f_right = trace_fraction;
333
334     //te_lightning1(world,self.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 float beamsweep_badpoint(vector point,float waterok)
344 {
345     float pc,pc2;
346
347     if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
348         return 1;
349
350     pc  = pointcontents(point);
351     pc2 = pointcontents(point - '0 0 1');
352
353     switch(pc)
354     {
355         case CONTENT_SOLID: break;
356         case CONTENT_SLIME: break;
357         case CONTENT_LAVA:  break;
358
359         case CONTENT_SKY:
360             return 1;
361
362         case CONTENT_EMPTY:
363             if (pc2 == CONTENT_SOLID)
364                 return 0;
365
366             if (pc2 == CONTENT_WATER)
367                 if(waterok)
368                     return 0;
369
370             break;
371
372         case CONTENT_WATER:
373             if(waterok)
374                 return 0;
375
376             break;
377     }
378
379     return 1;
380 }
381
382 //#define BEAMSTEER_VISUAL
383 float beamsweep(vector from, vector dir,float length, float step,float step_up, float step_down)
384 {SELFPARAM();
385     float i;
386     vector a,b,u,d;
387
388     u = '0 0 1' * step_up;
389     d = '0 0 1' * step_down;
390
391     traceline(from + u, from - d,MOVE_NORMAL,self);
392     if(trace_fraction == 1.0)
393         return 0;
394
395     if(beamsweep_badpoint(trace_endpos,0))
396         return 0;
397
398     a = trace_endpos;
399     for(i = 0; i < length; i += step)
400     {
401
402         b = a + dir * step;
403         tracebox(a + u,'-4 -4 -4','4 4 4', b + u,MOVE_NORMAL,self);
404         if(trace_fraction != 1.0)
405             return i / length;
406
407         traceline(b + u, b - d,MOVE_NORMAL,self);
408         if(trace_fraction == 1.0)
409             return i / length;
410
411         if(beamsweep_badpoint(trace_endpos,0))
412             return i / length;
413 #ifdef BEAMSTEER_VISUAL
414         te_lightning1(world,a+u,b+u);
415         te_lightning1(world,b+u,b-d);
416 #endif
417         a = trace_endpos;
418     }
419
420     return 1;
421 }
422
423 vector steerlib_beamsteer(vector dir, float length, float step, float step_up, float step_down)
424 {SELFPARAM();
425     float bm_forward, bm_right, bm_left,p;
426     vector vr,vl;
427
428     dir.z *= 0.15;
429     vr = vectoangles(dir);
430     //vr_x *= -1;
431
432     tracebox(self.origin + '0 0 1' * step_up, self.mins, self.maxs, ('0 0 1' * step_up) + self.origin +  (dir * length), MOVE_NOMONSTERS, self);
433     if(trace_fraction == 1.0)
434     {
435         //te_lightning1(self,self.origin,self.origin +  (dir * length));
436         return dir;
437     }
438
439
440
441
442     makevectors(vr);
443     bm_forward = beamsweep(self.origin, v_forward, length, step, step_up, step_down);
444
445     vr = normalize(v_forward + v_right * 0.125);
446     vl = normalize(v_forward - v_right * 0.125);
447
448     bm_right = beamsweep(self.origin, vr, length, step, step_up, step_down);
449     bm_left  = beamsweep(self.origin, vl, length, step, step_up, step_down);
450
451
452     p = bm_left + bm_right;
453     if(p == 2)
454     {
455         //te_lightning1(self,self.origin + '0 0 32',self.origin + '0 0 32' + vr * length);
456         //te_lightning1(self.tur_head,self.origin + '0 0 32',self.origin + '0 0 32' + vl * length);
457
458         return v_forward;
459     }
460
461     p = 2 - p;
462
463     vr = normalize(v_forward + v_right * p);
464     vl = normalize(v_forward - v_right * p);
465     bm_right = beamsweep(self.origin, vr, length, step, step_up, step_down);
466     bm_left  = beamsweep(self.origin, vl, length, step, step_up, step_down);
467
468
469     if(bm_left + bm_right < 0.15)
470     {
471         vr = normalize((v_forward*-1) + v_right * 0.75);
472         vl = normalize((v_forward*-1) - v_right * 0.75);
473
474         bm_right = beamsweep(self.origin, vr, length, step, step_up, step_down);
475         bm_left  = beamsweep(self.origin, vl, length, step, step_up, step_down);
476     }
477
478     //te_lightning1(self,self.origin + '0 0 32',self.origin + '0 0 32' + vr * length);
479     //te_lightning1(self.tur_head,self.origin + '0 0 32',self.origin + '0 0 32' + vl * length);
480
481     bm_forward *= bm_forward;
482     bm_right   *= bm_right;
483     bm_left    *= bm_left;
484
485     vr = vr * bm_right;
486     vl = vl * bm_left;
487
488     return normalize(vr + vl);
489
490 }
491
492
493 //////////////////////////////////////////////
494 //     Testting                             //
495 // Everything below this point is a mess :D //
496 //////////////////////////////////////////////
497 //#define TLIBS_TETSLIBS
498 #ifdef TLIBS_TETSLIBS
499 void flocker_die()
500 {SELFPARAM();
501         Send_Effect(EFFECT_ROCKET_EXPLODE, self.origin, '0 0 0', 1);
502
503     self.owner.cnt += 1;
504     self.owner = world;
505
506     self.nextthink = time;
507     self.think = SUB_Remove;
508 }
509
510
511 void flocker_think()
512 {SELFPARAM();
513     vector dodgemove,swarmmove;
514     vector reprellmove,wandermove,newmove;
515
516     self.angles_x = self.angles.x * -1;
517     makevectors(self.angles);
518     self.angles_x = self.angles.x * -1;
519
520     dodgemove   = steerlib_traceavoid(0.35,1000);
521     swarmmove   = steerlib_flock(500,75,700,500);
522     reprellmove = steerlib_repell(self.owner.enemy.origin+self.enemy.velocity,2000) * 700;
523
524     if(vlen(dodgemove) == 0)
525     {
526         self.pos1 = steerlib_wander(0.5,0.1,self.pos1);
527         wandermove  = self.pos1 * 50;
528     }
529     else
530         self.pos1 = normalize(self.velocity);
531
532     dodgemove = dodgemove * vlen(self.velocity) * 5;
533
534     newmove = swarmmove + reprellmove + wandermove + dodgemove;
535     self.velocity = movelib_inertmove_byspeed(newmove,300,0.2,0.9);
536     //self.velocity  = movelib_inertmove(dodgemove,0.65);
537
538     self.velocity = movelib_dragvec(0.01,0.6);
539
540     self.angles = vectoangles(self.velocity);
541
542     if(self.health <= 0)
543         flocker_die();
544     else
545         self.nextthink = time + 0.1;
546 }
547
548 MODEL(FLOCKER, "models/turrets/rocket.md3");
549
550 void spawn_flocker()
551 {SELFPARAM();
552     entity flocker;
553
554     flocker = spawn ();
555
556     setorigin(flocker, self.origin + '0 0 32');
557     setmodel (flocker, MDL_FLOCKER);
558     setsize (flocker, '-3 -3 -3', '3 3 3');
559
560     flocker.flock_id   = self.flock_id;
561     flocker.classname  = "flocker";
562     flocker.owner      = self;
563     flocker.think      = flocker_think;
564     flocker.nextthink  = time + random() * 5;
565     PROJECTILE_MAKETRIGGER(flocker);
566     flocker.movetype   = 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     self.cnt = self.cnt -1;
574
575 }
576
577 void flockerspawn_think()
578 {SELFPARAM();
579
580
581     if(self.cnt > 0)
582         spawn_flocker();
583
584     self.nextthink = time + self.delay;
585
586 }
587
588 void flocker_hunter_think()
589 {SELFPARAM();
590     vector dodgemove,attractmove,newmove;
591     entity e,ee;
592     float d,bd;
593
594     self.angles_x = self.angles.x * -1;
595     makevectors(self.angles);
596     self.angles_x = self.angles.x * -1;
597
598     if(self.enemy)
599     if(vlen(self.enemy.origin - self.origin) < 64)
600     {
601         ee = self.enemy;
602         ee.health = -1;
603         self.enemy = world;
604
605     }
606
607     if(!self.enemy)
608     {
609         e = findchainfloat(flock_id,self.flock_id);
610         while(e)
611         {
612             d = vlen(self.origin - e.origin);
613
614             if(e != self.owner)
615             if(e != ee)
616             if(d > bd)
617             {
618                 self.enemy = e;
619                 bd = d;
620             }
621             e = e.chain;
622         }
623     }
624
625     if(self.enemy)
626         attractmove = steerlib_attract(self.enemy.origin+self.enemy.velocity * 0.1,5000) * 1250;
627     else
628         attractmove = normalize(self.velocity) * 200;
629
630     dodgemove = steerlib_traceavoid(0.35,1500) * vlen(self.velocity);
631
632     newmove = dodgemove + attractmove;
633     self.velocity = movelib_inertmove_byspeed(newmove,1250,0.3,0.7);
634     self.velocity = movelib_dragvec(0.01,0.5);
635
636
637     self.angles = vectoangles(self.velocity);
638     self.nextthink = time + 0.1;
639 }
640
641
642 float globflockcnt;
643 spawnfunc(flockerspawn)
644 {SELFPARAM();
645     ++globflockcnt;
646
647     if(!self.cnt)      self.cnt = 20;
648     if(!self.delay)    self.delay = 0.25;
649     if(!self.flock_id) self.flock_id = globflockcnt;
650
651     self.think     = flockerspawn_think;
652     self.nextthink = time + 0.25;
653
654     self.enemy = spawn();
655
656     setmodel(self.enemy, MDL_FLOCKER);
657     setorigin(self.enemy,self.origin + '0 0 768' + (randomvec() * 128));
658
659     self.enemy.classname = "FLock Hunter";
660     self.enemy.scale     = 3;
661     self.enemy.effects   = EF_LOWPRECISION;
662     self.enemy.movetype  = MOVETYPE_BOUNCEMISSILE;
663     PROJECTILE_MAKETRIGGER(self.enemy);
664     self.enemy.think     = flocker_hunter_think;
665     self.enemy.nextthink = time + 10;
666     self.enemy.flock_id  = self.flock_id;
667     self.enemy.owner     = self;
668 }
669 #endif
670
671
672