mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
Jump nodes previously used SCP_list; refactor to use SCP_vector. Also improve resource handling and life cycle management. Follow-up to #7431.
2338 lines
66 KiB
C++
2338 lines
66 KiB
C++
/*
|
|
* 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 "asteroid/asteroid.h"
|
|
#include "cmeasure/cmeasure.h"
|
|
#include "debris/debris.h"
|
|
#include "debugconsole/console.h"
|
|
#include "fireball/fireballs.h"
|
|
#include "freespace.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "globalincs/pstypes.h"
|
|
#include "globalincs/vmallocator.h"
|
|
#include "iff_defs/iff_defs.h"
|
|
#include "io/timer.h"
|
|
#include "jumpnode/jumpnode.h"
|
|
#include "lighting/lighting.h"
|
|
#include "lighting/lighting_profiles.h"
|
|
#include "mission/missionparse.h" //For 2D Mode
|
|
#include "network/multi.h"
|
|
#include "network/multiutil.h"
|
|
#include "network//multi_obj.h"
|
|
#include "object/deadobjectdock.h"
|
|
#include "object/objcollide.h"
|
|
#include "object/object.h"
|
|
#include "object/objectdock.h"
|
|
#include "object/objectshield.h"
|
|
#include "object/objectsnd.h"
|
|
#include "observer/observer.h"
|
|
#include "prop/prop.h"
|
|
#include "scripting/global_hooks.h"
|
|
#include "scripting/api/libs/graphics.h"
|
|
#include "scripting/scripting.h"
|
|
#include "playerman/player.h"
|
|
#include "radar/radar.h"
|
|
#include "radar/radarsetup.h"
|
|
#include "render/3d.h"
|
|
#include "ship/afterburner.h"
|
|
#include "ship/ship.h"
|
|
#include "starfield/starfield.h"
|
|
#include "tracing/tracing.h"
|
|
#include "weapon/beam.h"
|
|
#include "weapon/shockwave.h"
|
|
#include "weapon/swarm.h"
|
|
#include "weapon/weapon.h"
|
|
#include "tracing/Monitor.h"
|
|
#include "graphics/light.h"
|
|
#include "graphics/color.h"
|
|
#include "math/curve.h"
|
|
|
|
extern void ship_reset_disabled_physics(object *objp, int ship_class);
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
|
|
object obj_free_list;
|
|
object obj_used_list;
|
|
object obj_create_list;
|
|
|
|
object *Player_obj = NULL;
|
|
object *Viewer_obj = NULL;
|
|
|
|
//Data for objects
|
|
object Objects[MAX_OBJECTS];
|
|
SCP_map<int, raw_pof_obj> Pof_objects;
|
|
|
|
#ifdef OBJECT_CHECK
|
|
checkobject CheckObjects[MAX_OBJECTS];
|
|
#endif
|
|
|
|
int Num_objects=-1;
|
|
int Highest_object_index=-1;
|
|
int Highest_ever_object_index=0;
|
|
int Object_next_signature = 1; //0 is bogus, start at 1
|
|
int Object_inited = 0;
|
|
int Show_waypoints = 0;
|
|
|
|
object_h::object_h(int in_objnum)
|
|
: objnum(in_objnum)
|
|
{
|
|
if (objnum >= 0 && objnum < MAX_OBJECTS)
|
|
sig = Objects[objnum].signature;
|
|
else
|
|
objnum = -1;
|
|
}
|
|
|
|
object_h::object_h(const object* in_objp)
|
|
{
|
|
if (in_objp)
|
|
{
|
|
objnum = OBJ_INDEX(in_objp);
|
|
sig = in_objp->signature;
|
|
}
|
|
}
|
|
|
|
object_h::object_h()
|
|
{}
|
|
|
|
bool object_h::isValid() const
|
|
{
|
|
// a signature of 0 is invalid, per obj_init()
|
|
if (objnum < 0 || sig <= 0 || objnum >= MAX_OBJECTS)
|
|
return false;
|
|
return Objects[objnum].signature == sig;
|
|
}
|
|
|
|
object* object_h::objp() const
|
|
{
|
|
return &Objects[objnum];
|
|
}
|
|
|
|
object* object_h::objp_or_null() const
|
|
{
|
|
return isValid() ? &Objects[objnum] : nullptr;
|
|
}
|
|
|
|
//WMC - Made these prettier
|
|
const char *Object_type_names[MAX_OBJECT_TYPES] = {
|
|
//XSTR:OFF
|
|
"None",
|
|
"Ship",
|
|
"Weapon",
|
|
"Fireball",
|
|
"Start",
|
|
"Waypoint",
|
|
"Debris",
|
|
"Countermeasure",
|
|
"Ghost",
|
|
"Point",
|
|
"Shockwave",
|
|
"Wing",
|
|
"Observer",
|
|
"Asteroid",
|
|
"Jump Node",
|
|
"Beam",
|
|
"Raw Pof"
|
|
//XSTR:ON
|
|
};
|
|
|
|
obj_flag_name Object_flag_names[] = {
|
|
{ Object::Object_Flags::Invulnerable, "invulnerable", },
|
|
{ Object::Object_Flags::Protected, "protect-ship", },
|
|
{ Object::Object_Flags::Beam_protected, "beam-protect-ship", },
|
|
{ Object::Object_Flags::No_shields, "no-shields", },
|
|
{ Object::Object_Flags::Targetable_as_bomb, "targetable-as-bomb", },
|
|
{ Object::Object_Flags::Flak_protected, "flak-protect-ship", },
|
|
{ Object::Object_Flags::Laser_protected, "laser-protect-ship", },
|
|
{ Object::Object_Flags::Missile_protected, "missile-protect-ship", },
|
|
{ Object::Object_Flags::Immobile, "immobile", },
|
|
{ Object::Object_Flags::Dont_change_position, "don't-change-position", },
|
|
{ Object::Object_Flags::Dont_change_orientation, "don't-change-orientation", },
|
|
{ Object::Object_Flags::Collides, "collides", },
|
|
{ Object::Object_Flags::Attackable_if_no_collide, "ai-attackable-if-no-collide", },
|
|
};
|
|
|
|
obj_flag_description Object_flag_descriptions[] = {
|
|
{ Object::Object_Flags::Invulnerable, "Stops this object from taking any damage."},
|
|
{ Object::Object_Flags::Protected, "Ship and Turret AI will ignore and not attack this object."},
|
|
{ Object::Object_Flags::Beam_protected, "Turrets with beam weapons will ignore and not attack this object."},
|
|
{ Object::Object_Flags::No_shields, "This object will have no shields. (If this object can otherwise have shields, its shield energy will be fully reallocated to other ETS systems.)"},
|
|
{ Object::Object_Flags::Targetable_as_bomb, "Allows this object to be targeted with the bomb targeting key."},
|
|
{ Object::Object_Flags::Flak_protected, "Turrets with flak weapons will ignore and not attack this object."},
|
|
{ Object::Object_Flags::Laser_protected, "Turrets with laser weapons will ignore and not attack this object."},
|
|
{ Object::Object_Flags::Missile_protected, "Turrets with missile weapons will ignore and not attack this object."},
|
|
{ Object::Object_Flags::Immobile, "Will not let an object change position or orientation. Upon destruction it will still do the death roll and explosion."},
|
|
{ Object::Object_Flags::Dont_change_position, "Will not let an object change position. Upon destruction it will still do the death roll and explosion."},
|
|
{ Object::Object_Flags::Dont_change_orientation, "Will not let an object change orientation. Upon destruction it will still do the death roll and explosion."},
|
|
{ Object::Object_Flags::Collides, "This object will collide with other objects."},
|
|
{ Object::Object_Flags::Attackable_if_no_collide, "Allows the AI to attack this object, even if no-collide is set. (Normally an object that does not collide is also not attacked.)"},
|
|
{ Object::Object_Flags::Player_ship, "Ship is a player ship."},
|
|
{ Object::Object_Flags::Special_warpin, "Ship uses the special Knossos warp-in animation."},
|
|
};
|
|
|
|
extern const int Num_object_flag_names = sizeof(Object_flag_names) / sizeof(obj_flag_name);
|
|
extern const size_t Num_object_flag_descriptions = sizeof(Object_flag_descriptions) / sizeof(obj_flag_description);
|
|
|
|
#ifdef OBJECT_CHECK
|
|
checkobject::checkobject()
|
|
: type(0), signature(0), parent_sig(0)
|
|
{
|
|
flags.reset();
|
|
}
|
|
#endif
|
|
|
|
// all we need to set are the pointers, but type, parent, and instance are useful to set as well
|
|
object::object()
|
|
: next(nullptr), prev(nullptr), signature(0), type(OBJ_NONE), parent(-1), parent_sig(0), instance(-1), pos(vmd_zero_vector), orient(vmd_identity_matrix),
|
|
radius(0.0f), last_pos(vmd_zero_vector), last_orient(vmd_identity_matrix), hull_strength(0.0f), sim_hull_strength(0.0f), net_signature(0), num_pairs(0),
|
|
dock_list(nullptr), dead_dock_list(nullptr), collision_group_id(0)
|
|
{
|
|
memset(&(this->phys_info), 0, sizeof(physics_info));
|
|
}
|
|
|
|
object::~object()
|
|
{
|
|
dock_free_dock_list(this);
|
|
dock_free_dead_dock_list(this);
|
|
}
|
|
|
|
// DO NOT set next and prev to NULL because they keep the object on the free and used lists
|
|
void object::clear()
|
|
{
|
|
signature = num_pairs = collision_group_id = 0;
|
|
parent = parent_sig = instance = -1;
|
|
type = OBJ_NONE;
|
|
flags.reset();
|
|
pos = last_pos = vmd_zero_vector;
|
|
orient = last_orient = vmd_identity_matrix;
|
|
radius = hull_strength = sim_hull_strength = 0.0f;
|
|
physics_init( &phys_info );
|
|
shield_quadrant.clear();
|
|
objsnd_num.clear();
|
|
net_signature = 0;
|
|
|
|
pre_move_event.clear();
|
|
post_move_event.clear();
|
|
|
|
// just in case nobody called obj_delete last mission
|
|
dock_free_dock_list(this);
|
|
dock_free_dead_dock_list(this);
|
|
}
|
|
|
|
/**
|
|
* Scan the object list, freeing down to num_used objects
|
|
*
|
|
* @param target_num_used Number of used objects to free down to
|
|
* @return Returns number of slots freed
|
|
*/
|
|
int free_object_slots(int target_num_used)
|
|
{
|
|
int i, olind, deleted_weapons;
|
|
int obj_list[MAX_OBJECTS];
|
|
int num_already_free, num_to_free, original_num_to_free;
|
|
object *objp;
|
|
|
|
olind = 0;
|
|
|
|
// calc num_already_free by walking the obj_free_list
|
|
num_already_free = 0;
|
|
for ( objp = GET_FIRST(&obj_free_list); objp != END_OF_LIST(&obj_free_list); objp = GET_NEXT(objp) )
|
|
num_already_free++;
|
|
|
|
if (MAX_OBJECTS - num_already_free < target_num_used)
|
|
return 0;
|
|
|
|
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead]) {
|
|
num_already_free++;
|
|
if (MAX_OBJECTS - num_already_free < target_num_used)
|
|
return num_already_free;
|
|
} else
|
|
switch (objp->type) {
|
|
case OBJ_NONE:
|
|
num_already_free++;
|
|
if (MAX_OBJECTS - num_already_free < target_num_used)
|
|
return 0;
|
|
break;
|
|
case OBJ_FIREBALL:
|
|
case OBJ_WEAPON:
|
|
case OBJ_DEBRIS:
|
|
// case OBJ_CMEASURE:
|
|
obj_list[olind++] = OBJ_INDEX(objp);
|
|
break;
|
|
|
|
case OBJ_GHOST:
|
|
case OBJ_SHIP:
|
|
case OBJ_START:
|
|
case OBJ_WAYPOINT:
|
|
case OBJ_POINT:
|
|
case OBJ_SHOCKWAVE:
|
|
case OBJ_WING:
|
|
case OBJ_OBSERVER:
|
|
case OBJ_ASTEROID:
|
|
case OBJ_JUMP_NODE:
|
|
case OBJ_BEAM:
|
|
case OBJ_RAW_POF:
|
|
case OBJ_PROP:
|
|
break;
|
|
default:
|
|
Int3(); // Hey, what kind of object is this? Unknown!
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
num_to_free = MAX_OBJECTS - target_num_used - num_already_free;
|
|
original_num_to_free = num_to_free;
|
|
|
|
if (num_to_free > olind) {
|
|
nprintf(("allender", "Warning: Asked to free %i objects, but can only free %i.\n", num_to_free, olind));
|
|
num_to_free = olind;
|
|
}
|
|
|
|
for (i=0; i<num_to_free; i++)
|
|
if ( (Objects[obj_list[i]].type == OBJ_DEBRIS) && (!Debris[Objects[obj_list[i]].instance].flags[Debris_Flags::DoNotExpire]) ) {
|
|
num_to_free--;
|
|
nprintf(("allender", "Freeing DEBRIS object %3i\n", obj_list[i]));
|
|
Objects[obj_list[i]].flags.set(Object::Object_Flags::Should_be_dead);
|
|
}
|
|
|
|
if (num_to_free <= 0) {
|
|
return original_num_to_free;
|
|
}
|
|
|
|
for (i=0; i<num_to_free; i++) {
|
|
object *tmp_obj = &Objects[obj_list[i]];
|
|
if ( (tmp_obj->type == OBJ_FIREBALL) && (fireball_is_perishable(tmp_obj)) ) {
|
|
num_to_free--;
|
|
if (num_to_free <= 0) {
|
|
return original_num_to_free;
|
|
}
|
|
nprintf(("allender", "Freeing FIREBALL object %3i\n", obj_list[i]));
|
|
tmp_obj->flags.set(Object::Object_Flags::Should_be_dead);
|
|
}
|
|
}
|
|
|
|
deleted_weapons = collide_remove_weapons();
|
|
|
|
num_to_free -= deleted_weapons;
|
|
if ( num_to_free <= 0){
|
|
return original_num_to_free;
|
|
}
|
|
|
|
for (i=0; i<num_to_free; i++){
|
|
if ( Objects[obj_list[i]].type == OBJ_WEAPON ) {
|
|
num_to_free--;
|
|
Objects[obj_list[i]].flags.set(Object::Object_Flags::Should_be_dead);
|
|
}
|
|
}
|
|
|
|
if (!num_to_free){
|
|
return original_num_to_free;
|
|
}
|
|
|
|
return original_num_to_free - num_to_free;
|
|
}
|
|
|
|
// Goober5000
|
|
// This helper function does not check the object type and is not intended as a public API.
|
|
float get_hull_or_sim_hull_pct_helper(const object *objp, const float object::*strength_field, const ship *shipp, bool allow_negative)
|
|
{
|
|
Assert(objp && shipp);
|
|
if (!objp || !shipp)
|
|
return 0.0f;
|
|
|
|
float total_strength = shipp->ship_max_hull_strength;
|
|
Assert(total_strength > 0.0f); // unlike shield, no ship can have 0 hull
|
|
if (total_strength == 0.0f)
|
|
return 0.0f;
|
|
|
|
if (!allow_negative && objp->*strength_field < 0.0f) // this sometimes happens when a ship is being destroyed
|
|
return 0.0f;
|
|
|
|
return objp->*strength_field / total_strength;
|
|
}
|
|
|
|
float get_hull_pct(const object *objp, bool allow_negative)
|
|
{
|
|
Assert(objp && objp->type == OBJ_SHIP);
|
|
if (!objp || objp->type != OBJ_SHIP)
|
|
return 0.0f;
|
|
return get_hull_or_sim_hull_pct_helper(objp, &object::hull_strength, &Ships[objp->instance], allow_negative);
|
|
}
|
|
|
|
float get_hull_pct(const ship_registry_entry *ship_entry, bool allow_negative)
|
|
{
|
|
return get_hull_or_sim_hull_pct_helper(ship_entry->objp(), &object::hull_strength, ship_entry->shipp(), allow_negative);
|
|
}
|
|
|
|
float get_sim_hull_pct(const object *objp, bool allow_negative)
|
|
{
|
|
Assert(objp && objp->type == OBJ_SHIP);
|
|
if (!objp || objp->type != OBJ_SHIP)
|
|
return 0.0f;
|
|
return get_hull_or_sim_hull_pct_helper(objp, &object::sim_hull_strength, &Ships[objp->instance], allow_negative);
|
|
}
|
|
|
|
float get_sim_hull_pct(const ship_registry_entry *ship_entry, bool allow_negative)
|
|
{
|
|
return get_hull_or_sim_hull_pct_helper(ship_entry->objp(), &object::sim_hull_strength, ship_entry->shipp(), allow_negative);
|
|
}
|
|
|
|
// Goober5000
|
|
// This helper function does not check the object type and is not intended as a public API.
|
|
float get_shield_pct_helper(const object *objp, const ship *shipp)
|
|
{
|
|
Assert(objp && shipp);
|
|
if (!objp || !shipp)
|
|
return 0.0f;
|
|
|
|
float total_strength = shield_get_max_strength(shipp);
|
|
if (total_strength == 0.0f)
|
|
return 0.0f;
|
|
|
|
return shield_get_strength(objp) / total_strength;
|
|
}
|
|
|
|
float get_shield_pct(const object *objp)
|
|
{
|
|
Assert(objp);
|
|
if (!objp)
|
|
return 0.0f;
|
|
|
|
// bah - we might have asteroids
|
|
if (objp->type != OBJ_SHIP)
|
|
return 0.0f;
|
|
|
|
return get_shield_pct_helper(objp, &Ships[objp->instance]);
|
|
}
|
|
|
|
float get_shield_pct(const ship_registry_entry *ship_entry)
|
|
{
|
|
return get_shield_pct_helper(ship_entry->objp(), ship_entry->shipp());
|
|
}
|
|
|
|
static void on_script_state_destroy(lua_State*) {
|
|
// Since events are mostly used for scripting, we clear the event handlers when the Lua state is destroyed
|
|
for (auto& obj : Objects) {
|
|
obj.pre_move_event.clear();
|
|
obj.post_move_event.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets up the free list & init player & whatever else
|
|
*/
|
|
void obj_init()
|
|
{
|
|
int i;
|
|
object *objp;
|
|
|
|
Object_inited = 1;
|
|
for (i = 0; i < MAX_OBJECTS; ++i)
|
|
Objects[i].clear();
|
|
Viewer_obj = NULL;
|
|
|
|
list_init( &obj_free_list );
|
|
list_init( &obj_used_list );
|
|
list_init( &obj_create_list );
|
|
|
|
// Link all object slots into the free list
|
|
objp = Objects;
|
|
for (i=0; i<MAX_OBJECTS; i++) {
|
|
list_append(&obj_free_list, objp);
|
|
objp++;
|
|
}
|
|
|
|
Object_next_signature = 1; //0 is invalid, others start at 1
|
|
Num_objects = 0;
|
|
Highest_object_index = 0;
|
|
|
|
obj_reset_colliders();
|
|
|
|
Script_system.OnStateDestroy.add(on_script_state_destroy);
|
|
}
|
|
|
|
void obj_shutdown()
|
|
{
|
|
for (auto& obj : Objects) {
|
|
obj.clear();
|
|
}
|
|
}
|
|
|
|
static int num_objects_hwm = 0;
|
|
|
|
/**
|
|
* Allocates an object
|
|
*
|
|
* Generally, obj_create() should be called to get an object, since it
|
|
* fills in important fields and does the linking.
|
|
*
|
|
* @return the number of a free object, updating Highest_object_index
|
|
* @return -1 if no free objects
|
|
*/
|
|
int obj_allocate(bool essential)
|
|
{
|
|
int objnum;
|
|
object *objp;
|
|
|
|
if (!Object_inited) {
|
|
mprintf(("Why hasn't obj_init() been called yet?\n"));
|
|
obj_init();
|
|
}
|
|
|
|
if ( (Num_objects >= MAX_OBJECTS-10) && essential ) {
|
|
int num_freed;
|
|
|
|
num_freed = free_object_slots(MAX_OBJECTS-10);
|
|
nprintf(("warning", " *** Freed %i objects\n", num_freed));
|
|
}
|
|
|
|
if (Num_objects >= MAX_OBJECTS) {
|
|
mprintf(("Object creation failed - too many objects!\n" ));
|
|
return -1;
|
|
}
|
|
|
|
// Find next available object
|
|
objp = GET_FIRST(&obj_free_list);
|
|
Assert ( objp != &obj_free_list ); // shouldn't have the dummy element
|
|
|
|
// remove objp from the free list
|
|
list_remove( &obj_free_list, objp );
|
|
|
|
// insert objp onto the end of create list
|
|
list_append( &obj_create_list, objp );
|
|
|
|
// increment counter
|
|
Num_objects++;
|
|
|
|
if (Num_objects > num_objects_hwm) {
|
|
num_objects_hwm = Num_objects;
|
|
}
|
|
|
|
// get objnum
|
|
objnum = OBJ_INDEX(objp);
|
|
|
|
if (objnum > Highest_object_index) {
|
|
Highest_object_index = objnum;
|
|
if (Highest_object_index > Highest_ever_object_index)
|
|
Highest_ever_object_index = Highest_object_index;
|
|
}
|
|
|
|
return objnum;
|
|
}
|
|
|
|
/**
|
|
* Frees up an object
|
|
*
|
|
* Generally, obj_delete() should be called to get rid of an object.
|
|
* This function deallocates the object entry after the object has been unlinked
|
|
*/
|
|
void obj_free(int objnum)
|
|
{
|
|
object *objp;
|
|
|
|
if (!Object_inited) {
|
|
mprintf(("Why hasn't obj_init() been called yet?\n"));
|
|
obj_init();
|
|
}
|
|
|
|
Assert( objnum >= 0 ); // Trying to free bogus object!!!
|
|
|
|
// get object pointer
|
|
objp = &Objects[objnum];
|
|
|
|
// remove objp from the used list
|
|
list_remove( &obj_used_list, objp );
|
|
|
|
// add objp to the end of the free
|
|
list_append( &obj_free_list, objp );
|
|
|
|
// decrement counter
|
|
Num_objects--;
|
|
|
|
Objects[objnum].type = OBJ_NONE;
|
|
|
|
Assert(Num_objects >= 0);
|
|
|
|
if (objnum == Highest_object_index) {
|
|
while (Highest_object_index >= 0 && Objects[Highest_object_index].type == OBJ_NONE) {
|
|
--Highest_object_index;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a raw pof item
|
|
* @return the objnum of this raw POF
|
|
*/
|
|
int obj_raw_pof_create(const char* pof_filename, const matrix* orient, const vec3d* pos)
|
|
{
|
|
static int next_raw_pof_id = 0;
|
|
|
|
// Unlikely this would ever be hit.. but just in case
|
|
if (next_raw_pof_id >= INT_MAX || next_raw_pof_id < 0) {
|
|
Error(LOCATION, "Too many RAW_POF objects created!");
|
|
return -1;
|
|
}
|
|
|
|
if (!VALID_FNAME(pof_filename)) {
|
|
Warning(LOCATION, "Invalid tech model POF: %s", pof_filename);
|
|
return -1;
|
|
}
|
|
|
|
int model_num = model_load(pof_filename);
|
|
if (model_num < 0) {
|
|
Warning(LOCATION, "Failed to load tech model: %s", pof_filename);
|
|
return -1;
|
|
}
|
|
|
|
int id = next_raw_pof_id++;
|
|
Pof_objects[id] = {model_num, -1, {}};
|
|
|
|
flagset<Object::Object_Flags> flags;
|
|
flags.set(Object::Object_Flags::Renders);
|
|
|
|
int objnum = obj_create(OBJ_RAW_POF, -1, id, orient, pos, model_get_radius(model_num), flags);
|
|
|
|
Pof_objects[id].model_instance = model_create_instance(objnum, model_num);
|
|
|
|
return objnum;
|
|
}
|
|
|
|
/**
|
|
* Initialize a new object. Adds to the list for the given segment.
|
|
*
|
|
* The object will be a non-rendering, non-physics object. Pass -1 if no parent.
|
|
* @return the object number
|
|
*/
|
|
int obj_create(ubyte type, int parent_obj, int instance, const matrix *orient,
|
|
const vec3d *pos, float radius, const flagset<Object::Object_Flags> &flags, bool essential)
|
|
{
|
|
int objnum;
|
|
object *obj;
|
|
|
|
// Find next free object
|
|
objnum = obj_allocate(essential);
|
|
|
|
if (objnum == -1) //no free objects
|
|
return -1;
|
|
|
|
obj = &Objects[objnum];
|
|
Assert(obj->type == OBJ_NONE); //make sure unused
|
|
|
|
// clear object in preparation for setting of custom values
|
|
obj->clear();
|
|
|
|
Assert(Object_next_signature > 0); // 0 is bogus!
|
|
obj->signature = Object_next_signature++;
|
|
|
|
obj->type = type;
|
|
obj->instance = instance;
|
|
obj->parent = parent_obj;
|
|
if (obj->parent != -1) {
|
|
obj->parent_sig = Objects[parent_obj].signature;
|
|
} else {
|
|
obj->parent_sig = obj->signature;
|
|
}
|
|
|
|
obj->flags = flags;
|
|
obj->flags.set(Object::Object_Flags::Not_in_coll);
|
|
if (pos) {
|
|
obj->pos = *pos;
|
|
obj->last_pos = *pos;
|
|
}
|
|
|
|
if (orient) {
|
|
obj->orient = *orient;
|
|
obj->last_orient = *orient;
|
|
}
|
|
obj->radius = radius;
|
|
|
|
obj->shield_quadrant.resize(DEFAULT_SHIELD_SECTIONS); // Might be changed by the ship creation code
|
|
|
|
return objnum;
|
|
}
|
|
|
|
void obj_delete_all()
|
|
{
|
|
int counter = 0;
|
|
for (int i = 0; i < MAX_OBJECTS; ++i)
|
|
{
|
|
if (Objects[i].type == OBJ_NONE)
|
|
continue;
|
|
++counter;
|
|
obj_delete(i);
|
|
}
|
|
|
|
mprintf(("Cleanup: Deleted %i objects\n", counter));
|
|
}
|
|
|
|
/**
|
|
* Remove object from the world
|
|
* If Player_obj, don't remove it!
|
|
*
|
|
* @param objnum Object number to remove
|
|
*/
|
|
void obj_delete(int objnum)
|
|
{
|
|
object *objp;
|
|
|
|
Assert(objnum >= 0 && objnum < MAX_OBJECTS);
|
|
objp = &Objects[objnum];
|
|
if (objp->type == OBJ_NONE) {
|
|
mprintf(("obj_delete() called for already deleted object %d.\n", objnum));
|
|
return;
|
|
};
|
|
|
|
// Remove all object pairs
|
|
obj_remove_collider(objnum);
|
|
|
|
switch( objp->type ) {
|
|
case OBJ_WEAPON:
|
|
weapon_delete( objp );
|
|
break;
|
|
case OBJ_SHIP:
|
|
if ((objp == Player_obj) && !Fred_running) {
|
|
objp->type = OBJ_GHOST;
|
|
objp->flags.remove(Object::Object_Flags::Should_be_dead);
|
|
|
|
// we have to traverse the ship_obj list and remove this guy from it as well
|
|
ship_obj *moveup = GET_FIRST(&Ship_obj_list);
|
|
while(moveup != END_OF_LIST(&Ship_obj_list)){
|
|
if(OBJ_INDEX(objp) == moveup->objnum){
|
|
list_remove(&Ship_obj_list,moveup);
|
|
break;
|
|
}
|
|
moveup = GET_NEXT(moveup);
|
|
}
|
|
|
|
physics_init(&objp->phys_info);
|
|
obj_snd_delete_type(OBJ_INDEX(objp));
|
|
|
|
return;
|
|
} else
|
|
ship_delete( objp );
|
|
break;
|
|
case OBJ_FIREBALL:
|
|
fireball_delete( objp );
|
|
break;
|
|
case OBJ_SHOCKWAVE:
|
|
shockwave_delete( objp );
|
|
break;
|
|
case OBJ_START:
|
|
case OBJ_WAYPOINT:
|
|
case OBJ_POINT:
|
|
break; // requires no action, handled by the Fred code.
|
|
case OBJ_JUMP_NODE:
|
|
jumpnode_delete(objp);
|
|
break;
|
|
case OBJ_DEBRIS:
|
|
debris_delete( objp );
|
|
break;
|
|
case OBJ_ASTEROID:
|
|
asteroid_delete(objp);
|
|
break;
|
|
/* case OBJ_CMEASURE:
|
|
cmeasure_delete( objp );
|
|
break;*/
|
|
case OBJ_GHOST:
|
|
if(!(Game_mode & GM_MULTIPLAYER)){
|
|
mprintf(("Warning: Tried to delete a ghost!\n"));
|
|
objp->flags.remove(Object::Object_Flags::Should_be_dead);
|
|
return;
|
|
} else {
|
|
// we need to be able to delete GHOST objects in multiplayer to allow for player respawns.
|
|
nprintf(("Network","Deleting GHOST object\n"));
|
|
objp->net_signature = 0;
|
|
}
|
|
break;
|
|
case OBJ_OBSERVER:
|
|
observer_delete(objp);
|
|
break;
|
|
case OBJ_BEAM:
|
|
break;
|
|
case OBJ_RAW_POF:
|
|
model_delete_instance(Pof_objects[objp->instance].model_instance);
|
|
Pof_objects.erase(objp->instance);
|
|
break;
|
|
case OBJ_PROP:
|
|
prop_delete(objp);
|
|
break;
|
|
case OBJ_NONE:
|
|
Int3();
|
|
break;
|
|
default:
|
|
Error( LOCATION, "Unhandled object type %d in obj_delete_all_that_should_be_dead", objp->type );
|
|
}
|
|
|
|
// this avoids include issues from physics state, multi interpolate and object code
|
|
extern void multi_interpolate_clear_helper(int objnum);
|
|
|
|
// clean up interpolation info
|
|
multi_interpolate_clear_helper(objnum);
|
|
|
|
// delete any dock information we still have
|
|
dock_free_dock_list(objp);
|
|
dock_free_dead_dock_list(objp);
|
|
|
|
// if a persistant sound has been created, delete it
|
|
obj_snd_delete_type(OBJ_INDEX(objp));
|
|
|
|
objp->type = OBJ_NONE; //unused!
|
|
objp->signature = 0;
|
|
|
|
obj_free(objnum);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------------------
|
|
void obj_delete_all_that_should_be_dead()
|
|
{
|
|
object *objp, *temp;
|
|
|
|
if (!Object_inited) {
|
|
mprintf(("Why hasn't obj_init() been called yet?\n"));
|
|
obj_init();
|
|
}
|
|
|
|
// Move all objects
|
|
objp = GET_FIRST(&obj_used_list);
|
|
while( objp !=END_OF_LIST(&obj_used_list) ) {
|
|
// Goober5000 - HACK HACK HACK - see obj_move_all
|
|
objp->flags.remove(Object::Object_Flags::Docked_already_handled);
|
|
|
|
temp = GET_NEXT(objp);
|
|
if ( objp->flags[Object::Object_Flags::Should_be_dead] )
|
|
obj_delete( OBJ_INDEX(objp) ); // MWA says that john says that let obj_delete handle everything because of the editor
|
|
objp = temp;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Add all newly created objects to the end of the used list and create their
|
|
* object pairs for collision detection
|
|
*/
|
|
void obj_merge_created_list(void)
|
|
{
|
|
// The old way just merged the two. This code takes one out of the create list,
|
|
// creates object pairs for it, and then adds it to the used list.
|
|
// OLD WAY: list_merge( &obj_used_list, &obj_create_list );
|
|
object *objp = GET_FIRST(&obj_create_list);
|
|
while( objp !=END_OF_LIST(&obj_create_list) ) {
|
|
list_remove( obj_create_list, objp );
|
|
|
|
// Add it to the object pairs array
|
|
obj_add_collider(OBJ_INDEX(objp));
|
|
|
|
// Then add it to the object used list
|
|
list_append( &obj_used_list, objp );
|
|
|
|
objp = GET_FIRST(&obj_create_list);
|
|
}
|
|
|
|
// Make sure the create list is empty.
|
|
list_init(&obj_create_list);
|
|
}
|
|
|
|
int physics_paused = 0, ai_paused = 0;
|
|
|
|
|
|
// Goober5000
|
|
extern void call_doa(object *child, object *parent);
|
|
void obj_move_one_docked_object(object *objp, object *parent_objp)
|
|
{
|
|
// in FRED, just move and return
|
|
if (Fred_running)
|
|
{
|
|
call_doa(objp, parent_objp);
|
|
return;
|
|
}
|
|
|
|
// support ships (and anyone else, for that matter) don't keep up if they're undocking
|
|
ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
|
|
if ( (aip->mode == AIM_DOCK) && (aip->submode >= AIS_UNDOCK_1) )
|
|
{
|
|
if (aip->goal_objnum == OBJ_INDEX(parent_objp))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check the guy that I'm docked with and don't move if he's undocking from me
|
|
ai_info *other_aip = &Ai_info[Ships[parent_objp->instance].ai_index];
|
|
if ( (other_aip->mode == AIM_DOCK) && (other_aip->submode >= AIS_UNDOCK_1) )
|
|
{
|
|
if (other_aip->goal_objnum == OBJ_INDEX(objp))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// we're here, so we move with our parent object
|
|
call_doa(objp, parent_objp);
|
|
}
|
|
|
|
/**
|
|
* Deals with firing player things like lasers, missiles, etc.
|
|
*
|
|
* Separated out because of multiplayer issues.
|
|
*/
|
|
void obj_player_fire_stuff( object *objp, control_info ci )
|
|
{
|
|
ship *shipp;
|
|
|
|
Assert( objp->flags[Object::Object_Flags::Player_ship]);
|
|
|
|
// try and get the ship pointer
|
|
shipp = NULL;
|
|
if((objp->type == OBJ_SHIP) && (objp->instance >= 0) && (objp->instance < MAX_SHIPS)){
|
|
shipp = &Ships[objp->instance];
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
ship_weapon* swp = &shipp->weapons;
|
|
|
|
// single player pilots, and all players in multiplayer take care of firing their own primaries
|
|
if(!(Game_mode & GM_MULTIPLAYER) || (objp == Player_obj))
|
|
{
|
|
if ( ci.fire_primary_count ) {
|
|
// flag the ship as having the trigger down
|
|
if(shipp != NULL){
|
|
shipp->flags.set(Ship::Ship_Flags::Trigger_down);
|
|
swp->flags.set(Ship::Weapon_Flags::Primary_trigger_down);
|
|
}
|
|
|
|
// fire non-streaming primaries here
|
|
// Cyborg17, this is where the inaccurate multi shots are being shot...
|
|
// so let's let the new system take over instead by excluding client player shots
|
|
// on the server.
|
|
if (!(MULTIPLAYER_MASTER) || (objp == Player_obj)) {
|
|
ship_fire_primary(objp);
|
|
}
|
|
|
|
} else {
|
|
// unflag the ship as having the trigger down
|
|
if(shipp != NULL){
|
|
shipp->flags.remove(Ship::Ship_Flags::Trigger_down);
|
|
swp->flags.remove(Ship::Weapon_Flags::Primary_trigger_down);
|
|
ship_stop_fire_primary(objp); //if it hasn't fired do the "has just stoped fireing" stuff
|
|
}
|
|
}
|
|
|
|
if ( ci.fire_countermeasure_count ) {
|
|
ship_launch_countermeasure( objp );
|
|
}
|
|
}
|
|
|
|
// single player and multiplayer masters do all of the following
|
|
if ( !MULTIPLAYER_CLIENT
|
|
// Cyborg17 - except clients now fire dumbfires for rollback on the server
|
|
|| !(Weapon_info[swp->secondary_bank_weapons[shipp->weapons.current_secondary_bank]].is_homing())) {
|
|
if (ci.fire_secondary_count) {
|
|
if ( !ship_start_secondary_fire(objp) ) {
|
|
ship_fire_secondary( objp );
|
|
}
|
|
|
|
// kill the secondary count
|
|
ci.fire_secondary_count = 0;
|
|
} else {
|
|
if ( ship_stop_secondary_fire(objp) ) {
|
|
ship_fire_secondary( objp );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( MULTIPLAYER_CLIENT && objp == Player_obj ) {
|
|
if (Weapon_info[swp->secondary_bank_weapons[shipp->weapons.current_secondary_bank]].trigger_lock) {
|
|
if (ci.fire_secondary_count) {
|
|
ship_start_secondary_fire(objp);
|
|
} else {
|
|
ship_stop_secondary_fire(objp);
|
|
}
|
|
}
|
|
}
|
|
|
|
// everyone does the following for their own ships.
|
|
if ( ci.afterburner_start ){
|
|
if (Ships[objp->instance].flags[Ship::Ship_Flags::Maneuver_despite_engines] || !ship_subsystems_blown(&Ships[objp->instance], SUBSYSTEM_ENGINE)) {
|
|
afterburners_start( objp );
|
|
}
|
|
}
|
|
|
|
if ( ci.afterburner_stop ){
|
|
afterburners_stop( objp, 1 );
|
|
}
|
|
|
|
}
|
|
|
|
void obj_move_call_physics(object *objp, float frametime)
|
|
{
|
|
TRACE_SCOPE(tracing::Physics);
|
|
|
|
// Do physics for objects with OF_PHYSICS flag set and with some engine strength remaining.
|
|
if ( objp->flags[Object::Object_Flags::Physics] ) {
|
|
// only set phys info if ship is not dead
|
|
if ((objp->type == OBJ_SHIP) && !(Ships[objp->instance].flags[Ship::Ship_Flags::Dying])) {
|
|
ship *shipp = &Ships[objp->instance];
|
|
|
|
if (!shipp->flags[Ship::Ship_Flags::Maneuver_despite_engines]) {
|
|
bool engines_blown = ship_subsystems_blown(shipp, SUBSYSTEM_ENGINE);
|
|
if ( ship_subsys_disrupted(shipp, SUBSYSTEM_ENGINE) ) {
|
|
engines_blown = true;
|
|
}
|
|
|
|
if (engines_blown) { // All this is necessary to make ship gradually come to a stop after engines are blown.
|
|
vm_vec_zero(&objp->phys_info.desired_vel);
|
|
vm_vec_zero(&objp->phys_info.desired_rotvel);
|
|
vm_mat_zero(&objp->phys_info.ai_desired_orient);
|
|
objp->phys_info.flags |= (PF_REDUCED_DAMP | PF_DEAD_DAMP);
|
|
objp->phys_info.side_slip_time_const = Ship_info[shipp->ship_info_index].damp * 4.0f;
|
|
}
|
|
}
|
|
// recover if we *are* maneuvering but the flag was added
|
|
else if ((objp->phys_info.flags & PF_DEAD_DAMP) && !shipp->flags[Ship::Ship_Flags::Dying]) {
|
|
ship_reset_disabled_physics(objp, shipp->ship_info_index);
|
|
}
|
|
}
|
|
|
|
// if a weapon is flagged as dead, kill its engines just like a ship
|
|
if((objp->type == OBJ_WEAPON) && (Weapons[objp->instance].weapon_flags[Weapon::Weapon_Flags::Dead_in_water])){
|
|
vm_vec_zero(&objp->phys_info.desired_vel);
|
|
vm_vec_zero(&objp->phys_info.desired_rotvel);
|
|
objp->phys_info.flags |= (PF_REDUCED_DAMP | PF_DEAD_DAMP);
|
|
objp->phys_info.side_slip_time_const = 1.0f; // FIXME? originally indexed into Ship_info[], which was a bug...
|
|
}
|
|
|
|
if (physics_paused) {
|
|
if (objp==Player_obj){
|
|
physics_sim(&objp->pos, &objp->orient, &objp->phys_info, &The_mission.gravity, frametime ); // simulate the physics
|
|
}
|
|
} else {
|
|
// Hack for dock mode.
|
|
// If docking with a ship, we don't obey the normal ship physics, we can slew about.
|
|
if (objp->type == OBJ_SHIP) {
|
|
ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
|
|
|
|
// Note: This conditional for using PF_USE_VEL (instantaneous acceleration) is probably too loose.
|
|
// A ships awaiting support will fly towards the support ship with instantaneous acceleration.
|
|
// But we want to have ships in the process of docking have quick acceleration, or they overshoot their goals.
|
|
// Probably can not key off objnum_I_am_docked_or_docking_with, but then need to add some other condition. Live with it for now. -- MK, 2/19/98
|
|
|
|
// Goober5000 - no need to key off objnum; other conditions get it just fine
|
|
|
|
if (/* (objnum_I_am_docked_or_docking_with != -1) || */
|
|
((aip->mode == AIM_DOCK) && ((aip->submode == AIS_DOCK_2) || (aip->submode == AIS_DOCK_3) || (aip->submode == AIS_UNDOCK_0))) ||
|
|
((aip->mode == AIM_WARP_OUT) && (aip->submode >= AIS_WARP_3))) {
|
|
if (Ships[objp->instance].flags[Ship::Ship_Flags::Maneuver_despite_engines] || !ship_subsystems_blown(&Ships[objp->instance], SUBSYSTEM_ENGINE)){
|
|
objp->phys_info.flags |= PF_USE_VEL;
|
|
} else {
|
|
objp->phys_info.flags &= ~PF_USE_VEL; // If engine blown, don't PF_USE_VEL, or ships stop immediately
|
|
}
|
|
} else {
|
|
objp->phys_info.flags &= ~PF_USE_VEL;
|
|
}
|
|
}
|
|
|
|
// simulate the physics
|
|
physics_sim(&objp->pos, &objp->orient, &objp->phys_info, &The_mission.gravity, frametime);
|
|
|
|
// if the object is the player object, do things that need to be done after the ship
|
|
// is moved (like firing weapons, etc). This routine will get called either single
|
|
// or multiplayer. We must find the player object to get to the control info field
|
|
if ( (objp->flags[Object::Object_Flags::Player_ship]) && (objp->type != OBJ_OBSERVER) && (objp == Player_obj)) {
|
|
player *pp;
|
|
if(Player != NULL){
|
|
pp = Player;
|
|
obj_player_fire_stuff( objp, pp->ci );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//2D MODE
|
|
//THIS IS A FREAKIN' HACK
|
|
//Do not let ship change position on Y axis
|
|
if(The_mission.flags[Mission::Mission_Flags::Mission_2d])
|
|
{
|
|
angles old_angles, new_angles;
|
|
objp->pos.xyz.y = objp->last_pos.xyz.y;
|
|
vm_extract_angles_matrix(&old_angles, &objp->last_orient);
|
|
vm_extract_angles_matrix(&new_angles, &objp->orient);
|
|
new_angles.p = old_angles.p;
|
|
new_angles.b = old_angles.b;
|
|
vm_angles_2_matrix(&objp->orient, &new_angles);
|
|
|
|
//Phys stuff hack
|
|
new_angles.h = old_angles.h;
|
|
vm_angles_2_matrix(&objp->phys_info.last_rotmat, &new_angles);
|
|
objp->phys_info.vel.xyz.y = 0.0f;
|
|
objp->phys_info.desired_rotvel.xyz.x = 0;
|
|
objp->phys_info.desired_rotvel.xyz.z = 0;
|
|
objp->phys_info.desired_vel.xyz.y = 0.0f;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef OBJECT_CHECK
|
|
|
|
void obj_check_object( object *obj )
|
|
{
|
|
int objnum = OBJ_INDEX(obj);
|
|
|
|
// PROGRAMMERS: If one of these Int3() gets hit, then someone
|
|
// is changing a value in the object structure that might cause
|
|
// collision detection to not work. See John for more info if
|
|
// you are hitting one of these.
|
|
|
|
if ( CheckObjects[objnum].type != obj->type ) {
|
|
if ( (obj->type==OBJ_WAYPOINT) && (CheckObjects[objnum].type==OBJ_SHIP) ) {
|
|
// We know about ships changing into waypoints and that is
|
|
// ok.
|
|
CheckObjects[objnum].type = OBJ_WAYPOINT;
|
|
} else if ( (obj->type==OBJ_SHIP) && (CheckObjects[objnum].type==OBJ_GHOST) ) {
|
|
// We know about player changing into a ghost after dying and that is
|
|
// ok.
|
|
CheckObjects[objnum].type = OBJ_GHOST;
|
|
} else if ( (obj->type==OBJ_GHOST) && (CheckObjects[objnum].type==OBJ_SHIP) ) {
|
|
// We know about player changing into a ghost after dying and that is
|
|
// ok.
|
|
CheckObjects[objnum].type = OBJ_SHIP;
|
|
} else {
|
|
mprintf(( "Object type changed! Old: %i, Current: %i\n", CheckObjects[objnum].type, obj->type ));
|
|
Int3();
|
|
}
|
|
}
|
|
if ( CheckObjects[objnum].signature != obj->signature ) {
|
|
mprintf(( "Object signature changed!\n" ));
|
|
Int3();
|
|
}
|
|
if ( (CheckObjects[objnum].flags[Object::Object_Flags::Collides]) != (obj->flags[Object::Object_Flags::Collides]) ) {
|
|
mprintf(( "Object flags changed!\n" ));
|
|
Int3();
|
|
}
|
|
if ( CheckObjects[objnum].parent_sig != obj->parent_sig ) {
|
|
mprintf(( "Object parent sig changed!\n" ));
|
|
Int3();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Call this if you want to change an object flag so that the
|
|
* object code knows what's going on. For instance if you turn
|
|
* off OF_COLLIDES, the object code needs to know this in order to
|
|
* actually turn the object collision detection off. By calling
|
|
* this you shouldn't get Int3's in the checkobject code. If you
|
|
* do, then put code in here to correctly handle the case.
|
|
*/
|
|
void obj_set_flags( object *obj, const flagset<Object::Object_Flags>& new_flags )
|
|
{
|
|
int objnum = OBJ_INDEX(obj);
|
|
|
|
// turning collision detection off
|
|
if ( (obj->flags[Object::Object_Flags::Collides]) && (!(new_flags[Object::Object_Flags::Collides]))) {
|
|
// Remove all object pairs
|
|
obj_remove_collider(objnum);
|
|
|
|
// update object flags properly
|
|
obj->flags = new_flags;
|
|
obj->flags.set(Object::Object_Flags::Not_in_coll);
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[objnum].flags = new_flags;
|
|
CheckObjects[objnum].flags.set(Object::Object_Flags::Not_in_coll);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
|
|
// turning collision detection on
|
|
if ( (!(obj->flags[Object::Object_Flags::Collides])) && (new_flags[Object::Object_Flags::Collides]) ) {
|
|
|
|
// observers can't collide or be hit, and they therefore have no hit or collide functions
|
|
// So, don't allow this bit to be set
|
|
if(obj->type == OBJ_OBSERVER){
|
|
mprintf(("Illegal to set collision bit for OBJ_OBSERVER!!\n"));
|
|
Int3();
|
|
}
|
|
|
|
obj->flags.set(Object::Object_Flags::Collides);
|
|
|
|
// Turn on collision detection
|
|
obj_add_collider(objnum);
|
|
|
|
obj->flags = new_flags;
|
|
obj->flags.remove(Object::Object_Flags::Not_in_coll);
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[objnum].flags = new_flags;
|
|
CheckObjects[objnum].flags.remove(Object::Object_Flags::Not_in_coll);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// for a multiplayer host -- use this debug code to help trap when non-player ships are getting
|
|
// marked as OF_COULD_BE_PLAYER
|
|
// this code is pretty much debug code and shouldn't be relied on to always do the right thing
|
|
// for flags other than
|
|
if ( MULTIPLAYER_MASTER && !(obj->flags[Object::Object_Flags::Could_be_player]) && (new_flags[Object::Object_Flags::Could_be_player]) ) {
|
|
ship *shipp;
|
|
int team, slot;
|
|
|
|
// this flag sometimes gets set for observers.
|
|
if ( obj->type == OBJ_OBSERVER ) {
|
|
return;
|
|
}
|
|
|
|
// sanity checks
|
|
if ( (obj->type != OBJ_SHIP) || (obj->instance < 0) ) {
|
|
return; // return because we really don't want to set the flag
|
|
}
|
|
|
|
// see if this ship is really a player ship (or should be)
|
|
shipp = &Ships[obj->instance];
|
|
extern void multi_ts_get_team_and_slot(char *, int *, int *);
|
|
multi_ts_get_team_and_slot(shipp->ship_name,&team,&slot);
|
|
if ( (shipp->wingnum == -1) || (team == -1) || (slot==-1) ) {
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
// set the flag
|
|
obj->flags = new_flags;
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[objnum].flags = new_flags;
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
// Check for unhandled flag changing
|
|
if ( (new_flags[Object::Object_Flags::Collides]) != (obj->flags[Object::Object_Flags::Collides]) ) {
|
|
mprintf(( "Unhandled flag changing in obj_set_flags!!\n" ));
|
|
mprintf(( "Add code to support it, see John for questions!!\n" ));
|
|
Int3();
|
|
} else {
|
|
// Since it wasn't an important flag, just bash it.
|
|
obj->flags = new_flags;
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[objnum].flags = new_flags;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
void obj_move_all_pre(object *objp, float frametime)
|
|
{
|
|
TRACE_SCOPE(tracing::PreMove);
|
|
|
|
switch( objp->type ) {
|
|
case OBJ_WEAPON:
|
|
if (!physics_paused){
|
|
weapon_process_pre( objp, frametime );
|
|
}
|
|
break;
|
|
case OBJ_SHIP:
|
|
if (!physics_paused || (objp==Player_obj )){
|
|
ship_process_pre( objp, frametime );
|
|
}
|
|
break;
|
|
case OBJ_FIREBALL:
|
|
// all fireballs are moved via fireball_process_post()
|
|
break;
|
|
case OBJ_SHOCKWAVE:
|
|
// all shockwaves are moved via shockwave_move_all()
|
|
break;
|
|
case OBJ_DEBRIS:
|
|
// all debris are moved via debris_process_post()
|
|
break;
|
|
case OBJ_ASTEROID:
|
|
if (!physics_paused){
|
|
asteroid_process_pre(objp);
|
|
}
|
|
break;
|
|
/* case OBJ_CMEASURE:
|
|
if (!physics_paused){
|
|
cmeasure_process_pre(objp, frametime);
|
|
}
|
|
break;*/
|
|
case OBJ_WAYPOINT:
|
|
break; // waypoints don't move..
|
|
case OBJ_GHOST:
|
|
break;
|
|
case OBJ_OBSERVER:
|
|
case OBJ_JUMP_NODE:
|
|
break;
|
|
case OBJ_BEAM:
|
|
break;
|
|
case OBJ_RAW_POF:
|
|
break;
|
|
case OBJ_PROP:
|
|
break;
|
|
case OBJ_NONE:
|
|
Int3();
|
|
break;
|
|
default:
|
|
Error(LOCATION, "Unhandled object type %d in obj_move_all_pre\n", objp->type);
|
|
}
|
|
|
|
objp->pre_move_event(objp);
|
|
}
|
|
|
|
// Used to tell if a particular group of lasers has cast light yet.
|
|
ubyte Obj_weapon_group_id_used[WEAPON_MAX_GROUP_IDS];
|
|
|
|
/**
|
|
* Called once a frame to mark all weapon groups as not having cast light yet.
|
|
*/
|
|
void obj_clear_weapon_group_id_list()
|
|
{
|
|
memset( Obj_weapon_group_id_used, 0, sizeof(Obj_weapon_group_id_used) );
|
|
}
|
|
|
|
int Arc_light = 1; // If set, electrical arcs on debris cast light
|
|
DCF_BOOL(arc_light, Arc_light)
|
|
|
|
void obj_move_all_post(object *objp, float frametime)
|
|
{
|
|
switch (objp->type)
|
|
{
|
|
case OBJ_WEAPON:
|
|
{
|
|
TRACE_SCOPE(tracing::WeaponPostMove);
|
|
|
|
if ( !physics_paused )
|
|
weapon_process_post( objp, frametime );
|
|
|
|
// Cast light
|
|
if ( (Detail.lighting > 3) && light_deferred_enabled() ) {
|
|
// Weapons cast light
|
|
|
|
int group_id = Weapons[objp->instance].group_id;
|
|
int cast_light = 1;
|
|
|
|
if (group_id >= 0) {
|
|
if (Obj_weapon_group_id_used[group_id] == 0) {
|
|
// Mark this group as done
|
|
Obj_weapon_group_id_used[group_id]++;
|
|
}
|
|
else {
|
|
// This group has already done its light casting
|
|
cast_light = 0;
|
|
}
|
|
}
|
|
if (cast_light) {
|
|
weapon* wp = &Weapons[objp->instance];
|
|
weapon_info* wi = &Weapon_info[wp->weapon_info_index];
|
|
auto lp = lighting_profiles::current();
|
|
hdr_color light_color;
|
|
|
|
float intensity_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_INTENSITY_MULT, *wp, &wp->modular_curves_instance);
|
|
float radius_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_RADIUS_MULT, *wp, &wp->modular_curves_instance);
|
|
float r_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_R_MULT, *wp, &wp->modular_curves_instance);
|
|
float g_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_G_MULT, *wp, &wp->modular_curves_instance);
|
|
float b_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_B_MULT, *wp, &wp->modular_curves_instance);
|
|
|
|
float source_radius = objp->radius;
|
|
float light_radius;
|
|
float light_brightness;
|
|
|
|
// Handle differing adjustments depending on weapon type.
|
|
if (wi->render_type == WRT_LASER) {
|
|
light_radius = lp->laser_light_radius.handle(wi->light_radius) * radius_mult;
|
|
// intensity is stored in the light color even if no user setting is done.
|
|
light_brightness = lp->laser_light_brightness.handle(wi->light_color.i());
|
|
} else {
|
|
// Missiles should typically not be treated as lights for their whole radius. TODO: make configurable.
|
|
source_radius *= 0.05f;
|
|
light_radius = lp->missile_light_radius.handle(wi->light_radius) * radius_mult;
|
|
light_brightness = lp->missile_light_brightness.handle(wi->light_color.i());
|
|
}
|
|
|
|
// If there is no specific color set in the table, laser render weapons have a dynamic color.
|
|
if (!wi->light_color_set && wi->render_type == WRT_LASER) {
|
|
// Classic dynamic laser color is handled with an old color object
|
|
color c;
|
|
weapon_get_laser_color(&c, objp);
|
|
light_color.set_rgbai((c.red/255.f), (c.green/255.f), (c.blue/255.f), 1.f, light_brightness);
|
|
} else {
|
|
// If not a laser then all default information needed is stored in the weapon light color
|
|
light_color.set_rgbai(wi->light_color.r(), wi->light_color.g(), wi->light_color.b(), 1.f, light_brightness);
|
|
}
|
|
|
|
light_color.multiply_rgbai(r_mult, g_mult, b_mult, 1.f, intensity_mult);
|
|
|
|
if(light_radius > 0.0f && intensity_mult > 0.0f && light_color.i() > 0.0f)
|
|
light_add_point(&objp->pos, light_radius, light_radius, &light_color, source_radius);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_SHIP:
|
|
{
|
|
TRACE_SCOPE(tracing::ShipPostMove);
|
|
|
|
if ( !physics_paused || (objp==Player_obj) ) {
|
|
ship_process_post( objp, frametime );
|
|
}
|
|
|
|
// Make any electrical arcs on ships cast light
|
|
if (Arc_light) {
|
|
if ( (Detail.lighting > 3) && (objp != Viewer_obj) ) {
|
|
auto shipp = &Ships[objp->instance];
|
|
|
|
for (auto &arc: shipp->electrical_arcs) {
|
|
if ( arc.timestamp.isValid() ) {
|
|
// Move arc endpoints into world coordinates
|
|
vec3d tmp1, tmp2;
|
|
vm_vec_unrotate(&tmp1,&arc.endpoint_1,&objp->orient);
|
|
vm_vec_add2(&tmp1,&objp->pos);
|
|
|
|
vm_vec_unrotate(&tmp2,&arc.endpoint_2,&objp->orient);
|
|
vm_vec_add2(&tmp2,&objp->pos);
|
|
|
|
light_add_point( &tmp1, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f);
|
|
light_add_point( &tmp2, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Check for changing team colors
|
|
ship* shipp = &Ships[objp->instance];
|
|
if (Ship_info[shipp->ship_info_index].uses_team_colors && stricmp(shipp->secondary_team_name.c_str(), "none") != 0) {
|
|
if (f2fl(Missiontime) * 1000 > f2fl(shipp->team_change_timestamp) * 1000 + shipp->team_change_time) {
|
|
shipp->team_name = shipp->secondary_team_name;
|
|
shipp->team_change_timestamp = 0;
|
|
shipp->team_change_time = 0;
|
|
shipp->secondary_team_name = "none";
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_FIREBALL:
|
|
{
|
|
TRACE_SCOPE(tracing::FireballPostMove);
|
|
|
|
if ( !physics_paused )
|
|
fireball_process_post(objp,frametime);
|
|
|
|
if (Detail.lighting > 2) {
|
|
float r = 0.0f, g = 0.0f, b = 0.0f;
|
|
// Make sure the new system works fine.
|
|
Assert(objp->instance > -1);
|
|
Assert(static_cast<int>(Fireballs.size()) > objp->instance);
|
|
|
|
fireball_get_color(Fireballs[objp->instance].fireball_info_index, &r, &g, &b);
|
|
|
|
// we don't cast black light, so just bail in that case
|
|
if ( (r == 0.0f) && (g == 0.0f) && (b == 0.0f) )
|
|
break;
|
|
|
|
// Make explosions cast light
|
|
float p = fireball_lifeleft_percent(objp);
|
|
if (p > 0.0f) {
|
|
if (p > 0.5f)
|
|
p = 1.0f - p;
|
|
|
|
p *= 2.0f;
|
|
float rad = p * (1.0f + frand() * 0.05f) * objp->radius;
|
|
|
|
float intensity = 1.0f;
|
|
if (fireball_is_warp(objp))
|
|
{
|
|
// Make sure the new system works fine.
|
|
Assert(static_cast<int>(Fireballs.size()) > objp->instance);
|
|
Assert(objp->instance > -1);
|
|
intensity = fireball_wormhole_intensity(&Fireballs[objp->instance]); // Valathil: Get wormhole radius for lighting
|
|
rad = objp->radius;
|
|
}
|
|
// P goes from 0 to 1 to 0 over the life of the explosion
|
|
// Only do this if rad is > 0.0000001f
|
|
// TODO: Make fireball source radius configurable, currently sized based on modern subspace portal textures as that will be a very prominent case of it
|
|
if (rad > 0.0001f)
|
|
light_add_point( &objp->pos, rad * 2.0f, rad * 5.0f, intensity, r, g, b,rad * 0.3f);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_SHOCKWAVE:
|
|
// all shockwaves are moved via shockwave_move_all()
|
|
break;
|
|
|
|
case OBJ_DEBRIS:
|
|
{
|
|
TRACE_SCOPE(tracing::DebrisPostMove);
|
|
|
|
if ( !physics_paused )
|
|
debris_process_post(objp, frametime);
|
|
|
|
// Make any electrical arcs on debris cast light
|
|
if (Arc_light) {
|
|
if ( Detail.lighting > 3 ) {
|
|
auto db = &Debris[objp->instance];
|
|
|
|
if (db->arc_frequency > 0) {
|
|
for (auto &arc: db->electrical_arcs) {
|
|
if ( arc.timestamp.isValid() ) {
|
|
// Move arc endpoints into world coordinates
|
|
vec3d tmp1, tmp2;
|
|
vm_vec_unrotate(&tmp1,&arc.endpoint_1,&objp->orient);
|
|
vm_vec_add2(&tmp1,&objp->pos);
|
|
|
|
vm_vec_unrotate(&tmp2,&arc.endpoint_2,&objp->orient);
|
|
vm_vec_add2(&tmp2,&objp->pos);
|
|
|
|
light_add_point( &tmp1, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f );
|
|
light_add_point( &tmp2, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_ASTEROID:
|
|
{
|
|
TRACE_SCOPE(tracing::AsteroidPostMove);
|
|
|
|
if ( !physics_paused )
|
|
asteroid_process_post(objp);
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_WAYPOINT:
|
|
break; // waypoints don't move..
|
|
|
|
case OBJ_GHOST:
|
|
break;
|
|
|
|
case OBJ_OBSERVER:
|
|
void observer_process_post(object *objp);
|
|
observer_process_post(objp);
|
|
break;
|
|
|
|
case OBJ_JUMP_NODE:
|
|
radar_plot_object(objp);
|
|
break;
|
|
|
|
case OBJ_BEAM:
|
|
break;
|
|
|
|
case OBJ_RAW_POF:
|
|
break;
|
|
|
|
case OBJ_PROP:
|
|
break;
|
|
|
|
case OBJ_NONE:
|
|
Int3();
|
|
break;
|
|
|
|
default:
|
|
Error(LOCATION, "Unhandled object type %d in obj_move_all_post\n", objp->type);
|
|
}
|
|
|
|
objp->post_move_event(objp);
|
|
}
|
|
|
|
|
|
int Collisions_enabled = 1;
|
|
|
|
DCF_BOOL( collisions, Collisions_enabled )
|
|
|
|
MONITOR( NumObjects )
|
|
|
|
/**
|
|
* Move all objects for the current frame
|
|
*/
|
|
void obj_move_all(float frametime)
|
|
{
|
|
TRACE_SCOPE(tracing::MoveObjects);
|
|
|
|
object *objp;
|
|
SCP_vector<object*> cmeasure_list;
|
|
const bool global_cmeasure_timer = (Cmeasures_homing_check > 0);
|
|
|
|
Assertion(Cmeasures_homing_check >= 0, "Cmeasures_homing_check is %d in obj_move_all(); it should never be negative. Get a coder!\n", Cmeasures_homing_check);
|
|
|
|
if (global_cmeasure_timer)
|
|
Cmeasures_homing_check--;
|
|
|
|
// Goober5000 - HACK HACK HACK
|
|
// this function also resets the OF_DOCKED_ALREADY_HANDLED flag, to save trips
|
|
// through the used object list
|
|
obj_delete_all_that_should_be_dead();
|
|
|
|
obj_merge_created_list();
|
|
|
|
// Clear the table that tells which groups of weapons have cast light so far.
|
|
if(!(Game_mode & GM_MULTIPLAYER) || (MULTIPLAYER_MASTER)) {
|
|
obj_clear_weapon_group_id_list();
|
|
}
|
|
|
|
MONITOR_INC( NumObjects, Num_objects );
|
|
|
|
for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
|
|
// skip objects which should be dead
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead]) {
|
|
continue;
|
|
}
|
|
|
|
// if this is an observer object, skip it
|
|
if (objp->type == OBJ_OBSERVER) {
|
|
continue;
|
|
}
|
|
|
|
// Compile a list of active countermeasures during an existing traversal of obj_used_list
|
|
if (objp->type == OBJ_WEAPON) {
|
|
weapon *wp = &Weapons[objp->instance];
|
|
weapon_info *wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
if (wip->wi_flags[Weapon::Info_Flags::Cmeasure]) {
|
|
if ((wip->cmeasure_timer_interval > 0 && timestamp_elapsed(wp->cmeasure_timer)) // If it's timer-based and ready to pulse...
|
|
|| (wip->cmeasure_timer_interval <= 0 && global_cmeasure_timer)) { // ...or it's not and the global counter is active...
|
|
// ...then it's actively pulsing and we need to add objp to cmeasure_list.
|
|
cmeasure_list.push_back(objp);
|
|
if (wip->cmeasure_timer_interval > 0) {
|
|
// Reset the timer
|
|
wp->cmeasure_timer = timestamp(wip->cmeasure_timer_interval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
vec3d cur_pos = objp->pos; // Save the current position
|
|
|
|
#ifdef OBJECT_CHECK
|
|
obj_check_object( objp );
|
|
#endif
|
|
|
|
// pre-move
|
|
obj_move_all_pre(objp, frametime);
|
|
|
|
bool interpolation_object = multi_oo_is_interp_object(objp);
|
|
|
|
// store last pos and orient, but only for non-interpolation objects
|
|
// interpolation objects will need to to work backwards from the last good position
|
|
// to prevent collision issues
|
|
if (!interpolation_object){
|
|
objp->last_pos = cur_pos;
|
|
objp->last_orient = objp->orient;
|
|
}
|
|
|
|
// Goober5000 - accommodate objects that aren't supposed to move in some way (at least until they're destroyed)
|
|
bool dont_change_position = objp->flags[Object::Object_Flags::Dont_change_position, Object::Object_Flags::Immobile] && objp->hull_strength > 0.0f;
|
|
bool dont_change_orientation = objp->flags[Object::Object_Flags::Dont_change_orientation, Object::Object_Flags::Immobile] && objp->hull_strength > 0.0f;
|
|
|
|
// skip the physics if we're totally immobile
|
|
if (!dont_change_position || !dont_change_orientation) {
|
|
// if this is an object which should be interpolated in multiplayer, do so
|
|
if (interpolation_object) {
|
|
extern void interpolate_main_helper(int objnum, vec3d* pos, matrix* ori, physics_info* pip, vec3d* last_pos, matrix* last_orient, vec3d* gravity, bool player_ship);
|
|
|
|
interpolate_main_helper(OBJ_INDEX(objp), &objp->pos, &objp->orient, &objp->phys_info, &objp->last_pos, &objp->last_orient, &The_mission.gravity, objp->flags[Object::Object_Flags::Player_ship]);
|
|
} else {
|
|
// physics
|
|
obj_move_call_physics(objp, frametime);
|
|
}
|
|
}
|
|
|
|
// If the object isn't supposed to move, roll back any movement that occurred. Most of the movement should already have been skipped, but this ensures complete immobility.
|
|
if (dont_change_position) {
|
|
objp->pos = objp->last_pos;
|
|
|
|
// make sure velocity is always 0
|
|
vm_vec_zero(&objp->phys_info.vel);
|
|
vm_vec_zero(&objp->phys_info.desired_vel);
|
|
objp->phys_info.speed = 0.0f;
|
|
objp->phys_info.fspeed = 0.0f;
|
|
}
|
|
if (dont_change_orientation) {
|
|
objp->orient = objp->last_orient;
|
|
|
|
// make sure velocity is always 0
|
|
vm_vec_zero(&objp->phys_info.rotvel);
|
|
vm_vec_zero(&objp->phys_info.desired_rotvel);
|
|
}
|
|
|
|
// Submodel movement now happens here, right after physics movement. It's not excluded by the "immobile", "don't-change-position", or "don't-change-orientation" flags.
|
|
|
|
// this flag only affects ship subsystems, not any other type of submodel movement
|
|
if (objp->type == OBJ_SHIP && !Ships[objp->instance].flags[Ship::Ship_Flags::Subsystem_movement_locked])
|
|
ship_move_subsystems(objp);
|
|
|
|
// do animation on this object
|
|
int model_instance_num = object_get_model_instance_num(objp);
|
|
if (model_instance_num >= 0) {
|
|
polymodel_instance* pmi = model_get_instance(model_instance_num);
|
|
animation::ModelAnimation::stepAnimations(frametime, pmi);
|
|
}
|
|
|
|
// finally, do intrinsic motion on this object
|
|
// (this happens last because look_at is a type of intrinsic rotation,
|
|
// and look_at needs to happen last or the angle may be off by a frame)
|
|
model_do_intrinsic_motions(objp);
|
|
|
|
// Future TODO: Props will need a version of this when submodel animation support is added.
|
|
// For ships, we now have to make sure that all the submodel detail levels remain consistent.
|
|
if (objp->type == OBJ_SHIP)
|
|
ship_model_replicate_submodels(objp);
|
|
|
|
// move post
|
|
obj_move_all_post(objp, frametime);
|
|
|
|
// Equipment script processing
|
|
if (objp->type == OBJ_SHIP) {
|
|
ship* shipp = &Ships[objp->instance];
|
|
object* target;
|
|
|
|
if (Ai_info[shipp->ai_index].target_objnum != -1)
|
|
target = &Objects[Ai_info[shipp->ai_index].target_objnum];
|
|
else
|
|
target = NULL;
|
|
if (objp == Player_obj && Player_ai->target_objnum != -1)
|
|
target = &Objects[Player_ai->target_objnum];
|
|
|
|
if (scripting::hooks::OnWeaponEquipped->isActive()) {
|
|
scripting::hooks::OnWeaponEquipped->run(scripting::hooks::WeaponEquippedConditions{ shipp, target },
|
|
scripting::hook_param_list(
|
|
scripting::hook_param("User", 'o', objp),
|
|
scripting::hook_param("Target", 'o', target)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now apply intrinsic motion to things that aren't objects (like skyboxes). This technically doesn't belong in the object code,
|
|
// but there isn't really a good place to put this, it doesn't hurt to have this here, and it's conceptually related to what's here.
|
|
model_do_intrinsic_motions(nullptr);
|
|
|
|
// After all objects have been moved, move all docked objects.
|
|
for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
|
|
// skip objects which should be dead
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead]) {
|
|
continue;
|
|
}
|
|
|
|
dock_move_docked_objects(objp);
|
|
|
|
//Valathil - Move the screen rotation calculation for billboards here to get the updated orientation matrices caused by docking interpolation
|
|
vec3d tangles;
|
|
|
|
tangles.xyz.x = -objp->phys_info.rotvel.xyz.x*frametime;
|
|
tangles.xyz.y = -objp->phys_info.rotvel.xyz.y*frametime;
|
|
tangles.xyz.z = objp->phys_info.rotvel.xyz.z*frametime;
|
|
|
|
// If this is the viewer_object, keep track of the
|
|
// changes in banking so that rotated bitmaps look correct.
|
|
// This is used by the g3_draw_rotated_bitmap function.
|
|
extern physics_info *Viewer_physics_info;
|
|
if ( &objp->phys_info == Viewer_physics_info ) {
|
|
vec3d tangles_r;
|
|
vm_vec_unrotate(&tangles_r, &tangles, &Eye_matrix);
|
|
vm_vec_rotate(&tangles, &tangles_r, &objp->orient);
|
|
|
|
if(objp->dock_list && objp->dock_list->docked_objp->type == OBJ_SHIP && Ai_info[Ships[objp->dock_list->docked_objp->instance].ai_index].submode == AIS_DOCK_4) {
|
|
Physics_viewer_bank -= tangles.xyz.z*0.65f;
|
|
} else {
|
|
Physics_viewer_bank -= tangles.xyz.z;
|
|
}
|
|
|
|
if ( Physics_viewer_bank < 0.0f ){
|
|
Physics_viewer_bank += 2.0f * PI;
|
|
}
|
|
|
|
if ( Physics_viewer_bank > 2.0f * PI ){
|
|
Physics_viewer_bank -= 2.0f * PI;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cmeasure_list.empty())
|
|
find_homing_object_cmeasures(cmeasure_list); // If any cmeasures are active, maybe steer away homing missiles
|
|
|
|
// do pre-collision stuff for beam weapons
|
|
beam_move_all_pre();
|
|
|
|
if ( Collisions_enabled ) {
|
|
TRACE_SCOPE(tracing::CollisionDetection);
|
|
obj_sort_and_collide();
|
|
}
|
|
|
|
turret_swarm_check_validity();
|
|
|
|
// do post-collision stuff for beam weapons
|
|
beam_move_all_post();
|
|
|
|
// Cyborg17 - Update the multi record on multi with these new positions. Clients need to get updated, too.
|
|
if (MULTIPLAYER_MASTER) {
|
|
multi_ship_record_update_all();
|
|
}
|
|
|
|
// update artillery locking info now
|
|
ship_update_artillery_lock();
|
|
|
|
if (Nmodel_instance_num >= 0) {
|
|
animation::ModelAnimation::stepAnimations(frametime, model_get_instance(Nmodel_instance_num));
|
|
}
|
|
|
|
if (Viewer_obj && Viewer_obj->type == OBJ_SHIP && Viewer_obj->instance >= 0) {
|
|
ship* shipp = &Ships[Viewer_obj->instance];
|
|
if (shipp->cockpit_model_instance >= 0) {
|
|
animation::ModelAnimation::stepAnimations(frametime, model_get_instance(shipp->cockpit_model_instance));
|
|
}
|
|
}
|
|
|
|
// mprintf(("moved all objects\n"));
|
|
}
|
|
|
|
|
|
MONITOR( NumObjectsRend )
|
|
|
|
/**
|
|
* Render an object. Calls one of several routines based on type
|
|
*/
|
|
extern int Cmdline_dis_weapons;
|
|
|
|
void obj_render(object *obj)
|
|
{
|
|
model_draw_list render_list;
|
|
|
|
obj_queue_render(obj, &render_list);
|
|
|
|
render_list.init_render();
|
|
render_list.render_all();
|
|
|
|
gr_zbias(0);
|
|
gr_set_cull(0);
|
|
gr_zbuffer_set(ZBUFFER_TYPE_READ);
|
|
gr_set_fill_mode(GR_FILL_MODE_SOLID);
|
|
|
|
gr_clear_states();
|
|
|
|
gr_reset_lighting();
|
|
}
|
|
|
|
void raw_pof_render(object* obj, model_draw_list* scene) {
|
|
model_render_params render_info;
|
|
|
|
auto pof_obj = Pof_objects[obj->instance];
|
|
|
|
uint64_t render_flags = MR_NORMAL | MR_IS_MISSILE | MR_NO_BATCH;
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_light])
|
|
render_flags |= MR_NO_LIGHTING;
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Glowmaps_disabled]) {
|
|
render_flags |= MR_NO_GLOWMAPS;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Draw_as_wireframe]) {
|
|
render_flags |= MR_SHOW_OUTLINE_HTL | MR_NO_POLYS | MR_NO_TEXTURING;
|
|
render_info.set_color(Wireframe_color);
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_full_detail]) {
|
|
render_flags |= MR_FULL_DETAIL;
|
|
}
|
|
|
|
uint debug_flags = render_info.get_debug_flags();
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_diffuse]) {
|
|
debug_flags |= MR_DEBUG_NO_DIFFUSE;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_glowmap]) {
|
|
debug_flags |= MR_DEBUG_NO_GLOW;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_normalmap]) {
|
|
debug_flags |= MR_DEBUG_NO_NORMAL;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_ambientmap]) {
|
|
debug_flags |= MR_DEBUG_NO_AMBIENT;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_specmap]) {
|
|
debug_flags |= MR_DEBUG_NO_SPEC;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_reflectmap]) {
|
|
debug_flags |= MR_DEBUG_NO_REFLECT;
|
|
}
|
|
|
|
render_info.set_object_number(OBJ_INDEX(obj));
|
|
|
|
render_info.set_flags(render_flags);
|
|
render_info.set_debug_flags(debug_flags);
|
|
|
|
model_render_queue(&render_info, scene, Pof_objects[obj->instance].model_num, &obj->orient, &obj->pos);
|
|
}
|
|
|
|
void obj_queue_render(object* obj, model_draw_list* scene)
|
|
{
|
|
TRACE_SCOPE(tracing::QueueRender);
|
|
|
|
if ( obj->flags[Object::Object_Flags::Should_be_dead] ) return;
|
|
|
|
if (scripting::hooks::OnObjectRender->isActive()) {
|
|
scripting::api::Current_scene = scene;
|
|
|
|
auto param_list = scripting::hook_param_list(scripting::hook_param("Self", 'o', obj));
|
|
|
|
// Always execute the hook content
|
|
bool skip_render = scripting::hooks::OnObjectRender->isOverride(scripting::hooks::ObjectDrawConditions{ obj }, param_list);
|
|
scripting::hooks::OnObjectRender->run(scripting::hooks::ObjectDrawConditions{ obj }, std::move(param_list));
|
|
|
|
// Clear the render scene context
|
|
scripting::api::Current_scene = nullptr;
|
|
|
|
if (skip_render) {
|
|
// Script said that it want's to skip rendering
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch ( obj->type ) {
|
|
case OBJ_NONE:
|
|
#ifndef NDEBUG
|
|
mprintf(( "ERROR!!!! Bogus obj " PTRDIFF_T_ARG " is rendering!\n", obj-Objects ));
|
|
Int3();
|
|
#endif
|
|
break;
|
|
case OBJ_WEAPON:
|
|
if ( Cmdline_dis_weapons ) return;
|
|
weapon_render(obj, scene);
|
|
break;
|
|
case OBJ_SHIP:
|
|
ship_render(obj, scene);
|
|
break;
|
|
case OBJ_FIREBALL:
|
|
fireball_render(obj, scene);
|
|
break;
|
|
case OBJ_SHOCKWAVE:
|
|
shockwave_render(obj, scene);
|
|
break;
|
|
case OBJ_DEBRIS:
|
|
debris_render(obj, scene);
|
|
break;
|
|
case OBJ_ASTEROID:
|
|
asteroid_render(obj, scene);
|
|
break;
|
|
case OBJ_JUMP_NODE: {
|
|
int objnum = OBJ_INDEX(obj);
|
|
for ( auto &jnp : Jump_nodes ) {
|
|
if ( jnp.GetSCPObjectNumber() != objnum ) {
|
|
continue;
|
|
}
|
|
|
|
jnp.Render(scene, &obj->pos, &Eye_position);
|
|
}
|
|
break;
|
|
}
|
|
case OBJ_WAYPOINT:
|
|
// if (Show_waypoints) {
|
|
// gr_set_color( 128, 128, 128 );
|
|
// g3_draw_sphere_ez( &obj->pos, 5.0f );
|
|
// }
|
|
break;
|
|
case OBJ_GHOST:
|
|
break;
|
|
case OBJ_BEAM:
|
|
break;
|
|
case OBJ_RAW_POF:
|
|
raw_pof_render(obj, scene);
|
|
break;
|
|
case OBJ_PROP:
|
|
prop_render(obj, scene);
|
|
break;
|
|
default:
|
|
Error( LOCATION, "Unhandled obj type %d in obj_render", obj->type );
|
|
}
|
|
}
|
|
|
|
void obj_init_all_ships_physics()
|
|
{
|
|
object *objp;
|
|
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if (objp->type == OBJ_SHIP)
|
|
physics_ship_init(objp);
|
|
}
|
|
|
|
}
|
|
|
|
void obj_observer_move(float frame_time)
|
|
{
|
|
object *objp;
|
|
float ft;
|
|
|
|
// if i'm not in multiplayer, or not an observer, bail
|
|
if(!(Game_mode & GM_MULTIPLAYER) || (Net_player == NULL) || !(Net_player->flags & NETINFO_FLAG_OBSERVER) || (Player_obj->type != OBJ_OBSERVER)){
|
|
return;
|
|
}
|
|
|
|
objp = Player_obj;
|
|
|
|
objp->last_pos = objp->pos;
|
|
objp->last_orient = objp->orient; // save the orientation -- useful in multiplayer.
|
|
|
|
ft = flFrametime;
|
|
obj_move_call_physics( objp, ft );
|
|
obj_move_all_post(objp, frame_time);
|
|
}
|
|
|
|
/**
|
|
* Returns a vector of the average position of all ships in the mission.
|
|
*/
|
|
void obj_get_average_ship_pos( vec3d *pos )
|
|
{
|
|
int count;
|
|
|
|
vm_vec_zero( pos );
|
|
|
|
// average up all ship positions
|
|
count = 0;
|
|
for (auto so: list_range(&Ship_obj_list)) {
|
|
auto objp = &Objects[so->objnum];
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
vm_vec_add2( pos, &objp->pos );
|
|
count++;
|
|
}
|
|
|
|
if ( count )
|
|
vm_vec_scale( pos, 1.0f/(float)count );
|
|
}
|
|
|
|
/**
|
|
* Return the team for the object passed as a parameter
|
|
*
|
|
* @param objp Pointer to object that you want team for
|
|
*
|
|
* @return enumerated team on success
|
|
* @return -1 on failure (for objects that don't have teams)
|
|
*/
|
|
int obj_team(object *objp)
|
|
{
|
|
Assert( objp != NULL );
|
|
int team = -1;
|
|
|
|
switch ( objp->type ) {
|
|
case OBJ_SHIP:
|
|
Assert( objp->instance >= 0 && objp->instance < MAX_SHIPS );
|
|
team = Ships[objp->instance].team;
|
|
break;
|
|
|
|
case OBJ_DEBRIS:
|
|
team = debris_get_team(objp);
|
|
Assertion(team != -1, "Obj_team called for a debris object with no team.");
|
|
break;
|
|
|
|
/* case OBJ_CMEASURE:
|
|
Assert( objp->instance >= 0 && objp->instance < MAX_CMEASURES);
|
|
team = Cmeasures[objp->instance].team;
|
|
break;
|
|
*/
|
|
case OBJ_WEAPON:
|
|
Assert( objp->instance >= 0 && objp->instance < MAX_WEAPONS );
|
|
team = Weapons[objp->instance].team;
|
|
break;
|
|
|
|
case OBJ_JUMP_NODE:
|
|
team = Player_ship->team;
|
|
break;
|
|
|
|
case OBJ_FIREBALL:
|
|
case OBJ_WAYPOINT:
|
|
case OBJ_START:
|
|
case OBJ_NONE:
|
|
case OBJ_GHOST:
|
|
case OBJ_SHOCKWAVE:
|
|
case OBJ_BEAM:
|
|
case OBJ_RAW_POF:
|
|
case OBJ_PROP:
|
|
team = -1;
|
|
break;
|
|
|
|
case OBJ_ASTEROID:
|
|
team = Iff_traitor;
|
|
break;
|
|
|
|
default:
|
|
Int3(); // can't happen
|
|
break;
|
|
} // end switch
|
|
|
|
Assertion(team != -1, "Obj_team called for a object of type %s with no team.", Object_type_names[objp->type]);
|
|
return team;
|
|
}
|
|
|
|
/**
|
|
* Removes any occurances of object 'a' from
|
|
* the pairs list.
|
|
*/
|
|
extern int Num_pairs;
|
|
|
|
/**
|
|
* Reset all collisions
|
|
*/
|
|
void obj_reset_all_collisions()
|
|
{
|
|
// clear checkobjects
|
|
#ifndef NDEBUG
|
|
for (int i = 0; i < MAX_OBJECTS; ++i) {
|
|
CheckObjects[i] = checkobject();
|
|
}
|
|
#endif
|
|
|
|
// clear object pairs
|
|
obj_reset_colliders();
|
|
|
|
// now add every object back into the object collision pairs
|
|
for (auto moveup: list_range(&obj_used_list)) {
|
|
if (moveup->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
// he's not in the collision list
|
|
moveup->flags.set(Object::Object_Flags::Not_in_coll);
|
|
|
|
// recalc pairs for this guy
|
|
obj_add_collider(OBJ_INDEX(moveup));
|
|
}
|
|
}
|
|
|
|
// Goober5000
|
|
int object_is_docked(object *objp)
|
|
{
|
|
return (objp->dock_list != NULL);
|
|
}
|
|
|
|
// Goober5000
|
|
int object_is_dead_docked(object *objp)
|
|
{
|
|
return (objp->dead_dock_list != NULL);
|
|
}
|
|
|
|
/**
|
|
* Makes an object start 'gliding'
|
|
*
|
|
* It will continue on the same velocity that it was going,
|
|
* regardless of orientation -WMC
|
|
*/
|
|
void object_set_gliding(object *objp, bool enable, bool force)
|
|
{
|
|
Assert(objp != NULL);
|
|
|
|
if(enable) {
|
|
if (!force) {
|
|
objp->phys_info.flags |= PF_GLIDING;
|
|
} else {
|
|
objp->phys_info.flags |= PF_FORCE_GLIDE;
|
|
}
|
|
} else {
|
|
if (!force) {
|
|
objp->phys_info.flags &= ~PF_GLIDING;
|
|
} else {
|
|
objp->phys_info.flags &= ~PF_FORCE_GLIDE;
|
|
}
|
|
vm_vec_rotate(&objp->phys_info.prev_ramp_vel, &objp->phys_info.vel, &objp->orient); //Backslash
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return whether an object is gliding -WMC
|
|
*/
|
|
bool object_get_gliding(object *objp)
|
|
{
|
|
Assert(objp != NULL);
|
|
|
|
return ( ((objp->phys_info.flags & PF_GLIDING) != 0) || ((objp->phys_info.flags & PF_FORCE_GLIDE) != 0));
|
|
}
|
|
|
|
bool object_glide_forced(object *objp)
|
|
{
|
|
return (objp->phys_info.flags & PF_FORCE_GLIDE) != 0;
|
|
}
|
|
|
|
/**
|
|
* Quickly finds an object by its signature
|
|
*/
|
|
int obj_get_by_signature(int sig)
|
|
{
|
|
Assert(sig > 0);
|
|
|
|
for (auto objp: list_range(&obj_used_list))
|
|
{
|
|
// don't skip over should-be-dead objects, since we assume we know what we're doing
|
|
if (objp->signature == sig)
|
|
return OBJ_INDEX(objp);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Gets the model number for this object, or -1 if none
|
|
*/
|
|
int object_get_model_num(const object *objp)
|
|
{
|
|
switch(objp->type)
|
|
{
|
|
case OBJ_ASTEROID:
|
|
{
|
|
asteroid *asp = &Asteroids[objp->instance];
|
|
return Asteroid_info[asp->asteroid_type].subtypes[asp->asteroid_subtype].model_number;
|
|
}
|
|
case OBJ_DEBRIS:
|
|
{
|
|
debris *debrisp = &Debris[objp->instance];
|
|
return debrisp->model_num;
|
|
}
|
|
case OBJ_SHIP:
|
|
{
|
|
ship *shipp = &Ships[objp->instance];
|
|
return Ship_info[shipp->ship_info_index].model_num;
|
|
}
|
|
case OBJ_WEAPON:
|
|
{
|
|
weapon *wp = &Weapons[objp->instance];
|
|
return Weapon_info[wp->weapon_info_index].model_num;
|
|
}
|
|
case OBJ_RAW_POF:
|
|
return Pof_objects[objp->instance].model_num;
|
|
case OBJ_PROP:
|
|
return Prop_info[objp->instance].model_num;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Gets the model for this object, or nullptr if none
|
|
*/
|
|
polymodel *object_get_model(const object *objp)
|
|
{
|
|
int model_num = object_get_model_num(objp);
|
|
return model_num >= 0 ? model_get(model_num) : nullptr;
|
|
}
|
|
|
|
/**
|
|
* Gets the model instance number for this object, or -1 if none
|
|
*/
|
|
int object_get_model_instance_num(const object *objp)
|
|
{
|
|
if (objp == nullptr)
|
|
return -1;
|
|
|
|
switch (objp->type)
|
|
{
|
|
case OBJ_ASTEROID:
|
|
{
|
|
asteroid *asp = &Asteroids[objp->instance];
|
|
return asp->model_instance_num;
|
|
}
|
|
case OBJ_DEBRIS:
|
|
{
|
|
debris *debrisp = &Debris[objp->instance];
|
|
return debrisp->model_instance_num;
|
|
}
|
|
case OBJ_SHIP:
|
|
{
|
|
ship *shipp = &Ships[objp->instance];
|
|
return shipp->model_instance_num;
|
|
}
|
|
case OBJ_WEAPON:
|
|
{
|
|
weapon *wp = &Weapons[objp->instance];
|
|
return wp->model_instance_num;
|
|
}
|
|
case OBJ_JUMP_NODE:
|
|
{
|
|
CJumpNode* jnp = jumpnode_get_by_objnum(OBJ_INDEX(objp));
|
|
Assertion(jnp != nullptr, "Could not find jump node!");
|
|
return jnp->GetPolymodelInstanceNum();
|
|
}
|
|
case OBJ_RAW_POF:
|
|
return Pof_objects[objp->instance].model_instance;
|
|
case OBJ_PROP:
|
|
return prop_id_lookup(objp->instance)->model_instance_num;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Gets the model instance for this object, or nullptr if none
|
|
*/
|
|
polymodel_instance *object_get_model_instance(const object *objp)
|
|
{
|
|
int model_instance_num = object_get_model_instance_num(objp);
|
|
return model_instance_num >= 0 ? model_get_instance(model_instance_num) : nullptr;
|
|
}
|
|
|
|
bool obj_compare(object* left, object* right) {
|
|
if (left == right) {
|
|
// Same pointer
|
|
return true;
|
|
}
|
|
if (left == nullptr || right == nullptr) {
|
|
// Only one is nullptr and the other is not (since they are not equal)
|
|
return false;
|
|
}
|
|
|
|
return OBJ_INDEX(left) == OBJ_INDEX(right);
|
|
}
|
|
|
|
void physics_populate_snapshot(physics_snapshot& snapshot, const object* objp)
|
|
{
|
|
Assertion(objp != nullptr, "Bad object (nullptr) passed to physics_overwrite_snapshot, please report to the SCP!");
|
|
|
|
snapshot.position = objp->pos;
|
|
snapshot.orientation = objp->orient;
|
|
snapshot.velocity = objp->phys_info.vel;
|
|
snapshot.desired_velocity = objp->phys_info.desired_vel;
|
|
snapshot.rotational_velocity = objp->phys_info.rotvel;
|
|
snapshot.desired_rotational_velocity = objp->phys_info.desired_rotvel;
|
|
}
|
|
|
|
void physics_apply_pstate_to_object(object* objp, const physics_snapshot& source)
|
|
{
|
|
Assertion(objp != nullptr, "Bad object passed to phsyics snapshot application code. This is a coder mistake, please report!");
|
|
|
|
objp->pos = source.position;
|
|
objp->orient = source.orientation;
|
|
objp->phys_info.vel = source.velocity;
|
|
objp->phys_info.desired_vel = source.desired_velocity;
|
|
objp->phys_info.rotvel = source.rotational_velocity;
|
|
objp->phys_info.desired_rotvel = source.desired_rotational_velocity;
|
|
}
|
|
|