Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | /* |
2 | * Portions of this file are copyright Rebirth contributors and licensed as |
||
3 | * described in COPYING.txt. |
||
4 | * Portions of this file are copyright Parallax Software and licensed |
||
5 | * according to the Parallax license below. |
||
6 | * See COPYING.txt for license details. |
||
7 | |||
8 | THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX |
||
9 | SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO |
||
10 | END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A |
||
11 | ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS |
||
12 | IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS |
||
13 | SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE |
||
14 | FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE |
||
15 | CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS |
||
16 | AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. |
||
17 | COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. |
||
18 | */ |
||
19 | |||
20 | /* |
||
21 | * |
||
22 | * This will contain the laser code |
||
23 | * |
||
24 | */ |
||
25 | |||
26 | #include <stdlib.h> |
||
27 | #include <stdio.h> |
||
28 | #include <time.h> |
||
29 | |||
30 | #include "inferno.h" |
||
31 | #include "game.h" |
||
32 | #include "bm.h" |
||
33 | #include "object.h" |
||
34 | #include "laser.h" |
||
35 | #include "args.h" |
||
36 | #include "segment.h" |
||
37 | #include "fvi.h" |
||
38 | #include "segpoint.h" |
||
39 | #include "dxxerror.h" |
||
40 | #include "key.h" |
||
41 | #include "texmap.h" |
||
42 | #include "gameseg.h" |
||
43 | #include "textures.h" |
||
44 | #include "render.h" |
||
45 | #include "vclip.h" |
||
46 | #include "fireball.h" |
||
47 | #include "polyobj.h" |
||
48 | #include "robot.h" |
||
49 | #include "weapon.h" |
||
50 | #include "newdemo.h" |
||
51 | #include "timer.h" |
||
52 | #include "player.h" |
||
53 | #include "sounds.h" |
||
54 | #include "ai.h" |
||
55 | #include "powerup.h" |
||
56 | #include "multi.h" |
||
57 | #include "physics.h" |
||
58 | #include "multi.h" |
||
59 | #include "fwd-wall.h" |
||
60 | #include "playsave.h" |
||
61 | |||
62 | #include "compiler-range_for.h" |
||
63 | #include "partial_range.h" |
||
64 | |||
65 | #ifdef NEWHOMER |
||
66 | #define HOMING_TRACKABLE_DOT_FRAME_TIME HOMING_TURN_TIME |
||
67 | static ubyte d_homer_tick_step = 0; |
||
68 | static fix d_homer_tick_count = 0; |
||
69 | #else |
||
70 | #define HOMING_TRACKABLE_DOT_FRAME_TIME FrameTime |
||
71 | #endif |
||
72 | |||
73 | static int Muzzle_queue_index; |
||
74 | |||
75 | namespace dsx { |
||
76 | static imobjptridx_t find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker, int track_obj_type1, int track_obj_type2); |
||
77 | static imobjptridx_t find_homing_object(const vms_vector &curpos, vmobjptridx_t tracker); |
||
78 | |||
79 | //--------------------------------------------------------------------------------- |
||
80 | // Called by render code.... determines if the laser is from a robot or the |
||
81 | // player and calls the appropriate routine. |
||
82 | |||
83 | void Laser_render(grs_canvas &canvas, const object_base &obj) |
||
84 | { |
||
85 | auto &wi = Weapon_info[get_weapon_id(obj)]; |
||
86 | switch(wi.render_type) |
||
87 | { |
||
88 | case WEAPON_RENDER_LASER: |
||
89 | Int3(); // Not supported anymore! |
||
90 | //Laser_draw_one(obj-Objects, Weapon_info[obj->id].bitmap ); |
||
91 | break; |
||
92 | case WEAPON_RENDER_BLOB: |
||
93 | draw_object_blob(canvas, obj, wi.bitmap); |
||
94 | break; |
||
95 | case WEAPON_RENDER_POLYMODEL: |
||
96 | break; |
||
97 | case WEAPON_RENDER_VCLIP: |
||
98 | Int3(); // Oops, not supported, type added by mk on 09/09/94, but not for lasers... |
||
99 | DXX_BOOST_FALLTHROUGH; |
||
100 | default: |
||
101 | Error( "Invalid weapon render type in Laser_render\n" ); |
||
102 | } |
||
103 | } |
||
104 | |||
105 | //--------------------------------------------------------------------------------- |
||
106 | // Draws a texture-mapped laser bolt |
||
107 | |||
108 | //void Laser_draw_one( int objnum, grs_bitmap * bmp ) |
||
109 | //{ |
||
110 | // int t1, t2, t3; |
||
111 | // g3s_point p1, p2; |
||
112 | // object *obj; |
||
113 | // vms_vector start_pos,end_pos; |
||
114 | // |
||
115 | // obj = &Objects[objnum]; |
||
116 | // |
||
117 | // start_pos = obj->pos; |
||
118 | // vm_vec_scale_add(&end_pos,&start_pos,&obj->orient.fvec,-Laser_length); |
||
119 | // |
||
120 | // g3_rotate_point(&p1,&start_pos); |
||
121 | // g3_rotate_point(&p2,&end_pos); |
||
122 | // |
||
123 | // t1 = Lighting_on; |
||
124 | // t2 = Interpolation_method; |
||
125 | // t3 = Transparency_on; |
||
126 | // |
||
127 | // Lighting_on = 0; |
||
128 | // //Interpolation_method = 3; // Full perspective |
||
129 | // Interpolation_method = 1; // Linear |
||
130 | // Transparency_on = 1; |
||
131 | // |
||
132 | // //gr_setcolor( gr_getcolor(31,15,0)); |
||
133 | // //g3_draw_line_ptrs(p1,p2); |
||
134 | // //g3_draw_rod(p1,0x2000,p2,0x2000); |
||
135 | // //g3_draw_rod(p1,Laser_width,p2,Laser_width); |
||
136 | // g3_draw_rod_tmap(bmp,&p2,Laser_width,&p1,Laser_width,0); |
||
137 | // Lighting_on = t1; |
||
138 | // Interpolation_method = t2; |
||
139 | // Transparency_on = t3; |
||
140 | // |
||
141 | //} |
||
142 | |||
143 | static bool ignore_proximity_weapon(const object &o) |
||
144 | { |
||
145 | if (!is_proximity_bomb_or_player_smart_mine(get_weapon_id(o))) |
||
146 | return false; |
||
147 | #if defined(DXX_BUILD_DESCENT_I) |
||
148 | return GameTime64 > o.ctype.laser_info.creation_time + F1_0*2; |
||
149 | #elif defined(DXX_BUILD_DESCENT_II) |
||
150 | return GameTime64 > o.ctype.laser_info.creation_time + F1_0*4; |
||
151 | #endif |
||
152 | } |
||
153 | |||
154 | #if defined(DXX_BUILD_DESCENT_I) |
||
155 | static bool ignore_phoenix_weapon(const object &) |
||
156 | { |
||
157 | return false; |
||
158 | } |
||
159 | |||
160 | static bool ignore_guided_missile_weapon(const object &) |
||
161 | { |
||
162 | return false; |
||
163 | } |
||
164 | #elif defined(DXX_BUILD_DESCENT_II) |
||
165 | static bool ignore_phoenix_weapon(const object &o) |
||
166 | { |
||
167 | return get_weapon_id(o) == weapon_id_type::PHOENIX_ID && GameTime64 > o.ctype.laser_info.creation_time + F1_0/4; |
||
168 | } |
||
169 | |||
170 | static bool ignore_guided_missile_weapon(const object &o) |
||
171 | { |
||
172 | return get_weapon_id(o) == weapon_id_type::GUIDEDMISS_ID && GameTime64 > o.ctype.laser_info.creation_time + F1_0*2; |
||
173 | } |
||
174 | #endif |
||
175 | |||
176 | // Changed by MK on 09/07/94 |
||
177 | // I want you to be able to blow up your own bombs. |
||
178 | // AND...Your proximity bombs can blow you up if they're 2.0 seconds or more old. |
||
179 | // Changed by MK on 06/06/95: Now must be 4.0 seconds old. Much valid Net-complaining. |
||
180 | bool laser_are_related(const vcobjptridx_t o1, const vcobjptridx_t o2) |
||
181 | { |
||
182 | // See if o2 is the parent of o1 |
||
183 | if (o1->type == OBJ_WEAPON) |
||
184 | if (laser_parent_is_object(o1->ctype.laser_info, o2)) |
||
185 | { |
||
186 | // o1 is a weapon, o2 is the parent of 1, so if o1 is PROXIMITY_BOMB and o2 is player, they are related only if o1 < 2.0 seconds old |
||
187 | if (ignore_proximity_weapon(o1) || ignore_guided_missile_weapon(o1) || ignore_phoenix_weapon(o1)) |
||
188 | { |
||
189 | return 0; |
||
190 | } else |
||
191 | return 1; |
||
192 | } |
||
193 | |||
194 | // See if o1 is the parent of o2 |
||
195 | if (o2->type == OBJ_WEAPON) |
||
196 | { |
||
197 | if (laser_parent_is_object(o2->ctype.laser_info, o1)) |
||
198 | { |
||
199 | #if defined(DXX_BUILD_DESCENT_II) |
||
200 | // o2 is a weapon, o1 is the parent of 2, so if o2 is PROXIMITY_BOMB and o1 is player, they are related only if o1 < 2.0 seconds old |
||
201 | if (ignore_proximity_weapon(o2) || ignore_guided_missile_weapon(o2) || ignore_phoenix_weapon(o2)) |
||
202 | { |
||
203 | return 0; |
||
204 | } else |
||
205 | #endif |
||
206 | return 1; |
||
207 | } |
||
208 | } |
||
209 | |||
210 | // They must both be weapons |
||
211 | if (o1->type != OBJ_WEAPON || o2->type != OBJ_WEAPON) |
||
212 | return 0; |
||
213 | |||
214 | // Here is the 09/07/94 change -- Siblings must be identical, others can hurt each other |
||
215 | // See if they're siblings... |
||
216 | // MK: 06/08/95, Don't allow prox bombs to detonate for 3/4 second. Else too likely to get toasted by your own bomb if hit by opponent. |
||
217 | const auto o1id = get_weapon_id(o1); |
||
218 | const auto o2id = get_weapon_id(o2); |
||
219 | auto &o1li = o1->ctype.laser_info; |
||
220 | auto &o2li = o2->ctype.laser_info; |
||
221 | if (o1li.parent_num == o2li.parent_num && o1li.parent_signature == o2li.parent_signature) |
||
222 | { |
||
223 | if (is_proximity_bomb_or_player_smart_mine(o1id) || is_proximity_bomb_or_player_smart_mine(o2id)) |
||
224 | { |
||
225 | // If neither is older than 1/2 second, then can't blow up! |
||
226 | #if defined(DXX_BUILD_DESCENT_II) |
||
227 | if (!(GameTime64 > o1li.creation_time + F1_0/2 || GameTime64 > o2li.creation_time + F1_0/2)) |
||
228 | return 1; |
||
229 | else |
||
230 | #endif |
||
231 | return 0; |
||
232 | } else |
||
233 | return 1; |
||
234 | } |
||
235 | |||
236 | #if defined(DXX_BUILD_DESCENT_II) |
||
237 | // Anything can cause a collision with a robot super prox mine. |
||
238 | if (!( |
||
239 | o1id == weapon_id_type::ROBOT_SUPERPROX_ID || o2id == weapon_id_type::ROBOT_SUPERPROX_ID || |
||
240 | o1id == weapon_id_type::PROXIMITY_ID || o2id == weapon_id_type::PROXIMITY_ID || |
||
241 | o1id == weapon_id_type::SUPERPROX_ID || o2id == weapon_id_type::SUPERPROX_ID || |
||
242 | o1id == weapon_id_type::PMINE_ID || o2id == weapon_id_type::PMINE_ID |
||
243 | )) |
||
244 | return 1; |
||
245 | #endif |
||
246 | return 0; |
||
247 | } |
||
248 | |||
249 | } |
||
250 | |||
251 | namespace dcx { |
||
252 | |||
253 | constexpr vm_distance MAX_SMART_DISTANCE(F1_0*150); |
||
254 | constexpr vm_distance_squared MAX_SMART_DISTANCE_SQUARED = MAX_SMART_DISTANCE * MAX_SMART_DISTANCE; |
||
255 | static void do_muzzle_stuff(segnum_t segnum, const vms_vector &pos) |
||
256 | { |
||
257 | auto &m = Muzzle_data[Muzzle_queue_index]; |
||
258 | Muzzle_queue_index++; |
||
259 | if (Muzzle_queue_index >= MUZZLE_QUEUE_MAX) |
||
260 | Muzzle_queue_index = 0; |
||
261 | m.segnum = segnum; |
||
262 | m.pos = pos; |
||
263 | m.create_time = timer_query(); |
||
264 | } |
||
265 | |||
266 | __attribute_noreturn |
||
267 | static void report_invalid_weapon_render_type(const int weapon_type, const unsigned render_type) |
||
268 | { |
||
269 | char buf[96]; |
||
270 | snprintf(buf, sizeof(buf), "invalid weapon render type %u on weapon %i", render_type, weapon_type); |
||
271 | throw std::runtime_error(buf); |
||
272 | } |
||
273 | |||
274 | } |
||
275 | |||
276 | namespace dsx { |
||
277 | |||
278 | //creates a weapon object |
||
279 | static imobjptridx_t create_weapon_object(int weapon_type,const vmsegptridx_t segnum, const vms_vector &position) |
||
280 | { |
||
281 | render_type_t rtype; |
||
282 | fix laser_radius = -1; |
||
283 | |||
284 | switch( Weapon_info[weapon_type].render_type ) { |
||
285 | |||
286 | case WEAPON_RENDER_BLOB: |
||
287 | rtype = RT_LASER; // Render as a laser even if blob (see render code above for explanation) |
||
288 | laser_radius = Weapon_info[weapon_type].blob_size; |
||
289 | break; |
||
290 | case WEAPON_RENDER_POLYMODEL: |
||
291 | laser_radius = 0; // Filled in below. |
||
292 | rtype = RT_POLYOBJ; |
||
293 | break; |
||
294 | case WEAPON_RENDER_LASER: |
||
295 | Int3(); // Not supported anymore |
||
296 | return object_none; |
||
297 | case WEAPON_RENDER_NONE: |
||
298 | rtype = RT_NONE; |
||
299 | laser_radius = F1_0; |
||
300 | break; |
||
301 | case WEAPON_RENDER_VCLIP: |
||
302 | rtype = RT_WEAPON_VCLIP; |
||
303 | laser_radius = Weapon_info[weapon_type].blob_size; |
||
304 | break; |
||
305 | default: |
||
306 | report_invalid_weapon_render_type(weapon_type, Weapon_info[weapon_type].render_type); |
||
307 | } |
||
308 | |||
309 | Assert(laser_radius != -1); |
||
310 | |||
311 | auto &&obj = obj_create( OBJ_WEAPON, weapon_type, segnum, position, NULL, laser_radius, CT_WEAPON, MT_PHYSICS, rtype); |
||
312 | if (obj == object_none) |
||
313 | return object_none; |
||
314 | |||
315 | if (Weapon_info[weapon_type].render_type == WEAPON_RENDER_POLYMODEL) { |
||
316 | auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; |
||
317 | obj->rtype.pobj_info.model_num = Weapon_info[get_weapon_id(obj)].model_num; |
||
318 | obj->size = fixdiv(Polygon_models[obj->rtype.pobj_info.model_num].rad,Weapon_info[get_weapon_id(obj)].po_len_to_width_ratio); |
||
319 | } |
||
320 | |||
321 | obj->mtype.phys_info.mass = Weapon_info[weapon_type].mass; |
||
322 | obj->mtype.phys_info.drag = Weapon_info[weapon_type].drag; |
||
323 | vm_vec_zero(obj->mtype.phys_info.thrust); |
||
324 | |||
325 | if (Weapon_info[weapon_type].bounce==1) |
||
326 | obj->mtype.phys_info.flags |= PF_BOUNCE; |
||
327 | |||
328 | #if defined(DXX_BUILD_DESCENT_II) |
||
329 | if (Weapon_info[weapon_type].bounce==2 || cheats.bouncyfire) |
||
330 | obj->mtype.phys_info.flags |= PF_BOUNCE+PF_BOUNCES_TWICE; |
||
331 | #endif |
||
332 | |||
333 | |||
334 | return obj; |
||
335 | } |
||
336 | |||
337 | #if defined(DXX_BUILD_DESCENT_II) |
||
338 | // ------------------------------------------------------------------------------------------------------------------------------- |
||
339 | // ***** HEY ARTISTS!! ***** |
||
340 | // Here are the constants you're looking for! --MK |
||
341 | |||
342 | // Change the following constants to affect the look of the omega cannon. |
||
343 | // Changing these constants will not affect the damage done. |
||
344 | // WARNING: If you change DESIRED_OMEGA_DIST and MAX_OMEGA_BLOBS, you don't merely change the look of the cannon, |
||
345 | // you change its range. If you decrease DESIRED_OMEGA_DIST, you decrease how far the gun can fire. |
||
346 | constexpr std::integral_constant<fix, F1_0/20> OMEGA_BASE_TIME{}; // How many blobs per second!! No FPS-based blob creation anymore, no FPS-based damage anymore! |
||
347 | constexpr std::integral_constant<unsigned, 3> MIN_OMEGA_BLOBS{}; // No matter how close the obstruction, at this many blobs created. |
||
348 | constexpr std::integral_constant<fix, F1_0*3> MIN_OMEGA_DIST{}; // At least this distance between blobs, unless doing so would violate MIN_OMEGA_BLOBS |
||
349 | constexpr std::integral_constant<fix, F1_0*5> DESIRED_OMEGA_DIST{}; // This is the desired distance between blobs. For distances > MIN_OMEGA_BLOBS*DESIRED_OMEGA_DIST, but not very large, this will apply. |
||
350 | constexpr std::integral_constant<unsigned, 16> MAX_OMEGA_BLOBS{}; // No matter how far away the obstruction, this is the maximum number of blobs. |
||
351 | constexpr vm_distance MAX_OMEGA_DIST{MAX_OMEGA_BLOBS * DESIRED_OMEGA_DIST}; // Maximum extent of lightning blobs. |
||
352 | constexpr vm_distance_squared MAX_OMEGA_DIST_SQUARED{MAX_OMEGA_DIST * MAX_OMEGA_DIST}; |
||
353 | |||
354 | // Additionally, several constants which apply to homing objects in general control the behavior of the Omega Cannon. |
||
355 | // They are defined in laser.h. They are copied here for reference. These values are valid on 1/10/96: |
||
356 | // If you want the Omega Cannon view cone to be different than the Homing Missile viewcone, contact MK to make the change. |
||
357 | // (Unless you are a programmer, in which case, do it yourself!) |
||
358 | #define OMEGA_MIN_TRACKABLE_DOT (15*F1_0/16) // Larger values mean narrower cone. F1_0 means damn near impossible. 0 means 180 degree field of view. |
||
359 | constexpr vm_distance OMEGA_MAX_TRACKABLE_DIST = MAX_OMEGA_DIST; // An object must be at least this close to be tracked. |
||
360 | |||
361 | // Note, you don't need to change these constants. You can control damage and energy consumption by changing the |
||
362 | // usual bitmaps.tbl parameters. |
||
363 | #define OMEGA_DAMAGE_SCALE 32 // Controls how much damage is done. This gets multiplied by the damage specified in bitmaps.tbl in the $WEAPON line. |
||
364 | #define OMEGA_ENERGY_CONSUMPTION 16 // Controls how much energy is consumed. This gets multiplied by the energy parameter from bitmaps.tbl. |
||
365 | // ------------------------------------------------------------------------------------------------------------------------------- |
||
366 | |||
367 | // Delete omega blobs further away than MAX_OMEGA_DIST |
||
368 | // Since last omega blob has VERY high velocity it's impossible to ensure a constant travel distance on varying FPS. So delete if they exceed their maximum distance. |
||
369 | static int omega_cleanup(fvcobjptr &vcobjptr, const vmobjptridx_t weapon) |
||
370 | { |
||
371 | if (weapon->type != OBJ_WEAPON || get_weapon_id(weapon) != weapon_id_type::OMEGA_ID) |
||
372 | return 0; |
||
373 | auto &weapon_laser_info = weapon->ctype.laser_info; |
||
374 | auto &obj = *vcobjptr(weapon_laser_info.parent_num); |
||
375 | if (laser_parent_is_matching_signature(weapon_laser_info, obj)) |
||
376 | if (vm_vec_dist2(weapon->pos, obj.pos) > MAX_OMEGA_DIST_SQUARED) |
||
377 | { |
||
378 | obj_delete(LevelUniqueObjectState, Segments, weapon); |
||
379 | return 1; |
||
380 | } |
||
381 | |||
382 | return 0; |
||
383 | } |
||
384 | |||
385 | // Return true if ok to do Omega damage. For Multiplayer games. See comment for omega_cleanup() |
||
386 | int ok_to_do_omega_damage(const object &weapon) |
||
387 | { |
||
388 | auto &Objects = LevelUniqueObjectState.Objects; |
||
389 | auto &vcobjptr = Objects.vcptr; |
||
390 | if (weapon.type != OBJ_WEAPON || get_weapon_id(weapon) != weapon_id_type::OMEGA_ID) |
||
391 | return 1; |
||
392 | if (!(Game_mode & GM_MULTI)) |
||
393 | return 1; |
||
394 | auto &weapon_laser_info = weapon.ctype.laser_info; |
||
395 | auto &obj = *vcobjptr(weapon_laser_info.parent_num); |
||
396 | if (laser_parent_is_matching_signature(weapon_laser_info, obj)) |
||
397 | if (vm_vec_dist2(obj.pos, weapon.pos) > MAX_OMEGA_DIST_SQUARED) |
||
398 | return 0; |
||
399 | |||
400 | return 1; |
||
401 | } |
||
402 | |||
403 | // --------------------------------------------------------------------------------- |
||
404 | static void create_omega_blobs(const imsegptridx_t firing_segnum, const vms_vector &firing_pos, const vms_vector &goal_pos, const vmobjptridx_t parent_objp) |
||
405 | { |
||
406 | imobjptridx_t last_created_objnum = object_none; |
||
407 | fix dist_to_goal = 0, omega_blob_dist = 0; |
||
408 | std::array<fix, MAX_OMEGA_BLOBS> perturb_array{}; |
||
409 | |||
410 | auto vec_to_goal = vm_vec_sub(goal_pos, firing_pos); |
||
411 | dist_to_goal = vm_vec_normalize_quick(vec_to_goal); |
||
412 | |||
413 | unsigned num_omega_blobs = 0; |
||
414 | if (dist_to_goal < MIN_OMEGA_BLOBS * MIN_OMEGA_DIST) { |
||
415 | omega_blob_dist = MIN_OMEGA_DIST; |
||
416 | num_omega_blobs = dist_to_goal/omega_blob_dist; |
||
417 | if (num_omega_blobs == 0) |
||
418 | num_omega_blobs = 1; |
||
419 | } else { |
||
420 | omega_blob_dist = DESIRED_OMEGA_DIST; |
||
421 | num_omega_blobs = dist_to_goal / omega_blob_dist; |
||
422 | if (num_omega_blobs > MAX_OMEGA_BLOBS) { |
||
423 | num_omega_blobs = MAX_OMEGA_BLOBS; |
||
424 | omega_blob_dist = dist_to_goal / num_omega_blobs; |
||
425 | } else if (num_omega_blobs < MIN_OMEGA_BLOBS) { |
||
426 | num_omega_blobs = MIN_OMEGA_BLOBS; |
||
427 | omega_blob_dist = dist_to_goal / num_omega_blobs; |
||
428 | } |
||
429 | } |
||
430 | |||
431 | vms_vector omega_delta_vector, blob_pos; |
||
432 | omega_delta_vector = vec_to_goal; |
||
433 | vm_vec_scale(omega_delta_vector, omega_blob_dist); |
||
434 | |||
435 | // Now, create all the blobs |
||
436 | blob_pos = firing_pos; |
||
437 | auto last_segnum = firing_segnum; |
||
438 | |||
439 | // If nearby, don't perturb vector. If not nearby, start halfway out. |
||
440 | if (dist_to_goal < MIN_OMEGA_DIST*4) { |
||
441 | } else { |
||
442 | vm_vec_scale_add2(blob_pos, omega_delta_vector, F1_0/2); // Put first blob half way out. |
||
443 | for (int i=0; i<num_omega_blobs/2; i++) { |
||
444 | perturb_array[i] = F1_0*i + F1_0/4; |
||
445 | perturb_array[num_omega_blobs-1-i] = F1_0*i; |
||
446 | } |
||
447 | } |
||
448 | |||
449 | // Create random perturbation vector, but favor _not_ going up in player's reference. |
||
450 | auto perturb_vec = make_random_vector(); |
||
451 | vm_vec_scale_add2(perturb_vec, parent_objp->orient.uvec, -F1_0/2); |
||
452 | |||
453 | Doing_lighting_hack_flag = 1; // Ugly, but prevents blobs which are probably outside the mine from killing framerate. |
||
454 | |||
455 | const auto &Difficulty_level = GameUniqueState.Difficulty_level; |
||
456 | for (int i=0; i<num_omega_blobs; i++) { |
||
457 | // This will put the last blob right at the destination object, causing damage. |
||
458 | if (i == num_omega_blobs-1) |
||
459 | vm_vec_scale_add2(blob_pos, omega_delta_vector, 15*F1_0/32); // Move last blob another (almost) half section |
||
460 | |||
461 | // Every so often, re-perturb blobs |
||
462 | if ((i % 4) == 3) { |
||
463 | vm_vec_scale_add2(perturb_vec, make_random_vector(), F1_0/4); |
||
464 | } |
||
465 | |||
466 | const auto temp_pos = vm_vec_scale_add(blob_pos, perturb_vec, perturb_array[i]); |
||
467 | |||
468 | const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, temp_pos, last_segnum); |
||
469 | if (segnum != segment_none) { |
||
470 | last_segnum = segnum; |
||
471 | auto objp = obj_create(OBJ_WEAPON, weapon_id_type::OMEGA_ID, segnum, temp_pos, NULL, 0, CT_WEAPON, MT_PHYSICS, RT_WEAPON_VCLIP ); |
||
472 | if (objp == object_none) |
||
473 | break; |
||
474 | |||
475 | last_created_objnum = objp; |
||
476 | |||
477 | objp->lifeleft = OMEGA_BASE_TIME+(d_rand()/8); // add little randomness so the lighting effect becomes a little more interesting |
||
478 | objp->mtype.phys_info.velocity = vec_to_goal; |
||
479 | |||
480 | // Only make the last one move fast, else multiple blobs might collide with target. |
||
481 | vm_vec_scale(objp->mtype.phys_info.velocity, F1_0*4); |
||
482 | |||
483 | const auto &weapon_info = Weapon_info[get_weapon_id(objp)]; |
||
484 | objp->size = weapon_info.blob_size; |
||
485 | |||
486 | objp->shields = fixmul(OMEGA_DAMAGE_SCALE*OMEGA_BASE_TIME, weapon_info.strength[Difficulty_level]); |
||
487 | |||
488 | objp->ctype.laser_info.parent_type = parent_objp->type; |
||
489 | objp->ctype.laser_info.parent_signature = parent_objp->signature; |
||
490 | objp->ctype.laser_info.parent_num = parent_objp; |
||
491 | objp->movement_type = MT_NONE; // Only last one moves, that will get bashed below. |
||
492 | |||
493 | } |
||
494 | vm_vec_add2(blob_pos, omega_delta_vector); |
||
495 | } |
||
496 | |||
497 | // Make last one move faster, but it's already moving at speed = F1_0*4. |
||
498 | if (last_created_objnum != object_none) { |
||
499 | vm_vec_scale(last_created_objnum->mtype.phys_info.velocity, Weapon_info[weapon_id_type::OMEGA_ID].speed[Difficulty_level]/4); |
||
500 | last_created_objnum->movement_type = MT_PHYSICS; |
||
501 | } |
||
502 | |||
503 | Doing_lighting_hack_flag = 0; |
||
504 | } |
||
505 | |||
506 | #define MIN_OMEGA_CHARGE (MAX_OMEGA_CHARGE/8) |
||
507 | #define OMEGA_CHARGE_SCALE 4 // FrameTime / OMEGA_CHARGE_SCALE added to Omega_charge every frame. |
||
508 | |||
509 | #define OMEGA_CHARGE_SCALE 4 |
||
510 | |||
511 | fix get_omega_energy_consumption(const fix delta_charge) |
||
512 | { |
||
513 | const fix energy_used = fixmul(F1_0 * 190 / 17, delta_charge); |
||
514 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
515 | return Difficulty_level < 2 |
||
516 | ? fixmul(energy_used, i2f(Difficulty_level + 2) / 4) |
||
517 | : energy_used; |
||
518 | } |
||
519 | |||
520 | // --------------------------------------------------------------------------------- |
||
521 | // Call this every frame to recharge the Omega Cannon. |
||
522 | void omega_charge_frame(player_info &player_info) |
||
523 | { |
||
524 | if (!(player_info.primary_weapon_flags & HAS_PRIMARY_FLAG(primary_weapon_index_t::OMEGA_INDEX))) |
||
525 | return; |
||
526 | auto &Omega_charge = player_info.Omega_charge; |
||
527 | if (Omega_charge >= MAX_OMEGA_CHARGE) |
||
528 | return; |
||
529 | |||
530 | if (Player_dead_state != player_dead_state::no) |
||
531 | return; |
||
532 | |||
533 | // Don't charge while firing. Wait 1/3 second after firing before recharging |
||
534 | auto &Omega_recharge_delay = player_info.Omega_recharge_delay; |
||
535 | if (Omega_recharge_delay) |
||
536 | { |
||
537 | if (Omega_recharge_delay > FrameTime) |
||
538 | { |
||
539 | Omega_recharge_delay -= FrameTime; |
||
540 | return; |
||
541 | } |
||
542 | Omega_recharge_delay = 0; |
||
543 | } |
||
544 | |||
545 | if (auto &energy = player_info.energy) |
||
546 | { |
||
547 | const auto old_omega_charge = Omega_charge; |
||
548 | Omega_charge += FrameTime/OMEGA_CHARGE_SCALE; |
||
549 | if (Omega_charge > MAX_OMEGA_CHARGE) |
||
550 | Omega_charge = MAX_OMEGA_CHARGE; |
||
551 | |||
552 | const auto energy_used = get_omega_energy_consumption(Omega_charge - old_omega_charge); |
||
553 | energy -= energy_used; |
||
554 | if (energy < 0) |
||
555 | energy = 0; |
||
556 | } |
||
557 | |||
558 | |||
559 | } |
||
560 | |||
561 | // -- fix Last_omega_muzzle_flash_time; |
||
562 | |||
563 | // --------------------------------------------------------------------------------- |
||
564 | // *objp is the object firing the omega cannon |
||
565 | // *pos is the location from which the omega bolt starts |
||
566 | static void do_omega_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t parent_objp, const vms_vector &firing_pos, const vmobjptridx_t weapon_objp) |
||
567 | { |
||
568 | vms_vector goal_pos; |
||
569 | if (parent_objp->type == OBJ_PLAYER && get_player_id(parent_objp) == Player_num) |
||
570 | { |
||
571 | // If charge >= min, or (some charge and zero energy), allow to fire. |
||
572 | auto &player_info = parent_objp->ctype.player_info; |
||
573 | auto &Omega_charge = player_info.Omega_charge; |
||
574 | if (!((Omega_charge >= MIN_OMEGA_CHARGE) || (Omega_charge && !player_info.energy))) { |
||
575 | obj_delete(LevelUniqueObjectState, Segments, weapon_objp); |
||
576 | return; |
||
577 | } |
||
578 | |||
579 | Omega_charge -= OMEGA_BASE_TIME; |
||
580 | if (Omega_charge < 0) |
||
581 | Omega_charge = 0; |
||
582 | |||
583 | player_info.Omega_recharge_delay = F1_0 / 3; |
||
584 | } |
||
585 | |||
586 | weapon_objp->ctype.laser_info.parent_type = parent_objp->type; |
||
587 | weapon_objp->ctype.laser_info.parent_num = parent_objp.get_unchecked_index(); |
||
588 | weapon_objp->ctype.laser_info.parent_signature = parent_objp->signature; |
||
589 | |||
590 | const auto &&lock_objnum = find_homing_object(firing_pos, weapon_objp); |
||
591 | |||
592 | const auto &&firing_segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, firing_pos, Segments.vmptridx(parent_objp->segnum)); |
||
593 | |||
594 | // Play sound. |
||
595 | { |
||
596 | const auto flash_sound = Weapon_info[get_weapon_id(weapon_objp)].flash_sound; |
||
597 | if ( parent_objp == Viewer ) |
||
598 | digi_play_sample(flash_sound, F1_0); |
||
599 | else |
||
600 | digi_link_sound_to_pos(flash_sound, vmsegptridx(weapon_objp->segnum), 0, weapon_objp->pos, 0, F1_0); |
||
601 | } |
||
602 | |||
603 | // -- if ((Last_omega_muzzle_flash_time + F1_0/4 < GameTime) || (Last_omega_muzzle_flash_time > GameTime)) { |
||
604 | // -- do_muzzle_stuff(firing_segnum, firing_pos); |
||
605 | // -- Last_omega_muzzle_flash_time = GameTime; |
||
606 | // -- } |
||
607 | |||
608 | // Delete the original object. Its only purpose in life was to determine which object to home in on. |
||
609 | obj_delete(LevelUniqueObjectState, Segments, weapon_objp); |
||
610 | |||
611 | // If couldn't lock on anything, fire straight ahead. |
||
612 | if (lock_objnum == object_none) { |
||
613 | fvi_query fq; |
||
614 | fvi_info hit_data; |
||
615 | int fate; |
||
616 | const auto &&perturbed_fvec = vm_vec_scale_add(parent_objp->orient.fvec, make_random_vector(), F1_0/16); |
||
617 | vm_vec_scale_add(goal_pos, firing_pos, perturbed_fvec, MAX_OMEGA_DIST); |
||
618 | fq.startseg = firing_segnum; |
||
619 | if (fq.startseg == segment_none) { |
||
620 | return; |
||
621 | } |
||
622 | fq.p0 = &firing_pos; |
||
623 | fq.p1 = &goal_pos; |
||
624 | fq.rad = 0; |
||
625 | fq.thisobjnum = parent_objp; |
||
626 | fq.ignore_obj_list.first = nullptr; |
||
627 | fq.flags = FQ_IGNORE_POWERUPS | FQ_TRANSPOINT | FQ_CHECK_OBJS; //what about trans walls??? |
||
628 | |||
629 | fate = find_vector_intersection(fq, hit_data); |
||
630 | if (fate != HIT_NONE) { |
||
631 | Assert(hit_data.hit_seg != segment_none); // How can this be? We went from inside the mine to outside without hitting anything? |
||
632 | goal_pos = hit_data.hit_pnt; |
||
633 | } |
||
634 | } else |
||
635 | goal_pos = lock_objnum->pos; |
||
636 | |||
637 | // This is where we create a pile of omega blobs! |
||
638 | create_omega_blobs(firing_segnum, firing_pos, goal_pos, parent_objp); |
||
639 | |||
640 | } |
||
641 | |||
642 | static int is_laser_weapon_type(const weapon_id_type weapon_type) |
||
643 | { |
||
644 | return weapon_type == weapon_id_type::LASER_ID_L1 || |
||
645 | weapon_type == weapon_id_type::LASER_ID_L2 || |
||
646 | weapon_type == weapon_id_type::LASER_ID_L3 || |
||
647 | weapon_type == weapon_id_type::LASER_ID_L4 || |
||
648 | weapon_type == weapon_id_type::LASER_ID_L5 || |
||
649 | weapon_type == weapon_id_type::LASER_ID_L6; |
||
650 | } |
||
651 | #endif |
||
652 | |||
653 | // --------------------------------------------------------------------------------- |
||
654 | // Initializes a laser after Fire is pressed |
||
655 | // Returns object number. |
||
656 | imobjptridx_t Laser_create_new(const vms_vector &direction, const vms_vector &position, const vmsegptridx_t segnum, const vmobjptridx_t parent, weapon_id_type weapon_type, int make_sound ) |
||
657 | { |
||
658 | auto &Objects = LevelUniqueObjectState.Objects; |
||
659 | auto &vmobjptr = Objects.vmptr; |
||
660 | fix parent_speed, weapon_speed; |
||
661 | fix volume; |
||
662 | fix laser_length=0; |
||
663 | |||
664 | if (weapon_type >= N_weapon_types) |
||
665 | { |
||
666 | con_printf(CON_URGENT, DXX_STRINGIZE_FL(__FILE__, __LINE__, "invalid weapon id %u fired by parent %hu (type %u) in segment %hu"), weapon_type, parent.get_unchecked_index(), parent->type, segnum.get_unchecked_index()); |
||
667 | weapon_type = weapon_id_type::LASER_ID_L1; |
||
668 | } |
||
669 | |||
670 | // Don't let homing blobs make muzzle flash. |
||
671 | if (parent->type == OBJ_ROBOT) |
||
672 | do_muzzle_stuff(segnum, position); |
||
673 | |||
674 | const imobjptridx_t obj = create_weapon_object(weapon_type,segnum,position); |
||
675 | |||
676 | if (obj == object_none) |
||
677 | { |
||
678 | return object_none; |
||
679 | } |
||
680 | const auto &weapon_info = Weapon_info[weapon_type]; |
||
681 | |||
682 | #if defined(DXX_BUILD_DESCENT_II) |
||
683 | // Do the special Omega Cannon stuff. Then return on account of everything that follows does |
||
684 | // not apply to the Omega Cannon. |
||
685 | if (weapon_type == weapon_id_type::OMEGA_ID) { |
||
686 | // Create orientation matrix for tracking purposes. |
||
687 | vm_vector_2_matrix( obj->orient, direction, &parent->orient.uvec ,nullptr); |
||
688 | |||
689 | if (parent != Viewer && parent->type != OBJ_WEAPON) { |
||
690 | // Muzzle flash |
||
691 | if (weapon_info.flash_vclip > -1 ) |
||
692 | object_create_muzzle_flash(vmsegptridx(obj->segnum), obj->pos, weapon_info.flash_size, weapon_info.flash_vclip); |
||
693 | } |
||
694 | |||
695 | do_omega_stuff(vmsegptridx, parent, position, obj); |
||
696 | |||
697 | return obj; |
||
698 | } |
||
699 | #endif |
||
700 | |||
701 | if (parent->type == OBJ_PLAYER) { |
||
702 | if (weapon_type == weapon_id_type::FUSION_ID) { |
||
703 | int fusion_scale; |
||
704 | #if defined(DXX_BUILD_DESCENT_I) |
||
705 | if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)) |
||
706 | fusion_scale = 2; |
||
707 | else |
||
708 | #endif |
||
709 | fusion_scale = 4; |
||
710 | |||
711 | auto &player_info = parent->ctype.player_info; |
||
712 | const auto Fusion_charge = player_info.Fusion_charge; |
||
713 | if (Fusion_charge <= 0) |
||
714 | obj->ctype.laser_info.multiplier = F1_0; |
||
715 | else if (Fusion_charge <= F1_0*fusion_scale) |
||
716 | obj->ctype.laser_info.multiplier = F1_0 + Fusion_charge/2; |
||
717 | else |
||
718 | obj->ctype.laser_info.multiplier = F1_0*fusion_scale; |
||
719 | |||
720 | #if defined(DXX_BUILD_DESCENT_I) |
||
721 | // Fusion damage was boosted by mk on 3/27 (for reg 1.1 release), but we only want it to apply to single player games. |
||
722 | if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)) |
||
723 | obj->ctype.laser_info.multiplier /= 2; |
||
724 | #endif |
||
725 | } |
||
726 | #if defined(DXX_BUILD_DESCENT_II) |
||
727 | else if (!EMULATING_D1 && is_laser_weapon_type(weapon_type) && (parent->ctype.player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)) |
||
728 | obj->ctype.laser_info.multiplier = F1_0*3/4; |
||
729 | else if (weapon_type == weapon_id_type::GUIDEDMISS_ID) { |
||
730 | if (parent==get_local_player().objnum) { |
||
731 | LevelUniqueObjectState.Guided_missile.set_player_active_guided_missile(obj, Player_num); |
||
732 | if (Newdemo_state==ND_STATE_RECORDING) |
||
733 | newdemo_record_guided_start(); |
||
734 | } |
||
735 | } |
||
736 | #endif |
||
737 | } |
||
738 | |||
739 | // Make children of smart bomb bounce so if they hit a wall right away, they |
||
740 | // won't detonate. The frame interval code will clear this bit after 1/2 second. |
||
741 | #if defined(DXX_BUILD_DESCENT_I) |
||
742 | if ((weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID)) |
||
743 | #elif defined(DXX_BUILD_DESCENT_II) |
||
744 | if ((weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID) || (weapon_type == weapon_id_type::SMART_MINE_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_MINE_HOMING_ID) || (weapon_type == weapon_id_type::EARTHSHAKER_MEGA_ID)) |
||
745 | #endif |
||
746 | obj->mtype.phys_info.flags |= PF_BOUNCE; |
||
747 | |||
748 | if (weapon_info.render_type == WEAPON_RENDER_POLYMODEL) |
||
749 | { |
||
750 | auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; |
||
751 | laser_length = Polygon_models[obj->rtype.pobj_info.model_num].rad * 2; |
||
752 | } |
||
753 | |||
754 | if (weapon_type == weapon_id_type::FLARE_ID) |
||
755 | obj->mtype.phys_info.flags |= PF_STICK; //this obj sticks to walls |
||
756 | |||
757 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
758 | obj->shields = weapon_info.strength[Difficulty_level]; |
||
759 | |||
760 | // Fill in laser-specific data |
||
761 | |||
762 | obj->lifeleft = weapon_info.lifetime; |
||
763 | obj->ctype.laser_info.parent_type = parent->type; |
||
764 | obj->ctype.laser_info.parent_signature = parent->signature; |
||
765 | obj->ctype.laser_info.parent_num = parent; |
||
766 | |||
767 | // Assign parent type to highest level creator. This propagates parent type down from |
||
768 | // the original creator through weapons which create children of their own (ie, smart missile) |
||
769 | if (parent->type == OBJ_WEAPON) { |
||
770 | auto highest_parent = parent; |
||
771 | int count; |
||
772 | |||
773 | count = 0; |
||
774 | while ((count++ < 10) && (highest_parent->type == OBJ_WEAPON)) { |
||
775 | const auto next_parent = highest_parent->ctype.laser_info.parent_num; |
||
776 | const auto &&parent_objp = parent.absolute_sibling(next_parent); |
||
777 | if (!laser_parent_is_object(highest_parent->ctype.laser_info, parent_objp)) |
||
778 | break; // Probably means parent was killed. Just continue. |
||
779 | |||
780 | if (next_parent == highest_parent) { |
||
781 | Int3(); // Hmm, object is parent of itself. This would seem to be bad, no? |
||
782 | break; |
||
783 | } |
||
784 | |||
785 | highest_parent = parent_objp; |
||
786 | |||
787 | obj->ctype.laser_info.parent_num = highest_parent; |
||
788 | obj->ctype.laser_info.parent_type = highest_parent->type; |
||
789 | obj->ctype.laser_info.parent_signature = highest_parent->signature; |
||
790 | } |
||
791 | } |
||
792 | |||
793 | // Create orientation matrix so we can look from this pov |
||
794 | // Homing missiles also need an orientation matrix so they know if they can make a turn. |
||
795 | if ((weapon_info.homing_flag && (obj->ctype.laser_info.track_goal = object_none, true)) || obj->render_type == RT_POLYOBJ) |
||
796 | vm_vector_2_matrix(obj->orient, direction, &parent->orient.uvec, nullptr); |
||
797 | |||
798 | if (( parent != Viewer ) && (parent->type != OBJ_WEAPON)) { |
||
799 | // Muzzle flash |
||
800 | if (weapon_info.flash_vclip > -1 ) |
||
801 | object_create_muzzle_flash(segnum.absolute_sibling(obj->segnum), obj->pos, weapon_info.flash_size, weapon_info.flash_vclip); |
||
802 | } |
||
803 | |||
804 | volume = F1_0; |
||
805 | if (weapon_info.flash_sound > -1) |
||
806 | { |
||
807 | if (make_sound) { |
||
808 | if (parent == Viewer) |
||
809 | { |
||
810 | if (weapon_type == weapon_id_type::VULCAN_ID) // Make your own vulcan gun 1/2 as loud. |
||
811 | volume = F1_0 / 2; |
||
812 | digi_play_sample(weapon_info.flash_sound, volume); |
||
813 | } else { |
||
814 | digi_link_sound_to_pos(weapon_info.flash_sound, segnum.absolute_sibling(obj->segnum), 0, obj->pos, 0, volume); |
||
815 | } |
||
816 | } |
||
817 | } |
||
818 | |||
819 | // Fire the laser from the gun tip so that the back end of the laser bolt is at the gun tip. |
||
820 | // Move 1 frame, so that the end-tip of the laser is touching the gun barrel. |
||
821 | // This also jitters the laser a bit so that it doesn't alias. |
||
822 | // Don't do for weapons created by weapons. |
||
823 | #if defined(DXX_BUILD_DESCENT_I) |
||
824 | if (parent->type != OBJ_WEAPON && weapon_info.render_type != WEAPON_RENDER_NONE && weapon_type != weapon_id_type::FLARE_ID) |
||
825 | #elif defined(DXX_BUILD_DESCENT_II) |
||
826 | if (parent->type == OBJ_PLAYER && weapon_info.render_type != WEAPON_RENDER_NONE && weapon_type != weapon_id_type::FLARE_ID) |
||
827 | #endif |
||
828 | { |
||
829 | const auto end_pos = vm_vec_scale_add(obj->pos, direction, (laser_length/2) ); |
||
830 | const auto &&end_segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, end_pos, Segments.vmptridx(obj->segnum)); |
||
831 | if (end_segnum != obj->segnum) { |
||
832 | if (end_segnum != segment_none) { |
||
833 | obj->pos = end_pos; |
||
834 | obj_relink(vmobjptr, vmsegptr, obj, end_segnum); |
||
835 | } |
||
836 | } else |
||
837 | obj->pos = end_pos; |
||
838 | } |
||
839 | |||
840 | // Here's where to fix the problem with objects which are moving backwards imparting higher velocity to their weaponfire. |
||
841 | // Find out if moving backwards. |
||
842 | if (is_proximity_bomb_or_player_smart_mine(weapon_type)) { |
||
843 | parent_speed = vm_vec_mag_quick(parent->mtype.phys_info.velocity); |
||
844 | if (vm_vec_dot(parent->mtype.phys_info.velocity, parent->orient.fvec) < 0) |
||
845 | parent_speed = -parent_speed; |
||
846 | } else |
||
847 | parent_speed = 0; |
||
848 | |||
849 | weapon_speed = weapon_info.speed[Difficulty_level]; |
||
850 | #if defined(DXX_BUILD_DESCENT_II) |
||
851 | if (weapon_info.speedvar != 128) |
||
852 | { |
||
853 | fix randval; |
||
854 | |||
855 | // Get a scale factor between speedvar% and 1.0. |
||
856 | randval = F1_0 - ((d_rand() * weapon_info.speedvar) >> 6); |
||
857 | weapon_speed = fixmul(weapon_speed, randval); |
||
858 | } |
||
859 | #endif |
||
860 | |||
861 | // Ugly hack (too bad we're on a deadline), for homing missiles dropped by smart bomb, start them out slower. |
||
862 | #if defined(DXX_BUILD_DESCENT_I) |
||
863 | if (weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID) |
||
864 | #elif defined(DXX_BUILD_DESCENT_II) |
||
865 | if (weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID || weapon_type == weapon_id_type::SMART_MINE_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_MINE_HOMING_ID || weapon_type == weapon_id_type::EARTHSHAKER_MEGA_ID) |
||
866 | #endif |
||
867 | weapon_speed /= 4; |
||
868 | |||
869 | if (weapon_info.thrust) |
||
870 | weapon_speed /= 2; |
||
871 | |||
872 | vm_vec_copy_scale(obj->mtype.phys_info.velocity, direction, weapon_speed + parent_speed ); |
||
873 | |||
874 | // Set thrust |
||
875 | if (weapon_info.thrust) |
||
876 | { |
||
877 | obj->mtype.phys_info.thrust = obj->mtype.phys_info.velocity; |
||
878 | vm_vec_scale(obj->mtype.phys_info.thrust, fixdiv(weapon_info.thrust, weapon_speed+parent_speed)); |
||
879 | } |
||
880 | |||
881 | if (obj->type == OBJ_WEAPON && weapon_type == weapon_id_type::FLARE_ID) |
||
882 | obj->lifeleft += (d_rand()-16384) << 2; // add in -2..2 seconds |
||
883 | |||
884 | return obj; |
||
885 | } |
||
886 | |||
887 | // ----------------------------------------------------------------------------------------------------------- |
||
888 | // Calls Laser_create_new, but takes care of the segment and point computation for you. |
||
889 | imobjptridx_t Laser_create_new_easy(const vms_vector &direction, const vms_vector &position, const vmobjptridx_t parent, weapon_id_type weapon_type, int make_sound ) |
||
890 | { |
||
891 | fvi_query fq; |
||
892 | fvi_info hit_data; |
||
893 | int fate; |
||
894 | |||
895 | // Find segment containing laser fire position. If the robot is straddling a segment, the position from |
||
896 | // which it fires may be in a different segment, which is bad news for find_vector_intersection. So, cast |
||
897 | // a ray from the object center (whose segment we know) to the laser position. Then, in the call to Laser_create_new |
||
898 | // use the data returned from this call to find_vector_intersection. |
||
899 | // Note that while find_vector_intersection is pretty slow, it is not terribly slow if the destination point is |
||
900 | // in the same segment as the source point. |
||
901 | |||
902 | fq.p0 = &parent->pos; |
||
903 | fq.startseg = parent->segnum; |
||
904 | fq.p1 = &position; |
||
905 | fq.rad = 0; |
||
906 | fq.thisobjnum = parent; |
||
907 | fq.ignore_obj_list.first = nullptr; |
||
908 | fq.flags = FQ_TRANSWALL | FQ_CHECK_OBJS; //what about trans walls??? |
||
909 | |||
910 | fate = find_vector_intersection(fq, hit_data); |
||
911 | if (fate != HIT_NONE || hit_data.hit_seg==segment_none) { |
||
912 | return object_none; |
||
913 | } |
||
914 | |||
915 | return Laser_create_new(direction, hit_data.hit_pnt, vmsegptridx(hit_data.hit_seg), parent, weapon_type, make_sound); |
||
916 | } |
||
917 | |||
918 | } |
||
919 | |||
920 | namespace dcx { |
||
921 | |||
922 | std::array<muzzle_info, MUZZLE_QUEUE_MAX> Muzzle_data; |
||
923 | |||
924 | static fix get_weapon_energy_usage_with_difficulty(const weapon_info &wi, const Difficulty_level_type Difficulty_level) |
||
925 | { |
||
926 | const auto energy_usage = wi.energy_usage; |
||
927 | if (Difficulty_level < 2) |
||
928 | return fixmul(energy_usage, i2f(Difficulty_level + 2) / 4); |
||
929 | return energy_usage; |
||
930 | } |
||
931 | |||
932 | } |
||
933 | |||
934 | namespace d1x { |
||
935 | |||
936 | static fix get_scaled_min_trackable_dot() |
||
937 | { |
||
938 | const fix curFT = HOMING_TRACKABLE_DOT_FRAME_TIME; |
||
939 | if (curFT <= F1_0 / 16) |
||
940 | return (3 * (F1_0 - HOMING_MIN_TRACKABLE_DOT) / 4 + HOMING_MIN_TRACKABLE_DOT); |
||
941 | else if (curFT < F1_0 / 4) |
||
942 | return (fixmul(F1_0 - HOMING_MIN_TRACKABLE_DOT, F1_0 - 4 * curFT) + HOMING_MIN_TRACKABLE_DOT); |
||
943 | else |
||
944 | return (HOMING_MIN_TRACKABLE_DOT); |
||
945 | } |
||
946 | |||
947 | } |
||
948 | |||
949 | namespace dsx { |
||
950 | |||
951 | // ----------------------------------------------------------------------------------------------------------- |
||
952 | // Determine if two objects are on a line of sight. If so, return true, else return false. |
||
953 | // Calls fvi. |
||
954 | int object_to_object_visibility(const vcobjptridx_t obj1, const object_base &obj2, int trans_type) |
||
955 | { |
||
956 | fvi_query fq; |
||
957 | fvi_info hit_data; |
||
958 | |||
959 | fq.p0 = &obj1->pos; |
||
960 | fq.startseg = obj1->segnum; |
||
961 | fq.p1 = &obj2.pos; |
||
962 | fq.rad = 0x10; |
||
963 | fq.thisobjnum = obj1; |
||
964 | fq.ignore_obj_list.first = nullptr; |
||
965 | fq.flags = trans_type; |
||
966 | |||
967 | switch(const auto fate = find_vector_intersection(fq, hit_data)) |
||
968 | { |
||
969 | case HIT_NONE: |
||
970 | return 1; |
||
971 | case HIT_WALL: |
||
972 | return 0; |
||
973 | default: |
||
974 | con_printf(CON_VERBOSE, "object_to_object_visibility: fate=%u for object %hu{%hu/%i,%i,%i} to {%i,%i,%i}", fate, static_cast<vcobjptridx_t::integral_type>(obj1), obj1->segnum, obj1->pos.x, obj1->pos.y, obj1->pos.z, obj2.pos.x, obj2.pos.y, obj2.pos.z); |
||
975 | // Int3(); // Contact Mike: Oops, what happened? What is fate? |
||
976 | // 2 = hit object (impossible), 3 = bad starting point (bad) |
||
977 | break; |
||
978 | } |
||
979 | return 0; |
||
980 | } |
||
981 | |||
982 | #if defined(DXX_BUILD_DESCENT_II) |
||
983 | static fix get_scaled_min_trackable_dot() |
||
984 | { |
||
985 | if (EMULATING_D1) |
||
986 | return ::d1x::get_scaled_min_trackable_dot(); |
||
987 | const fix curFT = HOMING_TRACKABLE_DOT_FRAME_TIME; |
||
988 | if (curFT <= F1_0/64) |
||
989 | return (HOMING_MIN_TRACKABLE_DOT); |
||
990 | else if (curFT < F1_0/32) |
||
991 | return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - 2*curFT); |
||
992 | else if (curFT < F1_0/4) |
||
993 | return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - F1_0/16 - curFT); |
||
994 | else |
||
995 | return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - F1_0/8); |
||
996 | } |
||
997 | #endif |
||
998 | |||
999 | // ----------------------------------------------------------------------------------------------------------- |
||
1000 | // Return true if weapon *tracker is able to track object Objects[track_goal], else return false. |
||
1001 | // In order for the object to be trackable, it must be within a reasonable turning radius for the missile |
||
1002 | // and it must not be obstructed by a wall. |
||
1003 | static int object_is_trackable(const imobjptridx_t objp, const vmobjptridx_t tracker, fix *dot) |
||
1004 | { |
||
1005 | if (objp == object_none) |
||
1006 | return 0; |
||
1007 | if (Game_mode & GM_MULTI_COOP) |
||
1008 | return 0; |
||
1009 | // Don't track player if he's cloaked. |
||
1010 | if ((objp == get_local_player().objnum) && (objp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)) |
||
1011 | return 0; |
||
1012 | #if defined(DXX_BUILD_DESCENT_II) |
||
1013 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
1014 | #endif |
||
1015 | |||
1016 | // Can't track AI object if he's cloaked. |
||
1017 | if (objp->type == OBJ_ROBOT) { |
||
1018 | if (objp->ctype.ai_info.CLOAKED) |
||
1019 | return 0; |
||
1020 | #if defined(DXX_BUILD_DESCENT_II) |
||
1021 | // Your missiles don't track your escort. |
||
1022 | if (Robot_info[get_robot_id(objp)].companion) |
||
1023 | if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER) |
||
1024 | return 0; |
||
1025 | #endif |
||
1026 | } |
||
1027 | auto vector_to_goal = vm_vec_normalized_quick(vm_vec_sub(objp->pos, tracker->pos)); |
||
1028 | *dot = vm_vec_dot(vector_to_goal, tracker->orient.fvec); |
||
1029 | |||
1030 | #if defined(DXX_BUILD_DESCENT_II) |
||
1031 | if ((*dot < get_scaled_min_trackable_dot()) && (*dot > F1_0*9/10)) { |
||
1032 | vm_vec_normalize(vector_to_goal); |
||
1033 | *dot = vm_vec_dot(vector_to_goal, tracker->orient.fvec); |
||
1034 | } |
||
1035 | #endif |
||
1036 | |||
1037 | if (*dot >= get_scaled_min_trackable_dot()) { |
||
1038 | int rval; |
||
1039 | // dot is in legal range, now see if object is visible |
||
1040 | rval = object_to_object_visibility(tracker, objp, FQ_TRANSWALL); |
||
1041 | return rval; |
||
1042 | } else { |
||
1043 | return 0; |
||
1044 | } |
||
1045 | } |
||
1046 | |||
1047 | // -------------------------------------------------------------------------------------------- |
||
1048 | static imobjptridx_t call_find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker) |
||
1049 | { |
||
1050 | if (Game_mode & GM_MULTI) { |
||
1051 | if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER) { |
||
1052 | // It's fired by a player, so if robots present, track robot, else track player. |
||
1053 | if (Game_mode & GM_MULTI_COOP) |
||
1054 | return find_homing_object_complete( curpos, tracker, OBJ_ROBOT, -1); |
||
1055 | else |
||
1056 | return find_homing_object_complete( curpos, tracker, OBJ_PLAYER, OBJ_ROBOT); |
||
1057 | } else { |
||
1058 | int goal2_type = -1; |
||
1059 | #if defined(DXX_BUILD_DESCENT_II) |
||
1060 | if (cheats.robotskillrobots) |
||
1061 | goal2_type = OBJ_ROBOT; |
||
1062 | #endif |
||
1063 | Assert(tracker->ctype.laser_info.parent_type == OBJ_ROBOT); |
||
1064 | return find_homing_object_complete(curpos, tracker, OBJ_PLAYER, goal2_type); |
||
1065 | } |
||
1066 | } else |
||
1067 | return find_homing_object_complete( curpos, tracker, OBJ_ROBOT, -1); |
||
1068 | } |
||
1069 | |||
1070 | // -------------------------------------------------------------------------------------------- |
||
1071 | // Find object to home in on. |
||
1072 | // Scan list of objects rendered last frame, find one that satisfies function of nearness to center and distance. |
||
1073 | static imobjptridx_t find_homing_object(const vms_vector &curpos, const vmobjptridx_t tracker) |
||
1074 | { |
||
1075 | // Contact Mike: This is a bad and stupid thing. Who called this routine with an illegal laser type?? |
||
1076 | #ifndef NDEBUG |
||
1077 | const auto tracker_id = get_weapon_id(tracker); |
||
1078 | #if defined(DXX_BUILD_DESCENT_II) |
||
1079 | if (tracker_id != weapon_id_type::OMEGA_ID) |
||
1080 | #endif |
||
1081 | assert(Weapon_info[tracker_id].homing_flag); |
||
1082 | #endif |
||
1083 | |||
1084 | // Find an object to track based on game mode (eg, whether in network play) and who fired it. |
||
1085 | |||
1086 | return call_find_homing_object_complete(curpos, tracker); |
||
1087 | } |
||
1088 | |||
1089 | // -------------------------------------------------------------------------------------------- |
||
1090 | // Find object to home in on. |
||
1091 | // Scan list of objects rendered last frame, find one that satisfies function of nearness to center and distance. |
||
1092 | // Can track two kinds of objects. If you are only interested in one type, set track_obj_type2 to NULL |
||
1093 | // Always track proximity bombs. --MK, 06/14/95 |
||
1094 | // Make homing objects not track parent's prox bombs. |
||
1095 | imobjptridx_t find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker, int track_obj_type1, int track_obj_type2) |
||
1096 | { |
||
1097 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1098 | auto &vcobjptr = Objects.vcptr; |
||
1099 | auto &vmobjptridx = Objects.vmptridx; |
||
1100 | fix max_dot = -F1_0*2; |
||
1101 | |||
1102 | const auto tracker_id = get_weapon_id(tracker); |
||
1103 | #if defined(DXX_BUILD_DESCENT_II) |
||
1104 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
1105 | if (tracker_id != weapon_id_type::OMEGA_ID) |
||
1106 | // Contact Mike: This is a bad and stupid thing. Who called this routine with an illegal laser type?? |
||
1107 | #endif |
||
1108 | { |
||
1109 | if (!Weapon_info[tracker_id].homing_flag) |
||
1110 | throw std::logic_error("tracking without homing_flag"); |
||
1111 | } |
||
1112 | |||
1113 | const fix64 HOMING_MAX_TRACKABLE_DIST = F1_0*250; |
||
1114 | vm_distance_squared max_trackable_dist{HOMING_MAX_TRACKABLE_DIST * HOMING_MAX_TRACKABLE_DIST}; |
||
1115 | fix min_trackable_dot = HOMING_MIN_TRACKABLE_DOT; |
||
1116 | |||
1117 | #if defined(DXX_BUILD_DESCENT_II) |
||
1118 | if (tracker_id == weapon_id_type::OMEGA_ID) |
||
1119 | { |
||
1120 | max_trackable_dist = OMEGA_MAX_TRACKABLE_DIST * OMEGA_MAX_TRACKABLE_DIST; |
||
1121 | min_trackable_dot = OMEGA_MIN_TRACKABLE_DOT; |
||
1122 | } |
||
1123 | #endif |
||
1124 | |||
1125 | imobjptridx_t best_objnum = object_none; |
||
1126 | range_for (const auto &&curobjp, vmobjptridx) |
||
1127 | { |
||
1128 | int is_proximity = 0; |
||
1129 | fix dot; |
||
1130 | |||
1131 | if ((curobjp->type != track_obj_type1) && (curobjp->type != track_obj_type2)) |
||
1132 | { |
||
1133 | #if defined(DXX_BUILD_DESCENT_II) |
||
1134 | if ((curobjp->type == OBJ_WEAPON) && (is_proximity_bomb_or_player_smart_mine(get_weapon_id(curobjp)))) { |
||
1135 | auto &cur_laser_info = curobjp->ctype.laser_info; |
||
1136 | auto &tracker_laser_info = tracker->ctype.laser_info; |
||
1137 | if (cur_laser_info.parent_num != tracker_laser_info.parent_num || cur_laser_info.parent_signature != tracker_laser_info.parent_signature) |
||
1138 | is_proximity = 1; |
||
1139 | else |
||
1140 | continue; |
||
1141 | } else |
||
1142 | #endif |
||
1143 | continue; |
||
1144 | } |
||
1145 | |||
1146 | if (curobjp == tracker->ctype.laser_info.parent_num) // Don't track shooter |
||
1147 | continue; |
||
1148 | |||
1149 | // Don't track cloaked players. |
||
1150 | if (curobjp->type == OBJ_PLAYER) |
||
1151 | { |
||
1152 | if (curobjp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) |
||
1153 | continue; |
||
1154 | // Don't track teammates in team games |
||
1155 | if (Game_mode & GM_TEAM) |
||
1156 | { |
||
1157 | const auto &&objparent = vcobjptr(tracker->ctype.laser_info.parent_num); |
||
1158 | if (objparent->type == OBJ_PLAYER && get_team(get_player_id(curobjp)) == get_team(get_player_id(objparent))) |
||
1159 | continue; |
||
1160 | } |
||
1161 | } |
||
1162 | |||
1163 | // Can't track AI object if he's cloaked. |
||
1164 | if (curobjp->type == OBJ_ROBOT) { |
||
1165 | if (curobjp->ctype.ai_info.CLOAKED) |
||
1166 | continue; |
||
1167 | |||
1168 | #if defined(DXX_BUILD_DESCENT_II) |
||
1169 | // Your missiles don't track your escort. |
||
1170 | if (Robot_info[get_robot_id(curobjp)].companion) |
||
1171 | if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER) |
||
1172 | continue; |
||
1173 | #endif |
||
1174 | } |
||
1175 | |||
1176 | auto vec_to_curobj = vm_vec_sub(curobjp->pos, curpos); |
||
1177 | auto dist = vm_vec_mag2(vec_to_curobj); |
||
1178 | |||
1179 | if (dist < max_trackable_dist) { |
||
1180 | vm_vec_normalize(vec_to_curobj); |
||
1181 | dot = vm_vec_dot(vec_to_curobj, tracker->orient.fvec); |
||
1182 | if (is_proximity) |
||
1183 | dot = ((dot << 3) + dot) >> 3; // I suspect Watcom would be too stupid to figure out the obvious... |
||
1184 | |||
1185 | if (dot > min_trackable_dot) { |
||
1186 | if (dot > max_dot) { |
||
1187 | if (object_to_object_visibility(tracker, curobjp, FQ_TRANSWALL)) { |
||
1188 | max_dot = dot; |
||
1189 | best_objnum = curobjp; |
||
1190 | } |
||
1191 | } |
||
1192 | } |
||
1193 | } |
||
1194 | |||
1195 | } |
||
1196 | return best_objnum; |
||
1197 | } |
||
1198 | |||
1199 | #ifdef NEWHOMER |
||
1200 | // Similar to calc_d_tick but made just for the homers. |
||
1201 | // Causes d_homer_tick_step to be true in intervals dictated by HOMING_TURN_TIME |
||
1202 | // and increments d_homer_tick_count accordingly |
||
1203 | void calc_d_homer_tick() |
||
1204 | { |
||
1205 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1206 | auto &vmobjptr = Objects.vmptr; |
||
1207 | static fix timer = 0; |
||
1208 | auto t = timer + FrameTime; |
||
1209 | d_homer_tick_step = t >= HOMING_TURN_TIME; |
||
1210 | if (d_homer_tick_step) |
||
1211 | { |
||
1212 | d_homer_tick_count++; |
||
1213 | if (d_homer_tick_count > F1_0) |
||
1214 | d_homer_tick_count = 0; |
||
1215 | t -= HOMING_TURN_TIME; |
||
1216 | // Don't let slowdowns have a lasting impact; allow you to build up at most 3 frames worth |
||
1217 | if (t > HOMING_TURN_TIME*3) |
||
1218 | t = HOMING_TURN_TIME*3; |
||
1219 | |||
1220 | get_local_plrobj().ctype.player_info.homing_object_dist = -1; // Assume not being tracked. Laser_do_weapon_sequence modifies this. Let's do this here since the homers do not track every frame, we may not want to reset this ever frame. |
||
1221 | } |
||
1222 | timer = t; |
||
1223 | } |
||
1224 | #endif |
||
1225 | |||
1226 | // ------------------------------------------------------------------------------------------------------------ |
||
1227 | // See if legal to keep tracking currently tracked object. If not, see if another object is trackable. If not, return -1, |
||
1228 | // else return object number of tracking object. |
||
1229 | // Computes and returns a fairly precise dot product. |
||
1230 | static imobjptridx_t track_track_goal(fvcobjptr &vcobjptr, const imobjptridx_t track_goal, const vmobjptridx_t tracker, fix *dot, fix tick_count) |
||
1231 | { |
||
1232 | #if defined(DXX_BUILD_DESCENT_I) |
||
1233 | if (object_is_trackable(track_goal, tracker, dot)) |
||
1234 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1235 | // Every 8 frames for each object, scan all objects. |
||
1236 | if (object_is_trackable(track_goal, tracker, dot) && (((tracker ^ tick_count) % 8) != 0)) |
||
1237 | #endif |
||
1238 | { |
||
1239 | return track_goal; |
||
1240 | } else if (((tracker ^ tick_count) % 4) == 0) |
||
1241 | { |
||
1242 | int goal_type, goal2_type; |
||
1243 | // If player fired missile, then search for an object, if not, then give up. |
||
1244 | if (vcobjptr(tracker->ctype.laser_info.parent_num)->type == OBJ_PLAYER) |
||
1245 | { |
||
1246 | |||
1247 | if (track_goal == object_none) |
||
1248 | { |
||
1249 | if (Game_mode & GM_MULTI) |
||
1250 | { |
||
1251 | if (Game_mode & GM_MULTI_COOP) |
||
1252 | goal_type = OBJ_ROBOT, goal2_type = -1; |
||
1253 | else |
||
1254 | { |
||
1255 | goal_type = OBJ_PLAYER; |
||
1256 | goal2_type = (Game_mode & GM_MULTI_ROBOTS) |
||
1257 | ? OBJ_ROBOT // Not cooperative, if robots, track either robot or player |
||
1258 | : -1; // Not cooperative and no robots, track only a player |
||
1259 | } |
||
1260 | } |
||
1261 | else |
||
1262 | goal_type = OBJ_PLAYER, goal2_type = OBJ_ROBOT; |
||
1263 | } |
||
1264 | else |
||
1265 | { |
||
1266 | goal_type = vcobjptr(tracker->ctype.laser_info.track_goal)->type; |
||
1267 | if ((goal_type == OBJ_PLAYER) || (goal_type == OBJ_ROBOT)) |
||
1268 | goal2_type = -1; |
||
1269 | else |
||
1270 | return object_none; |
||
1271 | } |
||
1272 | } |
||
1273 | else { |
||
1274 | goal2_type = -1; |
||
1275 | |||
1276 | #if defined(DXX_BUILD_DESCENT_II) |
||
1277 | if (cheats.robotskillrobots) |
||
1278 | goal2_type = OBJ_ROBOT; |
||
1279 | #endif |
||
1280 | |||
1281 | if (track_goal == object_none) |
||
1282 | goal_type = OBJ_PLAYER; |
||
1283 | else { |
||
1284 | goal_type = vcobjptr(tracker->ctype.laser_info.track_goal)->type; |
||
1285 | assert(goal_type != OBJ_GHOST); |
||
1286 | } |
||
1287 | } |
||
1288 | return find_homing_object_complete(tracker->pos, tracker, goal_type, goal2_type); |
||
1289 | } |
||
1290 | |||
1291 | return object_none; |
||
1292 | } |
||
1293 | |||
1294 | //-------------- Initializes a laser after Fire is pressed ----------------- |
||
1295 | |||
1296 | static imobjptridx_t Laser_player_fire_spread_delay(fvmsegptridx &vmsegptridx, const vmobjptridx_t obj, const weapon_id_type laser_type, const int gun_num, const fix spreadr, const fix spreadu, const fix delay_time, const int make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track) |
||
1297 | { |
||
1298 | int Fate; |
||
1299 | vms_vector LaserDir; |
||
1300 | fvi_query fq; |
||
1301 | fvi_info hit_data; |
||
1302 | vms_vector *pnt; |
||
1303 | |||
1304 | #if defined(DXX_BUILD_DESCENT_II) |
||
1305 | create_awareness_event(obj, player_awareness_type_t::PA_WEAPON_WALL_COLLISION, LevelUniqueRobotAwarenessState); |
||
1306 | #endif |
||
1307 | |||
1308 | // Find the initial position of the laser |
||
1309 | pnt = &Player_ship->gun_points[gun_num]; |
||
1310 | |||
1311 | vms_matrix m = vm_transposed_matrix(obj->orient); |
||
1312 | const auto gun_point = vm_vec_rotate(*pnt,m); |
||
1313 | |||
1314 | auto LaserPos = vm_vec_add(obj->pos,gun_point); |
||
1315 | |||
1316 | // If supposed to fire at a delayed time (delay_time), then move this point backwards. |
||
1317 | if (delay_time) |
||
1318 | vm_vec_scale_add2(LaserPos, shot_orientation, -fixmul(delay_time, Weapon_info[laser_type].speed[GameUniqueState.Difficulty_level])); |
||
1319 | |||
1320 | // do_muzzle_stuff(obj, &Pos); |
||
1321 | |||
1322 | //--------------- Find LaserPos and LaserSeg ------------------ |
||
1323 | fq.p0 = &obj->pos; |
||
1324 | fq.startseg = obj->segnum; |
||
1325 | fq.p1 = &LaserPos; |
||
1326 | fq.rad = 0x10; |
||
1327 | fq.thisobjnum = obj; |
||
1328 | fq.ignore_obj_list.first = nullptr; |
||
1329 | #if defined(DXX_BUILD_DESCENT_I) |
||
1330 | fq.flags = FQ_CHECK_OBJS; |
||
1331 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1332 | fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS; |
||
1333 | #endif |
||
1334 | |||
1335 | Fate = find_vector_intersection(fq, hit_data); |
||
1336 | |||
1337 | auto LaserSeg = hit_data.hit_seg; |
||
1338 | |||
1339 | if (LaserSeg == segment_none) //some sort of annoying error |
||
1340 | return object_none; |
||
1341 | |||
1342 | //SORT OF HACK... IF ABOVE WAS CORRECT THIS WOULDNT BE NECESSARY. |
||
1343 | if ( vm_vec_dist_quick(LaserPos, obj->pos) > 0x50000 ) |
||
1344 | return object_none; |
||
1345 | |||
1346 | if (Fate==HIT_WALL) { |
||
1347 | return object_none; |
||
1348 | } |
||
1349 | |||
1350 | if (Fate==HIT_OBJECT) { |
||
1351 | // if ( Objects[hit_data.hit_object].type == OBJ_ROBOT ) |
||
1352 | // Objects[hit_data.hit_object].flags |= OF_SHOULD_BE_DEAD; |
||
1353 | // if ( Objects[hit_data.hit_object].type != OBJ_POWERUP ) |
||
1354 | // return; |
||
1355 | //as of 12/6/94, we don't care if the laser is stuck in an object. We |
||
1356 | //just fire away normally |
||
1357 | } |
||
1358 | |||
1359 | // Now, make laser spread out. |
||
1360 | LaserDir = shot_orientation; |
||
1361 | if ((spreadr != 0) || (spreadu != 0)) { |
||
1362 | vm_vec_scale_add2(LaserDir, obj->orient.rvec, spreadr); |
||
1363 | vm_vec_scale_add2(LaserDir, obj->orient.uvec, spreadu); |
||
1364 | } |
||
1365 | |||
1366 | const auto &&objnum = Laser_create_new(LaserDir, LaserPos, vmsegptridx(LaserSeg), obj, laser_type, make_sound); |
||
1367 | |||
1368 | if (objnum == object_none) |
||
1369 | return object_none; |
||
1370 | |||
1371 | #if defined(DXX_BUILD_DESCENT_II) |
||
1372 | // Omega cannon is a hack, not surprisingly. Don't want to do the rest of this stuff. |
||
1373 | if (laser_type == weapon_id_type::OMEGA_ID) |
||
1374 | return objnum; |
||
1375 | |||
1376 | if (laser_type == weapon_id_type::GUIDEDMISS_ID && Multi_is_guided) { |
||
1377 | LevelUniqueObjectState.Guided_missile.set_player_active_guided_missile(objnum, get_player_id(obj)); |
||
1378 | } |
||
1379 | |||
1380 | Multi_is_guided=0; |
||
1381 | |||
1382 | if (laser_type == weapon_id_type::CONCUSSION_ID || |
||
1383 | laser_type == weapon_id_type::HOMING_ID || |
||
1384 | laser_type == weapon_id_type::SMART_ID || |
||
1385 | laser_type == weapon_id_type::MEGA_ID || |
||
1386 | laser_type == weapon_id_type::FLASH_ID || |
||
1387 | //laser_type == GUIDEDMISS_ID || |
||
1388 | //laser_type == SUPERPROX_ID || |
||
1389 | laser_type == weapon_id_type::MERCURY_ID || |
||
1390 | laser_type == weapon_id_type::EARTHSHAKER_ID) |
||
1391 | { |
||
1392 | const auto need_new_missile_viewer = [obj]{ |
||
1393 | if (!Missile_viewer) |
||
1394 | return true; |
||
1395 | if (Missile_viewer->type != OBJ_WEAPON) |
||
1396 | return true; |
||
1397 | if (Missile_viewer->signature != Missile_viewer_sig) |
||
1398 | return true; |
||
1399 | if (get_player_id(obj) == Player_num && Missile_viewer->ctype.laser_info.parent_num != get_local_player().objnum) |
||
1400 | /* New missile fired-by local player && |
||
1401 | * currently viewing missile not-fired-by local player |
||
1402 | */ |
||
1403 | return true; |
||
1404 | return false; |
||
1405 | }; |
||
1406 | const auto can_view_missile = [obj]{ |
||
1407 | const auto obj_id = get_player_id(obj); |
||
1408 | if (obj_id == Player_num) |
||
1409 | return true; |
||
1410 | if (PlayerCfg.MissileViewEnabled != MissileViewMode::EnabledSelfAndAllies) |
||
1411 | return false; |
||
1412 | { |
||
1413 | if (Game_mode & GM_MULTI_COOP) |
||
1414 | return true; |
||
1415 | if (Game_mode & GM_TEAM) |
||
1416 | return get_team(Player_num) == get_team(obj_id); |
||
1417 | } |
||
1418 | return false; |
||
1419 | }; |
||
1420 | if (need_new_missile_viewer() && can_view_missile()) |
||
1421 | { |
||
1422 | Missile_viewer = objnum; |
||
1423 | Missile_viewer_sig = objnum->signature; |
||
1424 | } |
||
1425 | } |
||
1426 | #endif |
||
1427 | |||
1428 | // If this weapon is supposed to be silent, set that bit! |
||
1429 | if (!make_sound) |
||
1430 | objnum->flags |= OF_SILENT; |
||
1431 | |||
1432 | // If the object firing the laser is the player, then indicate the laser object so robots can dodge. |
||
1433 | // New by MK on 6/8/95, don't let robots evade proximity bombs, thereby decreasing uselessness of bombs. |
||
1434 | if (obj == ConsoleObject) |
||
1435 | #if defined(DXX_BUILD_DESCENT_II) |
||
1436 | if (!is_proximity_bomb_or_player_smart_mine(get_weapon_id(objnum))) |
||
1437 | #endif |
||
1438 | Player_fired_laser_this_frame = objnum; |
||
1439 | |||
1440 | if (Weapon_info[laser_type].homing_flag) { |
||
1441 | if (obj == ConsoleObject) |
||
1442 | { |
||
1443 | objnum->ctype.laser_info.track_goal = find_homing_object(LaserPos, objnum); |
||
1444 | } |
||
1445 | else // Some other player shot the homing thing |
||
1446 | { |
||
1447 | Assert(Game_mode & GM_MULTI); |
||
1448 | objnum->ctype.laser_info.track_goal = Network_laser_track; |
||
1449 | } |
||
1450 | } |
||
1451 | |||
1452 | return objnum; |
||
1453 | } |
||
1454 | |||
1455 | // ----------------------------------------------------------------------------------------------------------- |
||
1456 | static imobjptridx_t Laser_player_fire_spread(const vmobjptridx_t obj, const weapon_id_type laser_type, const int gun_num, const fix spreadr, const fix spreadu, const int make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track) |
||
1457 | { |
||
1458 | return Laser_player_fire_spread_delay(vmsegptridx, obj, laser_type, gun_num, spreadr, spreadu, 0, make_sound, shot_orientation, Network_laser_track); |
||
1459 | } |
||
1460 | |||
1461 | |||
1462 | // ----------------------------------------------------------------------------------------------------------- |
||
1463 | imobjptridx_t Laser_player_fire(const vmobjptridx_t obj, const weapon_id_type laser_type, const int gun_num, const int make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track) |
||
1464 | { |
||
1465 | return Laser_player_fire_spread(obj, laser_type, gun_num, 0, 0, make_sound, shot_orientation, Network_laser_track); |
||
1466 | } |
||
1467 | |||
1468 | // ----------------------------------------------------------------------------------------------------------- |
||
1469 | void Flare_create(const vmobjptridx_t obj) |
||
1470 | { |
||
1471 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1472 | auto &vmobjptr = Objects.vmptr; |
||
1473 | |||
1474 | const auto energy_usage = get_weapon_energy_usage_with_difficulty(Weapon_info[weapon_id_type::FLARE_ID], GameUniqueState.Difficulty_level); |
||
1475 | |||
1476 | // MK, 11/04/95: Allowed to fire flare even if no energy. |
||
1477 | // -- if (Players[Player_num].energy >= energy_usage) |
||
1478 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
1479 | auto &energy = player_info.energy; |
||
1480 | #if defined(DXX_BUILD_DESCENT_I) |
||
1481 | if (energy > 0) |
||
1482 | #endif |
||
1483 | { |
||
1484 | const auto &&flare = Laser_player_fire(obj, weapon_id_type::FLARE_ID, 6, 1, get_local_plrobj().orient.fvec, object_none); |
||
1485 | if (flare == object_none) |
||
1486 | return; |
||
1487 | energy -= energy_usage; |
||
1488 | |||
1489 | if (energy <= 0) |
||
1490 | { |
||
1491 | energy = 0; |
||
1492 | #if defined(DXX_BUILD_DESCENT_I) |
||
1493 | auto_select_primary_weapon(player_info); |
||
1494 | #endif |
||
1495 | } |
||
1496 | |||
1497 | if (Game_mode & GM_MULTI) |
||
1498 | multi_send_fire(FLARE_ADJUST, 0, 0, 1, object_none, object_none); |
||
1499 | } |
||
1500 | |||
1501 | } |
||
1502 | |||
1503 | #if defined(DXX_BUILD_DESCENT_I) |
||
1504 | #define HOMING_MISSILE_SCALE 8 |
||
1505 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1506 | #define HOMING_MISSILE_SCALE 16 |
||
1507 | |||
1508 | static bool is_active_guided_missile(d_level_unique_object_state &LevelUniqueObjectState, const vcobjptridx_t obj) |
||
1509 | { |
||
1510 | if (obj->ctype.laser_info.parent_type != OBJ_PLAYER) |
||
1511 | return false; |
||
1512 | auto &vcobjptr = LevelUniqueObjectState.get_objects().vcptr; |
||
1513 | auto &parent_obj = *vcobjptr(obj->ctype.laser_info.parent_num); |
||
1514 | if (parent_obj.type != OBJ_PLAYER) |
||
1515 | return false; |
||
1516 | const auto pnum = get_player_id(parent_obj); |
||
1517 | return LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(pnum) == obj; |
||
1518 | } |
||
1519 | #endif |
||
1520 | |||
1521 | //-------------------------------------------------------------------- |
||
1522 | // Set object *objp's orientation to (or towards if I'm ambitious) its velocity. |
||
1523 | static void homing_missile_turn_towards_velocity(object_base &objp, const vms_vector &norm_vel, fix ft) |
||
1524 | { |
||
1525 | auto new_fvec = norm_vel; |
||
1526 | vm_vec_scale(new_fvec, ft * HOMING_MISSILE_SCALE); |
||
1527 | vm_vec_add2(new_fvec, objp.orient.fvec); |
||
1528 | vm_vec_normalize_quick(new_fvec); |
||
1529 | |||
1530 | // if ((norm_vel->x == 0) && (norm_vel->y == 0) && (norm_vel->z == 0)) |
||
1531 | // return; |
||
1532 | |||
1533 | vm_vector_2_matrix(objp.orient, new_fvec, nullptr, nullptr); |
||
1534 | } |
||
1535 | |||
1536 | |||
1537 | //------------------------------------------------------------------------------------------- |
||
1538 | //sequence this laser object for this _frame_ (underscores added here to aid MK in his searching!) |
||
1539 | void Laser_do_weapon_sequence(const vmobjptridx_t obj) |
||
1540 | { |
||
1541 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1542 | auto &imobjptridx = Objects.imptridx; |
||
1543 | auto &vcobjptr = Objects.vcptr; |
||
1544 | auto &vmobjptr = Objects.vmptr; |
||
1545 | Assert(obj->control_type == CT_WEAPON); |
||
1546 | |||
1547 | if (obj->lifeleft < 0 ) { // We died of old age |
||
1548 | obj->flags |= OF_SHOULD_BE_DEAD; |
||
1549 | if ( Weapon_info[get_weapon_id(obj)].damage_radius ) |
||
1550 | explode_badass_weapon(obj, obj->pos); |
||
1551 | return; |
||
1552 | } |
||
1553 | |||
1554 | #if defined(DXX_BUILD_DESCENT_II) |
||
1555 | if (omega_cleanup(vcobjptr, obj)) |
||
1556 | return; |
||
1557 | #endif |
||
1558 | |||
1559 | //delete weapons that are not moving |
||
1560 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
1561 | if ( !((d_tick_count ^ obj->signature.get()) & 3) && |
||
1562 | (get_weapon_id(obj) != weapon_id_type::FLARE_ID) && |
||
1563 | (Weapon_info[get_weapon_id(obj)].speed[Difficulty_level] > 0) && |
||
1564 | (vm_vec_mag_quick(obj->mtype.phys_info.velocity) < F2_0)) { |
||
1565 | obj_delete(LevelUniqueObjectState, Segments, obj); |
||
1566 | return; |
||
1567 | } |
||
1568 | |||
1569 | if ( get_weapon_id(obj) == weapon_id_type::FUSION_ID ) { //always set fusion weapon to max vel |
||
1570 | |||
1571 | vm_vec_normalize_quick(obj->mtype.phys_info.velocity); |
||
1572 | |||
1573 | vm_vec_scale(obj->mtype.phys_info.velocity, Weapon_info[get_weapon_id(obj)].speed[Difficulty_level]); |
||
1574 | } |
||
1575 | |||
1576 | // For homing missiles, turn towards target. (unless it's the guided missile) |
||
1577 | #if defined(DXX_BUILD_DESCENT_I) |
||
1578 | if (Weapon_info[get_weapon_id(obj)].homing_flag) |
||
1579 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1580 | if (Weapon_info[get_weapon_id(obj)].homing_flag && !is_active_guided_missile(LevelUniqueObjectState, obj)) |
||
1581 | #endif |
||
1582 | { |
||
1583 | vms_vector vector_to_object, temp_vec; |
||
1584 | fix dot=F1_0; |
||
1585 | fix speed, max_speed; |
||
1586 | |||
1587 | // For first 125ms of life, missile flies straight. |
||
1588 | if (obj->ctype.laser_info.creation_time + HOMING_FLY_STRAIGHT_TIME < GameTime64) |
||
1589 | { |
||
1590 | |||
1591 | // If it's time to do tracking, then it's time to grow up, stop bouncing and start exploding!. |
||
1592 | #if defined(DXX_BUILD_DESCENT_I) |
||
1593 | if ((get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::PLAYER_SMART_HOMING_ID)) |
||
1594 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1595 | if ((get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_MINE_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::SMART_MINE_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::PLAYER_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::EARTHSHAKER_MEGA_ID)) |
||
1596 | #endif |
||
1597 | { |
||
1598 | obj->mtype.phys_info.flags &= ~PF_BOUNCE; |
||
1599 | } |
||
1600 | |||
1601 | #ifdef NEWHOMER |
||
1602 | if (d_homer_tick_step) |
||
1603 | { |
||
1604 | const auto &&track_goal = track_track_goal(vcobjptr, imobjptridx(obj->ctype.laser_info.track_goal), obj, &dot, d_homer_tick_count); |
||
1605 | |||
1606 | if (track_goal == get_local_player().objnum) { |
||
1607 | fix dist_to_player; |
||
1608 | |||
1609 | dist_to_player = vm_vec_dist_quick(obj->pos, track_goal->pos); |
||
1610 | auto &homing_object_dist = get_local_plrobj().ctype.player_info.homing_object_dist; |
||
1611 | if (dist_to_player < homing_object_dist || homing_object_dist < 0) |
||
1612 | homing_object_dist = dist_to_player; |
||
1613 | } |
||
1614 | |||
1615 | if (track_goal != object_none) |
||
1616 | { |
||
1617 | vm_vec_sub(vector_to_object, track_goal->pos, obj->pos); |
||
1618 | vm_vec_normalize_quick(vector_to_object); |
||
1619 | temp_vec = obj->mtype.phys_info.velocity; |
||
1620 | speed = vm_vec_normalize_quick(temp_vec); |
||
1621 | max_speed = Weapon_info[get_weapon_id(obj)].speed[Difficulty_level]; |
||
1622 | if (speed+F1_0 < max_speed) { |
||
1623 | speed += fixmul(max_speed, HOMING_TURN_TIME/2); |
||
1624 | if (speed > max_speed) |
||
1625 | speed = max_speed; |
||
1626 | } |
||
1627 | #if defined(DXX_BUILD_DESCENT_I) |
||
1628 | dot = vm_vec_dot(temp_vec, vector_to_object); |
||
1629 | #endif |
||
1630 | vm_vec_add2(temp_vec, vector_to_object); |
||
1631 | // The boss' smart children track better... |
||
1632 | if (Weapon_info[get_weapon_id(obj)].render_type != WEAPON_RENDER_POLYMODEL) |
||
1633 | vm_vec_add2(temp_vec, vector_to_object); |
||
1634 | vm_vec_normalize_quick(temp_vec); |
||
1635 | #if defined(DXX_BUILD_DESCENT_I) |
||
1636 | vm_vec_scale(temp_vec, speed); |
||
1637 | obj->mtype.phys_info.velocity = temp_vec; |
||
1638 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1639 | obj->mtype.phys_info.velocity = temp_vec; |
||
1640 | vm_vec_scale(obj->mtype.phys_info.velocity, speed); |
||
1641 | #endif |
||
1642 | |||
1643 | // Subtract off life proportional to amount turned. |
||
1644 | // For hardest turn, it will lose 2 seconds per second. |
||
1645 | { |
||
1646 | fix lifelost, absdot; |
||
1647 | |||
1648 | absdot = abs(F1_0 - dot); |
||
1649 | #if defined(DXX_BUILD_DESCENT_I) |
||
1650 | if (absdot > F1_0/8) { |
||
1651 | if (absdot > F1_0/4) |
||
1652 | absdot = F1_0/4; |
||
1653 | lifelost = fixmul(absdot*16, HOMING_TURN_TIME); |
||
1654 | obj->lifeleft -= lifelost; |
||
1655 | } |
||
1656 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1657 | lifelost = fixmul(absdot*32, HOMING_TURN_TIME); |
||
1658 | obj->lifeleft -= lifelost; |
||
1659 | #endif |
||
1660 | } |
||
1661 | |||
1662 | // Only polygon objects have visible orientation, so only they should turn. |
||
1663 | if (Weapon_info[get_weapon_id(obj)].render_type == WEAPON_RENDER_POLYMODEL) |
||
1664 | homing_missile_turn_towards_velocity(obj, temp_vec, HOMING_TURN_TIME); // temp_vec is normalized velocity. |
||
1665 | } |
||
1666 | } |
||
1667 | #else // old FPS-dependent homers - NOTE: I know this is very redundant but I want to keep the historical code 100% preserved to compare against potential changes in the above. |
||
1668 | // Make sure the object we are tracking is still trackable. |
||
1669 | const auto &&track_goal = track_track_goalvcobjptr, (imobjptridx(obj->ctype.laser_info.track_goal), obj, &dot, d_tick_count); |
||
1670 | |||
1671 | if (track_goal == get_local_player().objnum) { |
||
1672 | fix dist_to_player; |
||
1673 | |||
1674 | dist_to_player = vm_vec_dist_quick(obj->pos, track_goal->pos); |
||
1675 | auto &homing_object_dist = get_local_plrobj().ctype.player_info.homing_object_dist; |
||
1676 | if (dist_to_player < homing_object_dist || homing_object_dist < 0) |
||
1677 | homing_object_dist = dist_to_player; |
||
1678 | } |
||
1679 | |||
1680 | if (track_goal != object_none) |
||
1681 | { |
||
1682 | vm_vec_sub(vector_to_object, track_goal->pos, obj->pos); |
||
1683 | vm_vec_normalize_quick(vector_to_object); |
||
1684 | temp_vec = obj->mtype.phys_info.velocity; |
||
1685 | speed = vm_vec_normalize_quick(temp_vec); |
||
1686 | max_speed = Weapon_info[get_weapon_id(obj)].speed[Difficulty_level]; |
||
1687 | if (speed+F1_0 < max_speed) { |
||
1688 | speed += fixmul(max_speed, FrameTime/2); |
||
1689 | if (speed > max_speed) |
||
1690 | speed = max_speed; |
||
1691 | } |
||
1692 | #if defined(DXX_BUILD_DESCENT_I) |
||
1693 | dot = vm_vec_dot(temp_vec, vector_to_object); |
||
1694 | #endif |
||
1695 | vm_vec_add2(temp_vec, vector_to_object); |
||
1696 | // The boss' smart children track better... |
||
1697 | if (Weapon_info[get_weapon_id(obj)].render_type != WEAPON_RENDER_POLYMODEL) |
||
1698 | vm_vec_add2(temp_vec, vector_to_object); |
||
1699 | vm_vec_normalize_quick(temp_vec); |
||
1700 | #if defined(DXX_BUILD_DESCENT_I) |
||
1701 | vm_vec_scale(temp_vec, speed); |
||
1702 | obj->mtype.phys_info.velocity = temp_vec; |
||
1703 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1704 | obj->mtype.phys_info.velocity = temp_vec; |
||
1705 | vm_vec_scale(obj->mtype.phys_info.velocity, speed); |
||
1706 | #endif |
||
1707 | |||
1708 | // Subtract off life proportional to amount turned. |
||
1709 | // For hardest turn, it will lose 2 seconds per second. |
||
1710 | { |
||
1711 | fix lifelost, absdot; |
||
1712 | |||
1713 | absdot = abs(F1_0 - dot); |
||
1714 | #if defined(DXX_BUILD_DESCENT_I) |
||
1715 | if (absdot > F1_0/8) { |
||
1716 | if (absdot > F1_0/4) |
||
1717 | absdot = F1_0/4; |
||
1718 | lifelost = fixmul(absdot*16, FrameTime); |
||
1719 | obj->lifeleft -= lifelost; |
||
1720 | } |
||
1721 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1722 | lifelost = fixmul(absdot*32, FrameTime); |
||
1723 | obj->lifeleft -= lifelost; |
||
1724 | #endif |
||
1725 | } |
||
1726 | |||
1727 | // Only polygon objects have visible orientation, so only they should turn. |
||
1728 | if (Weapon_info[get_weapon_id(obj)].render_type == WEAPON_RENDER_POLYMODEL) |
||
1729 | homing_missile_turn_towards_velocity(obj, temp_vec, FrameTime); // temp_vec is normalized velocity. |
||
1730 | } |
||
1731 | #endif |
||
1732 | } |
||
1733 | } |
||
1734 | |||
1735 | // Make sure weapon is not moving faster than allowed speed. |
||
1736 | const auto &weapon_info = Weapon_info[get_weapon_id(obj)]; |
||
1737 | #if defined(DXX_BUILD_DESCENT_I) |
||
1738 | if (weapon_info.thrust != 0) |
||
1739 | #endif |
||
1740 | { |
||
1741 | fix weapon_speed; |
||
1742 | |||
1743 | weapon_speed = vm_vec_mag_quick(obj->mtype.phys_info.velocity); |
||
1744 | if (weapon_speed > weapon_info.speed[Difficulty_level]) { |
||
1745 | // Only slow down if not allowed to move. Makes sense, huh? Allows proxbombs to get moved by physics force. --MK, 2/13/96 |
||
1746 | #if defined(DXX_BUILD_DESCENT_II) |
||
1747 | if (weapon_info.speed[Difficulty_level]) |
||
1748 | #endif |
||
1749 | { |
||
1750 | fix scale_factor; |
||
1751 | |||
1752 | scale_factor = fixdiv(Weapon_info[get_weapon_id(obj)].speed[Difficulty_level], weapon_speed); |
||
1753 | vm_vec_scale(obj->mtype.phys_info.velocity, scale_factor); |
||
1754 | } |
||
1755 | } |
||
1756 | } |
||
1757 | } |
||
1758 | |||
1759 | } |
||
1760 | |||
1761 | namespace dcx { |
||
1762 | |||
1763 | constexpr std::integral_constant<unsigned, 30> MAX_OBJDISTS{}; |
||
1764 | |||
1765 | static inline int sufficient_energy(int energy_used, fix energy) |
||
1766 | { |
||
1767 | return !energy_used || (energy >= energy_used); |
||
1768 | } |
||
1769 | |||
1770 | static inline int sufficient_ammo(int ammo_used, int uses_vulcan_ammo, ushort vulcan_ammo) |
||
1771 | { |
||
1772 | return !ammo_used || (!uses_vulcan_ammo || vulcan_ammo >= ammo_used); |
||
1773 | } |
||
1774 | |||
1775 | } |
||
1776 | |||
1777 | namespace dsx { |
||
1778 | |||
1779 | // -------------------------------------------------------------------------------------------------- |
||
1780 | // Assumption: This is only called by the actual console player, not for network players |
||
1781 | |||
1782 | void do_laser_firing_player(object &plrobj) |
||
1783 | { |
||
1784 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1785 | auto &vmobjptridx = Objects.vmptridx; |
||
1786 | int ammo_used; |
||
1787 | int rval = 0; |
||
1788 | int nfires = 1; |
||
1789 | |||
1790 | if (Player_dead_state != player_dead_state::no) |
||
1791 | return; |
||
1792 | |||
1793 | auto &player_info = plrobj.ctype.player_info; |
||
1794 | const auto Primary_weapon = player_info.Primary_weapon; |
||
1795 | const auto weapon_index = Primary_weapon_to_weapon_info[Primary_weapon]; |
||
1796 | |||
1797 | ammo_used = Weapon_info[weapon_index].ammo_usage; |
||
1798 | |||
1799 | const auto uses_vulcan_ammo = weapon_index_uses_vulcan_ammo(Primary_weapon); |
||
1800 | |||
1801 | auto &pl_energy = player_info.energy; |
||
1802 | const auto base_energy_used = |
||
1803 | #if defined(DXX_BUILD_DESCENT_II) |
||
1804 | (Primary_weapon == primary_weapon_index_t::OMEGA_INDEX) |
||
1805 | ? 0 // Omega consumes energy when recharging, not when firing. |
||
1806 | : |
||
1807 | #endif |
||
1808 | get_weapon_energy_usage_with_difficulty(Weapon_info[weapon_index], GameUniqueState.Difficulty_level); |
||
1809 | |||
1810 | const auto energy_used = |
||
1811 | #if defined(DXX_BUILD_DESCENT_II) |
||
1812 | // MK, 01/26/96, Helix use 2x energy in multiplayer. bitmaps.tbl parm should have been reduced for single player. |
||
1813 | (weapon_index == weapon_id_type::HELIX_ID && (Game_mode & GM_MULTI)) |
||
1814 | ? base_energy_used * 2 |
||
1815 | : |
||
1816 | #endif |
||
1817 | base_energy_used; |
||
1818 | |||
1819 | if (!(sufficient_energy(energy_used, pl_energy) && sufficient_ammo(ammo_used, uses_vulcan_ammo, player_info.vulcan_ammo))) |
||
1820 | auto_select_primary_weapon(player_info); // Make sure the player can fire from this weapon. |
||
1821 | |||
1822 | auto &Next_laser_fire_time = player_info.Next_laser_fire_time; |
||
1823 | while (Next_laser_fire_time <= GameTime64) { |
||
1824 | if (sufficient_energy(energy_used, pl_energy) && sufficient_ammo(ammo_used, uses_vulcan_ammo, player_info.vulcan_ammo)) { |
||
1825 | int laser_level, flags, fire_frame_overhead = 0; |
||
1826 | |||
1827 | if (GameTime64 - Next_laser_fire_time <= FrameTime) // if firing is prolonged by FrameTime overhead, let's try to fix that. |
||
1828 | fire_frame_overhead = GameTime64 - Next_laser_fire_time; |
||
1829 | |||
1830 | laser_level = player_info.laser_level; |
||
1831 | |||
1832 | flags = 0; |
||
1833 | |||
1834 | switch (Primary_weapon) |
||
1835 | { |
||
1836 | case primary_weapon_index_t::LASER_INDEX: |
||
1837 | if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS) |
||
1838 | flags |= LASER_QUAD; |
||
1839 | break; |
||
1840 | case primary_weapon_index_t::SPREADFIRE_INDEX: |
||
1841 | flags |= (player_info.Spreadfire_toggle ^= LASER_SPREADFIRE_TOGGLED) & LASER_SPREADFIRE_TOGGLED; |
||
1842 | break; |
||
1843 | case primary_weapon_index_t::VULCAN_INDEX: |
||
1844 | case primary_weapon_index_t::PLASMA_INDEX: |
||
1845 | case primary_weapon_index_t::FUSION_INDEX: |
||
1846 | default: |
||
1847 | break; |
||
1848 | #if defined(DXX_BUILD_DESCENT_II) |
||
1849 | case primary_weapon_index_t::HELIX_INDEX: |
||
1850 | flags |= (player_info.Helix_orientation++ & LASER_HELIX_MASK); |
||
1851 | break; |
||
1852 | case primary_weapon_index_t::SUPER_LASER_INDEX: |
||
1853 | case primary_weapon_index_t::GAUSS_INDEX: |
||
1854 | case primary_weapon_index_t::PHOENIX_INDEX: |
||
1855 | case primary_weapon_index_t::OMEGA_INDEX: |
||
1856 | break; |
||
1857 | #endif |
||
1858 | } |
||
1859 | |||
1860 | const auto shot_fired = do_laser_firing(vmobjptridx(get_local_player().objnum), Primary_weapon, laser_level, flags, nfires, plrobj.orient.fvec, object_none); |
||
1861 | rval += shot_fired; |
||
1862 | |||
1863 | if (!shot_fired) |
||
1864 | break; |
||
1865 | Next_laser_fire_time = GameTime64 - fire_frame_overhead + (unlikely(cheats.rapidfire) |
||
1866 | ? (F1_0 / 25) |
||
1867 | : ( |
||
1868 | #if defined(DXX_BUILD_DESCENT_II) |
||
1869 | weapon_index == weapon_id_type::OMEGA_ID |
||
1870 | ? OMEGA_BASE_TIME |
||
1871 | : |
||
1872 | #endif |
||
1873 | Weapon_info[weapon_index].fire_wait |
||
1874 | ) |
||
1875 | ); |
||
1876 | |||
1877 | pl_energy -= (energy_used * rval) / Weapon_info[weapon_index].fire_count; |
||
1878 | if (pl_energy < 0) |
||
1879 | pl_energy = 0; |
||
1880 | |||
1881 | if (uses_vulcan_ammo) { |
||
1882 | auto &v = player_info.vulcan_ammo; |
||
1883 | if (v < ammo_used) |
||
1884 | v = 0; |
||
1885 | else |
||
1886 | v -= ammo_used; |
||
1887 | maybe_drop_net_powerup(POW_VULCAN_AMMO, 1, 0); |
||
1888 | } |
||
1889 | } else { |
||
1890 | #if defined(DXX_BUILD_DESCENT_II) |
||
1891 | Next_laser_fire_time = GameTime64; // Prevents shots-to-fire from building up. |
||
1892 | #endif |
||
1893 | break; // Couldn't fire weapon, so abort. |
||
1894 | } |
||
1895 | } |
||
1896 | auto_select_primary_weapon(player_info); // Make sure the player can fire from this weapon. |
||
1897 | } |
||
1898 | |||
1899 | // -------------------------------------------------------------------------------------------------- |
||
1900 | // Object "objnum" fires weapon "weapon_num" of level "level". (Right now (9/24/94) level is used only for type 0 laser. |
||
1901 | // Flags are the player flags. For network mode, set to 0. |
||
1902 | // It is assumed that this is a player object (as in multiplayer), and therefore the gun positions are known. |
||
1903 | // Returns number of times a weapon was fired. This is typically 1, but might be more for low frame rates. |
||
1904 | // More than one shot is fired with a pseudo-delay so that players on slow machines can fire (for themselves |
||
1905 | // or other players) often enough for things like the vulcan cannon. |
||
1906 | int do_laser_firing(vmobjptridx_t objp, int weapon_num, int level, int flags, int nfires, vms_vector shot_orientation, const icobjidx_t Network_laser_track) |
||
1907 | { |
||
1908 | switch (weapon_num) { |
||
1909 | case primary_weapon_index_t::LASER_INDEX: { |
||
1910 | weapon_id_type weapon_type; |
||
1911 | |||
1912 | switch(level) |
||
1913 | { |
||
1914 | case LASER_LEVEL_1: |
||
1915 | weapon_type = weapon_id_type::LASER_ID_L1; |
||
1916 | break; |
||
1917 | case LASER_LEVEL_2: |
||
1918 | weapon_type = weapon_id_type::LASER_ID_L2; |
||
1919 | break; |
||
1920 | case LASER_LEVEL_3: |
||
1921 | weapon_type = weapon_id_type::LASER_ID_L3; |
||
1922 | break; |
||
1923 | case LASER_LEVEL_4: |
||
1924 | weapon_type = weapon_id_type::LASER_ID_L4; |
||
1925 | break; |
||
1926 | #if defined(DXX_BUILD_DESCENT_II) |
||
1927 | case LASER_LEVEL_5: |
||
1928 | weapon_type = weapon_id_type::LASER_ID_L5; |
||
1929 | break; |
||
1930 | case LASER_LEVEL_6: |
||
1931 | weapon_type = weapon_id_type::LASER_ID_L6; |
||
1932 | break; |
||
1933 | #endif |
||
1934 | default: |
||
1935 | Assert(0); |
||
1936 | return nfires; |
||
1937 | } |
||
1938 | Laser_player_fire(objp, weapon_type, 0, 1, shot_orientation, object_none); |
||
1939 | Laser_player_fire(objp, weapon_type, 1, 0, shot_orientation, object_none); |
||
1940 | |||
1941 | if (flags & LASER_QUAD) { |
||
1942 | // hideous system to make quad laser 1.5x powerful as normal laser, make every other quad laser bolt harmless |
||
1943 | Laser_player_fire(objp, weapon_type, 2, 0, shot_orientation, object_none); |
||
1944 | Laser_player_fire(objp, weapon_type, 3, 0, shot_orientation, object_none); |
||
1945 | } |
||
1946 | break; |
||
1947 | } |
||
1948 | case primary_weapon_index_t::VULCAN_INDEX: { |
||
1949 | // Only make sound for 1/4 of vulcan bullets. |
||
1950 | int make_sound = 1; |
||
1951 | //if (d_rand() > 24576) |
||
1952 | // make_sound = 1; |
||
1953 | Laser_player_fire_spread(objp, weapon_id_type::VULCAN_ID, 6, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, make_sound, shot_orientation, object_none); |
||
1954 | if (nfires > 1) { |
||
1955 | Laser_player_fire_spread(objp, weapon_id_type::VULCAN_ID, 6, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, 0, shot_orientation, object_none); |
||
1956 | if (nfires > 2) { |
||
1957 | Laser_player_fire_spread(objp, weapon_id_type::VULCAN_ID, 6, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, 0, shot_orientation, object_none); |
||
1958 | } |
||
1959 | } |
||
1960 | break; |
||
1961 | } |
||
1962 | case primary_weapon_index_t::SPREADFIRE_INDEX: |
||
1963 | { |
||
1964 | fix spreadr0, spreadu0, spreadr1, spreadu1; |
||
1965 | if (flags & LASER_SPREADFIRE_TOGGLED) |
||
1966 | spreadr0 = F1_0 / 16, spreadr1 = -F1_0 / 16, spreadu0 = spreadu1 = 0; |
||
1967 | else |
||
1968 | spreadu0 = F1_0 / 16, spreadu1 = -F1_0 / 16, spreadr0 = spreadr1 = 0; |
||
1969 | Laser_player_fire_spread(objp, weapon_id_type::SPREADFIRE_ID, 6, spreadr0, spreadu0, 0, shot_orientation, object_none); |
||
1970 | Laser_player_fire_spread(objp, weapon_id_type::SPREADFIRE_ID, 6, spreadr1, spreadu1, 0, shot_orientation, object_none); |
||
1971 | Laser_player_fire_spread(objp, weapon_id_type::SPREADFIRE_ID, 6, 0, 0, 1, shot_orientation, object_none); |
||
1972 | } |
||
1973 | break; |
||
1974 | |||
1975 | case primary_weapon_index_t::PLASMA_INDEX: |
||
1976 | Laser_player_fire(objp, weapon_id_type::PLASMA_ID, 0, 1, shot_orientation, object_none); |
||
1977 | Laser_player_fire(objp, weapon_id_type::PLASMA_ID, 1, 0, shot_orientation, object_none); |
||
1978 | if (nfires > 1) { |
||
1979 | Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PLASMA_ID, 0, 0, 0, FrameTime/2, 1, shot_orientation, object_none); |
||
1980 | Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PLASMA_ID, 1, 0, 0, FrameTime/2, 0, shot_orientation, object_none); |
||
1981 | } |
||
1982 | break; |
||
1983 | |||
1984 | case primary_weapon_index_t::FUSION_INDEX: { |
||
1985 | vms_vector force_vec; |
||
1986 | |||
1987 | Laser_player_fire(objp, weapon_id_type::FUSION_ID, 0, 1, shot_orientation, object_none); |
||
1988 | Laser_player_fire(objp, weapon_id_type::FUSION_ID, 1, 1, shot_orientation, object_none); |
||
1989 | |||
1990 | assert(objp->type == OBJ_PLAYER); |
||
1991 | auto &Fusion_charge = objp->ctype.player_info.Fusion_charge; |
||
1992 | flags = static_cast<int8_t>(Fusion_charge >> 12); |
||
1993 | |||
1994 | Fusion_charge = 0; |
||
1995 | |||
1996 | force_vec.x = -(objp->orient.fvec.x << 7); |
||
1997 | force_vec.y = -(objp->orient.fvec.y << 7); |
||
1998 | force_vec.z = -(objp->orient.fvec.z << 7); |
||
1999 | phys_apply_force(objp, force_vec); |
||
2000 | |||
2001 | force_vec.x = (force_vec.x >> 4) + d_rand() - 16384; |
||
2002 | force_vec.y = (force_vec.y >> 4) + d_rand() - 16384; |
||
2003 | force_vec.z = (force_vec.z >> 4) + d_rand() - 16384; |
||
2004 | phys_apply_rot(objp, force_vec); |
||
2005 | |||
2006 | } |
||
2007 | break; |
||
2008 | #if defined(DXX_BUILD_DESCENT_II) |
||
2009 | case primary_weapon_index_t::GAUSS_INDEX: { |
||
2010 | // Only make sound for 1/4 of vulcan bullets. |
||
2011 | int make_sound = 1; |
||
2012 | //if (d_rand() > 24576) |
||
2013 | // make_sound = 1; |
||
2014 | |||
2015 | Laser_player_fire_spread(objp, weapon_id_type::GAUSS_ID, 6, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, make_sound, shot_orientation, object_none); |
||
2016 | if (nfires > 1) { |
||
2017 | Laser_player_fire_spread(objp, weapon_id_type::GAUSS_ID, 6, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, 0, shot_orientation, object_none); |
||
2018 | if (nfires > 2) { |
||
2019 | Laser_player_fire_spread(objp, weapon_id_type::GAUSS_ID, 6, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, 0, shot_orientation, object_none); |
||
2020 | } |
||
2021 | } |
||
2022 | break; |
||
2023 | } |
||
2024 | case primary_weapon_index_t::HELIX_INDEX: { |
||
2025 | fix spreadr,spreadu; |
||
2026 | static const std::array<std::pair<fix, fix>, 8> spread{{ |
||
2027 | { F1_0/16, 0}, // Vertical |
||
2028 | { F1_0/17, F1_0/42}, // 22.5 degrees |
||
2029 | { F1_0/22, F1_0/22}, // 45 degrees |
||
2030 | { F1_0/42, F1_0/17}, // 67.5 degrees |
||
2031 | { 0, F1_0/16}, // 90 degrees |
||
2032 | {-F1_0/42, F1_0/17}, // 112.5 degrees |
||
2033 | {-F1_0/22, F1_0/22}, // 135 degrees |
||
2034 | {-F1_0/17, F1_0/42}, // 157.5 degrees |
||
2035 | }}; |
||
2036 | const unsigned helix_orient = flags & LASER_HELIX_MASK; |
||
2037 | if (helix_orient < spread.size()) |
||
2038 | { |
||
2039 | auto &s = spread[helix_orient]; |
||
2040 | spreadr = s.first; |
||
2041 | spreadu = s.second; |
||
2042 | } |
||
2043 | else |
||
2044 | break; |
||
2045 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, 0, 0, 1, shot_orientation, object_none); |
||
2046 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, spreadr, spreadu, 0, shot_orientation, object_none); |
||
2047 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, -spreadr, -spreadu, 0, shot_orientation, object_none); |
||
2048 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, spreadr*2, spreadu*2, 0, shot_orientation, object_none); |
||
2049 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, -spreadr*2, -spreadu*2, 0, shot_orientation, object_none); |
||
2050 | break; |
||
2051 | } |
||
2052 | |||
2053 | case primary_weapon_index_t::PHOENIX_INDEX: |
||
2054 | Laser_player_fire(objp, weapon_id_type::PHOENIX_ID, 0, 1, shot_orientation, object_none); |
||
2055 | Laser_player_fire(objp, weapon_id_type::PHOENIX_ID, 1, 0, shot_orientation, object_none); |
||
2056 | if (nfires > 1) { |
||
2057 | Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PHOENIX_ID, 0, 0, 0, FrameTime/2, 1, shot_orientation, object_none); |
||
2058 | Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PHOENIX_ID, 1, 0, 0, FrameTime/2, 0, shot_orientation, object_none); |
||
2059 | } |
||
2060 | break; |
||
2061 | |||
2062 | case primary_weapon_index_t::OMEGA_INDEX: |
||
2063 | Laser_player_fire(objp, weapon_id_type::OMEGA_ID, 1, 1, shot_orientation, Network_laser_track); |
||
2064 | break; |
||
2065 | |||
2066 | case primary_weapon_index_t::SUPER_LASER_INDEX: |
||
2067 | #endif |
||
2068 | default: |
||
2069 | Int3(); // Contact Yuan: Unknown Primary weapon type, setting to 0. |
||
2070 | } |
||
2071 | |||
2072 | // Set values to be recognized during comunication phase, if we are the |
||
2073 | // one shooting |
||
2074 | if ((Game_mode & GM_MULTI) && objp == get_local_player().objnum) |
||
2075 | multi_send_fire(weapon_num, level, flags, nfires, Network_laser_track, object_none); |
||
2076 | |||
2077 | return nfires; |
||
2078 | } |
||
2079 | |||
2080 | } |
||
2081 | |||
2082 | namespace dcx { |
||
2083 | |||
2084 | uint_fast8_t laser_info::test_set_hitobj(const vcobjidx_t o) |
||
2085 | { |
||
2086 | if (const auto r = test_hitobj(o)) |
||
2087 | return r; |
||
2088 | const std::size_t values_size = hitobj_values.size(); |
||
2089 | assert(hitobj_count <= values_size); |
||
2090 | assert(hitobj_pos < values_size); |
||
2091 | hitobj_values[hitobj_pos++] = o; |
||
2092 | if (unlikely(hitobj_pos == values_size)) |
||
2093 | hitobj_pos = 0; |
||
2094 | if (likely(hitobj_count != values_size)) |
||
2095 | ++ hitobj_count; |
||
2096 | return false; |
||
2097 | } |
||
2098 | |||
2099 | uint_fast8_t laser_info::test_hitobj(const vcobjidx_t o) const |
||
2100 | { |
||
2101 | /* Search backward so that the highest added element is the first |
||
2102 | * one considered. This preserves most of the benefit of tracking |
||
2103 | * the most recent hit separately, without storing it separately or |
||
2104 | * requiring expensive shift operations as new elements are added. |
||
2105 | * |
||
2106 | * This efficiency hack becomes ineffective (but not incorrect) if |
||
2107 | * the list wraps. Wrapping should be very rare, and even a full |
||
2108 | * search is relatively cheap, so it is not worth complicating the |
||
2109 | * code to ensure that elements are always searched in |
||
2110 | * most-recently-added order. |
||
2111 | */ |
||
2112 | const auto &&r = partial_range(hitobj_values, hitobj_count).reversed(); |
||
2113 | const auto &&e = r.end(); |
||
2114 | return std::find(r.begin(), e, o) != e; |
||
2115 | } |
||
2116 | |||
2117 | } |
||
2118 | |||
2119 | namespace dsx { |
||
2120 | |||
2121 | // ------------------------------------------------------------------------------------------- |
||
2122 | // if goal_obj == -1, then create random vector |
||
2123 | static imobjptridx_t create_homing_missile(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const imobjptridx_t goal_obj, weapon_id_type objtype, int make_sound) |
||
2124 | { |
||
2125 | vms_vector vector_to_goal; |
||
2126 | //vms_vector goal_pos; |
||
2127 | |||
2128 | if (goal_obj == object_none) { |
||
2129 | make_random_vector(vector_to_goal); |
||
2130 | } else { |
||
2131 | vm_vec_normalized_dir_quick(vector_to_goal, goal_obj->pos, objp->pos); |
||
2132 | vm_vec_scale_add2(vector_to_goal, make_random_vector(), F1_0/4); |
||
2133 | vm_vec_normalize_quick(vector_to_goal); |
||
2134 | } |
||
2135 | |||
2136 | // Create a vector towards the goal, then add some noise to it. |
||
2137 | const auto &&objnum = Laser_create_new(vector_to_goal, objp->pos, vmsegptridx(objp->segnum), objp, objtype, make_sound); |
||
2138 | if (objnum != object_none) |
||
2139 | { |
||
2140 | // Fixed to make sure the right person gets credit for the kill |
||
2141 | |||
2142 | // Objects[objnum].ctype.laser_info.parent_num = objp->ctype.laser_info.parent_num; |
||
2143 | // Objects[objnum].ctype.laser_info.parent_type = objp->ctype.laser_info.parent_type; |
||
2144 | // Objects[objnum].ctype.laser_info.parent_signature = objp->ctype.laser_info.parent_signature; |
||
2145 | objnum->ctype.laser_info.track_goal = goal_obj; |
||
2146 | } |
||
2147 | return objnum; |
||
2148 | } |
||
2149 | |||
2150 | namespace { |
||
2151 | |||
2152 | struct miniparent |
||
2153 | { |
||
2154 | short type; |
||
2155 | objnum_t num; |
||
2156 | }; |
||
2157 | |||
2158 | } |
||
2159 | |||
2160 | //----------------------------------------------------------------------------- |
||
2161 | // Create the children of a smart bomb, which is a bunch of homing missiles. |
||
2162 | static void create_smart_children(object_array &Objects, const vmobjptridx_t objp, const uint_fast32_t num_smart_children, const miniparent parent) |
||
2163 | { |
||
2164 | auto &vcobjptridx = Objects.vcptridx; |
||
2165 | auto &vcobjptr = Objects.vcptr; |
||
2166 | unsigned numobjs = 0; |
||
2167 | weapon_id_type blob_id; |
||
2168 | |||
2169 | std::array<objnum_t, MAX_OBJDISTS> objlist; |
||
2170 | #if defined(DXX_BUILD_DESCENT_II) |
||
2171 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
2172 | #endif |
||
2173 | { |
||
2174 | if (Game_mode & GM_MULTI) |
||
2175 | d_srand(8321L); |
||
2176 | |||
2177 | range_for (const auto &&curobjp, vcobjptridx) |
||
2178 | { |
||
2179 | if (((curobjp->type == OBJ_ROBOT && !curobjp->ctype.ai_info.CLOAKED) || curobjp->type == OBJ_PLAYER) && curobjp != parent.num) |
||
2180 | { |
||
2181 | if (curobjp->type == OBJ_PLAYER) |
||
2182 | { |
||
2183 | if (parent.type == OBJ_PLAYER && (Game_mode & GM_MULTI_COOP)) |
||
2184 | continue; |
||
2185 | if ((Game_mode & GM_TEAM) && get_team(get_player_id(curobjp)) == get_team(get_player_id(vcobjptr(parent.num)))) |
||
2186 | continue; |
||
2187 | if (curobjp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) |
||
2188 | continue; |
||
2189 | } |
||
2190 | |||
2191 | // Robot blobs can't track robots. |
||
2192 | if (curobjp->type == OBJ_ROBOT) { |
||
2193 | if (parent.type == OBJ_ROBOT) |
||
2194 | continue; |
||
2195 | |||
2196 | #if defined(DXX_BUILD_DESCENT_II) |
||
2197 | // Your shots won't track the buddy. |
||
2198 | if (parent.type == OBJ_PLAYER) |
||
2199 | if (Robot_info[get_robot_id(curobjp)].companion) |
||
2200 | continue; |
||
2201 | #endif |
||
2202 | } |
||
2203 | |||
2204 | const auto &&dist = vm_vec_dist2(objp->pos, curobjp->pos); |
||
2205 | if (dist < MAX_SMART_DISTANCE_SQUARED) |
||
2206 | { |
||
2207 | int oovis; |
||
2208 | |||
2209 | oovis = object_to_object_visibility(objp, curobjp, FQ_TRANSWALL); |
||
2210 | |||
2211 | if (oovis) { |
||
2212 | objlist[numobjs] = curobjp; |
||
2213 | numobjs++; |
||
2214 | if (numobjs >= MAX_OBJDISTS) { |
||
2215 | numobjs = MAX_OBJDISTS; |
||
2216 | break; |
||
2217 | } |
||
2218 | } |
||
2219 | } |
||
2220 | } |
||
2221 | } |
||
2222 | |||
2223 | // Get type of weapon for child from parent. |
||
2224 | #if defined(DXX_BUILD_DESCENT_I) |
||
2225 | if (parent.type == OBJ_PLAYER) |
||
2226 | { |
||
2227 | blob_id = weapon_id_type::PLAYER_SMART_HOMING_ID; |
||
2228 | } else { |
||
2229 | blob_id = ((N_weapon_types<weapon_id_type::ROBOT_SMART_HOMING_ID)?(weapon_id_type::PLAYER_SMART_HOMING_ID):(weapon_id_type::ROBOT_SMART_HOMING_ID)); // NOTE: Shareware & reg 1.0 do not have their own Smart structure for bots. It was introduced in 1.4 to make Smart blobs from lvl 7 boss easier to dodge. So if we do not have this type, revert to player's Smart behaviour.., |
||
2230 | } |
||
2231 | #elif defined(DXX_BUILD_DESCENT_II) |
||
2232 | if (objp->type == OBJ_WEAPON) { |
||
2233 | blob_id = Weapon_info[get_weapon_id(objp)].children; |
||
2234 | Assert(blob_id != weapon_none); // Hmm, missing data in bitmaps.tbl. Need "children=NN" parameter. |
||
2235 | } else { |
||
2236 | Assert(objp->type == OBJ_ROBOT); |
||
2237 | blob_id = weapon_id_type::ROBOT_SMART_HOMING_ID; |
||
2238 | } |
||
2239 | #endif |
||
2240 | |||
2241 | objnum_t last_sel_objnum = object_none; |
||
2242 | const auto get_random_different_object = [&]{ |
||
2243 | for (;;) |
||
2244 | { |
||
2245 | const auto r = objlist[(d_rand() * numobjs) >> 15]; |
||
2246 | if (last_sel_objnum != r) |
||
2247 | return last_sel_objnum = r; |
||
2248 | } |
||
2249 | }; |
||
2250 | for (auto i = num_smart_children; i--;) |
||
2251 | { |
||
2252 | const auto &&sel_objnum = numobjs |
||
2253 | ? Objects.imptridx((numobjs == 1) |
||
2254 | ? objlist[0] |
||
2255 | : get_random_different_object()) |
||
2256 | : object_none; |
||
2257 | create_homing_missile(vmsegptridx, objp, sel_objnum, blob_id, (i==0)?1:0); |
||
2258 | } |
||
2259 | } |
||
2260 | } |
||
2261 | |||
2262 | void create_weapon_smart_children(const vmobjptridx_t objp) |
||
2263 | { |
||
2264 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2265 | const auto wid = get_weapon_id(objp); |
||
2266 | #if defined(DXX_BUILD_DESCENT_I) |
||
2267 | if (wid != weapon_id_type::SMART_ID) |
||
2268 | #elif defined(DXX_BUILD_DESCENT_II) |
||
2269 | if (Weapon_info[wid].children == weapon_id_type::unspecified) |
||
2270 | #endif |
||
2271 | return; |
||
2272 | #if defined(DXX_BUILD_DESCENT_II) |
||
2273 | if (wid == weapon_id_type::EARTHSHAKER_ID) |
||
2274 | blast_nearby_glass(objp, Weapon_info[weapon_id_type::EARTHSHAKER_ID].strength[GameUniqueState.Difficulty_level]); |
||
2275 | #endif |
||
2276 | create_smart_children(Objects, objp, NUM_SMART_CHILDREN, {objp->ctype.laser_info.parent_type, objp->ctype.laser_info.parent_num}); |
||
2277 | } |
||
2278 | |||
2279 | #if defined(DXX_BUILD_DESCENT_II) |
||
2280 | |||
2281 | void create_robot_smart_children(const vmobjptridx_t objp, const uint_fast32_t num_smart_children) |
||
2282 | { |
||
2283 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2284 | create_smart_children(Objects, objp, num_smart_children, {OBJ_ROBOT, objp}); |
||
2285 | } |
||
2286 | |||
2287 | //give up control of the guided missile |
||
2288 | void release_guided_missile(d_level_unique_object_state &LevelUniqueObjectState, const unsigned player_num) |
||
2289 | { |
||
2290 | if (player_num == Player_num) |
||
2291 | { |
||
2292 | const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, player_num); |
||
2293 | if (gimobj == nullptr) |
||
2294 | return; |
||
2295 | |||
2296 | Missile_viewer = gimobj; |
||
2297 | if (Game_mode & GM_MULTI) |
||
2298 | multi_send_guided_info(gimobj, 1); |
||
2299 | if (Newdemo_state==ND_STATE_RECORDING) |
||
2300 | newdemo_record_guided_end(); |
||
2301 | } |
||
2302 | LevelUniqueObjectState.Guided_missile.clear_player_active_guided_missile(player_num); |
||
2303 | } |
||
2304 | #endif |
||
2305 | |||
2306 | // ------------------------------------------------------------------------------------------- |
||
2307 | //changed on 31/3/10 by kreatordxx to distinguish between drop bomb and secondary fire |
||
2308 | void do_missile_firing(int drop_bomb) |
||
2309 | { |
||
2310 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2311 | auto &vmobjptr = Objects.vmptr; |
||
2312 | auto &vmobjptridx = Objects.vmptridx; |
||
2313 | int gun_flag=0; |
||
2314 | fix fire_frame_overhead = 0; |
||
2315 | |||
2316 | auto &plrobj = get_local_plrobj(); |
||
2317 | const auto bomb = which_bomb(); |
||
2318 | auto &player_info = plrobj.ctype.player_info; |
||
2319 | const auto weapon = drop_bomb ? bomb : player_info.Secondary_weapon; |
||
2320 | assert(weapon < MAX_SECONDARY_WEAPONS); |
||
2321 | auto &Next_missile_fire_time = player_info.Next_missile_fire_time; |
||
2322 | if (GameTime64 - Next_missile_fire_time <= FrameTime) // if firing is prolonged by FrameTime overhead, let's try to fix that. |
||
2323 | fire_frame_overhead = GameTime64 - Next_missile_fire_time; |
||
2324 | |||
2325 | #if defined(DXX_BUILD_DESCENT_II) |
||
2326 | const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, Player_num); |
||
2327 | if (gimobj != nullptr) |
||
2328 | { |
||
2329 | release_guided_missile(LevelUniqueObjectState, Player_num); |
||
2330 | Next_missile_fire_time = GameTime64 + Weapon_info[Secondary_weapon_to_weapon_info[weapon]].fire_wait - fire_frame_overhead; |
||
2331 | return; |
||
2332 | } |
||
2333 | #endif |
||
2334 | |||
2335 | if (Player_dead_state != player_dead_state::no) |
||
2336 | return; |
||
2337 | if (auto &secondary_weapon_ammo = player_info.secondary_ammo[weapon]) |
||
2338 | { |
||
2339 | |||
2340 | int weapon_gun; |
||
2341 | |||
2342 | const auto weapon_index = Secondary_weapon_to_weapon_info[weapon]; |
||
2343 | |||
2344 | if (!cheats.rapidfire) |
||
2345 | Next_missile_fire_time = GameTime64 + Weapon_info[weapon_index].fire_wait - fire_frame_overhead; |
||
2346 | else |
||
2347 | Next_missile_fire_time = GameTime64 + (F1_0/25) - fire_frame_overhead; |
||
2348 | |||
2349 | weapon_gun = Secondary_weapon_to_gun_num[weapon]; |
||
2350 | |||
2351 | if (weapon_gun==4) { //alternate left/right |
||
2352 | auto &Missile_gun = get_local_plrobj().ctype.player_info.missile_gun; |
||
2353 | weapon_gun += (gun_flag = (Missile_gun & 1)); |
||
2354 | Missile_gun++; |
||
2355 | } |
||
2356 | |||
2357 | const auto &&objnum = Laser_player_fire(vmobjptridx(ConsoleObject), weapon_index, weapon_gun, 1, get_local_plrobj().orient.fvec, object_none); |
||
2358 | if (objnum != object_none) |
||
2359 | -- secondary_weapon_ammo; |
||
2360 | |||
2361 | if (weapon != CONCUSSION_INDEX) |
||
2362 | maybe_drop_net_powerup(Secondary_weapon_to_powerup[weapon], 1, 0); |
||
2363 | |||
2364 | #if defined(DXX_BUILD_DESCENT_I) |
||
2365 | if (weapon == MEGA_INDEX) |
||
2366 | #elif defined(DXX_BUILD_DESCENT_II) |
||
2367 | if (weapon == MEGA_INDEX || weapon == SMISSILE5_INDEX) |
||
2368 | #endif |
||
2369 | { |
||
2370 | vms_vector force_vec; |
||
2371 | |||
2372 | const auto &&console = vmobjptr(ConsoleObject); |
||
2373 | force_vec.x = -(ConsoleObject->orient.fvec.x << 7); |
||
2374 | force_vec.y = -(ConsoleObject->orient.fvec.y << 7); |
||
2375 | force_vec.z = -(ConsoleObject->orient.fvec.z << 7); |
||
2376 | phys_apply_force(console, force_vec); |
||
2377 | |||
2378 | force_vec.x = (force_vec.x >> 4) + d_rand() - 16384; |
||
2379 | force_vec.y = (force_vec.y >> 4) + d_rand() - 16384; |
||
2380 | force_vec.z = (force_vec.z >> 4) + d_rand() - 16384; |
||
2381 | phys_apply_rot(console, force_vec); |
||
2382 | } |
||
2383 | |||
2384 | if (Game_mode & GM_MULTI) |
||
2385 | { |
||
2386 | multi_send_fire(weapon+MISSILE_ADJUST, 0, gun_flag, 1, objnum == object_none ? object_none : objnum->ctype.laser_info.track_goal, weapon_index_is_player_bomb(weapon) ? objnum : object_none); |
||
2387 | } |
||
2388 | |||
2389 | // don't autoselect if dropping prox and prox not current weapon |
||
2390 | if (!drop_bomb || player_info.Secondary_weapon == bomb) |
||
2391 | auto_select_secondary_weapon(player_info); //select next missile, if this one out of ammo |
||
2392 | } |
||
2393 | } |
||
2394 | } |