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
 * Functions for weapons...
23
 *
24
 */
25
 
26
#include <stdexcept>
27
#include <stdlib.h>
28
#include <stdio.h>
29
#include <string.h>
30
#include <type_traits>
31
 
32
#include "hudmsg.h"
33
#include "game.h"
34
#include "laser.h"
35
#include "weapon.h"
36
#include "player.h"
37
#include "gauges.h"
38
#include "dxxerror.h"
39
#include "sounds.h"
40
#include "text.h"
41
#include "powerup.h"
42
#include "fireball.h"
43
#include "newdemo.h"
44
#include "multi.h"
45
#include "object.h"
46
#include "segment.h"
47
#include "newmenu.h"
48
#include "gamemine.h"
49
#include "ai.h"
50
#include "args.h"
51
#include "playsave.h"
52
#include "physfs-serial.h"
53
 
54
#include "compiler-range_for.h"
55
#include "partial_range.h"
56
 
57
static uint_fast32_t POrderList (uint_fast32_t num);
58
static uint_fast32_t SOrderList (uint_fast32_t num);
59
//      Note, only Vulcan cannon requires ammo.
60
// NOTE: Now Vulcan and Gauss require ammo. -5/3/95 Yuan
61
//ubyte Default_primary_ammo_level[MAX_PRIMARY_WEAPONS] = {255, 0, 255, 255, 255};
62
//ubyte Default_secondary_ammo_level[MAX_SECONDARY_WEAPONS] = {3, 0, 0, 0, 0};
63
 
64
constexpr std::integral_constant<uint8_t, 1> has_weapon_result::has_weapon_flag;
65
constexpr std::integral_constant<uint8_t, 2> has_weapon_result::has_energy_flag;
66
constexpr std::integral_constant<uint8_t, 4> has_weapon_result::has_ammo_flag;
67
 
68
//      Convert primary weapons to indices in Weapon_info array.
69
#if defined(DXX_BUILD_DESCENT_I)
70
namespace dsx {
71
const std::array<weapon_id_type, MAX_PRIMARY_WEAPONS> Primary_weapon_to_weapon_info{{
72
        weapon_id_type::LASER_ID, weapon_id_type::VULCAN_ID, weapon_id_type::CHEAP_SPREADFIRE_ID, weapon_id_type::PLASMA_ID, weapon_id_type::FUSION_ID
73
}};
74
const std::array<weapon_id_type, MAX_SECONDARY_WEAPONS> Secondary_weapon_to_weapon_info{{weapon_id_type::CONCUSSION_ID, weapon_id_type::HOMING_ID, weapon_id_type::PROXIMITY_ID, weapon_id_type::SMART_ID, weapon_id_type::MEGA_ID}};
75
 
76
//for each Secondary weapon, which gun it fires out of
77
const std::array<ubyte, MAX_SECONDARY_WEAPONS> Secondary_weapon_to_gun_num{{4,4,7,7,7}};
78
}
79
#elif defined(DXX_BUILD_DESCENT_II)
80
#include "fvi.h"
81
namespace dsx {
82
const std::array<weapon_id_type, MAX_PRIMARY_WEAPONS> Primary_weapon_to_weapon_info{{
83
        weapon_id_type::LASER_ID, weapon_id_type::VULCAN_ID, weapon_id_type::SPREADFIRE_ID, weapon_id_type::PLASMA_ID, weapon_id_type::FUSION_ID,
84
        weapon_id_type::SUPER_LASER_ID, weapon_id_type::GAUSS_ID, weapon_id_type::HELIX_ID, weapon_id_type::PHOENIX_ID, weapon_id_type::OMEGA_ID
85
}};
86
const std::array<weapon_id_type, MAX_SECONDARY_WEAPONS> Secondary_weapon_to_weapon_info{{
87
        weapon_id_type::CONCUSSION_ID, weapon_id_type::HOMING_ID, weapon_id_type::PROXIMITY_ID, weapon_id_type::SMART_ID, weapon_id_type::MEGA_ID,
88
        weapon_id_type::FLASH_ID, weapon_id_type::GUIDEDMISS_ID, weapon_id_type::SUPERPROX_ID, weapon_id_type::MERCURY_ID, weapon_id_type::EARTHSHAKER_ID
89
}};
90
 
91
//for each Secondary weapon, which gun it fires out of
92
const std::array<ubyte, MAX_SECONDARY_WEAPONS> Secondary_weapon_to_gun_num{{4,4,7,7,7,4,4,7,4,7}};
93
}
94
#endif
95
 
96
namespace dsx {
97
const std::array<uint8_t, MAX_SECONDARY_WEAPONS> Secondary_ammo_max{{20, 10, 10, 5, 5,
98
#if defined(DXX_BUILD_DESCENT_II)
99
        20, 20, 15, 10, 10
100
#endif
101
}};
102
 
103
//for each primary weapon, what kind of powerup gives weapon
104
const std::array<powerup_type_t, MAX_PRIMARY_WEAPONS> Primary_weapon_to_powerup{{POW_LASER,POW_VULCAN_WEAPON,POW_SPREADFIRE_WEAPON,POW_PLASMA_WEAPON,POW_FUSION_WEAPON,
105
#if defined(DXX_BUILD_DESCENT_II)
106
        POW_LASER,POW_GAUSS_WEAPON,POW_HELIX_WEAPON,POW_PHOENIX_WEAPON,POW_OMEGA_WEAPON
107
#endif
108
}};
109
 
110
//for each Secondary weapon, what kind of powerup gives weapon
111
const std::array<powerup_type_t, MAX_SECONDARY_WEAPONS> Secondary_weapon_to_powerup{{POW_MISSILE_1,POW_HOMING_AMMO_1,POW_PROXIMITY_WEAPON,POW_SMARTBOMB_WEAPON,POW_MEGA_WEAPON,
112
#if defined(DXX_BUILD_DESCENT_II)
113
        POW_SMISSILE1_1,POW_GUIDED_MISSILE_1,POW_SMART_MINE,POW_MERCURY_MISSILE_1,POW_EARTHSHAKER_MISSILE
114
#endif
115
}};
116
 
117
weapon_info_array Weapon_info;
118
}
119
namespace dcx {
120
unsigned N_weapon_types;
121
}
122
 
123
// autoselect ordering
124
 
125
namespace dsx {
126
#if defined(DXX_BUILD_DESCENT_I)
127
constexpr std::array<uint8_t, MAX_PRIMARY_WEAPONS + 1> DefaultPrimaryOrder{{ 4, 3, 2, 1, 0, 255 }};
128
constexpr std::array<uint8_t, MAX_SECONDARY_WEAPONS + 1> DefaultSecondaryOrder{{ 4, 3, 1, 0, 255, 2 }};
129
#elif defined(DXX_BUILD_DESCENT_II)
130
constexpr std::array<uint8_t, MAX_PRIMARY_WEAPONS + 1> DefaultPrimaryOrder={{9,8,7,6,5,4,3,2,1,0,255}};
131
constexpr std::array<uint8_t, MAX_SECONDARY_WEAPONS + 1> DefaultSecondaryOrder={{9,8,4,3,1,5,0,255,7,6,2}};
132
 
133
//flags whether the last time we use this weapon, it was the 'super' version
134
#endif
135
 
136
static unsigned get_mapped_weapon_index(const player_info &player_info, const primary_weapon_index_t weapon_index)
137
{
138
#if defined(DXX_BUILD_DESCENT_I)
139
        (void)player_info;
140
#elif defined(DXX_BUILD_DESCENT_II)
141
        if (weapon_index == primary_weapon_index_t::LASER_INDEX && player_info.laser_level > MAX_LASER_LEVEL)
142
                return primary_weapon_index_t::SUPER_LASER_INDEX;
143
#endif
144
        return weapon_index;
145
}
146
}
147
 
148
// ; (0) Laser Level 1
149
// ; (1) Laser Level 2
150
// ; (2) Laser Level 3
151
// ; (3) Laser Level 4
152
// ; (4) Unknown Use
153
// ; (5) Josh Blobs
154
// ; (6) Unknown Use
155
// ; (7) Unknown Use
156
// ; (8) ---------- Concussion Missile ----------
157
// ; (9) ---------- Flare ----------
158
// ; (10) ---------- Blue laser that blue guy shoots -----------
159
// ; (11) ---------- Vulcan Cannon ----------
160
// ; (12) ---------- Spreadfire Cannon ----------
161
// ; (13) ---------- Plasma Cannon ----------
162
// ; (14) ---------- Fusion Cannon ----------
163
// ; (15) ---------- Homing Missile ----------
164
// ; (16) ---------- Proximity Bomb ----------
165
// ; (17) ---------- Smart Missile ----------
166
// ; (18) ---------- Mega Missile ----------
167
// ; (19) ---------- Children of the PLAYER'S Smart Missile ----------
168
// ; (20) ---------- Bad Guy Spreadfire Laser ----------
169
// ; (21) ---------- SuperMech Homing Missile ----------
170
// ; (22) ---------- Regular Mech's missile -----------
171
// ; (23) ---------- Silent Spreadfire Laser ----------
172
// ; (24) ---------- Red laser that baby spiders shoot -----------
173
// ; (25) ---------- Green laser that rifleman shoots -----------
174
// ; (26) ---------- Plasma gun that 'plasguy' fires ------------
175
// ; (27) ---------- Blobs fired by Red Spiders -----------
176
// ; (28) ---------- Final Boss's Mega Missile ----------
177
// ; (29) ---------- Children of the ROBOT'S Smart Missile ----------
178
// ; (30) Laser Level 5
179
// ; (31) Laser Level 6
180
// ; (32) ---------- Super Vulcan Cannon ----------
181
// ; (33) ---------- Super Spreadfire Cannon ----------
182
// ; (34) ---------- Super Plasma Cannon ----------
183
// ; (35) ---------- Super Fusion Cannon ----------
184
 
185
//      ------------------------------------------------------------------------------------
186
//      Return:
187
// Bits set:
188
//              HAS_WEAPON_FLAG
189
//              HAS_ENERGY_FLAG
190
//              HAS_AMMO_FLAG
191
// See weapon.h for bit values
192
namespace dsx {
193
has_weapon_result player_has_primary_weapon(const player_info &player_info, int weapon_num)
194
{
195
        int     return_value = 0;
196
 
197
        //      Hack! If energy goes negative, you can't fire a weapon that doesn't require energy.
198
        //      But energy should not go negative (but it does), so find out why it does!
199
        auto &energy = player_info.energy;
200
 
201
        const auto weapon_index = Primary_weapon_to_weapon_info[weapon_num];
202
 
203
        if (player_info.primary_weapon_flags & HAS_PRIMARY_FLAG(weapon_num))
204
                        return_value |= has_weapon_result::has_weapon_flag;
205
 
206
                // Special case: Gauss cannon uses vulcan ammo.
207
                if (weapon_index_uses_vulcan_ammo(weapon_num)) {
208
                        if (Weapon_info[weapon_index].ammo_usage <= player_info.vulcan_ammo)
209
                                return_value |= has_weapon_result::has_ammo_flag;
210
                }
211
                /* Hack to work around check in do_primary_weapon_select */
212
                else
213
                        return_value |= has_weapon_result::has_ammo_flag;
214
 
215
#if defined(DXX_BUILD_DESCENT_I)
216
                //added on 1/21/99 by Victor Rachels... yet another hack
217
                //fusion has 0 energy usage, HAS_ENERGY_FLAG was always true
218
                if(weapon_num == primary_weapon_index_t::FUSION_INDEX)
219
                {
220
                        if (energy >= F1_0*2)
221
                                return_value |= has_weapon_result::has_energy_flag;
222
                }
223
#elif defined(DXX_BUILD_DESCENT_II)
224
                if (weapon_num == primary_weapon_index_t::OMEGA_INDEX) {        // Hack: Make sure player has energy to omega
225
                        if (energy > 0 || player_info.Omega_charge)
226
                                return_value |= has_weapon_result::has_energy_flag;
227
                }
228
#endif
229
                else
230
                {
231
                        const auto energy_usage = Weapon_info[weapon_index].energy_usage;
232
                        /* The test for `energy_usage <= 0` should not be needed.
233
                         * However, a Parallax comment suggests that players
234
                         * sometimes get negative energy.  Use this test in
235
                         * preference to coercing negative player energy to zero.
236
                         */
237
                        if (energy_usage <= 0 || energy_usage <= energy)
238
                                return_value |= has_weapon_result::has_energy_flag;
239
                }
240
        return return_value;
241
}
242
 
243
has_weapon_result player_has_secondary_weapon(const player_info &player_info, const secondary_weapon_index_t weapon_num)
244
{
245
        int     return_value = 0;
246
        const auto secondary_ammo = player_info.secondary_ammo[weapon_num];
247
        const auto weapon_index = Secondary_weapon_to_weapon_info[weapon_num];
248
        if (secondary_ammo && Weapon_info[weapon_index].ammo_usage <= secondary_ammo)
249
                return_value = has_weapon_result::has_weapon_flag | has_weapon_result::has_energy_flag | has_weapon_result::has_ammo_flag;
250
        return return_value;
251
}
252
 
253
void InitWeaponOrdering ()
254
 {
255
  // short routine to setup default weapon priorities for new pilots
256
        PlayerCfg.PrimaryOrder = DefaultPrimaryOrder;
257
        PlayerCfg.SecondaryOrder = DefaultSecondaryOrder;
258
 }
259
 
260
namespace {
261
 
262
class cycle_weapon_state
263
{
264
public:
265
        static constexpr std::integral_constant<uint8_t, 255> cycle_never_autoselect_below{};
266
        static constexpr char DXX_WEAPON_TEXT_NEVER_AUTOSELECT[] = "--- Never autoselect below ---";
267
        __attribute_cold
268
        __attribute_noreturn
269
        static void report_runtime_error(const char *);
270
};
271
 
272
class cycle_primary_state : public cycle_weapon_state
273
{
274
        using weapon_index_type = primary_weapon_index_t;
275
        player_info &pl_info;
276
public:
277
        cycle_primary_state(player_info &p) :
278
                pl_info(p)
279
        {
280
        }
281
        static constexpr std::integral_constant<uint_fast32_t, MAX_PRIMARY_WEAPONS> max_weapons{};
282
        static constexpr char reorder_title[] = "Reorder Primary";
283
        static constexpr char error_weapon_list_corrupt[] = "primary weapon list corrupt";
284
        static uint_fast32_t get_cycle_position(uint_fast32_t i)
285
        {
286
                return POrderList(i);
287
        }
288
        static player_config::primary_weapon_order::reference get_weapon_by_order_slot(player_config::primary_weapon_order::size_type cur_order_slot)
289
        {
290
                return PlayerCfg.PrimaryOrder[cur_order_slot];
291
        }
292
        bool maybe_select_weapon_by_order_slot(uint_fast32_t cur_order_slot) const
293
        {
294
                return maybe_select_weapon_by_type(get_weapon_by_order_slot(cur_order_slot));
295
        }
296
        bool maybe_select_weapon_by_type(const uint_fast32_t desired_weapon_idx) const
297
        {
298
                weapon_index_type desired_weapon = static_cast<weapon_index_type>(desired_weapon_idx);
299
#if defined(DXX_BUILD_DESCENT_II)
300
                // some remapping for SUPER LASER which is not an actual weapon type at all
301
                if (desired_weapon == primary_weapon_index_t::LASER_INDEX)
302
                {
303
                        if (pl_info.laser_level > MAX_LASER_LEVEL)
304
                                return false;
305
                }
306
                else if (desired_weapon == primary_weapon_index_t::SUPER_LASER_INDEX)
307
                {
308
                        if (pl_info.laser_level <= MAX_LASER_LEVEL)
309
                                return false;
310
                        else
311
                                desired_weapon = primary_weapon_index_t::LASER_INDEX;
312
                }
313
#endif
314
                if (!player_has_primary_weapon(pl_info, desired_weapon).has_all())
315
                        return false;
316
                select_primary_weapon(pl_info, PRIMARY_WEAPON_NAMES(desired_weapon), desired_weapon, 1);
317
                return true;
318
        }
319
        void abandon_auto_select()
320
        {
321
                HUD_init_message_literal(HM_DEFAULT, TXT_NO_PRIMARY);
322
                if (pl_info.Primary_weapon == primary_weapon_index_t::LASER_INDEX)
323
                        return;
324
                select_primary_weapon(pl_info, nullptr, primary_weapon_index_t::LASER_INDEX, 1);
325
        }
326
        static const char *get_weapon_name(uint8_t i)
327
        {
328
                return i == cycle_never_autoselect_below ? DXX_WEAPON_TEXT_NEVER_AUTOSELECT : PRIMARY_WEAPON_NAMES(i);
329
        }
330
};
331
 
332
class cycle_secondary_state : public cycle_weapon_state
333
{
334
        using weapon_index_type = secondary_weapon_index_t;
335
        player_info &pl_info;
336
public:
337
        cycle_secondary_state(player_info &p) :
338
                pl_info(p)
339
        {
340
        }
341
        static constexpr std::integral_constant<uint_fast32_t, MAX_SECONDARY_WEAPONS> max_weapons{};
342
        static constexpr char reorder_title[] = "Reorder Secondary";
343
        static constexpr char error_weapon_list_corrupt[] = "secondary weapon list corrupt";
344
        static uint_fast32_t get_cycle_position(uint_fast32_t i)
345
        {
346
                return SOrderList(i);
347
        }
348
        static player_config::secondary_weapon_order::reference get_weapon_by_order_slot(player_config::secondary_weapon_order::size_type cur_order_slot)
349
        {
350
                return PlayerCfg.SecondaryOrder[cur_order_slot];
351
        }
352
        bool maybe_select_weapon_by_order_slot(uint_fast32_t cur_order_slot)
353
        {
354
                return maybe_select_weapon_by_type(get_weapon_by_order_slot(cur_order_slot));
355
        }
356
        bool maybe_select_weapon_by_type(const uint_fast32_t desired_weapon_idx)
357
        {
358
                const weapon_index_type desired_weapon = static_cast<weapon_index_type>(desired_weapon_idx);
359
                if (!player_has_secondary_weapon(pl_info, desired_weapon).has_all())
360
                        return false;
361
                select_secondary_weapon(pl_info, SECONDARY_WEAPON_NAMES(desired_weapon), desired_weapon, 1);
362
                return true;
363
        }
364
        static void abandon_auto_select()
365
        {
366
                HUD_init_message_literal(HM_DEFAULT, "No secondary weapons available!");
367
        }
368
        static const char *get_weapon_name(uint8_t i)
369
        {
370
                return i == cycle_never_autoselect_below ? DXX_WEAPON_TEXT_NEVER_AUTOSELECT : SECONDARY_WEAPON_NAMES(i);
371
        }
372
};
373
 
374
constexpr std::integral_constant<uint8_t, 255> cycle_weapon_state::cycle_never_autoselect_below;
375
constexpr char cycle_weapon_state::DXX_WEAPON_TEXT_NEVER_AUTOSELECT[];
376
constexpr std::integral_constant<uint_fast32_t, MAX_PRIMARY_WEAPONS> cycle_primary_state::max_weapons;
377
constexpr char cycle_primary_state::reorder_title[];
378
constexpr char cycle_primary_state::error_weapon_list_corrupt[];
379
constexpr std::integral_constant<uint_fast32_t, MAX_SECONDARY_WEAPONS> cycle_secondary_state::max_weapons;
380
constexpr char cycle_secondary_state::reorder_title[];
381
constexpr char cycle_secondary_state::error_weapon_list_corrupt[];
382
 
383
void cycle_weapon_state::report_runtime_error(const char *const p)
384
{
385
        throw std::runtime_error(p);
386
}
387
 
388
template <typename T>
389
void CycleWeapon(T t, const uint_fast32_t effective_weapon)
390
{
391
        auto cur_order_slot = t.get_cycle_position(effective_weapon);
392
        const auto autoselect_order_slot = t.get_cycle_position(t.cycle_never_autoselect_below);
393
        const auto use_restricted_autoselect =
394
                (cur_order_slot < autoselect_order_slot) &&
395
                (1 < autoselect_order_slot) &&
396
                PlayerCfg.CycleAutoselectOnly;
397
        for (uint_fast32_t loop = t.max_weapons + 1; loop--;)
398
        {
399
                cur_order_slot++; // next slot
400
                if (cur_order_slot >= t.max_weapons + 1) // loop if necessary
401
                        cur_order_slot = 0;
402
                if (cur_order_slot == autoselect_order_slot) // what to to with non-autoselect weapons?
403
                {
404
                        if (use_restricted_autoselect)
405
                        {
406
                                cur_order_slot = 0; // loop over or ...
407
                        }
408
                        else
409
                        {
410
                                continue; // continue?
411
                        }
412
                }
413
                if (t.maybe_select_weapon_by_order_slot(cur_order_slot)) // now that is the weapon next to our current one
414
                // select the weapon if we have it
415
                        return;
416
        }
417
}
418
 
419
}
420
 
421
void CyclePrimary(player_info &player_info)
422
{
423
        CycleWeapon(cycle_primary_state(player_info), get_mapped_weapon_index(player_info, player_info.Primary_weapon));
424
}
425
 
426
void CycleSecondary(player_info &player_info)
427
{
428
        CycleWeapon(cycle_secondary_state(player_info), player_info.Secondary_weapon);
429
}
430
 
431
#if defined(DXX_BUILD_DESCENT_II)
432
static inline void set_weapon_last_was_super(uint8_t &last, const uint8_t mask, const bool is_super)
433
{
434
        if (is_super)
435
                last |= mask;
436
        else
437
                last &= ~mask;
438
}
439
 
440
static inline void set_weapon_last_was_super(uint8_t &last, const uint_fast32_t weapon_num)
441
{
442
        const bool is_super = weapon_num >= SUPER_WEAPON;
443
        set_weapon_last_was_super(last, 1 << (is_super ? weapon_num - SUPER_WEAPON : weapon_num), is_super);
444
}
445
#endif
446
 
447
void set_primary_weapon(player_info &player_info, const uint_fast32_t weapon_num)
448
{
449
        if (Newdemo_state == ND_STATE_RECORDING)
450
                newdemo_record_player_weapon(0, weapon_num);
451
        player_info.Fusion_charge=0;
452
        player_info.Next_laser_fire_time = 0;
453
        player_info.Primary_weapon = static_cast<primary_weapon_index_t>(weapon_num);
454
#if defined(DXX_BUILD_DESCENT_II)
455
        //save flag for whether was super version
456
        auto &Primary_last_was_super = player_info.Primary_last_was_super;
457
        set_weapon_last_was_super(Primary_last_was_super, weapon_num);
458
#endif
459
}
460
 
461
//      ------------------------------------------------------------------------------------
462
//if message flag set, print message saying selected
463
void select_primary_weapon(player_info &player_info, const char *const weapon_name, const uint_fast32_t weapon_num, const int wait_for_rearm)
464
{
465
        if (Newdemo_state==ND_STATE_RECORDING )
466
                newdemo_record_player_weapon(0, weapon_num);
467
 
468
        {
469
                auto &Primary_weapon = player_info.Primary_weapon;
470
                if (Primary_weapon != weapon_num) {
471
                        Primary_weapon = static_cast<primary_weapon_index_t>(weapon_num);
472
#ifndef FUSION_KEEPS_CHARGE
473
                        //added 8/6/98 by Victor Rachels to fix fusion charge bug
474
                        player_info.Fusion_charge=0;
475
                        //end edit - Victor Rachels
476
#endif
477
                        auto &Next_laser_fire_time = player_info.Next_laser_fire_time;
478
                        if (wait_for_rearm)
479
                        {
480
                                multi_digi_play_sample_once(SOUND_GOOD_SELECTION_PRIMARY, F1_0);
481
                                Next_laser_fire_time = GameTime64 + REARM_TIME;
482
                        }
483
                        else
484
                                Next_laser_fire_time = 0;
485
                }
486
                else
487
                {
488
#if defined(DXX_BUILD_DESCENT_I)
489
                        if (wait_for_rearm)
490
                                /*
491
                                 * In Descent 1, requesting a weapon that is already
492
                                 * armed is pointless, so play a warning sound.
493
                                 *
494
                                 * In Descent 2, the player may be trying to toggle
495
                                 * between the base and super forms of a weapon, so do
496
                                 * not generate a warning sound in that case.
497
                                 */
498
                                digi_play_sample(SOUND_ALREADY_SELECTED, F1_0);
499
#endif
500
                }
501
#if defined(DXX_BUILD_DESCENT_II)
502
                //save flag for whether was super version
503
                set_weapon_last_was_super(player_info.Primary_last_was_super, weapon_num);
504
#endif
505
        }
506
        if (weapon_name)
507
        {
508
#if defined(DXX_BUILD_DESCENT_II)
509
                if (weapon_num == primary_weapon_index_t::LASER_INDEX)
510
                        HUD_init_message(HM_DEFAULT, "%s Level %d %s", weapon_name, player_info.laser_level+1, TXT_SELECTED);
511
                else
512
#endif
513
                        HUD_init_message(HM_DEFAULT, "%s %s", weapon_name, TXT_SELECTED);
514
        }
515
 
516
}
517
 
518
void set_secondary_weapon_to_concussion(player_info &player_info)
519
{
520
        const uint_fast32_t weapon_num = 0;
521
        if (Newdemo_state == ND_STATE_RECORDING)
522
                newdemo_record_player_weapon(1, weapon_num);
523
 
524
        player_info.Next_missile_fire_time = 0;
525
        Global_missile_firing_count = 0;
526
        player_info.Secondary_weapon = static_cast<secondary_weapon_index_t>(weapon_num);
527
#if defined(DXX_BUILD_DESCENT_II)
528
        //save flag for whether was super version
529
        set_weapon_last_was_super(player_info.Secondary_last_was_super, weapon_num);
530
#endif
531
}
532
 
533
void select_secondary_weapon(player_info &player_info, const char *const weapon_name, const uint_fast32_t weapon_num, const int wait_for_rearm)
534
{
535
        if (Newdemo_state==ND_STATE_RECORDING )
536
                newdemo_record_player_weapon(1, weapon_num);
537
 
538
        {
539
                auto &Secondary_weapon = player_info.Secondary_weapon;
540
                if (Secondary_weapon != weapon_num) {
541
                        auto &Next_missile_fire_time = player_info.Next_missile_fire_time;
542
                        if (wait_for_rearm)
543
                        {
544
                                multi_digi_play_sample_once(SOUND_GOOD_SELECTION_SECONDARY, F1_0);
545
                                Next_missile_fire_time = GameTime64 + REARM_TIME;
546
                        }
547
                        else
548
                                Next_missile_fire_time = 0;
549
                        Global_missile_firing_count = 0;
550
                } else  {
551
                        if (wait_for_rearm)
552
                        {
553
                                digi_play_sample_once( SOUND_ALREADY_SELECTED, F1_0 );
554
                        }
555
 
556
                }
557
                Secondary_weapon = static_cast<secondary_weapon_index_t>(weapon_num);
558
#if defined(DXX_BUILD_DESCENT_II)
559
                //save flag for whether was super version
560
                set_weapon_last_was_super(player_info.Secondary_last_was_super, weapon_num);
561
#endif
562
        }
563
        if (weapon_name)
564
        {
565
                HUD_init_message(HM_DEFAULT, "%s %s", weapon_name, TXT_SELECTED);
566
        }
567
}
568
}
569
 
570
#if defined(DXX_BUILD_DESCENT_I)
571
static bool reject_shareware_weapon_select(const uint_fast32_t weapon_num, const char *const weapon_name)
572
{
573
        // do special hud msg. for picking registered weapon in shareware version.
574
        if (PCSharePig)
575
                if (weapon_num >= NUM_SHAREWARE_WEAPONS) {
576
                        HUD_init_message(HM_DEFAULT, "%s %s!", weapon_name,TXT_NOT_IN_SHAREWARE);
577
                        return true;
578
                }
579
        return false;
580
}
581
 
582
static bool reject_unusable_primary_weapon_select(const player_info &player_info, const uint_fast32_t weapon_num, const char *const weapon_name)
583
{
584
        const auto weapon_status = player_has_primary_weapon(player_info, weapon_num);
585
        const char *prefix;
586
        if (!weapon_status.has_weapon())
587
                prefix = TXT_DONT_HAVE;
588
        else if (!weapon_status.has_ammo())
589
                prefix = TXT_DONT_HAVE_AMMO;
590
        else
591
                return false;
592
        HUD_init_message(HM_DEFAULT, "%s %s!", prefix, weapon_name);
593
        return true;
594
}
595
 
596
static bool reject_unusable_secondary_weapon_select(const player_info &player_info, const secondary_weapon_index_t weapon_num, const char *const weapon_name)
597
{
598
        const auto weapon_status = player_has_secondary_weapon(player_info, weapon_num);
599
        if (weapon_status.has_all())
600
                return false;
601
        HUD_init_message(HM_DEFAULT, "%s %s%s", TXT_HAVE_NO, weapon_name, TXT_SX);
602
        return true;
603
}
604
#endif
605
 
606
//      ------------------------------------------------------------------------------------
607
//      Select a weapon, primary or secondary.
608
namespace dsx {
609
void do_primary_weapon_select(player_info &player_info, uint_fast32_t weapon_num)
610
{
611
#if defined(DXX_BUILD_DESCENT_I)
612
        //added on 10/9/98 by Victor Rachels to add laser cycle
613
        //end this section addition - Victor Rachels
614
        const auto weapon_name = PRIMARY_WEAPON_NAMES(weapon_num);
615
        if (reject_shareware_weapon_select(weapon_num, weapon_name) || reject_unusable_primary_weapon_select(player_info, weapon_num, weapon_name))
616
        {
617
                digi_play_sample(SOUND_BAD_SELECTION, F1_0);
618
                return;
619
        }
620
#elif defined(DXX_BUILD_DESCENT_II)
621
        has_weapon_result weapon_status;
622
 
623
        auto &Primary_weapon = player_info.Primary_weapon;
624
        const auto current = Primary_weapon.get_active();
625
        const auto last_was_super = player_info.Primary_last_was_super & (1 << weapon_num);
626
        const auto has_flag = weapon_status.has_weapon_flag;
627
 
628
        if (current == weapon_num || current == weapon_num+SUPER_WEAPON) {
629
 
630
                //already have this selected, so toggle to other of normal/super version
631
 
632
                weapon_num += weapon_num+SUPER_WEAPON - current;
633
                weapon_status = player_has_primary_weapon(player_info, weapon_num);
634
        }
635
        else {
636
                const auto weapon_num_save = weapon_num;
637
 
638
                //go to last-select version of requested missile
639
 
640
                if (last_was_super)
641
                        weapon_num += SUPER_WEAPON;
642
 
643
                weapon_status = player_has_primary_weapon(player_info, weapon_num);
644
 
645
                //if don't have last-selected, try other version
646
 
647
                if ((weapon_status.flags() & has_flag) != has_flag) {
648
                        weapon_num = 2*weapon_num_save+SUPER_WEAPON - weapon_num;
649
                        weapon_status = player_has_primary_weapon(player_info, weapon_num);
650
                        if ((weapon_status.flags() & has_flag) != has_flag)
651
                                weapon_num = 2*weapon_num_save+SUPER_WEAPON - weapon_num;
652
                }
653
        }
654
 
655
        //if we don't have the weapon we're switching to, give error & bail
656
        const auto weapon_name = PRIMARY_WEAPON_NAMES(weapon_num);
657
        if ((weapon_status.flags() & has_flag) != has_flag) {
658
                {
659
                        if (weapon_num == primary_weapon_index_t::SUPER_LASER_INDEX)
660
                                return;                 //no such thing as super laser, so no error
661
                        HUD_init_message(HM_DEFAULT, "%s %s!", TXT_DONT_HAVE, weapon_name);
662
                }
663
                digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
664
                return;
665
        }
666
 
667
        //now actually select the weapon
668
#endif
669
        select_primary_weapon(player_info, weapon_name, weapon_num, 1);
670
}
671
 
672
void do_secondary_weapon_select(player_info &player_info, secondary_weapon_index_t weapon_num)
673
{
674
#if defined(DXX_BUILD_DESCENT_I)
675
        //added on 10/9/98 by Victor Rachels to add laser cycle
676
        //end this section addition - Victor Rachels
677
        // do special hud msg. for picking registered weapon in shareware version.
678
        const auto weapon_name = SECONDARY_WEAPON_NAMES(weapon_num);
679
        if (reject_shareware_weapon_select(weapon_num, weapon_name) || reject_unusable_secondary_weapon_select(player_info, weapon_num, weapon_name))
680
        {
681
                digi_play_sample(SOUND_BAD_SELECTION, F1_0);
682
                return;
683
        }
684
#elif defined(DXX_BUILD_DESCENT_II)
685
        has_weapon_result weapon_status;
686
 
687
        const auto current = player_info.Secondary_weapon.get_active();
688
        const auto last_was_super = player_info.Secondary_last_was_super & (1 << weapon_num);
689
        const auto has_flag = weapon_status.has_weapon_flag | weapon_status.has_ammo_flag;
690
 
691
        if (current == weapon_num || current == weapon_num+SUPER_WEAPON) {
692
 
693
                //already have this selected, so toggle to other of normal/super version
694
 
695
                weapon_num = static_cast<secondary_weapon_index_t>((static_cast<unsigned>(weapon_num) * 2) + SUPER_WEAPON - current);
696
                weapon_status = player_has_secondary_weapon(player_info, weapon_num);
697
        }
698
        else {
699
                const auto weapon_num_save = weapon_num;
700
 
701
                //go to last-select version of requested missile
702
 
703
                if (last_was_super)
704
                        weapon_num = static_cast<secondary_weapon_index_t>(static_cast<unsigned>(weapon_num) + SUPER_WEAPON);
705
 
706
                weapon_status = player_has_secondary_weapon(player_info, weapon_num);
707
 
708
                //if don't have last-selected, try other version
709
 
710
                if ((weapon_status.flags() & has_flag) != has_flag) {
711
                        weapon_num = static_cast<secondary_weapon_index_t>((static_cast<unsigned>(weapon_num_save) * 2) + SUPER_WEAPON - static_cast<unsigned>(weapon_num));
712
                        weapon_status = player_has_secondary_weapon(player_info, weapon_num);
713
                        if ((weapon_status.flags() & has_flag) != has_flag)
714
                                weapon_num = static_cast<secondary_weapon_index_t>((static_cast<unsigned>(weapon_num_save) * 2) + SUPER_WEAPON - static_cast<unsigned>(weapon_num));
715
                }
716
        }
717
 
718
        //if we don't have the weapon we're switching to, give error & bail
719
        const auto weapon_name = SECONDARY_WEAPON_NAMES(weapon_num);
720
        if ((weapon_status.flags() & has_flag) != has_flag) {
721
                HUD_init_message(HM_DEFAULT, "%s %s%s", TXT_HAVE_NO, weapon_name, TXT_SX);
722
                digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
723
                return;
724
        }
725
 
726
        //now actually select the weapon
727
#endif
728
        select_secondary_weapon(player_info, weapon_name, weapon_num, 1);
729
}
730
}
731
 
732
template <typename T>
733
void auto_select_weapon(T t)
734
{
735
        for (uint_fast32_t cur_order_slot = 0; cur_order_slot != t.max_weapons + 1; ++cur_order_slot)
736
        {
737
                const auto weapon_type = t.get_weapon_by_order_slot(cur_order_slot);
738
                if (weapon_type >= t.max_weapons)
739
                {
740
                        t.abandon_auto_select();
741
                        return;
742
                }
743
                if (t.maybe_select_weapon_by_type(weapon_type))
744
                        return;
745
        }
746
}
747
 
748
namespace dsx {
749
 
750
//      ----------------------------------------------------------------------------------------
751
//      Automatically select next best weapon if unable to fire current weapon.
752
// Weapon type: 0==primary, 1==secondary
753
void auto_select_primary_weapon(player_info &player_info)
754
{
755
        if (!player_has_primary_weapon(player_info, player_info.Primary_weapon).has_all())
756
                auto_select_weapon(cycle_primary_state(player_info));
757
}
758
 
759
void auto_select_secondary_weapon(player_info &player_info)
760
{
761
        if (!player_has_secondary_weapon(player_info, player_info.Secondary_weapon).has_all())
762
                auto_select_weapon(cycle_secondary_state(player_info));
763
}
764
 
765
void delayed_autoselect(player_info &player_info)
766
{
767
        if (!Controls.state.fire_primary)
768
        {
769
                auto &Primary_weapon = player_info.Primary_weapon;
770
                const auto primary_weapon = Primary_weapon.get_active();
771
                const auto delayed_primary = Primary_weapon.get_delayed();
772
                if (delayed_primary != primary_weapon)
773
                {
774
                        if (player_has_primary_weapon(player_info, delayed_primary).has_all())
775
                                select_primary_weapon(player_info, nullptr, delayed_primary, 1);
776
                        else
777
                                Primary_weapon.set_delayed(primary_weapon);
778
                }
779
        }
780
        if (!Controls.state.fire_secondary)
781
        {
782
                auto &Secondary_weapon = player_info.Secondary_weapon;
783
                const auto secondary_weapon = Secondary_weapon.get_active();
784
                const auto delayed_secondary = Secondary_weapon.get_delayed();
785
                if (delayed_secondary != secondary_weapon)
786
                {
787
                        if (player_has_secondary_weapon(player_info, delayed_secondary).has_all())
788
                                select_secondary_weapon(player_info, nullptr, delayed_secondary, 1);
789
                        else
790
                                Secondary_weapon.set_delayed(secondary_weapon);
791
                }
792
        }
793
}
794
 
795
static void maybe_autoselect_primary_weapon(player_info &player_info, int weapon_index)
796
{
797
        const auto want_switch = [weapon_index, &player_info]{
798
                const auto cutpoint = POrderList(255);
799
                const auto weapon_order = POrderList(weapon_index);
800
                return weapon_order < cutpoint && weapon_order < POrderList(get_mapped_weapon_index(player_info, player_info.Primary_weapon.get_delayed()));
801
        };
802
        if (Controls.state.fire_primary && PlayerCfg.NoFireAutoselect != FiringAutoselectMode::Immediate)
803
        {
804
                if (PlayerCfg.NoFireAutoselect == FiringAutoselectMode::Delayed)
805
                {
806
                        if (want_switch())
807
                                player_info.Primary_weapon.set_delayed(static_cast<primary_weapon_index_t>(weapon_index));
808
                }
809
        }
810
        else if (want_switch())
811
                select_primary_weapon(player_info, nullptr, weapon_index, 1);
812
}
813
 
814
//      ---------------------------------------------------------------------
815
//called when one of these weapons is picked up
816
//when you pick up a secondary, you always get the weapon & ammo for it
817
//      Returns true if powerup picked up, else returns false.
818
int pick_up_secondary(player_info &player_info, int weapon_index,int count)
819
{
820
        int     num_picked_up;
821
        const auto max = PLAYER_MAX_AMMO(player_info.powerup_flags, Secondary_ammo_max[weapon_index]);
822
        auto &secondary_ammo = player_info.secondary_ammo;
823
        if (secondary_ammo[weapon_index] >= max)
824
        {
825
                HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, "%s %i %ss!", TXT_ALREADY_HAVE, secondary_ammo[weapon_index], SECONDARY_WEAPON_NAMES(weapon_index));
826
                return 0;
827
        }
828
 
829
        secondary_ammo[weapon_index] += count;
830
 
831
        num_picked_up = count;
832
        if (secondary_ammo[weapon_index] > max)
833
        {
834
                num_picked_up = count - (secondary_ammo[weapon_index] - max);
835
                secondary_ammo[weapon_index] = max;
836
        }
837
 
838
        if (secondary_ammo[weapon_index] == count)      // only autoselect if player didn't have any
839
        {
840
                const auto weapon_order = SOrderList(weapon_index);
841
                auto &Secondary_weapon = player_info.Secondary_weapon;
842
                const auto want_switch = [weapon_order, &secondary_ammo, &Secondary_weapon]{
843
                        return weapon_order < SOrderList(255) && (
844
                                secondary_ammo[Secondary_weapon.get_delayed()] == 0 ||
845
                                weapon_order < SOrderList(Secondary_weapon.get_delayed())
846
                                );
847
                };
848
                if (Controls.state.fire_secondary && PlayerCfg.NoFireAutoselect != FiringAutoselectMode::Immediate)
849
                {
850
                        if (PlayerCfg.NoFireAutoselect == FiringAutoselectMode::Delayed)
851
                        {
852
                                if (want_switch())
853
                                        Secondary_weapon.set_delayed(static_cast<secondary_weapon_index_t>(weapon_index));
854
                        }
855
                }
856
                else if (want_switch())
857
                        select_secondary_weapon(player_info, nullptr, weapon_index, 1);
858
#if defined(DXX_BUILD_DESCENT_II)
859
                        //if it's a proxbomb or smart mine,
860
                        //we want to do a mini-auto-selection that applies to the drop bomb key
861
 
862
                        if (weapon_index_is_player_bomb(weapon_index) &&
863
                                !weapon_index_is_player_bomb(player_info.Secondary_weapon))
864
                        {
865
                                const auto mask = 1 << PROXIMITY_INDEX;
866
                                if (weapon_order < SOrderList((player_info.Secondary_last_was_super & mask) ? SMART_MINE_INDEX : PROXIMITY_INDEX))
867
                                        set_weapon_last_was_super(player_info.Secondary_last_was_super, mask, weapon_index == SMART_MINE_INDEX);
868
                        }
869
#endif
870
        }
871
 
872
        //note: flash for all but concussion was 7,14,21
873
        if (num_picked_up>1) {
874
                PALETTE_FLASH_ADD(15,15,15);
875
                HUD_init_message(HM_DEFAULT, "%d %s%s",num_picked_up,SECONDARY_WEAPON_NAMES(weapon_index), TXT_SX);
876
        }
877
        else {
878
                PALETTE_FLASH_ADD(10,10,10);
879
                HUD_init_message(HM_DEFAULT, "%s!",SECONDARY_WEAPON_NAMES(weapon_index));
880
        }
881
 
882
        return 1;
883
}
884
}
885
 
886
template <typename T>
887
static void ReorderWeapon()
888
{
889
        std::array<newmenu_item, T::max_weapons + 1> m;
890
        for (unsigned i = 0; i != m.size(); ++i)
891
        {
892
                const auto o = T::get_weapon_by_order_slot(i);
893
                m[i].value = o;
894
                nm_set_item_menu(m[i], T::get_weapon_name(o));
895
        }
896
        newmenu_doreorder(T::reorder_title, "Shift+Up/Down arrow to move item", m.size(), m.data());
897
        for (unsigned i = 0; i != m.size(); ++i)
898
                T::get_weapon_by_order_slot(i) = m[i].value;
899
}
900
 
901
void ReorderPrimary ()
902
{
903
        ReorderWeapon<cycle_primary_state>();
904
}
905
 
906
void ReorderSecondary ()
907
{
908
        ReorderWeapon<cycle_secondary_state>();
909
}
910
 
911
template <typename T>
912
static uint_fast32_t search_weapon_order_list(uint8_t goal)
913
{
914
        for (uint_fast32_t i = 0; i != T::max_weapons + 1; ++i)
915
                if (T::get_weapon_by_order_slot(i) == goal)
916
                        return i;
917
        T::report_runtime_error(T::error_weapon_list_corrupt);
918
    return 0; // Pierre-Marie Baty -- unreachable code to make compiler happy
919
}
920
 
921
uint_fast32_t POrderList (uint_fast32_t num)
922
{
923
        return search_weapon_order_list<cycle_primary_state>(num);
924
}
925
 
926
uint_fast32_t SOrderList (uint_fast32_t num)
927
{
928
        return search_weapon_order_list<cycle_secondary_state>(num);
929
}
930
 
931
namespace dsx {
932
//called when a primary weapon is picked up
933
//returns true if actually picked up
934
int pick_up_primary(player_info &player_info, int weapon_index)
935
{
936
        ushort flag = HAS_PRIMARY_FLAG(weapon_index);
937
 
938
        if (weapon_index != primary_weapon_index_t::LASER_INDEX &&
939
                (player_info.primary_weapon_flags & flag))
940
        {               //already have
941
                HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, "%s %s!", TXT_ALREADY_HAVE_THE, PRIMARY_WEAPON_NAMES(weapon_index));
942
                return 0;
943
        }
944
 
945
        player_info.primary_weapon_flags |= flag;
946
 
947
        maybe_autoselect_primary_weapon(player_info, weapon_index);
948
 
949
        PALETTE_FLASH_ADD(7,14,21);
950
 
951
        if (weapon_index != primary_weapon_index_t::LASER_INDEX)
952
        HUD_init_message(HM_DEFAULT, "%s!",PRIMARY_WEAPON_NAMES(weapon_index));
953
 
954
        return 1;
955
}
956
 
957
#if defined(DXX_BUILD_DESCENT_II)
958
void check_to_use_primary_super_laser(player_info &player_info)
959
{
960
        if (!(player_info.primary_weapon_flags & HAS_SUPER_LASER_FLAG))
961
        {
962
                const auto weapon_index = primary_weapon_index_t::SUPER_LASER_INDEX;
963
                const auto pwi = POrderList(weapon_index);
964
                if (pwi < POrderList(255) &&
965
                        pwi < POrderList(player_info.Primary_weapon))
966
                {
967
                        select_primary_weapon(player_info, nullptr, primary_weapon_index_t::LASER_INDEX, 1);
968
                }
969
        }
970
        PALETTE_FLASH_ADD(7,14,21);
971
}
972
#endif
973
 
974
static void maybe_autoselect_vulcan_weapon(player_info &player_info)
975
{
976
#if defined(DXX_BUILD_DESCENT_I)
977
        const auto weapon_flag_mask = HAS_VULCAN_FLAG;
978
#elif defined(DXX_BUILD_DESCENT_II)
979
        const auto weapon_flag_mask = HAS_VULCAN_FLAG | HAS_GAUSS_FLAG;
980
#endif
981
        const auto primary_weapon_flags = player_info.primary_weapon_flags;
982
        if (!(primary_weapon_flags & weapon_flag_mask))
983
                return;
984
        const auto cutpoint = POrderList(255);
985
        auto weapon_index = primary_weapon_index_t::VULCAN_INDEX;
986
#if defined(DXX_BUILD_DESCENT_I)
987
        const auto weapon_order_vulcan = POrderList(primary_weapon_index_t::VULCAN_INDEX);
988
        const auto better = weapon_order_vulcan;
989
#elif defined(DXX_BUILD_DESCENT_II)
990
        /* If a weapon is missing, pretend its auto-select priority is equal
991
         * to cutpoint.  Priority at or worse than cutpoint is never
992
         * auto-selected.
993
         */
994
        const auto weapon_order_vulcan = (primary_weapon_flags & HAS_VULCAN_FLAG)
995
                ? POrderList(primary_weapon_index_t::VULCAN_INDEX)
996
                : cutpoint;
997
        const auto weapon_order_gauss = (primary_weapon_flags & HAS_GAUSS_FLAG)
998
                ? POrderList(primary_weapon_index_t::GAUSS_INDEX)
999
                : cutpoint;
1000
        /* Set better to whichever vulcan-based weapon is higher priority.
1001
         * The chosen weapon might still be worse than cutpoint.
1002
         */
1003
        const auto better = (weapon_order_vulcan < weapon_order_gauss)
1004
                ? weapon_order_vulcan
1005
                : (weapon_index = primary_weapon_index_t::GAUSS_INDEX, weapon_order_gauss);
1006
#endif
1007
        if (better >= cutpoint)
1008
                /* Preferred weapon is not auto-selectable */
1009
                return;
1010
        if (better >= POrderList(get_mapped_weapon_index(player_info, player_info.Primary_weapon)))
1011
                /* Preferred weapon is not as desirable as the current weapon */
1012
                return;
1013
        maybe_autoselect_primary_weapon(player_info, weapon_index);
1014
}
1015
 
1016
//called when ammo (for the vulcan cannon) is picked up
1017
//      Returns the amount picked up
1018
int pick_up_vulcan_ammo(player_info &player_info, uint_fast32_t ammo_count, const bool change_weapon)
1019
{
1020
        const auto max = PLAYER_MAX_AMMO(player_info.powerup_flags, VULCAN_AMMO_MAX);
1021
        auto &plr_vulcan_ammo = player_info.vulcan_ammo;
1022
        const auto old_ammo = plr_vulcan_ammo;
1023
        if (old_ammo >= max)
1024
                return 0;
1025
 
1026
        plr_vulcan_ammo += ammo_count;
1027
 
1028
        if (plr_vulcan_ammo > max) {
1029
                ammo_count += (max - plr_vulcan_ammo);
1030
                plr_vulcan_ammo = max;
1031
        }
1032
        if (change_weapon &&
1033
                !old_ammo)
1034
                maybe_autoselect_vulcan_weapon(player_info);
1035
        return ammo_count;      //return amount used
1036
}
1037
 
1038
#if defined(DXX_BUILD_DESCENT_II)
1039
// Homing weapons cheat
1040
 
1041
void weapons_homing_all()
1042
{
1043
        auto &Objects = LevelUniqueObjectState.Objects;
1044
        auto &vmobjptr = Objects.vmptr;
1045
        range_for(auto &&objp, vmobjptr)
1046
                if (objp->type == OBJ_WEAPON)
1047
                        objp->ctype.laser_info.track_goal = object_none;
1048
        range_for (auto &w, Weapon_info)
1049
                w.homing_flag |= 2;
1050
}
1051
 
1052
void weapons_homing_all_reset()
1053
{
1054
        range_for (auto &w, Weapon_info)
1055
                w.homing_flag &= ~2;
1056
}
1057
 
1058
#define SMEGA_SHAKE_TIME                (F1_0*2)
1059
 
1060
//      Call this to initialize for a new level.
1061
//      Sets all super mega missile detonation times to 0 which means there aren't any.
1062
void init_smega_detonates()
1063
{
1064
        LevelUniqueSeismicState.Earthshaker_detonate_times = {};
1065
}
1066
 
1067
constexpr std::integral_constant<int, SOUND_SEISMIC_DISTURBANCE_START> Seismic_sound{};
1068
 
1069
static void start_seismic_sound()
1070
{
1071
        if (LevelUniqueSeismicState.Next_seismic_sound_time)
1072
                return;
1073
        LevelUniqueSeismicState.Next_seismic_sound_time = GameTime64 + d_rand()/2;
1074
        digi_play_sample_looping(Seismic_sound, F1_0, -1, -1);
1075
}
1076
 
1077
static void apply_seismic_effect(const int entry_fc)
1078
{
1079
        const auto fc = std::min(std::max(entry_fc, 16), 1);
1080
        LevelUniqueSeismicState.Seismic_tremor_volume += fc;
1081
 
1082
        if (!d_tick_step)
1083
                return;
1084
        const auto get_base_disturbance = [fc]() {
1085
                return fixmul(d_rand() - 16384, 3 * F1_0 / 16 + (F1_0 * (16 - fc)) / 32);
1086
        };
1087
        const fix disturb_x = get_base_disturbance();
1088
        const fix disturb_z = get_base_disturbance();
1089
 
1090
        {
1091
                auto &rotvel = ConsoleObject->mtype.phys_info.rotvel;
1092
                rotvel.x += disturb_x;
1093
                rotvel.z += disturb_z;
1094
        }
1095
 
1096
        //      Shake the buddy!
1097
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
1098
        const auto Buddy_objnum = BuddyState.Buddy_objnum;
1099
        if (Buddy_objnum != object_none) {
1100
                auto &objp = *LevelUniqueObjectState.Objects.vmptr(Buddy_objnum);
1101
                auto &rotvel = objp.mtype.phys_info.rotvel;
1102
                rotvel.x += disturb_x * 4;
1103
                rotvel.z += disturb_z * 4;
1104
        }
1105
        //      Shake a guided missile!
1106
        LevelUniqueSeismicState.Seismic_tremor_magnitude += disturb_x;
1107
}
1108
 
1109
//      If a smega missile been detonated, rock the mine!
1110
//      This should be called every frame.
1111
//      Maybe this should affect all robots, being called when they get their physics done.
1112
void rock_the_mine_frame(void)
1113
{
1114
        range_for (auto &i, LevelUniqueSeismicState.Earthshaker_detonate_times)
1115
        {
1116
                if (i != 0) {
1117
                        fix     delta_time = GameTime64 - i;
1118
                        start_seismic_sound();
1119
                        if (delta_time < SMEGA_SHAKE_TIME) {
1120
 
1121
                                //      Control center destroyed, rock the player's ship.
1122
                                // -- fc = abs(delta_time - SMEGA_SHAKE_TIME/2);
1123
                                //      Changed 10/23/95 to make decreasing for super mega missile.
1124
                                const int fc = (SMEGA_SHAKE_TIME - delta_time) / 2;
1125
                                apply_seismic_effect(fc / (SMEGA_SHAKE_TIME / 32));
1126
                        } else
1127
                                i = 0;
1128
                }
1129
        }
1130
 
1131
        //      Hook in the rumble sound effect here.
1132
}
1133
 
1134
void init_seismic_disturbances()
1135
{
1136
        LevelUniqueSeismicState.Seismic_disturbance_end_time = 0;
1137
}
1138
 
1139
//      Return true if time to start a seismic disturbance.
1140
static bool seismic_disturbance_active()
1141
{
1142
        const auto level_shake_duration = LevelSharedSeismicState.Level_shake_duration;
1143
        if (level_shake_duration < 1)
1144
                return false;
1145
 
1146
        if (LevelUniqueSeismicState.Seismic_disturbance_end_time && LevelUniqueSeismicState.Seismic_disturbance_end_time < GameTime64)
1147
                return true;
1148
 
1149
        bool rval;
1150
        rval =  (2 * fixmul(d_rand(), LevelSharedSeismicState.Level_shake_frequency)) < FrameTime;
1151
 
1152
        if (rval) {
1153
                LevelUniqueSeismicState.Seismic_disturbance_end_time = GameTime64 + level_shake_duration;
1154
                start_seismic_sound();
1155
                if (Game_mode & GM_MULTI)
1156
                        multi_send_seismic(level_shake_duration);
1157
        }
1158
        return rval;
1159
}
1160
 
1161
static void seismic_disturbance_frame(void)
1162
{
1163
        if (LevelSharedSeismicState.Level_shake_frequency) {
1164
                if (seismic_disturbance_active()) {
1165
                        fix delta_time = static_cast<fix>(GameTime64 - LevelUniqueSeismicState.Seismic_disturbance_end_time);
1166
                        const int fc = abs(delta_time - LevelSharedSeismicState.Level_shake_duration / 2);
1167
                        apply_seismic_effect(fc / (F1_0 / 16));
1168
                }
1169
        }
1170
}
1171
 
1172
 
1173
//      Call this when a smega detonates to start the process of rocking the mine.
1174
void smega_rock_stuff(void)
1175
{
1176
        auto least = &LevelUniqueSeismicState.Earthshaker_detonate_times[0];
1177
        range_for (auto &i, LevelUniqueSeismicState.Earthshaker_detonate_times)
1178
        {
1179
                if (i + SMEGA_SHAKE_TIME < GameTime64)
1180
                        i = 0;
1181
                if (*least > i)
1182
                        least = &i;
1183
        }
1184
        *least = GameTime64;
1185
}
1186
 
1187
static int      Super_mines_yes = 1;
1188
 
1189
static bool immediate_detonate_smart_mine(const vcobjptridx_t smart_mine, const vcobjptridx_t target)
1190
{
1191
        if (smart_mine->segnum == target->segnum)
1192
                return true;
1193
        //      Object which is close enough to detonate smart mine is not in same segment as smart mine.
1194
        //      Need to do a more expensive check to make sure there isn't an obstruction.
1195
        if (likely((d_tick_count ^ (static_cast<vcobjptridx_t::integral_type>(smart_mine) + static_cast<vcobjptridx_t::integral_type>(target))) % 4))
1196
                // Maybe next frame
1197
                return false;
1198
        fvi_query       fq{};
1199
        fvi_info                hit_data;
1200
        fq.startseg = smart_mine->segnum;
1201
        fq.p0                                           = &smart_mine->pos;
1202
        fq.p1                                           = &target->pos;
1203
        fq.thisobjnum                   = smart_mine;
1204
        auto fate = find_vector_intersection(fq, hit_data);
1205
        return fate != HIT_WALL;
1206
}
1207
 
1208
//      Call this once/frame to process all super mines in the level.
1209
void process_super_mines_frame(void)
1210
{
1211
        auto &Objects = LevelUniqueObjectState.Objects;
1212
        auto &vmobjptridx = Objects.vmptridx;
1213
        int     start, add;
1214
 
1215
        //      If we don't know of there being any super mines in the level, just
1216
        //      check every 8th object each frame.
1217
        if (Super_mines_yes == 0) {
1218
                start = d_tick_count & 7;
1219
                add = 8;
1220
        } else {
1221
                start = 0;
1222
                add = 1;
1223
        }
1224
 
1225
        Super_mines_yes = 0;
1226
 
1227
        for (objnum_t i=start; i<=Highest_object_index; i+=add) {
1228
                const auto io = vmobjptridx(i);
1229
                if (likely(io->type != OBJ_WEAPON || get_weapon_id(io) != weapon_id_type::SUPERPROX_ID))
1230
                        continue;
1231
                Super_mines_yes = 1;
1232
                if (unlikely(io->lifeleft + F1_0*2 >= Weapon_info[weapon_id_type::SUPERPROX_ID].lifetime))
1233
                        continue;
1234
                const auto parent_num = io->ctype.laser_info.parent_num;
1235
                const auto &bombpos = io->pos;
1236
                range_for (const auto &&jo, vmobjptridx)
1237
                {
1238
                        if (unlikely(jo == parent_num))
1239
                                continue;
1240
                        if (jo->type != OBJ_PLAYER && jo->type != OBJ_ROBOT)
1241
                                continue;
1242
                        const auto dist_squared = vm_vec_dist2(bombpos, jo->pos);
1243
                        const vm_distance distance_threshold{F1_0 * 20};
1244
                        const auto distance_threshold_squared = distance_threshold * distance_threshold;
1245
                        if (likely(distance_threshold_squared < dist_squared))
1246
                                /* Cheap check, some false negatives */
1247
                                continue;
1248
                        const fix64 j_size = jo->size;
1249
                        const fix64 j_size_squared = j_size * j_size;
1250
                        if (dist_squared - j_size_squared >= distance_threshold_squared)
1251
                                /* Accurate check */
1252
                                continue;
1253
                        if (immediate_detonate_smart_mine(io, jo))
1254
                                io->lifeleft = 1;
1255
                }
1256
        }
1257
}
1258
#endif
1259
 
1260
#define SPIT_SPEED 20
1261
 
1262
//this function is for when the player intentionally drops a powerup
1263
//this function is based on drop_powerup()
1264
imobjptridx_t spit_powerup(const d_vclip_array &Vclip, const object_base &spitter, const unsigned id, const unsigned seed)
1265
{
1266
        d_srand(seed);
1267
 
1268
        auto new_velocity = vm_vec_scale_add(spitter.mtype.phys_info.velocity, spitter.orient.fvec, i2f(SPIT_SPEED));
1269
 
1270
        new_velocity.x += (d_rand() - 16384) * SPIT_SPEED * 2;
1271
        new_velocity.y += (d_rand() - 16384) * SPIT_SPEED * 2;
1272
        new_velocity.z += (d_rand() - 16384) * SPIT_SPEED * 2;
1273
 
1274
        // Give keys zero velocity so they can be tracked better in multi
1275
 
1276
        if ((Game_mode & GM_MULTI) && (id >= POW_KEY_BLUE) && (id <= POW_KEY_GOLD))
1277
                vm_vec_zero(new_velocity);
1278
 
1279
        //there's a piece of code which lets the player pick up a powerup if
1280
        //the distance between him and the powerup is less than 2 time their
1281
        //combined radii.  So we need to create powerups pretty far out from
1282
        //the player.
1283
 
1284
        const auto new_pos = vm_vec_scale_add(spitter.pos, spitter.orient.fvec, spitter.size);
1285
 
1286
        if (Game_mode & GM_MULTI)
1287
        {
1288
                if (Net_create_loc >= MAX_NET_CREATE_OBJECTS)
1289
                {
1290
                        return object_none;
1291
                }
1292
        }
1293
 
1294
        const auto &&obj = obj_create(OBJ_POWERUP, id, vmsegptridx(spitter.segnum), new_pos, &vmd_identity_matrix, Powerup_info[id].size, CT_POWERUP, MT_PHYSICS, RT_POWERUP);
1295
 
1296
        if (obj == object_none)
1297
        {
1298
                Int3();
1299
                return object_none;
1300
        }
1301
        obj->mtype.phys_info.velocity = new_velocity;
1302
        obj->mtype.phys_info.drag = 512;        //1024;
1303
        obj->mtype.phys_info.mass = F1_0;
1304
 
1305
        obj->mtype.phys_info.flags = PF_BOUNCE;
1306
 
1307
        obj->rtype.vclip_info.vclip_num = Powerup_info[get_powerup_id(obj)].vclip_num;
1308
        obj->rtype.vclip_info.frametime = Vclip[obj->rtype.vclip_info.vclip_num].frame_time;
1309
        obj->rtype.vclip_info.framenum = 0;
1310
 
1311
        if (&spitter == ConsoleObject)
1312
                obj->ctype.powerup_info.flags |= PF_SPAT_BY_PLAYER;
1313
 
1314
        switch (get_powerup_id(obj)) {
1315
                case POW_MISSILE_1:
1316
                case POW_MISSILE_4:
1317
                case POW_SHIELD_BOOST:
1318
                case POW_ENERGY:
1319
                        obj->lifeleft = (d_rand() + F1_0*3) * 64;               //      Lives for 3 to 3.5 binary minutes (a binary minute is 64 seconds)
1320
                        if (Game_mode & GM_MULTI)
1321
                                obj->lifeleft /= 2;
1322
                        break;
1323
                default:
1324
                        //if (Game_mode & GM_MULTI)
1325
                        //      obj->lifeleft = (d_rand() + F1_0*3) * 64;               //      Lives for 5 to 5.5 binary minutes (a binary minute is 64 seconds)
1326
                        break;
1327
        }
1328
        return obj;
1329
}
1330
 
1331
void DropCurrentWeapon (player_info &player_info)
1332
{
1333
        auto &Objects = LevelUniqueObjectState.Objects;
1334
        auto &vmobjptr = Objects.vmptr;
1335
        if (LevelUniqueObjectState.num_objects >= Objects.size())
1336
                return;
1337
 
1338
        powerup_type_t drop_type;
1339
        const auto &Primary_weapon = player_info.Primary_weapon;
1340
        const auto GrantedItems = (Game_mode & GM_MULTI) ? Netgame.SpawnGrantedItems : 0;
1341
        auto weapon_name = PRIMARY_WEAPON_NAMES(Primary_weapon);
1342
        if (Primary_weapon == primary_weapon_index_t::LASER_INDEX)
1343
        {
1344
                if ((player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS) && !GrantedItems.has_quad_laser())
1345
                {
1346
                        /* Sorry, no message.  Need to fall through in case player
1347
                         * wanted to drop a laser powerup.
1348
                         */
1349
                        drop_type = POW_QUAD_FIRE;
1350
                        weapon_name = TXT_QUAD_LASERS;
1351
                }
1352
                else if (player_info.laser_level == LASER_LEVEL_1)
1353
                {
1354
                        HUD_init_message_literal(HM_DEFAULT, "You cannot drop your base weapon!");
1355
                        return;
1356
                }
1357
#if defined(DXX_BUILD_DESCENT_II)
1358
                else if (player_info.laser_level > MAX_LASER_LEVEL)
1359
                {
1360
                        /* Disallow dropping any super lasers until someone requests
1361
                         * it.
1362
                         */
1363
                        HUD_init_message_literal(HM_DEFAULT, "You cannot drop super lasers!");
1364
                        return;
1365
                }
1366
#endif
1367
                else if (player_info.laser_level <= map_granted_flags_to_laser_level(GrantedItems))
1368
                {
1369
                        HUD_init_message_literal(HM_DEFAULT, "You cannot drop granted lasers!");
1370
                        return;
1371
                }
1372
                else
1373
                        drop_type = POW_LASER;
1374
        }
1375
        else
1376
        {
1377
                if (HAS_PRIMARY_FLAG(Primary_weapon) & map_granted_flags_to_primary_weapon_flags(GrantedItems))
1378
                {
1379
                        HUD_init_message(HM_DEFAULT, "You cannot drop granted %s!", weapon_name);
1380
                        return;
1381
                }
1382
                drop_type = Primary_weapon_to_powerup[Primary_weapon];
1383
        }
1384
 
1385
        const auto seed = d_rand();
1386
        const auto objnum = spit_powerup(Vclip, vmobjptr(ConsoleObject), drop_type, seed);
1387
        if (objnum == object_none)
1388
        {
1389
                HUD_init_message(HM_DEFAULT, "Failed to drop %s!", weapon_name);
1390
                return;
1391
        }
1392
 
1393
        HUD_init_message(HM_DEFAULT, "%s dropped!", weapon_name);
1394
#if defined(DXX_BUILD_DESCENT_II)
1395
        digi_play_sample (SOUND_DROP_WEAPON,F1_0);
1396
#endif
1397
 
1398
        if (weapon_index_uses_vulcan_ammo(Primary_weapon)) {
1399
 
1400
                //if it's one of these, drop some ammo with the weapon
1401
                auto &plr_vulcan_ammo = player_info.vulcan_ammo;
1402
                auto ammo = plr_vulcan_ammo;
1403
#if defined(DXX_BUILD_DESCENT_II)
1404
                const auto HAS_VULCAN_AND_GAUSS_FLAGS = HAS_VULCAN_FLAG | HAS_GAUSS_FLAG;
1405
                if ((player_info.primary_weapon_flags & HAS_VULCAN_AND_GAUSS_FLAGS) == HAS_VULCAN_AND_GAUSS_FLAGS)
1406
                        ammo /= 2;              //if both vulcan & gauss, drop half
1407
#endif
1408
 
1409
                plr_vulcan_ammo -= ammo;
1410
 
1411
                        objnum->ctype.powerup_info.count = ammo;
1412
        }
1413
#if defined(DXX_BUILD_DESCENT_II)
1414
        if (Primary_weapon == primary_weapon_index_t::OMEGA_INDEX) {
1415
 
1416
                //dropped weapon has current energy
1417
 
1418
                        objnum->ctype.powerup_info.count = player_info.Omega_charge;
1419
        }
1420
#endif
1421
 
1422
        if (Game_mode & GM_MULTI)
1423
                multi_send_drop_weapon(objnum,seed);
1424
 
1425
        if (Primary_weapon == primary_weapon_index_t::LASER_INDEX)
1426
        {
1427
                if (drop_type == POW_QUAD_FIRE)
1428
                        player_info.powerup_flags &= ~PLAYER_FLAGS_QUAD_LASERS;
1429
                else
1430
                        -- player_info.laser_level;
1431
        }
1432
        else
1433
                player_info.primary_weapon_flags &= ~HAS_PRIMARY_FLAG(Primary_weapon);
1434
        auto_select_primary_weapon(player_info);
1435
}
1436
 
1437
void DropSecondaryWeapon (player_info &player_info)
1438
{
1439
        auto &Objects = LevelUniqueObjectState.Objects;
1440
        auto &vmobjptr = Objects.vmptr;
1441
        int seed;
1442
        ushort sub_ammo=0;
1443
 
1444
        if (LevelUniqueObjectState.num_objects >= Objects.size())
1445
                return;
1446
 
1447
        auto &Secondary_weapon = player_info.Secondary_weapon;
1448
        auto &secondary_ammo = player_info.secondary_ammo[Secondary_weapon];
1449
        if (secondary_ammo == 0)
1450
        {
1451
                HUD_init_message_literal(HM_DEFAULT, "No secondary weapon to drop!");
1452
                return;
1453
        }
1454
 
1455
        auto weapon_drop_id = Secondary_weapon_to_powerup[Secondary_weapon];
1456
 
1457
        // see if we drop single or 4-pack
1458
        switch (weapon_drop_id)
1459
        {
1460
                case POW_MISSILE_1:
1461
                case POW_HOMING_AMMO_1:
1462
#if defined(DXX_BUILD_DESCENT_II)
1463
                case POW_SMISSILE1_1:
1464
                case POW_GUIDED_MISSILE_1:
1465
                case POW_MERCURY_MISSILE_1:
1466
#endif
1467
                        if (secondary_ammo % 4)
1468
                        {
1469
                                sub_ammo = 1;
1470
                        }
1471
                        else
1472
                        {
1473
                                sub_ammo = 4;
1474
                                //4-pack always is next index
1475
                                weapon_drop_id = static_cast<powerup_type_t>(1 + static_cast<uint_fast32_t>(weapon_drop_id));
1476
                        }
1477
                        break;
1478
                case POW_PROXIMITY_WEAPON:
1479
#if defined(DXX_BUILD_DESCENT_II)
1480
                case POW_SMART_MINE:
1481
#endif
1482
                        if (secondary_ammo < 4)
1483
                        {
1484
                                HUD_init_message_literal(HM_DEFAULT, "You need at least 4 to drop!");
1485
                                return;
1486
                        }
1487
                        else
1488
                        {
1489
                                sub_ammo = 4;
1490
                        }
1491
                        break;
1492
                case POW_SMARTBOMB_WEAPON:
1493
                case POW_MEGA_WEAPON:
1494
#if defined(DXX_BUILD_DESCENT_II)
1495
                case POW_EARTHSHAKER_MISSILE:
1496
#endif
1497
                        sub_ammo = 1;
1498
                        break;
1499
                case POW_EXTRA_LIFE:
1500
                case POW_ENERGY:
1501
                case POW_SHIELD_BOOST:
1502
                case POW_LASER:
1503
                case POW_KEY_BLUE:
1504
                case POW_KEY_RED:
1505
                case POW_KEY_GOLD:
1506
                case POW_MISSILE_4:
1507
                case POW_QUAD_FIRE:
1508
                case POW_VULCAN_WEAPON:
1509
                case POW_SPREADFIRE_WEAPON:
1510
                case POW_PLASMA_WEAPON:
1511
                case POW_FUSION_WEAPON:
1512
                case POW_HOMING_AMMO_4:
1513
                case POW_VULCAN_AMMO:
1514
                case POW_CLOAK:
1515
                case POW_TURBO:
1516
                case POW_INVULNERABILITY:
1517
                case POW_MEGAWOW:
1518
#if defined(DXX_BUILD_DESCENT_II)
1519
                case POW_GAUSS_WEAPON:
1520
                case POW_HELIX_WEAPON:
1521
                case POW_PHOENIX_WEAPON:
1522
                case POW_OMEGA_WEAPON:
1523
                case POW_SUPER_LASER:
1524
                case POW_FULL_MAP:
1525
                case POW_CONVERTER:
1526
                case POW_AMMO_RACK:
1527
                case POW_AFTERBURNER:
1528
                case POW_HEADLIGHT:
1529
                case POW_SMISSILE1_4:
1530
                case POW_GUIDED_MISSILE_4:
1531
                case POW_MERCURY_MISSILE_4:
1532
                case POW_FLAG_BLUE:
1533
                case POW_FLAG_RED:
1534
                case POW_HOARD_ORB:
1535
#endif
1536
                        break;
1537
        }
1538
 
1539
        HUD_init_message(HM_DEFAULT, "%s dropped!",SECONDARY_WEAPON_NAMES(Secondary_weapon));
1540
#if defined(DXX_BUILD_DESCENT_II)
1541
        digi_play_sample (SOUND_DROP_WEAPON,F1_0);
1542
#endif
1543
 
1544
        seed = d_rand();
1545
 
1546
        auto objnum = spit_powerup(Vclip, vmobjptr(ConsoleObject), weapon_drop_id, seed);
1547
 
1548
        if (objnum == object_none)
1549
                return;
1550
 
1551
        if (Game_mode & GM_MULTI)
1552
                multi_send_drop_weapon(objnum,seed);
1553
 
1554
        secondary_ammo -= sub_ammo;
1555
 
1556
        if (secondary_ammo == 0)
1557
        {
1558
                auto_select_secondary_weapon(player_info);
1559
        }
1560
}
1561
 
1562
#if defined(DXX_BUILD_DESCENT_II)
1563
//      ---------------------------------------------------------------------------------------
1564
//      Do seismic disturbance stuff including the looping sounds with changing volume.
1565
void do_seismic_stuff(void)
1566
{
1567
        const auto stv_save = std::exchange(LevelUniqueSeismicState.Seismic_tremor_volume, 0);
1568
        LevelUniqueSeismicState.Seismic_tremor_magnitude = 0;
1569
 
1570
        rock_the_mine_frame();
1571
        seismic_disturbance_frame();
1572
 
1573
        if (stv_save != 0) {
1574
                const auto Seismic_tremor_volume = LevelUniqueSeismicState.Seismic_tremor_volume;
1575
                if (Seismic_tremor_volume == 0)
1576
                {
1577
                        digi_stop_looping_sound();
1578
                        LevelUniqueSeismicState.Next_seismic_sound_time = 0;
1579
                }
1580
                else if (GameTime64 > LevelUniqueSeismicState.Next_seismic_sound_time)
1581
                {
1582
                        int     volume;
1583
 
1584
                        volume = Seismic_tremor_volume * 2048;
1585
                        if (volume > F1_0)
1586
                                volume = F1_0;
1587
                        digi_change_looping_volume(volume);
1588
                        LevelUniqueSeismicState.Next_seismic_sound_time = GameTime64 + d_rand()/4 + 8192;
1589
                }
1590
        }
1591
 
1592
}
1593
#endif
1594
 
1595
}
1596
 
1597
DEFINE_BITMAP_SERIAL_UDT();
1598
 
1599
#if defined(DXX_BUILD_DESCENT_I)
1600
DEFINE_SERIAL_UDT_TO_MESSAGE(weapon_info, w, (w.render_type, w.model_num, w.model_num_inner, w.persistent, w.flash_vclip, w.flash_sound, w.robot_hit_vclip, w.robot_hit_sound, w.wall_hit_vclip, w.wall_hit_sound, w.fire_count, w.ammo_usage, w.weapon_vclip, w.destroyable, w.matter, w.bounce, w.homing_flag, w.dum1, w.dum2, w.dum3, w.energy_usage, w.fire_wait, w.bitmap, w.blob_size, w.flash_size, w.impact_size, w.strength, w.speed, w.mass, w.drag, w.thrust, w.po_len_to_width_ratio, w.light, w.lifetime, w.damage_radius, w.picture));
1601
#elif defined(DXX_BUILD_DESCENT_II)
1602
namespace {
1603
struct v2_weapon_info : weapon_info {};
1604
}
1605
 
1606
template <typename Accessor>
1607
void postprocess_udt(Accessor &, v2_weapon_info &w)
1608
{
1609
        w.children = weapon_id_type::unspecified;
1610
        w.multi_damage_scale = F1_0;
1611
        w.hires_picture = w.picture;
1612
}
1613
 
1614
DEFINE_SERIAL_UDT_TO_MESSAGE(v2_weapon_info, w, (w.render_type, w.persistent, w.model_num, w.model_num_inner, w.flash_vclip, w.robot_hit_vclip, w.flash_sound, w.wall_hit_vclip, w.fire_count, w.robot_hit_sound, w.ammo_usage, w.weapon_vclip, w.wall_hit_sound, w.destroyable, w.matter, w.bounce, w.homing_flag, w.speedvar, w.flags, w.flash, w.afterburner_size, w.energy_usage, w.fire_wait, w.bitmap, w.blob_size, w.flash_size, w.impact_size, w.strength, w.speed, w.mass, w.drag, w.thrust, w.po_len_to_width_ratio, w.light, w.lifetime, w.damage_radius, w.picture));
1615
DEFINE_SERIAL_UDT_TO_MESSAGE(weapon_info, w, (w.render_type, w.persistent, w.model_num, w.model_num_inner, w.flash_vclip, w.robot_hit_vclip, w.flash_sound, w.wall_hit_vclip, w.fire_count, w.robot_hit_sound, w.ammo_usage, w.weapon_vclip, w.wall_hit_sound, w.destroyable, w.matter, w.bounce, w.homing_flag, w.speedvar, w.flags, w.flash, w.afterburner_size, w.children, w.energy_usage, w.fire_wait, w.multi_damage_scale, w.bitmap, w.blob_size, w.flash_size, w.impact_size, w.strength, w.speed, w.mass, w.drag, w.thrust, w.po_len_to_width_ratio, w.light, w.lifetime, w.damage_radius, w.picture, w.hires_picture));
1616
#endif
1617
 
1618
#if 0
1619
void weapon_info_write(PHYSFS_File *fp, const weapon_info &w)
1620
{
1621
        PHYSFSX_serialize_write(fp, w);
1622
}
1623
#endif
1624
 
1625
/*
1626
 * reads n weapon_info structs from a PHYSFS_File
1627
 */
1628
namespace dsx {
1629
void weapon_info_read_n(weapon_info_array &wi, std::size_t count, PHYSFS_File *fp, int file_version, std::size_t offset)
1630
{
1631
        auto r = partial_range(wi, offset, count);
1632
#if defined(DXX_BUILD_DESCENT_I)
1633
        (void)file_version;
1634
#elif defined(DXX_BUILD_DESCENT_II)
1635
        if (file_version < 3)
1636
        {
1637
                range_for (auto &w, r)
1638
                        PHYSFSX_serialize_read(fp, static_cast<v2_weapon_info &>(w));
1639
                /* Set the type of children correctly when using old
1640
                 * datafiles.  In earlier descent versions this was simply
1641
                 * hard-coded in create_smart_children().
1642
                 */
1643
                wi[weapon_id_type::SMART_ID].children = weapon_id_type::PLAYER_SMART_HOMING_ID;
1644
                wi[weapon_id_type::SUPERPROX_ID].children = weapon_id_type::SMART_MINE_HOMING_ID;
1645
                return;
1646
        }
1647
#endif
1648
        range_for (auto &w, r)
1649
        {
1650
                PHYSFSX_serialize_read(fp, w);
1651
        }
1652
}
1653
}