Subversion Repositories Games.Descent

Rev

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
}