/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/ 



#include "cmdline/cmdline.h"
#include "debris/debris.h"
#include "fireball/fireballs.h"
#include "freespace.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/linklist.h"
#include "io/timer.h"
#include "network/multi.h"
#include "network/multimsgs.h"
#include "network/multiutil.h"
#include "object/objcollide.h"
#include "object/objectsnd.h"
#include "particle/particle.h"
#include "particle/ParticleEffect.h"
#include "particle/volumes/LegacyAACuboidVolume.h"
#include "radar/radarsetup.h"
#include "render/3d.h"
#include "scripting/global_hooks.h"
#include "scripting/hook_api.h"
#include "scripting/scripting.h"
#include "scripting/api/objs/vecmath.h"
#include "ship/ship.h"
#include "ship/shipfx.h"
#include "species_defs/species_defs.h"
#include "tracing/Monitor.h"
#include "utils/Random.h"
#include "weapon/weapon.h"

constexpr int DEBRIS_SOUND_DELAY = 2000;				// time to start debris sound after created

int Num_hull_pieces;	// number of hull pieces in existence
						// we now maintain a kind of "virtual" Hull_debris_list,
						// but all we really need to know is how many hull pieces there are and whether a debris piece is on it

						// This list holds debris pieces that are set to expire when their lifetime runs out;
						// pieces that were placed by FREDers (i.e. that have the DoNotExpire flag) should not be on it.

SCP_vector<debris> Debris;

int Debris_inited = 0;

int Debris_model = -1;
int Debris_vaporize_model = -1;
int Debris_num_submodels = 0;

particle::ParticleEffectHandle Debris_hit_particle;

#define	DEBRIS_INDEX(dp) (int)(dp-Debris.data())

// Find the first available arc slot.  If none is available, and no_create is false, add one.
debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create);

/**
 * Start the sequence of a piece of debris writhing in unholy agony!!!
 */
static void debris_start_death_roll(object *debris_obj, debris *debris_p, vec3d *hitpos = nullptr)
{
	auto sip = &Ship_info[debris_p->ship_info_index];
	if (debris_p->is_hull)	{
		// tell everyone else to blow up the piece of debris
		if( MULTIPLAYER_MASTER )
			send_debris_update_packet(debris_obj,DEBRIS_UPDATE_NUKE);

		if (sip->debris_end_particles.isValid()) {
			auto source = particle::ParticleManager::get()->createSource(sip->debris_end_particles);

			// Use the position since the object is going to be invalid soon
			auto host = std::make_unique<EffectHostVector>(debris_obj->pos, debris_obj->orient, debris_obj->phys_info.vel);
			host->setRadius(debris_obj->radius);
			source->setHost(std::move(host));
			source->setNormal(debris_obj->orient.vec.uvec);
			source->finishCreation();
		} else {
			int fireball_type = fireball_ship_explosion_type(sip);
			if(fireball_type < 0) {
				fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
			}
			fireball_create( &debris_obj->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(debris_obj), debris_obj->radius*1.75f);
		}

		// only play debris destroy sound if hull piece and it has been around for at least 2 seconds
		if ( Missiontime > debris_p->time_started + 2*F1_0 ) {
			auto snd_id = sip->debris_explosion_sound;
			if (snd_id.isValid()) {
				snd_play_3d( gamesnd_get_game_sound(snd_id), &debris_obj->pos, &View_position, debris_obj->radius );
			}
		}
	} else {
		if (sip->shrapnel_end_particles.isValid()) {
			auto source = particle::ParticleManager::get()->createSource(sip->shrapnel_end_particles);

			// Use the position since the object is going to be invalid soon
			auto host = std::make_unique<EffectHostVector>(debris_obj->pos, debris_obj->orient, debris_obj->phys_info.vel);
			host->setRadius(debris_obj->radius);
			source->setHost(std::move(host));
			source->setNormal(debris_obj->orient.vec.uvec);
			source->finishCreation();
		}
	}

	if (scripting::hooks::OnDebrisDeath->isActive()) {
		scripting::hooks::OnDebrisDeath->run(scripting::hook_param_list(
			scripting::hook_param("Debris", 'o', debris_obj),
			scripting::hook_param("Hitpos",
				'o',
				scripting::api::l_Vector.Set(hitpos ? *hitpos : vmd_zero_vector),
				hitpos != nullptr)));
	}

    debris_obj->flags.set(Object::Object_Flags::Should_be_dead);
}

/**
 * This will get called at the start of each level.
 */
void debris_init()
{
	if ( !Debris_inited ) {
		Debris_inited = 1;
	}

	Debris_model = -1;
	Debris_vaporize_model = -1;
	Debris_num_submodels = 0;
		
	// Reset everything between levels
	Debris.clear();
	Debris.reserve(SOFT_LIMIT_DEBRIS_PIECES);

	Num_hull_pieces = 0;

	Debris_hit_particle = particle::ParticleManager::get()->addEffect(particle::ParticleEffect(
		"", //Name
		::util::UniformFloatRange(10.f), //Particle num
		particle::ParticleEffect::Duration::ONETIME, //Single Particle Emission
		::util::UniformFloatRange(), //No duration
		::util::UniformFloatRange (-1.f), //Single particle only
		particle::ParticleEffect::ShapeDirection::ALIGNED, //Particle direction
		::util::UniformFloatRange(1.f), //Velocity Inherit
		false, //Velocity Inherit absolute?
		make_unique<particle::LegacyAACuboidVolume>(0.3f, 1.f, true), //Velocity volume
		::util::UniformFloatRange(0.f, 10.f), //Velocity volume multiplier
		particle::ParticleEffect::VelocityScaling::NONE, //Velocity directional scaling
		std::nullopt, //Orientation-based velocity
		std::nullopt, //Position-based velocity
		nullptr, //Position volume
		particle::ParticleEffectHandle::invalid(), //Trail
		1.f, //Chance
		true, //Affected by detail
		1.f, //Culling range multiplier
		true, //Disregard Animation Length. Must be true for everything using particle::Anim_bitmap_X
		false, //Don't reverse animation
		false, //parent local
		false, //ignore velocity inherit if parented
		false, //position velocity inherit absolute?
		std::nullopt, //Local velocity offset
		std::nullopt, //Local offset
		::util::UniformFloatRange(0.25f, 0.75f), //Lifetime
		::util::UniformFloatRange(0.2f, 0.4f), //Radius
		particle::Anim_bitmap_id_fire)); //Bitmap
}

/**
 * Page in debris bitmaps at level load
 */
void debris_page_in()
{
	uint i;

	Debris_model = model_load( NOX("debris01.pof") );
	if (Debris_model >= 0)	{
		polymodel * pm;
		pm = model_get(Debris_model);
		Debris_num_submodels = pm->n_models;
	}

	Debris_vaporize_model = model_load( NOX("debris02.pof") );

	for (i=0; i<Species_info.size(); i++ )
	{
		species_info *species = &Species_info[i];

		nprintf(( "Paging", "Paging in debris texture '%s'\n", species->debris_texture.filename));

		species->debris_texture.bitmap_id = bm_load(species->debris_texture.filename);
		if (species->debris_texture.bitmap_id < 0)
		{
			Warning( LOCATION, "Couldn't load species %s debris\ntexture, '%s'\n", species->species_name, species->debris_texture.filename);
		}

		bm_page_in_texture(species->debris_texture.bitmap_id);
	}
	
}

MONITOR(NumSmallDebrisRend)
MONITOR(NumHullDebrisRend)

/**
 * Add item to [virtual] Hull_debris_list
 */
void debris_add_to_hull_list(debris *db)
{
	if ( db->is_hull && !db->flags[Debris_Flags::OnHullDebrisList] ) {
		Num_hull_pieces++;
		db->flags.set(Debris_Flags::OnHullDebrisList);
	}
}

/**
 * Remove item from [virtual] Hull_debris_list
 */
void debris_remove_from_hull_list(debris *db)
{
	if ( db->is_hull && db->flags[Debris_Flags::OnHullDebrisList] ) {
		Num_hull_pieces--;
		db->flags.remove(Debris_Flags::OnHullDebrisList);
	}
}

/**
 * Delete the debris object.  
 * This is only ever called via obj_delete().  Do not call directly.
 * Use debris_start_death_roll() if you want to force a debris piece to die.
 */
void debris_delete( object * obj )
{
	int		num;
	debris	*db;

	num = obj->instance;

	Assert(num >= 0 && num < (int)Debris.size());
	Assert(Debris[num].objnum == OBJ_INDEX(obj));

	db = &Debris[num];

	if (db->model_instance_num >= 0) {
		model_delete_instance(db->model_instance_num);
	}

	if ( db->is_hull ) {
		debris_remove_from_hull_list(db);
	}

	db->flags.reset();
	db->objnum = -1;
}


/**
 * Do various updates to debris:  check if time to die, start fireballs
 * Maybe delete debris if it's very far away from player.
 *
 * @param obj			pointer to debris object
 * @param frame_time	time elapsed since last debris_move() called
 */
void debris_process_post(object * obj, float frame_time)
{
	int num = obj->instance;
	int objnum = OBJ_INDEX(obj);

	Assert(num >= 0 && num < (int)Debris.size());
	Assert(Debris[num].objnum == objnum);

	debris *db = &Debris[num];

	if ( db->is_hull ) {
		radar_plot_object( obj );

		if ( timestamp_elapsed(db->sound_delay) ) {
			obj_snd_assign(objnum, db->ambient_sound, &vmd_zero_vector);
			db->sound_delay = TIMESTAMP::invalid();
		}
	}

	if (!db->flags[Debris_Flags::DoNotExpire]) {
		Assertion(db->lifeleft >= 0.0f, "A ship with a negative lifeleft should also have the DoNotExpire flag!");

		db->lifeleft -= frame_time;
		if (db->lifeleft < 0.0f) {
			debris_start_death_roll(obj, db);
		}
	}

	// ================== DO THE ELECTRIC ARCING STUFF =====================
	if ( db->arc_frequency <= 0 )	{
		return;			// If arc_frequency <= 0, this piece has no arcs on it
	}

	if ( !timestamp_elapsed(db->arc_timeout) && timestamp_elapsed(db->arc_next_time))	{

		db->arc_next_time = _timestamp_rand(db->arc_frequency,db->arc_frequency*2 );
		db->arc_frequency += 100;	

		if (db->is_hull)	{

			int n_arcs = Random::next(1, 3);		// Create 1-3 sparks

			vec3d v1 = submodel_get_random_point(db->model_num, db->submodel_num);
			vec3d v2 = submodel_get_random_point(db->model_num, db->submodel_num);
			vec3d v3 = submodel_get_random_point(db->model_num, db->submodel_num);
			vec3d v4 = submodel_get_random_point(db->model_num, db->submodel_num);

			int lifetime = Random::next(100, 1000);

			// Create the spark effects
			for (int n = 0; n < n_arcs; n++) {
				auto arc = debris_find_or_create_electrical_arc_slot(db, db->electrical_arcs.size() >= MAX_DEBRIS_ARCS);
				if (arc) {
					arc->timestamp = _timestamp(lifetime);	// live up to a second

					switch( n )	{
					case 0:
						arc->endpoint_1 = v1;
						arc->endpoint_2 = v2;
						break;
					case 1:
						arc->endpoint_1 = v2;
						arc->endpoint_2 = v3;
						break;
					case 2:
						arc->endpoint_1 = v2;
						arc->endpoint_2 = v4;
						break;

					default:
						UNREACHABLE("Unhandled case %d for electrical arc creation in debris_process_post()!", n);
					}
				}
			}

			// rotate v2 out of local coordinates into world.
			// Use v2 since it is used in every bolt.  See above switch().
			vec3d snd_pos;
			vm_vec_unrotate(&snd_pos, &v2, &obj->orient);
			vm_vec_add2(&snd_pos, &obj->pos );

			//Play a sound effect
			if ( lifetime > 750 )	{
				// 1.00 second effect
				snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_05), &snd_pos, &View_position, obj->radius );
			} else if ( lifetime >  500 )	{
				// 0.75 second effect
				snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_04), &snd_pos, &View_position, obj->radius );
			} else if ( lifetime >  250 )	{
				// 0.50 second effect
				snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_03), &snd_pos, &View_position, obj->radius );
			} else if ( lifetime >  100 )	{
				// 0.25 second effect
				snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_02), &snd_pos, &View_position, obj->radius );
			} else {
				// 0.10 second effect
				snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_01), &snd_pos, &View_position, obj->radius );
			}
		}
	}

	for (auto &arc: db->electrical_arcs) {
		if (arc.timestamp.isValid()) {
			if (timestamp_elapsed(arc.timestamp)) {
				// Kill off the spark
				arc.timestamp = TIMESTAMP::invalid();
			} else {
				// Maybe move a vertex....  20% of the time maybe?
				int mr = Random::next();
				if ( mr < Random::MAX_VALUE/5 )	{
					auto pt = submodel_get_random_point(db->model_num, db->submodel_num);
					if (mr % 2 == 0)
						arc.endpoint_1 = pt;
					else
						arc.endpoint_2 = pt;
				}
			}
		}
	}
}

/**
 * Locate the oldest hull debris chunk.  Search through the Hull_debris_list, which is a list
 * of all the hull debris chunks.
 */
int debris_find_oldest()
{
	int		oldest_index;
	fix		oldest_time;

	oldest_index = -1;
	oldest_time = 0x7fffffff;

	for (auto &db: Debris) {
		if (db.flags[Debris_Flags::OnHullDebrisList]) {
			if ((db.time_started < oldest_time) && !(Objects[db.objnum].flags[Object::Object_Flags::Should_be_dead])) {
				oldest_index = DEBRIS_INDEX(&db);
				oldest_time = db.time_started;
			}
		}
	}

	return oldest_index;
}

#define	DEBRIS_ROTVEL_SCALE	5.0f
void calc_debris_physics_properties( physics_info *pi, vec3d *min, vec3d *max, float density );

MONITOR(NumSmallDebris)
MONITOR(NumHullDebris)

/**
 * Create debris from an object
 *
 * @param source_obj	Source object
 * @param model_num		Model number
 * @param submodel_num	Sub-model number
 * @param pos			Position in global coordinates
 * @param exp_center	Explosion center in global coordinates
 * @param hull_flag		Whether this debris is a chunk of a ship's hull (as opposed to shrapnel)
 * @param exp_force		Explosion force, used to assign velocity to pieces. 1.0f assigns velocity like before. 2.0f assigns twice as much to non-inherited part of velocity
 * @param source_subsys	The subsystem this debris came from, if any
 */
object *debris_create(object *source_obj, int model_num, int submodel_num, const vec3d *pos, const vec3d *exp_center, bool hull_flag, float exp_force, const ship_subsys* source_subsys)
{
	int             parent_objnum;
	object  *obj;
	ship            *shipp;
	bool vaporize;

	parent_objnum = OBJ_INDEX(source_obj);

	Assert( (source_obj->type == OBJ_SHIP ) || (source_obj->type == OBJ_GHOST));
	Assert( source_obj->instance >= 0 && source_obj->instance < MAX_SHIPS );	
	shipp = &Ships[source_obj->instance];

	vaporize = (shipp->flags[Ship::Ship_Flags::Vaporize]);

	obj = debris_create_only(parent_objnum, shipp->ship_info_index, shipp->alt_type_index, shipp->team, -1.0f, -1, model_num, submodel_num, pos, nullptr, hull_flag, vaporize, shipp->debris_damage_type_idx);
	if (obj != nullptr)
	{
		debris_create_set_velocity(&Debris[obj->instance], shipp, exp_center, exp_force, source_subsys);
		debris_create_fire_hook(obj, source_obj);
		const auto& sip = Ship_info[Ships[source_obj->instance].ship_info_index];
		particle::ParticleEffectHandle flame_effect;
		if (source_subsys != nullptr) {
			if (hull_flag) {
				if (source_subsys->system_info->debris_flame_particles.isValid()) {
					flame_effect = source_subsys->system_info->debris_flame_particles;
				} else {
					flame_effect = sip.default_subsys_debris_flame_particles;
				}
			} else {
				if (source_subsys->system_info->shrapnel_flame_particles.isValid()) {
					flame_effect = source_subsys->system_info->shrapnel_flame_particles;
				} else {
					flame_effect = sip.default_subsys_shrapnel_flame_particles;
				}
			}
		} else {
			flame_effect = hull_flag ? sip.debris_flame_particles : sip.shrapnel_flame_particles;
		}
		if (flame_effect.isValid()) {
			auto source = particle::ParticleManager::get()->createSource(flame_effect);
			source->setHost(std::make_unique<EffectHostObject>(obj, vmd_zero_vector));
			source->setTriggerRadius(source_obj->radius);
			source->setTriggerVelocity(vm_vec_mag_quick(&source_obj->phys_info.vel));
			source->finishCreation();
		}
	}

	return obj;
}

object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_type_index, int team, float hull_strength, int spark_timeout, int model_num, int submodel_num, const vec3d *pos, const matrix *orient, bool hull_flag, bool vaporize, int damage_type_idx)
{
	int		objnum;
	object	*obj;
	debris	*db;
	ship_info *sip;
	matrix orient_buf;

	// try to maintain our soft limit
	if (hull_flag && (Num_hull_pieces >= SOFT_LIMIT_DEBRIS_PIECES)) {
		// cause oldest hull debris chunk to blow up
		int oldest_n = debris_find_oldest();
		if (oldest_n >= 0)
			debris_start_death_roll(&Objects[Debris[oldest_n].objnum], &Debris[oldest_n]);
	}

	int n = 0;
	for (auto &db_temp: Debris) {
		if ( !(db_temp.flags[Debris_Flags::Used]) )
			break;
		++n;
	}

	// we might have to create a new slot
	if (n == (int)Debris.size()) {
		Debris.emplace_back();
	}

	db = &Debris[n];


	// validate or deduce some parameters

	if ( pos == nullptr ) {
		if (parent_objnum >= 0) {
			pos = &Objects[parent_objnum].pos;
		} else {
			pos = &vmd_zero_vector;
		}
	}

	if ( orient == nullptr ) {
		if (parent_objnum >= 0 && hull_flag) {
			orient = &Objects[parent_objnum].orient;
		} else {
			// non-hull debris has no relation to its parent orientation
			vec3d rand;
			vm_vec_rand_vec(&rand);
			vm_vector_2_matrix_norm(&orient_buf, &rand);
			orient = &orient_buf;
		}
	}

	sip = (parent_ship_class < 0) ? nullptr : &Ship_info[parent_ship_class];
	if (hull_flag && !sip)
	{
		Warning(LOCATION, "Cannot create hull debris without a ship class!");
		return nullptr;
	}

	if (hull_strength < 0.0f)
	{
		if (parent_objnum >= 0 && Objects[parent_objnum].type == OBJ_SHIP)
			hull_strength = Ships[Objects[parent_objnum].instance].ship_max_hull_strength / 8.0f;
		else
			hull_strength = 10.0f;
	}

	if (hull_flag && (model_num < 0 || submodel_num < 0))
	{
		Warning(LOCATION, "Model and submodel numbers must be specified for hull debris!");
		return nullptr;
	}

	if (team < 0 && hull_flag)
	{
		if (parent_objnum >= 0 && Objects[parent_objnum].type == OBJ_SHIP)
			team = Ships[Objects[parent_objnum].instance].team;
		else if (Player_ship)
			team = Player_ship->team;
		else
			team = 0;
	}

	if (damage_type_idx < 0)
	{
		if (parent_objnum >= 0 && Objects[parent_objnum].type == OBJ_SHIP)
			damage_type_idx = Ships[Objects[parent_objnum].instance].debris_damage_type_idx;
	}

	// done validation


	if (!hull_flag) {
		if (model_num >= 0) {
			db->model_num = model_num;
			db->submodel_num = (sip == nullptr || sip->generic_debris_num_submodels <= 0) ? 0 : Random::next(sip->generic_debris_num_submodels);
		}
		else if (vaporize) {
			db->model_num = Debris_vaporize_model;
			db->submodel_num = (Debris_num_submodels <= 0 ? 0 : Random::next(Debris_num_submodels));
		}
		else {
			db->model_num = Debris_model;
			db->submodel_num = (Debris_num_submodels <= 0 ? 0 : Random::next(Debris_num_submodels));
		}
	}
	else {
		db->model_num = model_num;
		db->submodel_num = submodel_num;
	}

	float radius = submodel_get_radius(db->model_num, db->submodel_num);

	// if its generic, maybe cull it if its too small and far
	if (!hull_flag) {
		float dist = vm_vec_dist_quick(pos, &Eye_position);
		// Make vaporize debris seen from farther away
		if (vaporize) {
			dist /= 2.0f;
		}
		if (dist > radius * 200.0f) {
			return nullptr;
		}
	}

	// Create Debris piece n!
	if(hull_flag)
	{
		// set lifeleft based on tabled entries if they are not negative 
		// coded originally by WMC then clean-up added by wookieejedi
		if(sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
		{
			db->lifeleft = (( sip->debris_max_lifetime - sip->debris_min_lifetime ) * frand()) + sip->debris_min_lifetime;
		}
		else if(sip->debris_min_lifetime >= 0.0f)
		{
			if(db->lifeleft < sip->debris_min_lifetime)
				db->lifeleft = sip->debris_min_lifetime;
		}
		else if(sip->debris_max_lifetime >= 0.0f)
		{
			if(db->lifeleft > sip->debris_max_lifetime)
				db->lifeleft = sip->debris_max_lifetime;
		}
		// By default, make some pieces blow up shortly after explosion.
		else if (Random::next() < (Random::MAX_VALUE / 6)) 
		{
			db->lifeleft = 2.0f * (frand()) + 0.5f;
		}
		else 
		{
			db->flags.set(Debris_Flags::DoNotExpire);
			db->lifeleft = -1.0f; // large hull pieces stay around forever
		}
	} 
	else 
	{
		// small non-hull pieces should stick around longer the larger they are
		// sqrtf should make sure its never too crazy long
		db->lifeleft = (frand() * 2.0f + 0.1f) * sqrtf(radius);
	}

	// increase lifetime for vaporized debris
	if (vaporize) {
		db->lifeleft *= 3.0f;
	}
	db->flags.set(Debris_Flags::Used);
	db->is_hull = hull_flag;
	db->source_objnum = parent_objnum;
	db->damage_type_idx = damage_type_idx;
	db->ship_info_index = parent_ship_class;
	db->team = team;
	db->ambient_sound = (sip == nullptr) ? gamesnd_id(-1) : sip->debris_ambient_sound;
	db->arc_timeout = TIMESTAMP::never();	// if not changed, timestamp_elapsed() will return false
	db->time_started = Missiontime;
	db->species = (sip == nullptr) ? -1 : sip->species;
	db->parent_alt_name = alt_type_index;
	db->damage_mult = 1.0f;

	db->electrical_arcs.clear();

	if ( db->is_hull )	{
		// Percent of debris pieces with arcs controlled via table (default 50%)
		if (frand() < sip->debris_arc_percent) {
			db->arc_frequency = 1000;
		} else {
			db->arc_frequency = 0;
		}
	} else {
		db->arc_frequency = 0;
	}

	db->arc_next_time = _timestamp_rand(500,2000);	//start one 1/2 - 2 secs later

	flagset<Object::Object_Flags> default_flags;
	default_flags.set(Object::Object_Flags::Renders);
	default_flags.set(Object::Object_Flags::Physics);
	default_flags.set(Object::Object_Flags::Collides, hull_flag != 0);

	objnum = obj_create(OBJ_DEBRIS, parent_objnum, n, orient, pos, radius, default_flags, false);
	if ( objnum == -1 ) {
		mprintf(("Couldn't create debris object -- out of object slots\n"));
		return nullptr;
	}

	db->objnum = objnum;
	obj = &Objects[objnum];

	db->model_instance_num = hull_flag ? model_create_instance(objnum, db->model_num) : -1;

	// ensure that any texture replace on parent ship gets copied to debris --wookieejedi
	int parent_model_instance_num;
	if ((db->model_instance_num >= 0) && (parent_objnum >= 0) && ((parent_model_instance_num = object_get_model_instance_num(&Objects[parent_objnum])) >= 0))
		model_get_instance(db->model_instance_num)->texture_replace = model_get_instance(parent_model_instance_num)->texture_replace;

	// assign the network signature.  The signature will be 0 for non-hull pieces, but since that
	// is our invalid signature, it should be okay.
	obj->net_signature = 0;

	if ( (Game_mode & GM_MULTIPLAYER) && hull_flag ) {
		obj->net_signature = multi_get_next_network_signature( MULTI_SIG_DEBRIS );
	}

	obj->hull_strength = hull_strength;

	if (hull_flag) {
		float min_hp = sip->debris_min_hitpoints;
		float max_hp = sip->debris_max_hitpoints;
		if (sip->debris_hitpoints_radius_multi >= 0.0f) {
			min_hp *= sip->debris_hitpoints_radius_multi * radius;
			max_hp *= sip->debris_hitpoints_radius_multi * radius;
		}
		if (min_hp >= 0.0f && max_hp >= 0.0f)
		{
			obj->hull_strength = ((max_hp - min_hp) * frand()) + min_hp;
		}
		else if(min_hp >= 0.0f)
		{
			if (obj->hull_strength < min_hp)
				obj->hull_strength = min_hp;
		}
		else if(max_hp >= 0.0f)
		{
			if (obj->hull_strength > max_hp)
				obj->hull_strength = max_hp;
		}
		db->damage_mult = sip->debris_damage_mult;

		db->sound_delay = _timestamp(DEBRIS_SOUND_DELAY);

		// limit the amount of time that fireballs appear
		// let fireball length be linked to radius of ship.  Range is .33 radius => 3.33 radius seconds.
		if (spark_timeout >= 0) {
			db->arc_timeout = _timestamp(spark_timeout);
		} else if (parent_objnum >= 0) {
			float t = 1000*Objects[parent_objnum].radius/3 + (fl2i(1000*3*Objects[parent_objnum].radius) == 0 ? 0 : Random::next(fl2i(1000*3*Objects[parent_objnum].radius)));
			db->arc_timeout = _timestamp(fl2i(t));		// fireballs last from 5 - 30 seconds
		} else {
			db->arc_timeout = TIMESTAMP::immediate();
		}

		if (parent_objnum >= 0 && Objects[parent_objnum].radius >= Min_radius_for_persistent_debris) {
			db->flags.set(Debris_Flags::DoNotExpire);
		} else {
			debris_add_to_hull_list(db);
		}
	}

	db->max_hull = obj->hull_strength;

	if (hull_flag) {
		MONITOR_INC(NumHullDebris,1);
	} else {
		MONITOR_INC(NumSmallDebris,1);
	}

	return obj;
}

void debris_create_set_velocity(const debris *db, const ship *source_shipp, const vec3d *exp_center, float exp_force, const ship_subsys* source_subsys)
{
	auto obj = &Objects[db->objnum];
	auto source_obj = (source_shipp == nullptr) ? nullptr : &Objects[source_shipp->objnum];
	physics_info *pi = &obj->phys_info;
	vec3d rotvel, radial_vel, to_center;

	if ( exp_center )
		vm_vec_sub( &to_center, &obj->pos, exp_center );
	else
		vm_vec_zero(&to_center);

	float scale;
	if ( db->is_hull )	{
		scale = exp_force * i2fl(Random::next(10, 29));	// for radial_vel away from location of blast center

		// set up physics mass and I_inv for hull debris pieces
		auto pm = model_get(db->model_num);
		vec3d *min, *max;
		min = &pm->submodel[db->submodel_num].min;
		max = &pm->submodel[db->submodel_num].max;
		float density = source_subsys != nullptr ? source_subsys->system_info->density : Ship_info[source_shipp->ship_info_index].debris_density;
		calc_debris_physics_properties( &obj->phys_info, min, max, density);
	}
	else {
		scale = exp_force * i2fl(Random::next(10, 29));	// for radial_vel away from blast center (non-hull)
	}

	if ( vm_vec_mag_squared( &to_center ) < 0.1f )	{
		vm_vec_rand_vec_quick(&radial_vel);
		vm_vec_scale(&radial_vel, scale );
	}
	else {
		vm_vec_normalize(&to_center);
		vm_vec_copy_scale(&radial_vel, &to_center, scale );
	}

	if (source_obj)
	{
		// DA: here we need to vel_from_rot = w x to_center, where w is world is unrotated to world coords and offset is the 
		// displacement fromt the center of the parent object to the center of the debris piece
		vec3d world_rotvel, vel_from_rotvel;
		vm_vec_unrotate ( &world_rotvel, &source_obj->phys_info.rotvel, &source_obj->orient );
		vm_vec_cross ( &vel_from_rotvel, &world_rotvel, &to_center );
		vm_vec_scale ( &vel_from_rotvel, DEBRIS_ROTVEL_SCALE);

		vm_vec_add (&obj->phys_info.vel, &radial_vel, &source_obj->phys_info.vel);
		vm_vec_add2(&obj->phys_info.vel, &vel_from_rotvel);
	}


	float radius = submodel_get_radius(db->model_num, db->submodel_num);

	// make sure rotational velocity does not get too high
	if (radius < 1.0) {
		radius = 1.0f;
	}

	scale = (6.0f + i2fl(Random::next(4))) / radius;

	vm_vec_rand_vec_quick(&rotvel);
	vm_vec_scale(&rotvel, scale);

	pi->rotvel = rotvel;
	pi->flags |= (PF_DEAD_DAMP | PF_BALLISTIC);
	check_rotvel_limit( &obj->phys_info );

	// check that debris is not created with too high a velocity
	if (db->is_hull)
	{
		shipfx_debris_limit_speed(db, source_shipp);
	}

	// blow out his reverse thrusters. Or drag, same thing.
	pi->rotdamp = 10000.0f;
	if (source_shipp != nullptr) {
		pi->gravity_const = Ship_info[source_shipp->ship_info_index].debris_gravity_const;
	} else {
		pi->gravity_const = 1.0f;
	}
	

	vm_vec_zero(&pi->max_vel);		// make so he can't turn on his own VOLITION anymore.
	vm_vec_zero(&pi->max_rotvel);	// make so he can't change speed on his own VOLITION anymore.

	// ensure vel is valid
	Assert( !vm_is_vec_nan(&obj->phys_info.vel) );
}

void debris_create_fire_hook(object *obj, object *source_obj)
{
	if (scripting::hooks::OnDebrisCreated->isActive()) {
		scripting::hooks::ShipSourceConditions conditions;
		conditions.source_shipp = (source_obj != nullptr && source_obj->type == OBJ_SHIP) ? &Ships[source_obj->instance] : nullptr;
		scripting::hooks::OnDebrisCreated->run(std::move(conditions),
			scripting::hook_param_list(
				scripting::hook_param("Debris", 'o', obj),
				scripting::hook_param("Source", 'o', source_obj)));
	}
}

/**
 * Alas, poor debris_obj got whacked.  Fortunately, we know who did it, where and how hard, so we
 * can do something about it.
 */
void debris_hit(object *debris_obj, object * /*other_obj*/, vec3d *hitpos, float damage, vec3d* force)
{
	debris	*debris_p = &Debris[debris_obj->instance];

	// Do a little particle spark shower to show we hit
	if (Debris_hit_particle.isValid()){			// Where the particles emit from
		vec3d tmp_norm;
		vm_vec_sub( &tmp_norm, hitpos, &debris_obj->pos );
		vm_vec_normalize_safe(&tmp_norm);
		matrix orient;
		vm_vector_2_matrix_norm(&orient, &tmp_norm);

		auto source = particle::ParticleManager::get()->createSource(Debris_hit_particle);
		auto host = std::make_unique<EffectHostVector>(*hitpos, orient, debris_obj->phys_info.vel);
		host->setRadius(debris_obj->radius);
		source->setHost(std::move(host));
		source->finishCreation();
	}

	// multiplayer clients bail here
	if(MULTIPLAYER_CLIENT){
		return;
	}

	if ( damage < 0.0f ) {
		damage = 0.0f;
	}

	if (hitpos && force && The_mission.ai_profile->flags[AI::Profile_Flags::Whackable_debris]) {
		vec3d rel_hit_pos = *hitpos - debris_obj->pos;
		physics_calculate_and_apply_whack(force, &rel_hit_pos, &debris_obj->phys_info, &debris_obj->orient, &debris_obj->phys_info.I_body_inv);
	}

	debris_obj->hull_strength -= damage;

	if (debris_obj->hull_strength < 0.0f) {
		debris_start_death_roll(debris_obj, debris_p, hitpos);
	} else {
		// otherwise, give all the other players an update on the debris
		if(MULTIPLAYER_MASTER){
			send_debris_update_packet(debris_obj,DEBRIS_UPDATE_UPDATE);
		}
	}
}

/**
 * See if poor debris object *obj got whacked by evil *other_obj at point *hitpos.
 * NOTE: debris_hit_info pointer NULL for debris:weapon collision, otherwise debris:ship collision.
 * @return true if hit, else return false.
 */
int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, collision_info_struct *debris_hit_info, vec3d* hitNormal)
{
	mc_info	mc;

	Assert( pdebris->type == OBJ_DEBRIS );

	int num = pdebris->instance;

	Assert( num >= 0 && num < (int)Debris.size() );
	Assert( Debris[num].objnum == OBJ_INDEX(pdebris));	

	// debris_hit_info NULL - so debris-weapon collision
	if ( debris_hit_info == NULL ) {
		// debris weapon collision
		Assert( other_obj->type == OBJ_WEAPON );
		mc.model_instance_num = -1;
		mc.model_num = Debris[num].model_num;	// Fill in the model to check
		mc.submodel_num = Debris[num].submodel_num;
		mc.orient = &pdebris->orient;					// The object's orient
		mc.pos = &pdebris->pos;							// The object's position
		mc.p0 = &other_obj->last_pos;				// Point 1 of ray to check
		mc.p1 = &other_obj->pos;					// Point 2 of ray to check
		mc.flags = (MC_CHECK_MODEL | MC_SUBMODEL);

		if (model_collide(&mc)) {
			*hitpos = mc.hit_point_world;

			if (hitNormal)
			{
				vec3d normal;

				if (mc.model_instance_num >= 0)
					model_instance_local_to_global_dir(&normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, mc.orient);
				else
					model_local_to_global_dir(&normal, &mc.hit_normal, mc.model_num, mc.hit_submodel, mc.orient);

				*hitNormal = normal;
			}
		}

		weapon *wp = &Weapons[other_obj->instance];
		wp->collisionInfo = new mc_info;	// The weapon will free this memory later
		*wp->collisionInfo = mc;

		return mc.num_hits;
	}
	
	// debris ship collision -- use debris_hit_info to calculate physics
	object *pship_obj = other_obj;
	Assert( pship_obj->type == OBJ_SHIP );

	object *heavy = debris_hit_info->heavy;
	object *lighter = debris_hit_info->light;
	object *heavy_obj = heavy;
	object *light_obj = lighter;

	vec3d zero, p0, p1;
	vm_vec_zero(&zero);
	vm_vec_sub(&p0, &lighter->last_pos, &heavy->last_pos);
	vm_vec_sub(&p1, &lighter->pos, &heavy->pos);

	mc.pos = &zero;								// The object's position
	mc.p0 = &p0;									// Point 1 of ray to check
	mc.p1 = &p1;									// Point 2 of ray to check

	// find the light object's position in the heavy object's reference frame at last frame and also in this frame.
	vec3d p0_temp, p0_rotated;
		
	// Collision detection from rotation enabled if at rotation is less than 30 degree in frame
	// This should account for all ships
	if ( (vm_vec_mag_squared(&heavy->phys_info.rotvel) * flFrametime*flFrametime) < (PI*PI/36) ) {
		// collide_rotate calculate (1) start position and (2) relative velocity
		debris_hit_info->collide_rotate = true;
		vm_vec_rotate(&p0_temp, &p0, &heavy->last_orient);
		vm_vec_unrotate(&p0_rotated, &p0_temp, &heavy->orient);
		mc.p0 = &p0_rotated;				// Point 1 of ray to check
		vm_vec_sub(&debris_hit_info->light_rel_vel, &p1, &p0_rotated);
		vm_vec_scale(&debris_hit_info->light_rel_vel, 1/flFrametime);
	} else {
		debris_hit_info->collide_rotate = false;
		vm_vec_sub(&debris_hit_info->light_rel_vel, &lighter->phys_info.vel, &heavy->phys_info.vel);
	}

	int mc_ret_val = 0;

	if ( debris_hit_info->heavy == pship_obj ) {	// ship is heavier, so debris is sphere. Check sphere collision against ship poly model
		mc.model_instance_num = Ships[pship_obj->instance].model_instance_num;
		mc.model_num = Ship_info[Ships[pship_obj->instance].ship_info_index].model_num;	// Fill in the model to check
		mc.orient = &pship_obj->orient;								// The object's orient
		mc.radius = pdebris->radius;
		mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE);

		// copy important data
		int orig_flags = mc.flags;  // make a copy of start end positions of sphere in big ship RF
		vec3d orig_p0 = *mc.p0;
		vec3d orig_p1 = *mc.p1;

		// first test against the sphere - if this fails then don't do any submodel tests
		mc.flags = MC_ONLY_SPHERE | MC_CHECK_SPHERELINE;

		if (model_collide(&mc)) {

			// Set earliest hit time
			debris_hit_info->hit_time = FLT_MAX;

			auto pmi = model_get_instance(Ships[heavy_obj->instance].model_instance_num);
			auto pm = model_get(pmi->model_num);

			// Do collision the cool new way
			if ( debris_hit_info->collide_rotate ) {
				// We collide with the sphere, find the list of moving submodels and test one at a time
				SCP_vector<int> submodel_vector;
				model_get_moving_submodel_list(submodel_vector, heavy_obj);

				// turn off all moving submodels, collide against only 1 at a time.
				// turn off collision detection for all moving submodels
				for (auto submodel : submodel_vector) {
					pmi->submodel[submodel].collision_checked = true;
				}

				// Only check single submodel now, since children of moving submodels are handled as moving as well
				mc.flags = orig_flags | MC_SUBMODEL;

				if (Ship_info[Ships[pship_obj->instance].ship_info_index].collision_lod > -1) {
					mc.lod = Ship_info[Ships[pship_obj->instance].ship_info_index].collision_lod;
				}

				// check each submodel in turn
				for (auto submodel: submodel_vector) {
					auto smi = &pmi->submodel[submodel];

					// turn on just one submodel for collision test
					smi->collision_checked = false;

					// find the start and end positions of the sphere in submodel RF
					model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
					model_instance_global_to_local_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);

					mc.p0 = &p0;
					mc.p1 = &p1;

					mc.orient = &vmd_identity_matrix;
					mc.submodel_num = submodel;

					if ( model_collide(&mc) ) {
						if ( mc.hit_dist < debris_hit_info->hit_time ) {
							mc_ret_val = 1;

							// set up debris_hit_info common
							set_hit_struct_info(debris_hit_info, &mc, true);
							model_instance_local_to_global_point(&debris_hit_info->hit_pos, &mc.hit_point, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);

							// set up debris_hit_info for rotating submodel
							if (!debris_hit_info->edge_hit) {
								model_instance_local_to_global_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
							}

							// find position in submodel RF of light object at collison
							vec3d int_light_pos, diff;
							vm_vec_sub(&diff, mc.p1, mc.p0);
							vm_vec_scale_add(&int_light_pos, mc.p0, &diff, mc.hit_dist);
							model_instance_local_to_global_point(&debris_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
						}
					}

					// Don't look at this submodel again
					smi->collision_checked = true;
				}
			}

			// Now complete base model collision checks that do not take into account rotating submodels.
			mc.flags = orig_flags;
			mc.p0 = &orig_p0;
			mc.p1 = &orig_p1;
			mc.orient = &heavy_obj->orient;

			// usual ship_ship collision test
			if ( model_collide(&mc) )	{
				// check if this is the earliest hit
				if (mc.hit_dist < debris_hit_info->hit_time) {
					mc_ret_val = 1;

					set_hit_struct_info(debris_hit_info, &mc, false);

					// get collision normal if not edge hit
					if (!debris_hit_info->edge_hit) {
						model_instance_local_to_global_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
					}

					// find position in submodel RF of light object at collison
					vec3d diff;
					vm_vec_sub(&diff, mc.p1, mc.p0);
					vm_vec_scale_add(&debris_hit_info->light_collision_cm_pos, mc.p0, &diff, mc.hit_dist);

				}
			}
		}

	} else {
		// Debris is heavier obj
		mc.model_instance_num = -1;
		mc.model_num = Debris[num].model_num;		// Fill in the model to check
		mc.submodel_num = Debris[num].submodel_num;
		mc.orient = &pdebris->orient;				// The object's orient
		mc.radius = model_get_core_radius(Ship_info[Ships[pship_obj->instance].ship_info_index].model_num);

		// check for collision between debris model and ship sphere
		mc.flags = (MC_CHECK_MODEL | MC_SUBMODEL | MC_CHECK_SPHERELINE);

		mc_ret_val = model_collide(&mc);

		if (mc_ret_val) {
			set_hit_struct_info(debris_hit_info, &mc, false);

			// set normal if not edge hit
			if ( !debris_hit_info->edge_hit ) {
				vm_vec_unrotate(&debris_hit_info->collision_normal, &mc.hit_normal, &heavy->orient);
			}

			// find position in submodel RF of light object at collison
			vec3d diff;
			vm_vec_sub(&diff, mc.p1, mc.p0);
			vm_vec_scale_add(&debris_hit_info->light_collision_cm_pos, mc.p0, &diff, mc.hit_dist);

		}
	}

	if (mc_ret_val) {
		WarpEffect* warp_effect = nullptr;
		ship* shipp = &Ships[pship_obj->instance];

		// this is extremely confusing but mc.hit_point_world isn't actually in world coords
		// everything above was calculated relative to the heavy's position
		vec3d actual_world_hit_pos = mc.hit_point_world + heavy_obj->pos;
		if ((shipp->is_arriving()) && (shipp->warpin_effect != nullptr))
			warp_effect = shipp->warpin_effect;
		else if ((shipp->flags[Ship::Ship_Flags::Depart_warp]) && (shipp->warpout_effect != nullptr))
			warp_effect = shipp->warpout_effect;

		if (warp_effect != nullptr && point_is_clipped_by_warp(&actual_world_hit_pos, warp_effect))
			mc_ret_val = 0;
	}


	if ( mc_ret_val )	{

		// SET PHYSICS PARAMETERS
		// already have (hitpos - heavy) and light_cm_pos

		// get r_heavy and r_light
		debris_hit_info->r_heavy = debris_hit_info->hit_pos;
		vm_vec_sub(&debris_hit_info->r_light, &debris_hit_info->hit_pos, &debris_hit_info->light_collision_cm_pos);

		// set normal for edge hit
		if ( debris_hit_info->edge_hit ) {
			vm_vec_copy_normalize(&debris_hit_info->collision_normal, &debris_hit_info->r_light);
			vm_vec_negate(&debris_hit_info->collision_normal);
		}

		// get world hitpos
		vm_vec_add(hitpos, &debris_hit_info->heavy->pos, &debris_hit_info->r_heavy);

		return 1;
	} else {
		// no hit
		return 0;
	}
}

/**
 * Return the team field for a debris object
 */
int debris_get_team(const object *objp)
{
	Assert( objp->type == OBJ_DEBRIS );
	Assert( objp->instance >= 0 && objp->instance < (int)Debris.size() );

	return Debris[objp->instance].team;
}

/**
 * Fills in debris physics properties when created, specifically mass and moment of inertia
 */
void calc_debris_physics_properties( physics_info *pi, vec3d *mins, vec3d *maxs, float density )
{
	float dx, dy, dz, mass;
	dx = maxs->xyz.x - mins->xyz.x;
	dy = maxs->xyz.y - mins->xyz.y;
	dz = maxs->xyz.z - mins->xyz.z;

	// John, with new bspgen, just set pi->mass = mass
	mass = 0.12f * dx * dy * dz;
	pi->mass = (float) pow(mass, 0.6666667f) * 4.65f * density;

	pi->I_body_inv.vec.rvec.xyz.x = 12.0f / (pi->mass *  (dy*dy + dz*dz));
	pi->I_body_inv.vec.rvec.xyz.y = 0.0f;
	pi->I_body_inv.vec.rvec.xyz.z = 0.0f;

	pi->I_body_inv.vec.uvec.xyz.x = 0.0f;
	pi->I_body_inv.vec.uvec.xyz.y = 12.0f / (pi->mass *  (dx*dx + dz*dz));
	pi->I_body_inv.vec.uvec.xyz.z = 0.0f;

	pi->I_body_inv.vec.fvec.xyz.x = 0.0f;
	pi->I_body_inv.vec.fvec.xyz.y = 0.0f;
	pi->I_body_inv.vec.fvec.xyz.z = 12.0f / (pi->mass *  (dx*dx + dy*dy));
}

/**
* Renders debris
*/
void debris_render(object * obj, model_draw_list *scene)
{
	int			num, swapped;
	debris		*db;

	swapped = -1;
	num = obj->instance;

	Assert(num >= 0 && num < (int)Debris.size());
	db = &Debris[num];

	Assert(db->flags[Debris_Flags::Used]);

	texture_info *tbase = nullptr;

	auto pm = model_get(db->model_num);
	model_clear_instance( db->model_num );

	// Swap in a different texture depending on the species
	if (db->species >= 0)
	{
		//WMC - Someday, we should have glowing debris.
		if ( pm->n_textures == 1 ) {
			tbase = &pm->maps[0].textures[TM_BASE_TYPE];
			swapped = tbase->GetTexture();
			tbase->SetTexture(Species_info[db->species].debris_texture.bitmap_id);
		}
	}

	polymodel_instance *pmi = nullptr;
	if (db->model_instance_num >= 0)
	{
		pmi = model_get_instance(db->model_instance_num);
		model_instance_clear_arcs(pm, pmi);

		// Only render electrical arcs if within 500m of the eye (for a 10m piece)
		if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f )	{
			for (auto &arc: db->electrical_arcs) {
				if ( arc.timestamp.isValid() )	{
					model_instance_add_arc( pm, pmi, db->submodel_num, &arc.endpoint_1, &arc.endpoint_2, nullptr, MARC_TYPE_DAMAGED );
				}
			}
		}
	}

	model_render_params render_info;

	if ( db->is_hull ) {
		MONITOR_INC(NumHullDebrisRend, 1);
	} else {
		MONITOR_INC(NumSmallDebrisRend, 1);
		// render_info.set_flags(MR_NO_LIGHTING);
	}

	if (db->model_instance_num >= 0)
		render_info.set_replacement_textures(model_get_instance(db->model_instance_num)->texture_replace);

	submodel_render_queue( &render_info, scene, pm, pmi, db->submodel_num, &obj->orient, &obj->pos );

	if (tbase != NULL && (swapped!=-1) && pm)	{
		tbase->SetTexture(swapped);
	}
}

bool debris_is_generic(const debris *db)
{
	return db->model_num == Debris_model;
}

bool debris_is_vaporized(const debris *db)
{
	return db->model_num == Debris_vaporize_model;
}

void create_generic_debris(object* ship_objp, const vec3d* pos, float min_num_debris, float max_num_debris, float speed_mult, bool use_ship_debris)
{
	Assertion(ship_objp->type == OBJ_SHIP, "create_generic_debris called for a non-ship, only ships can spew debris!");
	if (ship_objp->type != OBJ_SHIP)
		return;

	// determine if using custom or non-custom generic debris
	int model_num = use_ship_debris ? Ship_info[Ships[ship_objp->instance].ship_info_index].generic_debris_model_num : -1;

	// if using non-custom generic debris, then bail early if that kind of debris is not allowed --wookeejedi
	if ( (model_num < 0) && (Disable_all_noncustom_generic_debris) ) {
		return;
	}

	float num_debris = frand_range(min_num_debris, max_num_debris);

	num_debris *= (Detail.num_small_debris + 0.5f) / 4.5f;

	vec3d create_pos = *pos;
	for (int i = 0; i < num_debris; i++) {
		debris_create(ship_objp, model_num, -1, &create_pos, pos, false, speed_mult);
	}
}

debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create)
{
	size_t i = 0;
	for (auto& ii : db->electrical_arcs)
	{
		if (!ii.timestamp.isValid())
			break;
		i++;
	}

	if (i == db->electrical_arcs.size())
	{
		if (no_create)
			return nullptr;
		db->electrical_arcs.emplace_back();
	}

	return &db->electrical_arcs[i];
}
