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 rendering & otherwise dealing with explosions
*
*/
2012-11-11 22:12:51 +00:00
# include <algorithm>
2016-10-02 00:34:41 +00:00
# include <numeric>
2021-09-04 12:17:14 +00:00
# include <random>
2021-09-22 21:11:42 +00:00
# include <optional>
2006-03-20 17:12:09 +00:00
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
2012-07-07 18:35:06 +00:00
# include "dxxerror.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 "inferno.h"
# include "object.h"
# include "vclip.h"
# include "game.h"
2015-04-02 02:36:57 +00:00
# include "robot.h"
2006-03-20 17:12:09 +00:00
# include "sounds.h"
# include "player.h"
# include "gauges.h"
# include "powerup.h"
# include "bm.h"
# include "ai.h"
# include "weapon.h"
# include "fireball.h"
# include "collide.h"
# include "physics.h"
# include "laser.h"
# include "wall.h"
# include "multi.h"
# include "timer.h"
2015-04-19 04:18:51 +00:00
# include "playsave.h"
2006-03-20 17:12:09 +00:00
# include "gameseg.h"
# include "automap.h"
2014-07-03 01:47:29 +00:00
# include "byteutil.h"
2006-03-20 17:12:09 +00:00
2014-07-30 02:52:35 +00:00
# include "compiler-range_for.h"
2021-09-04 12:17:14 +00:00
# include "d_array.h"
# include "d_enumerate.h"
2020-08-10 03:45:14 +00:00
# include "d_levelstate.h"
2021-09-04 12:17:14 +00:00
# include "d_range.h"
# include "d_zip.h"
2015-02-28 19:36:01 +00:00
# include "partial_range.h"
2014-07-30 02:52:35 +00:00
# include "segiter.h"
2012-11-11 22:12:51 +00:00
using std : : min ;
2006-03-20 17:12:09 +00:00
# define EXPLOSION_SCALE (F1_0*5 / 2) //explosion is the obj size times this
2018-08-12 21:08:07 +00:00
namespace dcx {
2021-09-04 12:17:14 +00:00
namespace {
/* C arrays are normally indexed starting from 0. For some use cases,
* values in [ 0 , Bias ) for some Bias are not useful . Callers must
* either waste Bias unused elements at the head of the array , or
* subtract Bias from all indexes when accessing the array . This class
* automates the latter approach . Its actual size is N , and all index
* operations are offset by subtracting Bias . As with std : : array ,
* callers are responsible for passing only valid index values .
*/
template < std : : size_t Bias , typename T , std : : size_t N >
struct biased_index_array : std : : array < T , N >
{
using base_type = std : : array < T , N > ;
using typename base_type : : reference ;
using typename base_type : : const_reference ;
using typename base_type : : size_type ;
/* Not implemented, so delete it to avoid using the base class
* implementation , which would not adjust by Bias . This could be
* implemented if needed .
*/
constexpr reference at ( size_type position ) = delete ;
constexpr reference at ( size_type position ) const = delete ;
constexpr reference operator [ ] ( size_type pos )
{
return this - > base_type : : operator [ ] ( pos - Bias ) ;
}
constexpr const_reference operator [ ] ( size_type pos ) const
{
return this - > base_type : : operator [ ] ( pos - Bias ) ;
}
[[nodiscard]]
static constexpr bool valid_index ( size_type pos )
{
return pos > = Bias & & pos - Bias < N ;
}
} ;
struct connected_segment_raw_distances
{
using segment_distance_count_type = uint8_t ;
/* Minimum depth must be at least 2, since very low values are used
* for special purposes .
*/
static constexpr std : : integral_constant < segment_distance_count_type , 4 > minimum_supported_max_depth { } ;
static constexpr std : : integral_constant < segment_distance_count_type , 30 > maximum_supported_max_depth { } ;
static constexpr std : : integral_constant < segment_distance_count_type , 1 + maximum_supported_max_depth - minimum_supported_max_depth > supported_max_depth_range { } ;
/* The value_type must be sufficient to hold the largest number of
* segments that could be at a distance of
* ` maximum_supported_max_depth ` .
*/
using depth_count_array = biased_index_array < minimum_supported_max_depth , uint16_t , supported_max_depth_range > ;
enum class biased_distance : uint8_t
{
indeterminate ,
depth_0 ,
} ;
static segment_distance_count_type get_distance_from_biased_distance ( const biased_distance d )
{
return static_cast < segment_distance_count_type > ( d ) - static_cast < unsigned > ( biased_distance : : depth_0 ) ;
}
static biased_distance get_biased_distance_from_distance ( const segment_distance_count_type d )
{
return static_cast < biased_distance > ( d + static_cast < unsigned > ( biased_distance : : depth_0 ) ) ;
}
/* To reduce the stack footprint when recursively visiting segments,
* all the arguments that remain constant during the search are
* stored in an instance of ` builder ` , and ` visit_segment ` is a
* member method on ` builder ` .
*/
struct builder
{
fvcsegptr & vcsegptr ;
fvcwallptr & vcwallptr ;
const segment_distance_count_type max_depth ;
depth_count_array & count_segments_at_depth ;
enumerated_array < biased_distance , MAX_SEGMENTS , segnum_t > & depth_by_segment ;
/* Segments that are farther away than max_depth do not have a
* specific distance computed , and are only known to be too far
* away .
*/
void visit_segment ( vcsegidx_t current_segment_idx , segment_distance_count_type current_depth ) const ;
} ;
/* Initialize all depths to 0. These will be updated as the
* constructor traverses the level . Counts are only maintained for
* supported depths . If a segment is closer than
* ` minimum_supported_max_depth ` or farther than
* ` maximum_supported_max_depth ` , then it is not counted .
*/
depth_count_array count_segments_at_depth { } ;
/* Initialize all segments to be at the indeterminate distance.
*
* Traversal of the level data will replace the placeholder distance
* with a real value . Traversal requires that every element either
* be the value ` indeterminate ` or have a valid depth from earlier
* in the traversal .
*/
static_assert ( static_cast < unsigned > ( biased_distance : : indeterminate ) = = 0 , " `indeterminate` must be 0 so that zero-initialization assigns the value `indeterminate` to every element in the array " ) ;
enumerated_array < biased_distance , MAX_SEGMENTS , segnum_t > depth_by_segment { } ;
/* Prefer that the maximum depth be passed as a
* std : : integral_constant so that it can be checked at compile time .
* Allow use of non - integral_constant expressions if the caller uses
* the explicit constructor .
*/
explicit connected_segment_raw_distances ( fvcsegptr & vcsegptr , fvcwallptr & vcwallptr , segment_distance_count_type max_depth , vcsegidx_t current_segment_idx ) ;
template < segment_distance_count_type max_depth >
connected_segment_raw_distances ( fvcsegptr & vcsegptr , fvcwallptr & vcwallptr , std : : integral_constant < segment_distance_count_type , max_depth > , vcsegidx_t current_segment_idx ) : connected_segment_raw_distances ( vcsegptr , vcwallptr , max_depth , current_segment_idx )
{
static_assert ( max_depth < = maximum_supported_max_depth ) ;
}
/* Scan depth_by_segment to find the Nth segment of the desired
* depth , where N is drawn from ` uniform_int_distribution ( 0 ,
* count_of_segments_at_depth - 1 ) ( mrd ) ` . If there are no segments
* at this depth , return an empty std : : optional .
*/
std : : optional < segnum_t > scan_segment_depths ( unsigned desired_depth , std : : minstd_rand & mrd ) const ;
} ;
}
2018-08-12 21:08:07 +00:00
unsigned Num_exploding_walls ;
void init_exploding_walls ( )
{
Num_exploding_walls = 0 ;
}
2021-09-04 12:17:14 +00:00
connected_segment_raw_distances : : connected_segment_raw_distances ( fvcsegptr & vcsegptr , fvcwallptr & vcwallptr , segment_distance_count_type max_depth , vcsegidx_t current_segment_idx )
{
builder { vcsegptr , vcwallptr , max_depth , count_segments_at_depth , depth_by_segment } . visit_segment ( current_segment_idx , 0u ) ;
}
std : : optional < segnum_t > connected_segment_raw_distances : : scan_segment_depths ( const unsigned desired_depth , std : : minstd_rand & mrd ) const
{
const auto count_segments_at_desired_depth = count_segments_at_depth [ desired_depth ] ;
if ( ! count_segments_at_desired_depth )
/* No segments exist at this depth. Move to next depth.
*/
return std : : nullopt ;
/* Pick a random segment within the segments at the selected
* depth .
*/
std : : uniform_int_distribution uid ( 0u , count_segments_at_desired_depth - 1u ) ;
/* Walk over all segments. Ignore any segment at the wrong depth.
* Ignore the first ` skip_count ` segments at the right depth .
*/
const auto initial_skip_count = uid ( mrd ) ;
unsigned skip_count = initial_skip_count ;
const auto desired_biased_depth = get_biased_distance_from_distance ( desired_depth ) ;
for ( const auto & & [ sn , biased_depth ] : enumerate ( depth_by_segment ) )
{
if ( biased_depth ! = desired_biased_depth )
continue ;
if ( ! skip_count )
/* Found a segment at the correct depth and the required
* number of segments have been skipped . Pick this segment .
*/
return sn ;
- - skip_count ;
}
/* This should not happen. `skip_count` is computed such that it is
* below the number of segments at a depth of ` desired_depth ` , so
* control should always ` return ` from the loop .
*/
con_printf ( CON_URGENT , DXX_STRINGIZE_FL ( __FILE__ , __LINE__ , " error: count=%u, skip_count=%u, and no segment found at depth %u " ) , count_segments_at_desired_depth , initial_skip_count , desired_depth ) ;
return std : : nullopt ;
}
void connected_segment_raw_distances : : builder : : visit_segment ( const vcsegidx_t current_segment_idx , const segment_distance_count_type current_depth ) const
{
auto & biased_depth = depth_by_segment [ current_segment_idx ] ;
if ( biased_depth ! = biased_distance : : indeterminate )
{
const auto d = get_distance_from_biased_distance ( biased_depth ) ;
if ( d < = current_depth )
/* This segment was already found through some other
* route . Leave that result in place .
*/
return ;
/* If this segment was previously found at a greater depth, and
* the current step found a shorter path to that segment , then
* reduce the count of segments at the greater depth , since the
* recorded depth of this segment will be changed .
*/
if ( count_segments_at_depth . valid_index ( d ) )
- - count_segments_at_depth [ d ] ;
}
biased_depth = get_biased_distance_from_distance ( current_depth ) ;
if ( current_depth > = max_depth )
return ;
const shared_segment & seg = vcsegptr ( current_segment_idx ) ;
2021-11-01 03:37:19 +00:00
if ( seg . special = = segment_special : : controlcen )
2021-09-04 12:17:14 +00:00
{
/* Every caller wants to exclude the control-center segment from
* the set of choices . If the segment is encountered here ,
* assign it a depth value that prevents it being selected , and
* stop . Stopping here prevents exploring segments beyond the
* control - center . If there is a path around the
* control - center , that path can be used to reach those segments
* instead . This will lead to a slightly higher than accurate
* distance value for those segments , but the original Descent
* implementation had a random walk that could wander about and
* assign high values to any segment it reached . By refusing to
* traverse through the control center , this implementation
* avoids selecting areas that are only reachable by flying
* through the control center .
*/
biased_depth = get_biased_distance_from_distance ( 0 ) ;
return ;
}
/* Segments closer than minimum_supported_max_depth or farther than
* maximum_supported_max_depth will not be analyzed later , so no
* count is maintained for them .
*
* This increment is deferred until after the control - center check ,
* so that it does not need to be undone when the control - center is
* found .
*/
if ( count_segments_at_depth . valid_index ( current_depth ) )
+ + count_segments_at_depth [ current_depth ] ;
for ( const auto & & [ child_segnum , side ] : zip ( seg . children , seg . sides ) )
{
if ( ! IS_CHILD ( child_segnum ) )
continue ;
if ( const auto wall_num = side . wall_num ; wall_num ! = wall_none )
{
auto & w = * vcwallptr ( wall_num ) ;
if ( w . type = = WALL_CLOSED )
continue ;
2021-11-01 03:37:19 +00:00
if ( w . type = = WALL_DOOR & & ( w . flags & wall_flag : : door_locked ) )
2021-09-04 12:17:14 +00:00
continue ;
}
visit_segment ( child_segnum , current_depth + 1 ) ;
}
}
2018-08-12 21:08:07 +00:00
}
2015-12-22 04:18:50 +00:00
namespace dsx {
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2013-11-24 05:30:37 +00:00
fix Flash_effect = 0 ;
2016-07-16 16:52:04 +00:00
constexpr int PK1 = 1 , PK2 = 8 ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2022-07-09 13:39:29 +00:00
namespace {
2017-02-11 21:42:43 +00:00
static bool can_collide ( const object * const weapon_object , const object_base & iter_object , const object * const parent_object )
2016-12-25 00:33:24 +00:00
{
2019-07-27 17:48:03 +00:00
/* `weapon_object` may not be a weapon in some cases, though the
* caller originally expected it would be . For this function , the
* distinction is currently irrelevant .
*/
2016-12-25 00:33:24 +00:00
# if defined(DXX_BUILD_DESCENT_I)
( void ) weapon_object ;
# elif defined(DXX_BUILD_DESCENT_II)
2017-02-11 21:42:43 +00:00
if ( weapon_object = = & iter_object )
2016-12-25 00:33:24 +00:00
return false ;
2016-12-25 00:33:24 +00:00
if ( iter_object . type = = OBJ_NONE )
return false ;
2016-12-25 00:33:24 +00:00
if ( iter_object . flags & OF_SHOULD_BE_DEAD )
return false ;
# endif
switch ( iter_object . type )
{
# if defined(DXX_BUILD_DESCENT_II)
case OBJ_WEAPON :
2019-08-15 01:34:22 +00:00
return is_proximity_bomb_or_player_smart_mine_or_placed_mine ( get_weapon_id ( iter_object ) ) ;
2016-12-25 00:33:24 +00:00
# endif
case OBJ_CNTRLCEN :
case OBJ_PLAYER :
return true ;
case OBJ_ROBOT :
if ( parent_object = = nullptr )
return false ;
if ( parent_object - > type ! = OBJ_ROBOT )
return true ;
return get_robot_id ( * parent_object ) ! = iter_object . id ;
default :
return false ;
}
}
2022-11-07 01:59:34 +00:00
}
imobjptridx_t object_create_explosion_without_damage ( const d_vclip_array & Vclip , const vmsegptridx_t segnum , const vms_vector & position , const fix size , const int vclip_type )
2006-03-20 17:12:09 +00:00
{
2022-07-02 18:10:45 +00:00
const auto & & obj_fireball = obj_create ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , OBJ_FIREBALL , vclip_type , segnum , position , & vmd_identity_matrix , size ,
2020-08-10 03:45:13 +00:00
object : : control_type : : explosion , object : : movement_type : : None , RT_FIREBALL ) ;
2006-03-20 17:12:09 +00:00
2019-07-27 17:48:03 +00:00
if ( obj_fireball = = object_none )
2016-10-29 23:16:15 +00:00
{
return object_none ;
2006-03-20 17:12:09 +00:00
}
//now set explosion-specific data
2019-07-27 17:48:03 +00:00
obj_fireball - > lifeleft = Vclip [ vclip_type ] . play_time ;
obj_fireball - > ctype . expl_info . spawn_time = - 1 ;
obj_fireball - > ctype . expl_info . delete_objnum = object_none ;
obj_fireball - > ctype . expl_info . delete_time = - 1 ;
2022-07-09 13:39:29 +00:00
return obj_fireball ;
}
2006-03-20 17:12:09 +00:00
2022-11-07 01:59:34 +00:00
namespace {
2022-07-09 13:39:29 +00:00
static imobjptridx_t object_create_explosion_with_damage ( const d_robot_info_array & Robot_info , const d_vclip_array & Vclip , fvmobjptridx & vmobjptridx , const imobjptridx_t obj_explosion_origin , const vmsegptridx_t segnum , const vms_vector & position , const fix size , const int vclip_type , const fix maxdamage , const fix maxdistance , const fix maxforce , const icobjptridx_t parent )
2022-07-09 13:39:29 +00:00
{
/* `obj_explosion_origin` may not be a weapon in some cases, though
* this function originally expected it would be .
*/
const auto & & obj_fireball = object_create_explosion_without_damage ( Vclip , segnum , position , size , vclip_type ) ;
if ( obj_fireball = = object_none )
return object_none ;
# if defined(DXX_BUILD_DESCENT_II)
auto & Objects = LevelUniqueObjectState . Objects ;
# endif
2006-03-20 17:12:09 +00:00
if ( maxdamage > 0 ) {
2015-03-12 02:21:19 +00:00
fix force ;
2006-03-20 17:12:09 +00:00
vms_vector pos_hit , vforce ;
fix damage ;
2019-07-27 17:48:03 +00:00
// -- now legal for badass explosions on a wall. Assert(obj_explosion_origin != NULL);
2006-03-20 17:12:09 +00:00
2019-07-27 17:48:03 +00:00
range_for ( const auto & & obj_iter , vmobjptridx )
2014-10-12 23:05:46 +00:00
{
2006-03-20 17:12:09 +00:00
// Weapons used to be affected by badass explosions, but this introduces serious problems.
// When a smart bomb blows up, if one of its children goes right towards a nearby wall, it will
// blow up, blowing up all the children. So I remove it. MK, 09/11/94
2019-07-27 17:48:03 +00:00
if ( can_collide ( obj_explosion_origin , obj_iter , parent ) )
2013-11-24 05:30:37 +00:00
{
2019-07-27 17:48:03 +00:00
const auto dist = vm_vec_dist_quick ( obj_iter - > pos , obj_fireball - > pos ) ;
2006-03-20 17:12:09 +00:00
// Make damage be from 'maxdamage' to 0.0, where 0.0 is 'maxdistance' away;
if ( dist < maxdistance ) {
2019-07-27 17:48:03 +00:00
if ( object_to_object_visibility ( obj_fireball , obj_iter , FQ_TRANSWALL ) )
{
2006-03-20 17:12:09 +00:00
damage = maxdamage - fixmuldiv ( dist , maxdamage , maxdistance ) ;
force = maxforce - fixmuldiv ( dist , maxforce , maxdistance ) ;
// Find the force vector on the object
2019-07-27 17:48:03 +00:00
vm_vec_normalized_dir_quick ( vforce , obj_iter - > pos , obj_fireball - > pos ) ;
2014-09-28 21:11:05 +00:00
vm_vec_scale ( vforce , force ) ;
2006-03-20 17:12:09 +00:00
// Find where the point of impact is... ( pos_hit )
2019-07-27 17:48:03 +00:00
vm_vec_scale ( vm_vec_sub ( pos_hit , obj_fireball - > pos , obj_iter - > pos ) , fixdiv ( obj_iter - > size , obj_iter - > size + dist ) ) ;
switch ( obj_iter - > type )
{
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
case OBJ_WEAPON :
2019-07-27 17:48:03 +00:00
phys_apply_force ( obj_iter , vforce ) ;
2006-03-20 17:12:09 +00:00
2019-08-15 01:34:22 +00:00
if ( is_proximity_bomb_or_player_smart_mine ( get_weapon_id ( obj_iter ) ) )
2015-11-27 03:56:13 +00:00
{ //prox bombs have chance of blowing up
2006-03-20 17:12:09 +00:00
if ( fixmul ( dist , force ) > i2f ( 8000 ) ) {
2019-07-27 17:48:03 +00:00
obj_iter - > flags | = OF_SHOULD_BE_DEAD ;
2022-07-09 13:39:29 +00:00
explode_badass_weapon ( Robot_info , obj_iter , obj_iter - > pos ) ;
2006-03-20 17:12:09 +00:00
}
}
break ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
case OBJ_ROBOT :
{
2019-07-27 17:48:03 +00:00
phys_apply_force ( obj_iter , vforce ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// If not a boss, stun for 2 seconds at 32 force, 1 second at 16 force
2019-07-27 17:48:03 +00:00
fix flash ;
if ( obj_explosion_origin ! = object_none & & obj_explosion_origin - > type = = OBJ_WEAPON & & ! Robot_info [ get_robot_id ( obj_iter ) ] . boss_flag & & ( flash = Weapon_info [ get_weapon_id ( obj_explosion_origin ) ] . flash ) )
2017-01-28 18:12:21 +00:00
{
2019-07-27 17:48:03 +00:00
ai_static * const aip = & obj_iter - > ctype . ai_info ;
2006-03-20 17:12:09 +00:00
2019-07-27 17:48:03 +00:00
if ( obj_fireball - > ctype . ai_info . SKIP_AI_COUNT * FrameTime < F1_0 )
{
2019-07-27 17:48:03 +00:00
const int force_val = f2i ( fixdiv ( vm_vec_mag_quick ( vforce ) * flash , FrameTime ) / 128 ) + 2 ;
2006-03-20 17:12:09 +00:00
aip - > SKIP_AI_COUNT + = force_val ;
2019-07-27 17:48:03 +00:00
obj_iter - > mtype . phys_info . rotthrust . x = ( ( d_rand ( ) - 16384 ) * force_val ) / 16 ;
obj_iter - > mtype . phys_info . rotthrust . y = ( ( d_rand ( ) - 16384 ) * force_val ) / 16 ;
obj_iter - > mtype . phys_info . rotthrust . z = ( ( d_rand ( ) - 16384 ) * force_val ) / 16 ;
obj_iter - > mtype . phys_info . flags | = PF_USES_THRUST ;
2006-03-20 17:12:09 +00:00
} else
aip - > SKIP_AI_COUNT - - ;
}
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2022-07-16 15:26:12 +00:00
const auto Difficulty_level = underlying_value ( GameUniqueState . Difficulty_level ) ;
2006-03-20 17:12:09 +00:00
// When a robot gets whacked by a badass force, he looks towards it because robots tend to get blasted from behind.
{
vms_vector neg_vforce ;
neg_vforce . x = vforce . x * - 2 * ( 7 - Difficulty_level ) / 8 ;
neg_vforce . y = vforce . y * - 2 * ( 7 - Difficulty_level ) / 8 ;
neg_vforce . z = vforce . z * - 2 * ( 7 - Difficulty_level ) / 8 ;
2019-07-27 17:48:03 +00:00
phys_apply_rot ( obj_iter , neg_vforce ) ;
2006-03-20 17:12:09 +00:00
}
2019-07-27 17:48:03 +00:00
if ( obj_iter - > shields > = 0 )
{
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2019-07-27 17:48:03 +00:00
const auto & robot_info = Robot_info [ get_robot_id ( obj_iter ) ] ;
2015-11-27 03:56:13 +00:00
if ( robot_info . boss_flag > = BOSS_D2 & & Boss_invulnerable_matter [ robot_info . boss_flag - BOSS_D2 ] )
2006-03-20 17:12:09 +00:00
damage / = 4 ;
2013-03-03 01:03:33 +00:00
# endif
2022-07-09 13:39:29 +00:00
if ( apply_damage_to_robot ( Robot_info , obj_iter , damage , parent ) )
2019-07-27 17:48:03 +00:00
if ( obj_explosion_origin ! = object_none & & parent = = get_local_player ( ) . objnum )
2021-09-12 16:20:52 +00:00
add_points_to_score ( ConsoleObject - > ctype . player_info , Robot_info [ get_robot_id ( obj_iter ) ] . score_value , Game_mode ) ;
2006-03-20 17:12:09 +00:00
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2019-07-27 17:48:03 +00:00
if ( obj_explosion_origin ! = object_none & & Robot_info [ get_robot_id ( obj_iter ) ] . companion & & ! ( obj_explosion_origin - > type = = OBJ_WEAPON & & Weapon_info [ get_weapon_id ( obj_explosion_origin ) ] . flash ) )
2015-11-27 03:56:13 +00:00
{
2012-12-02 22:28:51 +00:00
static const char ouch_str [ ] = " ouch! " " ouch! " " ouch! " " ouch! " ;
int count ;
2006-03-20 17:12:09 +00:00
count = f2i ( damage / 8 ) ;
if ( count > 4 )
count = 4 ;
else if ( count < = 0 )
count = 1 ;
2013-12-08 23:37:40 +00:00
buddy_message_str ( & ouch_str [ ( 4 - count ) * 6 ] ) ;
2006-03-20 17:12:09 +00:00
}
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
break ;
}
case OBJ_CNTRLCEN :
2019-07-27 17:48:03 +00:00
if ( parent ! = object_none & & obj_iter - > shields > = 0 )
2022-07-09 13:39:29 +00:00
apply_damage_to_controlcen ( Robot_info , obj_iter , damage , parent ) ;
2006-03-20 17:12:09 +00:00
break ;
case OBJ_PLAYER : {
2017-06-10 03:31:02 +00:00
icobjptridx_t killer = object_none ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// Hack! Warning! Test code!
2019-07-27 17:48:03 +00:00
fix flash ;
if ( obj_explosion_origin ! = object_none & & obj_explosion_origin - > type = = OBJ_WEAPON & & ( flash = Weapon_info [ get_weapon_id ( obj_explosion_origin ) ] . flash ) & & get_player_id ( obj_iter ) = = Player_num )
2015-11-27 03:56:13 +00:00
{
2019-07-27 17:48:03 +00:00
int fe = min ( F1_0 * 4 , force * flash / 32 ) ; // For four seconds or less
2006-03-20 17:12:09 +00:00
2019-07-27 17:48:03 +00:00
if ( laser_parent_is_player ( Objects . vcptr , obj_explosion_origin - > ctype . laser_info , * ConsoleObject ) )
2019-07-07 22:00:02 +00:00
{
2006-03-20 17:12:09 +00:00
fe / = 2 ;
force / = 2 ;
}
if ( force > F1_0 ) {
Flash_effect = fe ;
PALETTE_FLASH_ADD ( PK1 + f2i ( PK2 * force ) , PK1 + f2i ( PK2 * force ) , PK1 + f2i ( PK2 * force ) ) ;
}
}
2013-03-03 01:03:33 +00:00
# endif
2019-07-27 17:48:03 +00:00
if ( obj_explosion_origin ! = object_none & & ( Game_mode & GM_MULTI ) & & obj_explosion_origin - > type = = OBJ_PLAYER )
{
killer = obj_explosion_origin ;
2006-03-20 17:12:09 +00:00
}
2016-08-25 23:31:37 +00:00
auto vforce2 = vforce ;
2013-12-26 22:21:16 +00:00
if ( parent ! = object_none ) {
2014-07-05 22:21:47 +00:00
killer = parent ;
2006-03-20 17:12:09 +00:00
if ( killer ! = ConsoleObject ) // if someone else whacks you, cut force by 2x
2016-08-25 23:31:37 +00:00
{
2006-03-20 17:12:09 +00:00
vforce2 . x / = 2 ; vforce2 . y / = 2 ; vforce2 . z / = 2 ;
2016-08-25 23:31:37 +00:00
}
2006-03-20 17:12:09 +00:00
}
vforce2 . x / = 2 ; vforce2 . y / = 2 ; vforce2 . z / = 2 ;
2019-07-27 17:48:03 +00:00
phys_apply_force ( obj_iter , vforce ) ;
phys_apply_rot ( obj_iter , vforce2 ) ;
if ( obj_iter - > shields > = 0 )
2016-08-25 23:31:37 +00:00
{
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2022-07-16 15:26:12 +00:00
if ( GameUniqueState . Difficulty_level = = Difficulty_level_type : : _0 )
2006-03-20 17:12:09 +00:00
damage / = 4 ;
2013-03-03 01:03:33 +00:00
# endif
2022-07-23 20:58:10 +00:00
apply_damage_to_player ( obj_iter , killer , damage , apply_damage_player : : check_for_friendly ) ;
2016-08-25 23:31:37 +00:00
}
2006-03-20 17:12:09 +00:00
}
break ;
default :
Int3 ( ) ; // Illegal object type
} // end switch
} else {
2008-04-06 20:23:28 +00:00
;
2006-03-20 17:12:09 +00:00
} // end if (object_to_object_visibility...
} // end if (dist < maxdistance)
}
} // end for
} // end if (maxdamage...
2019-07-27 17:48:03 +00:00
return obj_fireball ;
2006-03-20 17:12:09 +00:00
}
2022-07-09 13:39:29 +00:00
}
2022-07-09 13:39:29 +00:00
imobjptridx_t object_create_badass_explosion ( const d_robot_info_array & Robot_info , const imobjptridx_t objp , const vmsegptridx_t segnum , const vms_vector & position , fix size , int vclip_type , fix maxdamage , fix maxdistance , fix maxforce , const icobjptridx_t parent )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptridx = Objects . vmptridx ;
2022-07-09 13:39:29 +00:00
const imobjptridx_t rval = object_create_explosion_with_damage ( Robot_info , Vclip , vmobjptridx , objp , segnum , position , size , vclip_type , maxdamage , maxdistance , maxforce , parent ) ;
2006-03-20 17:12:09 +00:00
2014-01-11 23:34:11 +00:00
if ( ( objp ! = object_none ) & & ( objp - > type = = OBJ_WEAPON ) )
2015-05-09 17:38:58 +00:00
create_weapon_smart_children ( objp ) ;
2006-03-20 17:12:09 +00:00
return rval ;
}
//blows up a badass weapon, creating the badass explosion
//return the explosion object
2022-07-09 13:39:29 +00:00
void explode_badass_weapon ( const d_robot_info_array & Robot_info , const vmobjptridx_t obj , const vms_vector & pos )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & imobjptridx = Objects . imptridx ;
2015-11-27 03:56:13 +00:00
const auto weapon_id = get_weapon_id ( obj ) ;
const weapon_info * wi = & Weapon_info [ weapon_id ] ;
2006-03-20 17:12:09 +00:00
Assert ( wi - > damage_radius ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2015-12-03 03:26:49 +00:00
if ( weapon_id = = weapon_id_type : : EARTHSHAKER_ID | | weapon_id = = weapon_id_type : : ROBOT_EARTHSHAKER_ID )
2006-03-20 17:12:09 +00:00
smega_rock_stuff ( ) ;
2013-03-03 01:03:33 +00:00
# endif
2018-05-13 03:14:34 +00:00
digi_link_sound_to_object ( SOUND_BADASS_EXPLOSION , obj , 0 , F1_0 , sound_stack : : allow_stacking ) ;
2006-03-20 17:12:09 +00:00
2019-06-27 03:26:20 +00:00
const auto Difficulty_level = GameUniqueState . Difficulty_level ;
2022-07-09 13:39:29 +00:00
object_create_badass_explosion ( Robot_info , obj , vmsegptridx ( obj - > segnum ) , pos ,
2006-03-20 17:12:09 +00:00
wi - > impact_size ,
wi - > robot_hit_vclip ,
wi - > strength [ Difficulty_level ] ,
wi - > damage_radius , wi - > strength [ Difficulty_level ] ,
2017-06-10 03:31:02 +00:00
imobjptridx ( obj - > ctype . laser_info . parent_num ) ) ;
2006-03-20 17:12:09 +00:00
}
2022-07-09 13:39:29 +00:00
namespace {
static void explode_badass_object ( const d_robot_info_array & Robot_info , fvmsegptridx & vmsegptridx , const vmobjptridx_t objp , fix damage , fix distance , fix force )
2006-03-20 17:12:09 +00:00
{
2022-07-09 13:39:29 +00:00
const auto & & rval = object_create_badass_explosion ( Robot_info , objp , vmsegptridx ( objp - > segnum ) , objp - > pos , objp - > size ,
get_explosion_vclip ( Robot_info , objp , explosion_vclip_stage : : s0 ) ,
2006-03-20 17:12:09 +00:00
damage , distance , force ,
2014-01-05 05:17:34 +00:00
objp ) ;
2014-01-10 03:40:43 +00:00
if ( rval ! = object_none )
2018-05-13 03:14:34 +00:00
digi_link_sound_to_object ( SOUND_BADASS_EXPLOSION , rval , 0 , F1_0 , sound_stack : : allow_stacking ) ;
2006-03-20 17:12:09 +00:00
}
2022-07-09 13:39:29 +00:00
}
2006-03-20 17:12:09 +00:00
//blows up the player with a badass explosion
//return the explosion object
2022-07-09 13:39:29 +00:00
void explode_badass_player ( const d_robot_info_array & Robot_info , const vmobjptridx_t objp )
2006-03-20 17:12:09 +00:00
{
2022-07-09 13:39:29 +00:00
explode_badass_object ( Robot_info , vmsegptridx , objp , F1_0 * 50 , F1_0 * 40 , F1_0 * 150 ) ;
2006-03-20 17:12:09 +00:00
}
2007-02-10 23:19:21 +00:00
# define DEBRIS_LIFE (f1_0 * (PERSISTENT_DEBRIS?60:2)) //lifespan in seconds
2006-03-20 17:12:09 +00:00
2022-07-09 13:39:29 +00:00
namespace {
2017-07-26 03:15:59 +00:00
static void object_create_debris ( fvmsegptridx & vmsegptridx , const object_base & parent , int subobj_num )
2006-03-20 17:12:09 +00:00
{
2016-04-23 17:59:47 +00:00
Assert ( parent . type = = OBJ_ROBOT | | parent . type = = OBJ_PLAYER ) ;
2006-03-20 17:12:09 +00:00
2018-12-30 00:43:59 +00:00
auto & Polygon_models = LevelSharedPolygonModelState . Polygon_models ;
2022-07-02 18:10:45 +00:00
const auto & & obj = obj_create ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , OBJ_DEBRIS , 0 , vmsegptridx ( parent . segnum ) , parent . pos , & parent . orient , Polygon_models [ parent . rtype . pobj_info . model_num ] . submodel_rads [ subobj_num ] ,
2020-08-10 03:45:13 +00:00
object : : control_type : : debris , object : : movement_type : : physics , RT_POLYOBJ ) ;
2006-03-20 17:12:09 +00:00
2014-01-10 04:02:53 +00:00
if ( obj = = object_none )
2014-01-02 00:26:58 +00:00
return ; // Not enough debris slots!
2006-03-20 17:12:09 +00:00
Assert ( subobj_num < 32 ) ;
//Set polygon-object-specific data
2016-04-23 17:59:47 +00:00
obj - > rtype . pobj_info . model_num = parent . rtype . pobj_info . model_num ;
2006-03-20 17:12:09 +00:00
obj - > rtype . pobj_info . subobj_flags = 1 < < subobj_num ;
2016-04-23 17:59:47 +00:00
obj - > rtype . pobj_info . tmap_override = parent . rtype . pobj_info . tmap_override ;
2006-03-20 17:12:09 +00:00
//Set physics data for this object
2007-02-10 23:19:21 +00:00
obj - > mtype . phys_info . velocity . x = D_RAND_MAX / 2 - d_rand ( ) ;
obj - > mtype . phys_info . velocity . y = D_RAND_MAX / 2 - d_rand ( ) ;
obj - > mtype . phys_info . velocity . z = D_RAND_MAX / 2 - d_rand ( ) ;
2014-09-28 21:11:04 +00:00
vm_vec_normalize_quick ( obj - > mtype . phys_info . velocity ) ;
2014-09-28 21:11:05 +00:00
vm_vec_scale ( obj - > mtype . phys_info . velocity , i2f ( 10 + ( 30 * d_rand ( ) / D_RAND_MAX ) ) ) ;
2006-03-20 17:12:09 +00:00
2016-04-23 17:59:47 +00:00
vm_vec_add2 ( obj - > mtype . phys_info . velocity , parent . mtype . phys_info . velocity ) ;
2006-03-20 17:12:09 +00:00
// -- used to be: Notice, not random! vm_vec_make(&obj->mtype.phys_info.rotvel,10*0x2000/3,10*0x4000/3,10*0x7000/3);
2015-01-12 00:26:02 +00:00
obj - > mtype . phys_info . rotvel = { d_rand ( ) + 0x1000 , d_rand ( ) * 2 + 0x4000 , d_rand ( ) * 3 + 0x2000 } ;
2014-09-28 21:11:04 +00:00
vm_vec_zero ( obj - > mtype . phys_info . rotthrust ) ;
2006-03-20 17:12:09 +00:00
obj - > lifeleft = 3 * DEBRIS_LIFE / 4 + fixmul ( d_rand ( ) , DEBRIS_LIFE ) ; // Some randomness, so they don't all go away at the same time.
2016-04-23 17:59:47 +00:00
obj - > mtype . phys_info . mass = fixmuldiv ( parent . mtype . phys_info . mass , obj - > size , parent . size ) ;
2006-03-20 17:12:09 +00:00
obj - > mtype . phys_info . drag = 0 ; //fl2f(0.2); //parent->mtype.phys_info.drag;
2011-01-24 20:51:08 +00:00
if ( PERSISTENT_DEBRIS )
{
obj - > mtype . phys_info . flags | = PF_BOUNCE ;
obj - > mtype . phys_info . drag = 128 ;
}
2006-03-20 17:12:09 +00:00
}
2022-07-09 13:39:29 +00:00
}
2018-10-21 00:24:07 +00:00
void draw_fireball ( const d_vclip_array & Vclip , grs_canvas & canvas , const vcobjptridx_t obj )
2006-03-20 17:12:09 +00:00
{
2019-04-13 18:00:07 +00:00
const auto lifeleft = obj - > lifeleft ;
if ( lifeleft > 0 )
draw_vclip_object ( canvas , obj , lifeleft , Vclip [ get_fireball_id ( obj ) ] ) ;
2006-03-20 17:12:09 +00:00
}
2022-07-09 13:39:29 +00:00
namespace {
2021-09-04 12:17:14 +00:00
static unsigned disallowed_cc_dist ( fvcsegptr & vcsegptr , fvcvertptr & vcvertptr , const vcsegptridx_t & segp , const vms_vector & player_pos , const vcsegptridx_t player_seg , const unsigned cur_drop_depth )
2018-06-24 05:06:15 +00:00
{
2020-08-24 01:31:28 +00:00
const shared_segment & sseg = segp ;
2018-06-24 05:06:15 +00:00
//don't drop in any children of control centers
2020-08-24 01:31:28 +00:00
for ( const auto ch : sseg . children )
2018-06-24 05:06:15 +00:00
{
if ( ! IS_CHILD ( ch ) )
continue ;
2021-09-04 12:17:14 +00:00
const shared_segment & childsegp = * vcsegptr ( ch ) ;
2021-11-01 03:37:19 +00:00
if ( childsegp . special = = segment_special : : controlcen )
2021-09-04 12:17:14 +00:00
return 1 ;
2018-06-24 05:06:15 +00:00
}
//bail if not far enough from original position
2020-08-24 01:31:28 +00:00
const auto & & tempv = compute_segment_center ( vcvertptr , sseg ) ;
2020-12-19 16:13:26 +00:00
if ( find_connected_distance ( player_pos , player_seg , tempv , segp , - 1 , WALL_IS_DOORWAY_FLAG : : fly ) < static_cast < fix > ( i2f ( 20 ) * cur_drop_depth ) )
2021-09-04 12:17:14 +00:00
return 1 ;
return 0 ;
2018-06-24 05:06:15 +00:00
}
2006-03-20 17:12:09 +00:00
// ------------------------------------------------------------------------------------------------------
// Choose segment to drop a powerup in.
// For all active net players, try to create a N segment path from the player. If possible, return that
// segment. If not possible, try another player. After a few tries, use a random segment.
// Don't drop if control center in segment.
2021-09-04 12:17:14 +00:00
static vmsegptridx_t choose_drop_segment ( fvmsegptridx & vmsegptridx , fvcvertptr & vcvertptr , fvcwallptr & vcwallptr , const playernum_t drop_pnum )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vcobjptr = Objects . vcptr ;
auto & vmobjptr = Objects . vmptr ;
2018-06-24 05:06:15 +00:00
auto & drop_player = * vcplayerptr ( drop_pnum ) ;
auto & drop_playerobj = * vmobjptr ( drop_player . objnum ) ;
2006-03-20 17:12:09 +00:00
2021-09-04 12:17:14 +00:00
auto mrd = std : : minstd_rand ( timer_query ( ) ) ;
2022-09-24 17:47:53 +00:00
per_player_array < playernum_t > candidate_drop_players ;
2021-09-04 12:17:14 +00:00
const auto end_drop_players = [ & candidate_drop_players , drop_pnum ] ( ) {
const auto b = candidate_drop_players . begin ( ) ;
auto r = b ;
# if defined(DXX_BUILD_DESCENT_II)
/* Build a list of active players, excluding the initiator and
* anyone from the same team as the initiator .
*/
const auto team_of_drop_player = get_team ( drop_pnum ) ;
for ( auto & & [ pnum , plr ] : enumerate ( Players ) )
2013-11-24 05:30:37 +00:00
{
2021-09-04 12:17:14 +00:00
/* Skip the initiator, so that items will try to jump from
* player to player as the item is spent and respawned .
*/
if ( pnum = = drop_pnum )
continue ;
2022-07-16 15:26:12 +00:00
if ( plr . connected = = player_connection_status : : disconnected )
2021-09-04 12:17:14 +00:00
continue ;
2021-09-12 16:20:52 +00:00
if ( ( Game_mode & GM_TEAM ) & & get_team ( pnum ) = = team_of_drop_player )
2021-09-04 12:17:14 +00:00
continue ;
* r + + = pnum ;
2006-03-20 17:12:09 +00:00
}
2021-09-04 12:17:14 +00:00
if ( b ! = r )
/* If at least one such player found, stop and use that
* list .
*/
return r ;
# endif
/* Otherwise, try again, but allow teammates of the initiator.
* Exclude the drop initiator .
*/
for ( auto & & [ pnum , plr ] : enumerate ( Players ) )
{
if ( pnum = = drop_pnum )
continue ;
2022-07-16 15:26:12 +00:00
if ( plr . connected = = player_connection_status : : disconnected )
2021-09-04 12:17:14 +00:00
continue ;
* r + + = pnum ;
2006-03-20 17:12:09 +00:00
}
2021-09-04 12:17:14 +00:00
if ( b ! = r )
/* If a teammate was found, use that. This encourages the
* game to move the items among players .
*/
return r ;
/* If no player found through other rules, use the player
* who triggered the drop event .
*/
* r + + = drop_pnum ;
return r ;
} ( ) ;
/* candidate_drop_players now contains the player IDs of all players
* that can be used for a drop . Shuffle them for randomness to vary
* who will be checked first for each new item .
*/
std : : shuffle ( candidate_drop_players . begin ( ) , end_drop_players , mrd ) ;
static constexpr std : : integral_constant < connected_segment_raw_distances : : segment_distance_count_type , 8 > net_drop_max_depth_lower { } ;
static constexpr std : : integral_constant < connected_segment_raw_distances : : segment_distance_count_type , 24 > net_drop_max_depth_upper { } ;
static constexpr std : : integral_constant < connected_segment_raw_distances : : segment_distance_count_type , 4 > net_drop_min_depth { } ;
static_assert ( connected_segment_raw_distances : : depth_count_array : : valid_index ( net_drop_max_depth_lower ) ) ;
static_assert ( connected_segment_raw_distances : : depth_count_array : : valid_index ( net_drop_max_depth_upper ) ) ;
static_assert ( connected_segment_raw_distances : : depth_count_array : : valid_index ( net_drop_min_depth + 1u ) ) ;
const auto & & drop_player_seg = vmsegptridx ( drop_playerobj . segnum ) ;
/* Defer constructing instances until needed. In most games, a
* segment should be found before all MAX_PLAYERS instances are
* constructed .
*/
2022-09-24 17:47:53 +00:00
per_player_array < std : : unique_ptr < connected_segment_raw_distances > > distance_by_player ;
2021-09-04 12:17:14 +00:00
std : : optional < vmsegptridx_t > fallback_drop ;
for ( const unsigned candidate_depth : xrange ( std : : uniform_int_distribution ( net_drop_max_depth_lower + 0u , net_drop_max_depth_upper + 0u ) ( mrd ) , net_drop_min_depth , xrange_descending ( ) ) )
{
2022-10-31 00:51:32 +00:00
for ( const auto pnum : ranges : : subrange ( candidate_drop_players . begin ( ) , end_drop_players ) )
2012-04-07 08:58:46 +00:00
{
2021-09-04 12:17:14 +00:00
auto & plr = * vcplayerptr ( pnum ) ;
auto & plrobj = * vcobjptr ( plr . objnum ) ;
auto & rdp = distance_by_player [ pnum ] ;
if ( ! rdp )
/* connected_segment_raw_distances computes data for all
* distances below candidate_depth too . Since the
* xrange counts down , this initialization is
* sufficient to cover all data required by all passes
* of the loop .
*/
rdp = std : : make_unique < connected_segment_raw_distances > ( vcsegptr , vcwallptr , candidate_depth , plrobj . segnum ) ;
auto & rd = * rdp ;
const auto sn = rd . scan_segment_depths ( candidate_depth , mrd ) ;
if ( ! sn )
continue ;
const auto segnum = * sn ;
const auto & & segp = vmsegptridx ( segnum ) ;
if ( disallowed_cc_dist ( vcsegptr , vcvertptr , segp , drop_playerobj . pos , drop_player_seg , candidate_depth ) )
{
if ( ! fallback_drop . has_value ( ) )
fallback_drop = segp ;
continue ;
}
return segp ;
2012-04-07 08:58:46 +00:00
}
2016-01-09 16:38:14 +00:00
}
2021-09-04 12:17:14 +00:00
if ( fallback_drop . has_value ( ) )
/* If no player found a segment acceptable to the first pass
* rules , but a player found a reachable segment , use that
* segment . This differs slightly from retail , which would
* compute the fallback from the initiator , rather than a random
* successful player . However , since saving the fallback is
* cheap , this is done instead of performing yet another search .
*/
return * fallback_drop ;
/* This can be reached if there are no segments found at any of the
* acceptable depths . If the origin segment is in a small room with
* no passable exits , this can happen .
*
* Give up and pick a completely random segment . This is compatible
* with retail .
*/
2022-07-02 18:10:45 +00:00
std : : uniform_int_distribution < typename std : : underlying_type < segnum_t > : : type > uid ( 0u , Highest_segment_index ) ;
return vmsegptridx ( vmsegidx_t ( segnum_t { uid ( mrd ) } ) ) ;
2006-03-20 17:12:09 +00:00
}
2022-07-09 13:39:29 +00:00
}
2006-03-20 17:12:09 +00:00
// ------------------------------------------------------------------------------------------------------
2016-02-25 13:11:08 +00:00
// (Re)spawns powerup if in a network game.
void maybe_drop_net_powerup ( powerup_type_t powerup_type , bool adjust_cap , bool random_player )
2006-03-20 17:12:09 +00:00
{
2020-05-17 23:35:25 +00:00
auto & LevelSharedVertexState = LevelSharedSegmentState . get_vertex_state ( ) ;
2019-08-15 01:34:22 +00:00
auto & LevelUniqueControlCenterState = LevelUniqueObjectState . ControlCenterState ;
2020-05-17 23:35:25 +00:00
auto & Vertices = LevelSharedVertexState . get_vertices ( ) ;
2016-02-25 13:11:08 +00:00
playernum_t pnum = Player_num ;
2006-03-20 17:12:09 +00:00
if ( ( Game_mode & GM_MULTI ) & & ! ( Game_mode & GM_MULTI_COOP ) ) {
2016-02-25 13:11:08 +00:00
if ( ( Game_mode & GM_NETWORK ) & & adjust_cap )
2011-01-14 13:29:08 +00:00
{
2016-07-14 01:59:03 +00:00
MultiLevelInv_Recount ( ) ; // recount current items
2016-02-25 13:11:08 +00:00
if ( ! MultiLevelInv_AllowSpawn ( powerup_type ) )
2011-01-14 13:29:08 +00:00
return ;
}
2006-03-20 17:12:09 +00:00
2022-07-09 13:39:29 +00:00
if ( LevelUniqueControlCenterState . Control_center_destroyed | | Network_status = = network_state : : endlevel )
2006-03-20 17:12:09 +00:00
return ;
2016-02-25 13:11:08 +00:00
if ( random_player )
{
2016-02-29 15:31:20 +00:00
uint_fast32_t failsafe_count = 0 ;
do {
2016-02-25 13:11:08 +00:00
pnum = d_rand ( ) % MAX_PLAYERS ;
2016-02-29 15:31:20 +00:00
if ( failsafe_count > MAX_PLAYERS * 4 ) // that was plenty of tries to find a good player...
{
pnum = Player_num ; // ... go with Player_num instead
break ;
}
failsafe_count + + ;
2022-07-16 15:26:12 +00:00
} while ( vcplayerptr ( pnum ) - > connected ! = player_connection_status : : playing ) ;
2016-02-25 13:11:08 +00:00
}
2006-03-20 17:12:09 +00:00
//--old-- segnum = (d_rand() * Highest_segment_index) >> 15;
//--old-- Assert((segnum >= 0) && (segnum <= Highest_segment_index));
//--old-- if (segnum < 0)
//--old-- segnum = -segnum;
//--old-- while (segnum > Highest_segment_index)
//--old-- segnum /= 2;
Net_create_loc = 0 ;
2018-12-30 00:43:57 +00:00
auto & vcvertptr = Vertices . vcptr ;
2021-09-04 12:17:14 +00:00
const auto & & segnum = choose_drop_segment ( LevelUniqueSegmentState . get_segments ( ) . vmptridx , vcvertptr , LevelUniqueWallSubsystemState . Walls . vcptr , pnum ) ;
2021-11-01 03:37:20 +00:00
const auto & & new_pos = pick_random_point_in_seg ( vcvertptr , segnum , std : : minstd_rand ( d_rand ( ) ) ) ;
2022-07-23 20:58:10 +00:00
const auto & & objnum = drop_powerup ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , Vclip , powerup_type , { } , new_pos , segnum , true ) ;
2022-02-05 13:30:56 +00:00
if ( objnum = = object_none )
return ;
2014-10-30 03:32:38 +00:00
multi_send_create_powerup ( powerup_type , segnum , objnum , new_pos ) ;
2022-11-07 01:59:34 +00:00
object_create_explosion_without_damage ( Vclip , segnum , new_pos , i2f ( 5 ) , VCLIP_POWERUP_DISAPPEARANCE ) ;
2006-03-20 17:12:09 +00:00
}
}
2022-07-09 13:39:29 +00:00
namespace {
2006-03-20 17:12:09 +00:00
// ------------------------------------------------------------------------------------------------------
// Return true if current segment contains some object.
2018-12-30 00:43:57 +00:00
static const object * segment_contains_powerup ( fvcobjptridx & vcobjptridx , fvcsegptr & vcsegptr , const unique_segment & segnum , const powerup_type_t obj_id )
2006-03-20 17:12:09 +00:00
{
2019-12-22 05:34:08 +00:00
range_for ( const object & objp , objects_in ( segnum , vcobjptridx , vcsegptr ) )
2018-12-30 00:43:57 +00:00
{
2019-12-22 05:34:08 +00:00
auto & o = objp ;
2018-12-30 00:43:57 +00:00
if ( o . type = = OBJ_POWERUP & & get_powerup_id ( o ) = = obj_id )
return & o ;
}
return nullptr ;
2006-03-20 17:12:09 +00:00
}
// ------------------------------------------------------------------------------------------------------
2018-12-30 00:43:57 +00:00
static const object * powerup_nearby_aux ( fvcobjptridx & vcobjptridx , fvcsegptr & vcsegptr , const vcsegidx_t segnum , const powerup_type_t object_id , uint_fast32_t depth )
2006-03-20 17:12:09 +00:00
{
2020-08-24 01:31:28 +00:00
const cscusegment & & segp = vcsegptr ( segnum ) ;
2018-12-30 00:43:57 +00:00
if ( auto r = segment_contains_powerup ( vcobjptridx , vcsegptr , segp , object_id ) )
2015-10-18 21:01:18 +00:00
return r ;
2015-01-20 02:46:42 +00:00
if ( ! - - depth )
2018-12-30 00:43:57 +00:00
return nullptr ;
2020-08-24 01:31:28 +00:00
for ( const auto seg2 : segp . s . children )
2015-01-20 02:46:42 +00:00
{
2013-12-26 22:21:16 +00:00
if ( seg2 ! = segment_none )
2018-12-30 00:43:57 +00:00
if ( auto r = powerup_nearby_aux ( vcobjptridx , vcsegptr , seg2 , object_id , depth ) )
2015-10-18 21:01:18 +00:00
return r ;
2006-03-20 17:12:09 +00:00
}
2018-12-30 00:43:57 +00:00
return nullptr ;
2006-03-20 17:12:09 +00:00
}
// ------------------------------------------------------------------------------------------------------
// Return true if some powerup is nearby (within 3 segments).
2018-12-30 00:43:57 +00:00
static const object * weapon_nearby ( fvcobjptridx & vcobjptridx , fvcsegptr & vcsegptr , const object_base & objp , const powerup_type_t weapon_id )
2006-03-20 17:12:09 +00:00
{
2018-12-30 00:43:57 +00:00
return powerup_nearby_aux ( vcobjptridx , vcsegptr , objp . segnum , weapon_id , 2 ) ;
2006-03-20 17:12:09 +00:00
}
2022-07-09 13:39:29 +00:00
}
2006-03-20 17:12:09 +00:00
// ------------------------------------------------------------------------------------------------------
2018-06-24 05:06:15 +00:00
void maybe_replace_powerup_with_energy ( object_base & del_obj )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vcobjptridx = Objects . vcptridx ;
auto & vmobjptr = Objects . vmptr ;
2021-01-25 00:45:07 +00:00
/* This function has no special handling to remap laser weapons, so
* borrow LASER_INDEX as a flag value to indicate that the powerup
* ID was not recognized .
*/
constexpr primary_weapon_index_t unset_weapon_index = primary_weapon_index_t : : LASER_INDEX ;
primary_weapon_index_t weapon_index = unset_weapon_index ;
2006-03-20 17:12:09 +00:00
2018-06-24 05:06:15 +00:00
if ( del_obj . contains_type ! = OBJ_POWERUP )
2006-03-20 17:12:09 +00:00
return ;
2018-06-24 05:06:15 +00:00
switch ( del_obj . contains_id ) {
case POW_CLOAK :
2018-12-30 00:43:57 +00:00
if ( weapon_nearby ( vcobjptridx , vcsegptr , del_obj , POW_CLOAK ) ! = nullptr )
2018-06-24 05:06:15 +00:00
{
del_obj . contains_count = 0 ;
}
return ;
2015-10-18 21:01:18 +00:00
case POW_VULCAN_WEAPON :
weapon_index = primary_weapon_index_t : : VULCAN_INDEX ;
break ;
case POW_SPREADFIRE_WEAPON :
weapon_index = primary_weapon_index_t : : SPREADFIRE_INDEX ;
break ;
case POW_PLASMA_WEAPON :
weapon_index = primary_weapon_index_t : : PLASMA_INDEX ;
break ;
case POW_FUSION_WEAPON :
weapon_index = primary_weapon_index_t : : FUSION_INDEX ;
break ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2015-10-18 21:01:18 +00:00
case POW_GAUSS_WEAPON :
weapon_index = primary_weapon_index_t : : GAUSS_INDEX ;
break ;
case POW_HELIX_WEAPON :
weapon_index = primary_weapon_index_t : : HELIX_INDEX ;
break ;
case POW_PHOENIX_WEAPON :
weapon_index = primary_weapon_index_t : : PHOENIX_INDEX ;
break ;
case POW_OMEGA_WEAPON :
weapon_index = primary_weapon_index_t : : OMEGA_INDEX ;
break ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
}
// Don't drop vulcan ammo if player maxed out.
2016-10-02 00:34:40 +00:00
auto & player_info = get_local_plrobj ( ) . ctype . player_info ;
2018-06-24 05:06:15 +00:00
if ( ( weapon_index_uses_vulcan_ammo ( weapon_index ) | | del_obj . contains_id = = POW_VULCAN_AMMO ) & &
2016-10-02 00:34:40 +00:00
player_info . vulcan_ammo > = VULCAN_AMMO_MAX )
2018-06-24 05:06:15 +00:00
del_obj . contains_count = 0 ;
2021-01-25 00:45:07 +00:00
else if ( weapon_index ! = unset_weapon_index )
{
2018-12-30 00:43:57 +00:00
if ( player_has_primary_weapon ( player_info , weapon_index ) . has_weapon ( ) | | weapon_nearby ( vcobjptridx , vcsegptr , del_obj , static_cast < powerup_type_t > ( del_obj . contains_id ) ) ! = nullptr )
2015-10-18 21:01:18 +00:00
{
2006-03-20 17:12:09 +00:00
if ( d_rand ( ) > 16384 ) {
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2018-06-24 05:06:15 +00:00
del_obj . contains_count = 1 ;
2013-03-03 01:03:33 +00:00
# endif
2018-06-24 05:06:15 +00:00
del_obj . contains_type = OBJ_POWERUP ;
2013-09-20 23:12:51 +00:00
if ( weapon_index_uses_vulcan_ammo ( weapon_index ) ) {
2018-06-24 05:06:15 +00:00
del_obj . contains_id = POW_VULCAN_AMMO ;
2013-11-24 05:30:37 +00:00
}
else {
2018-06-24 05:06:15 +00:00
del_obj . contains_id = POW_ENERGY ;
2006-03-20 17:12:09 +00:00
}
} else {
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2018-06-24 05:06:15 +00:00
del_obj . contains_count = 0 ;
2013-03-03 01:03:33 +00:00
# elif defined(DXX_BUILD_DESCENT_II)
2018-06-24 05:06:15 +00:00
del_obj . contains_type = OBJ_POWERUP ;
del_obj . contains_id = POW_SHIELD_BOOST ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
}
}
2018-06-24 05:06:15 +00:00
} else if ( del_obj . contains_id = = POW_QUAD_FIRE )
2016-10-02 00:34:40 +00:00
{
2018-12-30 00:43:57 +00:00
if ( ( player_info . powerup_flags & PLAYER_FLAGS_QUAD_LASERS ) | | weapon_nearby ( vcobjptridx , vcsegptr , del_obj , static_cast < powerup_type_t > ( del_obj . contains_id ) ) ! = nullptr )
2015-10-18 21:01:18 +00:00
{
2006-03-20 17:12:09 +00:00
if ( d_rand ( ) > 16384 ) {
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2018-06-24 05:06:15 +00:00
del_obj . contains_count = 1 ;
2013-03-03 01:03:33 +00:00
# endif
2018-06-24 05:06:15 +00:00
del_obj . contains_type = OBJ_POWERUP ;
del_obj . contains_id = POW_ENERGY ;
2006-03-20 17:12:09 +00:00
} else {
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2018-06-24 05:06:15 +00:00
del_obj . contains_count = 0 ;
2013-03-03 01:03:33 +00:00
# elif defined(DXX_BUILD_DESCENT_II)
2018-06-24 05:06:15 +00:00
del_obj . contains_type = OBJ_POWERUP ;
del_obj . contains_id = POW_SHIELD_BOOST ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
}
}
2016-10-02 00:34:40 +00:00
}
2006-03-20 17:12:09 +00:00
// If this robot was gated in by the boss and it now contains energy, make it contain nothing,
// else the room gets full of energy.
2018-06-24 05:06:15 +00:00
if ( ( del_obj . matcen_creator = = BOSS_GATE_MATCEN_NUM ) & & ( del_obj . contains_id = = POW_ENERGY ) & & ( del_obj . contains_type = = OBJ_POWERUP ) ) {
del_obj . contains_count = 0 ;
2006-03-20 17:12:09 +00:00
}
// Change multiplayer extra-lives into invulnerability
2018-06-24 05:06:15 +00:00
if ( ( Game_mode & GM_MULTI ) & & ( del_obj . contains_id = = POW_EXTRA_LIFE ) )
2006-03-20 17:12:09 +00:00
{
2018-06-24 05:06:15 +00:00
del_obj . contains_id = POW_INVULNERABILITY ;
2006-03-20 17:12:09 +00:00
}
}
2022-07-23 20:58:10 +00:00
imobjptridx_t drop_powerup ( d_level_unique_object_state & LevelUniqueObjectState , const d_level_shared_segment_state & LevelSharedSegmentState , d_level_unique_segment_state & LevelUniqueSegmentState , const d_vclip_array & Vclip , int id , const vms_vector & init_vel , const vms_vector & pos , vmsegptridx_t segnum , bool player )
2006-03-20 17:12:09 +00:00
{
int rand_scale ;
2015-03-12 02:21:19 +00:00
const auto old_mag = vm_vec_mag_quick ( init_vel ) ;
2006-03-20 17:12:09 +00:00
// We want powerups to move more in network mode.
if ( ( Game_mode & GM_MULTI ) & & ! ( Game_mode & GM_MULTI_ROBOTS ) ) {
rand_scale = 4 ;
// extra life powerups are converted to invulnerability in multiplayer, for what is an extra life, anyway?
if ( id = = POW_EXTRA_LIFE )
id = POW_INVULNERABILITY ;
} else
rand_scale = 2 ;
if ( Game_mode & GM_MULTI )
{
if ( Net_create_loc > = MAX_NET_CREATE_OBJECTS )
{
2020-08-01 18:29:01 +00:00
con_printf ( CON_URGENT , DXX_STRINGIZE_FL ( " %s " , __LINE__ , " unable to record network object; Net_create_loc=%u " ) , __FILE__ , Net_create_loc ) ;
2013-12-26 22:21:16 +00:00
return object_none ;
2006-03-20 17:12:09 +00:00
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2022-07-09 13:39:29 +00:00
if ( ( Game_mode & GM_NETWORK ) & & Network_status = = network_state : : endlevel )
2013-12-26 22:21:16 +00:00
return object_none ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
}
2022-07-02 18:10:45 +00:00
const auto & & objp = obj_create ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , OBJ_POWERUP , id , segnum , pos , & vmd_identity_matrix , Powerup_info [ id ] . size , object : : control_type : : powerup , object : : movement_type : : physics , RT_POWERUP ) ;
2006-03-20 17:12:09 +00:00
2022-02-06 16:12:31 +00:00
if ( objp = = object_none )
return objp ;
auto & obj = * objp ;
2016-12-04 20:39:03 +00:00
# if defined(DXX_BUILD_DESCENT_II)
if ( player )
2022-02-06 16:12:31 +00:00
obj . flags | = OF_PLAYER_DROPPED ;
2016-12-04 20:39:03 +00:00
# endif
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI )
{
2022-02-06 16:12:31 +00:00
Net_create_objnums [ Net_create_loc + + ] = objp ;
2006-03-20 17:12:09 +00:00
}
2021-09-19 10:53:48 +00:00
// Give keys zero velocity so they can be tracked better in multi
2022-02-06 16:12:31 +00:00
auto & object_velocity = obj . mtype . phys_info . velocity ;
2021-09-19 10:53:48 +00:00
if ( ( Game_mode & GM_MULTI ) & & ( id > = POW_KEY_BLUE ) & & ( id < = POW_KEY_GOLD ) )
object_velocity = { } ;
else
{
object_velocity = init_vel ;
const auto random_velocity_adjustment = [ old_mag , rand_scale ] ( ) {
return fixmul ( old_mag + F1_0 * 32 , d_rand ( ) * rand_scale - 16384 * rand_scale ) ;
} ;
object_velocity . x + = random_velocity_adjustment ( ) ;
object_velocity . y + = random_velocity_adjustment ( ) ;
object_velocity . z + = random_velocity_adjustment ( ) ;
}
2006-03-20 17:12:09 +00:00
2022-02-06 16:12:31 +00:00
obj . mtype . phys_info . drag = 512 ; //1024;
obj . mtype . phys_info . mass = F1_0 ;
2006-03-20 17:12:09 +00:00
2022-02-06 16:12:31 +00:00
obj . mtype . phys_info . flags = PF_BOUNCE ;
2006-03-20 17:12:09 +00:00
2022-02-06 16:12:31 +00:00
obj . rtype . vclip_info . vclip_num = Powerup_info [ id ] . vclip_num ;
obj . rtype . vclip_info . frametime = Vclip [ obj . rtype . vclip_info . vclip_num ] . frame_time ;
obj . rtype . vclip_info . framenum = 0 ;
2006-03-20 17:12:09 +00:00
2022-02-06 16:12:31 +00:00
switch ( id )
{
2006-03-20 17:12:09 +00:00
case POW_MISSILE_1 :
case POW_MISSILE_4 :
case POW_SHIELD_BOOST :
case POW_ENERGY :
2022-02-06 16:12:31 +00:00
obj . lifeleft = ( d_rand ( ) + F1_0 * 3 ) * 64 ; // Lives for 3 to 3.5 binary minutes (a binary minute is 64 seconds)
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI )
2022-02-06 16:12:31 +00:00
obj . lifeleft / = 2 ;
2006-03-20 17:12:09 +00:00
break ;
2017-08-11 23:43:53 +00:00
# if defined(DXX_BUILD_DESCENT_II)
case POW_OMEGA_WEAPON :
if ( ! player )
2022-02-06 16:12:31 +00:00
obj . ctype . powerup_info . count = MAX_OMEGA_CHARGE ;
2017-08-11 23:43:53 +00:00
break ;
case POW_GAUSS_WEAPON :
# endif
case POW_VULCAN_WEAPON :
if ( ! player )
2022-02-06 16:12:31 +00:00
obj . ctype . powerup_info . count = VULCAN_WEAPON_AMMO_AMOUNT ;
2017-08-11 23:43:53 +00:00
break ;
2006-03-20 17:12:09 +00:00
default :
break ;
}
2022-02-06 16:12:31 +00:00
return objp ;
}
2022-07-23 20:58:10 +00:00
bool drop_powerup ( d_level_unique_object_state & LevelUniqueObjectState , const d_level_shared_segment_state & LevelSharedSegmentState , d_level_unique_segment_state & LevelUniqueSegmentState , const d_vclip_array & Vclip , int id , const unsigned num , const vms_vector & init_vel , const vms_vector & pos , const vmsegptridx_t segnum , const bool player )
2022-02-06 16:12:31 +00:00
{
bool created = false ;
for ( const auto i : xrange ( num ) )
{
( void ) i ;
2022-07-23 20:58:10 +00:00
const auto & & obj = drop_powerup ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , Vclip , id , init_vel , pos , segnum , player ) ;
2022-02-06 16:12:31 +00:00
if ( obj = = object_none )
/* If one drop failed, assume every additional drop will also fail.
*/
break ;
created = true ;
}
return created ;
2017-08-11 23:43:53 +00:00
}
2006-03-20 17:12:09 +00:00
2022-07-09 13:39:29 +00:00
namespace {
static bool drop_robot_egg ( const d_robot_info_array & Robot_info , const int type , const int id , const unsigned num , const vms_vector & init_vel , const vms_vector & pos , const vmsegptridx_t segnum )
2017-08-11 23:43:53 +00:00
{
2020-12-27 22:03:09 +00:00
if ( ! num )
2022-02-06 16:12:31 +00:00
return false ;
2017-08-11 23:43:53 +00:00
switch ( type )
{
case OBJ_POWERUP :
2022-07-23 20:58:10 +00:00
return drop_powerup ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , Vclip , id , num , init_vel , pos , segnum , false ) ;
2006-03-20 17:12:09 +00:00
case OBJ_ROBOT :
2017-08-11 23:43:53 +00:00
break ;
default :
con_printf ( CON_URGENT , DXX_STRINGIZE_FL ( __FILE__ , __LINE__ , " ignoring invalid object type; expected OBJ_POWERUP or OBJ_ROBOT, got type=%i, id=%i " ) , type , id ) ;
2022-02-06 16:12:31 +00:00
return false ;
2017-08-11 23:43:53 +00:00
}
2018-12-30 00:43:59 +00:00
auto & Polygon_models = LevelSharedPolygonModelState . Polygon_models ;
2022-02-06 16:12:31 +00:00
bool created = false ;
2022-02-06 16:12:31 +00:00
for ( const auto count : xrange ( num ) )
{
( void ) count ;
2006-03-20 17:12:09 +00:00
int rand_scale ;
2014-10-29 03:24:31 +00:00
auto new_velocity = vm_vec_normalized_quick ( init_vel ) ;
2015-03-12 02:21:19 +00:00
const auto old_mag = vm_vec_mag_quick ( init_vel ) ;
2006-03-20 17:12:09 +00:00
// We want powerups to move more in network mode.
// if (Game_mode & GM_MULTI)
// rand_scale = 4;
// else
rand_scale = 2 ;
new_velocity . x + = ( d_rand ( ) - 16384 ) * 2 ;
new_velocity . y + = ( d_rand ( ) - 16384 ) * 2 ;
new_velocity . z + = ( d_rand ( ) - 16384 ) * 2 ;
2014-09-28 21:11:04 +00:00
vm_vec_normalize_quick ( new_velocity ) ;
2014-09-28 21:11:05 +00:00
vm_vec_scale ( new_velocity , ( F1_0 * 32 + old_mag ) * rand_scale ) ;
2017-08-11 23:43:53 +00:00
auto new_pos = pos ;
2006-03-20 17:12:09 +00:00
// This is dangerous, could be outside mine.
// new_pos.x += (d_rand()-16384)*8;
// new_pos.y += (d_rand()-16384)*7;
// new_pos.z += (d_rand()-16384)*6;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2022-12-18 18:32:14 +00:00
/* ObjId appears to serve as both a polygon_model_index and as
* a robot index .
*/
const auto robot_id = static_cast < unsigned > ( ObjId [ type ] ) ;
2013-03-03 01:03:33 +00:00
# elif defined(DXX_BUILD_DESCENT_II)
2016-02-06 22:12:55 +00:00
const auto robot_id = id ;
2013-03-03 01:03:33 +00:00
# endif
2022-07-09 13:39:29 +00:00
const auto & & objp = robot_create ( Robot_info , id , segnum , new_pos , & vmd_identity_matrix , Polygon_models [ Robot_info [ robot_id ] . model_num ] . rad , ai_behavior : : AIB_NORMAL ) ;
if ( objp = = object_none )
break ;
auto & obj = * objp ;
2022-02-06 16:12:31 +00:00
created = true ;
2019-11-16 23:14:41 +00:00
+ + LevelUniqueObjectState . accumulated_robots ;
+ + GameUniqueState . accumulated_robots ;
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI )
{
2022-07-09 13:39:29 +00:00
Net_create_objnums [ Net_create_loc + + ] = objp ;
2006-03-20 17:12:09 +00:00
}
//Set polygon-object-specific data
2022-07-09 13:39:29 +00:00
obj . rtype . pobj_info . model_num = Robot_info [ get_robot_id ( obj ) ] . model_num ;
obj . rtype . pobj_info . subobj_flags = 0 ;
2006-03-20 17:12:09 +00:00
//set Physics info
2022-07-09 13:39:29 +00:00
obj . mtype . phys_info . velocity = new_velocity ;
2006-03-20 17:12:09 +00:00
2022-07-09 13:39:29 +00:00
obj . mtype . phys_info . mass = Robot_info [ get_robot_id ( obj ) ] . mass ;
obj . mtype . phys_info . drag = Robot_info [ get_robot_id ( obj ) ] . drag ;
2006-03-20 17:12:09 +00:00
2022-07-09 13:39:29 +00:00
obj . mtype . phys_info . flags | = ( PF_LEVELLING ) ;
2006-03-20 17:12:09 +00:00
2022-07-09 13:39:29 +00:00
obj . shields = Robot_info [ get_robot_id ( obj ) ] . strength ;
2006-03-20 17:12:09 +00:00
2022-07-09 13:39:29 +00:00
ai_local * ailp = & obj . ctype . ai_info . ail ;
2015-04-02 02:36:53 +00:00
ailp - > player_awareness_type = player_awareness_type_t : : PA_WEAPON_ROBOT_COLLISION ;
2013-12-25 03:16:41 +00:00
ailp - > player_awareness_time = F1_0 * 3 ;
2022-07-09 13:39:29 +00:00
obj . ctype . ai_info . CURRENT_STATE = AIS_LOCK ;
obj . ctype . ai_info . GOAL_STATE = AIS_LOCK ;
obj . ctype . ai_info . REMOTE_OWNER = - 1 ;
2006-03-20 17:12:09 +00:00
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// At JasenW's request, robots which contain robots
// sometimes drop shields.
if ( d_rand ( ) > 16384 )
2022-02-06 16:12:31 +00:00
{
2022-07-23 20:58:10 +00:00
const auto & & objp = drop_powerup ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , Vclip , POW_SHIELD_BOOST , init_vel , pos , segnum , false ) ;
2022-02-06 16:12:31 +00:00
if ( objp ! = object_none )
created = true ;
}
2013-03-03 01:03:33 +00:00
# endif
2022-02-06 16:12:31 +00:00
return created ;
2006-03-20 17:12:09 +00:00
}
2015-05-09 17:39:00 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2016-10-02 00:34:42 +00:00
static bool skip_create_egg_powerup ( const object & player , powerup_type_t powerup )
2015-05-09 17:39:00 +00:00
{
2015-10-30 02:52:55 +00:00
fix current ;
2016-10-02 00:34:42 +00:00
auto & player_info = player . ctype . player_info ;
2015-05-09 17:39:00 +00:00
if ( powerup = = POW_SHIELD_BOOST )
2016-10-02 00:34:42 +00:00
current = player . shields ;
2015-05-09 17:39:00 +00:00
else if ( powerup = = POW_ENERGY )
2016-09-11 18:49:13 +00:00
current = player_info . energy ;
2015-05-09 17:39:00 +00:00
else
return false ;
int limit ;
if ( current > = i2f ( 150 ) )
limit = 8192 ;
else if ( current > = i2f ( 100 ) )
limit = 16384 ;
else
return false ;
return d_rand ( ) > limit ;
}
# endif
2022-07-09 13:39:29 +00:00
}
bool object_create_robot_egg ( const d_robot_info_array & Robot_info , const int type , const int id , const unsigned num , const vms_vector & init_vel , const vms_vector & pos , const vmsegptridx_t segnum )
2015-05-09 17:39:01 +00:00
{
# if defined(DXX_BUILD_DESCENT_II)
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
2015-05-09 17:39:01 +00:00
if ( ! ( Game_mode & GM_MULTI ) )
2006-03-20 17:12:09 +00:00
{
2015-05-09 17:39:01 +00:00
if ( type = = OBJ_POWERUP )
2006-03-20 17:12:09 +00:00
{
2016-10-02 00:34:42 +00:00
if ( skip_create_egg_powerup ( get_local_plrobj ( ) , static_cast < powerup_type_t > ( id ) ) )
2022-02-06 16:12:31 +00:00
return false ;
2006-03-20 17:12:09 +00:00
}
}
2013-03-03 01:03:33 +00:00
# endif
2022-07-09 13:39:29 +00:00
return drop_robot_egg ( Robot_info , type , id , num , init_vel , pos , segnum ) ;
2006-03-20 17:12:09 +00:00
}
2022-07-09 13:39:29 +00:00
bool object_create_robot_egg ( const d_robot_info_array & Robot_info , object_base & objp )
2015-05-09 17:39:01 +00:00
{
2022-07-09 13:39:29 +00:00
return object_create_robot_egg ( Robot_info , objp . contains_type , objp . contains_id , objp . contains_count , objp . mtype . phys_info . velocity , objp . pos , vmsegptridx ( objp . segnum ) ) ;
2015-05-09 17:39:01 +00:00
}
2006-03-20 17:12:09 +00:00
// -------------------------------------------------------------------------------------------------------
// Put count objects of type type (eg, powerup), id = id (eg, energy) into *objp, then drop them! Yippee!
// Returns created object number.
2022-02-06 16:12:31 +00:00
imobjptridx_t call_object_create_egg ( const object_base & objp , const int id )
{
2022-07-23 20:58:10 +00:00
return drop_powerup ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , Vclip , id , objp . mtype . phys_info . velocity , objp . pos , vmsegptridx ( objp . segnum ) , true ) ;
2022-02-06 16:12:31 +00:00
}
void call_object_create_egg ( const object_base & objp , const unsigned count , const int id )
2006-03-20 17:12:09 +00:00
{
if ( count > 0 ) {
2022-07-23 20:58:10 +00:00
drop_powerup ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , Vclip , id , count , objp . mtype . phys_info . velocity , objp . pos , vmsegptridx ( objp . segnum ) , true ) ;
2006-03-20 17:12:09 +00:00
}
}
//what vclip does this explode with?
2022-07-09 13:39:29 +00:00
int get_explosion_vclip ( const d_robot_info_array & Robot_info , const object_base & obj , explosion_vclip_stage stage )
2006-03-20 17:12:09 +00:00
{
2018-06-24 05:06:15 +00:00
if ( obj . type = = OBJ_ROBOT )
{
2015-11-15 22:30:41 +00:00
const auto vclip_ptr = stage = = explosion_vclip_stage : : s0
? & robot_info : : exp1_vclip_num
: & robot_info : : exp2_vclip_num ;
const auto vclip_num = Robot_info [ get_robot_id ( obj ) ] . * vclip_ptr ;
if ( vclip_num > - 1 )
return vclip_num ;
2006-03-20 17:12:09 +00:00
}
2018-06-24 05:06:15 +00:00
else if ( obj . type = = OBJ_PLAYER & & Player_ship - > expl_vclip_num > - 1 )
2006-03-20 17:12:09 +00:00
return Player_ship - > expl_vclip_num ;
return VCLIP_SMALL_EXPLOSION ; //default
}
2022-07-09 13:39:29 +00:00
namespace {
2006-03-20 17:12:09 +00:00
//blow up a polygon model
2016-04-23 17:59:47 +00:00
static void explode_model ( object_base & obj )
2006-03-20 17:12:09 +00:00
{
2016-04-23 17:59:47 +00:00
Assert ( obj . render_type = = RT_POLYOBJ ) ;
2006-03-20 17:12:09 +00:00
2016-04-23 17:59:47 +00:00
const auto poly_model_num = obj . rtype . pobj_info . model_num ;
2015-11-15 22:30:41 +00:00
const auto dying_model_num = Dying_modelnums [ poly_model_num ] ;
2022-12-18 18:32:14 +00:00
const auto model_num = ( dying_model_num ! = polygon_model_index : : None )
2016-04-23 17:59:47 +00:00
? ( obj . rtype . pobj_info . model_num = dying_model_num )
2015-11-15 22:30:41 +00:00
: poly_model_num ;
2018-12-30 00:43:59 +00:00
auto & Polygon_models = LevelSharedPolygonModelState . Polygon_models ;
2015-11-15 22:30:41 +00:00
const auto n_models = Polygon_models [ model_num ] . n_models ;
if ( n_models > 1 ) {
for ( unsigned i = 1 ; i < n_models ; + + i )
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2016-04-23 17:59:47 +00:00
if ( ! ( i = = 5 & & obj . type = = OBJ_ROBOT & & get_robot_id ( obj ) = = 44 ) ) //energy sucker energy part
2013-03-03 01:03:33 +00:00
# endif
2017-07-26 03:15:59 +00:00
object_create_debris ( vmsegptridx , obj , i ) ;
2006-03-20 17:12:09 +00:00
//make parent object only draw center part
2016-04-23 17:59:47 +00:00
obj . rtype . pobj_info . subobj_flags = 1 ;
2006-03-20 17:12:09 +00:00
}
}
//if the object has a destroyed model, switch to it. Otherwise, delete it.
2016-04-23 17:59:47 +00:00
static void maybe_delete_object ( object_base & del_obj )
2006-03-20 17:12:09 +00:00
{
2016-04-23 17:59:47 +00:00
const auto dead_modelnum = Dead_modelnums [ del_obj . rtype . pobj_info . model_num ] ;
2022-12-18 18:32:14 +00:00
if ( dead_modelnum ! = polygon_model_index : : None )
2016-04-23 17:59:47 +00:00
{
del_obj . rtype . pobj_info . model_num = dead_modelnum ;
del_obj . flags | = OF_DESTROYED ;
2006-03-20 17:12:09 +00:00
}
else { //normal, multi-stage explosion
2016-04-23 17:59:47 +00:00
if ( del_obj . type = = OBJ_PLAYER )
del_obj . render_type = RT_NONE ;
2006-03-20 17:12:09 +00:00
else
2016-04-23 17:59:47 +00:00
del_obj . flags | = OF_SHOULD_BE_DEAD ;
2006-03-20 17:12:09 +00:00
}
}
2022-07-09 13:39:29 +00:00
}
2006-03-20 17:12:09 +00:00
// -------------------------------------------------------------------------------------------------------
//blow up an object. Takes the object to destroy, and the point of impact
2022-07-09 13:39:29 +00:00
void explode_object ( d_level_unique_object_state & LevelUniqueObjectState , const d_robot_info_array & Robot_info , const d_level_shared_segment_state & LevelSharedSegmentState , d_level_unique_segment_state & LevelUniqueSegmentState , const vmobjptridx_t hitobj , const fix delay_time )
2006-03-20 17:12:09 +00:00
{
if ( hitobj - > flags & OF_EXPLODING ) return ;
if ( delay_time ) { //wait a little while before creating explosion
//create a placeholder object to do the delay, with id==-1
2022-07-02 18:10:45 +00:00
const auto & & obj = obj_create ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , OBJ_FIREBALL , - 1 , vmsegptridx ( hitobj - > segnum ) , hitobj - > pos , & vmd_identity_matrix , 0 ,
2020-08-10 03:45:13 +00:00
object : : control_type : : explosion , object : : movement_type : : None , RT_NONE ) ;
2014-09-08 03:24:48 +00:00
if ( obj = = object_none ) {
2006-03-20 17:12:09 +00:00
maybe_delete_object ( hitobj ) ; //no explosion, die instantly
Int3 ( ) ;
return ;
}
//now set explosion-specific data
obj - > lifeleft = delay_time ;
2014-01-11 23:55:24 +00:00
obj - > ctype . expl_info . delete_objnum = hitobj ;
2006-03-20 17:12:09 +00:00
obj - > ctype . expl_info . delete_time = - 1 ;
obj - > ctype . expl_info . spawn_time = 0 ;
}
else {
int vclip_num ;
2022-07-09 13:39:29 +00:00
vclip_num = get_explosion_vclip ( Robot_info , hitobj , explosion_vclip_stage : : s0 ) ;
2006-03-20 17:12:09 +00:00
2022-11-07 01:59:34 +00:00
const imobjptr_t expl_obj = object_create_explosion_without_damage ( Vclip , vmsegptridx ( hitobj - > segnum ) , hitobj - > pos , fixmul ( hitobj - > size , EXPLOSION_SCALE ) , vclip_num ) ;
2006-03-20 17:12:09 +00:00
if ( ! expl_obj ) {
maybe_delete_object ( hitobj ) ; //no explosion, die instantly
return ;
}
//don't make debris explosions have physics, because they often
//happen when the debris has hit the wall, so the fireball is trying
//to move into the wall, which shows off FVI problems.
2020-08-10 03:45:13 +00:00
if ( hitobj - > type ! = OBJ_DEBRIS & & hitobj - > movement_source = = object : : movement_type : : physics ) {
expl_obj - > movement_source = object : : movement_type : : physics ;
2006-03-20 17:12:09 +00:00
expl_obj - > mtype . phys_info = hitobj - > mtype . phys_info ;
}
if ( hitobj - > render_type = = RT_POLYOBJ & & hitobj - > type ! = OBJ_DEBRIS )
explode_model ( hitobj ) ;
maybe_delete_object ( hitobj ) ;
}
hitobj - > flags | = OF_EXPLODING ; //say that this is blowing up
2020-08-10 03:45:13 +00:00
hitobj - > control_source = object : : control_type : : None ; //become inert while exploding
2006-03-20 17:12:09 +00:00
}
//do whatever needs to be done for this piece of debris for this frame
2022-07-09 13:39:29 +00:00
void do_debris_frame ( const d_robot_info_array & Robot_info , const vmobjptridx_t obj )
2006-03-20 17:12:09 +00:00
{
2020-08-10 03:45:13 +00:00
assert ( obj - > control_source = = object : : control_type : : debris ) ;
2006-03-20 17:12:09 +00:00
if ( obj - > lifeleft < 0 )
2022-07-09 13:39:29 +00:00
explode_object ( LevelUniqueObjectState , Robot_info , LevelSharedSegmentState , LevelUniqueSegmentState , obj , 0 ) ;
2006-03-20 17:12:09 +00:00
}
//do whatever needs to be done for this explosion for this frame
2022-07-09 13:39:29 +00:00
void do_explosion_sequence ( const d_robot_info_array & Robot_info , object & obj )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
auto & vmobjptridx = Objects . vmptridx ;
2020-08-10 03:45:13 +00:00
assert ( obj . control_source = = object : : control_type : : explosion ) ;
2006-03-20 17:12:09 +00:00
//See if we should die of old age
2019-12-22 05:34:08 +00:00
if ( obj . lifeleft < = 0 ) { // We died of old age
obj . flags | = OF_SHOULD_BE_DEAD ;
obj . lifeleft = 0 ;
2006-03-20 17:12:09 +00:00
}
//See if we should create a secondary explosion
2019-12-22 05:34:08 +00:00
if ( obj . lifeleft < = obj . ctype . expl_info . spawn_time ) {
auto del_obj = vmobjptridx ( obj . ctype . expl_info . delete_objnum ) ;
2014-10-26 21:33:50 +00:00
auto & spawn_pos = del_obj - > pos ;
2013-11-24 05:30:37 +00:00
Assert ( del_obj - > type = = OBJ_ROBOT | | del_obj - > type = = OBJ_CLUTTER | | del_obj - > type = = OBJ_CNTRLCEN | | del_obj - > type = = OBJ_PLAYER ) ;
2013-12-26 22:21:16 +00:00
Assert ( del_obj - > segnum ! = segment_none ) ;
2006-03-20 17:12:09 +00:00
2015-08-17 02:44:56 +00:00
const auto & & expl_obj = [ & ] {
2022-07-09 13:39:29 +00:00
const auto vclip_num = get_explosion_vclip ( Robot_info , del_obj , explosion_vclip_stage : : s1 ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2015-08-17 02:44:56 +00:00
if ( del_obj - > type = = OBJ_ROBOT )
{
const auto & ri = Robot_info [ get_robot_id ( del_obj ) ] ;
if ( ri . badass )
2022-07-09 13:39:29 +00:00
return object_create_badass_explosion ( Robot_info , object_none , vmsegptridx ( del_obj - > segnum ) , spawn_pos , fixmul ( del_obj - > size , EXPLOSION_SCALE ) , vclip_num , F1_0 * ri . badass , i2f ( 4 ) * ri . badass , i2f ( 35 ) * ri . badass , object_none ) ;
2015-08-17 02:44:56 +00:00
}
2013-03-03 01:03:33 +00:00
# endif
2022-11-07 01:59:34 +00:00
return object_create_explosion_without_damage ( Vclip , vmsegptridx ( del_obj - > segnum ) , spawn_pos , fixmul ( del_obj - > size , EXPLOSION_SCALE ) , vclip_num ) ;
2015-08-17 02:44:56 +00:00
} ( ) ;
2006-03-20 17:12:09 +00:00
if ( ( del_obj - > contains_count > 0 ) & & ! ( Game_mode & GM_MULTI ) ) { // Multiplayer handled outside of this code!!
// If dropping a weapon that the player has, drop energy instead, unless it's vulcan, in which case drop vulcan ammo.
if ( del_obj - > contains_type = = OBJ_POWERUP )
maybe_replace_powerup_with_energy ( del_obj ) ;
2022-07-09 13:39:29 +00:00
object_create_robot_egg ( Robot_info , del_obj ) ;
2006-03-20 17:12:09 +00:00
} else if ( ( del_obj - > type = = OBJ_ROBOT ) & & ! ( Game_mode & GM_MULTI ) ) { // Multiplayer handled outside this code!!
2017-08-26 19:47:51 +00:00
auto & robptr = Robot_info [ get_robot_id ( del_obj ) ] ;
if ( robptr . contains_count ) {
if ( ( ( d_rand ( ) * 16 ) > > 15 ) < robptr . contains_prob ) {
del_obj - > contains_count = ( ( d_rand ( ) * robptr . contains_count ) > > 15 ) + 1 ;
del_obj - > contains_type = robptr . contains_type ;
del_obj - > contains_id = robptr . contains_id ;
2006-03-20 17:12:09 +00:00
maybe_replace_powerup_with_energy ( del_obj ) ;
2022-07-09 13:39:29 +00:00
object_create_robot_egg ( Robot_info , del_obj ) ;
2006-03-20 17:12:09 +00:00
}
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2013-11-03 22:27:28 +00:00
if ( robot_is_thief ( robptr ) )
2022-07-23 20:58:10 +00:00
drop_stolen_items_local ( LevelUniqueObjectState , LevelSharedSegmentState , LevelUniqueSegmentState , Vclip , vmsegptridx ( del_obj - > segnum ) , del_obj - > mtype . phys_info . velocity , del_obj - > pos , LevelUniqueObjectState . ThiefState . Stolen_items ) ;
2017-08-26 19:47:51 +00:00
else if ( robot_is_companion ( robptr ) )
{
2006-03-20 17:12:09 +00:00
DropBuddyMarker ( del_obj ) ;
}
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
}
2017-08-26 19:47:51 +00:00
auto & robptr = Robot_info [ get_robot_id ( del_obj ) ] ;
if ( robptr . exp2_sound_num > - 1 )
2022-06-05 17:44:52 +00:00
digi_link_sound_to_pos ( robptr . exp2_sound_num , vmsegptridx ( del_obj - > segnum ) , sidenum_t : : WLEFT , spawn_pos , 0 , F1_0 ) ;
2006-03-20 17:12:09 +00:00
//PLAY_SOUND_3D( Robot_info[del_obj->id].exp2_sound_num, spawn_pos, del_obj->segnum );
2019-12-22 05:34:08 +00:00
obj . ctype . expl_info . spawn_time = - 1 ;
2006-03-20 17:12:09 +00:00
//make debris
if ( del_obj - > render_type = = RT_POLYOBJ )
explode_model ( del_obj ) ; //explode a polygon model
//set some parm in explosion
2009-02-22 10:53:10 +00:00
//If num_objects < MAX_USED_OBJECTS, expl_obj could be set to dead before this setting causing the delete_obj not to be removed. If so, directly delete del_obj
if ( expl_obj & & ! ( expl_obj - > flags & OF_SHOULD_BE_DEAD ) )
{
2020-08-10 03:45:13 +00:00
if ( del_obj - > movement_source = = object : : movement_type : : physics ) {
expl_obj - > movement_source = object : : movement_type : : physics ;
2006-03-20 17:12:09 +00:00
expl_obj - > mtype . phys_info = del_obj - > mtype . phys_info ;
}
expl_obj - > ctype . expl_info . delete_time = expl_obj - > lifeleft / 2 ;
2013-12-24 04:53:59 +00:00
expl_obj - > ctype . expl_info . delete_objnum = del_obj ;
2006-03-20 17:12:09 +00:00
}
else {
maybe_delete_object ( del_obj ) ;
}
}
//See if we should delete an object
2019-12-22 05:34:08 +00:00
if ( obj . lifeleft < = obj . ctype . expl_info . delete_time ) {
const auto & & del_obj = vmobjptr ( obj . ctype . expl_info . delete_objnum ) ;
obj . ctype . expl_info . delete_time = - 1 ;
2006-03-20 17:12:09 +00:00
maybe_delete_object ( del_obj ) ;
}
}
2018-08-12 21:08:07 +00:00
# define EXPL_WALL_TIME UINT16_MAX
2006-03-20 17:12:09 +00:00
# define EXPL_WALL_TOTAL_FIREBALLS 32
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
# define EXPL_WALL_FIREBALL_SIZE 0x48000 //smallest size
# elif defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
# define EXPL_WALL_FIREBALL_SIZE (0x48000*6 / 10) //smallest size
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
//explode the given wall
2022-01-09 15:25:42 +00:00
void explode_wall ( fvcvertptr & vcvertptr , const vcsegptridx_t segnum , const sidenum_t sidenum , wall & w )
2006-03-20 17:12:09 +00:00
{
2021-11-01 03:37:19 +00:00
if ( w . flags & wall_flag : : exploding )
2018-08-12 21:08:07 +00:00
/* Already exploding */
2006-03-20 17:12:09 +00:00
return ;
2018-08-12 21:08:07 +00:00
w . explode_time_elapsed = 0 ;
2021-11-01 03:37:19 +00:00
w . flags | = wall_flag : : exploding ;
2018-08-12 21:08:07 +00:00
+ + Num_exploding_walls ;
2006-03-20 17:12:09 +00:00
//play one long sound for whole door wall explosion
2018-03-12 03:43:47 +00:00
const auto & & pos = compute_center_point_on_side ( vcvertptr , segnum , sidenum ) ;
2014-10-26 21:33:50 +00:00
digi_link_sound_to_pos ( SOUND_EXPLODING_WALL , segnum , sidenum , pos , 0 , F1_0 ) ;
2006-03-20 17:12:09 +00:00
}
2022-07-09 13:39:29 +00:00
unsigned do_exploding_wall_frame ( const d_robot_info_array & Robot_info , wall & w1 )
2006-03-20 17:12:09 +00:00
{
2020-05-17 23:35:25 +00:00
auto & LevelSharedVertexState = LevelSharedSegmentState . get_vertex_state ( ) ;
auto & Vertices = LevelSharedVertexState . get_vertices ( ) ;
2020-04-04 19:30:22 +00:00
auto & WallAnims = GameSharedState . WallAnims ;
2021-11-01 03:37:19 +00:00
assert ( w1 . flags & wall_flag : : exploding ) ;
2018-08-12 21:08:07 +00:00
fix w1_explode_time_elapsed = w1 . explode_time_elapsed ;
const fix oldfrac = fixdiv ( w1_explode_time_elapsed , EXPL_WALL_TIME ) ;
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
w1_explode_time_elapsed + = FrameTime ;
if ( w1_explode_time_elapsed > EXPL_WALL_TIME )
w1_explode_time_elapsed = EXPL_WALL_TIME ;
w1 . explode_time_elapsed = w1_explode_time_elapsed ;
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
const auto w1sidenum = w1 . sidenum ;
const auto & & seg = vmsegptridx ( w1 . segnum ) ;
2019-06-20 04:02:27 +00:00
unsigned walls_updated = 0 ;
2018-08-12 21:08:07 +00:00
if ( w1_explode_time_elapsed > ( EXPL_WALL_TIME * 3 ) / 4 )
{
2018-12-13 02:31:38 +00:00
const auto & & csegp = seg . absolute_sibling ( seg - > shared_segment : : children [ w1sidenum ] ) ;
2018-08-12 21:08:07 +00:00
const auto cside = find_connect_side ( seg , csegp ) ;
const auto a = w1 . clip_num ;
2020-04-04 19:30:22 +00:00
auto & wa = WallAnims [ a ] ;
const auto n = wa . num_frames ;
wall_set_tmap_num ( wa , seg , w1sidenum , csegp , cside , n - 1 ) ;
2018-08-12 21:08:07 +00:00
2018-12-30 00:43:58 +00:00
auto & Walls = LevelUniqueWallSubsystemState . Walls ;
auto & vmwallptr = Walls . vmptr ;
2020-06-30 09:40:09 +00:00
auto cwall_num = csegp - > shared_segment : : sides [ cside ] . wall_num ;
if ( cwall_num ! = wall_none )
2018-08-12 21:08:07 +00:00
{
2020-06-30 09:40:09 +00:00
auto & w2 = * vmwallptr ( cwall_num ) ;
assert ( & w1 ! = & w2 ) ;
2021-11-01 03:37:19 +00:00
w2 . flags | = wall_flag : : blasted ;
assert ( ( w1 . flags & wall_flag : : exploding ) | | ( w2 . flags & wall_flag : : exploding ) ) ;
if ( w1_explode_time_elapsed > = EXPL_WALL_TIME & & w2 . flags & wall_flag : : exploding )
2018-08-12 21:08:07 +00:00
{
2021-11-01 03:37:19 +00:00
w2 . flags & = ~ wall_flag : : exploding ;
2019-06-20 04:02:27 +00:00
+ + walls_updated ;
2018-08-12 21:08:07 +00:00
}
}
2020-06-30 09:40:09 +00:00
else
2021-11-01 03:37:19 +00:00
assert ( w1 . flags & wall_flag : : exploding ) ;
2020-06-30 09:40:09 +00:00
2021-11-01 03:37:19 +00:00
w1 . flags | = wall_flag : : blasted ;
if ( w1_explode_time_elapsed > = EXPL_WALL_TIME & & w1 . flags & wall_flag : : exploding )
2020-06-30 09:40:09 +00:00
{
2021-11-01 03:37:19 +00:00
w1 . flags & = ~ wall_flag : : exploding ;
2020-06-30 09:40:09 +00:00
+ + walls_updated ;
}
Num_exploding_walls - = walls_updated ;
2018-08-12 21:08:07 +00:00
}
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
const fix newfrac = fixdiv ( w1_explode_time_elapsed , EXPL_WALL_TIME ) ;
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
const int old_count = f2i ( EXPL_WALL_TOTAL_FIREBALLS * fixmul ( oldfrac , oldfrac ) ) ;
const int new_count = f2i ( EXPL_WALL_TOTAL_FIREBALLS * fixmul ( newfrac , newfrac ) ) ;
if ( old_count > = new_count )
/* for loop would exit with zero iterations if this `if` is
* true . Skip the setup for the loop in that case .
*/
2019-06-20 04:02:27 +00:00
return walls_updated ;
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
const auto vertnum_list = get_side_verts ( seg , w1sidenum ) ;
2006-03-20 17:12:09 +00:00
2018-12-30 00:43:57 +00:00
auto & vcvertptr = Vertices . vcptr ;
2018-08-12 21:08:07 +00:00
auto & v0 = * vcvertptr ( vertnum_list [ 0 ] ) ;
auto & v1 = * vcvertptr ( vertnum_list [ 1 ] ) ;
auto & v2 = * vcvertptr ( vertnum_list [ 2 ] ) ;
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
const auto & & vv0 = vm_vec_sub ( v0 , v1 ) ;
const auto & & vv1 = vm_vec_sub ( v2 , v1 ) ;
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
//now create all the next explosions
2006-03-20 17:12:09 +00:00
2018-12-13 02:31:38 +00:00
auto & w1normal0 = seg - > shared_segment : : sides [ w1sidenum ] . normals [ 0 ] ;
2018-08-12 21:08:07 +00:00
for ( int e = old_count ; e < new_count ; + + e )
{
//calc expl position
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
auto pos = vm_vec_scale_add ( v1 , vv0 , d_rand ( ) * 2 ) ;
vm_vec_scale_add2 ( pos , vv1 , d_rand ( ) * 2 ) ;
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
const fix size = EXPL_WALL_FIREBALL_SIZE + ( 2 * EXPL_WALL_FIREBALL_SIZE * e / EXPL_WALL_TOTAL_FIREBALLS ) ;
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
//fireballs start away from door, with subsequent ones getting closer
vm_vec_scale_add2 ( pos , w1normal0 , size * ( EXPL_WALL_TOTAL_FIREBALLS - e ) / EXPL_WALL_TOTAL_FIREBALLS ) ;
2006-03-20 17:12:09 +00:00
2018-08-12 21:08:07 +00:00
if ( e & 3 ) //3 of 4 are normal
2022-11-07 01:59:34 +00:00
object_create_explosion_without_damage ( Vclip , seg , pos , size , VCLIP_SMALL_EXPLOSION ) ;
2018-08-12 21:08:07 +00:00
else
2022-07-09 13:39:29 +00:00
object_create_badass_explosion ( Robot_info , object_none , seg , pos ,
2018-08-12 21:08:07 +00:00
size ,
VCLIP_SMALL_EXPLOSION ,
i2f ( 4 ) , // damage strength
i2f ( 20 ) , // damage radius
i2f ( 50 ) , // damage force
object_none // parent id
) ;
2006-03-20 17:12:09 +00:00
}
2019-06-20 04:02:27 +00:00
return walls_updated ;
2006-03-20 17:12:09 +00:00
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
//creates afterburner blobs behind the specified object
2019-12-22 05:34:08 +00:00
void drop_afterburner_blobs ( object & obj , int count , fix size_scale , fix lifetime )
2006-03-20 17:12:09 +00:00
{
2019-12-22 05:34:08 +00:00
auto pos_left = vm_vec_scale_add ( obj . pos , obj . orient . fvec , - obj . size ) ;
vm_vec_scale_add2 ( pos_left , obj . orient . rvec , - obj . size / 4 ) ;
const auto pos_right = vm_vec_scale_add ( pos_left , obj . orient . rvec , obj . size / 2 ) ;
2006-03-20 17:12:09 +00:00
if ( count = = 1 )
2014-09-28 21:43:01 +00:00
vm_vec_avg ( pos_left , pos_left , pos_right ) ;
2006-03-20 17:12:09 +00:00
2019-12-22 05:34:08 +00:00
const auto & & objseg = Segments . vmptridx ( obj . segnum ) ;
2016-01-09 16:38:13 +00:00
{
2018-09-19 02:13:30 +00:00
const auto & & segnum = find_point_seg ( LevelSharedSegmentState , LevelUniqueSegmentState , pos_left , objseg ) ;
2013-12-26 22:21:16 +00:00
if ( segnum ! = segment_none )
2022-11-07 01:59:34 +00:00
object_create_explosion_without_damage ( Vclip , segnum , pos_left , size_scale , VCLIP_AFTERBURNER_BLOB ) ;
2016-01-09 16:38:13 +00:00
}
2006-03-20 17:12:09 +00:00
if ( count > 1 ) {
2018-09-19 02:13:30 +00:00
const auto & & segnum = find_point_seg ( LevelSharedSegmentState , LevelUniqueSegmentState , pos_right , objseg ) ;
2013-12-26 22:21:16 +00:00
if ( segnum ! = segment_none ) {
2022-11-07 01:59:34 +00:00
const auto & & blob_obj = object_create_explosion_without_damage ( Vclip , segnum , pos_right , size_scale , VCLIP_AFTERBURNER_BLOB ) ;
2014-09-13 22:01:17 +00:00
if ( lifetime ! = - 1 & & blob_obj ! = object_none )
2006-03-20 17:12:09 +00:00
blob_obj - > lifeleft = lifetime ;
}
}
}
2009-10-05 02:51:37 +00:00
/*
2016-01-09 16:38:14 +00:00
* reads n expl_wall structs from a PHYSFS_File and swaps if specified
2009-10-05 02:51:37 +00:00
*/
2018-08-12 21:08:07 +00:00
void expl_wall_read_n_swap ( fvmwallptr & vmwallptr , PHYSFS_File * const fp , const int swap , const unsigned count )
2009-10-05 02:51:37 +00:00
{
2018-08-12 21:08:07 +00:00
assert ( ! Num_exploding_walls ) ;
unsigned num_exploding_walls = 0 ;
/* Legacy versions of Descent always write a fixed number of
* entries , even if some or all of those entries are empty . This
* loop needs to count how many entries were valid , as well as load
* them into the correct walls .
*/
for ( unsigned i = count ; i - - ; )
2015-02-28 19:36:01 +00:00
{
disk_expl_wall d ;
PHYSFS_read ( fp , & d , sizeof ( d ) , 1 ) ;
if ( swap )
{
d . segnum = SWAPINT ( d . segnum ) ;
d . sidenum = SWAPINT ( d . sidenum ) ;
d . time = SWAPINT ( d . time ) ;
}
2022-07-02 18:10:45 +00:00
const auto s = segnum_t { static_cast < uint16_t > ( d . segnum ) } ;
if ( ! vmsegidx_t : : check_nothrow_index ( s ) )
continue ;
const icsegidx_t dseg = s ;
2018-08-12 21:08:07 +00:00
if ( dseg = = segment_none )
continue ;
range_for ( auto & & wp , vmwallptr )
{
auto & w = * wp ;
if ( w . segnum ! = dseg )
continue ;
2022-06-05 17:44:52 +00:00
if ( underlying_value ( w . sidenum ) ! = d . sidenum )
2018-08-12 21:08:07 +00:00
continue ;
2021-11-01 03:37:19 +00:00
w . flags | = wall_flag : : exploding ;
2018-08-12 21:08:07 +00:00
w . explode_time_elapsed = d . time ;
+ + num_exploding_walls ;
break ;
}
}
Num_exploding_walls = num_exploding_walls ;
}
void expl_wall_write ( fvcwallptr & vcwallptr , PHYSFS_File * const fp )
{
const unsigned num_exploding_walls = Num_exploding_walls ;
PHYSFS_write ( fp , & num_exploding_walls , sizeof ( unsigned ) , 1 ) ;
range_for ( auto & & wp , vcwallptr )
{
auto & e = * wp ;
2021-11-01 03:37:19 +00:00
if ( ! ( e . flags & wall_flag : : exploding ) )
2018-08-12 21:08:07 +00:00
continue ;
disk_expl_wall d ;
d . segnum = e . segnum ;
2022-06-05 17:44:52 +00:00
d . sidenum = underlying_value ( e . sidenum ) ;
2018-08-12 21:08:07 +00:00
d . time = e . explode_time_elapsed ;
PHYSFS_write ( fp , & d , sizeof ( d ) , 1 ) ;
2015-02-28 19:36:01 +00:00
}
2009-10-05 02:51:37 +00:00
}
2021-09-04 12:17:14 +00:00
// ------------------------------------------------------------------------------------------------------
// Choose segment to recreate thief in.
vmsegidx_t choose_thief_recreation_segment ( fvcsegptr & vcsegptr , fvcwallptr & vcwallptr , const vcsegidx_t plrseg )
{
static constexpr std : : integral_constant < connected_segment_raw_distances : : segment_distance_count_type , 20 > thief_max_depth { } ;
static constexpr std : : integral_constant < connected_segment_raw_distances : : segment_distance_count_type , thief_max_depth / 2 > thief_min_depth { } ;
static_assert ( thief_min_depth > = connected_segment_raw_distances : : minimum_supported_max_depth ) ;
const connected_segment_raw_distances rd ( vcsegptr , vcwallptr , thief_max_depth , plrseg ) ;
auto mrd = std : : minstd_rand ( d_rand ( ) ) ;
/* connected_segment_raw_distances explicitly avoids reporting any
2021-11-01 03:37:19 +00:00
* segment with a - > special of segment_special : : controlcen , even if that
2021-09-04 12:17:14 +00:00
* segment is in range . Therefore , any returned segment of the
* appropriate distance is usable .
*/
static_assert ( rd . count_segments_at_depth . valid_index ( thief_max_depth ) ) ;
static_assert ( rd . count_segments_at_depth . valid_index ( thief_min_depth + 1u ) ) ;
for ( const unsigned candidate_depth : xrange ( thief_max_depth , thief_min_depth , xrange_descending ( ) ) )
{
if ( const auto sn = rd . scan_segment_depths ( candidate_depth , mrd ) )
return * sn ;
}
/* This can be reached if there are no segments found at any of the
* acceptable depths . If the origin segment is in a small room with
* no passable exits , this can happen .
*
* Give up and pick a completely random segment . This is compatible
* with retail Descent 2.
*/
2022-07-02 18:10:45 +00:00
using distribution_type = typename std : : underlying_type < segnum_t > : : type ;
std : : uniform_int_distribution uid ( distribution_type { 0 } , static_cast < distribution_type > ( Highest_segment_index ) ) ;
return segnum_t { uid ( mrd ) } ;
2021-09-04 12:17:14 +00:00
}
2013-03-03 01:03:33 +00:00
# endif
2015-12-22 04:18:50 +00:00
}