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