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