- Change D1X to use D2X rules regarding Vulcan cannon pickup. The D1X
rules were confusing at best, and seem outright wrong in some ways.
- When a Vulcan cannon was picked up, it was treated as containing not
less than VULCAN_WEAPON_AMMO_AMOUNT rounds, regardless of what it
actually contained. D2X respected the actual contained count, even
when running in D1X emulation mode.
- In D1X single player, if the Vulcan cannon was not picked up, then
it could be treated as Vulcan ammo. If at least 1 unit of
ammunition was added to the player, the entire powerup would be
consumed, regardless of how much ammunition remained.
- In D2X single player, if the Vulcan cannon was not picked up, then
ammunition would be taken from it, but the powerup would only be
consumed if all its ammunition was taken.
- In D1X multiplayer, a player who already had a Vulcan cannon could
not get anything from touching a new cannon, and the cannon would
not be changed.
- In D2X multiplayer, a player who already had a Vulcan cannon could
take ammunition from the cannon, but the cannon could not be drained
below VULCAN_AMMO_AMOUNT. If the cannon had VULCAN_AMMO_AMOUNT or
less, then the player could not take ammunition. If the cannon had
more, the player could drain it to that level.
- Replace all that with a simplified version of the D2X rules:
- If the player does not have the cannon, the cannon is picked up
and removed from the mine. The player takes as much of its
ammunition as possible. If the cannon was well stocked, and the
player was nearly full, some ammunition will be lost. This is
unfortunate, but the game has always had this rule, and changing
it would require dropping one or more Vulcan Ammo packs to
represent the untaken ammunition.
- If the player already had that cannon, then the player takes as
much ammunition as the cannon has, while not exceeding the
ammunition cap. Other players, if any, are updated about how much
ammunition remains in the cannon. The cannon remains in the mine.
- Backport to D1X the network message for updating the contained
ammunition in a vulcan cannon. zico added the basic feature in
7684ce92
, but only applied it to D2X. With the change to let D1X
multiplayer take ammunition from the cannon, D1X now needs the same
feature.
- Remove the special case to delete an empty cannon. Instead, let the
cannon remain in the mine without ammunition. This allows a player in
single player mode to leave behind a backup cannon, which could be
useful if the player is killed and wishes to rearm before returning to
the death site. Similarly, under the new rule that players in
multiplayer can drain the cannon down to 0 ammunition, this removal
allows the cannon to remain behind for someone else to take, rather
than allowing it to be deleted by a player who already had an instance
of it.
733 lines
22 KiB
C++
733 lines
22 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.
|
|
*/
|
|
|
|
/*
|
|
*
|
|
* Code for powerup objects.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "maths.h"
|
|
#include "vecmat.h"
|
|
#include "gr.h"
|
|
#include "3d.h"
|
|
#include "dxxerror.h"
|
|
#include "inferno.h"
|
|
#include "object.h"
|
|
#include "game.h"
|
|
#include "fireball.h"
|
|
#include "powerup.h"
|
|
#include "gauges.h"
|
|
#include "sounds.h"
|
|
#include "player.h"
|
|
#include "physfs-serial.h"
|
|
#include "text.h"
|
|
#include "weapon.h"
|
|
#include "laser.h"
|
|
#include "scores.h"
|
|
#include "multi.h"
|
|
#include "segment.h"
|
|
#include "controls.h"
|
|
#include "kconfig.h"
|
|
#include "newdemo.h"
|
|
#include "escort.h"
|
|
#if DXX_USE_EDITOR
|
|
#include "gr.h" // for powerup outline drawing
|
|
#endif
|
|
#include "hudmsg.h"
|
|
#include "playsave.h"
|
|
#include "d_enumerate.h"
|
|
#include "d_levelstate.h"
|
|
#include "partial_range.h"
|
|
#include "vclip.h"
|
|
|
|
namespace dcx {
|
|
unsigned N_powerup_types;
|
|
}
|
|
namespace dsx {
|
|
d_powerup_info_array Powerup_info;
|
|
|
|
//process this powerup for this frame
|
|
void do_powerup_frame(const d_vclip_array &Vclip, const vmobjptridx_t obj)
|
|
{
|
|
vclip_info *vci = &obj->rtype.vclip_info;
|
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
const fix fudge = 0;
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
long objnum = obj;
|
|
const fix fudge = (FrameTime * (objnum&3)) >> 4;
|
|
#endif
|
|
|
|
auto &vc = Vclip[vci->vclip_num];
|
|
const auto vc_frame_time = vc.frame_time;
|
|
if (vc_frame_time > 0)
|
|
{
|
|
const auto vc_num_frames1 = vc.num_frames - 1;
|
|
vci->frametime -= FrameTime+fudge;
|
|
|
|
while (vci->frametime < 0 ) {
|
|
|
|
vci->frametime += vc_frame_time;
|
|
if (vci->framenum > vc_num_frames1)
|
|
vci->framenum=0;
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (objnum&1)
|
|
{
|
|
if (-- vci->framenum > vc_num_frames1)
|
|
vci->framenum = vc_num_frames1;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (vci->framenum >= vc_num_frames1)
|
|
vci->framenum=0;
|
|
else
|
|
vci->framenum++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (obj->lifeleft <= 0) {
|
|
object_create_explosion(vmsegptridx(obj->segnum), obj->pos, F1_0*7/2, VCLIP_POWERUP_DISAPPEARANCE);
|
|
|
|
if ( Vclip[VCLIP_POWERUP_DISAPPEARANCE].sound_num > -1 )
|
|
digi_link_sound_to_object(Vclip[VCLIP_POWERUP_DISAPPEARANCE].sound_num, obj, 0, F1_0, sound_stack::allow_stacking);
|
|
}
|
|
}
|
|
|
|
void draw_powerup(const d_vclip_array &Vclip, grs_canvas &canvas, const object_base &obj)
|
|
{
|
|
auto &vci = obj.rtype.vclip_info;
|
|
draw_object_blob(canvas, obj, Vclip[vci.vclip_num].frames[vci.framenum]);
|
|
}
|
|
|
|
namespace {
|
|
|
|
static void _powerup_basic_nonhud(int redadd, int greenadd, int blueadd, int score)
|
|
{
|
|
PALETTE_FLASH_ADD(redadd,greenadd,blueadd);
|
|
add_points_to_score(ConsoleObject->ctype.player_info, score, Game_mode);
|
|
}
|
|
|
|
#define powerup_basic(A1,A2,A3,A4,F,...) dxx_call_printf_checked(powerup_basic,powerup_basic_str,(A1,A2,A3,A4),(F),##__VA_ARGS__)
|
|
__attribute_format_printf(5, 6)
|
|
void (powerup_basic)(int redadd, int greenadd, int blueadd, int score, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, format );
|
|
HUD_init_message_va(HM_DEFAULT, format, args);
|
|
va_end(args);
|
|
_powerup_basic_nonhud(redadd, greenadd, blueadd, score);
|
|
}
|
|
|
|
}
|
|
|
|
void powerup_basic_str(int redadd, int greenadd, int blueadd, int score, const char *str)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, str);
|
|
_powerup_basic_nonhud(redadd, greenadd, blueadd, score);
|
|
}
|
|
|
|
//#ifndef RELEASE
|
|
// Give the megawow powerup!
|
|
void do_megawow_powerup(object &plrobj, const int quantity)
|
|
{
|
|
powerup_basic(30, 0, 30, 1, "MEGA-WOWIE-ZOWIE!");
|
|
auto &player_info = plrobj.ctype.player_info;
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
player_info.primary_weapon_flags = (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG | HAS_PLASMA_FLAG | HAS_FUSION_FLAG);
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
player_info.primary_weapon_flags = (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG | HAS_PLASMA_FLAG | HAS_FUSION_FLAG) | (HAS_GAUSS_FLAG | HAS_HELIX_FLAG | HAS_PHOENIX_FLAG | HAS_OMEGA_FLAG);
|
|
#endif
|
|
player_info.vulcan_ammo = VULCAN_AMMO_MAX;
|
|
|
|
auto &secondary_ammo = player_info.secondary_ammo;
|
|
range_for (auto &i, partial_range(secondary_ammo, 3u))
|
|
i = quantity;
|
|
|
|
range_for (auto &i, partial_range(secondary_ammo, 3u, secondary_ammo.size()))
|
|
i = quantity/5;
|
|
|
|
player_info.energy = F1_0*200;
|
|
plrobj.shields = F1_0*200;
|
|
player_info.powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
const auto laser_level = MAX_LASER_LEVEL;
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
player_info.Omega_charge = MAX_OMEGA_CHARGE;
|
|
if (game_mode_hoard())
|
|
player_info.hoard.orbs = player_info.max_hoard_orbs;
|
|
const auto laser_level = MAX_SUPER_LASER_LEVEL;
|
|
#endif
|
|
if (Newdemo_state == ND_STATE_RECORDING)
|
|
newdemo_record_laser_level(player_info.laser_level, laser_level);
|
|
player_info.laser_level = laser_level;
|
|
}
|
|
//#endif
|
|
|
|
namespace {
|
|
|
|
static int pick_up_energy(player_info &player_info)
|
|
{
|
|
int used=0;
|
|
|
|
auto &energy = player_info.energy;
|
|
if (energy < MAX_ENERGY) {
|
|
fix boost;
|
|
const auto Difficulty_level = GameUniqueState.Difficulty_level;
|
|
boost = 3*F1_0 + 3*F1_0*(NDL - Difficulty_level);
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (Difficulty_level == 0)
|
|
boost += boost/2;
|
|
#endif
|
|
energy += boost;
|
|
if (energy > MAX_ENERGY)
|
|
energy = MAX_ENERGY;
|
|
powerup_basic(15, 15, 7, ENERGY_SCORE, "%s %s %d", TXT_ENERGY, TXT_BOOSTED_TO, f2ir(energy));
|
|
used=1;
|
|
} else
|
|
HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, TXT_MAXED_OUT,TXT_ENERGY);
|
|
|
|
return used;
|
|
}
|
|
|
|
static int pick_up_primary_or_energy(player_info &player_info, const primary_weapon_index_t weapon_index)
|
|
{
|
|
const auto used = pick_up_primary(player_info, weapon_index);
|
|
if (used || (Game_mode & GM_MULTI))
|
|
return used;
|
|
return pick_up_energy(player_info);
|
|
}
|
|
|
|
static int pick_up_vulcan_ammo(player_info &player_info)
|
|
{
|
|
int used=0;
|
|
if (pick_up_vulcan_ammo(player_info, VULCAN_AMMO_AMOUNT, false)) {
|
|
powerup_basic(7, 14, 21, VULCAN_AMMO_SCORE, "%s!", TXT_VULCAN_AMMO);
|
|
used = 1;
|
|
} else {
|
|
const auto max = PLAYER_MAX_AMMO(player_info.powerup_flags, VULCAN_AMMO_MAX);
|
|
HUD_init_message(HM_DEFAULT | HM_REDUNDANT | HM_MAYDUPL, "%s %d %s!", TXT_ALREADY_HAVE, vulcan_ammo_scale(max), TXT_VULCAN_ROUNDS);
|
|
used = 0;
|
|
}
|
|
return used;
|
|
}
|
|
|
|
static int pick_up_key(const int r, const int g, const int b, player_flags &player_flags, const PLAYER_FLAG key_flag, const char *const key_name, const powerup_type_t id)
|
|
{
|
|
if (player_flags & key_flag)
|
|
return 0;
|
|
player_flags |= key_flag;
|
|
powerup_basic(r, g, b, KEY_SCORE, "%s %s", key_name, TXT_ACCESS_GRANTED);
|
|
multi_digi_play_sample(Powerup_info[id].hit_sound, F1_0);
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
auto &BuddyState = LevelUniqueObjectState.BuddyState;
|
|
invalidate_escort_goal(BuddyState);
|
|
#endif
|
|
return (Game_mode & GM_MULTI) ? 0 : 1;
|
|
}
|
|
|
|
// returns true if powerup consumed
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
template <int r, int g, int b>
|
|
struct player_hit_basic_silent_powerup
|
|
{
|
|
const char *const desc_pickup;
|
|
player_hit_basic_silent_powerup(const char *const p) :
|
|
desc_pickup(p)
|
|
{
|
|
}
|
|
void report() const
|
|
{
|
|
powerup_basic_str(r, g, b, 0, desc_pickup);
|
|
}
|
|
template <PLAYER_FLAG player_flag>
|
|
void pickup(player_flags &powerup_flags) const
|
|
{
|
|
powerup_flags |= player_flag;
|
|
report();
|
|
}
|
|
};
|
|
|
|
template <int r, int g, int b, powerup_type_t id>
|
|
struct player_hit_basic_sound_powerup : player_hit_basic_silent_powerup<r, g, b>
|
|
{
|
|
using base_type = player_hit_basic_silent_powerup<r, g, b>;
|
|
using base_type::base_type;
|
|
template <PLAYER_FLAG player_flag>
|
|
void pickup(player_flags &powerup_flags) const
|
|
{
|
|
multi_digi_play_sample(Powerup_info[id].hit_sound, F1_0);
|
|
base_type::template pickup<player_flag>(powerup_flags);
|
|
}
|
|
};
|
|
|
|
using player_hit_silent_rb_powerup = player_hit_basic_silent_powerup<15, 0, 15>;
|
|
|
|
struct player_hit_afterburner_powerup : player_hit_basic_sound_powerup<15, 15, 15, POW_AFTERBURNER>
|
|
{
|
|
using base_type = player_hit_basic_sound_powerup<15, 15, 15, POW_AFTERBURNER>;
|
|
using base_type::base_type;
|
|
template <PLAYER_FLAG player_flag>
|
|
void pickup(player_flags &powerup_flags) const
|
|
{
|
|
Afterburner_charge = f1_0;
|
|
base_type::template pickup<player_flag>(powerup_flags);
|
|
}
|
|
};
|
|
|
|
struct player_hit_headlight_powerup
|
|
{
|
|
/* Template parameter unused, but required for signature
|
|
* compatibility with the other player_hit_* structures.
|
|
*/
|
|
template <PLAYER_FLAG>
|
|
void pickup(player_flags &powerup_flags) const
|
|
{
|
|
process(powerup_flags);
|
|
}
|
|
void process(player_flags &powerup_flags) const
|
|
{
|
|
const auto active = PlayerCfg.HeadlightActiveDefault;
|
|
powerup_flags |= active
|
|
? PLAYER_FLAG::HEADLIGHT_PRESENT_AND_ON
|
|
: PLAYER_FLAG::HEADLIGHT;
|
|
powerup_basic(15, 0, 15, 0, "HEADLIGHT BOOST! (Headlight is O%s)", active ? "N" : "FF");
|
|
multi_digi_play_sample(Powerup_info[POW_HEADLIGHT].hit_sound, F1_0);
|
|
if (active && (Game_mode & GM_MULTI))
|
|
multi_send_flags (Player_num);
|
|
}
|
|
};
|
|
|
|
template <unsigned TEAM>
|
|
static int player_hit_flag_powerup(player_info &player_info, const char *const desc)
|
|
{
|
|
if (!game_mode_capture_flag())
|
|
return 0;
|
|
const auto pnum = Player_num;
|
|
if (get_team(pnum) == TEAM)
|
|
{
|
|
player_info.powerup_flags |= PLAYER_FLAGS_FLAG;
|
|
powerup_basic_str(15, 0, 15, 0, desc);
|
|
multi_send_got_flag(pnum);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
struct player_hit_quadlaser_powerup
|
|
{
|
|
/* Template parameter unused, but required for signature
|
|
* compatibility with the other player_hit_* structures.
|
|
*/
|
|
template <PLAYER_FLAG>
|
|
void pickup(player_flags &powerup_flags) const
|
|
{
|
|
process(powerup_flags);
|
|
}
|
|
void process(player_flags &powerup_flags) const
|
|
{
|
|
powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
|
|
powerup_basic(15, 15, 7, QUAD_FIRE_SCORE, "%s!", TXT_QUAD_LASERS);
|
|
update_laser_weapon_info();
|
|
}
|
|
};
|
|
|
|
static int player_has_powerup(player_info &player_info, const char *const desc_have)
|
|
{
|
|
HUD_init_message(HM_DEFAULT | HM_REDUNDANT | HM_MAYDUPL, "%s %s!", TXT_ALREADY_HAVE, desc_have);
|
|
return (Game_mode & GM_MULTI) ? 0 : pick_up_energy(player_info);
|
|
}
|
|
|
|
template <PLAYER_FLAG player_flag, typename F>
|
|
static int player_hit_powerup(player_info &player_info, const char *const desc_have, const F &&pickup)
|
|
{
|
|
auto &powerup_flags = player_info.powerup_flags;
|
|
return (powerup_flags & player_flag)
|
|
? player_has_powerup(player_info, desc_have)
|
|
: (pickup.template pickup<player_flag>(powerup_flags), 1);
|
|
}
|
|
|
|
}
|
|
|
|
int do_powerup(const vmobjptridx_t obj)
|
|
{
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &vcobjptr = Objects.vcptr;
|
|
auto &vmobjptr = Objects.vmptr;
|
|
int used=0;
|
|
int special_used=0; //for when hitting vulcan cannon gets vulcan ammo
|
|
|
|
if (Player_dead_state != player_dead_state::no ||
|
|
ConsoleObject->type == OBJ_GHOST ||
|
|
get_local_plrobj().shields < 0)
|
|
return 0;
|
|
|
|
if ((obj->ctype.powerup_info.flags & PF_SPAT_BY_PLAYER) && obj->ctype.powerup_info.creation_time>0 && GameTime64<obj->ctype.powerup_info.creation_time+i2f(2))
|
|
return 0; //not enough time elapsed
|
|
|
|
if (Game_mode & GM_MULTI)
|
|
{
|
|
/*
|
|
* The fact: Collecting a powerup is decided Client-side and due to PING it takes time for other players to know if one collected a powerup actually. This may lead to the case two players collect the same powerup!
|
|
* The solution: Let us check if someone else is closer to a powerup and if so, do not collect it.
|
|
* NOTE: Player positions computed by 'shortpos' and PING can still cause a small margin of error.
|
|
*/
|
|
vms_vector tvec;
|
|
const fix mydist = vm_vec_normalized_dir(tvec, obj->pos, ConsoleObject->pos);
|
|
|
|
for (auto &&[i, plr] : enumerate(Players))
|
|
{
|
|
if (i == Player_num)
|
|
continue;
|
|
if (plr.connected != CONNECT_PLAYING)
|
|
continue;
|
|
auto &o = *vcobjptr(plr.objnum);
|
|
if (o.type == OBJ_GHOST)
|
|
continue;
|
|
if (mydist > vm_vec_normalized_dir(tvec, obj->pos, o.pos))
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
auto &plrobj = get_local_plrobj();
|
|
auto &player_info = plrobj.ctype.player_info;
|
|
auto id = get_powerup_id(obj);
|
|
switch (id)
|
|
{
|
|
case POW_EXTRA_LIFE:
|
|
get_local_player().lives++;
|
|
powerup_basic_str(15, 15, 15, 0, TXT_EXTRA_LIFE);
|
|
used=1;
|
|
break;
|
|
case POW_ENERGY:
|
|
used = pick_up_energy(player_info);
|
|
break;
|
|
case POW_SHIELD_BOOST:
|
|
{
|
|
auto &shields = plrobj.shields;
|
|
if (shields < MAX_SHIELDS) {
|
|
const auto Difficulty_level = GameUniqueState.Difficulty_level;
|
|
fix boost = 3*F1_0 + 3*F1_0*(NDL - Difficulty_level);
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (Difficulty_level == 0)
|
|
boost += boost/2;
|
|
#endif
|
|
shields += boost;
|
|
if (shields > MAX_SHIELDS)
|
|
shields = MAX_SHIELDS;
|
|
powerup_basic(0, 0, 15, SHIELD_SCORE, "%s %s %d", TXT_SHIELD, TXT_BOOSTED_TO, f2ir(shields));
|
|
used=1;
|
|
} else
|
|
HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, TXT_MAXED_OUT,TXT_SHIELD);
|
|
break;
|
|
}
|
|
case POW_LASER:
|
|
if (player_info.laser_level >= MAX_LASER_LEVEL) {
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
player_info.laser_level = MAX_LASER_LEVEL;
|
|
#endif
|
|
HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, TXT_MAXED_OUT,TXT_LASER);
|
|
} else {
|
|
const auto level_before_powerup = player_info.laser_level;
|
|
++ player_info.laser_level;
|
|
const auto level_after_powerup = player_info.laser_level;
|
|
if (Newdemo_state == ND_STATE_RECORDING)
|
|
newdemo_record_laser_level(level_before_powerup, level_after_powerup);
|
|
powerup_basic(10, 0, 10, LASER_SCORE, "%s %s %u", TXT_LASER, TXT_BOOSTED_TO, static_cast<unsigned>(level_after_powerup) + 1);
|
|
pick_up_primary(player_info, primary_weapon_index_t::LASER_INDEX);
|
|
used=1;
|
|
}
|
|
if (!used && !(Game_mode & GM_MULTI) )
|
|
used = pick_up_energy(player_info);
|
|
break;
|
|
case POW_MISSILE_1:
|
|
used = pick_up_secondary(player_info, CONCUSSION_INDEX, 1, Controls);
|
|
break;
|
|
case POW_MISSILE_4:
|
|
used = pick_up_secondary(player_info, CONCUSSION_INDEX, 4, Controls);
|
|
break;
|
|
|
|
case POW_KEY_BLUE:
|
|
used = pick_up_key(0, 0, 15, player_info.powerup_flags, PLAYER_FLAGS_BLUE_KEY, TXT_BLUE, id);
|
|
break;
|
|
case POW_KEY_RED:
|
|
used = pick_up_key(15, 0, 0, player_info.powerup_flags, PLAYER_FLAGS_RED_KEY, TXT_RED, id);
|
|
break;
|
|
case POW_KEY_GOLD:
|
|
used = pick_up_key(15, 15, 7, player_info.powerup_flags, PLAYER_FLAGS_GOLD_KEY, TXT_YELLOW, id);
|
|
break;
|
|
case POW_QUAD_FIRE:
|
|
used = player_hit_powerup<PLAYER_FLAGS_QUAD_LASERS>(player_info, TXT_QUAD_LASERS, player_hit_quadlaser_powerup());
|
|
break;
|
|
|
|
case POW_VULCAN_WEAPON:
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
case POW_GAUSS_WEAPON:
|
|
#endif
|
|
{
|
|
used = pick_up_primary(player_info,
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
(id == POW_GAUSS_WEAPON)
|
|
? primary_weapon_index_t::GAUSS_INDEX
|
|
:
|
|
#endif
|
|
primary_weapon_index_t::VULCAN_INDEX
|
|
);
|
|
if (const auto ammo_used = pick_up_vulcan_ammo(player_info, obj->ctype.powerup_info.count))
|
|
{
|
|
/* Even if the cannon is made empty, leave `used` set to
|
|
* false, so that the cannon can remain in the mine.
|
|
*/
|
|
obj->ctype.powerup_info.count -= ammo_used;
|
|
if (!used)
|
|
{
|
|
powerup_basic(7, 14, 21, VULCAN_AMMO_SCORE, "%s!", TXT_VULCAN_AMMO);
|
|
special_used = 1;
|
|
id = POW_VULCAN_AMMO; //set new id for making sound at end of this function
|
|
if (Game_mode & GM_MULTI)
|
|
multi_send_vulcan_weapon_ammo_adjust(obj); // let other players know how much ammo we took.
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case POW_SPREADFIRE_WEAPON:
|
|
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::SPREADFIRE_INDEX);
|
|
break;
|
|
case POW_PLASMA_WEAPON:
|
|
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::PLASMA_INDEX);
|
|
break;
|
|
case POW_FUSION_WEAPON:
|
|
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::FUSION_INDEX);
|
|
break;
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
case POW_HELIX_WEAPON:
|
|
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::HELIX_INDEX);
|
|
break;
|
|
|
|
case POW_PHOENIX_WEAPON:
|
|
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::PHOENIX_INDEX);
|
|
break;
|
|
|
|
case POW_OMEGA_WEAPON:
|
|
used = pick_up_primary(player_info, primary_weapon_index_t::OMEGA_INDEX);
|
|
if (used)
|
|
player_info.Omega_charge = obj->ctype.powerup_info.count;
|
|
if (!used && !(Game_mode & GM_MULTI) )
|
|
used = pick_up_energy(player_info);
|
|
break;
|
|
#endif
|
|
|
|
case POW_PROXIMITY_WEAPON:
|
|
used = pick_up_secondary(player_info, PROXIMITY_INDEX, 4, Controls);
|
|
break;
|
|
case POW_SMARTBOMB_WEAPON:
|
|
used = pick_up_secondary(player_info, SMART_INDEX, 1, Controls);
|
|
break;
|
|
case POW_MEGA_WEAPON:
|
|
used = pick_up_secondary(player_info, MEGA_INDEX, 1, Controls);
|
|
break;
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
case POW_SMISSILE1_1:
|
|
used = pick_up_secondary(player_info, SMISSILE1_INDEX, 1, Controls);
|
|
break;
|
|
case POW_SMISSILE1_4:
|
|
used = pick_up_secondary(player_info, SMISSILE1_INDEX, 4, Controls);
|
|
break;
|
|
case POW_GUIDED_MISSILE_1:
|
|
used = pick_up_secondary(player_info, GUIDED_INDEX, 1, Controls);
|
|
break;
|
|
case POW_GUIDED_MISSILE_4:
|
|
used = pick_up_secondary(player_info, GUIDED_INDEX, 4, Controls);
|
|
break;
|
|
case POW_SMART_MINE:
|
|
used = pick_up_secondary(player_info, SMART_MINE_INDEX, 4, Controls);
|
|
break;
|
|
case POW_MERCURY_MISSILE_1:
|
|
used = pick_up_secondary(player_info, SMISSILE4_INDEX, 1, Controls);
|
|
break;
|
|
case POW_MERCURY_MISSILE_4:
|
|
used = pick_up_secondary(player_info, SMISSILE4_INDEX, 4, Controls);
|
|
break;
|
|
case POW_EARTHSHAKER_MISSILE:
|
|
used = pick_up_secondary(player_info, SMISSILE5_INDEX, 1, Controls);
|
|
break;
|
|
#endif
|
|
case POW_VULCAN_AMMO:
|
|
used = pick_up_vulcan_ammo(player_info);
|
|
break;
|
|
case POW_HOMING_AMMO_1:
|
|
used = pick_up_secondary(player_info, HOMING_INDEX, 1, Controls);
|
|
break;
|
|
case POW_HOMING_AMMO_4:
|
|
used = pick_up_secondary(player_info, HOMING_INDEX, 4, Controls);
|
|
break;
|
|
case POW_CLOAK:
|
|
if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) {
|
|
HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, "%s %s!",TXT_ALREADY_ARE,TXT_CLOAKED);
|
|
break;
|
|
} else {
|
|
player_info.cloak_time = GameTime64; // Not! changed by awareness events (like player fires laser).
|
|
player_info.powerup_flags |= PLAYER_FLAGS_CLOAKED;
|
|
ai_do_cloak_stuff();
|
|
if (Game_mode & GM_MULTI)
|
|
multi_send_cloak();
|
|
powerup_basic(-10,-10,-10, CLOAK_SCORE, "%s!",TXT_CLOAKING_DEVICE);
|
|
used = 1;
|
|
break;
|
|
}
|
|
case POW_INVULNERABILITY:
|
|
{
|
|
auto &pl_flags = player_info.powerup_flags;
|
|
if (pl_flags & PLAYER_FLAGS_INVULNERABLE) {
|
|
if (!player_info.FakingInvul)
|
|
{
|
|
HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, "%s %s!",TXT_ALREADY_ARE,TXT_INVULNERABLE);
|
|
break;
|
|
}
|
|
}
|
|
player_info.FakingInvul = 0;
|
|
pl_flags |= PLAYER_FLAGS_INVULNERABLE;
|
|
player_info.invulnerable_time = GameTime64;
|
|
powerup_basic(7, 14, 21, INVULNERABILITY_SCORE, "%s!",TXT_INVULNERABILITY);
|
|
used = 1;
|
|
break;
|
|
}
|
|
#ifndef RELEASE
|
|
case POW_MEGAWOW:
|
|
do_megawow_powerup(plrobj, 50);
|
|
used = 1;
|
|
break;
|
|
#endif
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
case POW_FULL_MAP:
|
|
used = player_hit_powerup<PLAYER_FLAGS_MAP_ALL>(player_info, "the FULL MAP", player_hit_silent_rb_powerup("FULL MAP!"));
|
|
break;
|
|
|
|
case POW_CONVERTER:
|
|
used = player_hit_powerup<PLAYER_FLAGS_CONVERTER>(player_info, "the Converter", player_hit_silent_rb_powerup("Energy -> shield converter!"));
|
|
break;
|
|
|
|
case POW_SUPER_LASER:
|
|
if (player_info.laser_level >= MAX_SUPER_LASER_LEVEL)
|
|
{
|
|
player_info.laser_level = MAX_SUPER_LASER_LEVEL;
|
|
HUD_init_message_literal(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, "SUPER LASER MAXED OUT!");
|
|
} else {
|
|
const auto old_level = player_info.laser_level;
|
|
|
|
if (player_info.laser_level <= MAX_LASER_LEVEL)
|
|
player_info.laser_level = MAX_LASER_LEVEL;
|
|
++ player_info.laser_level;
|
|
if (Newdemo_state == ND_STATE_RECORDING)
|
|
newdemo_record_laser_level(old_level, player_info.laser_level);
|
|
powerup_basic(10, 0, 10, LASER_SCORE, "Super Boost to Laser level %u", static_cast<unsigned>(player_info.laser_level) + 1);
|
|
if (player_info.Primary_weapon != primary_weapon_index_t::LASER_INDEX)
|
|
check_to_use_primary_super_laser(player_info);
|
|
used=1;
|
|
}
|
|
if (!used && !(Game_mode & GM_MULTI) )
|
|
used = pick_up_energy(player_info);
|
|
break;
|
|
|
|
case POW_AMMO_RACK:
|
|
used = player_hit_powerup<PLAYER_FLAGS_AMMO_RACK>(player_info, "the Ammo rack", player_hit_basic_sound_powerup<15, 0, 15, POW_AMMO_RACK>("AMMO RACK!"));
|
|
break;
|
|
|
|
case POW_AFTERBURNER:
|
|
used = player_hit_powerup<PLAYER_FLAGS_AFTERBURNER>(player_info, "the Afterburner", player_hit_afterburner_powerup("AFTERBURNER!"));
|
|
break;
|
|
|
|
case POW_HEADLIGHT:
|
|
used = player_hit_powerup<PLAYER_FLAGS_HEADLIGHT>(player_info, "the Headlight boost", player_hit_headlight_powerup());
|
|
break;
|
|
|
|
case POW_FLAG_BLUE:
|
|
used = player_hit_flag_powerup<TEAM_RED>(player_info, "BLUE FLAG!");
|
|
break;
|
|
|
|
case POW_HOARD_ORB:
|
|
if (game_mode_hoard())
|
|
{
|
|
auto &proximity = player_info.hoard.orbs;
|
|
if (proximity < player_info.max_hoard_orbs)
|
|
{
|
|
++ proximity;
|
|
powerup_basic(15, 0, 15, 0, "Orb!!!");
|
|
player_info.powerup_flags |= PLAYER_FLAGS_FLAG;
|
|
used=1;
|
|
multi_send_got_orb (Player_num);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case POW_FLAG_RED:
|
|
used = player_hit_flag_powerup<TEAM_BLUE>(player_info, "RED FLAG!");
|
|
break;
|
|
|
|
// case POW_HOARD_ORB:
|
|
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//always say used, until physics problem (getting stuck on unused powerup)
|
|
//is solved. Note also the break statements above that are commented out
|
|
//!! used=1;
|
|
|
|
if ((used || special_used) && Powerup_info[id].hit_sound > -1 ) {
|
|
multi_digi_play_sample(Powerup_info[id].hit_sound, F1_0);
|
|
detect_escort_goal_accomplished(obj);
|
|
}
|
|
|
|
return used;
|
|
|
|
}
|
|
}
|
|
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(powerup_type_info, pti, (pti.vclip_num, pti.hit_sound, pti.size, pti.light));
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(powerup_type_info, 16);
|
|
|
|
namespace dcx {
|
|
|
|
void powerup_type_info_read(PHYSFS_File *fp, powerup_type_info &pti)
|
|
{
|
|
PHYSFSX_serialize_read(fp, pti);
|
|
}
|
|
|
|
void powerup_type_info_write(PHYSFS_File *fp, const powerup_type_info &pti)
|
|
{
|
|
PHYSFSX_serialize_write(fp, pti);
|
|
}
|
|
|
|
}
|