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