]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/tturrets/system/turret_system_main.qc
tZork's turrets, code part (disabled by default, see includes for details). and a...
[divverent/nexuiz.git] / data / qcsrc / server / tturrets / system / turret_system_main.qc
1 #define cvar_base "g_turrets_unit_"\r
2 \r
3 .float tur_lastscore;\r
4 .string cvar_basename;\r
5 \r
6 string cvar_gets(string s_base,string s_add)\r
7 {\r
8     return strcat(s_base,s_add);\r
9 }\r
10 \r
11 .float turret_scale_damage;\r
12 .float turret_scale_range;\r
13 .float turret_scale_refire;\r
14 .float turret_scale_ammo;\r
15 .float turret_scale_aim;\r
16 .float turret_scale_health;\r
17 .float turret_scale_respawn;\r
18 \r
19 void load_unit_settings(entity ent,string unitname,float is_reload)\r
20 {\r
21 \r
22     string sbase;\r
23 \r
24     if (ent == world)\r
25         return;\r
26 \r
27     if (!ent.turret_scale_damage)    ent.turret_scale_damage  = 1;\r
28     if (!ent.turret_scale_range)     ent.turret_scale_range   = 1;\r
29     if (!ent.turret_scale_refire)    ent.turret_scale_refire  = 1;\r
30     if (!ent.turret_scale_ammo)      ent.turret_scale_ammo    = 1;\r
31     if (!ent.turret_scale_aim)       ent.turret_scale_aim     = 1;\r
32     if (!ent.turret_scale_health)    ent.turret_scale_health  = 1;\r
33     if (!ent.turret_scale_respawn)   ent.turret_scale_respawn = 1;\r
34 \r
35     sbase = strcat(cvar_base,unitname);\r
36     if (is_reload)\r
37     {\r
38         ent.enemy = world;\r
39         ent.tur_head.avelocity = '0 0 0';\r
40         ent.tur_head.angles = ent.angles;\r
41     }\r
42     ent.health = cvar(cvar_gets(sbase,"_health")) * ent.turret_scale_health;\r
43     ent.respawntime = cvar(cvar_gets(sbase,"_respawntime")) * ent.turret_scale_respawn;\r
44 \r
45     ent.shot_dmg = cvar(cvar_gets(sbase,"_shot_dmg")) * ent.turret_scale_damage;\r
46     ent.shot_refire = cvar(cvar_gets(sbase,"_shot_refire")) * ent.turret_scale_refire;\r
47     ent.shot_radius = cvar(cvar_gets(sbase,"_shot_radius")) * ent.turret_scale_damage;\r
48     ent.shot_speed = cvar(cvar_gets(sbase,"_shot_speed"));\r
49     ent.shot_spread = cvar(cvar_gets(sbase,"_shot_spread"));\r
50     ent.shot_force = cvar(cvar_gets(sbase,"_shot_force")) * ent.turret_scale_damage;\r
51     ent.shot_volly = cvar(cvar_gets(sbase,"_shot_volly"));\r
52     ent.shot_volly_refire = cvar(cvar_gets(sbase,"_shot_volly_refire")) * ent.turret_scale_refire;\r
53 \r
54     ent.target_range = cvar(cvar_gets(sbase,"_target_range")) * ent.turret_scale_range;\r
55     ent.target_range_min = cvar(cvar_gets(sbase,"_target_range_min")) * ent.turret_scale_range;\r
56     ent.target_range_fire = cvar(cvar_gets(sbase,"_target_range_fire")) * ent.turret_scale_range;\r
57     ent.target_range_optimal = cvar(cvar_gets(sbase,"_target_range_optimal")) * ent.turret_scale_range;\r
58 \r
59     ent.target_select_rangebias = cvar(cvar_gets(sbase,"_target_select_rangebias"));\r
60     ent.target_select_samebias = cvar(cvar_gets(sbase,"_target_select_samebias"));\r
61     ent.target_select_anglebias = cvar(cvar_gets(sbase,"_target_select_anglebias"));\r
62     ent.target_select_playerbias = cvar(cvar_gets(sbase,"_target_select_playerbias"));\r
63 \r
64     ent.ammo_max = cvar(cvar_gets(sbase,"_ammo_max")) * ent.turret_scale_ammo;\r
65     //ent.ammo = cvar(cvar_gets(sbase,"_ammo"));\r
66     ent.ammo_recharge = cvar(cvar_gets(sbase,"_ammo_recharge")) * ent.turret_scale_ammo;\r
67 \r
68     ent.aim_firetolerance_dist = cvar(cvar_gets(sbase,"_aim_firetolerance_dist"));\r
69     ent.aim_firetolerance_angle = cvar(cvar_gets(sbase,"_aim_firetolerance_angle"));\r
70     ent.aim_speed = cvar(cvar_gets(sbase,"_aim_speed")) * ent.turret_scale_aim;\r
71     ent.aim_maxrot = cvar(cvar_gets(sbase,"_aim_maxrot"));\r
72     ent.aim_maxpitch = cvar(cvar_gets(sbase,"_aim_maxpitch"));\r
73 \r
74     ent.track_type = cvar(cvar_gets(sbase,"_track_type"));\r
75     ent.track_accel_pitch = cvar(cvar_gets(sbase,"_track_accel_pitch"));\r
76     ent.track_accel_rot = cvar(cvar_gets(sbase,"_track_accel_rot"));\r
77     ent.track_blendrate = cvar(cvar_gets(sbase,"_track_blendrate"));\r
78 }\r
79 \r
80 float turret_stdproc_true()\r
81 {\r
82     return 1;\r
83 }\r
84 \r
85 float turret_stdproc_false()\r
86 {\r
87     return 0;\r
88 }\r
89 \r
90 void turret_stdproc_nothing()\r
91 {\r
92     return;\r
93 }\r
94 \r
95 /**\r
96 ** updates enemy distances, preicted impact point/time\r
97 ** & aim<->predict impact distance.\r
98 ** Also translates shoot & aimorgs by current rotation.\r
99 **/\r
100 void turret_do_updates(entity e_turret)\r
101 {\r
102     //vector trueaimpoint;\r
103 \r
104     if (self.turrcaps_flags & TFL_TURRCAPS_LINKED)\r
105     {\r
106         e_turret.tur_head.angles_x = e_turret.tur_head.angles_x * -1;\r
107         e_turret.angles_x = e_turret.angles_x * -1;\r
108         makevectors(e_turret.tur_head.angles + e_turret.angles);\r
109         e_turret.tur_head.angles_x = e_turret.tur_head.angles_x * -1;\r
110         e_turret.angles_x = e_turret.angles_x * -1;\r
111     }\r
112     else\r
113     {\r
114         e_turret.tur_head.angles_x = e_turret.tur_head.angles_x * -1;\r
115         makevectors(e_turret.tur_head.angles);\r
116         e_turret.tur_head.angles_x = e_turret.tur_head.angles_x * -1;\r
117     }\r
118 \r
119     //traceline_hitcorpse(e_turret, e_turret.origin, e_turret.origin + v_forward * MAX_SHOT_DISTANCE, MOVE_NOMONSTERS, e_turret);\r
120     //trueaimpoint = trace_endpos;\r
121 \r
122     e_turret.tur_shotorg_updated = e_turret.origin + v_forward * e_turret.tur_shotorg_x + v_right * e_turret.tur_shotorg_y + v_up * e_turret.tur_shotorg_z;\r
123     //e_turret.tur_shotdir_updated = normalize(trueaimpoint - e_turret.tur_shotorg_updated);\r
124     e_turret.tur_shotdir_updated = normalize((e_turret.tur_shotorg_updated + v_forward * 512) - e_turret.tur_shotorg_updated);\r
125     e_turret.tur_aimorg_updated = e_turret.origin + v_forward * e_turret.tur_aimorg_x + v_right * e_turret.tur_aimorg_y + v_up * e_turret.tur_aimorg_z;\r
126     //e_turret.tur_aimorg_updated = e_turret.tur_shotorg_updated;\r
127 \r
128     e_turret.tur_dist_enemy = vlen(e_turret.tur_aimorg_updated - real_origin(e_turret.enemy));\r
129     //e_turret.tur_dist_enemy = vlen(e_turret.tur_aimpos - e_turret.tur_shotorg_updated);\r
130 \r
131     traceline(e_turret.tur_aimorg_updated,e_turret.tur_aimorg_updated+(e_turret.tur_shotdir_updated * e_turret.tur_dist_enemy),TRUE,e_turret);\r
132 \r
133     e_turret.tur_impactpoint = trace_endpos;\r
134     e_turret.tur_impactent = trace_ent;\r
135     e_turret.tur_impacttime = e_turret.tur_dist_enemy / e_turret.shot_speed;\r
136     e_turret.tur_dist_toaimpos = vlen(trace_endpos - e_turret.tur_aimpos);\r
137 }\r
138 \r
139 /**\r
140 ** Handles head rotation according to\r
141 ** the units .track_type and .track_flags\r
142 **/\r
143 void turret_stdproc_track()\r
144 {\r
145     vector wish_angle;  // This is where we'd need to be\r
146     vector real_angle;  // This is where we can go\r
147     float f_tmp;\r
148 \r
149     if (self.track_flags == TFL_TRACK_NO)\r
150         return;\r
151 \r
152     if (self.enemy == world)\r
153     {\r
154         if (self.turrcaps_flags & TFL_TURRCAPS_LINKED)\r
155             wish_angle = self.idle_aim + self.angles;\r
156         else\r
157             wish_angle = self.idle_aim;\r
158     }\r
159     else\r
160     {\r
161         if (self.turrcaps_flags & TFL_TURRCAPS_LINKED)\r
162             wish_angle = normalize(self.tur_aimpos - self.origin); // Find the direction\r
163         else\r
164             wish_angle = normalize(self.tur_aimpos - self.tur_head.origin); // Find the direction\r
165 \r
166         wish_angle = vectoangles(wish_angle);                           // And make a angle\r
167     }\r
168 \r
169     // Find the diffrence between where we currently aim and where we want to aim\r
170     if (self.turrcaps_flags & TFL_TURRCAPS_LINKED)\r
171         real_angle = wish_angle - (self.tur_head.angles + self.angles);\r
172     else\r
173         real_angle = wish_angle - self.tur_head.angles;\r
174 \r
175     // Constrain it within +/- 360\r
176     if (real_angle_x < 0) real_angle_x += 360;\r
177     if (real_angle_x > 180) real_angle_x -= 360;\r
178 \r
179     if (real_angle_y < 0) real_angle_y += 360;\r
180     if (real_angle_y > 180) real_angle_y -= 360;\r
181 \r
182 \r
183     if (self.track_type == TFL_TRACKTYPE_STEPMOTOR)\r
184     {\r
185         f_tmp = self.aim_speed * self.ticrate; // dgr/sec -> dgr/tic\r
186 \r
187         // Limit turning speed\r
188         real_angle_x = bound((-1 * f_tmp),real_angle_x, f_tmp);\r
189         real_angle_y = bound((-1 * f_tmp),real_angle_y, f_tmp);\r
190 \r
191         // Limit pich and rot.\r
192         if (self.track_flags & TFL_TRACK_PITCH)\r
193             self.tur_head.angles_x = bound((-1 * self.aim_maxpitch),self.tur_head.angles_x + real_angle_x,self.aim_maxpitch);\r
194 \r
195         if (self.track_flags & TFL_TRACK_ROT)\r
196             self.tur_head.angles_y = bound((-1 * self.aim_maxrot),self.tur_head.angles_y  + real_angle_y,self.aim_maxrot);\r
197 \r
198         return;\r
199     }\r
200 \r
201     if (self.track_type == TFL_TRACKTYPE_FLUIDPRECISE)\r
202     {\r
203         if (self.track_flags & TFL_TRACK_PITCH)\r
204             self.tur_head.avelocity_x = real_angle_x;\r
205 \r
206         if (self.track_flags & TFL_TRACK_ROT)\r
207             self.tur_head.avelocity_y = real_angle_y;\r
208     }\r
209     else if (self.track_type == TFL_TRACKTYPE_FLUIDINERTIA)\r
210     {\r
211         f_tmp = self.aim_speed * self.ticrate;\r
212 \r
213         real_angle_y = bound(self.aim_speed * -1,real_angle_y * self.track_accel_rot * f_tmp,self.aim_speed);\r
214         real_angle_x = bound(self.aim_speed * -1,real_angle_x * self.track_accel_pitch * f_tmp,self.aim_speed);\r
215         real_angle = (self.tur_head.avelocity * self.track_blendrate) + (real_angle * (1 - self.track_blendrate));\r
216 \r
217         if (self.track_flags & TFL_TRACK_PITCH) self.tur_head.avelocity_x = real_angle_x;\r
218         if (self.track_flags & TFL_TRACK_ROT)   self.tur_head.avelocity_y = real_angle_y;\r
219         self.tur_head.avelocity_z = real_angle_z;\r
220     }\r
221 \r
222     // Limit pitch\r
223     if (self.track_flags & TFL_TRACK_PITCH)\r
224     {\r
225         if (self.tur_head.angles_x > self.aim_maxpitch)\r
226         {\r
227             self.tur_head.angles_x = self.aim_maxpitch;\r
228             self.tur_head.avelocity_x = 0;\r
229         }\r
230 \r
231         if (self.tur_head.angles_x < (self.aim_maxpitch * -1))\r
232         {\r
233             self.tur_head.angles_x = (self.aim_maxpitch * -1);\r
234             self.tur_head.avelocity_x = 0;\r
235         }\r
236     }\r
237 \r
238     // Limit rot\r
239     if (self.track_flags & TFL_TRACK_ROT)\r
240         if (self.tur_head.angles_y > self.aim_maxrot)\r
241         {\r
242             self.tur_head.angles_y = self.aim_maxrot;\r
243             self.tur_head.avelocity_y = 0;\r
244         }\r
245 \r
246     if (self.track_flags & TFL_TRACK_ROT)\r
247         if (self.tur_head.angles_y < (self.aim_maxrot * -1))\r
248         {\r
249             self.tur_head.angles_y = (self.aim_maxrot * -1);\r
250             self.tur_head.avelocity_y = 0;\r
251         }\r
252 \r
253 \r
254 }\r
255 \r
256 /*\r
257  + = implemented\r
258  - = not implemented\r
259 \r
260  + TFL_FIRECHECK_NO\r
261  + TFL_FIRECHECK_WORLD\r
262  + TFL_FIRECHECK_DEAD\r
263  + TFL_FIRECHECK_DISTANCES\r
264  - TFL_FIRECHECK_LOS\r
265  + TFL_FIRECHECK_AIMDIST\r
266  + TFL_FIRECHECK_REALDIST\r
267  - TFL_FIRECHECK_ANGLEDIST\r
268  - TFL_FIRECHECK_TEAMCECK\r
269  + TFL_FIRECHECK_AFF\r
270  + TFL_FIRECHECK_OWM_AMMO\r
271  + TFL_FIRECHECK_OTHER_AMMO\r
272  + TFL_FIRECHECK_REFIRE\r
273 */\r
274 \r
275 /**\r
276 ** Preforms pre-fire checks based on the uints firecheck_flags\r
277 **/\r
278 float turret_stdproc_firecheck()\r
279 {\r
280     // This one just dont care =)\r
281     if (self.firecheck_flags & TFL_FIRECHECK_NO) return 1;\r
282 \r
283     // Ready?\r
284     if (self.firecheck_flags & TFL_FIRECHECK_REFIRE)\r
285         if (self.attack_finished >= time) return 0;\r
286 \r
287     //\r
288     if (self.firecheck_flags & TFL_FIRECHECK_DEAD)\r
289         if (self.enemy.deadflag != DEAD_NO) return 0;\r
290 \r
291     // Plz stop killing the world!\r
292     if (self.firecheck_flags & TFL_FIRECHECK_WORLD)\r
293         if (self.enemy == world) return 0;\r
294 \r
295     // Own ammo?\r
296     if (self.firecheck_flags & TFL_FIRECHECK_OWM_AMMO)\r
297         if (self.ammo < self.shot_dmg) return 0;\r
298 \r
299     // Other's ammo? (carefull using this...)\r
300     if (self.firecheck_flags & TFL_FIRECHECK_OTHER_AMMO)\r
301         if (self.enemy.ammo >= self.enemy.ammo_max) return 0;\r
302 \r
303     if (self.firecheck_flags & TFL_FIRECHECK_DISTANCES)\r
304     {\r
305         // Not close enougth?\r
306         if (self.tur_dist_enemy > self.target_range_fire) return 0;\r
307 \r
308         // To close?\r
309         if (self.tur_dist_enemy < self.target_range_min) return 0;\r
310     }\r
311 \r
312     // Try to avoid FF?\r
313     if (self.firecheck_flags & TFL_FIRECHECK_AFF)\r
314         if (self.tur_impactent.team == self.team) return 0;\r
315 \r
316     // aim<->predicted impact\r
317     if (self.firecheck_flags & TFL_FIRECHECK_AIMDIST)\r
318         if (self.tur_dist_toaimpos  > self.aim_firetolerance_dist) return 0;\r
319 \r
320     // Volly status\r
321     if (self.shot_volly > 1)\r
322     {\r
323         if (self.volly_counter == self.shot_volly)\r
324             if (self.ammo < (self.shot_dmg * self.shot_volly +1))\r
325                 return 0;\r
326     }\r
327 \r
328     //if(self.tur_enemy_adist >= self.aim_firetolerance) return 0;\r
329 \r
330 \r
331     return 1;\r
332 }\r
333 \r
334 /*\r
335  + TFL_TARGETSELECT_NO\r
336  + TFL_TARGETSELECT_LOS\r
337  + TFL_TARGETSELECT_PLAYERS\r
338  + TFL_TARGETSELECT_MISSILES\r
339  - TFL_TARGETSELECT_TRIGGERTARGET\r
340  + TFL_TARGETSELECT_ANGLELIMITS\r
341  + TFL_TARGETSELECT_RANGELIMTS\r
342  + TFL_TARGETSELECT_TEAMCHECK\r
343  - TFL_TARGETSELECT_NOBUILTIN\r
344  + TFL_TARGETSELECT_OWNTEAM\r
345 */\r
346 \r
347 /**\r
348 ** Evaluate a entity for target valitity based on validate_flags\r
349 **/\r
350 float turret_validate_target(entity e_turret,entity e_target,float validate_flags)\r
351 {\r
352     vector v_tmp;\r
353 \r
354     //if(!validate_flags & TFL_TARGETSELECT_NOBUILTIN)\r
355     //    return -0.5;\r
356 \r
357     if (!e_target)// == world)\r
358         return -1;\r
359 \r
360     if (e_target.classname == "grapplinghook")\r
361         return - 1.5;\r
362 \r
363     if (validate_flags & TFL_TARGETSELECT_NO)\r
364         return -2;\r
365 \r
366     // If only this was used more..\r
367     if (e_target.flags & FL_NOTARGET)\r
368         return -3;\r
369 \r
370     // Cant touch this\r
371     if ((e_target.takedamage == DAMAGE_NO) || (e_target.health < 0))\r
372         return -4;\r
373 \r
374     // player\r
375     if (e_target.flags & FL_CLIENT)\r
376     {\r
377         if (!(validate_flags & TFL_TARGETSELECT_PLAYERS))\r
378             return -5;\r
379 \r
380         if (e_target.deadflag != DEAD_NO)\r
381             return -6;\r
382     }\r
383 \r
384     // Missile\r
385     if (e_target.flags & FL_PROJECTILE)\r
386     {\r
387         if (!(validate_flags & TFL_TARGETSELECT_MISSILES))\r
388             return -7;\r
389     }\r
390 \r
391     // Team check\r
392     if (validate_flags & TFL_TARGETSELECT_TEAMCHECK)\r
393     {\r
394         if (validate_flags & TFL_TARGETSELECT_OWNTEAM)\r
395         {\r
396             if (e_target.team != e_turret.team)\r
397                 return -8;\r
398 \r
399             if (e_turret.team != e_target.owner.team)\r
400                 return -8.5;\r
401 \r
402         }\r
403         else\r
404         {\r
405             if (e_target.team == e_turret.team)\r
406                 return -9;\r
407 \r
408             if (e_turret.team == e_target.owner.team)\r
409                 return -9.5;\r
410         }\r
411     }\r
412 \r
413     // Line of sight?\r
414     if (validate_flags & TFL_TARGETSELECT_LOS)\r
415     {\r
416         v_tmp = real_origin(e_target) + ((e_target.mins + e_target.maxs) * 0.5);\r
417         //v_tmp = e_target.origin;\r
418         traceline(e_turret.origin,v_tmp,0,e_turret);\r
419 \r
420         if (e_turret.aim_firetolerance_dist < vlen(v_tmp - trace_endpos))\r
421             return -10;\r
422     }\r
423 \r
424     // Can we even aim this thing? (anglecheck)\r
425     tvt_thadv = angleofs(e_turret.tur_head,e_target);\r
426     tvt_tadv  = angleofs(e_turret,e_target);\r
427     tvt_thadf = vlen(tvt_thadv);\r
428     tvt_tadf  = vlen(tvt_tadv);\r
429 \r
430     if (validate_flags & TFL_TARGETSELECT_ANGLELIMITS)\r
431     {\r
432         if (fabs(tvt_tadv_x) > e_turret.aim_maxpitch)\r
433             return -11;\r
434 \r
435         if (fabs(tvt_tadv_y) > e_turret.aim_maxrot)\r
436             return -12;\r
437     }\r
438 \r
439     // Range limits?\r
440     tvt_dist = vlen(e_turret.origin - real_origin(e_target));\r
441     if (validate_flags & TFL_TARGETSELECT_RANGELIMTS)\r
442     {\r
443         if (tvt_dist < e_turret.target_range_min)\r
444             return -13;\r
445 \r
446         if (tvt_dist > e_turret.target_range)\r
447             return -14;\r
448     }\r
449 \r
450 #ifdef TURRET_DEBUG_TARGETSELECT\r
451     bprint("Target:",e_target.netname," is a valid target for ",e_turret.netname,"\n");\r
452 #endif\r
453 \r
454     return 1;\r
455 }\r
456 \r
457 entity turret_select_target()\r
458 {\r
459     entity e;        // target looper entity\r
460     entity e_enemy;  // currently best scoreing enemy\r
461 \r
462     float score;     // current target (e) score\r
463     float m_score;   // current best target (e_enemy) score\r
464     float f;\r
465     // string s;\r
466     e = findradius(self.origin,self.target_range);\r
467 \r
468     // Nothing to aim at.\r
469     if (!e) return world;\r
470 \r
471     m_score = 0;\r
472 \r
473     while (e)\r
474     {\r
475         f = turret_validate_target(self,e,self.target_select_flags);\r
476         //s = ftos(f);\r
477         //bprint(e.netname, " = ",s,"\n");\r
478         if (f > 0)\r
479         {\r
480 \r
481             score = self.turret_score_target(self,e);\r
482 \r
483             if ((score > m_score) && (score > 0))\r
484             {\r
485                 e_enemy = e;\r
486                 m_score = score;\r
487             }\r
488         }\r
489 \r
490         e = e.chain;\r
491     }\r
492 \r
493     self.tur_lastscore = m_score;\r
494 \r
495     //if (self.enemy != e_enemy)\r
496     //self.volly_counter = 0;\r
497 \r
498     return e_enemy;\r
499 }\r
500 \r
501 void turret_think()\r
502 {\r
503     entity e;\r
504 \r
505     self.nextthink = (time + self.ticrate);\r
506 \r
507     if (cvar("g_turrets_reloadcvars") == 1)\r
508     {\r
509         e = nextent(world);\r
510         while (e)\r
511         {\r
512             if (e.tur_head != world)\r
513             {\r
514                 load_unit_settings(e,e.cvar_basename,1);\r
515                 self.turret_postthink();\r
516             }\r
517 \r
518             e = nextent(e);\r
519         }\r
520 \r
521         cvar_set("g_turrets_reloadcvars","0");\r
522     }\r
523 \r
524 #ifdef TURRET_DEBUG\r
525     if (self.tur_dbg_tmr1 < time)\r
526     {\r
527         if (self.enemy) paint_target (self.enemy,128,self.tur_dbg_rvec,0.9);\r
528         paint_target(self,256,self.tur_dbg_rvec,0.9);\r
529         self.tur_dbg_tmr1 = time + 1;\r
530     }\r
531 #endif\r
532 \r
533     //Do custom prethink, and bail if it fails.\r
534     if (!self.turret_prethink()) return;\r
535 \r
536     // Handle ammo\r
537     if (self.ammo < self.ammo_max)\r
538         self.ammo = min(self.ammo + self.ammo_recharge,self.ammo_max);\r
539 \r
540 \r
541     if ((!self.tur_active) || (self.deadflag != DEAD_NO))\r
542     {\r
543         dprint("Warning: Inactive or dead turret running the think function!\n");\r
544         self.enemy = world;\r
545         self.turret_track();\r
546         return;\r
547     }\r
548 \r
549     if (self.shoot_flags & TFL_SHOOT_HITALLVALID)\r
550     {\r
551 \r
552         // Do a self.turret_fire for every valid target.\r
553         e = findradius(self.origin,self.target_range);\r
554 \r
555         while (e)\r
556         {\r
557             if (turret_validate_target(self,e,self.target_validate_flags))\r
558             {\r
559                 self.enemy = e;\r
560 \r
561                 turret_do_updates(self);\r
562 \r
563                 if ( self.turret_firecheckfunc() ) turret_fire();\r
564             }\r
565 \r
566             e = e.chain;\r
567         }\r
568         self.enemy = world;\r
569 \r
570     }\r
571     else\r
572     {\r
573         // Check if we have a vailid enemy, and get one if we dont.\r
574         if (self.emaster != world)\r
575         {\r
576             self.enemy = self.emaster.enemy;\r
577         }\r
578         else\r
579         {\r
580             // turret_do_updates(self);\r
581             if (turret_validate_target(self,self.enemy,self.target_validate_flags) <= 0)// || (self.cnt < time))\r
582             {\r
583                 self.enemy = turret_select_target();\r
584                 self.cnt = time + self.ticrate * 5;\r
585             }\r
586         }\r
587 \r
588         // No target, just go to idle, do any custom stuff and bail.\r
589         if (self.enemy == world)\r
590         {\r
591             // Turn & pitch\r
592             self.turret_track();\r
593 \r
594             // do any per-turret stuff\r
595             self.turret_postthink();\r
596 \r
597             // And bail.\r
598             return;\r
599         }\r
600 \r
601         // Update\r
602         turret_do_updates(self);\r
603 \r
604         // Predict or whatnot\r
605         if (!self.aim_flags & TFL_AIM_NO)\r
606             self.tur_aimpos = self.turret_aim();\r
607 \r
608         // Turn & pitch\r
609         if (!self.track_flags & TFL_TRACK_NO)\r
610             self.turret_track();\r
611 \r
612         // Update\r
613         turret_do_updates(self);\r
614 \r
615         // Fire?\r
616         if (self.turret_firecheckfunc() != 0)\r
617             turret_fire();\r
618     }\r
619 \r
620     // do any per-turret stuff\r
621     self.turret_postthink();\r
622 }\r
623 \r
624 void turret_fire()\r
625 {\r
626     if (cvar("g_turrets_nofire") != 0)   return;\r
627     if ((!self.tur_active) || (self.deadflag != DEAD_NO)) return;\r
628 \r
629     self.turret_firefunc();\r
630 \r
631     self.attack_finished    = time + self.shot_refire;\r
632     self.ammo               = self.ammo - self.shot_dmg;\r
633     self.volly_counter      = self.volly_counter - 1;\r
634 \r
635 \r
636     if (self.volly_counter <= 0)\r
637     {\r
638         self.volly_counter = self.shot_volly;\r
639         if (self.shoot_flags & TFL_SHOOT_CLEARTARGET) self.enemy = world;\r
640 \r
641         if (self.shot_volly > 1)\r
642             self.attack_finished = time + self.shot_volly_refire;\r
643     }\r
644 \r
645 \r
646 #ifdef TURRET_DEBUG\r
647     if (self.enemy) paint_target3(self.tur_aimpos ,64,self.tur_dbg_rvec,self.tur_impacttime+0.25);\r
648 #endif\r
649 }\r
650 \r
651 void turret_stdproc_fire()\r
652 {\r
653     dprint("^1Bang, ^3your dead^7 ",self.enemy.netname,"! ^1(turret with no real firefunc)\n");\r
654 }\r
655 \r
656 void turret_stdproc_use()\r
657 {\r
658     // bprint("Used:",self.netname,"\n");\r
659     if (self.tur_active)\r
660         self.tur_active = 0;\r
661     else\r
662         self.tur_active = 1;\r
663 \r
664 }\r
665 \r
666 /*\r
667 * Standard turret initialization. use this!\r
668 * (unless you have a very good reason not to.)\r
669 * Any special stuff like multiple cannon models should be done\r
670 * after this is proc called.\r
671 * if the return value is 0, the turret should be removed.\r
672 */\r
673 float turret_stdproc_init (string cvar_base_name)\r
674 {\r
675     // Are turrets allowed atm?\r
676     if (cvar("g_turrets") == 0) return 0;\r
677 \r
678     // Better more then once then never.\r
679     turret_gibs_precash();\r
680 \r
681     if (self.spawnflags & 2)\r
682     {\r
683         entity tb;\r
684         precache_model("models/turrets/terrainbase.md3");\r
685         tb = spawn();\r
686         setmodel(tb,"models/turrets/terrainbase.md3");\r
687         setorigin(tb,self.origin);\r
688         tb.solid = SOLID_BBOX;\r
689         makestatic(tb);\r
690     }\r
691 \r
692     self.cvar_basename = cvar_base_name;\r
693     load_unit_settings(self,self.cvar_basename,0);\r
694 \r
695     // Group all turrets into the same team if in non teamplaymode, so they dont try to kill eachother.\r
696     if (cvar("g_assult") != 0)\r
697     {\r
698         if (!self.team)\r
699             self.team = 14; // Assume turrets are on the defending side if not explicitly set otehrwize\r
700     }\r
701     else\r
702         if ((!teamplay) || (!self.team))\r
703             self.team = MAX_SHOT_DISTANCE;\r
704 \r
705 \r
706     /*\r
707     * Try to guess some reasonaly defaults\r
708     * for missing params and do sanety checks\r
709     * thise checks could produce some "interesting" results\r
710     * if it hits a glitch in my logic :P so try to set as mutch\r
711     * as possible beforehand.\r
712     */\r
713     if (self.turrcaps_flags & TFL_TURRCAPS_SUPPORT)\r
714     {\r
715         // Support units generaly dont need to have a high speed ai-loop\r
716         if (!self.ticrate) self.ticrate = 0.25;     // Speed of this turrets AI loop\r
717     }\r
718     else\r
719     {\r
720         if (!self.ticrate) self.ticrate = 0.1;     // Speed of this turrets AI loop\r
721     }\r
722 \r
723     self.ticrate = bound(0.01,self.ticrate,60);  // keep it sane plz\r
724 \r
725 // General stuff\r
726     if (self.netname == "")  self.netname        = "turret";\r
727 \r
728     if (!self.respawntime) self.respawntime = 60;\r
729     self.respawntime = max(-1,self.respawntime);\r
730 \r
731     if (!self.health)        self.health         = 1000;\r
732     self.tur_health = max(1,self.health);\r
733 \r
734     if (!self.turrcaps_flags) self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL;\r
735 \r
736     if (!self.damage_flags) self.damage_flags = TFL_DMG_YES | TFL_DMG_RETALIATE | TFL_DMG_AIMSHAKE;\r
737 \r
738 // Shot stuff.\r
739     if (!self.shot_refire) self.shot_refire     = 1;\r
740     self.shot_refire = bound(0.01,self.shot_refire,9999);\r
741 \r
742     if (!self.shot_dmg) self.shot_dmg        = self.shot_refire * 50;\r
743     self.shot_dmg = max(1,self.shot_dmg);\r
744 \r
745     if (!self.shot_radius) self.shot_radius     = self.shot_dmg * 0.5;\r
746     self.shot_radius = max(1,self.shot_radius);\r
747 \r
748     if (!self.shot_speed) self.shot_speed      = 2500;\r
749     self.shot_speed = max(1,self.shot_speed);\r
750 \r
751     if (!self.shot_spread) self.shot_spread     = 0.0125;\r
752     self.shot_spread = bound(0.0001,self.shot_spread,500);\r
753 \r
754     if (!self.shot_force) self.shot_force      = self.shot_dmg * 0.5 + self.shot_radius * 0.5;\r
755     self.shot_force = bound(0.001,self.shot_force,MAX_SHOT_DISTANCE * 0.5);\r
756 \r
757     if (!self.shot_volly) self.shot_volly = 1;\r
758     self.shot_volly = bound(1,self.shot_volly,floor(self.ammo_max / self.shot_dmg));\r
759 \r
760     if (!self.shot_volly_refire) self.shot_volly_refire = self.shot_refire * self.shot_volly;\r
761     self.shot_volly_refire = bound(self.shot_refire,self.shot_volly_refire,60);\r
762 \r
763     if (!self.firecheck_flags)\r
764         self.firecheck_flags = TFL_FIRECHECK_WORLD | TFL_FIRECHECK_DEAD | TFL_FIRECHECK_DISTANCES |\r
765                                TFL_FIRECHECK_LOS | TFL_FIRECHECK_AIMDIST | TFL_FIRECHECK_TEAMCECK |\r
766                                TFL_FIRECHECK_OWM_AMMO | TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_WORLD;\r
767 \r
768 // Range stuff.\r
769     if (!self.target_range) self.target_range               = self.shot_speed * 0.5;\r
770     self.target_range = bound(0,self.target_range,MAX_SHOT_DISTANCE);\r
771 \r
772     if (!self.target_range_min)          self.target_range_min           = self.shot_radius * 2;\r
773     self.target_range_min = bound(0,self.target_range_min,MAX_SHOT_DISTANCE);\r
774 \r
775     if (!self.target_range_fire)         self.target_range_fire          = self.target_range * 0.8;\r
776     self.target_range_fire = bound(0,self.target_range_fire,MAX_SHOT_DISTANCE);\r
777 \r
778     if (!self.target_range_optimal)      self.target_range_optimal       = self.target_range_fire * 0.5;\r
779     self.target_range_optimal = bound(0,self.target_range_optimal,MAX_SHOT_DISTANCE);\r
780 \r
781 \r
782 // Aim stuff.\r
783     if (!self.aim_maxrot)    self.aim_maxrot    = 45;\r
784     self.aim_maxrot = bound(0,self.aim_maxrot,361);\r
785 \r
786     if (!self.aim_maxpitch)  self.aim_maxpitch  = 20;\r
787     self.aim_maxpitch = bound(0,self.aim_maxpitch,90);\r
788 \r
789     if (!self.aim_speed)     self.aim_speed     = 36;\r
790     self.aim_speed  = bound(0.1,self.aim_speed, 1000);\r
791 \r
792     if (!self.aim_firetolerance_dist)     self.aim_firetolerance_dist  = 5 + (self.shot_radius * 2);\r
793     self.aim_firetolerance_dist = bound(0.1,self.aim_firetolerance_dist,MAX_SHOT_DISTANCE);\r
794 \r
795     if (!self.aim_firetolerance_angle)     self.aim_firetolerance_angle  = 10;\r
796     self.aim_firetolerance_angle = bound(0.1,self.aim_firetolerance_angle,360);\r
797 \r
798     if (!self.aim_flags) self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE | TFL_AIM_ZEASE;\r
799 \r
800     // Sill the most tested (and aim-effective)\r
801     if (!self.track_type) self.track_type = TFL_TRACKTYPE_STEPMOTOR;\r
802 \r
803     if (self.track_type != TFL_TRACKTYPE_STEPMOTOR)\r
804     {\r
805         // Fluid / ineria mode. Looks mutch nicer, bit experimental &\r
806         // Can inmapt aim preformance alot.\r
807         // needs a bit diffrent aimspeed\r
808         if (!self.aim_speed) self.aim_speed = 180;\r
809         self.aim_speed  = bound(0.1,self.aim_speed, 1000);\r
810 \r
811         if (!self.track_accel_pitch) self.track_accel_pitch = 0.75;\r
812         if (!self.track_accel_rot)   self.track_accel_rot   = 0.75;\r
813         if (!self.track_blendrate)   self.track_blendrate   = 0.35;\r
814     }\r
815 \r
816     if (!self.track_flags) self.track_flags = TFL_TRACK_PITCH | TFL_TRACK_ROT;\r
817 \r
818 \r
819 // Target selection stuff.\r
820     if (!self.target_select_rangebias)   self.target_select_rangebias     = 1;\r
821     self.target_select_rangebias = bound(-10,self.target_select_rangebias,10);\r
822 \r
823     if (!self.target_select_samebias)    self.target_select_samebias      = 1;\r
824     self.target_select_samebias = bound(-10,self.target_select_samebias,10);\r
825 \r
826     if (!self.target_select_anglebias)   self.target_select_anglebias     = 1;\r
827     self.target_select_anglebias = bound(-10,self.target_select_anglebias,10);\r
828 \r
829     if (!self.target_select_missilebias)   self.target_select_missilebias = -10;\r
830     self.target_select_missilebias = bound(-10,self.target_select_missilebias,10);\r
831     self.target_select_playerbias = bound(-10,self.target_select_playerbias,10);\r
832 \r
833     if (!self.target_select_flags)\r
834         if (self.turrcaps_flags & TFL_TURRCAPS_MISSILEKILL)\r
835             self.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_MISSILES |\r
836                                        TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_ANGLELIMITS;\r
837         else\r
838             self.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_PLAYERS |\r
839                                        TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_ANGLELIMITS;\r
840 \r
841     //if(!self.target_validate_flags)\r
842     self.target_validate_flags = self.target_select_flags;\r
843 \r
844 \r
845 // Ammo stuff\r
846     if (!self.ammo_max)          self.ammo_max       = self.shot_dmg * 10;\r
847     self.ammo_max = max(self.shot_dmg,self.ammo_max);\r
848 \r
849     if (!self.ammo)              self.ammo           = self.shot_dmg * 5;\r
850     self.ammo = bound(0,self.ammo,self.ammo_max);\r
851 \r
852     if (!self.ammo_recharge)     self.ammo_recharge = self.shot_dmg / 2;\r
853     self.ammo_recharge = max(0,self.ammo_recharge);\r
854 \r
855     // Convert the recharge from X per sec to X per ticrate\r
856     self.ammo_recharge = self.ammo_recharge * self.ticrate;\r
857 \r
858     if (!self.ammo_flags) self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE;\r
859 \r
860 // Offsets & origins\r
861     if (!self.tur_aimorg)    self.tur_aimorg = '0 0 50';\r
862     if (!self.tur_shotorg)   self.tur_shotorg = '50 0 50';\r
863 \r
864 // End of default & sanety checks, start building the turret.\r
865 \r
866 // Spawn extra bits\r
867     self.tur_head   = spawn();\r
868 \r
869     self.tur_head.netname = self.tur_head.classname     = "turret_head";\r
870     self.tur_head.team = self.team;\r
871 \r
872     //Slave mode?\r
873     if (self.master_nameof != "")\r
874     {\r
875         self.emaster = find(world, master_name, self.master_name);\r
876         if (self.emaster == world)\r
877         {\r
878             self.master_nameof = "";\r
879             dprint("Turret has bad invalid!\n");\r
880         }\r
881     }\r
882 \r
883     // Defend mode?\r
884     if (self.target != "")\r
885     {\r
886         self.tur_defend = find(world, targetname, self.target);\r
887         if (self.tur_defend == world)\r
888         {\r
889             self.target = "";\r
890             dprint("Turret has invalid defendpoint!\n");\r
891         }\r
892     }\r
893 \r
894 // Claim ownership\r
895     self.tur_head.owner   = self;\r
896 \r
897 // Put pices in place\r
898 \r
899     if (!(self.turrcaps_flags & TFL_TURRCAPS_LINKED))\r
900         setorigin(self.tur_head,self.origin);\r
901 \r
902     // In target defense mode, aim on the spot to defens when idle.\r
903     if (self.tur_defend)\r
904         self.idle_aim  = self.tur_head.angles + angleofs(self.tur_head,self.tur_defend);\r
905     else\r
906         self.idle_aim  = self.angles;\r
907 \r
908     if (!(self.turrcaps_flags & TFL_TURRCAPS_LINKED))\r
909         self.tur_head.angles    = self.idle_aim;\r
910 \r
911     if (!self.health) self.health  = 150;\r
912     self.tur_health = self.health;\r
913 \r
914     //Solid bbox for preformance reasons\r
915     self.solid              = SOLID_BBOX;\r
916     self.tur_head.solid     = SOLID_BBOX;\r
917 \r
918     self.takedamage             = DAMAGE_AIM;\r
919     self.tur_head.takedamage    = DAMAGE_AIM;\r
920 \r
921     self.movetype            = MOVETYPE_NOCLIP;\r
922     self.tur_head.movetype   = MOVETYPE_NOCLIP;\r
923 \r
924     // Team colouring?track\r
925     if (self.team == COLOR_TEAM1) self.colormod = '1.4 0.8 0.8';\r
926     if (self.team == COLOR_TEAM2) self.colormod = '0.8 0.8 1.4';\r
927 \r
928     // Attach stdprocs. override when and what needed\r
929     if (self.turrcaps_flags & TFL_TURRCAPS_SUPPORT)\r
930     {\r
931         self.turret_prethink        = turret_stdproc_true;\r
932         self.turret_score_target    = turret_stdproc_targetscore_support;\r
933         self.turret_aim             = turret_stdproc_aim_generic;\r
934         self.turret_track           = turret_stdproc_track;\r
935         self.turret_firecheckfunc   = turret_stdproc_firecheck;\r
936         self.turret_firefunc        = turret_stdproc_fire;\r
937         self.turret_postthink       = turret_stdproc_nothing;\r
938 \r
939         self.turret_damagefunc          = turret_stdproc_damage;\r
940         self.event_damage               = turret_stdproc_damage;\r
941         self.tur_head.event_damage      = turret_stdproc_damage;\r
942 \r
943         self.turret_diefunc             = turret_stdproc_die;\r
944         self.turret_spawnfunc           = turret_stdproc_respawn;\r
945 \r
946     }\r
947     else\r
948     {\r
949 \r
950         self.turret_prethink        = turret_stdproc_true;\r
951         self.turret_score_target    = turret_stdproc_targetscore_generic;\r
952 \r
953         if (self.aim_flags & TFL_AIM_SIMPLE)\r
954             self.turret_aim             = turret_stdproc_aim_simple;\r
955         else\r
956             self.turret_aim             = turret_stdproc_aim_generic;\r
957 \r
958         self.turret_track           = turret_stdproc_track;\r
959         self.turret_firecheckfunc   = turret_stdproc_firecheck;\r
960         self.turret_firefunc        = turret_stdproc_fire;\r
961         self.turret_postthink       = turret_stdproc_nothing;\r
962 \r
963         self.turret_damagefunc          = turret_stdproc_damage;\r
964         self.event_damage               = turret_stdproc_damage;\r
965         self.tur_head.event_damage      = turret_stdproc_damage;\r
966 \r
967         self.turret_diefunc             = turret_stdproc_die;\r
968         self.turret_spawnfunc           = turret_stdproc_respawn;\r
969         self.turret_addtarget           = turret_stdproc_false;\r
970     }\r
971 \r
972     self.use = turret_stdproc_use;\r
973 \r
974     // Initiate the main AI loop\r
975     self.think     = turret_think;\r
976     self.nextthink = time + self.ticrate;\r
977 \r
978     self.tur_head.team = self.team;\r
979     self.view_ofs = '0 0 0';\r
980 \r
981 #ifdef TURRET_DEBUG\r
982     self.tur_dbg_start = self.nextthink;\r
983     while (vlen(self.tur_dbg_rvec) < 2)\r
984         self.tur_dbg_rvec  = randomvec() * 4;\r
985 \r
986     self.tur_dbg_rvec_x = fabs(self.tur_dbg_rvec_x);\r
987     self.tur_dbg_rvec_y = fabs(self.tur_dbg_rvec_y);\r
988     self.tur_dbg_rvec_z = fabs(self.tur_dbg_rvec_z);\r
989 #endif\r
990 \r
991     // Its all good.\r
992     self.classname = "turret_main";\r
993 \r
994     self.tur_active = 1;\r
995 \r
996     return 1;\r
997 }\r
998 \r
999 \r