dxx-rebirth/similar/main/powerup.cpp

733 lines
22 KiB
C++
Raw Normal View History

2006-03-20 17:12:09 +00:00
/*
2014-06-01 17:55:23 +00:00
* 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.
2006-03-20 17:12:09 +00:00
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>
2012-07-01 02:54:33 +00:00
#include "maths.h"
2006-03-20 17:12:09 +00:00
#include "vecmat.h"
#include "gr.h"
#include "3d.h"
#include "dxxerror.h"
2006-03-20 17:12:09 +00:00
#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"
2006-03-20 17:12:09 +00:00
#include "text.h"
#include "weapon.h"
#include "laser.h"
#include "scores.h"
#include "multi.h"
2015-01-29 04:27:36 +00:00
#include "segment.h"
2006-03-20 17:12:09 +00:00
#include "controls.h"
#include "kconfig.h"
#include "newdemo.h"
#include "escort.h"
#if DXX_USE_EDITOR
2006-03-20 17:12:09 +00:00
#include "gr.h" // for powerup outline drawing
#endif
2015-04-19 04:18:51 +00:00
#include "hudmsg.h"
#include "playsave.h"
#include "d_enumerate.h"
#include "d_levelstate.h"
#include "partial_range.h"
#include "vclip.h"
2006-03-20 17:12:09 +00:00
namespace dcx {
2014-01-18 18:02:02 +00:00
unsigned N_powerup_types;
}
namespace dsx {
2018-10-21 00:24:07 +00:00
d_powerup_info_array Powerup_info;
2006-03-20 17:12:09 +00:00
//process this powerup for this frame
2018-10-21 00:24:07 +00:00
void do_powerup_frame(const d_vclip_array &Vclip, const vmobjptridx_t obj)
2006-03-20 17:12:09 +00:00
{
vclip_info *vci = &obj->rtype.vclip_info;
#if defined(DXX_BUILD_DESCENT_I)
2016-07-16 16:52:04 +00:00
const fix fudge = 0;
#elif defined(DXX_BUILD_DESCENT_II)
long objnum = obj;
2016-07-16 16:52:04 +00:00
const fix fudge = (FrameTime * (objnum&3)) >> 4;
#endif
2006-03-20 17:12:09 +00:00
2016-07-16 16:52:04 +00:00
auto &vc = Vclip[vci->vclip_num];
const auto vc_frame_time = vc.frame_time;
if (vc_frame_time > 0)
{
2016-07-16 16:52:04 +00:00
const auto vc_num_frames1 = vc.num_frames - 1;
2006-03-20 17:12:09 +00:00
vci->frametime -= FrameTime+fudge;
while (vci->frametime < 0 ) {
2016-07-16 16:52:04 +00:00
vci->frametime += vc_frame_time;
if (vci->framenum > vc_num_frames1)
2006-03-20 17:12:09 +00:00
vci->framenum=0;
#if defined(DXX_BUILD_DESCENT_II)
if (objnum&1)
{
2016-07-16 16:52:04 +00:00
if (-- vci->framenum > vc_num_frames1)
vci->framenum = vc_num_frames1;
}
else
#endif
{
2016-07-16 16:52:04 +00:00
if (vci->framenum >= vc_num_frames1)
vci->framenum=0;
else
vci->framenum++;
}
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
if (obj->lifeleft <= 0) {
object_create_explosion(vmsegptridx(obj->segnum), obj->pos, F1_0*7/2, VCLIP_POWERUP_DISAPPEARANCE);
2006-03-20 17:12:09 +00:00
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);
2006-03-20 17:12:09 +00:00
}
}
2018-10-21 00:24:07 +00:00
void draw_powerup(const d_vclip_array &Vclip, grs_canvas &canvas, const object_base &obj)
2006-03-20 17:12:09 +00:00
{
2016-04-06 03:34:14 +00:00
auto &vci = obj.rtype.vclip_info;
2022-06-05 17:44:52 +00:00
draw_object_blob(GameBitmaps, *Viewer, canvas, obj, Vclip[vci.vclip_num].frames[vci.framenum]);
2016-04-06 03:34:14 +00:00
}
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, ...)
2006-03-20 17:12:09 +00:00
{
va_list args;
va_start(args, format );
HUD_init_message_va(HM_DEFAULT, format, args);
2006-03-20 17:12:09 +00:00
va_end(args);
_powerup_basic_nonhud(redadd, greenadd, blueadd, score);
}
2006-03-20 17:12:09 +00:00
}
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);
2006-03-20 17:12:09 +00:00
}
//#ifndef RELEASE
// Give the megawow powerup!
void do_megawow_powerup(object &plrobj, const int quantity)
2006-03-20 17:12:09 +00:00
{
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;
2006-03-20 17:12:09 +00:00
auto &secondary_ammo = player_info.secondary_ammo;
range_for (auto &i, partial_range(secondary_ammo, 3u))
i = quantity;
2006-03-20 17:12:09 +00:00
range_for (auto &i, partial_range(secondary_ammo, 3u, secondary_ammo.size()))
i = quantity/5;
2006-03-20 17:12:09 +00:00
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())
2017-03-18 18:07:36 +00:00
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;
}
2006-03-20 17:12:09 +00:00
//#endif
namespace {
2016-10-02 00:34:47 +00:00
static int pick_up_energy(player_info &player_info)
2006-03-20 17:12:09 +00:00
{
int used=0;
auto &energy = player_info.energy;
if (energy < MAX_ENERGY) {
2006-03-20 17:12:09 +00:00
fix boost;
const auto Difficulty_level = GameUniqueState.Difficulty_level;
boost = 3 * F1_0 + 3 * F1_0 * (NDL - underlying_value(Difficulty_level));
#if defined(DXX_BUILD_DESCENT_II)
if (Difficulty_level == Difficulty_level_type::_0)
2006-03-20 17:12:09 +00:00
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));
2006-03-20 17:12:09 +00:00
used=1;
} else
HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, TXT_MAXED_OUT,TXT_ENERGY);
2006-03-20 17:12:09 +00:00
return used;
}
static int pick_up_primary_or_energy(player_info &player_info, const primary_weapon_index_t weapon_index)
{
2016-10-02 00:34:45 +00:00
const auto used = pick_up_primary(player_info, weapon_index);
if (used || (Game_mode & GM_MULTI))
return used;
2016-10-02 00:34:47 +00:00
return pick_up_energy(player_info);
}
static int pick_up_vulcan_ammo(player_info &player_info)
2006-03-20 17:12:09 +00:00
{
2015-04-19 04:18:52 +00:00
int used=0;
if (pick_up_vulcan_ammo(player_info, VULCAN_AMMO_AMOUNT, false)) {
2006-03-20 17:12:09 +00:00
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);
2006-03-20 17:12:09 +00:00
used = 0;
}
return used;
}
2016-04-06 03:34:14 +00:00
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
2016-04-06 03:34:14 +00:00
return (Game_mode & GM_MULTI) ? 0 : 1;
}
2006-03-20 17:12:09 +00:00
// returns true if powerup consumed
2016-08-28 22:41:47 +00:00
#if defined(DXX_BUILD_DESCENT_II)
2016-08-28 22:41:47 +00:00
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;
2016-08-28 22:41:47 +00:00
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;
2016-08-28 22:41:47 +00:00
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);
}
};
2016-08-28 22:41:47 +00:00
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
2016-08-28 22:41:47 +00:00
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();
}
};
2016-10-02 00:34:47 +00:00
static int player_has_powerup(player_info &player_info, const char *const desc_have)
2016-08-28 22:41:47 +00:00
{
HUD_init_message(HM_DEFAULT | HM_REDUNDANT | HM_MAYDUPL, "%s %s!", TXT_ALREADY_HAVE, desc_have);
2016-10-02 00:34:47 +00:00
return (Game_mode & GM_MULTI) ? 0 : pick_up_energy(player_info);
2016-08-28 22:41:47 +00:00
}
template <PLAYER_FLAG player_flag, typename F>
2016-10-02 00:34:47 +00:00
static int player_hit_powerup(player_info &player_info, const char *const desc_have, const F &&pickup)
2016-08-28 22:41:47 +00:00
{
2016-10-02 00:34:47 +00:00
auto &powerup_flags = player_info.powerup_flags;
2016-08-28 22:41:47 +00:00
return (powerup_flags & player_flag)
2016-10-02 00:34:47 +00:00
? player_has_powerup(player_info, desc_have)
2016-08-28 22:41:47 +00:00
: (pickup.template pickup<player_flag>(powerup_flags), 1);
}
}
int do_powerup(const vmobjptridx_t obj)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vcobjptr = Objects.vcptr;
auto &vmobjptr = Objects.vmptr;
2006-03-20 17:12:09 +00:00
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)
2006-03-20 17:12:09 +00:00
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))
2006-03-20 17:12:09 +00:00
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))
{
2017-08-13 20:38:31 +00:00
if (i == Player_num)
continue;
if (plr.connected != player_connection_status::playing)
2017-08-13 20:38:31 +00:00
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;
2016-04-06 03:34:14 +00:00
auto id = get_powerup_id(obj);
switch (id)
{
2006-03-20 17:12:09 +00:00
case POW_EXTRA_LIFE:
get_local_player().lives++;
powerup_basic_str(15, 15, 15, 0, TXT_EXTRA_LIFE);
2006-03-20 17:12:09 +00:00
used=1;
break;
case POW_ENERGY:
2016-10-02 00:34:47 +00:00
used = pick_up_energy(player_info);
2006-03-20 17:12:09 +00:00
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 - underlying_value(Difficulty_level));
#if defined(DXX_BUILD_DESCENT_II)
if (Difficulty_level == Difficulty_level_type::_0)
2006-03-20 17:12:09 +00:00
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));
2006-03-20 17:12:09 +00:00
used=1;
} else
HUD_init_message(HM_DEFAULT|HM_REDUNDANT|HM_MAYDUPL, TXT_MAXED_OUT,TXT_SHIELD);
2006-03-20 17:12:09 +00:00
break;
}
2006-03-20 17:12:09 +00:00
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);
2006-03-20 17:12:09 +00:00
} 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);
2016-10-02 00:34:45 +00:00
pick_up_primary(player_info, primary_weapon_index_t::LASER_INDEX);
2006-03-20 17:12:09 +00:00
used=1;
}
if (!used && !(Game_mode & GM_MULTI) )
2016-10-02 00:34:47 +00:00
used = pick_up_energy(player_info);
2006-03-20 17:12:09 +00:00
break;
case POW_MISSILE_1:
used = pick_up_secondary(player_info, CONCUSSION_INDEX, 1, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_MISSILE_4:
used = pick_up_secondary(player_info, CONCUSSION_INDEX, 4, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_KEY_BLUE:
2016-04-06 03:34:14 +00:00
used = pick_up_key(0, 0, 15, player_info.powerup_flags, PLAYER_FLAGS_BLUE_KEY, TXT_BLUE, id);
2006-03-20 17:12:09 +00:00
break;
case POW_KEY_RED:
2016-04-06 03:34:14 +00:00
used = pick_up_key(15, 0, 0, player_info.powerup_flags, PLAYER_FLAGS_RED_KEY, TXT_RED, id);
2006-03-20 17:12:09 +00:00
break;
case POW_KEY_GOLD:
2016-04-06 03:34:14 +00:00
used = pick_up_key(15, 15, 7, player_info.powerup_flags, PLAYER_FLAGS_GOLD_KEY, TXT_YELLOW, id);
2006-03-20 17:12:09 +00:00
break;
case POW_QUAD_FIRE:
2016-10-02 00:34:47 +00:00
used = player_hit_powerup<PLAYER_FLAGS_QUAD_LASERS>(player_info, TXT_QUAD_LASERS, player_hit_quadlaser_powerup());
2006-03-20 17:12:09 +00:00
break;
case POW_VULCAN_WEAPON:
Improve vulcan/gauss pickup rules - 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.
2022-02-05 13:30:56 +00:00
#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
2015-10-18 21:01:18 +00:00
);
Improve vulcan/gauss pickup rules - 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.
2022-02-05 13:30:56 +00:00
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.
*/
2006-03-20 17:12:09 +00:00
obj->ctype.powerup_info.count -= ammo_used;
Improve vulcan/gauss pickup rules - 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.
2022-02-05 13:30:56 +00:00
if (!used)
{
2006-03-20 17:12:09 +00:00
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.
2006-03-20 17:12:09 +00:00
}
}
break;
}
case POW_SPREADFIRE_WEAPON:
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::SPREADFIRE_INDEX);
2006-03-20 17:12:09 +00:00
break;
case POW_PLASMA_WEAPON:
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::PLASMA_INDEX);
2006-03-20 17:12:09 +00:00
break;
case POW_FUSION_WEAPON:
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::FUSION_INDEX);
2006-03-20 17:12:09 +00:00
break;
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
case POW_HELIX_WEAPON:
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::HELIX_INDEX);
2006-03-20 17:12:09 +00:00
break;
case POW_PHOENIX_WEAPON:
used = pick_up_primary_or_energy(player_info, primary_weapon_index_t::PHOENIX_INDEX);
2006-03-20 17:12:09 +00:00
break;
case POW_OMEGA_WEAPON:
2016-10-02 00:34:45 +00:00
used = pick_up_primary(player_info, primary_weapon_index_t::OMEGA_INDEX);
2006-03-20 17:12:09 +00:00
if (used)
2016-08-28 22:41:47 +00:00
player_info.Omega_charge = obj->ctype.powerup_info.count;
2006-03-20 17:12:09 +00:00
if (!used && !(Game_mode & GM_MULTI) )
2016-10-02 00:34:47 +00:00
used = pick_up_energy(player_info);
2006-03-20 17:12:09 +00:00
break;
#endif
2006-03-20 17:12:09 +00:00
case POW_PROXIMITY_WEAPON:
used = pick_up_secondary(player_info, PROXIMITY_INDEX, 4, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_SMARTBOMB_WEAPON:
used = pick_up_secondary(player_info, SMART_INDEX, 1, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_MEGA_WEAPON:
used = pick_up_secondary(player_info, MEGA_INDEX, 1, Controls);
2006-03-20 17:12:09 +00:00
break;
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
case POW_SMISSILE1_1:
used = pick_up_secondary(player_info, SMISSILE1_INDEX, 1, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_SMISSILE1_4:
used = pick_up_secondary(player_info, SMISSILE1_INDEX, 4, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_GUIDED_MISSILE_1:
used = pick_up_secondary(player_info, GUIDED_INDEX, 1, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_GUIDED_MISSILE_4:
used = pick_up_secondary(player_info, GUIDED_INDEX, 4, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_SMART_MINE:
used = pick_up_secondary(player_info, SMART_MINE_INDEX, 4, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_MERCURY_MISSILE_1:
used = pick_up_secondary(player_info, SMISSILE4_INDEX, 1, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_MERCURY_MISSILE_4:
used = pick_up_secondary(player_info, SMISSILE4_INDEX, 4, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_EARTHSHAKER_MISSILE:
used = pick_up_secondary(player_info, SMISSILE5_INDEX, 1, Controls);
2006-03-20 17:12:09 +00:00
break;
#endif
2006-03-20 17:12:09 +00:00
case POW_VULCAN_AMMO:
used = pick_up_vulcan_ammo(player_info);
2006-03-20 17:12:09 +00:00
break;
case POW_HOMING_AMMO_1:
used = pick_up_secondary(player_info, HOMING_INDEX, 1, Controls);
2006-03-20 17:12:09 +00:00
break;
case POW_HOMING_AMMO_4:
used = pick_up_secondary(player_info, HOMING_INDEX, 4, Controls);
2006-03-20 17:12:09 +00:00
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);
2006-03-20 17:12:09 +00:00
break;
} else {
player_info.cloak_time = GameTime64; // Not! changed by awareness events (like player fires laser).
player_info.powerup_flags |= PLAYER_FLAGS_CLOAKED;
2006-03-20 17:12:09 +00:00
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);
2006-03-20 17:12:09 +00:00
break;
}
}
player_info.FakingInvul = 0;
pl_flags |= PLAYER_FLAGS_INVULNERABLE;
player_info.invulnerable_time = GameTime64;
2006-03-20 17:12:09 +00:00
powerup_basic(7, 14, 21, INVULNERABILITY_SCORE, "%s!",TXT_INVULNERABILITY);
used = 1;
break;
}
#ifndef RELEASE
case POW_MEGAWOW:
do_megawow_powerup(plrobj, 50);
2006-03-20 17:12:09 +00:00
used = 1;
break;
#endif
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
case POW_FULL_MAP:
2016-10-02 00:34:47 +00:00
used = player_hit_powerup<PLAYER_FLAGS_MAP_ALL>(player_info, "the FULL MAP", player_hit_silent_rb_powerup("FULL MAP!"));
2006-03-20 17:12:09 +00:00
break;
case POW_CONVERTER:
2016-10-02 00:34:47 +00:00
used = player_hit_powerup<PLAYER_FLAGS_CONVERTER>(player_info, "the Converter", player_hit_silent_rb_powerup("Energy -> shield converter!"));
2006-03-20 17:12:09 +00:00
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!");
2006-03-20 17:12:09 +00:00
} else {
const auto old_level = player_info.laser_level;
2006-03-20 17:12:09 +00:00
if (player_info.laser_level <= MAX_LASER_LEVEL)
player_info.laser_level = MAX_LASER_LEVEL;
++ player_info.laser_level;
2006-03-20 17:12:09 +00:00
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);
2016-08-28 22:41:49 +00:00
if (player_info.Primary_weapon != primary_weapon_index_t::LASER_INDEX)
check_to_use_primary_super_laser(player_info);
2006-03-20 17:12:09 +00:00
used=1;
}
if (!used && !(Game_mode & GM_MULTI) )
2016-10-02 00:34:47 +00:00
used = pick_up_energy(player_info);
2006-03-20 17:12:09 +00:00
break;
case POW_AMMO_RACK:
2016-10-02 00:34:47 +00:00
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!"));
2006-03-20 17:12:09 +00:00
break;
case POW_AFTERBURNER:
2016-10-02 00:34:47 +00:00
used = player_hit_powerup<PLAYER_FLAGS_AFTERBURNER>(player_info, "the Afterburner", player_hit_afterburner_powerup("AFTERBURNER!"));
2006-03-20 17:12:09 +00:00
break;
case POW_HEADLIGHT:
2016-10-02 00:34:47 +00:00
used = player_hit_powerup<PLAYER_FLAGS_HEADLIGHT>(player_info, "the Headlight boost", player_hit_headlight_powerup());
2006-03-20 17:12:09 +00:00
break;
case POW_FLAG_BLUE:
2016-08-28 22:41:47 +00:00
used = player_hit_flag_powerup<TEAM_RED>(player_info, "BLUE FLAG!");
2006-03-20 17:12:09 +00:00
break;
case POW_HOARD_ORB:
if (game_mode_hoard())
{
2017-03-18 18:07:36 +00:00
auto &proximity = player_info.hoard.orbs;
if (proximity < player_info.max_hoard_orbs)
{
++ proximity;
2006-03-20 17:12:09 +00:00
powerup_basic(15, 0, 15, 0, "Orb!!!");
player_info.powerup_flags |= PLAYER_FLAGS_FLAG;
2006-03-20 17:12:09 +00:00
used=1;
multi_send_got_orb (Player_num);
}
}
2006-03-20 17:12:09 +00:00
break;
case POW_FLAG_RED:
2016-08-28 22:41:47 +00:00
used = player_hit_flag_powerup<TEAM_BLUE>(player_info, "RED FLAG!");
2006-03-20 17:12:09 +00:00
break;
// case POW_HOARD_ORB:
#endif
2006-03-20 17:12:09 +00:00
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 ) {
2015-08-05 02:59:02 +00:00
multi_digi_play_sample(Powerup_info[id].hit_sound, F1_0);
detect_escort_goal_accomplished(obj);
2006-03-20 17:12:09 +00:00
}
return used;
}
}
2006-03-20 17:12:09 +00:00
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);
2018-10-08 03:58:48 +00:00
namespace dcx {
void powerup_type_info_read(PHYSFS_File *fp, powerup_type_info &pti)
2006-03-20 17:12:09 +00:00
{
PHYSFSX_serialize_read(fp, pti);
}
void powerup_type_info_write(PHYSFS_File *fp, const powerup_type_info &pti)
{
PHYSFSX_serialize_write(fp, pti);
2006-03-20 17:12:09 +00:00
}
2018-10-08 03:58:48 +00:00
}