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