]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/tturrets/units/unit_walker.qc
Merge branch 'master' into sev/luma_revisions
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / tturrets / units / unit_walker.qc
1 #include "../../_all.qh"
2
3 #include "../../movelib.qh"
4 #include "../../steerlib.qh"
5
6 const float ANIM_NO         = 0;
7 const float ANIM_TURN       = 1;
8 const float ANIM_WALK       = 2;
9 const float ANIM_RUN        = 3;
10 const float ANIM_STRAFE_L   = 4;
11 const float ANIM_STRAFE_R   = 5;
12 const float ANIM_JUMP       = 6;
13 const float ANIM_LAND       = 7;
14 const float ANIM_PAIN       = 8;
15 const float ANIM_MEELE      = 9;
16 const float ANIM_SWIM       = 10;
17 const float ANIM_ROAM       = 11;
18 .float animflag;
19
20 const vector WALKER_MIN = '-70 -70 0';
21 const vector WALKER_MAX = '70 70 95';
22
23 #define WALKER_PATH(s,e) pathlib_astar(s,e)
24
25 float walker_firecheck()
26 {
27     if (self.animflag == ANIM_MEELE)
28         return 0;
29
30     return turret_stdproc_firecheck();
31 }
32
33 void walker_meele_do_dmg()
34 {
35     vector where;
36     entity e;
37
38     makevectors(self.angles);
39     where = self.origin + v_forward * 128;
40
41     e = findradius(where,32);
42     while (e)
43     {
44         if (turret_validate_target(self, e, self.target_validate_flags))
45             if (e != self && e.owner != self)
46                 Damage(e, self, self, autocvar_g_turrets_unit_walker_std_meele_dmg, DEATH_TURRET_WALK_MEELE, '0 0 0', v_forward * autocvar_g_turrets_unit_walker_std_meele_force);
47
48         e = e.chain;
49     }
50 }
51
52 void walker_setnoanim()
53 {
54     turrets_setframe(ANIM_NO, false);
55     self.animflag = self.frame;
56 }
57 void walker_rocket_explode()
58 {
59     RadiusDamage (self, self.owner, autocvar_g_turrets_unit_walker_std_rocket_dmg, 0, autocvar_g_turrets_unit_walker_std_rocket_radius, self, world, autocvar_g_turrets_unit_walker_std_rocket_force, DEATH_TURRET_WALK_ROCKET, world);
60
61     remove (self);
62 }
63
64 void walker_rocket_damage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector vforce)
65 {
66     self.health = self.health - damage;
67     self.velocity = self.velocity + vforce;
68
69     if (self.health <= 0)
70         W_PrepareExplosionByDamage(self.owner, walker_rocket_explode);
71 }
72
73 #define WALKER_ROCKET_MOVE movelib_move_simple(newdir, autocvar_g_turrets_unit_walker_std_rocket_speed, autocvar_g_turrets_unit_walker_std_rocket_turnrate); UpdateCSQCProjectile(self)
74 void walker_rocket_loop();
75 void walker_rocket_think()
76 {
77     vector newdir;
78     float edist;
79     float itime;
80     float m_speed;
81
82     self.nextthink = time;
83
84     edist = vlen(self.enemy.origin - self.origin);
85
86     // Simulate crude guidance
87     if (self.cnt < time)
88     {
89         if (edist < 1000)
90             self.tur_shotorg = randomvec() * min(edist, 64);
91         else
92             self.tur_shotorg = randomvec() * min(edist, 256);
93
94         self.cnt = time + 0.5;
95     }
96
97     if (edist < 128)
98         self.tur_shotorg = '0 0 0';
99
100     if (self.tur_health < time)
101     {
102         self.think      = walker_rocket_explode;
103         self.nextthink  = time;
104         return;
105     }
106
107     if (self.shot_dmg != 1337 && random() < 0.01)
108     {
109         walker_rocket_loop();
110         return;
111     }
112
113     m_speed = vlen(self.velocity);
114
115     // Enemy dead? just keep on the current heading then.
116     if (self.enemy == world || self.enemy.deadflag != DEAD_NO)
117         self.enemy = world;
118
119     if (self.enemy)
120     {
121         itime = max(edist / m_speed, 1);
122         newdir = steerlib_pull(self.enemy.origin + self.tur_shotorg);
123     }
124     else
125         newdir  = normalize(self.velocity);
126
127     WALKER_ROCKET_MOVE;
128 }
129
130 void walker_rocket_loop3()
131 {
132     vector newdir;
133     self.nextthink = time;
134
135     if (self.tur_health < time)
136     {
137         self.think = walker_rocket_explode;
138         return;
139     }
140
141     if (vlen(self.origin - self.tur_shotorg) < 100 )
142     {
143         self.think = walker_rocket_think;
144         return;
145     }
146
147     newdir = steerlib_pull(self.tur_shotorg);
148     WALKER_ROCKET_MOVE;
149
150     self.angles = vectoangles(self.velocity);
151 }
152
153 void walker_rocket_loop2()
154 {
155     vector newdir;
156
157     self.nextthink = time;
158
159     if (self.tur_health < time)
160     {
161         self.think = walker_rocket_explode;
162         return;
163     }
164
165     if (vlen(self.origin - self.tur_shotorg) < 100 )
166     {
167         self.tur_shotorg = self.origin - '0 0 200';
168         self.think = walker_rocket_loop3;
169         return;
170     }
171
172     newdir = steerlib_pull(self.tur_shotorg);
173     WALKER_ROCKET_MOVE;
174 }
175
176 void walker_rocket_loop()
177 {
178     self.nextthink = time;
179     self.tur_shotorg = self.origin + '0 0 300';
180     self.think = walker_rocket_loop2;
181     self.shot_dmg = 1337;
182 }
183
184 void walker_fire_rocket(vector org)
185 {
186     entity rocket;
187
188     fixedmakevectors(self.angles);
189
190     te_explosion (org);
191
192     rocket = spawn ();
193     setorigin(rocket, org);
194
195     sound (self, CH_WEAPON_A, "weapons/hagar_fire.wav", VOL_BASE, ATTEN_NORM);
196     setsize (rocket, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
197
198     rocket.classname          = "walker_rocket";
199     rocket.owner              = self;
200     rocket.bot_dodge          = true;
201     rocket.bot_dodgerating    = 50;
202     rocket.takedamage         = DAMAGE_YES;
203     rocket.damageforcescale   = 2;
204     rocket.health             = 25;
205     rocket.tur_shotorg        = randomvec() * 512;
206     rocket.cnt                = time + 1;
207     rocket.enemy              = self.enemy;
208
209     if (random() < 0.01)
210         rocket.think          = walker_rocket_loop;
211     else
212         rocket.think          = walker_rocket_think;
213
214     rocket.event_damage       = walker_rocket_damage;
215
216     rocket.nextthink          = time;
217     rocket.movetype           = MOVETYPE_FLY;
218     rocket.velocity           = normalize((v_forward + v_up * 0.5) + (randomvec() * 0.2)) * autocvar_g_turrets_unit_walker_std_rocket_speed;
219     rocket.angles             = vectoangles(rocket.velocity);
220     rocket.touch              = walker_rocket_explode;
221     rocket.flags              = FL_PROJECTILE;
222     rocket.solid              = SOLID_BBOX;
223     rocket.tur_health         = time + 9;
224     rocket.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_GUIDED_HEAT;
225
226     CSQCProjectile(rocket, false, PROJECTILE_ROCKET, false); // no culling, has fly sound
227 }
228
229 .vector enemy_last_loc;
230 .float enemy_last_time;
231 void walker_move_to(vector _target, float _dist)
232 {
233     switch (self.waterlevel)
234     {
235     case WATERLEVEL_NONE:
236         if (_dist > 500)
237             self.animflag = ANIM_RUN;
238         else
239             self.animflag = ANIM_WALK;
240     case WATERLEVEL_WETFEET:
241     case WATERLEVEL_SWIMMING:
242         if (self.animflag != ANIM_SWIM)
243             self.animflag = ANIM_WALK;
244         else
245             self.animflag = ANIM_SWIM;
246         break;
247     case WATERLEVEL_SUBMERGED:
248         self.animflag = ANIM_SWIM;
249     }
250
251     self.moveto = _target;
252     self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
253
254     if(self.enemy)
255     {
256         self.enemy_last_loc = _target;
257         self.enemy_last_time = time;
258     }
259 }
260
261 //#define WALKER_FANCYPATHING
262
263 void walker_move_path()
264 {
265 #ifdef WALKER_FANCYPATHING
266     // Are we close enougth to a path node to switch to the next?
267     if (vlen(self.origin  - self.pathcurrent.origin) < 64)
268         if (self.pathcurrent.path_next == world)
269         {
270             // Path endpoint reached
271             pathlib_deletepath(self.pathcurrent.owner);
272             self.pathcurrent = world;
273
274             if (self.pathgoal)
275             {
276                 if (self.pathgoal.use)
277                     self.pathgoal.use();
278
279                 if (self.pathgoal.enemy)
280                 {
281                     self.pathcurrent = WALKER_PATH(self.pathgoal.origin,self.pathgoal.enemy.origin);
282                     self.pathgoal = self.pathgoal.enemy;
283                 }
284             }
285             else
286                 self.pathgoal = world;
287         }
288         else
289             self.pathcurrent = self.pathcurrent.path_next;
290
291     self.moveto = self.pathcurrent.origin;
292     self.steerto = steerlib_attract2(self.moveto,0.5,500,0.95);
293     walker_move_to(self.moveto, 0);
294
295 #else
296     if (vlen(self.origin - self.pathcurrent.origin) < 64)
297         self.pathcurrent = self.pathcurrent.enemy;
298
299     if(!self.pathcurrent)
300         return;
301
302     self.moveto = self.pathcurrent.origin;
303     self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
304     walker_move_to(self.moveto, 0);
305 #endif
306 }
307
308 .float idletime;
309 void walker_postthink()
310 {
311     fixedmakevectors(self.angles);
312
313     if (self.spawnflags & TSF_NO_PATHBREAK && self.pathcurrent)
314         walker_move_path();
315     else if (self.enemy == world)
316     {
317         if(self.pathcurrent)
318             walker_move_path();
319         else
320         {
321             if(self.enemy_last_time != 0)
322             {
323                 if(vlen(self.origin - self.enemy_last_loc) < 128 || time - self.enemy_last_time > 10)
324                     self.enemy_last_time = 0;
325                 else
326                     walker_move_to(self.enemy_last_loc, 0);
327             }
328             else
329             {
330                 if(self.animflag != ANIM_NO)
331                 {
332                     traceline(self.origin + '0 0 64', self.origin + '0 0 64' + v_forward * 128, MOVE_NORMAL, self);
333
334                     if(trace_fraction != 1.0)
335                         self.tur_head.idletime = -1337;
336                     else
337                     {
338                         traceline(trace_endpos, trace_endpos - '0 0 256', MOVE_NORMAL, self);
339                         if(trace_fraction == 1.0)
340                             self.tur_head.idletime = -1337;
341                     }
342
343                     if(self.tur_head.idletime == -1337)
344                     {
345                         self.moveto = self.origin + randomvec() * 256;
346                         self.tur_head.idletime = 0;
347                     }
348
349                     self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1;
350                     self.moveto_z = self.origin.z + 64;
351                     walker_move_to(self.moveto, 0);
352                 }
353
354                 if(self.idletime < time)
355                 {
356                     if(random() < 0.5 || !(self.spawnflags & TSL_ROAM))
357                     {
358                         self.idletime = time + 1 + random() * 5;
359                         self.moveto = self.origin;
360                         self.animflag = ANIM_NO;
361                     }
362                     else
363                     {
364                         self.animflag = ANIM_WALK;
365                         self.idletime = time + 4 + random() * 2;
366                         self.moveto = self.origin + randomvec() * 256;
367                         self.tur_head.moveto = self.moveto;
368                         self.tur_head.idletime = 0;
369                     }
370                 }
371             }
372         }
373     }
374     else
375     {
376         if (self.tur_dist_enemy < autocvar_g_turrets_unit_walker_std_meele_range && self.animflag != ANIM_MEELE)
377         {
378             vector wish_angle;
379
380             wish_angle = angleofs(self, self.enemy);
381             if (self.animflag != ANIM_SWIM)
382             if (fabs(wish_angle.y) < 15)
383             {
384                 self.moveto   = self.enemy.origin;
385                 self.steerto  = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
386                 self.animflag = ANIM_MEELE;
387             }
388         }
389         else if (self.tur_head.attack_finished_single < time)
390         {
391             if(self.tur_head.shot_volly)
392             {
393                 self.animflag = ANIM_NO;
394
395                 self.tur_head.shot_volly = self.tur_head.shot_volly -1;
396                 if(self.tur_head.shot_volly == 0)
397                     self.tur_head.attack_finished_single = time + autocvar_g_turrets_unit_walker_std_rocket_refire;
398                 else
399                     self.tur_head.attack_finished_single = time + 0.2;
400
401                 if(self.tur_head.shot_volly > 1)
402                     walker_fire_rocket(gettaginfo(self, gettagindex(self, "tag_rocket01")));
403                 else
404                     walker_fire_rocket(gettaginfo(self, gettagindex(self, "tag_rocket02")));
405             }
406             else
407             {
408                 if (self.tur_dist_enemy > autocvar_g_turrets_unit_walker_std_rockets_range_min)
409                 if (self.tur_dist_enemy < autocvar_g_turrets_unit_walker_std_rockets_range)
410                     self.tur_head.shot_volly = 4;
411             }
412         }
413         else
414         {
415             if (self.animflag != ANIM_MEELE)
416                 walker_move_to(self.enemy.origin, self.tur_dist_enemy);
417         }
418     }
419
420     //if(self.animflag != ANIM_NO)
421     {
422         vector real_angle;
423         float turny = 0, turnx = 0;
424         float  vz;
425
426         real_angle = vectoangles(self.steerto) - self.angles;
427         vz         = self.velocity.z;
428
429         switch (self.animflag)
430         {
431             case ANIM_NO:
432                 movelib_beak_simple(autocvar_g_turrets_unit_walker_speed_stop);
433                 break;
434
435             case ANIM_TURN:
436                 turny = autocvar_g_turrets_unit_walker_turn;
437                 movelib_beak_simple(autocvar_g_turrets_unit_walker_speed_stop);
438                 break;
439
440             case ANIM_WALK:
441                 turny = autocvar_g_turrets_unit_walker_turn_walk;
442                 movelib_move_simple(v_forward, autocvar_g_turrets_unit_walker_speed_walk, 0.6);
443                 break;
444
445             case ANIM_RUN:
446                 turny = autocvar_g_turrets_unit_walker_turn_run;
447                 movelib_move_simple(v_forward, autocvar_g_turrets_unit_walker_speed_run, 0.6);
448                 break;
449
450             case ANIM_STRAFE_L:
451                 turny = autocvar_g_turrets_unit_walker_turn_strafe;
452                 movelib_move_simple(v_right * -1, autocvar_g_turrets_unit_walker_speed_walk, 0.8);
453                 break;
454
455             case ANIM_STRAFE_R:
456                 turny = autocvar_g_turrets_unit_walker_turn_strafe;
457                 movelib_move_simple(v_right, autocvar_g_turrets_unit_walker_speed_walk, 0.8);
458                 break;
459
460             case ANIM_JUMP:
461                 self.velocity += '0 0 1' * autocvar_g_turrets_unit_walker_speed_jump;
462                 break;
463
464             case ANIM_LAND:
465                 break;
466
467             case ANIM_PAIN:
468                 if(self.frame != ANIM_PAIN)
469                     defer(0.25, walker_setnoanim);
470
471                 break;
472
473             case ANIM_MEELE:
474                 if(self.frame != ANIM_MEELE)
475                 {
476                     defer(0.41, walker_setnoanim);
477                     defer(0.21, walker_meele_do_dmg);
478                 }
479
480                 movelib_beak_simple(autocvar_g_turrets_unit_walker_speed_stop);
481                 break;
482
483             case ANIM_SWIM:
484                 turny = autocvar_g_turrets_unit_walker_turn_swim;
485                 turnx = autocvar_g_turrets_unit_walker_turn_swim;
486
487                 self.angles_x += bound(-10, shortangle_f(real_angle.x, self.angles.x), 10);
488                 movelib_move_simple(v_forward, autocvar_g_turrets_unit_walker_speed_swim, 0.3);
489                 vz = self.velocity_z + sin(time * 4) * 8;
490                 break;
491
492             case ANIM_ROAM:
493                 turny = autocvar_g_turrets_unit_walker_turn_walk;
494                 movelib_move_simple(v_forward ,autocvar_g_turrets_unit_walker_speed_roam, 0.5);
495                 break;
496         }
497
498         if(turny)
499         {
500             turny = bound( turny * -1, shortangle_f(real_angle.y, self.angles.y), turny );
501             self.angles_y += turny;
502         }
503
504         if(turnx)
505         {
506             turnx = bound( turnx * -1, shortangle_f(real_angle.x, self.angles.x), turnx );
507             self.angles_x += turnx;
508         }
509
510         self.velocity_z = vz;
511     }
512
513
514     if(self.origin != self.oldorigin)
515         self.SendFlags |= TNSF_MOVE;
516
517     self.oldorigin = self.origin;
518     turrets_setframe(self.animflag, false);
519 }
520
521 void walker_attack()
522 {
523     sound (self, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTEN_NORM);
524     fireBullet (self.tur_shotorg, self.tur_shotdir_updated, self.shot_spread, 0, self.shot_dmg, self.shot_force, DEATH_TURRET_WALK_GUN, 0);
525     pointparticles(particleeffectnum("laser_muzzleflash"), self.tur_shotorg, self.tur_shotdir_updated * 1000, 1);
526 }
527
528
529 void walker_respawnhook()
530 {
531     entity e;
532
533     // Respawn is called & first spawn to, to set team. need to make sure we do not move the initial spawn.
534     if(self.movetype != MOVETYPE_WALK)
535                 return;
536
537     setorigin(self, self.pos1);
538     self.angles = self.pos2;
539
540     if (self.target != "")
541     {
542         e = find(world, targetname, self.target);
543         if (!e)
544         {
545             dprint("Warning! initital waypoint for Walker does NOT exsist!\n");
546             self.target = "";
547         }
548
549         if (e.classname != "turret_checkpoint")
550             dprint("Warning: not a turrret path\n");
551         else
552         {
553  #ifdef WALKER_FANCYPATHING
554             self.pathcurrent = WALKER_PATH(self.origin, e.origin);
555             self.pathgoal = e;
556 #else
557             self.pathcurrent = e;
558 #endif
559         }
560     }
561 }
562
563 void walker_diehook()
564 {
565 #ifdef WALKER_FANCYPATHING
566     if (self.pathcurrent)
567         pathlib_deletepath(self.pathcurrent.owner);
568 #endif
569     self.pathcurrent = world;
570 }
571
572 void turret_walker_dinit()
573 {
574     entity e;
575
576     if (self.netname == "")      self.netname     = "Walker Turret";
577
578     self.ammo_flags = TFL_AMMO_BULLETS | TFL_AMMO_RECHARGE | TFL_AMMO_RECIVE;
579     self.turrcaps_flags = TFL_TURRCAPS_PLAYERKILL | TFL_TURRCAPS_MOVE ;
580     self.aim_flags = TFL_AIM_LEAD;
581
582     self.turrcaps_flags |= TFL_TURRCAPS_HITSCAN;
583
584
585     self.turret_respawnhook = walker_respawnhook;
586     self.turret_diehook = walker_diehook;
587
588     self.ticrate = 0.05;
589     if (turret_stdproc_init("walker_std", "models/turrets/walker_body.md3", "models/turrets/walker_head_minigun.md3", TID_WALKER) == 0)
590     {
591         remove(self);
592         return;
593     }
594     setsize(self, WALKER_MIN, WALKER_MAX);
595     self.target_select_flags   = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS;
596     self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS;
597     self.iscreature = true;
598     self.teleportable = TELEPORT_NORMAL;
599     self.damagedbycontents = true;
600     self.movetype   = MOVETYPE_WALK;
601     self.solid      = SOLID_SLIDEBOX;
602     self.takedamage = DAMAGE_AIM;
603     setorigin(self, self.origin);
604     tracebox(self.origin + '0 0 128', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_NORMAL, self);
605     setorigin(self, trace_endpos + '0 0 4');
606     self.pos1 = self.origin;
607     self.pos2 = self.angles;
608     self.idle_aim = '0 0 0';
609     self.turret_firecheckfunc = walker_firecheck;
610     self.turret_firefunc      = walker_attack;
611     self.turret_postthink     = walker_postthink;
612
613     if (self.target != "")
614     {
615         e = find(world, targetname, self.target);
616         if (!e)
617         {
618             dprint("Initital waypoint for walker does NOT exsist, fix your map!\n");
619             self.target = "";
620         }
621
622         if (e.classname != "turret_checkpoint")
623             dprint("Warning: not a turrret path\n");
624         else
625         {
626 #ifdef WALKER_FANCYPATHING
627             self.pathcurrent = WALKER_PATH(self.origin, e.origin);
628             self.pathgoal = e;
629 #else
630             self.pathcurrent = e;
631 #endif
632         }
633     }
634 }
635
636
637 void spawnfunc_turret_walker()
638 {
639     g_turrets_common_precash();
640
641     precache_model ("models/turrets/walker_head_minigun.md3");
642     precache_model ("models/turrets/walker_body.md3");
643     precache_model ( "models/turrets/rocket.md3");
644     precache_sound ( "weapons/rocket_impact.wav" );
645
646     self.think = turret_walker_dinit;
647     self.nextthink = time + 0.5;
648 }