]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/tturrets/system/system_main.qc
Merge remote branch 'origin/master' into fruitiex/animations
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / tturrets / system / system_main.qc
1 #define cvar_base "g_turrets_unit_"
2 .float clientframe;
3 void turrets_setframe(float _frame, float client_only)
4 {        
5     if((client_only ? self.clientframe : self.frame ) != _frame)
6     {
7         self.SendFlags |= TNSF_ANIM;
8         self.anim_start_time = time;
9     }
10     
11      if(client_only)
12         self.clientframe = _frame;
13     else
14         self.frame = _frame;
15    
16 }
17
18 float turret_send(entity to, float sf)
19 {
20         
21         WriteByte(MSG_ENTITY, ENT_CLIENT_TURRET);    
22         WriteByte(MSG_ENTITY, sf);
23         if(sf & TNSF_SETUP)
24         {
25             WriteByte(MSG_ENTITY, self.turret_type);
26             
27             WriteCoord(MSG_ENTITY, self.origin_x);
28             WriteCoord(MSG_ENTITY, self.origin_y);
29             WriteCoord(MSG_ENTITY, self.origin_z);
30             
31             WriteAngle(MSG_ENTITY, self.angles_x);
32             WriteAngle(MSG_ENTITY, self.angles_y);
33     }
34     
35     if(sf & TNSF_ANG)
36     {
37         WriteShort(MSG_ENTITY, rint(self.tur_head.angles_x));
38         WriteShort(MSG_ENTITY, rint(self.tur_head.angles_y));
39     }
40     
41     if(sf & TNSF_AVEL)
42     {        
43         WriteShort(MSG_ENTITY, rint(self.tur_head.avelocity_x));
44         WriteShort(MSG_ENTITY, rint(self.tur_head.avelocity_y));
45     }
46     
47     if(sf & TNSF_MOVE)
48     {
49         WriteShort(MSG_ENTITY, rint(self.origin_x));
50         WriteShort(MSG_ENTITY, rint(self.origin_y));
51         WriteShort(MSG_ENTITY, rint(self.origin_z));
52
53         WriteShort(MSG_ENTITY, rint(self.velocity_x));
54         WriteShort(MSG_ENTITY, rint(self.velocity_y));
55         WriteShort(MSG_ENTITY, rint(self.velocity_z));        
56         
57         WriteShort(MSG_ENTITY, rint(self.angles_y));        
58     }
59     
60     if(sf & TNSF_ANIM)
61     {
62         WriteCoord(MSG_ENTITY, self.anim_start_time);
63         WriteByte(MSG_ENTITY, self.frame);
64     }
65     
66     if(sf & TNSF_STATUS)
67     {
68         WriteByte(MSG_ENTITY, self.team);
69         
70         if(self.health <= 0)
71             WriteByte(MSG_ENTITY, 0);
72         else
73             WriteByte(MSG_ENTITY, ceil((self.health / self.tur_health) * 255));
74     }
75     
76         return TRUE;
77 }
78
79 void load_unit_settings(entity ent, string unitname, float is_reload)
80 {
81     string sbase;
82
83     if (ent == world)
84         return;
85
86     if not (ent.turret_scale_damage)    ent.turret_scale_damage  = 1;
87     if not (ent.turret_scale_range)     ent.turret_scale_range   = 1;
88     if not (ent.turret_scale_refire)    ent.turret_scale_refire  = 1;
89     if not (ent.turret_scale_ammo)      ent.turret_scale_ammo    = 1;
90     if not (ent.turret_scale_aim)       ent.turret_scale_aim     = 1;
91     if not (ent.turret_scale_health)    ent.turret_scale_health  = 1;
92     if not (ent.turret_scale_respawn)   ent.turret_scale_respawn = 1;
93
94     sbase = strcat(cvar_base,unitname);
95     if (is_reload)
96     {
97         ent.enemy = world;
98         ent.tur_head.avelocity = '0 0 0';
99
100         ent.tur_head.angles = '0 0 0';
101     }
102
103     ent.health      = cvar(strcat(sbase,"_health")) * ent.turret_scale_health;
104     ent.respawntime = cvar(strcat(sbase,"_respawntime")) * ent.turret_scale_respawn;
105
106     ent.shot_dmg          = cvar(strcat(sbase,"_shot_dmg")) * ent.turret_scale_damage;
107     ent.shot_refire       = cvar(strcat(sbase,"_shot_refire")) * ent.turret_scale_refire;
108     ent.shot_radius       = cvar(strcat(sbase,"_shot_radius")) * ent.turret_scale_damage;
109     ent.shot_speed        = cvar(strcat(sbase,"_shot_speed"));
110     ent.shot_spread       = cvar(strcat(sbase,"_shot_spread"));
111     ent.shot_force        = cvar(strcat(sbase,"_shot_force")) * ent.turret_scale_damage;
112     ent.shot_volly        = cvar(strcat(sbase,"_shot_volly"));
113     ent.shot_volly_refire = cvar(strcat(sbase,"_shot_volly_refire")) * ent.turret_scale_refire;
114
115     ent.target_range         = cvar(strcat(sbase,"_target_range")) * ent.turret_scale_range;
116     ent.target_range_min     = cvar(strcat(sbase,"_target_range_min")) * ent.turret_scale_range;
117     ent.target_range_optimal = cvar(strcat(sbase,"_target_range_optimal")) * ent.turret_scale_range;
118     //ent.target_range_fire    = cvar(strcat(sbase,"_target_range_fire")) * ent.turret_scale_range;
119
120     ent.target_select_rangebias  = cvar(strcat(sbase,"_target_select_rangebias"));
121     ent.target_select_samebias   = cvar(strcat(sbase,"_target_select_samebias"));
122     ent.target_select_anglebias  = cvar(strcat(sbase,"_target_select_anglebias"));
123     ent.target_select_playerbias = cvar(strcat(sbase,"_target_select_playerbias"));
124     //ent.target_select_fov = cvar(cvar_gets(sbase,"_target_select_fov"));
125
126     ent.ammo_max      = cvar(strcat(sbase,"_ammo_max")) * ent.turret_scale_ammo;
127     ent.ammo_recharge = cvar(strcat(sbase,"_ammo_recharge")) * ent.turret_scale_ammo;
128
129     ent.aim_firetolerance_dist = cvar(strcat(sbase,"_aim_firetolerance_dist"));
130     ent.aim_speed    = cvar(strcat(sbase,"_aim_speed")) * ent.turret_scale_aim;
131     ent.aim_maxrot   = cvar(strcat(sbase,"_aim_maxrot"));
132     ent.aim_maxpitch = cvar(strcat(sbase,"_aim_maxpitch"));
133
134     ent.track_type        = cvar(strcat(sbase,"_track_type"));
135     ent.track_accel_pitch = cvar(strcat(sbase,"_track_accel_pitch"));
136     ent.track_accel_rot   = cvar(strcat(sbase,"_track_accel_rot"));
137     ent.track_blendrate   = cvar(strcat(sbase,"_track_blendrate"));
138
139     if(is_reload)
140         if(ent.turret_respawnhook)
141             ent.turret_respawnhook();
142 }
143
144 /**
145 ** updates enemy distances, predicted impact point/time
146 ** and updated aim<->predict impact distance.
147 **/
148 void turret_do_updates(entity t_turret)
149 {
150     vector enemy_pos, oldpos;
151     entity oldself;
152
153     oldself = self;
154     self = t_turret;
155
156     enemy_pos = real_origin(self.enemy);
157
158     turret_tag_fire_update();
159
160     self.tur_shotdir_updated = v_forward;
161     self.tur_dist_enemy  = vlen(self.tur_shotorg - enemy_pos);
162     self.tur_dist_aimpos = vlen(self.tur_shotorg - self.tur_aimpos);
163
164     if((self.firecheck_flags & TFL_FIRECHECK_VERIFIED) && (self.enemy))
165     {
166         oldpos = self.enemy.origin;
167         setorigin(self.enemy,self.tur_aimpos);
168         tracebox(self.tur_shotorg, '-1 -1 -1','1 1 1',self.tur_shotorg + (self.tur_shotdir_updated * self.tur_dist_aimpos),MOVE_NORMAL,self);
169         setorigin(self.enemy,oldpos);
170
171         if(trace_ent == self.enemy)
172             self.tur_dist_impact_to_aimpos = 0;
173         else
174             self.tur_dist_impact_to_aimpos = vlen(trace_endpos - self.tur_aimpos);
175     }
176     else
177         tracebox(self.tur_shotorg, '-1 -1 -1','1 1 1', self.tur_shotorg + (self.tur_shotdir_updated * self.tur_dist_aimpos),MOVE_NORMAL,self);
178         
179         self.tur_dist_impact_to_aimpos = vlen(trace_endpos - self.tur_aimpos) - (vlen(self.enemy.maxs - self.enemy.mins) * 0.5);                
180         self.tur_impactent             = trace_ent;
181         self.tur_impacttime            = vlen(self.tur_shotorg - trace_endpos) / self.shot_speed;
182
183     self = oldself;
184 }
185
186 /*
187 vector turret_fovsearch_pingpong()
188 {
189     vector wish_angle;
190     if(self.phase < time)
191     {
192         if( self.tur_head.phase )
193             self.tur_head.phase = 0;
194         else
195             self.tur_head.phase = 1;
196         self.phase = time + 5;
197     }
198
199     if( self.tur_head.phase)
200         wish_angle = self.idle_aim + '0 1 0' * (self.aim_maxrot * (self.target_select_fov / 360));
201     else
202         wish_angle = self.idle_aim - '0 1 0' * (self.aim_maxrot * (self.target_select_fov / 360));
203
204     return wish_angle;
205 }
206
207 vector turret_fovsearch_steprot()
208 {
209     vector wish_angle;
210     //float rot_add;
211
212     wish_angle   = self.tur_head.angles;
213     wish_angle_x = self.idle_aim_x;
214
215     if (self.phase < time)
216     {
217         //rot_add = self.aim_maxrot / self.target_select_fov;
218         wish_angle_y += (self.target_select_fov * 2);
219
220         if(wish_angle_y > 360)
221             wish_angle_y = wish_angle_y - 360;
222
223          self.phase = time + 1.5;
224     }
225
226     return wish_angle;
227 }
228
229 vector turret_fovsearch_random()
230 {
231     vector wish_angle;
232
233     if (self.phase < time)
234     {
235         wish_angle_y = random() * self.aim_maxrot;
236         if(random() < 0.5)
237             wish_angle_y *= -1;
238
239         wish_angle_x = random() * self.aim_maxpitch;
240         if(random() < 0.5)
241             wish_angle_x *= -1;
242
243         self.phase = time + 5;
244
245         self.tur_aimpos = wish_angle;
246     }
247
248     return self.idle_aim + self.tur_aimpos;
249 }
250 */
251
252 /**
253 ** Handles head rotation according to
254 ** the units .track_type and .track_flags
255 **/
256 .float turret_framecounter;
257 void turret_stdproc_track()
258 {
259     vector target_angle; // This is where we want to aim
260     vector move_angle;   // This is where we can aim
261     float f_tmp;
262     vector v1, v2;
263     v1 = self.tur_head.angles;
264     v2 = self.tur_head.avelocity;
265     
266     if (self.track_flags == TFL_TRACK_NO)
267         return;
268
269     if not (self.tur_active)
270         target_angle = self.idle_aim - ('1 0 0' * self.aim_maxpitch);
271     else if (self.enemy == world)
272     {
273         if(time > self.lip)
274             target_angle = self.idle_aim + self.angles;
275         else
276             target_angle = vectoangles(normalize(self.tur_aimpos - self.tur_shotorg));
277     }
278     else
279     {
280         target_angle = vectoangles(normalize(self.tur_aimpos - self.tur_shotorg)); 
281     }
282     
283     self.tur_head.angles_x = anglemods(self.tur_head.angles_x);
284     self.tur_head.angles_y = anglemods(self.tur_head.angles_y);
285
286     // Find the diffrence between where we currently aim and where we want to aim
287     //move_angle = target_angle - (self.angles + self.tur_head.angles);
288     //move_angle = shortangle_vxy(move_angle,(self.angles + self.tur_head.angles));
289     
290     move_angle = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(self.angles), AnglesTransform_FromAngles(target_angle))) - self.tur_head.angles; 
291     move_angle = shortangle_vxy(move_angle, self.tur_head.angles);
292
293     switch(self.track_type)
294     {
295         case TFL_TRACKTYPE_STEPMOTOR:
296             f_tmp = self.aim_speed * self.ticrate; // dgr/sec -> dgr/tic
297             if (self.track_flags & TFL_TRACK_PITCH)
298             {
299                 self.tur_head.angles_x += bound(-f_tmp,move_angle_x, f_tmp);
300                 if(self.tur_head.angles_x > self.aim_maxpitch)
301                     self.tur_head.angles_x = self.aim_maxpitch;
302
303                 if(self.tur_head.angles_x  < -self.aim_maxpitch)
304                     self.tur_head.angles_x = self.aim_maxpitch;
305             }
306
307             if (self.track_flags & TFL_TRACK_ROT)
308             {
309                 self.tur_head.angles_y += bound(-f_tmp, move_angle_y, f_tmp);
310                 if(self.tur_head.angles_y > self.aim_maxrot)
311                     self.tur_head.angles_y = self.aim_maxrot;
312
313                 if(self.tur_head.angles_y  < -self.aim_maxrot)
314                     self.tur_head.angles_y = self.aim_maxrot;
315             }
316             
317             // CSQC
318             self.SendFlags  = TNSF_ANG;
319             
320             return;
321
322         case TFL_TRACKTYPE_FLUIDINERTIA:
323             f_tmp = self.aim_speed * self.ticrate; // dgr/sec -> dgr/tic
324             move_angle_x = bound(-self.aim_speed, move_angle_x * self.track_accel_pitch * f_tmp, self.aim_speed);
325             move_angle_y = bound(-self.aim_speed, move_angle_y * self.track_accel_rot * f_tmp, self.aim_speed);
326             move_angle = (self.tur_head.avelocity * self.track_blendrate) + (move_angle * (1 - self.track_blendrate));
327             break;
328
329         case TFL_TRACKTYPE_FLUIDPRECISE:
330
331             move_angle_y = bound(-self.aim_speed, move_angle_y, self.aim_speed);
332             move_angle_x = bound(-self.aim_speed, move_angle_x, self.aim_speed);
333
334             break;
335     }
336
337     //  pitch
338     if (self.track_flags & TFL_TRACK_PITCH)
339     {
340         self.tur_head.avelocity_x = move_angle_x;
341         if((self.tur_head.angles_x + self.tur_head.avelocity_x * self.ticrate) > self.aim_maxpitch)
342         {
343             self.tur_head.avelocity_x = 0;
344             self.tur_head.angles_x = self.aim_maxpitch;
345             
346             self.SendFlags  |= TNSF_ANG;
347         }
348         
349         if((self.tur_head.angles_x + self.tur_head.avelocity_x * self.ticrate) < -self.aim_maxpitch)
350         {
351             self.tur_head.avelocity_x = 0;
352             self.tur_head.angles_x = -self.aim_maxpitch;
353                         
354             self.SendFlags  |= TNSF_ANG;
355         }
356     }
357
358     //  rot
359     if (self.track_flags & TFL_TRACK_ROT)
360     {
361         self.tur_head.avelocity_y = move_angle_y;
362
363         if((self.tur_head.angles_y + self.tur_head.avelocity_y * self.ticrate) > self.aim_maxrot)
364         {
365             self.tur_head.avelocity_y = 0;
366             self.tur_head.angles_y = self.aim_maxrot;
367             
368             self.SendFlags  |= TNSF_ANG;
369         }
370
371         if((self.tur_head.angles_y + self.tur_head.avelocity_y * self.ticrate) < -self.aim_maxrot)
372         {
373             self.tur_head.avelocity_y = 0;
374             self.tur_head.angles_y = -self.aim_maxrot;
375             
376             self.SendFlags  |= TNSF_ANG;
377         }
378     }
379         
380     self.SendFlags  |= TNSF_AVEL;
381     
382     // Force a angle update every 10'th frame
383     self.turret_framecounter += 1;
384     if(self.turret_framecounter >= 10)
385     {        
386         self.SendFlags |= TNSF_ANG;
387         self.turret_framecounter = 0;
388     }            
389 }
390
391
392 /*
393  + = implemented
394  - = not implemented
395
396  + TFL_FIRECHECK_NO
397  + TFL_FIRECHECK_WORLD
398  + TFL_FIRECHECK_DEAD
399  + TFL_FIRECHECK_DISTANCES
400  - TFL_FIRECHECK_LOS
401  + TFL_FIRECHECK_AIMDIST
402  + TFL_FIRECHECK_REALDIST
403  - TFL_FIRECHECK_ANGLEDIST
404  - TFL_FIRECHECK_TEAMCECK
405  + TFL_FIRECHECK_AFF
406  + TFL_FIRECHECK_OWM_AMMO
407  + TFL_FIRECHECK_OTHER_AMMO
408  + TFL_FIRECHECK_REFIRE
409 */
410
411 /**
412 ** Preforms pre-fire checks based on the uints firecheck_flags
413 **/
414 float turret_stdproc_firecheck()
415 {
416     // This one just dont care =)
417     if (self.firecheck_flags & TFL_FIRECHECK_NO) return 1;
418
419     // Ready?
420     if (self.firecheck_flags & TFL_FIRECHECK_REFIRE)
421         if (self.attack_finished_single > time) return 0;
422
423     // Special case: volly fire turret that has to fire a full volly if a shot was fired.
424     if (self.shoot_flags & TFL_SHOOT_VOLLYALWAYS)
425         if (self.volly_counter != self.shot_volly)
426                         if(self.ammo >= self.shot_dmg)
427                                 return 1;               
428
429     // Lack of zombies makes shooting dead things unnecessary :P
430     if (self.firecheck_flags & TFL_FIRECHECK_DEAD)
431         if (self.enemy.deadflag != DEAD_NO)
432             return 0;
433
434     // Plz stop killing the world!
435     if (self.firecheck_flags & TFL_FIRECHECK_WORLD)
436         if (self.enemy == world)
437             return 0;
438
439     // Own ammo?
440     if (self.firecheck_flags & TFL_FIRECHECK_OWM_AMMO)
441         if (self.ammo < self.shot_dmg)
442             return 0;
443
444     // Other's ammo? (support-supply units)
445     if (self.firecheck_flags & TFL_FIRECHECK_OTHER_AMMO)
446         if (self.enemy.ammo >= self.enemy.ammo_max)
447             return 0;
448         
449         // Target of opertunity?
450         if(turret_validate_target(self, self.tur_impactent, self.target_validate_flags) > 0)
451         {
452                 self.enemy = self.tur_impactent;
453                 return 1;
454         }                               
455
456     if (self.firecheck_flags & TFL_FIRECHECK_DISTANCES)
457     {
458         // To close?
459         if (self.tur_dist_aimpos < self.target_range_min)
460                         if(turret_validate_target(self, self.tur_impactent, self.target_validate_flags) > 0)                    
461                                 return 1; // Target of opertunity?
462                         else 
463                                 return 0;                               
464     }
465
466     // Try to avoid FF?
467     if (self.firecheck_flags & TFL_FIRECHECK_AFF)
468         if (self.tur_impactent.team == self.team)
469             return 0;
470
471     // aim<->predicted impact
472     if (self.firecheck_flags & TFL_FIRECHECK_AIMDIST)
473         if (self.tur_dist_impact_to_aimpos > self.aim_firetolerance_dist)
474             return 0;
475
476     // Volly status
477     if (self.shot_volly > 1)
478         if (self.volly_counter == self.shot_volly)
479             if (self.ammo < (self.shot_dmg * self.shot_volly))
480                 return 0;
481
482     if(self.firecheck_flags & TFL_FIRECHECK_VERIFIED)
483         if(self.tur_impactent != self.enemy)
484             return 0;
485
486     return 1;
487 }
488
489 /*
490  + TFL_TARGETSELECT_NO
491  + TFL_TARGETSELECT_LOS
492  + TFL_TARGETSELECT_PLAYERS
493  + TFL_TARGETSELECT_MISSILES
494  - TFL_TARGETSELECT_TRIGGERTARGET
495  + TFL_TARGETSELECT_ANGLELIMITS
496  + TFL_TARGETSELECT_RANGELIMTS
497  + TFL_TARGETSELECT_TEAMCHECK
498  - TFL_TARGETSELECT_NOBUILTIN
499  + TFL_TARGETSELECT_OWNTEAM
500 */
501
502 /**
503 ** Evaluate a entity for target valitity based on validate_flags
504 ** NOTE: the caller must check takedamage before calling this, to inline this check.
505 **/
506 float turret_validate_target(entity e_turret, entity e_target, float validate_flags)
507 {
508     vector v_tmp;
509
510     //if(!validate_flags & TFL_TARGETSELECT_NOBUILTIN)
511     //    return -0.5;
512
513     if(e_target.owner == e_turret)
514         return -0.5;
515
516     if not(checkpvs(e_target.origin, e_turret))
517         return -1;
518
519     if not (e_target)
520         return -2;
521
522         if(g_onslaught)
523                 if (substring(e_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job!
524                         return - 3;
525
526     if (validate_flags & TFL_TARGETSELECT_NO)
527         return -4;
528
529     // If only this was used more..
530     if (e_target.flags & FL_NOTARGET)
531         return -5;
532
533     // Cant touch this
534     if (e_target.health < 0)
535         return -6;
536
537     // player
538     if (e_target.flags & FL_CLIENT)
539     {
540         if not (validate_flags & TFL_TARGETSELECT_PLAYERS)
541             return -7;
542
543         if (e_target.deadflag != DEAD_NO)
544             return -8;
545     }
546
547         // enemy turrets
548         if (validate_flags & TFL_TARGETSELECT_NOTURRETS)
549         if (e_target.turret_firefunc || e_target.owner.tur_head == e_target)
550             if(e_target.team != e_turret.team) // Dont break support units.
551                 return -9;
552
553     // Missile
554     if (e_target.flags & FL_PROJECTILE)
555         if not (validate_flags & TFL_TARGETSELECT_MISSILES)
556             return -10;
557
558     if (validate_flags & TFL_TARGETSELECT_MISSILESONLY)
559         if not (e_target.flags & FL_PROJECTILE)
560             return -10.5;
561
562     // Team check
563     if (validate_flags & TFL_TARGETSELECT_TEAMCHECK)
564     {
565         if (validate_flags & TFL_TARGETSELECT_OWNTEAM)
566         {
567             if (e_target.team != e_turret.team)
568                 return -11;
569
570             if (e_turret.team != e_target.owner.team)
571                 return -12;
572         }
573         else
574         {
575             if (e_target.team == e_turret.team)
576                 return -13;
577
578             if (e_turret.team == e_target.owner.team)
579                 return -14;
580         }
581     }
582
583     // Range limits?
584     tvt_dist = vlen(e_turret.origin - real_origin(e_target));
585     if (validate_flags & TFL_TARGETSELECT_RANGELIMTS)
586     {
587         if (tvt_dist < e_turret.target_range_min)
588             return -15;
589
590         if (tvt_dist > e_turret.target_range)
591             return -16;
592     }
593
594     // Can we even aim this thing?
595     tvt_thadv = angleofs3(e_turret.tur_head.origin, e_turret.angles + e_turret.tur_head.angles, e_target);
596     tvt_tadv  = shortangle_vxy(angleofs(e_turret, e_target), e_turret.angles);
597     tvt_thadf = vlen(tvt_thadv);
598     tvt_tadf  = vlen(tvt_tadv);
599
600     /*
601     if(validate_flags & TFL_TARGETSELECT_FOV)
602     {
603         if(e_turret.target_select_fov < tvt_thadf)
604             return -21;
605     }
606     */
607
608     if (validate_flags & TFL_TARGETSELECT_ANGLELIMITS)
609     {
610         if (fabs(tvt_tadv_x) > e_turret.aim_maxpitch)
611             return -17;
612
613         if (fabs(tvt_tadv_y) > e_turret.aim_maxrot)
614             return -18;
615     }
616
617     // Line of sight?
618     if (validate_flags & TFL_TARGETSELECT_LOS)
619     {
620         v_tmp = real_origin(e_target) + ((e_target.mins + e_target.maxs) * 0.5);
621
622         traceline(e_turret.tur_shotorg, v_tmp, 0, e_turret);
623
624         if (e_turret.aim_firetolerance_dist < vlen(v_tmp - trace_endpos))
625             return -19;
626     }
627
628     if (e_target.classname == "grapplinghook")
629         return -20;
630
631     /*
632     if (e_target.classname == "func_button")
633         return -21;
634     */
635
636 #ifdef TURRET_DEBUG_TARGETSELECT
637     dprint("Target:",e_target.netname," is a valid target for ",e_turret.netname,"\n");
638 #endif
639
640     return 1;
641 }
642
643 entity turret_select_target()
644 {
645     entity e;        // target looper entity
646     float  score;    // target looper entity score
647     entity e_enemy;  // currently best scoreing target
648     float  m_score;  // currently best scoreing target's score
649
650     m_score = 0;
651     if(self.enemy)
652         if(self.enemy.takedamage)
653     if(turret_validate_target(self,self.enemy,self.target_validate_flags) > 0)
654     {
655         e_enemy = self.enemy;
656         m_score = self.turret_score_target(self,e_enemy) * self.target_select_samebias;
657     }
658     else
659         self.enemy = world;
660
661     e = findradius(self.origin, self.target_range);
662
663     // Nothing to aim at?
664     if (!e) 
665                 return world;
666
667     while (e)
668     {
669                 if(e.takedamage)
670                 {
671                         if (turret_validate_target(self, e, self.target_select_flags) > 0)
672                         {
673                                 score = self.turret_score_target(self,e);
674                                 if ((score > m_score) && (score > 0))
675                                 {
676                                         e_enemy = e;
677                                         m_score = score;
678                                 }
679                         }
680                 }
681         e = e.chain;
682     }
683
684     return e_enemy;
685 }
686
687 void turret_think()
688 {
689     entity e;
690
691     self.nextthink = time + self.ticrate;
692     
693     // ONS uses somewhat backwards linking.
694     if (teamplay)
695     {
696         if not (g_onslaught)
697             if (self.target)
698             {
699                 e = find(world, targetname,self.target);
700                 if (e != world)
701                     self.team = e.team;
702             }
703
704         if (self.team != self.tur_head.team)
705             turret_stdproc_respawn();
706     }
707
708 #ifdef TURRET_DEBUG
709     if (self.tur_dbg_tmr1 < time)
710     {
711         if (self.enemy) paint_target (self.enemy,128,self.tur_dbg_rvec,0.9);
712         paint_target(self,256,self.tur_dbg_rvec,0.9);
713         self.tur_dbg_tmr1 = time + 1;
714     }
715 #endif
716
717     // Handle ammo
718     if not (self.spawnflags & TSF_NO_AMMO_REGEN)
719     if (self.ammo < self.ammo_max)
720         self.ammo = min(self.ammo + self.ammo_recharge, self.ammo_max);
721                         
722     // Inactive turrets needs to run the think loop,
723     // So they can handle animation and wake up if need be.
724     if not (self.tur_active)
725     {
726         turret_stdproc_track();
727         return;
728     }
729
730     // This is typicaly used for zaping every target in range
731     // turret_fusionreactor uses this to recharge friendlys.
732     if (self.shoot_flags & TFL_SHOOT_HITALLVALID)
733     {
734         // Do a self.turret_fire for every valid target.
735         e = findradius(self.origin,self.target_range);
736         while (e)
737         {
738                         if(e.takedamage)
739                         {
740                                 if (turret_validate_target(self,e,self.target_validate_flags))
741                                 {
742                                         self.enemy = e;
743
744                                         turret_do_updates(self);
745
746                                         if (self.turret_firecheckfunc())
747                                                 turret_fire();
748                                 }
749                         }
750
751             e = e.chain;
752         }
753         self.enemy = world;
754     }
755     else if(self.shoot_flags & TFL_SHOOT_CUSTOM)
756     {
757         // This one is doing something.. oddball. assume its handles what needs to be handled.
758
759         // Predict?
760         if not(self.aim_flags & TFL_AIM_NO)
761             self.tur_aimpos = turret_stdproc_aim_generic();
762
763         // Turn & pitch?
764         if not(self.track_flags & TFL_TRACK_NO)
765             turret_stdproc_track();
766
767         turret_do_updates(self);
768
769         // Fire?
770         if (self.turret_firecheckfunc())
771             turret_fire();
772     }
773     else
774     {
775         // Special case for volly always. if it fired once it must compleate the volly.
776         if(self.shoot_flags & TFL_SHOOT_VOLLYALWAYS)
777             if(self.volly_counter != self.shot_volly)
778             {
779                 // Predict or whatnot
780                 if not(self.aim_flags & TFL_AIM_NO)
781                     self.tur_aimpos = turret_stdproc_aim_generic();
782
783                 // Turn & pitch
784                 if not(self.track_flags & TFL_TRACK_NO)
785                     turret_stdproc_track();
786
787                 turret_do_updates(self);
788
789                 // Fire!
790                 if (self.turret_firecheckfunc() != 0)
791                     turret_fire();
792
793                 if(self.turret_postthink)
794                     self.turret_postthink();
795
796                 return;
797             }
798
799         // Check if we have a vailid enemy, and try to find one if we dont.
800
801         // g_turrets_targetscan_maxdelay forces a target re-scan at least this often
802         float do_target_scan;
803         if((self.target_select_time + autocvar_g_turrets_targetscan_maxdelay) < time)
804             do_target_scan = 1;
805
806         // Old target (if any) invalid?
807         if(self.target_validate_time < time)
808         if (turret_validate_target(self, self.enemy, self.target_validate_flags) <= 0)
809         {
810                 self.enemy = world;
811                 self.target_validate_time = time + 0.5;
812                 do_target_scan = 1;
813         }
814
815         // But never more often then g_turrets_targetscan_mindelay!
816         if (self.target_select_time + autocvar_g_turrets_targetscan_mindelay > time)
817             do_target_scan = 0;
818
819         if(do_target_scan)
820         {
821             self.enemy = turret_select_target();
822             self.target_select_time = time;
823         }
824
825         // No target, just go to idle, do any custom stuff and bail.
826         if (self.enemy == world)
827         {
828             // Turn & pitch
829             if not(self.track_flags & TFL_TRACK_NO)
830                 turret_stdproc_track();
831
832             // do any per-turret stuff
833             if(self.turret_postthink)
834                 self.turret_postthink();
835
836             // And bail.
837             return;
838         }
839         else
840             self.lip = time + autocvar_g_turrets_aimidle_delay; // Keep track of the last time we had a target.
841
842         // Predict?
843         if not(self.aim_flags & TFL_AIM_NO)
844             self.tur_aimpos = turret_stdproc_aim_generic();
845
846         // Turn & pitch?
847         if not(self.track_flags & TFL_TRACK_NO)
848             turret_stdproc_track();
849
850         turret_do_updates(self);
851
852         // Fire?
853         if (self.turret_firecheckfunc())
854             turret_fire();
855     }
856
857     // do any custom per-turret stuff
858     if(self.turret_postthink)
859         self.turret_postthink();
860 }
861
862 void turret_fire()
863 {
864     if (autocvar_g_turrets_nofire != 0)
865         return;
866
867     self.turret_firefunc();
868
869     self.attack_finished_single = time + self.shot_refire;
870     self.ammo -= self.shot_dmg;
871     self.volly_counter = self.volly_counter - 1;
872
873     if (self.volly_counter <= 0)
874     {
875         self.volly_counter = self.shot_volly;
876
877         if (self.shoot_flags & TFL_SHOOT_CLEARTARGET)
878             self.enemy = world;
879
880         if (self.shot_volly > 1)
881             self.attack_finished_single = time + self.shot_volly_refire;
882     }
883
884 #ifdef TURRET_DEBUG
885     if (self.enemy) paint_target3(self.tur_aimpos, 64, self.tur_dbg_rvec, self.tur_impacttime + 0.25);
886 #endif
887 }
888
889 void turret_stdproc_fire()
890 {
891     dprint("^1Bang, ^3your dead^7 ",self.enemy.netname,"! ^1(turret with no real firefunc)\n");
892 }
893
894 /*
895     When .used a turret switch team to activator.team.
896     If activator is world, the turret go inactive.
897 */
898 void turret_stdproc_use()
899 {
900     dprint("Turret ",self.netname, " used by ", activator.classname, "\n");
901
902     self.team = activator.team;
903
904     if(self.team == 0)
905         self.tur_active = 0;
906     else
907         self.tur_active = 1;
908
909 }
910
911 void turret_link()
912 {
913     Net_LinkEntity(self, TRUE, 0, turret_send);
914     self.think      = turret_think;
915     self.nextthink  = time;
916     self.tur_head.effects = EF_NODRAW;
917 }
918
919 void turrets_manager_think()
920 {
921     self.nextthink = time + 1;
922
923     entity e;
924     if (autocvar_g_turrets_reloadcvars == 1)
925     {
926         e = nextent(world);
927         while (e)
928         {
929             if (e.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
930             {
931                 load_unit_settings(e,e.cvar_basename,1);
932                 if(e.turret_postthink)
933                     e.turret_postthink();
934             }
935
936             e = nextent(e);
937         }
938         cvar_set("g_turrets_reloadcvars","0");
939     }
940 }
941
942 /*
943 * Standard turret initialization. use this!
944 * (unless you have a very good reason not to)
945 * if the return value is 0, the turret should be removed.
946 */
947 float turret_stdproc_init (string cvar_base_name, string base, string head, float _turret_type)
948 {
949         entity e, ee;
950
951     // Are turrets allowed?
952     if (autocvar_g_turrets == 0)
953         return 0;
954     
955     if(_turret_type < 1 || _turret_type > TID_LAST)
956     {
957         dprint("Invalid / Unkown turret type\"", ftos(_turret_type), "\", aborting!\n");
958         return 0;
959     }    
960     self.turret_type = _turret_type;
961     
962     e = find(world, classname, "turret_manager");
963     if not (e)
964     {
965         e = spawn();
966
967         /*
968         setorigin(e,'0 0 0');
969         setmodel(e,"models/turrets/plasma.md3");
970         vector v;
971         v = gettaginfo(e,gettagindex(e,"tag_fire"));
972         if(v == '0 0 0')
973         {
974             //objerror("^1ERROR: Engine is borken! Turrets will NOT work. force g_turrets to 0 to run maps with turrets anyway.");
975             //crash();
976         }
977         setmodel(e,"");
978         */
979
980         e.classname = "turret_manager";
981         e.think = turrets_manager_think;
982         e.nextthink = time + 2;
983     }
984
985     /*
986     if(csqc_shared)
987     {
988         dprint("WARNING: turret requested csqc_shared but this is not implemented. Expect strange things to happen.\n");
989         csqc_shared = 0;
990     }
991     */
992     
993     if not (self.spawnflags & TSF_SUSPENDED)
994         droptofloor_builtin();
995
996     // Terrainbase spawnflag. This puts a enlongated model
997     // under the turret, so it looks ok on uneaven surfaces.
998     /*  TODO: Handle this with CSQC
999     if (self.spawnflags & TSF_TERRAINBASE)
1000     {
1001         entity tb;
1002         tb = spawn();
1003         setmodel(tb,"models/turrets/terrainbase.md3");
1004         setorigin(tb,self.origin);
1005         tb.solid = SOLID_BBOX;
1006     }
1007     */
1008
1009     self.cvar_basename = cvar_base_name;
1010     load_unit_settings(self, self.cvar_basename, 0);
1011
1012     self.effects = EF_NODRAW;
1013     
1014     // Handle turret teams.
1015     if (autocvar_g_assault != 0)
1016     {
1017         if not (self.team)
1018             self.team = 14; // Assume turrets are on the defending side if not explicitly set otehrwize
1019     }
1020     else if not (teamplay)
1021                 self.team = MAX_SHOT_DISTANCE; // Group all turrets into the same team, so they dont kill eachother.
1022         else if(g_onslaught && self.targetname)
1023         {
1024                 e = find(world,target,self.targetname);
1025                 if(e != world)
1026                 {
1027                         self.team = e.team;
1028                         ee = e;
1029                 }
1030         }
1031         else if(!self.team)
1032                 self.team = MAX_SHOT_DISTANCE; // Group all turrets into the same team, so they dont kill eachother.
1033
1034     /*
1035     * Try to guess some reasonaly defaults
1036     * for missing params and do sanety checks
1037     * thise checks could produce some "interesting" results
1038     * if it hits a glitch in my logic :P so try to set as mutch
1039     * as possible beforehand.
1040     */
1041     if (self.turrcaps_flags & TFL_TURRCAPS_SUPPORT)
1042         self.ticrate = 0.2;     // Support units generaly dont need to have a high speed ai-loop
1043     else
1044         self.ticrate = 0.1;     // 10 fps for normal turrets
1045
1046     self.ticrate = bound(sys_frametime, self.ticrate, 60);  // keep it sane
1047
1048 // General stuff
1049     if (self.netname == "")
1050         self.netname = self.classname;
1051
1052     if not (self.respawntime)
1053         self.respawntime = 60;
1054     self.respawntime = max(-1, self.respawntime);
1055
1056     if not (self.health)
1057         self.health = 1000;
1058     self.tur_health = max(1, self.health);
1059
1060     if not (self.turrcaps_flags)
1061         self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL;
1062
1063     if not (self.damage_flags)
1064         self.damage_flags = TFL_DMG_YES | TFL_DMG_RETALIATE | TFL_DMG_AIMSHAKE;
1065
1066 // Shot stuff.
1067     if not (self.shot_refire)
1068         self.shot_refire = 1;
1069     self.shot_refire = bound(0.01, self.shot_refire, 9999);
1070
1071     if not (self.shot_dmg)
1072         self.shot_dmg  = self.shot_refire * 50;
1073     self.shot_dmg = max(1, self.shot_dmg);
1074
1075     if not (self.shot_radius)
1076         self.shot_radius = self.shot_dmg * 0.5;
1077     self.shot_radius = max(1, self.shot_radius);
1078
1079     if not (self.shot_speed)
1080         self.shot_speed = 2500;
1081     self.shot_speed = max(1, self.shot_speed);
1082
1083     if not (self.shot_spread)
1084         self.shot_spread = 0.0125;
1085     self.shot_spread = bound(0.0001, self.shot_spread, 500);
1086
1087     if not (self.shot_force)
1088         self.shot_force = self.shot_dmg * 0.5 + self.shot_radius * 0.5;
1089     self.shot_force = bound(0.001, self.shot_force, 5000);
1090
1091     if not (self.shot_volly)
1092         self.shot_volly = 1;
1093     self.shot_volly = bound(1, self.shot_volly, floor(self.ammo_max / self.shot_dmg));
1094
1095     if not (self.shot_volly_refire)
1096         self.shot_volly_refire = self.shot_refire * self.shot_volly;
1097     self.shot_volly_refire = bound(self.shot_refire, self.shot_volly_refire, 60);
1098
1099     if not (self.firecheck_flags)
1100         self.firecheck_flags = TFL_FIRECHECK_WORLD | TFL_FIRECHECK_DEAD | TFL_FIRECHECK_DISTANCES |
1101                                TFL_FIRECHECK_LOS | TFL_FIRECHECK_AIMDIST | TFL_FIRECHECK_TEAMCECK |
1102                                TFL_FIRECHECK_OWM_AMMO | TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_WORLD;
1103
1104 // Range stuff.
1105     if not (self.target_range)
1106         self.target_range = self.shot_speed * 0.5;
1107     self.target_range = bound(0, self.target_range, MAX_SHOT_DISTANCE);
1108
1109     if not (self.target_range_min)
1110         self.target_range_min = self.shot_radius * 2;
1111     self.target_range_min = bound(0, self.target_range_min, MAX_SHOT_DISTANCE);
1112
1113     if not (self.target_range_optimal)
1114         self.target_range_optimal = self.target_range * 0.5;
1115     self.target_range_optimal = bound(0, self.target_range_optimal, MAX_SHOT_DISTANCE);
1116
1117
1118 // Aim stuff.
1119     if not (self.aim_maxrot)
1120         self.aim_maxrot = 90;
1121     self.aim_maxrot = bound(0, self.aim_maxrot, 360);
1122
1123     if not (self.aim_maxpitch)
1124         self.aim_maxpitch = 20;
1125     self.aim_maxpitch = bound(0, self.aim_maxpitch, 90);
1126
1127     if not (self.aim_speed)
1128         self.aim_speed = 36;
1129     self.aim_speed  = bound(0.1, self.aim_speed, 1000);
1130
1131     if not (self.aim_firetolerance_dist)
1132         self.aim_firetolerance_dist  = 5 + (self.shot_radius * 2);
1133     self.aim_firetolerance_dist = bound(0.1, self.aim_firetolerance_dist, MAX_SHOT_DISTANCE);
1134
1135     if not (self.aim_flags)
1136     {
1137         self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE;
1138         if(self.turrcaps_flags & TFL_TURRCAPS_RADIUSDMG)
1139             self.aim_flags |= TFL_AIM_GROUND2;
1140     }
1141
1142     if not (self.track_type)
1143         self.track_type = TFL_TRACKTYPE_STEPMOTOR;
1144
1145     if (self.track_type != TFL_TRACKTYPE_STEPMOTOR)
1146     {
1147         // Fluid / Ineria mode. Looks mutch nicer.
1148         // Can reduce aim preformance alot, needs a bit diffrent aimspeed
1149
1150         if not (self.aim_speed)
1151             self.aim_speed = 180;
1152         self.aim_speed = bound(0.1, self.aim_speed, 1000);
1153
1154         if not (self.track_accel_pitch)
1155             self.track_accel_pitch = 0.5;
1156
1157         if not (self.track_accel_rot)
1158             self.track_accel_rot   = 0.5;
1159
1160         if not (self.track_blendrate)
1161             self.track_blendrate   = 0.35;
1162     }
1163
1164     if (!self.track_flags)
1165         self.track_flags = TFL_TRACK_PITCH | TFL_TRACK_ROT;
1166
1167
1168 // Target selection stuff.
1169     if not (self.target_select_rangebias)
1170         self.target_select_rangebias = 1;
1171     self.target_select_rangebias = bound(-10, self.target_select_rangebias, 10);
1172
1173     if not (self.target_select_samebias)
1174         self.target_select_samebias = 1;
1175     self.target_select_samebias = bound(-10, self.target_select_samebias, 10);
1176
1177     if not (self.target_select_anglebias)
1178         self.target_select_anglebias = 1;
1179     self.target_select_anglebias = bound(-10, self.target_select_anglebias, 10);
1180
1181     if not (self.target_select_missilebias)
1182         self.target_select_missilebias = -10;
1183
1184     self.target_select_missilebias = bound(-10, self.target_select_missilebias, 10);
1185     self.target_select_playerbias = bound(-10, self.target_select_playerbias, 10);
1186
1187     if not (self.target_select_flags)
1188     {
1189             self.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_TEAMCHECK
1190                                      | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_ANGLELIMITS;
1191
1192         if (self.turrcaps_flags & TFL_TURRCAPS_MISSILEKILL)
1193             self.target_select_flags |= TFL_TARGETSELECT_MISSILES;
1194
1195         if (self.turrcaps_flags & TFL_TURRCAPS_PLAYERKILL)
1196             self.target_select_flags |= TFL_TARGETSELECT_PLAYERS;
1197         //else
1198         //    self.target_select_flags = TFL_TARGETSELECT_NO;
1199     }
1200
1201     self.target_validate_flags = self.target_select_flags;
1202
1203 // Ammo stuff
1204     if not (self.ammo_max)
1205         self.ammo_max = self.shot_dmg * 10;
1206     self.ammo_max = max(self.shot_dmg, self.ammo_max);
1207
1208     if not (self.ammo)
1209         self.ammo = self.shot_dmg * 5;
1210     self.ammo = bound(0,self.ammo, self.ammo_max);
1211
1212     if not (self.ammo_recharge)
1213         self.ammo_recharge = self.shot_dmg * 0.5;
1214     self.ammo_recharge = max(0 ,self.ammo_recharge);
1215
1216     // Convert the recharge from X per sec to X per ticrate
1217     self.ammo_recharge = self.ammo_recharge * self.ticrate;
1218
1219     if not (self.ammo_flags)
1220         self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE;
1221
1222 // Damage stuff
1223     if(self.spawnflags & TSL_NO_RESPAWN)
1224         if not (self.damage_flags & TFL_DMG_DEATH_NORESPAWN)
1225             self.damage_flags |= TFL_DMG_DEATH_NORESPAWN;
1226
1227 // Offsets & origins
1228     if (!self.tur_shotorg)   self.tur_shotorg = '50 0 50';
1229     
1230     if (!self.health)
1231         self.health = 150;
1232
1233 // Game hooks
1234         if(MUTATOR_CALLHOOK(TurretSpawn))
1235                 return 0;
1236
1237 // End of default & sanety checks, start building the turret.
1238
1239 // Spawn extra bits
1240     self.tur_head         = spawn();
1241     self.tur_head.netname = self.tur_head.classname = "turret_head";
1242     self.tur_head.team    = self.team;
1243     self.tur_head.owner   = self;
1244
1245     setmodel(self, base);
1246     setmodel(self.tur_head, head);
1247
1248     setsize(self, '-32 -32 0', '32 32 64');
1249     setsize(self.tur_head, '0 0 0', '0 0 0');
1250
1251     setorigin(self.tur_head, '0 0 0');
1252     setattachment(self.tur_head, self, "tag_head");
1253
1254     self.tur_health          = self.health;
1255     self.solid               = SOLID_BBOX;
1256     self.tur_head.solid      = SOLID_NOT;
1257     self.takedamage          = DAMAGE_AIM;
1258     self.tur_head.takedamage = DAMAGE_NO;
1259     self.movetype            = MOVETYPE_NOCLIP;
1260     self.tur_head.movetype   = MOVETYPE_NOCLIP;
1261
1262     // Defend mode?
1263     if not (self.tur_defend)
1264     if (self.target != "")
1265     {
1266         self.tur_defend = find(world, targetname, self.target);
1267         if (self.tur_defend == world)
1268         {
1269             self.target = "";
1270             dprint("Turret has invalid defendpoint!\n");
1271         }
1272     }
1273
1274     // In target defend mode, aim on the spot to defend when idle.
1275     if (self.tur_defend)
1276         self.idle_aim  = self.tur_head.angles + angleofs(self.tur_head, self.tur_defend);
1277     else
1278         self.idle_aim  = '0 0 0';
1279
1280     // Attach stdprocs. override when and what needed
1281     self.turret_firecheckfunc   = turret_stdproc_firecheck;
1282     self.turret_firefunc        = turret_stdproc_fire;
1283     self.event_damage           = turret_stdproc_damage;
1284     
1285     if (self.turrcaps_flags & TFL_TURRCAPS_SUPPORT)
1286         self.turret_score_target    = turret_stdproc_targetscore_support;
1287     else
1288         self.turret_score_target    = turret_stdproc_targetscore_generic;
1289
1290     self.use = turret_stdproc_use;
1291     self.bot_attack = TRUE;
1292
1293     ++turret_count;
1294     self.nextthink = time + 1;
1295     self.nextthink +=  turret_count * sys_frametime;
1296
1297     self.tur_head.team = self.team;
1298     self.view_ofs = '0 0 0';
1299
1300 #ifdef TURRET_DEBUG
1301     self.tur_dbg_start = self.nextthink;
1302     while (vlen(self.tur_dbg_rvec) < 2)
1303         self.tur_dbg_rvec  = randomvec() * 4;
1304
1305     self.tur_dbg_rvec_x = fabs(self.tur_dbg_rvec_x);
1306     self.tur_dbg_rvec_y = fabs(self.tur_dbg_rvec_y);
1307     self.tur_dbg_rvec_z = fabs(self.tur_dbg_rvec_z);
1308 #endif
1309
1310     // Its all good.
1311     self.turrcaps_flags |= TFL_TURRCAPS_ISTURRET;
1312
1313     self.classname = "turret_main";
1314
1315     self.tur_active = 1;
1316
1317     // In ONS mode, and linked to a ONS ent. need to call the use to set team.
1318     if (g_onslaught && ee)
1319     {
1320         activator = ee;
1321         self.use();
1322     }
1323     
1324         turret_link();
1325         turret_stdproc_respawn();
1326             
1327     if (!turret_tag_fire_update())
1328         dprint("Warning: Turret ",self.classname, " faild to initialize md3 tags\n");
1329     
1330     return 1;
1331 }
1332
1333