dxx-rebirth/similar/main/weapon.cpp
Kp bc210c2c05 Halve the volume of weapon changes
The previous commit removed an incorrect double scaling of the player's
weapon sounds, which will make all such sounds louder.  Players with
their FX volume set to maximum will now have twice as loud a sound.
Halve the intensity in the source to return to the volume such players
would have had before.  Players with an FX volume less than maximum will
still get a somewhat louder sound than before.
2021-06-28 03:37:51 +00:00

1826 lines
58 KiB
C++

/*
* Portions of this file are copyright Rebirth contributors and licensed as
* described in COPYING.txt.
* Portions of this file are copyright Parallax Software and licensed
* according to the Parallax license below.
* See COPYING.txt for license details.
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
*/
/*
*
* Functions for weapons...
*
*/
#include <stdexcept>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <type_traits>
#include "hudmsg.h"
#include "game.h"
#include "laser.h"
#include "weapon.h"
#include "player.h"
#include "dxxerror.h"
#include "sounds.h"
#include "text.h"
#include "powerup.h"
#include "newdemo.h"
#include "multi.h"
#include "object.h"
#include "segment.h"
#include "newmenu.h"
#include "playsave.h"
#include "physfs-serial.h"
#include "vclip.h"
#include "compiler-range_for.h"
#include "d_enumerate.h"
#include "d_levelstate.h"
#include "partial_range.h"
// Note, only Vulcan cannon requires ammo.
// NOTE: Now Vulcan and Gauss require ammo. -5/3/95 Yuan
//ubyte Default_primary_ammo_level[MAX_PRIMARY_WEAPONS] = {255, 0, 255, 255, 255};
//ubyte Default_secondary_ammo_level[MAX_SECONDARY_WEAPONS] = {3, 0, 0, 0, 0};
// Convert primary weapons to indices in Weapon_info array.
#if defined(DXX_BUILD_DESCENT_I)
namespace dsx {
const enumerated_array<weapon_id_type, MAX_PRIMARY_WEAPONS, primary_weapon_index_t> Primary_weapon_to_weapon_info{{
{
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
}
}};
const enumerated_array<weapon_id_type, MAX_SECONDARY_WEAPONS, secondary_weapon_index_t> 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
}
}};
//for each Secondary weapon, which gun it fires out of
const std::array<ubyte, MAX_SECONDARY_WEAPONS> Secondary_weapon_to_gun_num{{4,4,7,7,7}};
}
#elif defined(DXX_BUILD_DESCENT_II)
#include "fvi.h"
namespace dsx {
const enumerated_array<weapon_id_type, MAX_PRIMARY_WEAPONS, primary_weapon_index_t> Primary_weapon_to_weapon_info{{
{
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,
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
}
}};
const enumerated_array<weapon_id_type, MAX_SECONDARY_WEAPONS, secondary_weapon_index_t> 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,
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
}
}};
//for each Secondary weapon, which gun it fires out of
const std::array<ubyte, MAX_SECONDARY_WEAPONS> Secondary_weapon_to_gun_num{{4,4,7,7,7,4,4,7,4,7}};
namespace {
/*
* On entry:
* - base_weapon must be the non-super version of a weapon.
*/
template <typename T>
static T get_super_weapon_from_base_weapon(const T base_weapon)
{
return static_cast<T>(static_cast<unsigned>(base_weapon) + SUPER_WEAPON);
}
/*
* On entry:
* - current_weapon may be any valid weapon index, whether regular or
* super.
* - base_weapon must be the non-super version of current_weapon. If
* current_weapon is the regular version, then base_weapon ==
* current_weapon. If current_weapon is the super version, then
* base_weapon + SUPER_WEAPON == current_weapon.
*/
template <typename T>
static T get_alternate_weapon(const T current_weapon, const T base_weapon)
{
const auto b = static_cast<unsigned>(base_weapon);
const auto c = static_cast<unsigned>(current_weapon);
const auto s = static_cast<unsigned>(get_super_weapon_from_base_weapon(base_weapon));
/* If current_weapon == base_weapon, then this expression simplifies
* to (base_weapon + SUPER_WEAPON) and produces the super form of
* base_weapon.
*
* If current_weapon == base_weapon+SUPER_WEAPON, then this
* expression simplifies to (base_weapon), and produces the
* non-super form of base_weapon.
*/
return static_cast<T>(b + s - c);
}
}
}
#endif
namespace dsx {
const enumerated_array<uint8_t, MAX_SECONDARY_WEAPONS, secondary_weapon_index_t> Secondary_ammo_max{{
{
20, 10, 10, 5, 5,
#if defined(DXX_BUILD_DESCENT_II)
20, 20, 15, 10, 10
#endif
}
}};
//for each primary weapon, what kind of powerup gives weapon
const enumerated_array<powerup_type_t, MAX_PRIMARY_WEAPONS, primary_weapon_index_t> Primary_weapon_to_powerup{{
{
powerup_type_t::POW_LASER,
powerup_type_t::POW_VULCAN_WEAPON,
powerup_type_t::POW_SPREADFIRE_WEAPON,
powerup_type_t::POW_PLASMA_WEAPON,
powerup_type_t::POW_FUSION_WEAPON,
#if defined(DXX_BUILD_DESCENT_II)
powerup_type_t::POW_LASER,
powerup_type_t::POW_GAUSS_WEAPON,
powerup_type_t::POW_HELIX_WEAPON,
powerup_type_t::POW_PHOENIX_WEAPON,
powerup_type_t::POW_OMEGA_WEAPON,
#endif
}
}};
//for each Secondary weapon, what kind of powerup gives weapon
const enumerated_array<powerup_type_t, MAX_SECONDARY_WEAPONS, secondary_weapon_index_t> Secondary_weapon_to_powerup{{
{
powerup_type_t::POW_MISSILE_1,
powerup_type_t::POW_HOMING_AMMO_1,
powerup_type_t::POW_PROXIMITY_WEAPON,
powerup_type_t::POW_SMARTBOMB_WEAPON,
powerup_type_t::POW_MEGA_WEAPON,
#if defined(DXX_BUILD_DESCENT_II)
powerup_type_t::POW_SMISSILE1_1,
powerup_type_t::POW_GUIDED_MISSILE_1,
powerup_type_t::POW_SMART_MINE,
powerup_type_t::POW_MERCURY_MISSILE_1,
powerup_type_t::POW_EARTHSHAKER_MISSILE,
#endif
}
}};
weapon_info_array Weapon_info;
}
namespace dcx {
unsigned N_weapon_types;
namespace {
template <typename cycle_weapon_state>
struct weapon_reorder_menu_items
{
std::array<newmenu_item, cycle_weapon_state::max_weapons + 1> menu_items;
weapon_reorder_menu_items();
};
template <typename cycle_weapon_state>
struct weapon_reorder_menu : weapon_reorder_menu_items<cycle_weapon_state>, reorder_newmenu
{
using weapon_reorder_menu_items<cycle_weapon_state>::menu_items;
weapon_reorder_menu(grs_canvas &src) :
reorder_newmenu(menu_title{cycle_weapon_state::reorder_title}, menu_subtitle{"Shift+Up/Down arrow to move item"}, menu_filename{nullptr}, tiny_mode_flag::normal, tab_processing_flag::ignore, adjusted_citem::create(menu_items, 0), src)
{
}
virtual window_event_result event_handler(const d_event &event) override;
};
template <typename cycle_weapon_state>
weapon_reorder_menu_items<cycle_weapon_state>::weapon_reorder_menu_items()
{
for (auto &&[i, mi] : enumerate(menu_items))
{
const auto o = cycle_weapon_state::get_weapon_by_order_slot(i);
mi.value = o;
nm_set_item_menu(mi, cycle_weapon_state::get_weapon_name(o));
}
}
template <typename cycle_weapon_state>
window_event_result weapon_reorder_menu<cycle_weapon_state>::event_handler(const d_event &event)
{
switch(event.type)
{
case EVENT_KEY_COMMAND:
event_key_command(event);
break;
case EVENT_WINDOW_CLOSE:
for (auto &&[i, mi] : enumerate(menu_items))
cycle_weapon_state::get_weapon_by_order_slot(i) = mi.value;
break;
default:
break;
}
return newmenu::event_handler(event);
}
}
}
// autoselect ordering
namespace dsx {
#if defined(DXX_BUILD_DESCENT_I)
constexpr std::array<uint8_t, MAX_PRIMARY_WEAPONS + 1> DefaultPrimaryOrder{{ 4, 3, 2, 1, 0, 255 }};
constexpr std::array<uint8_t, MAX_SECONDARY_WEAPONS + 1> DefaultSecondaryOrder{{ 4, 3, 1, 0, 255, 2 }};
#elif defined(DXX_BUILD_DESCENT_II)
constexpr std::array<uint8_t, MAX_PRIMARY_WEAPONS + 1> DefaultPrimaryOrder={{9,8,7,6,5,4,3,2,1,0,255}};
constexpr std::array<uint8_t, MAX_SECONDARY_WEAPONS + 1> DefaultSecondaryOrder={{9,8,4,3,1,5,0,255,7,6,2}};
//flags whether the last time we use this weapon, it was the 'super' version
#endif
static primary_weapon_index_t get_mapped_weapon_index(const player_info &player_info, const primary_weapon_index_t weapon_index)
{
#if defined(DXX_BUILD_DESCENT_I)
(void)player_info;
#elif defined(DXX_BUILD_DESCENT_II)
if (weapon_index == primary_weapon_index_t::LASER_INDEX && player_info.laser_level > MAX_LASER_LEVEL)
return primary_weapon_index_t::SUPER_LASER_INDEX;
#endif
return weapon_index;
}
}
// ; (0) Laser Level 1
// ; (1) Laser Level 2
// ; (2) Laser Level 3
// ; (3) Laser Level 4
// ; (4) Unknown Use
// ; (5) Josh Blobs
// ; (6) Unknown Use
// ; (7) Unknown Use
// ; (8) ---------- Concussion Missile ----------
// ; (9) ---------- Flare ----------
// ; (10) ---------- Blue laser that blue guy shoots -----------
// ; (11) ---------- Vulcan Cannon ----------
// ; (12) ---------- Spreadfire Cannon ----------
// ; (13) ---------- Plasma Cannon ----------
// ; (14) ---------- Fusion Cannon ----------
// ; (15) ---------- Homing Missile ----------
// ; (16) ---------- Proximity Bomb ----------
// ; (17) ---------- Smart Missile ----------
// ; (18) ---------- Mega Missile ----------
// ; (19) ---------- Children of the PLAYER'S Smart Missile ----------
// ; (20) ---------- Bad Guy Spreadfire Laser ----------
// ; (21) ---------- SuperMech Homing Missile ----------
// ; (22) ---------- Regular Mech's missile -----------
// ; (23) ---------- Silent Spreadfire Laser ----------
// ; (24) ---------- Red laser that baby spiders shoot -----------
// ; (25) ---------- Green laser that rifleman shoots -----------
// ; (26) ---------- Plasma gun that 'plasguy' fires ------------
// ; (27) ---------- Blobs fired by Red Spiders -----------
// ; (28) ---------- Final Boss's Mega Missile ----------
// ; (29) ---------- Children of the ROBOT'S Smart Missile ----------
// ; (30) Laser Level 5
// ; (31) Laser Level 6
// ; (32) ---------- Super Vulcan Cannon ----------
// ; (33) ---------- Super Spreadfire Cannon ----------
// ; (34) ---------- Super Plasma Cannon ----------
// ; (35) ---------- Super Fusion Cannon ----------
// ------------------------------------------------------------------------------------
// Return:
// Bits set:
// HAS_WEAPON_FLAG
// HAS_ENERGY_FLAG
// HAS_AMMO_FLAG
// See weapon.h for bit values
namespace dsx {
has_weapon_result player_has_primary_weapon(const player_info &player_info, primary_weapon_index_t weapon_num)
{
int return_value = 0;
// Hack! If energy goes negative, you can't fire a weapon that doesn't require energy.
// But energy should not go negative (but it does), so find out why it does!
auto &energy = player_info.energy;
const auto weapon_index = Primary_weapon_to_weapon_info[weapon_num];
if (player_info.primary_weapon_flags & HAS_PRIMARY_FLAG(weapon_num))
return_value |= has_weapon_result::has_weapon_flag;
// Special case: Gauss cannon uses vulcan ammo.
if (weapon_index_uses_vulcan_ammo(weapon_num)) {
if (Weapon_info[weapon_index].ammo_usage <= player_info.vulcan_ammo)
return_value |= has_weapon_result::has_ammo_flag;
}
/* Hack to work around check in do_primary_weapon_select */
else
return_value |= has_weapon_result::has_ammo_flag;
#if defined(DXX_BUILD_DESCENT_I)
//added on 1/21/99 by Victor Rachels... yet another hack
//fusion has 0 energy usage, HAS_ENERGY_FLAG was always true
if(weapon_num == primary_weapon_index_t::FUSION_INDEX)
{
if (energy >= F1_0*2)
return_value |= has_weapon_result::has_energy_flag;
}
#elif defined(DXX_BUILD_DESCENT_II)
if (weapon_num == primary_weapon_index_t::OMEGA_INDEX) { // Hack: Make sure player has energy to omega
if (energy > 0 || player_info.Omega_charge)
return_value |= has_weapon_result::has_energy_flag;
}
#endif
else
{
const auto energy_usage = Weapon_info[weapon_index].energy_usage;
/* The test for `energy_usage <= 0` should not be needed.
* However, a Parallax comment suggests that players
* sometimes get negative energy. Use this test in
* preference to coercing negative player energy to zero.
*/
if (energy_usage <= 0 || energy_usage <= energy)
return_value |= has_weapon_result::has_energy_flag;
}
return return_value;
}
has_weapon_result player_has_secondary_weapon(const player_info &player_info, const secondary_weapon_index_t weapon_num)
{
int return_value = 0;
const auto secondary_ammo = player_info.secondary_ammo[weapon_num];
const auto weapon_index = Secondary_weapon_to_weapon_info[weapon_num];
if (secondary_ammo && Weapon_info[weapon_index].ammo_usage <= secondary_ammo)
return_value = has_weapon_result::has_weapon_flag | has_weapon_result::has_energy_flag | has_weapon_result::has_ammo_flag;
return return_value;
}
void InitWeaponOrdering ()
{
// short routine to setup default weapon priorities for new pilots
PlayerCfg.PrimaryOrder = DefaultPrimaryOrder;
PlayerCfg.SecondaryOrder = DefaultSecondaryOrder;
}
namespace {
static uint_fast32_t POrderList(primary_weapon_index_t num);
static uint_fast32_t SOrderList(secondary_weapon_index_t num);
class cycle_weapon_state
{
public:
static constexpr char DXX_WEAPON_TEXT_NEVER_AUTOSELECT[] = "--- Never autoselect below ---";
[[noreturn]]
__attribute_cold
static void report_runtime_error(const char *);
};
class cycle_primary_state : public cycle_weapon_state
{
player_info &pl_info;
public:
using weapon_index_type = primary_weapon_index_t;
static constexpr std::integral_constant<weapon_index_type, weapon_index_type{255}> cycle_never_autoselect_below{};
cycle_primary_state(player_info &p) :
pl_info(p)
{
}
static constexpr std::integral_constant<uint_fast32_t, MAX_PRIMARY_WEAPONS> max_weapons{};
static constexpr char reorder_title[] = "Reorder Primary";
static constexpr char error_weapon_list_corrupt[] = "primary weapon list corrupt";
static uint_fast32_t get_cycle_position(primary_weapon_index_t i)
{
return POrderList(i);
}
static player_config::primary_weapon_order::reference get_weapon_by_order_slot(player_config::primary_weapon_order::size_type cur_order_slot)
{
return PlayerCfg.PrimaryOrder[cur_order_slot];
}
bool maybe_select_weapon_by_order_slot(uint_fast32_t cur_order_slot) const
{
return maybe_select_weapon_by_type(get_weapon_by_order_slot(cur_order_slot));
}
bool maybe_select_weapon_by_type(const uint_fast32_t desired_weapon_idx) const
{
weapon_index_type desired_weapon = static_cast<weapon_index_type>(desired_weapon_idx);
#if defined(DXX_BUILD_DESCENT_II)
// some remapping for SUPER LASER which is not an actual weapon type at all
if (desired_weapon == primary_weapon_index_t::LASER_INDEX)
{
if (pl_info.laser_level > MAX_LASER_LEVEL)
return false;
}
else if (desired_weapon == primary_weapon_index_t::SUPER_LASER_INDEX)
{
if (pl_info.laser_level <= MAX_LASER_LEVEL)
return false;
else
desired_weapon = primary_weapon_index_t::LASER_INDEX;
}
#endif
if (!player_has_primary_weapon(pl_info, desired_weapon).has_all())
return false;
select_primary_weapon(pl_info, PRIMARY_WEAPON_NAMES(desired_weapon), desired_weapon, 1);
return true;
}
void abandon_auto_select()
{
HUD_init_message_literal(HM_DEFAULT, TXT_NO_PRIMARY);
if (pl_info.Primary_weapon == primary_weapon_index_t::LASER_INDEX)
return;
select_primary_weapon(pl_info, nullptr, primary_weapon_index_t::LASER_INDEX, 1);
}
static const char *get_weapon_name(uint8_t i)
{
return i == cycle_never_autoselect_below ? DXX_WEAPON_TEXT_NEVER_AUTOSELECT : PRIMARY_WEAPON_NAMES(i);
}
};
class cycle_secondary_state : public cycle_weapon_state
{
player_info &pl_info;
public:
using weapon_index_type = secondary_weapon_index_t;
static constexpr std::integral_constant<weapon_index_type, weapon_index_type{255}> cycle_never_autoselect_below{};
cycle_secondary_state(player_info &p) :
pl_info(p)
{
}
static constexpr std::integral_constant<uint_fast32_t, MAX_SECONDARY_WEAPONS> max_weapons{};
static constexpr char reorder_title[] = "Reorder Secondary";
static constexpr char error_weapon_list_corrupt[] = "secondary weapon list corrupt";
static uint_fast32_t get_cycle_position(secondary_weapon_index_t i)
{
return SOrderList(i);
}
static player_config::secondary_weapon_order::reference get_weapon_by_order_slot(player_config::secondary_weapon_order::size_type cur_order_slot)
{
return PlayerCfg.SecondaryOrder[cur_order_slot];
}
bool maybe_select_weapon_by_order_slot(uint_fast32_t cur_order_slot)
{
return maybe_select_weapon_by_type(get_weapon_by_order_slot(cur_order_slot));
}
bool maybe_select_weapon_by_type(const uint_fast32_t desired_weapon_idx)
{
const weapon_index_type desired_weapon = static_cast<weapon_index_type>(desired_weapon_idx);
if (!player_has_secondary_weapon(pl_info, desired_weapon).has_all())
return false;
select_secondary_weapon(pl_info, SECONDARY_WEAPON_NAMES(desired_weapon), desired_weapon, 1);
return true;
}
static void abandon_auto_select()
{
HUD_init_message_literal(HM_DEFAULT, "No secondary weapons available!");
}
static const char *get_weapon_name(uint8_t i)
{
return i == cycle_never_autoselect_below ? DXX_WEAPON_TEXT_NEVER_AUTOSELECT : SECONDARY_WEAPON_NAMES(i);
}
};
void cycle_weapon_state::report_runtime_error(const char *const p)
{
throw std::runtime_error(p);
}
template <typename T>
void CycleWeapon(T t, const typename T::weapon_index_type effective_weapon)
{
auto cur_order_slot = t.get_cycle_position(effective_weapon);
const auto autoselect_order_slot = t.get_cycle_position(t.cycle_never_autoselect_below);
const auto use_restricted_autoselect =
(cur_order_slot < autoselect_order_slot) &&
(1 < autoselect_order_slot) &&
PlayerCfg.CycleAutoselectOnly;
for (uint_fast32_t loop = t.max_weapons + 1; loop--;)
{
cur_order_slot++; // next slot
if (cur_order_slot >= t.max_weapons + 1) // loop if necessary
cur_order_slot = 0;
if (cur_order_slot == autoselect_order_slot) // what to to with non-autoselect weapons?
{
if (use_restricted_autoselect)
{
cur_order_slot = 0; // loop over or ...
}
else
{
continue; // continue?
}
}
if (t.maybe_select_weapon_by_order_slot(cur_order_slot)) // now that is the weapon next to our current one
// select the weapon if we have it
return;
}
}
}
void CyclePrimary(player_info &player_info)
{
CycleWeapon(cycle_primary_state(player_info), get_mapped_weapon_index(player_info, player_info.Primary_weapon));
}
void CycleSecondary(player_info &player_info)
{
CycleWeapon(cycle_secondary_state(player_info), player_info.Secondary_weapon);
}
#if defined(DXX_BUILD_DESCENT_II)
namespace {
static inline void set_weapon_last_was_super(uint8_t &last, const uint8_t mask, const bool is_super)
{
if (is_super)
last |= mask;
else
last &= ~mask;
}
static inline void set_weapon_last_was_super(uint8_t &last, const uint_fast32_t weapon_num)
{
const bool is_super = weapon_num >= SUPER_WEAPON;
set_weapon_last_was_super(last, 1 << (is_super ? weapon_num - SUPER_WEAPON : weapon_num), is_super);
}
}
#endif
void set_primary_weapon(player_info &player_info, const uint_fast32_t weapon_num)
{
if (Newdemo_state == ND_STATE_RECORDING)
newdemo_record_player_weapon(0, weapon_num);
player_info.Fusion_charge=0;
player_info.Next_laser_fire_time = 0;
player_info.Primary_weapon = static_cast<primary_weapon_index_t>(weapon_num);
#if defined(DXX_BUILD_DESCENT_II)
//save flag for whether was super version
auto &Primary_last_was_super = player_info.Primary_last_was_super;
set_weapon_last_was_super(Primary_last_was_super, weapon_num);
#endif
}
// ------------------------------------------------------------------------------------
//if message flag set, print message saying selected
void select_primary_weapon(player_info &player_info, const char *const weapon_name, const uint_fast32_t weapon_num, const int wait_for_rearm)
{
if (Newdemo_state==ND_STATE_RECORDING )
newdemo_record_player_weapon(0, weapon_num);
{
auto &Primary_weapon = player_info.Primary_weapon;
if (Primary_weapon != weapon_num) {
Primary_weapon = static_cast<primary_weapon_index_t>(weapon_num);
#ifndef FUSION_KEEPS_CHARGE
//added 8/6/98 by Victor Rachels to fix fusion charge bug
player_info.Fusion_charge=0;
//end edit - Victor Rachels
#endif
auto &Next_laser_fire_time = player_info.Next_laser_fire_time;
if (wait_for_rearm)
{
multi_digi_play_sample_once(SOUND_GOOD_SELECTION_PRIMARY, F0_5);
Next_laser_fire_time = GameTime64 + REARM_TIME;
}
else
Next_laser_fire_time = 0;
}
else
{
#if defined(DXX_BUILD_DESCENT_I)
if (wait_for_rearm)
/*
* In Descent 1, requesting a weapon that is already
* armed is pointless, so play a warning sound.
*
* In Descent 2, the player may be trying to toggle
* between the base and super forms of a weapon, so do
* not generate a warning sound in that case.
*/
digi_play_sample(SOUND_ALREADY_SELECTED, F1_0);
#endif
}
#if defined(DXX_BUILD_DESCENT_II)
//save flag for whether was super version
set_weapon_last_was_super(player_info.Primary_last_was_super, weapon_num);
#endif
}
if (weapon_name)
{
#if defined(DXX_BUILD_DESCENT_II)
if (weapon_num == primary_weapon_index_t::LASER_INDEX)
HUD_init_message(HM_DEFAULT, "%s Level %u %s", weapon_name, static_cast<unsigned>(player_info.laser_level) + 1, TXT_SELECTED);
else
#endif
HUD_init_message(HM_DEFAULT, "%s %s", weapon_name, TXT_SELECTED);
}
}
void set_secondary_weapon_to_concussion(player_info &player_info)
{
const uint_fast32_t weapon_num = 0;
if (Newdemo_state == ND_STATE_RECORDING)
newdemo_record_player_weapon(1, weapon_num);
player_info.Next_missile_fire_time = 0;
Global_missile_firing_count = 0;
player_info.Secondary_weapon = static_cast<secondary_weapon_index_t>(weapon_num);
#if defined(DXX_BUILD_DESCENT_II)
//save flag for whether was super version
set_weapon_last_was_super(player_info.Secondary_last_was_super, weapon_num);
#endif
}
void select_secondary_weapon(player_info &player_info, const char *const weapon_name, const uint_fast32_t weapon_num, const int wait_for_rearm)
{
if (Newdemo_state==ND_STATE_RECORDING )
newdemo_record_player_weapon(1, weapon_num);
{
auto &Secondary_weapon = player_info.Secondary_weapon;
if (Secondary_weapon != weapon_num) {
auto &Next_missile_fire_time = player_info.Next_missile_fire_time;
if (wait_for_rearm)
{
multi_digi_play_sample_once(SOUND_GOOD_SELECTION_SECONDARY, F0_5);
Next_missile_fire_time = GameTime64 + REARM_TIME;
}
else
Next_missile_fire_time = 0;
Global_missile_firing_count = 0;
} else {
if (wait_for_rearm)
{
digi_play_sample_once( SOUND_ALREADY_SELECTED, F1_0 );
}
}
Secondary_weapon = static_cast<secondary_weapon_index_t>(weapon_num);
#if defined(DXX_BUILD_DESCENT_II)
//save flag for whether was super version
set_weapon_last_was_super(player_info.Secondary_last_was_super, weapon_num);
#endif
}
if (weapon_name)
{
HUD_init_message(HM_DEFAULT, "%s %s", weapon_name, TXT_SELECTED);
}
}
#if defined(DXX_BUILD_DESCENT_I)
namespace {
static bool reject_shareware_weapon_select(const uint_fast32_t weapon_num, const char *const weapon_name)
{
// do special hud msg. for picking registered weapon in shareware version.
if (PCSharePig)
if (weapon_num >= NUM_SHAREWARE_WEAPONS) {
HUD_init_message(HM_DEFAULT, "%s %s!", weapon_name,TXT_NOT_IN_SHAREWARE);
return true;
}
return false;
}
static bool reject_unusable_primary_weapon_select(const player_info &player_info, const primary_weapon_index_t weapon_num, const char *const weapon_name)
{
const auto weapon_status = player_has_primary_weapon(player_info, weapon_num);
const char *prefix;
if (!weapon_status.has_weapon())
prefix = TXT_DONT_HAVE;
else if (!weapon_status.has_ammo())
prefix = TXT_DONT_HAVE_AMMO;
else
return false;
HUD_init_message(HM_DEFAULT, "%s %s!", prefix, weapon_name);
return true;
}
static bool reject_unusable_secondary_weapon_select(const player_info &player_info, const secondary_weapon_index_t weapon_num, const char *const weapon_name)
{
const auto weapon_status = player_has_secondary_weapon(player_info, weapon_num);
if (weapon_status.has_all())
return false;
HUD_init_message(HM_DEFAULT, "%s %s%s", TXT_HAVE_NO, weapon_name, TXT_SX);
return true;
}
}
#endif
// ------------------------------------------------------------------------------------
// Select a weapon, primary or secondary.
void do_primary_weapon_select(player_info &player_info, primary_weapon_index_t weapon_num)
{
#if defined(DXX_BUILD_DESCENT_I)
//added on 10/9/98 by Victor Rachels to add laser cycle
//end this section addition - Victor Rachels
const auto weapon_name = PRIMARY_WEAPON_NAMES(weapon_num);
if (reject_shareware_weapon_select(weapon_num, weapon_name) || reject_unusable_primary_weapon_select(player_info, weapon_num, weapon_name))
{
digi_play_sample(SOUND_BAD_SELECTION, F1_0);
return;
}
#elif defined(DXX_BUILD_DESCENT_II)
has_weapon_result weapon_status;
auto &Primary_weapon = player_info.Primary_weapon;
const auto current = Primary_weapon.get_active();
const auto last_was_super = player_info.Primary_last_was_super & (1 << weapon_num);
const auto has_flag = weapon_status.has_weapon_flag;
if (current == weapon_num || current == weapon_num+SUPER_WEAPON) {
//already have this selected, so toggle to other of normal/super version
weapon_num = get_alternate_weapon(current, weapon_num);
weapon_status = player_has_primary_weapon(player_info, weapon_num);
}
else {
const auto weapon_num_save = weapon_num;
//go to last-select version of requested missile
if (last_was_super)
weapon_num = get_super_weapon_from_base_weapon(weapon_num);
weapon_status = player_has_primary_weapon(player_info, weapon_num);
//if don't have last-selected, try other version
if ((weapon_status.flags() & has_flag) != has_flag) {
weapon_num = get_alternate_weapon(weapon_num, weapon_num_save);
weapon_status = player_has_primary_weapon(player_info, weapon_num);
if ((weapon_status.flags() & has_flag) != has_flag)
weapon_num = get_alternate_weapon(weapon_num, weapon_num_save);
}
}
//if we don't have the weapon we're switching to, give error & bail
const auto weapon_name = PRIMARY_WEAPON_NAMES(weapon_num);
if ((weapon_status.flags() & has_flag) != has_flag) {
{
if (weapon_num == primary_weapon_index_t::SUPER_LASER_INDEX)
return; //no such thing as super laser, so no error
HUD_init_message(HM_DEFAULT, "%s %s!", TXT_DONT_HAVE, weapon_name);
}
digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
return;
}
//now actually select the weapon
#endif
select_primary_weapon(player_info, weapon_name, weapon_num, 1);
}
void do_secondary_weapon_select(player_info &player_info, secondary_weapon_index_t weapon_num)
{
#if defined(DXX_BUILD_DESCENT_I)
//added on 10/9/98 by Victor Rachels to add laser cycle
//end this section addition - Victor Rachels
// do special hud msg. for picking registered weapon in shareware version.
const auto weapon_name = SECONDARY_WEAPON_NAMES(weapon_num);
if (reject_shareware_weapon_select(weapon_num, weapon_name) || reject_unusable_secondary_weapon_select(player_info, weapon_num, weapon_name))
{
digi_play_sample(SOUND_BAD_SELECTION, F1_0);
return;
}
#elif defined(DXX_BUILD_DESCENT_II)
has_weapon_result weapon_status;
const auto current = player_info.Secondary_weapon.get_active();
const auto last_was_super = player_info.Secondary_last_was_super & (1 << weapon_num);
const auto has_flag = weapon_status.has_weapon_flag | weapon_status.has_ammo_flag;
if (current == weapon_num || current == weapon_num+SUPER_WEAPON) {
//already have this selected, so toggle to other of normal/super version
weapon_num = get_alternate_weapon(current, weapon_num);
weapon_status = player_has_secondary_weapon(player_info, weapon_num);
}
else {
const auto weapon_num_save = weapon_num;
//go to last-select version of requested missile
if (last_was_super)
weapon_num = get_super_weapon_from_base_weapon(weapon_num);
weapon_status = player_has_secondary_weapon(player_info, weapon_num);
//if don't have last-selected, try other version
if ((weapon_status.flags() & has_flag) != has_flag) {
weapon_num = get_alternate_weapon(weapon_num, weapon_num_save);
weapon_status = player_has_secondary_weapon(player_info, weapon_num);
if ((weapon_status.flags() & has_flag) != has_flag)
weapon_num = get_alternate_weapon(weapon_num, weapon_num_save);
}
}
//if we don't have the weapon we're switching to, give error & bail
const auto weapon_name = SECONDARY_WEAPON_NAMES(weapon_num);
if ((weapon_status.flags() & has_flag) != has_flag) {
HUD_init_message(HM_DEFAULT, "%s %s%s", TXT_HAVE_NO, weapon_name, TXT_SX);
digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
return;
}
//now actually select the weapon
#endif
select_secondary_weapon(player_info, weapon_name, weapon_num, 1);
}
}
namespace {
template <typename T>
void auto_select_weapon(T t)
{
for (uint_fast32_t cur_order_slot = 0; cur_order_slot != t.max_weapons + 1; ++cur_order_slot)
{
const auto weapon_type = t.get_weapon_by_order_slot(cur_order_slot);
if (weapon_type >= t.max_weapons)
{
t.abandon_auto_select();
return;
}
if (t.maybe_select_weapon_by_type(weapon_type))
return;
}
}
}
namespace dsx {
// ----------------------------------------------------------------------------------------
// Automatically select next best weapon if unable to fire current weapon.
// Weapon type: 0==primary, 1==secondary
void auto_select_primary_weapon(player_info &player_info)
{
if (!player_has_primary_weapon(player_info, player_info.Primary_weapon).has_all())
auto_select_weapon(cycle_primary_state(player_info));
}
void auto_select_secondary_weapon(player_info &player_info)
{
if (!player_has_secondary_weapon(player_info, player_info.Secondary_weapon).has_all())
auto_select_weapon(cycle_secondary_state(player_info));
}
void delayed_autoselect(player_info &player_info, const control_info &Controls)
{
if (!Controls.state.fire_primary)
{
auto &Primary_weapon = player_info.Primary_weapon;
const auto primary_weapon = Primary_weapon.get_active();
const auto delayed_primary = Primary_weapon.get_delayed();
if (delayed_primary != primary_weapon)
{
if (player_has_primary_weapon(player_info, delayed_primary).has_all())
select_primary_weapon(player_info, nullptr, delayed_primary, 1);
else
Primary_weapon.set_delayed(primary_weapon);
}
}
if (!Controls.state.fire_secondary)
{
auto &Secondary_weapon = player_info.Secondary_weapon;
const auto secondary_weapon = Secondary_weapon.get_active();
const auto delayed_secondary = Secondary_weapon.get_delayed();
if (delayed_secondary != secondary_weapon)
{
if (player_has_secondary_weapon(player_info, delayed_secondary).has_all())
select_secondary_weapon(player_info, nullptr, delayed_secondary, 1);
else
Secondary_weapon.set_delayed(secondary_weapon);
}
}
}
namespace {
static void maybe_autoselect_primary_weapon(player_info &player_info, primary_weapon_index_t weapon_index, const control_info &Controls)
{
const auto want_switch = [weapon_index, &player_info]{
const auto cutpoint = POrderList(cycle_primary_state::cycle_never_autoselect_below);
const auto weapon_order = POrderList(weapon_index);
return weapon_order < cutpoint && weapon_order < POrderList(get_mapped_weapon_index(player_info, player_info.Primary_weapon.get_delayed()));
};
if (Controls.state.fire_primary && PlayerCfg.NoFireAutoselect != FiringAutoselectMode::Immediate)
{
if (PlayerCfg.NoFireAutoselect == FiringAutoselectMode::Delayed)
{
if (want_switch())
player_info.Primary_weapon.set_delayed(weapon_index);
}
}
else if (want_switch())
select_primary_weapon(player_info, nullptr, weapon_index, 1);
}
}
// ---------------------------------------------------------------------
//called when one of these weapons is picked up
//when you pick up a secondary, you always get the weapon & ammo for it
// Returns true if powerup picked up, else returns false.
int pick_up_secondary(player_info &player_info, secondary_weapon_index_t weapon_index, int count, const control_info &Controls)
{
int num_picked_up;
const auto max = PLAYER_MAX_AMMO(player_info.powerup_flags, Secondary_ammo_max[weapon_index]);
auto &secondary_ammo = player_info.secondary_ammo;
if (secondary_ammo[weapon_index] >= max)
{
HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, "%s %i %ss!", TXT_ALREADY_HAVE, secondary_ammo[weapon_index], SECONDARY_WEAPON_NAMES(weapon_index));
return 0;
}
secondary_ammo[weapon_index] += count;
num_picked_up = count;
if (secondary_ammo[weapon_index] > max)
{
num_picked_up = count - (secondary_ammo[weapon_index] - max);
secondary_ammo[weapon_index] = max;
}
if (secondary_ammo[weapon_index] == count) // only autoselect if player didn't have any
{
const auto weapon_order = SOrderList(weapon_index);
auto &Secondary_weapon = player_info.Secondary_weapon;
const auto want_switch = [weapon_order, &secondary_ammo, &Secondary_weapon]{
return weapon_order < SOrderList(cycle_secondary_state::cycle_never_autoselect_below) && (
secondary_ammo[Secondary_weapon.get_delayed()] == 0 ||
weapon_order < SOrderList(Secondary_weapon.get_delayed())
);
};
if (Controls.state.fire_secondary && PlayerCfg.NoFireAutoselect != FiringAutoselectMode::Immediate)
{
if (PlayerCfg.NoFireAutoselect == FiringAutoselectMode::Delayed)
{
if (want_switch())
Secondary_weapon.set_delayed(weapon_index);
}
}
else if (want_switch())
select_secondary_weapon(player_info, nullptr, weapon_index, 1);
#if defined(DXX_BUILD_DESCENT_II)
//if it's a proxbomb or smart mine,
//we want to do a mini-auto-selection that applies to the drop bomb key
if (weapon_index_is_player_bomb(weapon_index) &&
!weapon_index_is_player_bomb(player_info.Secondary_weapon))
{
const auto mask = 1 << PROXIMITY_INDEX;
if (weapon_order < SOrderList((player_info.Secondary_last_was_super & mask) ? SMART_MINE_INDEX : PROXIMITY_INDEX))
set_weapon_last_was_super(player_info.Secondary_last_was_super, mask, weapon_index == SMART_MINE_INDEX);
}
#endif
}
//note: flash for all but concussion was 7,14,21
if (num_picked_up>1) {
PALETTE_FLASH_ADD(15,15,15);
HUD_init_message(HM_DEFAULT, "%d %s%s",num_picked_up,SECONDARY_WEAPON_NAMES(weapon_index), TXT_SX);
}
else {
PALETTE_FLASH_ADD(10,10,10);
HUD_init_message(HM_DEFAULT, "%s!",SECONDARY_WEAPON_NAMES(weapon_index));
}
return 1;
}
}
namespace {
template <typename cycle_weapon_state>
static void ReorderWeapon()
{
auto menu = window_create<weapon_reorder_menu<cycle_weapon_state>>(grd_curscreen->sc_canvas);
(void)menu;
}
}
namespace dsx {
void ReorderPrimary()
{
ReorderWeapon<cycle_primary_state>();
}
void ReorderSecondary()
{
ReorderWeapon<cycle_secondary_state>();
}
}
namespace {
template <typename T>
static uint_fast32_t search_weapon_order_list(uint8_t goal)
{
for (uint_fast32_t i = 0; i != T::max_weapons + 1; ++i)
if (T::get_weapon_by_order_slot(i) == goal)
return i;
T::report_runtime_error(T::error_weapon_list_corrupt);
}
}
namespace dsx {
namespace {
uint_fast32_t POrderList (primary_weapon_index_t num)
{
return search_weapon_order_list<cycle_primary_state>(num);
}
uint_fast32_t SOrderList (secondary_weapon_index_t num)
{
return search_weapon_order_list<cycle_secondary_state>(num);
}
}
//called when a primary weapon is picked up
//returns true if actually picked up
int pick_up_primary(player_info &player_info, const primary_weapon_index_t weapon_index)
{
ushort flag = HAS_PRIMARY_FLAG(weapon_index);
if (weapon_index != primary_weapon_index_t::LASER_INDEX &&
(player_info.primary_weapon_flags & flag))
{ //already have
HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, "%s %s!", TXT_ALREADY_HAVE_THE, PRIMARY_WEAPON_NAMES(weapon_index));
return 0;
}
player_info.primary_weapon_flags |= flag;
maybe_autoselect_primary_weapon(player_info, weapon_index, Controls);
PALETTE_FLASH_ADD(7,14,21);
if (weapon_index != primary_weapon_index_t::LASER_INDEX)
HUD_init_message(HM_DEFAULT, "%s!",PRIMARY_WEAPON_NAMES(weapon_index));
return 1;
}
#if defined(DXX_BUILD_DESCENT_II)
void check_to_use_primary_super_laser(player_info &player_info)
{
if (!(player_info.primary_weapon_flags & HAS_SUPER_LASER_FLAG))
{
const auto weapon_index = primary_weapon_index_t::SUPER_LASER_INDEX;
const auto pwi = POrderList(weapon_index);
if (pwi < POrderList(cycle_primary_state::cycle_never_autoselect_below) &&
pwi < POrderList(player_info.Primary_weapon))
{
select_primary_weapon(player_info, nullptr, primary_weapon_index_t::LASER_INDEX, 1);
}
}
PALETTE_FLASH_ADD(7,14,21);
}
#endif
namespace {
static void maybe_autoselect_vulcan_weapon(player_info &player_info)
{
#if defined(DXX_BUILD_DESCENT_I)
const auto weapon_flag_mask = HAS_VULCAN_FLAG;
#elif defined(DXX_BUILD_DESCENT_II)
const auto weapon_flag_mask = HAS_VULCAN_FLAG | HAS_GAUSS_FLAG;
#endif
const auto primary_weapon_flags = player_info.primary_weapon_flags;
if (!(primary_weapon_flags & weapon_flag_mask))
return;
const auto cutpoint = POrderList(cycle_primary_state::cycle_never_autoselect_below);
auto weapon_index = primary_weapon_index_t::VULCAN_INDEX;
#if defined(DXX_BUILD_DESCENT_I)
const auto weapon_order_vulcan = POrderList(primary_weapon_index_t::VULCAN_INDEX);
const auto better = weapon_order_vulcan;
#elif defined(DXX_BUILD_DESCENT_II)
/* If a weapon is missing, pretend its auto-select priority is equal
* to cutpoint. Priority at or worse than cutpoint is never
* auto-selected.
*/
const auto weapon_order_vulcan = (primary_weapon_flags & HAS_VULCAN_FLAG)
? POrderList(primary_weapon_index_t::VULCAN_INDEX)
: cutpoint;
const auto weapon_order_gauss = (primary_weapon_flags & HAS_GAUSS_FLAG)
? POrderList(primary_weapon_index_t::GAUSS_INDEX)
: cutpoint;
/* Set better to whichever vulcan-based weapon is higher priority.
* The chosen weapon might still be worse than cutpoint.
*/
const auto better = (weapon_order_vulcan < weapon_order_gauss)
? weapon_order_vulcan
: (weapon_index = primary_weapon_index_t::GAUSS_INDEX, weapon_order_gauss);
#endif
if (better >= cutpoint)
/* Preferred weapon is not auto-selectable */
return;
if (better >= POrderList(get_mapped_weapon_index(player_info, player_info.Primary_weapon)))
/* Preferred weapon is not as desirable as the current weapon */
return;
maybe_autoselect_primary_weapon(player_info, weapon_index, Controls);
}
}
//called when ammo (for the vulcan cannon) is picked up
// Returns the amount picked up
int pick_up_vulcan_ammo(player_info &player_info, uint_fast32_t ammo_count, const bool change_weapon)
{
const auto max = PLAYER_MAX_AMMO(player_info.powerup_flags, VULCAN_AMMO_MAX);
auto &plr_vulcan_ammo = player_info.vulcan_ammo;
const auto old_ammo = plr_vulcan_ammo;
if (old_ammo >= max)
return 0;
plr_vulcan_ammo += ammo_count;
if (plr_vulcan_ammo > max) {
ammo_count += (max - plr_vulcan_ammo);
plr_vulcan_ammo = max;
}
if (change_weapon &&
!old_ammo)
maybe_autoselect_vulcan_weapon(player_info);
return ammo_count; //return amount used
}
#if defined(DXX_BUILD_DESCENT_II)
// Homing weapons cheat
void weapons_homing_all()
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
range_for(auto &&objp, vmobjptr)
if (objp->type == OBJ_WEAPON)
objp->ctype.laser_info.track_goal = object_none;
range_for (auto &w, Weapon_info)
w.homing_flag |= 2;
}
void weapons_homing_all_reset()
{
range_for (auto &w, Weapon_info)
w.homing_flag &= ~2;
}
#define SMEGA_SHAKE_TIME (F1_0*2)
// Call this to initialize for a new level.
// Sets all super mega missile detonation times to 0 which means there aren't any.
void init_smega_detonates()
{
LevelUniqueSeismicState.Earthshaker_detonate_times = {};
}
constexpr std::integral_constant<int, SOUND_SEISMIC_DISTURBANCE_START> Seismic_sound{};
namespace {
static void start_seismic_sound()
{
if (LevelUniqueSeismicState.Next_seismic_sound_time)
return;
LevelUniqueSeismicState.Next_seismic_sound_time = GameTime64 + d_rand()/2;
digi_play_sample_looping(Seismic_sound, F1_0, -1, -1);
}
static void apply_seismic_effect(const int entry_fc)
{
const auto fc = std::min(std::max(entry_fc, 16), 1);
LevelUniqueSeismicState.Seismic_tremor_volume += fc;
if (!d_tick_step)
return;
const auto get_base_disturbance = [fc]() {
return fixmul(d_rand() - 16384, 3 * F1_0 / 16 + (F1_0 * (16 - fc)) / 32);
};
const fix disturb_x = get_base_disturbance();
const fix disturb_z = get_base_disturbance();
{
auto &rotvel = ConsoleObject->mtype.phys_info.rotvel;
rotvel.x += disturb_x;
rotvel.z += disturb_z;
}
// Shake the buddy!
auto &BuddyState = LevelUniqueObjectState.BuddyState;
const auto Buddy_objnum = BuddyState.Buddy_objnum;
if (Buddy_objnum != object_none) {
auto &objp = *LevelUniqueObjectState.Objects.vmptr(Buddy_objnum);
auto &rotvel = objp.mtype.phys_info.rotvel;
rotvel.x += disturb_x * 4;
rotvel.z += disturb_z * 4;
}
// Shake a guided missile!
LevelUniqueSeismicState.Seismic_tremor_magnitude += disturb_x;
}
}
// If a smega missile been detonated, rock the mine!
// This should be called every frame.
// Maybe this should affect all robots, being called when they get their physics done.
void rock_the_mine_frame(void)
{
range_for (auto &i, LevelUniqueSeismicState.Earthshaker_detonate_times)
{
if (i != 0) {
fix delta_time = GameTime64 - i;
start_seismic_sound();
if (delta_time < SMEGA_SHAKE_TIME) {
// Control center destroyed, rock the player's ship.
// -- fc = abs(delta_time - SMEGA_SHAKE_TIME/2);
// Changed 10/23/95 to make decreasing for super mega missile.
const int fc = (SMEGA_SHAKE_TIME - delta_time) / 2;
apply_seismic_effect(fc / (SMEGA_SHAKE_TIME / 32));
} else
i = 0;
}
}
// Hook in the rumble sound effect here.
}
void init_seismic_disturbances()
{
LevelUniqueSeismicState.Seismic_disturbance_end_time = 0;
}
namespace {
// Return true if time to start a seismic disturbance.
static bool seismic_disturbance_active()
{
const auto level_shake_duration = LevelSharedSeismicState.Level_shake_duration;
if (level_shake_duration < 1)
return false;
if (LevelUniqueSeismicState.Seismic_disturbance_end_time && LevelUniqueSeismicState.Seismic_disturbance_end_time < GameTime64)
return true;
bool rval;
rval = (2 * fixmul(d_rand(), LevelSharedSeismicState.Level_shake_frequency)) < FrameTime;
if (rval) {
LevelUniqueSeismicState.Seismic_disturbance_end_time = GameTime64 + level_shake_duration;
start_seismic_sound();
if (Game_mode & GM_MULTI)
multi_send_seismic(level_shake_duration);
}
return rval;
}
static void seismic_disturbance_frame(void)
{
if (LevelSharedSeismicState.Level_shake_frequency) {
if (seismic_disturbance_active()) {
fix delta_time = static_cast<fix>(GameTime64 - LevelUniqueSeismicState.Seismic_disturbance_end_time);
const int fc = abs(delta_time - LevelSharedSeismicState.Level_shake_duration / 2);
apply_seismic_effect(fc / (F1_0 / 16));
}
}
}
}
// Call this when a smega detonates to start the process of rocking the mine.
void smega_rock_stuff(void)
{
auto least = &LevelUniqueSeismicState.Earthshaker_detonate_times[0];
range_for (auto &i, LevelUniqueSeismicState.Earthshaker_detonate_times)
{
if (i + SMEGA_SHAKE_TIME < GameTime64)
i = 0;
if (*least > i)
least = &i;
}
*least = GameTime64;
}
static int Super_mines_yes = 1;
namespace {
static bool immediate_detonate_smart_mine(const vcobjptridx_t smart_mine, const vcobjptridx_t target)
{
if (smart_mine->segnum == target->segnum)
return true;
// Object which is close enough to detonate smart mine is not in same segment as smart mine.
// Need to do a more expensive check to make sure there isn't an obstruction.
if (likely((d_tick_count ^ (static_cast<vcobjptridx_t::integral_type>(smart_mine) + static_cast<vcobjptridx_t::integral_type>(target))) % 4))
// Maybe next frame
return false;
fvi_query fq{};
fvi_info hit_data;
fq.startseg = smart_mine->segnum;
fq.p0 = &smart_mine->pos;
fq.p1 = &target->pos;
fq.thisobjnum = smart_mine;
auto fate = find_vector_intersection(fq, hit_data);
return fate != HIT_WALL;
}
}
// Call this once/frame to process all super mines in the level.
void process_super_mines_frame(void)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptridx = Objects.vmptridx;
int start, add;
// If we don't know of there being any super mines in the level, just
// check every 8th object each frame.
if (Super_mines_yes == 0) {
start = d_tick_count & 7;
add = 8;
} else {
start = 0;
add = 1;
}
Super_mines_yes = 0;
for (objnum_t i=start; i<=Highest_object_index; i+=add) {
const auto io = vmobjptridx(i);
if (likely(io->type != OBJ_WEAPON || get_weapon_id(io) != weapon_id_type::SUPERPROX_ID))
continue;
Super_mines_yes = 1;
if (unlikely(io->lifeleft + F1_0*2 >= Weapon_info[weapon_id_type::SUPERPROX_ID].lifetime))
continue;
const auto parent_num = io->ctype.laser_info.parent_num;
const auto &bombpos = io->pos;
range_for (const auto &&jo, vmobjptridx)
{
if (unlikely(jo == parent_num))
continue;
if (jo->type != OBJ_PLAYER && jo->type != OBJ_ROBOT)
continue;
const auto dist_squared = vm_vec_dist2(bombpos, jo->pos);
const vm_distance distance_threshold{F1_0 * 20};
const auto distance_threshold_squared = distance_threshold * distance_threshold;
if (likely(distance_threshold_squared < dist_squared))
/* Cheap check, some false negatives */
continue;
const fix64 j_size = jo->size;
const fix64 j_size_squared = j_size * j_size;
if (dist_squared - j_size_squared >= distance_threshold_squared)
/* Accurate check */
continue;
if (immediate_detonate_smart_mine(io, jo))
io->lifeleft = 1;
}
}
}
#endif
#define SPIT_SPEED 20
//this function is for when the player intentionally drops a powerup
//this function is based on drop_powerup()
imobjptridx_t spit_powerup(const d_vclip_array &Vclip, const object_base &spitter, const unsigned id, const unsigned seed)
{
d_srand(seed);
auto new_velocity = vm_vec_scale_add(spitter.mtype.phys_info.velocity, spitter.orient.fvec, i2f(SPIT_SPEED));
new_velocity.x += (d_rand() - 16384) * SPIT_SPEED * 2;
new_velocity.y += (d_rand() - 16384) * SPIT_SPEED * 2;
new_velocity.z += (d_rand() - 16384) * SPIT_SPEED * 2;
// Give keys zero velocity so they can be tracked better in multi
if ((Game_mode & GM_MULTI) && (id >= POW_KEY_BLUE) && (id <= POW_KEY_GOLD))
vm_vec_zero(new_velocity);
//there's a piece of code which lets the player pick up a powerup if
//the distance between him and the powerup is less than 2 time their
//combined radii. So we need to create powerups pretty far out from
//the player.
const auto new_pos = vm_vec_scale_add(spitter.pos, spitter.orient.fvec, spitter.size);
if (Game_mode & GM_MULTI)
{
if (Net_create_loc >= MAX_NET_CREATE_OBJECTS)
{
return object_none;
}
}
const auto &&obj = obj_create(OBJ_POWERUP, id, vmsegptridx(spitter.segnum), new_pos, &vmd_identity_matrix, Powerup_info[id].size, object::control_type::powerup, object::movement_type::physics, RT_POWERUP);
if (obj == object_none)
{
Int3();
return object_none;
}
obj->mtype.phys_info.velocity = new_velocity;
obj->mtype.phys_info.drag = 512; //1024;
obj->mtype.phys_info.mass = F1_0;
obj->mtype.phys_info.flags = PF_BOUNCE;
obj->rtype.vclip_info.vclip_num = Powerup_info[get_powerup_id(obj)].vclip_num;
obj->rtype.vclip_info.frametime = Vclip[obj->rtype.vclip_info.vclip_num].frame_time;
obj->rtype.vclip_info.framenum = 0;
if (&spitter == ConsoleObject)
obj->ctype.powerup_info.flags |= PF_SPAT_BY_PLAYER;
switch (get_powerup_id(obj)) {
case POW_MISSILE_1:
case POW_MISSILE_4:
case POW_SHIELD_BOOST:
case POW_ENERGY:
obj->lifeleft = (d_rand() + F1_0*3) * 64; // Lives for 3 to 3.5 binary minutes (a binary minute is 64 seconds)
if (Game_mode & GM_MULTI)
obj->lifeleft /= 2;
break;
default:
//if (Game_mode & GM_MULTI)
// obj->lifeleft = (d_rand() + F1_0*3) * 64; // Lives for 5 to 5.5 binary minutes (a binary minute is 64 seconds)
break;
}
return obj;
}
void DropCurrentWeapon (player_info &player_info)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
if (LevelUniqueObjectState.num_objects >= Objects.size())
return;
powerup_type_t drop_type;
const auto &Primary_weapon = player_info.Primary_weapon;
const auto GrantedItems = (Game_mode & GM_MULTI) ? Netgame.SpawnGrantedItems : 0;
auto weapon_name = PRIMARY_WEAPON_NAMES(Primary_weapon);
if (Primary_weapon == primary_weapon_index_t::LASER_INDEX)
{
if ((player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS) && !GrantedItems.has_quad_laser())
{
/* Sorry, no message. Need to fall through in case player
* wanted to drop a laser powerup.
*/
drop_type = POW_QUAD_FIRE;
weapon_name = TXT_QUAD_LASERS;
}
else if (player_info.laser_level == laser_level::_1)
{
HUD_init_message_literal(HM_DEFAULT, "You cannot drop your base weapon!");
return;
}
#if defined(DXX_BUILD_DESCENT_II)
else if (player_info.laser_level > MAX_LASER_LEVEL)
{
/* Disallow dropping any super lasers until someone requests
* it.
*/
HUD_init_message_literal(HM_DEFAULT, "You cannot drop super lasers!");
return;
}
#endif
else if (player_info.laser_level <= map_granted_flags_to_laser_level(GrantedItems))
{
HUD_init_message_literal(HM_DEFAULT, "You cannot drop granted lasers!");
return;
}
else
drop_type = POW_LASER;
}
else
{
if (HAS_PRIMARY_FLAG(Primary_weapon) & map_granted_flags_to_primary_weapon_flags(GrantedItems))
{
HUD_init_message(HM_DEFAULT, "You cannot drop granted %s!", weapon_name);
return;
}
drop_type = Primary_weapon_to_powerup[Primary_weapon];
}
const auto seed = d_rand();
const auto objnum = spit_powerup(Vclip, vmobjptr(ConsoleObject), drop_type, seed);
if (objnum == object_none)
{
HUD_init_message(HM_DEFAULT, "Failed to drop %s!", weapon_name);
return;
}
HUD_init_message(HM_DEFAULT, "%s dropped!", weapon_name);
#if defined(DXX_BUILD_DESCENT_II)
digi_play_sample (SOUND_DROP_WEAPON,F1_0);
#endif
if (weapon_index_uses_vulcan_ammo(Primary_weapon)) {
//if it's one of these, drop some ammo with the weapon
auto &plr_vulcan_ammo = player_info.vulcan_ammo;
auto ammo = plr_vulcan_ammo;
#if defined(DXX_BUILD_DESCENT_II)
const auto HAS_VULCAN_AND_GAUSS_FLAGS = HAS_VULCAN_FLAG | HAS_GAUSS_FLAG;
if ((player_info.primary_weapon_flags & HAS_VULCAN_AND_GAUSS_FLAGS) == HAS_VULCAN_AND_GAUSS_FLAGS)
ammo /= 2; //if both vulcan & gauss, drop half
#endif
plr_vulcan_ammo -= ammo;
objnum->ctype.powerup_info.count = ammo;
}
#if defined(DXX_BUILD_DESCENT_II)
if (Primary_weapon == primary_weapon_index_t::OMEGA_INDEX) {
//dropped weapon has current energy
objnum->ctype.powerup_info.count = player_info.Omega_charge;
}
#endif
if (Game_mode & GM_MULTI)
multi_send_drop_weapon(objnum,seed);
if (Primary_weapon == primary_weapon_index_t::LASER_INDEX)
{
if (drop_type == POW_QUAD_FIRE)
player_info.powerup_flags &= ~PLAYER_FLAGS_QUAD_LASERS;
else
-- player_info.laser_level;
}
else
player_info.primary_weapon_flags &= ~HAS_PRIMARY_FLAG(Primary_weapon);
auto_select_primary_weapon(player_info);
}
void DropSecondaryWeapon (player_info &player_info)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
int seed;
ushort sub_ammo=0;
if (LevelUniqueObjectState.num_objects >= Objects.size())
return;
auto &Secondary_weapon = player_info.Secondary_weapon;
auto &secondary_ammo = player_info.secondary_ammo[Secondary_weapon];
if (secondary_ammo == 0)
{
HUD_init_message_literal(HM_DEFAULT, "No secondary weapon to drop!");
return;
}
auto weapon_drop_id = Secondary_weapon_to_powerup[Secondary_weapon];
// see if we drop single or 4-pack
switch (weapon_drop_id)
{
case POW_MISSILE_1:
case POW_HOMING_AMMO_1:
#if defined(DXX_BUILD_DESCENT_II)
case POW_SMISSILE1_1:
case POW_GUIDED_MISSILE_1:
case POW_MERCURY_MISSILE_1:
#endif
if (secondary_ammo % 4)
{
sub_ammo = 1;
}
else
{
sub_ammo = 4;
//4-pack always is next index
weapon_drop_id = static_cast<powerup_type_t>(1 + static_cast<uint_fast32_t>(weapon_drop_id));
}
break;
case POW_PROXIMITY_WEAPON:
#if defined(DXX_BUILD_DESCENT_II)
case POW_SMART_MINE:
#endif
if (secondary_ammo < 4)
{
HUD_init_message_literal(HM_DEFAULT, "You need at least 4 to drop!");
return;
}
else
{
sub_ammo = 4;
}
break;
case POW_SMARTBOMB_WEAPON:
case POW_MEGA_WEAPON:
#if defined(DXX_BUILD_DESCENT_II)
case POW_EARTHSHAKER_MISSILE:
#endif
sub_ammo = 1;
break;
case POW_EXTRA_LIFE:
case POW_ENERGY:
case POW_SHIELD_BOOST:
case POW_LASER:
case POW_KEY_BLUE:
case POW_KEY_RED:
case POW_KEY_GOLD:
case POW_MISSILE_4:
case POW_QUAD_FIRE:
case POW_VULCAN_WEAPON:
case POW_SPREADFIRE_WEAPON:
case POW_PLASMA_WEAPON:
case POW_FUSION_WEAPON:
case POW_HOMING_AMMO_4:
case POW_VULCAN_AMMO:
case POW_CLOAK:
case POW_TURBO:
case POW_INVULNERABILITY:
case POW_MEGAWOW:
#if defined(DXX_BUILD_DESCENT_II)
case POW_GAUSS_WEAPON:
case POW_HELIX_WEAPON:
case POW_PHOENIX_WEAPON:
case POW_OMEGA_WEAPON:
case POW_SUPER_LASER:
case POW_FULL_MAP:
case POW_CONVERTER:
case POW_AMMO_RACK:
case POW_AFTERBURNER:
case POW_HEADLIGHT:
case POW_SMISSILE1_4:
case POW_GUIDED_MISSILE_4:
case POW_MERCURY_MISSILE_4:
case POW_FLAG_BLUE:
case POW_FLAG_RED:
case POW_HOARD_ORB:
#endif
break;
}
HUD_init_message(HM_DEFAULT, "%s dropped!",SECONDARY_WEAPON_NAMES(Secondary_weapon));
#if defined(DXX_BUILD_DESCENT_II)
digi_play_sample (SOUND_DROP_WEAPON,F1_0);
#endif
seed = d_rand();
auto objnum = spit_powerup(Vclip, vmobjptr(ConsoleObject), weapon_drop_id, seed);
if (objnum == object_none)
return;
if (Game_mode & GM_MULTI)
multi_send_drop_weapon(objnum,seed);
secondary_ammo -= sub_ammo;
if (secondary_ammo == 0)
{
auto_select_secondary_weapon(player_info);
}
}
#if defined(DXX_BUILD_DESCENT_II)
// ---------------------------------------------------------------------------------------
// Do seismic disturbance stuff including the looping sounds with changing volume.
void do_seismic_stuff(void)
{
const auto stv_save = std::exchange(LevelUniqueSeismicState.Seismic_tremor_volume, 0);
LevelUniqueSeismicState.Seismic_tremor_magnitude = 0;
rock_the_mine_frame();
seismic_disturbance_frame();
if (stv_save != 0) {
const auto Seismic_tremor_volume = LevelUniqueSeismicState.Seismic_tremor_volume;
if (Seismic_tremor_volume == 0)
{
digi_stop_looping_sound();
LevelUniqueSeismicState.Next_seismic_sound_time = 0;
}
else if (GameTime64 > LevelUniqueSeismicState.Next_seismic_sound_time)
{
int volume;
volume = Seismic_tremor_volume * 2048;
if (volume > F1_0)
volume = F1_0;
digi_change_looping_volume(volume);
LevelUniqueSeismicState.Next_seismic_sound_time = GameTime64 + d_rand()/4 + 8192;
}
}
}
#endif
}
DEFINE_BITMAP_SERIAL_UDT();
#if defined(DXX_BUILD_DESCENT_I)
DEFINE_SERIAL_UDT_TO_MESSAGE(dsx::weapon_info, w, (w.render, 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));
#elif defined(DXX_BUILD_DESCENT_II)
namespace {
struct v2_weapon_info : weapon_info
{
};
}
template <typename Accessor>
void postprocess_udt(Accessor &, v2_weapon_info &w)
{
w.children = weapon_id_type::unspecified;
w.multi_damage_scale = F1_0;
w.hires_picture = w.picture;
}
DEFINE_SERIAL_UDT_TO_MESSAGE(v2_weapon_info, w, (w.render, 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));
DEFINE_SERIAL_UDT_TO_MESSAGE(weapon_info, w, (w.render, 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));
#endif
#if 0
void weapon_info_write(PHYSFS_File *fp, const weapon_info &w)
{
PHYSFSX_serialize_write(fp, w);
}
#endif
/*
* reads n weapon_info structs from a PHYSFS_File
*/
namespace dsx {
void weapon_info_read_n(weapon_info_array &wi, std::size_t count, PHYSFS_File *fp, int file_version, std::size_t offset)
{
auto r = partial_range(wi, offset, count);
#if defined(DXX_BUILD_DESCENT_I)
(void)file_version;
#elif defined(DXX_BUILD_DESCENT_II)
if (file_version < 3)
{
range_for (auto &w, r)
PHYSFSX_serialize_read(fp, static_cast<v2_weapon_info &>(w));
/* Set the type of children correctly when using old
* datafiles. In earlier descent versions this was simply
* hard-coded in create_smart_children().
*/
wi[weapon_id_type::SMART_ID].children = weapon_id_type::PLAYER_SMART_HOMING_ID;
wi[weapon_id_type::SUPERPROX_ID].children = weapon_id_type::SMART_MINE_HOMING_ID;
return;
}
#endif
range_for (auto &w, r)
{
PHYSFSX_serialize_read(fp, w);
}
}
}