From c133c0b12c672bebe707f55f3899dc1ae0b1e93e Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 2 Jun 2025 08:39:00 -0500 Subject: [PATCH 01/25] First draft of prop object type --- code/globalincs/globals.h | 3 + code/graphics/shadows.cpp | 1 + code/mission/missionparse.cpp | 57 ++- .../model/animation/modelanimation_driver.cpp | 1 + code/model/modelread.cpp | 1 + code/model/modelrender.cpp | 4 + code/nebula/neb.cpp | 4 + code/object/collidedebrisship.cpp | 2 + code/object/collidepropdebris.cpp | 1 + code/object/collidepropship.cpp | 1 + code/object/collidepropweapon.cpp | 1 + code/object/collideshipweapon.cpp | 2 + code/object/objcollide.cpp | 23 + code/object/objcollide.h | 9 + code/object/object.cpp | 23 +- code/object/object.h | 1 + code/object/objectsort.cpp | 8 +- code/parse/parselo.cpp | 52 +++ code/parse/parselo.h | 1 + code/prop/prop.cpp | 441 ++++++++++++++++++ code/prop/prop.h | 57 +++ code/prop/prop_flags.h | 33 ++ code/scripting/api/objs/object.cpp | 8 + code/ship/shiphit.cpp | 9 + code/source_groups.cmake | 10 + fred2/fred.rc | 2 + fred2/fredrender.cpp | 45 +- fred2/fredview.cpp | 40 +- fred2/mainfrm.cpp | 162 ++++++- fred2/mainfrm.h | 40 ++ fred2/management.cpp | 100 +++- fred2/management.h | 4 +- fred2/missionsave.cpp | 40 +- fred2/missionsave.h | 11 + fred2/resource.h | 3 + freespace2/freespace.cpp | 2 + qtfred/src/mission/management.cpp | 4 + 37 files changed, 1158 insertions(+), 48 deletions(-) create mode 100644 code/object/collidepropdebris.cpp create mode 100644 code/object/collidepropship.cpp create mode 100644 code/object/collidepropweapon.cpp create mode 100644 code/prop/prop.cpp create mode 100644 code/prop/prop.h create mode 100644 code/prop/prop_flags.h diff --git a/code/globalincs/globals.h b/code/globalincs/globals.h index 6c1fcb3dca5..141e9c2e20f 100644 --- a/code/globalincs/globals.h +++ b/code/globalincs/globals.h @@ -34,6 +34,9 @@ #define MAX_SHIPS 500 // max number of ship instances there can be.DTP; bumped from 200 to 400, then to 500 in 2022 #define SHIPS_LIMIT 500 // what MAX_SHIPS will be at release time (for error checking in debug mode); dtp Bumped from 200 to 400, then to 500 in 2022 +// from prop.h +#define MAX_PROPS 200 // Arbitrary guess + // from missionparse.h and then redefined to the same value in sexp.h #define TOKEN_LENGTH 32 diff --git a/code/graphics/shadows.cpp b/code/graphics/shadows.cpp index f9db7329300..7e76994bb2b 100644 --- a/code/graphics/shadows.cpp +++ b/code/graphics/shadows.cpp @@ -509,6 +509,7 @@ void shadows_render_all(fov_t fov, matrix *eye_orient, vec3d *eye_pos) switch(objp->type) { case OBJ_RAW_POF: + case OBJ_PROP: case OBJ_SHIP: { obj_queue_render(objp, &scene); diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 8cfdaf5781e..0e2d4efbbce 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -64,6 +64,7 @@ #include "parse/generic_log.h" #include "parse/parselo.h" #include "parse/sexp_container.h" +#include "prop/prop.h" #include "scripting/global_hooks.h" #include "scripting/hook_api.h" #include "scripting/hook_conditions.h" @@ -140,6 +141,9 @@ p_object Ship_arrival_list; // for linked list of ships to arrive later // all the ships that we parse SCP_vector Parse_objects; +// all the props that we parse +SCP_vector Parse_props; + // list for arriving support ship p_object Support_ship_pobj; @@ -5106,17 +5110,50 @@ void parse_wing(mission *pm) // Goober5000 - wing creation stuff moved to post_process_ships_wings } +void parse_prop(mission* pm) +{ + parsed_prop prop; + required_string("$Name:"); + stuff_string(prop.name, F_NAME, NAME_LENGTH); + + // Maybe do this by name instead? + required_string("$Class:"); + stuff_int(&prop.prop_info_index); + + required_string("$Location:"); + stuff_vec3d(&prop.position); + + required_string("$Orientation:"); + stuff_matrix(&prop.orientation); + + Parse_props.emplace_back(prop); +} + void parse_wings(mission* pm) { required_string("#Wings"); - while (required_string_either("#Events", "$Name:")) - { + while (true) { + int which = required_string_one_of(3, "#Events", "#Props", "$Name:"); + + if (which == -1 || which == 0 || which == 1) // #Events or #Props + break; + Assert(Num_wings < MAX_WINGS); parse_wing(pm); Num_wings++; } } +void parse_props(mission* pm) +{ + if (optional_string("#Props")) { + while (required_string_either("#Events", "$Name:")) { + Assert(Parse_props.size() < MAX_PROPS); + parse_prop(pm); + } + } +} + // Goober5000 void resolve_path_masks(int anchor, int *path_mask) { @@ -5195,6 +5232,15 @@ void post_process_path_stuff() } } +// MjnMixael +void post_process_props() +{ + for (int i = 0; i < static_cast(Parse_props.size()); i++) { + parsed_prop* propp = &Parse_props[i]; + prop_create(&propp->orientation, &propp->position, propp->prop_info_index, propp->name); + } +} + // Goober5000 void post_process_ships_wings() { @@ -5319,7 +5365,6 @@ void post_process_ships_wings() mission_parse_maybe_create_parse_object(&p_obj); } - // ----------------- at this point the ships have been created ----------------- // Now set up the wings. This must be done after both dock stuff and ship stuff. @@ -6470,6 +6515,7 @@ bool parse_mission(mission *pm, int flags) parse_player_info(pm); parse_objects(pm, flags); parse_wings(pm); + parse_props(pm); parse_events(pm); parse_goals(pm); parse_waypoints_and_jumpnodes(pm); @@ -6555,6 +6601,8 @@ bool post_process_mission(mission *pm) ship_weapon *swp; ship_obj *so; + post_process_props(); + // Goober5000 - this must be done even before post_process_ships_wings because it is a prerequisite ship_clear_ship_type_counts(); @@ -7036,6 +7084,7 @@ void mission_init(mission *pm) jumpnode_level_close(); waypoint_level_close(); + props_level_close(); red_alert_invalidate_timestamp(); event_music_reset_choices(); @@ -7067,6 +7116,8 @@ void mission_init(mission *pm) Num_reinforcements = 0; + Parse_props.clear(); + Asteroid_field.num_initial_asteroids = 0; // This could be set with a sexp, so we should reset it here diff --git a/code/model/animation/modelanimation_driver.cpp b/code/model/animation/modelanimation_driver.cpp index bf59530689c..596b6c32fd7 100644 --- a/code/model/animation/modelanimation_driver.cpp +++ b/code/model/animation/modelanimation_driver.cpp @@ -98,6 +98,7 @@ namespace animation { } } + // ADD PROP HERE template static float get_ship_subproperty_float(polymodel_instance* pmi){ int objnum = get_pmi_objnum(pmi); diff --git a/code/model/modelread.cpp b/code/model/modelread.cpp index 11f671cb817..622b775c386 100644 --- a/code/model/modelread.cpp +++ b/code/model/modelread.cpp @@ -4925,6 +4925,7 @@ void model_get_moving_submodel_list(SCP_vector &submodel_vector, const obje int model_instance_num; int model_num; + // ADD PROP HERE if (objp->type == OBJ_SHIP) { model_instance_num = Ships[objp->instance].model_instance_num; model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index 5cac795db25..3099ba74a38 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -1329,6 +1329,7 @@ int model_render_determine_elapsed_time(int objnum, uint64_t flags) if ( objnum >= 0 ) { object *objp = &Objects[objnum]; + // ADD PROP HERE if ( objp->type == OBJ_SHIP ) { return timestamp_since(Ships[objp->instance].base_texture_anim_timestamp); } @@ -1925,6 +1926,7 @@ void model_render_set_glow_points(const polymodel *pm, int objnum) if ( objnum > -1 ) { object *objp = &Objects[objnum]; + // ADD PROP HERE if ( objp != NULL && objp->type == OBJ_SHIP ) { shipp = &Ships[Objects[objnum].instance]; sip = &Ship_info[shipp->ship_info_index]; @@ -2609,6 +2611,7 @@ void model_render_queue(const model_render_params* interp, model_draw_list* scen objp = &Objects[objnum]; int tentative_num = -1; + // ADD PROP HERE if (objp->type == OBJ_SHIP) { shipp = &Ships[objp->instance]; tentative_num = shipp->model_instance_num; @@ -2932,6 +2935,7 @@ void model_render_only_glowpoint_lights(const model_render_params* interp, int m objp = &Objects[objnum]; int tentative_num = -1; + // ADD PROP HERE if (objp->type == OBJ_SHIP) { shipp = &Ships[objp->instance]; tentative_num = shipp->model_instance_num; diff --git a/code/nebula/neb.cpp b/code/nebula/neb.cpp index ba8a250aabc..c4d86b75288 100644 --- a/code/nebula/neb.cpp +++ b/code/nebula/neb.cpp @@ -680,6 +680,7 @@ int neb2_skip_render(object *objp, float z_depth) // any ship or raw pof less than 3% visible at their closest point case OBJ_RAW_POF: + case OBJ_PROP: case OBJ_SHIP: if (fog < 0.03f) return 1; @@ -715,6 +716,8 @@ float neb2_get_lod_scale(int objnum) ship *shipp; ship_info *sip; + // ADD PROP HERE + // bogus if ( (objnum < 0) || (objnum >= MAX_OBJECTS) @@ -1101,6 +1104,7 @@ void neb2_get_fog_values(float *fnear, float *ffar, object *objp) return; } + // ADD PROP HERE // determine what fog index to use if(objp->type == OBJ_SHIP) { Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS)); diff --git a/code/object/collidedebrisship.cpp b/code/object/collidedebrisship.cpp index e54acaa6db6..afc4d6c025b 100644 --- a/code/object/collidedebrisship.cpp +++ b/code/object/collidedebrisship.cpp @@ -23,6 +23,8 @@ #include "ship/ship.h" #include "ship/shiphit.h" +// Need a version of this for PROPS + void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_info); diff --git a/code/object/collidepropdebris.cpp b/code/object/collidepropdebris.cpp new file mode 100644 index 00000000000..928df3cb886 --- /dev/null +++ b/code/object/collidepropdebris.cpp @@ -0,0 +1 @@ +// STUB \ No newline at end of file diff --git a/code/object/collidepropship.cpp b/code/object/collidepropship.cpp new file mode 100644 index 00000000000..5b1ff7dfc63 --- /dev/null +++ b/code/object/collidepropship.cpp @@ -0,0 +1 @@ +//STUB \ No newline at end of file diff --git a/code/object/collidepropweapon.cpp b/code/object/collidepropweapon.cpp new file mode 100644 index 00000000000..928df3cb886 --- /dev/null +++ b/code/object/collidepropweapon.cpp @@ -0,0 +1 @@ +// STUB \ No newline at end of file diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index 68c6278fd06..e3a8cfc92f4 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -28,6 +28,8 @@ #include "ship/shiphit.h" #include "weapon/weapon.h" +// Need a version of this for PROPS + //mc, notify_ai_shield_down, shield_collision, quadrant_num, shield_tri_hit, shield_hitpoint using ship_weapon_collision_data = std::tuple, int, bool, int, int, vec3d>; diff --git a/code/object/objcollide.cpp b/code/object/objcollide.cpp index 5d65219fa04..121878cc0e2 100644 --- a/code/object/objcollide.cpp +++ b/code/object/objcollide.cpp @@ -429,6 +429,8 @@ int cpls_aux(vec3d *goal_pos, object *objp2, object *objp) return 0; } +// Maybe consider PROP here too + // Return true if objp will collide with some large object. // Don't check for an object this ship is docked to. int collide_predict_large_ship(object *objp, float distance) @@ -975,6 +977,27 @@ void obj_collide_pair(object *A, object *B) break; } + /* case COLLISION_OF(OBJ_SHIP, OBJ_PROP): + check_collision = collide_prop_ship; + break; + case COLLISION_OF(OBJ_PROP, OBJ_SHIP): + check_collision = collide_prop_ship; + swapped = 1; + break; + case COLLISION_OF(OBJ_WEAPON, OBJ_PROP): + check_collision = collide_prop_weapon; + break; + case COLLISION_OF(OBJ_PROP, OBJ_WEAPON): + check_collision = collide_prop_weapon; + swapped = 1; + break; + case COLLISION_OF(OBJ_DEBRIS, OBJ_PROP): + check_collision = collide_prop_debris; + break; + case COLLISION_OF(OBJ_PROP, OBJ_DEBRIS): + check_collision = collide_prop_debris; + swapped = 1; + break; */ default: return; diff --git a/code/object/objcollide.h b/code/object/objcollide.h index 4f3b851d310..2ae1dcbfc14 100644 --- a/code/object/objcollide.h +++ b/code/object/objcollide.h @@ -132,6 +132,15 @@ collision_result collide_ship_ship_check( obj_pair * pair ); void collide_mp_worker_thread(size_t threadIdx); +// Checks prop-ship collisions. +int collide_prop_ship(obj_pair* pair); + +// Checks prop-ship collisions. +int collide_prop_weapon(obj_pair* pair); + +// Checks prop-debris collisions +int collide_prop_debris(obj_pair* pair); + // Predictive functions. // Returns true if vector from curpos to goalpos with radius radius will collide with object goalobjp int pp_collide(vec3d *curpos, vec3d *goalpos, object *goalobjp, float radius); diff --git a/code/object/object.cpp b/code/object/object.cpp index 0fa507ff8e8..d9ded4fefe7 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -35,6 +35,7 @@ #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" @@ -279,6 +280,7 @@ int free_object_slots(int target_num_used) 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! @@ -760,6 +762,9 @@ void obj_delete(int objnum) 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; @@ -1185,7 +1190,7 @@ void obj_set_flags( object *obj, const flagset& new_flags if ( obj->type == OBJ_OBSERVER ) { return; } - + // Maybe add PROP here // sanity checks if ( (obj->type != OBJ_SHIP) || (obj->instance < 0) ) { return; // return because we really don't want to set the flag @@ -1269,6 +1274,9 @@ void obj_move_all_pre(object *objp, float frametime) break; case OBJ_RAW_POF: break; + case OBJ_PROP: + // Handle moving submodels maybe? + break; case OBJ_NONE: Int3(); break; @@ -1531,6 +1539,10 @@ void obj_move_all_post(object *objp, float frametime) case OBJ_RAW_POF: break; + case OBJ_PROP: + // Not sure if anything will be needed here + break; + case OBJ_NONE: Int3(); break; @@ -1680,6 +1692,7 @@ void obj_move_all(float frametime) // and look_at needs to happen last or the angle may be off by a frame) model_do_intrinsic_motions(objp); + // PROP probably will need something like this // 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); @@ -1948,6 +1961,9 @@ void obj_queue_render(object* obj, model_draw_list* scene) 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 ); } @@ -2054,6 +2070,7 @@ int obj_team(object *objp) case OBJ_SHOCKWAVE: case OBJ_BEAM: case OBJ_RAW_POF: + case OBJ_PROP: team = -1; break; @@ -2203,6 +2220,8 @@ int object_get_model_num(const object *objp) } case OBJ_RAW_POF: return Pof_objects[objp->instance].model_num; + case OBJ_PROP: + return Prop_info[objp->instance].model_num; default: break; } @@ -2257,6 +2276,8 @@ int object_get_model_instance_num(const object *objp) } case OBJ_RAW_POF: return Pof_objects[objp->instance].model_instance; + case OBJ_PROP: + return Props[objp->instance].model_instance_num; default: break; } diff --git a/code/object/object.h b/code/object/object.h index 21fa9b27c98..cef8b8d62bb 100644 --- a/code/object/object.h +++ b/code/object/object.h @@ -51,6 +51,7 @@ #define OBJ_JUMP_NODE 14 // A jump node object, used only in Fred. #define OBJ_BEAM 15 // beam weapons. we have to roll them into the object system to get the benefits of the collision pairs #define OBJ_RAW_POF 16 // A raw pof file. has no physics, ai or anything. Currently only used in the Lab to render tech models +#define OBJ_PROP 17 // A prop object like a landscape. Similar to ships but is entirely non-interactable with the exception of collisions //Make sure to change Object_type_names in Object.c when adding another type! #define MAX_OBJECT_TYPES 17 diff --git a/code/object/objectsort.cpp b/code/object/objectsort.cpp index f438936d6b6..9f0159afca2 100644 --- a/code/object/objectsort.cpp +++ b/code/object/objectsort.cpp @@ -22,6 +22,7 @@ #include "model/modelrender.h" #include "nebula/neb.h" #include "object/object.h" +#include "prop/prop.h" #include "scripting/scripting.h" #include "render/3d.h" #include "render/batching.h" @@ -75,6 +76,8 @@ inline bool sorted_obj::operator < (const sorted_obj &other) const model_num_a = Asteroid_info[asp->asteroid_type].subtypes[asp->asteroid_subtype].model_number; } else if (obj->type == OBJ_RAW_POF) { model_num_a = Pof_objects[obj->instance].model_num; + } else if (obj->type == OBJ_PROP) { + model_num_a = Props[obj->instance].model_instance_num; } if ( other.obj->type == OBJ_SHIP ) { @@ -101,6 +104,8 @@ inline bool sorted_obj::operator < (const sorted_obj &other) const model_num_b = Asteroid_info[asp->asteroid_type].subtypes[asp->asteroid_subtype].model_number; } else if (other.obj->type == OBJ_RAW_POF) { model_num_b = Pof_objects[other.obj->instance].model_num; + } else if (other.obj->type == OBJ_PROP) { + model_num_b = Props[other.obj->instance].model_instance_num; } if ( model_num_a == model_num_b ) { @@ -186,7 +191,8 @@ inline bool obj_render_is_model(object *obj) || obj->type == OBJ_ASTEROID || obj->type == OBJ_DEBRIS || obj->type == OBJ_JUMP_NODE - || obj->type == OBJ_RAW_POF; + || obj->type == OBJ_RAW_POF + || obj->type == OBJ_PROP; } // Are there reasons to hide objects base on distance? diff --git a/code/parse/parselo.cpp b/code/parse/parselo.cpp index 729838a305c..761b9e7cb74 100644 --- a/code/parse/parselo.cpp +++ b/code/parse/parselo.cpp @@ -828,6 +828,58 @@ int required_string_one_of(int arg_count, ...) return -1; } +int required_string_one_of_fred(int arg_count, ...) +{ + Assertion(arg_count > 0, "required_string_one_of_fred() called with arg_count of %d; get a coder!\n", arg_count); + + va_list vl; + int idx; + char* expected; + + ignore_white_space(); + + while (*Mp != '\0') { + va_start(vl, arg_count); + for (idx = 0; idx < arg_count; idx++) { + expected = va_arg(vl, char*); + if (strnicmp(expected, Mp, strlen(expected)) == 0) { + diag_printf("Found required string [%s]\n", token_found = expected); + va_end(vl); + return idx; + } + } + va_end(vl); + + advance_to_eoln(NULL); + ignore_white_space(); + } + + // EOF reached without finding any token + if (*Mp == '\0') { + SCP_string message = "Unable to find any required token: "; + + va_start(vl, arg_count); + for (idx = 0; idx < arg_count; idx++) { + expected = va_arg(vl, char*); + message += "["; + message += expected; + message += "]"; + if (arg_count == 2 && idx == 0) { + message += " or "; + } else if (idx == arg_count - 2) { + message += ", or "; + } else if (idx < arg_count - 2) { + message += ", "; + } + } + va_end(vl); + + diag_printf("%s\n", message.c_str()); + } + + return -1; +} + int required_string_either_fred(const char *str1, const char *str2) { ignore_white_space(); diff --git a/code/parse/parselo.h b/code/parse/parselo.h index f446e690645..362486ee145 100644 --- a/code/parse/parselo.h +++ b/code/parse/parselo.h @@ -392,6 +392,7 @@ SCP_vector> str_wrap_to_width(const char* source_strin extern int required_string_fred(const char *pstr, const char *end = NULL); extern int required_string_either_fred(const char *str1, const char *str2); extern int optional_string_fred(const char *pstr, const char *end = NULL, const char *end2 = NULL); +extern int required_string_one_of_fred(int arg_count, ...); // Goober5000 extern ptrdiff_t replace_one(char *str, const char *oldstr, const char *newstr, size_t max_len, ptrdiff_t range = 0); diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp new file mode 100644 index 00000000000..52b41582997 --- /dev/null +++ b/code/prop/prop.cpp @@ -0,0 +1,441 @@ +#include "prop.h" + +#include "model/model.h" +#include "parse/parselo.h" +#include "render/3d.h" + +#include "tracing/Monitor.h" + +MONITOR(NumPropsRend) + +SCP_vector Prop_info; + +SCP_vector Props; + +static SCP_vector Removed_props; + +/** + * Return the index of Prop_info[].name that is *token. + */ +static int prop_info_lookup_sub(const char* token) +{ + Assertion(token != nullptr, "NULL token passed to prop_info_lookup_sub"); + + for (auto it = Prop_info.cbegin(); it != Prop_info.cend(); ++it) + if (!stricmp(token, it->name)) + return (int)std::distance(Prop_info.cbegin(), it); + + return -1; +} + +int prop_name_lookup(const char *name) +{ + Assertion(name != nullptr, "NULL name passed to prop_name_lookup"); + + for (int i=0; i(Props.size()); i++){ + if (Props[i].objnum >= 0){ + if (Objects[Props[i].objnum].type == OBJ_PROP){ + if (!stricmp(name, Props[i].prop_name)){ + return i; + } + } + } + } + + // couldn't find it + return -1; +} + +void parse_prop_table(const char* filename) +{ + read_file_text(filename, CF_TYPE_TABLES); + reset_parse(); + + required_string("#PROPS"); + + while (optional_string("$Name:")) { + + char fname[NAME_LENGTH]; + prop_info* pip = nullptr; + bool first_time = false; + bool create_if_not_found = true; + bool remove_prop = false; + + stuff_string(fname, F_NAME, NAME_LENGTH); + + if (optional_string("+nocreate")) { + if (!Parsing_modular_table) { + Warning(LOCATION, "+nocreate flag used for prop in non-modular table"); + } + create_if_not_found = false; + } + + // check first if this is on the remove blacklist + auto it = std::find(Removed_props.begin(), Removed_props.end(), fname); + if (it != Removed_props.end()) { + remove_prop = true; + } + + // we might have a remove tag + if (optional_string("+remove")) { + if (!Parsing_modular_table) { + Warning(LOCATION, "+remove flag used for prop in non-modular table"); + } + if (!remove_prop) { + Removed_props.push_back(fname); + remove_prop = true; + } + } + + // Check if prop exists already + int prop_id = prop_info_lookup_sub(fname); + + // maybe remove it + if (remove_prop) { + if (prop_id >= 0) { + mprintf(("Removing previously parsed prop '%s'\n", fname)); + Prop_info.erase(Prop_info.begin() + prop_id); + } + + if (!skip_to_start_of_string_either("$Name:", "#End")) { + error_display(1, "Missing [#End] or [$Name] after prop class %s", fname); + } + continue; + } + + // an entry for this prop exists + if (prop_id >= 0) { + pip = &Prop_info[prop_id]; + if (!Parsing_modular_table) { + Warning(LOCATION, + "Error: Prop name %s already exists in %s. All prop class names must be unique.", + fname, + filename); + if (!skip_to_start_of_string_either("$Name:", "#End")) { + error_display(1, "Missing [#End] or [$Name] after duplicate prop class %s", fname); + } + continue; + } + } + // an entry does not exist + else { + // Don't create ship if it has +nocreate and is in a modular table. + if (!create_if_not_found && Parsing_modular_table) { + if (!skip_to_start_of_string_either("$Name:", "#End")) { + error_display(1, "Missing [#End] or [$Name] after prop class %s", fname); + } + continue; + } + + // Check if there are too many ship classes + //if (Prop_info.size() >= MAX_SHIP_CLASSES) { + //Error(LOCATION, "Too many prop classes before '%s'; maximum is %d.\n", fname, MAX_SHIP_CLASSES); + //} + + Prop_info.push_back(prop_info()); + pip = &Prop_info.back(); + first_time = true; + + strcpy_s(pip->name, fname); + } + + required_string("+POF file:"); + stuff_string(pip->pof_file, F_NAME, MAX_FILENAME_LEN); + + if(optional_string("$Detail distance:")) { + pip->num_detail_levels = (int)stuff_int_list(pip->detail_distance, MAX_PROP_DETAIL_LEVELS, RAW_INTEGER_TYPE); + } + } + + required_string("#END"); +} + +void prop_init() +{ + + // first parse the default table + parse_prop_table("props.tbl"); + + // parse any modular tables + parse_modular_table("*-prp.tbm", parse_prop_table); +} + +/** + * Returns object index of ship. + * @return -1 means failed. + */ +int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) +{ + prop_info* pip; + prop* propp; + + if (Props.size() >= MAX_PROPS) { + if (!Fred_running) { + Error(LOCATION, + "There is a limit of %d props in the mission at once. Please be sure that you do not have more " + "than %d props present in the mission at the same time.", + MAX_PROPS, + MAX_PROPS); + } + return -1; + } + + Assertion((prop_type >= 0) && (prop_type < static_cast(Prop_info.size())), + "Invalid prop_type %d passed to prop_create() (expected value in the range 0-%d)\n", + prop_type, + static_cast(Prop_info.size()) - 1); + + pip = &(Prop_info[prop_type]); + + Props.emplace_back(prop()); + propp = &Props.back(); + propp->prop_info_index = prop_type; + + if ((name == nullptr) || (prop_name_lookup(name) >= 0)) { + // regular name, regular suffix + char base_name[NAME_LENGTH]; + char suffix[NAME_LENGTH]; + strcpy_s(base_name, Prop_info[prop_type].name); + sprintf(suffix, NOX(" %d"), Props.size()); + + // start building name + strcpy_s(propp->prop_name, base_name); + + // if generated name will be longer than allowable name, truncate the class section of the name by the overflow + int char_overflow = static_cast(strlen(base_name) + strlen(suffix)) - (NAME_LENGTH - 1); + if (char_overflow > 0) { + propp->prop_name[strlen(base_name) - static_cast(char_overflow)] = '\0'; + } + + // complete building the name by adding suffix number + strcat_s(propp->prop_name, suffix); + + } else { + strcpy_s(propp->prop_name, name); + } + + if (!VALID_FNAME(pip->pof_file)) { + Error(LOCATION, "Cannot create prop %s; pof file is not valid", pip->name); + return -1; + } + pip->model_num = model_load(pip->pof_file); + + polymodel* pm = model_get(pip->model_num); + + if (pip->num_detail_levels != pm->n_detail_levels) { + if (!Is_standalone) { + // just log to file for standalone servers + Warning(LOCATION, + "For prop '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", + pip->name, + pip->num_detail_levels, + pm->n_detail_levels); + } else { + nprintf(("Warning", + "For prop '%s', detail level mismatch. Table has %d, POF has %d.", + pip->name, + pip->num_detail_levels, + pm->n_detail_levels)); + } + } + for (int i = 0; i < pm->n_detail_levels; i++) + pm->detail_depth[i] = (i < pip->num_detail_levels) ? i2fl(pip->detail_distance[i]) : 0.0f; + + flagset default_ship_object_flags; + default_ship_object_flags.set(Object::Object_Flags::Renders); + default_ship_object_flags.set(Object::Object_Flags::Physics); + // JAS: Nav buoys don't need to do collisions! + // G5K: Corrected to apply specifically for ships with the no-collide flag. (In retail, navbuoys already have this + // flag, so this doesn't break anything.) + default_ship_object_flags.set(Object::Object_Flags::Collides, !pip->flags[Prop::Info_Flags::No_collide]); + + int objnum = obj_create(OBJ_PROP, + -1, + static_cast(Props.size() - 1), + orient, + pos, + model_get_radius(pip->model_num), + default_ship_object_flags); + Assert(objnum >= 0); + + propp->objnum = objnum; + + propp->model_instance_num = model_create_instance(objnum, pip->model_num); + + // allocate memory for keeping glow point bank status (enabled/disabled) + { + bool val = true; // default value, enabled + + if (pm->n_glow_point_banks) + propp->glow_point_bank_active.resize(pm->n_glow_point_banks, val); + + // set any default off banks to off + for (int bank = 0; bank < pm->n_glow_point_banks; bank++) { + glow_point_bank_override* gpo = nullptr; + + SCP_unordered_map::iterator gpoi = pip->glowpoint_bank_override_map.find(bank); + if (gpoi != pip->glowpoint_bank_override_map.end()) { + gpo = (glow_point_bank_override*)pip->glowpoint_bank_override_map[bank]; + } + + if (gpo) { + if (gpo->default_off) { + propp->glow_point_bank_active[bank] = false; + } + } + } + } + + //animation::anim_set_initial_states(propp); + + // Start up stracking for this ship in multi. + //if (Game_mode & (GM_MULTIPLAYER)) { + //multi_rollback_ship_record_add_ship(objnum); + //} + + // Set time when ship is created + propp->create_time = timer_get_milliseconds(); + + //ship_make_create_time_unique(shipp); + + propp->time_created = Missiontime; + + return objnum; +} + +void prop_delete(object* obj) +{ + int num = obj->instance; + Assert(num >= 0 && num <= static_cast(Props.size())); + + int objnum = OBJ_INDEX(obj); + Assert(Props[num].objnum == objnum); + + prop* propp = &Props[num]; + + propp->objnum = -1; + + //animation::ModelAnimationSet::stopAnimations(model_get_instance(propp->model_instance_num)); + + // glow point banks + propp->glow_point_bank_active.clear(); + + model_delete_instance(propp->model_instance_num); + + Props.erase(Props.begin() + num); +} + +void prop_render(object* obj, model_draw_list* scene) +{ + int num = obj->instance; + Assert(num >= 0 && num <= static_cast(Props.size())); + + prop* propp = &Props[num]; + prop_info* pip = &Prop_info[Props[num].prop_info_index]; + + MONITOR_INC(NumPropsRend, 1); + + model_render_params render_info; + + auto pmi = model_get_instance(propp->model_instance_num); + auto pm = model_get(pmi->model_num); + + model_clear_instance(pip->model_num); + model_instance_clear_arcs(pm, pmi); + + uint64_t render_flags = MR_NORMAL; + + if (propp->flags[Prop::Prop_Flags::Render_with_alpha_mult]) { + render_info.set_alpha_mult(propp->alpha_mult); + } + + // Valathil - maybe do a scripting hook here to do some scriptable effects? + //ship_render_set_animated_effect(&render_info, shipp, &render_flags); + + if (pip->flags[Prop::Info_Flags::No_lighting]) { + render_flags |= MR_NO_LIGHTING; + } + + if (Rendering_to_shadow_map) { + render_flags = MR_NO_TEXTURING | MR_NO_LIGHTING; + } + + if (propp->flags[Prop::Prop_Flags::Glowmaps_disabled]) { + render_flags |= MR_NO_GLOWMAPS; + } + + if (propp->flags[Prop::Prop_Flags::Draw_as_wireframe]) { + render_flags |= MR_SHOW_OUTLINE_HTL | MR_NO_POLYS | MR_NO_TEXTURING; + render_info.set_color(Wireframe_color); + } + + if (propp->flags[Prop::Prop_Flags::Render_full_detail]) { + render_flags |= MR_FULL_DETAIL; + } + + if (propp->flags[Prop::Prop_Flags::Render_without_light]) { + render_flags |= MR_NO_LIGHTING; + } + + uint debug_flags = render_info.get_debug_flags(); + + if (propp->flags[Prop::Prop_Flags::Render_without_diffuse]) { + debug_flags |= MR_DEBUG_NO_DIFFUSE; + } + + if (propp->flags[Prop::Prop_Flags::Render_without_glowmap]) { + debug_flags |= MR_DEBUG_NO_GLOW; + } + + if (propp->flags[Prop::Prop_Flags::Render_without_normalmap]) { + debug_flags |= MR_DEBUG_NO_NORMAL; + } + + if (propp->flags[Prop::Prop_Flags::Render_without_ambientmap]) { + debug_flags |= MR_DEBUG_NO_AMBIENT; + } + + if (propp->flags[Prop::Prop_Flags::Render_without_specmap]) { + debug_flags |= MR_DEBUG_NO_SPEC; + } + + if (propp->flags[Prop::Prop_Flags::Render_without_reflectmap]) { + debug_flags |= MR_DEBUG_NO_REFLECT; + } + + render_info.set_flags(render_flags); + render_info.set_debug_flags(debug_flags); + + render_info.set_object_number(OBJ_INDEX(obj)); + render_info.set_replacement_textures(pmi->texture_replace); + + model_render_queue(&render_info, scene, pip->model_num, &obj->orient, &obj->pos); +} + +void spawn_test_prop() +{ + int prop_idx = prop_info_lookup_sub("TestProp"); // whatever’s in your props.tbl + if (prop_idx < 0) { + mprintf(("TEST: Prop not found!\n")); + return; + } + + matrix mtx = vmd_identity_matrix; + vec3d pos = ZERO_VECTOR; + pos.xyz.z = -2000.0f; + + int objnum = prop_create(&mtx, &pos, prop_idx, "Test Prop"); + + if (objnum >= 0) { + mprintf(("TEST: Spawned prop '%s' at objnum %d\n", Prop_info[prop_idx].name, objnum)); + } else { + mprintf(("TEST: Failed to create prop\n")); + } +} + +void props_level_close() +{ + while (!Props.empty()) { + prop_delete(&Objects[Props.back().objnum]); + } +} \ No newline at end of file diff --git a/code/prop/prop.h b/code/prop/prop.h new file mode 100644 index 00000000000..4bdb51964fe --- /dev/null +++ b/code/prop/prop.h @@ -0,0 +1,57 @@ +#pragma once + +#include "globalincs/pstypes.h" + +#include "object/object.h" +#include "prop/prop_flags.h" +#include "ship/ship.h" + +#define MAX_PROP_DETAIL_LEVELS MAX_SHIP_DETAIL_LEVELS + +typedef struct prop_info { + char name[NAME_LENGTH]; // Prop name + char pof_file[MAX_FILENAME_LEN]; // Pof filename + int model_num; // The model number of the loaded POF + //int model_instance; // The model instance + int num_detail_levels; // Detail levels of the model + int detail_distance[MAX_PROP_DETAIL_LEVELS]; // distance to change detail levels at + SCP_unordered_map glowpoint_bank_override_map; + flagset flags; // Info flags +} prop_info; + +typedef struct prop { + char prop_name[NAME_LENGTH]; + int objnum; + int prop_info_index; + int model_instance_num; + uint create_time; // time prop was created, set by gettime() + fix time_created; + float alpha_mult; + // glow points + std::deque glow_point_bank_active; + flagset flags; // Render flags +} prop; + +typedef struct parsed_prop { + char name[NAME_LENGTH]; + int prop_info_index; + matrix orientation; + vec3d position; +} parsed_prop; + +// Global prop info array +extern SCP_vector Prop_info; + +extern SCP_vector Props; + +// Load all props from table +void prop_init(); + +// Object management +int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* ship_name = nullptr); +void prop_delete(object* obj); +void prop_render(object* obj, model_draw_list* scene); + +void props_level_close(); + +void spawn_test_prop(); \ No newline at end of file diff --git a/code/prop/prop_flags.h b/code/prop/prop_flags.h new file mode 100644 index 00000000000..e0de9be515d --- /dev/null +++ b/code/prop/prop_flags.h @@ -0,0 +1,33 @@ +#ifndef PROP_FLAGS_H +#define PROP_FLAGS_H + +#include "globalincs/flagset.h" + +namespace Prop { + +FLAG_LIST(Prop_Flags){ + Glowmaps_disabled, // No glowmaps for this weapon instance + Draw_as_wireframe, // Render wireframe for this weapon instance + Render_full_detail, // Render full detail for this weapon instance + Render_without_light, // Render without light for this weapon instance + Render_without_diffuse, // Render without diffuse for this weapon instance + Render_without_glowmap, // Render without glowmap for this weapon instance + Render_without_normalmap, // Render without normal map for this weapon instance + Render_without_heightmap, // Render without height map for this weapon instance + Render_without_ambientmap, // Render without ambient for this weapon instance + Render_without_specmap, // Render without spec for this weapon instance + Render_without_reflectmap, // Render without reflect for this weapon instance + Render_with_alpha_mult, // Render with an alpha multiplier + + NUM_VALUES}; + +FLAG_LIST(Info_Flags){ + No_collide = 0, // No collisions + No_fred, // not available in fred + No_impact_debris, // wookieejedi - Don't spawn the small debris on impact + No_lighting, + + NUM_VALUES}; + +} // namespace Prop +#endif diff --git a/code/scripting/api/objs/object.cpp b/code/scripting/api/objs/object.cpp index 11092efe3ad..014b70e7d94 100644 --- a/code/scripting/api/objs/object.cpp +++ b/code/scripting/api/objs/object.cpp @@ -105,6 +105,9 @@ ADE_FUNC(__tostring, l_Object, NULL, "Returns name of object (if any)", "string" case OBJ_BEAM: sprintf(buf, "%s beam", Weapon_info[Beams[objh->objp()->instance].weapon_info_index].name); break; + case OBJ_PROP: + sprintf(buf, "%s prop", "TEMP"); + break; default: sprintf(buf, "object num=%d sig=%d type=%d instance=%d", objh->objnum, objh->sig, objh->objp()->type, objh->objp()->instance); break; @@ -540,6 +543,9 @@ ADE_FUNC( model_num = Asteroid_info[Asteroids[obj->instance].asteroid_type].subtypes[temp].model_number; flags = (MC_CHECK_MODEL | MC_CHECK_RAY); break; + case OBJ_PROP: + // do this + break; default: return ADE_RETURN_NIL; } @@ -566,6 +572,8 @@ ADE_FUNC( model_instance_num = Weapons[obj->instance].model_instance_num; } else if (obj->type == OBJ_ASTEROID) { model_instance_num = Asteroids[obj->instance].model_instance_num; + } else if (obj->type == OBJ_PROP) { + model_instance_num = -1; // YOUR MOM } mc_info hull_check; diff --git a/code/ship/shiphit.cpp b/code/ship/shiphit.cpp index 9006bb20333..e74af316329 100755 --- a/code/ship/shiphit.cpp +++ b/code/ship/shiphit.cpp @@ -1194,6 +1194,10 @@ static void shiphit_record_player_killer(const object *killer_objp, player *p) strcpy_s(p->killer_parent_name, ""); } break; + + case OBJ_PROP: + // handle this + break; case OBJ_NONE: if ( Game_mode & GM_MULTIPLAYER ) { @@ -1352,6 +1356,8 @@ void ship_hit_sparks_no_rotate(object *ship_objp, vec3d *hitpos) } } +// Need a prop version? Maybe, maybe not + // find the max number of sparks allowed for ship // limited for fighter by hull % others by radius. int get_max_sparks(const object* ship_objp) @@ -1612,6 +1618,9 @@ static void player_died_start(const object *killer_objp) other_objp = &Objects[beam_obj_parent]; } break; + case OBJ_PROP: + //hmmmmmm + break; default: UNREACHABLE("Unhandled object type %d in player_died_start()", killer_objp->type); // Killed by an object of a peculiar type. What is it? diff --git a/code/source_groups.cmake b/code/source_groups.cmake index 09ca8bbb330..738bf797a3e 100644 --- a/code/source_groups.cmake +++ b/code/source_groups.cmake @@ -1013,6 +1013,9 @@ ENDIF(WIN32) add_file_folder("Object" object/collidedebrisship.cpp object/collidedebrisweapon.cpp + object/collidepropdebris.cpp + object/collidepropship.cpp + object/collidepropweapon.cpp object/collideshipship.cpp object/collideshipweapon.cpp object/collideweaponweapon.cpp @@ -1210,6 +1213,13 @@ add_file_folder("Popup" popup/popupdead.h ) +# Prop files +add_file_folder("Prop" + prop/prop.cpp + prop/prop.h + prop/prop_flags.h +) + # Radar files add_file_folder("Radar" radar/radar.cpp diff --git a/fred2/fred.rc b/fred2/fred.rc index a548547acf6..3e6192b4ce1 100644 --- a/fred2/fred.rc +++ b/fred2/fred.rc @@ -192,6 +192,8 @@ BEGIN BUTTON ID_LOOKAT_OBJ SEPARATOR BUTTON ID_NEW_SHIP_TYPE + SEPARATOR + BUTTON ID_NEW_PROP_TYPE END diff --git a/fred2/fredrender.cpp b/fred2/fredrender.cpp index 2c91d00d849..2d4a590cce6 100644 --- a/fred2/fredrender.cpp +++ b/fred2/fredrender.cpp @@ -45,6 +45,7 @@ #include "mod_table/mod_table.h" #include "object/object.h" #include "physics/physics.h" +#include "prop/prop.h" #include "render/3d.h" #include "render/3dinternal.h" #include "ship/ship.h" @@ -541,6 +542,14 @@ void display_ship_info() { } else if (objp->type == OBJ_JUMP_NODE) { CJumpNode* jnp = jumpnode_get_by_objnum(OBJ_INDEX(objp)); sprintf(buf, "%s\n%s", jnp->GetName(), jnp->GetDisplayName()); + } else if (objp->type == OBJ_PROP) { + prop* propp; + int prop_type; + + propp = &Props[objp->instance]; + prop_type = propp->prop_info_index; + ASSERT(prop_type >= 0); + sprintf(buf, "%s\n", propp->prop_name); } else Assert(0); } @@ -1322,6 +1331,8 @@ int object_check_collision(object *objp, vec3d *p0, vec3d *p1, vec3d *hitpos) { return 0; } + // PROP HERE? + if (objp->flags[Object::Object_Flags::Hidden, Object::Object_Flags::Locked_from_editing]) return 0; @@ -1849,10 +1860,36 @@ void render_one_model_htl(object *objp) { } else if ((objp->type == OBJ_START) && Show_outlines) { Fred_outline = 0x007f00; - } else Fred_outline = 0; + if (objp->type == OBJ_PROP) { + uint64_t flags = MR_NORMAL; + + if (!Lighting_on) { + flags |= MR_NO_LIGHTING; + } + + if (FullDetail) { + flags |= MR_FULL_DETAIL; + } + + z = objp->instance; + + model_render_params render_info; + + render_info.set_debug_flags(debug_flags); + + if (Fred_outline) { + render_info.set_color(Fred_outline >> 16, (Fred_outline >> 8) & 0xff, Fred_outline & 0xff); + render_info.set_flags(flags | MR_SHOW_OUTLINE_HTL | MR_NO_LIGHTING | MR_NO_POLYS | MR_NO_TEXTURING); + model_render_immediate(&render_info, Prop_info[Props[z].prop_info_index].model_num, Props[z].model_instance_num, &objp->orient, &objp->pos); + } + // + render_info.set_flags(flags); + model_render_immediate(&render_info, Prop_info[Props[z].prop_info_index].model_num, Props[z].model_instance_num, &objp->orient, &objp->pos); + } + // build flags if ((Show_ship_models || Show_outlines) && ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START))) { g3_start_instance_matrix(&Eye_position, &Eye_matrix, 0); @@ -1883,7 +1920,7 @@ void render_one_model_htl(object *objp) { render_info.set_debug_flags(debug_flags); render_info.set_replacement_textures(model_get_instance(Ships[z].model_instance_num)->texture_replace); - + if (Fred_outline) { render_info.set_color(Fred_outline >> 16, (Fred_outline >> 8) & 0xff, Fred_outline & 0xff); render_info.set_flags(flags | MR_SHOW_OUTLINE_HTL | MR_NO_LIGHTING | MR_NO_POLYS | MR_NO_TEXTURING); @@ -1942,6 +1979,10 @@ void render_one_model_htl(object *objp) { } r = 196; g = 32; b = 196; + } else if (objp->type == OBJ_PROP) { + r = 255; + g = 255; + b = 255; } else Assert(0); diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp index 81b9ae2d343..3bed33b275d 100644 --- a/fred2/fredview.cpp +++ b/fred2/fredview.cpp @@ -966,23 +966,31 @@ void CFREDView::OnLButtonDown(UINT nFlags, CPoint point) drag_rotate_save_backup(); if (nFlags & MK_CONTROL) { // add a new object - if (!Bg_bitmap_dialog) { - if (on_object == -1) { - Selection_lock = 0; // force off selection lock - on_object = create_object_on_grid(waypoint_instance); - - } else - Dup_drag = 1; + bool shift_down = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; + if (shift_down) { + Selection_lock = 0; // force off selection lock + on_object = create_object_on_grid(waypoint_instance, true); } else { - /* - Selection_lock = 0; // force off selection lock - on_object = Cur_bitmap = create_bg_bitmap(); - Bg_bitmap_dialog->update_data(); - Update_window = 1; - if (Cur_bitmap == -1) - MessageBox("Background bitmap limit reached.\nCan't add more."); - */ + + if (!Bg_bitmap_dialog) { + if (on_object == -1) { + Selection_lock = 0; // force off selection lock + on_object = create_object_on_grid(waypoint_instance); + + } else + Dup_drag = 1; + + } else { + /* + Selection_lock = 0; // force off selection lock + on_object = Cur_bitmap = create_bg_bitmap(); + Bg_bitmap_dialog->update_data(); + Update_window = 1; + if (Cur_bitmap == -1) + MessageBox("Background bitmap limit reached.\nCan't add more."); + */ + } } } else if (!Selection_lock) { @@ -2585,7 +2593,7 @@ int CFREDView::global_error_check() //Shouldn't be needed anymore. //If we really do need it, call me and I'll write a is_valid function for jumpnodes -WMC } - else if (ptr->type == OBJ_JUMP_NODE) + else if (ptr->type == OBJ_JUMP_NODE || ptr->type == OBJ_PROP) { //nothing needs to be done here, we just need to make sure the else doesn't occur } diff --git a/fred2/mainfrm.cpp b/fred2/mainfrm.cpp index 846fa840ebb..910ae439ad9 100644 --- a/fred2/mainfrm.cpp +++ b/fred2/mainfrm.cpp @@ -22,6 +22,7 @@ #include "species_defs/species_defs.h" #include "iff_defs/iff_defs.h" +#include "prop/prop.h" #ifdef _DEBUG #undef THIS_FILE @@ -76,6 +77,8 @@ static UINT indicators[] = CMainFrame *Fred_main_wnd; color_combo_box m_new_ship_type_combo_box; size_t ship_type_combo_box_size = 0; +color_combo_box_prop m_new_prop_type_combo_box; +size_t prop_type_combo_box_size = 0; int Toggle1_var = 0; CPoint Global_point2; @@ -123,6 +126,16 @@ void CMainFrame::init_tools() } } + for (auto it = Prop_info.begin(); it != Prop_info.end(); ++it) { + if (it->flags[Prop::Info_Flags::No_fred]) { + continue; + } else { + m_new_prop_type_combo_box.AddString(it->name); + m_new_prop_type_combo_box.SetItemData((int)prop_type_combo_box_size, std::distance(Prop_info.begin(), it)); + prop_type_combo_box_size++; + } + } + Id_select_type_waypoint = ship_type_combo_box_size; Id_select_type_jump_node = ship_type_combo_box_size + 1; @@ -144,6 +157,7 @@ void CMainFrame::init_tools() } */ m_new_ship_type_combo_box.SetCurSel(0); + m_new_prop_type_combo_box.SetCurSel(0); } void CMainFrame::OnClose() @@ -164,7 +178,7 @@ void CMainFrame::OnClose() } int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { - int z; + int z, zz; CRect rect; if (CFrameWnd::OnCreate(lpCreateStruct) == -1) @@ -176,7 +190,18 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { return -1; // fail to create } - // Create the combo box + // Add static label for "Objects" + int ship_label_index = m_wndToolBar.CommandToIndex(ID_NEW_SHIP_TYPE) - 1; + m_wndToolBar.SetButtonInfo(ship_label_index, ID_STATIC_SHIP_LABEL, TBBS_SEPARATOR, 70); + m_wndToolBar.GetItemRect(ship_label_index, &rect); + rect.top = 3; + rect.bottom = rect.top + 20; + if (!m_ship_label.Create(_T("Objects"), WS_VISIBLE | WS_CHILD, rect, &m_wndToolBar)) { + TRACE0("Failed to create 'Objects' label\n"); + return -1; + } + + // Create the objects combo box z = m_wndToolBar.CommandToIndex(ID_NEW_SHIP_TYPE); Assert(z != -1); m_wndToolBar.SetButtonInfo(z, ID_NEW_SHIP_TYPE, TBBS_SEPARATOR, 230); @@ -192,6 +217,33 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { return FALSE; } + // Add static label for "Props" + int prop_label_index = m_wndToolBar.CommandToIndex(ID_NEW_PROP_TYPE) - 1; + m_wndToolBar.SetButtonInfo(prop_label_index, ID_STATIC_PROP_LABEL, TBBS_SEPARATOR, 60); + m_wndToolBar.GetItemRect(prop_label_index, &rect); + rect.top = 3; + rect.bottom = rect.top + 20; + if (!m_prop_label.Create(_T("Props"), WS_VISIBLE | WS_CHILD, rect, &m_wndToolBar)) { + TRACE0("Failed to create 'Props' label\n"); + return -1; + } + + // Create the props combo box + zz = m_wndToolBar.CommandToIndex(ID_NEW_PROP_TYPE); + Assert(zz != -1); + m_wndToolBar.SetButtonInfo(zz, ID_NEW_PROP_TYPE, TBBS_SEPARATOR, 230); + + // Design guide advises 12 pixel gap between combos and buttons + // m_wndToolBar.SetButtonInfo(1, ID_SEPARATOR, TBBS_SEPARATOR, 12); + m_wndToolBar.GetItemRect(zz, &rect); + rect.top = 3; + rect.bottom = rect.top + 550; + if (!m_new_prop_type_combo_box.Create(CBS_DROPDOWNLIST | WS_VISIBLE | WS_VSCROLL | CBS_HASSTRINGS | LBS_OWNERDRAWFIXED, + rect, &m_wndToolBar, ID_NEW_PROP_TYPE)) { + TRACE0("Failed to create new prop type combo-box\n"); + return FALSE; + } + /* if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) @@ -368,6 +420,112 @@ BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { } +int color_combo_box_prop::CalcMinimumItemHeight() +{ + int nResult = 1; + + if ((GetStyle() & (LBS_HASSTRINGS | LBS_OWNERDRAWFIXED)) == (LBS_HASSTRINGS | LBS_OWNERDRAWFIXED)) { + CClientDC dc(this); + CFont* pOldFont = dc.SelectObject(GetFont()); + TEXTMETRIC tm; + VERIFY(dc.GetTextMetrics(&tm)); + dc.SelectObject(pOldFont); + + nResult = tm.tmHeight; + } + + return nResult; +} + +void color_combo_box_prop::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + int m_cyText = 24; + CString strText; + + // You must override DrawItem and MeasureItem for LBS_OWNERDRAWVARIABLE + ASSERT((GetStyle() & (LBS_OWNERDRAWFIXED | CBS_HASSTRINGS)) == (LBS_OWNERDRAWFIXED | CBS_HASSTRINGS)); + + CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); + + if ((lpDrawItemStruct->itemID >= 0) && (lpDrawItemStruct->itemAction & (ODA_DRAWENTIRE | ODA_SELECT))) { + prop_info* pip = nullptr; + + // get the ship class corresponding to this item, if any + auto itemData = lpDrawItemStruct->itemData; + if (itemData >= 0 && itemData < Prop_info.size()) { + pip = &Prop_info[itemData]; + } + + int cyItem = GetItemHeight(lpDrawItemStruct->itemID); + BOOL fDisabled = !IsWindowEnabled(); + + COLORREF newTextColor = RGB(0x80, 0x80, 0x80); // light gray + if (!fDisabled && pip == nullptr) { + newTextColor = RGB(0, 0, 0); + } + + COLORREF oldTextColor = pDC->SetTextColor(newTextColor); + COLORREF newBkColor = GetSysColor(COLOR_WINDOW); + COLORREF oldBkColor = pDC->SetBkColor(newBkColor); + + if (newTextColor == newBkColor) { + newTextColor = RGB(0xC0, 0xC0, 0xC0); // dark gray + } + + if (!fDisabled && ((lpDrawItemStruct->itemState & ODS_SELECTED) != 0)) { + pDC->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT)); + pDC->SetBkColor(GetSysColor(COLOR_HIGHLIGHT)); + } + + if (m_cyText == 0) { + VERIFY(cyItem >= CalcMinimumItemHeight()); + } + + if (pip != nullptr) { + strText = _T(pip->name); + } else { + strText = _T("Invalid index!"); + } + + pDC->ExtTextOut(lpDrawItemStruct->rcItem.left, + lpDrawItemStruct->rcItem.top + std::max(0, (cyItem - m_cyText) / 2), + ETO_OPAQUE, + &(lpDrawItemStruct->rcItem), + strText, + strText.GetLength(), + NULL); + + pDC->SetTextColor(oldTextColor); + pDC->SetBkColor(oldBkColor); + } + + if ((lpDrawItemStruct->itemAction & ODA_FOCUS) != 0) { + pDC->DrawFocusRect(&(lpDrawItemStruct->rcItem)); + } +} + +void color_combo_box_prop::MeasureItem(LPMEASUREITEMSTRUCT) +{ + // You must override DrawItem and MeasureItem for LBS_OWNERDRAWVARIABLE + ASSERT((GetStyle() & (LBS_OWNERDRAWFIXED | CBS_HASSTRINGS)) == (LBS_OWNERDRAWFIXED | CBS_HASSTRINGS)); +} + +int color_combo_box_prop::GetPropClass(int item_index) +{ + if (item_index < 0 || item_index >= GetCount()) + return -1; + return (int)GetItemData(item_index); +} + +int color_combo_box_prop::GetItemIndex(int prop_class) +{ + for (int i = 0; i < m_new_prop_type_combo_box.GetCount(); i++) + if ((int)m_new_prop_type_combo_box.GetItemData(i) == prop_class) + return i; + return -1; +} + + int color_combo_box::CalcMinimumItemHeight() { int nResult = 1; diff --git a/fred2/mainfrm.h b/fred2/mainfrm.h index 3ad74be4c34..db076a1b473 100644 --- a/fred2/mainfrm.h +++ b/fred2/mainfrm.h @@ -13,6 +13,40 @@ #define WM_MENU_POPUP_TEST (WM_USER+9) +class color_combo_box_prop : public CComboBox { + public: + /** + * Gets the ship class corresponding to the item index + */ + int GetPropClass(int item_index); + + /** + * Gets the item index corresponding to the ship class + */ + int GetItemIndex(int ship_class); + + private: + /** + * @brief Draws the given item + * + * @param[in] lpDrawItemStruct Item to draw + */ + void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + + /** + * @brief Calculates the minimum height required to fit all items. This is determined by the "tallest" character + */ + int CalcMinimumItemHeight(); + + /** + * @brief Measures the given item + * + * @details Er, actually does nothing other than Assert that the style of the combo box is LBS_ONWERDRAWFIXED and + * CBS_HASSTRINGS + */ + void MeasureItem(LPMEASUREITEMSTRUCT); +}; + /** * @class color_combo_box * @@ -203,4 +237,10 @@ extern CMainFrame *Fred_main_wnd; //!< The main FRED window extern color_combo_box m_new_ship_type_combo_box; //!< The combo box extern size_t ship_type_combo_box_size; +extern color_combo_box_prop m_new_prop_type_combo_box; +extern size_t prop_type_combo_box_size; + +CStatic m_ship_label; +CStatic m_prop_label; + #endif // _MAINFRM_H diff --git a/fred2/management.cpp b/fred2/management.cpp index 534a7dc22a4..c1a10a41b7e 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -73,6 +73,7 @@ #include "scripting/scripting.h" #include "scripting/global_hooks.h" #include "utils/Random.h" +#include "prop/prop.h" #include #include "cmdline/cmdline.h" @@ -155,7 +156,8 @@ int common_object_delete(int obj); int create_waypoint(vec3d *pos, int waypoint_instance); int create_ship(matrix *orient, vec3d *pos, int ship_type); int query_ship_name_duplicate(int ship); -char *reg_read_string( char *section, char *name, char *default_value ); +int query_prop_name_duplicate(int prop); +char* reg_read_string(char* section, char* name, char* default_value); // Note that the parameter here is max *length*, not max buffer size. Leave room for the null-terminator! void string_copy(char *dest, const CString &src, size_t max_len, bool modify) @@ -381,6 +383,7 @@ bool fred_init(std::unique_ptr&& graphicsOps) weapon_init(); glowpoint_init(); ship_init(); + prop_init(); techroom_intel_init(); hud_positions_init(); @@ -481,7 +484,7 @@ void set_physics_controls() theApp.write_ini_file(1); } -int create_object_on_grid(int waypoint_instance) +int create_object_on_grid(int waypoint_instance, bool prop) { int obj = -1; float rval; @@ -493,18 +496,27 @@ int create_object_on_grid(int waypoint_instance) if (rval>=0.0f) { unmark_all(); - obj = create_object(&pos, waypoint_instance); + obj = create_object(&pos, waypoint_instance, prop); if (obj >= 0) { mark_object(obj); FREDDoc_ptr->autosave("object create"); } else if (obj == -1) - Fred_main_wnd->MessageBox("Maximum ship limit reached. Can't add any more ships."); + Fred_main_wnd->MessageBox("Maximum object limit reached. Can't add any more object."); } return obj; } +void fix_prop_name(int prop) +{ + int i = 1; + + do { + sprintf(Props[prop].prop_name, "U.R.A. Dummy %d", i++); + } while (query_prop_name_duplicate(prop)); +} + void fix_ship_name(int ship) { int i = 1; @@ -600,6 +612,43 @@ int create_ship(matrix *orient, vec3d *pos, int ship_type) return obj; } +int create_prop(matrix* orient, vec3d* pos, int prop_type) +{ + // Save the Current Working dir to restore in a minute - fred is being stupid + char pwd[MAX_PATH_LEN]; + getcwd(pwd, MAX_PATH_LEN); // get the present working dir - probably [/modpath]/data/missions/ + + // "pop" and cfile_chdirs off the stack + chdir(Fred_base_dir); + + int obj = prop_create(orient, pos, prop_type); + if (obj == -1) + return -1; + + // ok, done with file io, restore the pwd + chdir(pwd); + + prop* propp = &Props[Objects[obj].instance]; + prop_info* pip = &Prop_info[propp->prop_info_index]; + + if (query_prop_name_duplicate(Objects[obj].instance)) + fix_prop_name(Objects[obj].instance); + + return obj; +} + +int query_prop_name_duplicate(int prop) +{ + int i; + + for (i = 0; i < static_cast(Props.size()); i++) + if ((i != prop) && (Props[i].objnum != -1)) + if (!stricmp(Props[i].prop_name, Props[prop].prop_name)) + return 1; + + return 0; +} + int query_ship_name_duplicate(int ship) { int i; @@ -704,29 +753,40 @@ int dup_object(object *objp) return obj; } -int create_object(vec3d *pos, int waypoint_instance) +int create_object(vec3d *pos, int waypoint_instance, bool prop) { int obj, n; - if (cur_ship_type_combo_index == static_cast(Id_select_type_waypoint)) { - obj = create_waypoint(pos, waypoint_instance); - } else if (cur_ship_type_combo_index == static_cast(Id_select_type_jump_node)) { - CJumpNode jnp(pos); - obj = jnp.GetSCPObjectNumber(); - Jump_nodes.push_back(std::move(jnp)); - } else { // creating a ship - int ship_class = m_new_ship_type_combo_box.GetShipClass(cur_ship_type_combo_index); - if (ship_class < 0 || ship_class >= ship_info_size()) + if (prop) { + int prop_class = m_new_prop_type_combo_box.GetCurSel(); + if (prop_class < 0 || prop_class >= ship_info_size()) return -1; - obj = create_ship(nullptr, pos, ship_class); + obj = create_prop(nullptr, pos, prop_class); if (obj == -1) return -1; - - n = Objects[obj].instance; - Ships[n].arrival_cue = alloc_sexp("true", SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1); - Ships[n].departure_cue = alloc_sexp("false", SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1); - Ships[n].cargo1 = 0; + } else { + + if (cur_ship_type_combo_index == static_cast(Id_select_type_waypoint)) { + obj = create_waypoint(pos, waypoint_instance); + } else if (cur_ship_type_combo_index == static_cast(Id_select_type_jump_node)) { + CJumpNode jnp(pos); + obj = jnp.GetSCPObjectNumber(); + Jump_nodes.push_back(std::move(jnp)); + } else { // creating a ship + int ship_class = m_new_ship_type_combo_box.GetShipClass(cur_ship_type_combo_index); + if (ship_class < 0 || ship_class >= ship_info_size()) + return -1; + + obj = create_ship(nullptr, pos, ship_class); + if (obj == -1) + return -1; + + n = Objects[obj].instance; + Ships[n].arrival_cue = alloc_sexp("true", SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1); + Ships[n].departure_cue = alloc_sexp("false", SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1); + Ships[n].cargo1 = 0; + } } if (obj < 0) diff --git a/fred2/management.h b/fred2/management.h index 00867f99507..81cd7ffb4a4 100644 --- a/fred2/management.h +++ b/fred2/management.h @@ -63,8 +63,8 @@ CString get_display_name_for_text_box(const char *orig_name); bool fred_init(std::unique_ptr&& graphicsOps); void set_physics_controls(); int dup_object(object* objp); -int create_object_on_grid(int waypoint_instance = -1); -int create_object(vec3d* pos, int waypoint_instance = -1); +int create_object_on_grid(int waypoint_instance = -1, bool prop = false); +int create_object(vec3d* pos, int waypoint_instance = -1, bool prop = false); int create_player(vec3d* pos, matrix* orient, int type = -1); void create_new_mission(); void reset_mission(); diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index a7a0fff94fe..eef37773b3b 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -50,6 +50,7 @@ #include "osapi/osapi.h" #include "parse/sexp.h" #include "parse/sexp_container.h" +#include "prop/prop.h" #include "sound/ds.h" #include "sound/sound.h" #include "starfield/nebula.h" @@ -3270,6 +3271,8 @@ void CFred_mission_save::save_mission_internal(const char *pathname) err = -7; else if (save_wings()) err = -8; + else if (save_props()) + err = -18; else if (save_events()) err = -9; else if (save_goals()) @@ -5006,7 +5009,7 @@ int CFred_mission_save::save_wings() continue; count++; - required_string_either_fred("$Name:", "#Events"); + required_string_one_of_fred(3, "$Name:", "#Events", "#Props"); required_string_fred("$Name:"); parse_comments(2); fout(" %s", Wings[i].name); @@ -5271,3 +5274,38 @@ int CFred_mission_save::save_wings() Assert(count == Num_wings); return err; } + +int CFred_mission_save::save_props() +{ + if (Mission_save_format != FSO_FORMAT_RETAIL) { + fred_parse_flag = 0; + required_string_fred("#Props"); + parse_comments(2); + fout("\t\t;! %d total", static_cast(Props.size())); + + for (int i = 0; i < static_cast(Props.size()); i++) { + required_string_either_fred("$Name:", "#Events"); + required_string_fred("$Name:"); + parse_comments(2); + fout(" %s", Props[i].prop_name); + + required_string_fred("$Class:"); + parse_comments(2); + fout(" %d", Props[i].prop_info_index); + + required_string_fred("$Location:"); + parse_comments(); + save_vector(Objects[Props[i].objnum].pos); + + required_string_fred("$Orientation:"); + parse_comments(); + save_matrix(Objects[Props[i].objnum].orient); + + fso_comment_pop(); + } + } + + fso_comment_pop(true); + //Assert(count == Num_props); + return err; +} diff --git a/fred2/missionsave.h b/fred2/missionsave.h index 9cdcde7a4fe..f2656282a68 100644 --- a/fred2/missionsave.h +++ b/fred2/missionsave.h @@ -505,6 +505,17 @@ class CFred_mission_save */ int save_wings(); + /** + * @brief Saves the prop entries to file + * + * @details Returns the value of CFred_mission_save::err, which is: + * + * @returns 0 for no error, or + * @returns A negative value if an error occurred + */ + int save_props(); + + char *raw_ptr; /** * @brief Utility function to save a raw comment, the start of which precedes the current raw_ptr, to a file while handling newlines properly */ diff --git a/fred2/resource.h b/fred2/resource.h index 36ebdff5399..9c286ffd139 100644 --- a/fred2/resource.h +++ b/fred2/resource.h @@ -1445,6 +1445,9 @@ #define ID_EDITORS_WAYPOINT 32979 #define ID_VIEW_OUTLINES 32980 #define ID_NEW_SHIP_TYPE 32981 +#define ID_NEW_PROP_TYPE 33104 +#define ID_STATIC_SHIP_LABEL 33105 +#define ID_STATIC_PROP_LABEL 33106 #define ID_VIEW_OUTLINES_ON_SELECTED 32982 #define ID_SHOW_STARFIELD 32983 #define ID_ASTEROID_EDITOR 32984 diff --git a/freespace2/freespace.cpp b/freespace2/freespace.cpp index f4b4cf1e9e1..666b25b5ede 100644 --- a/freespace2/freespace.cpp +++ b/freespace2/freespace.cpp @@ -165,6 +165,7 @@ #include "playerman/player.h" #include "popup/popup.h" #include "popup/popupdead.h" +#include "prop/prop.h" #include "radar/radar.h" #include "radar/radarsetup.h" #include "render/3d.h" @@ -2022,6 +2023,7 @@ void game_init() weapon_init(); glowpoint_init(); ship_init(); // read in ships.tbl + prop_init(); player_init(); mission_campaign_init(); // load in the default campaign diff --git a/qtfred/src/mission/management.cpp b/qtfred/src/mission/management.cpp index 1efac46f8d5..45b935cf8c2 100644 --- a/qtfred/src/mission/management.cpp +++ b/qtfred/src/mission/management.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -218,6 +219,9 @@ initialize(const std::string& cfilepath, int argc, char* argv[], Editor* editor, listener(SubSystem::Ships); ship_init(); + //listener(subsystem::Props); + //prop_init(); + listener(SubSystem::TechroomIntel); techroom_intel_init(); From af73ad55e6ecced0541f8d2e27a81ff2a2e18e3c Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 11 Jun 2025 16:43:34 -0500 Subject: [PATCH 02/25] Add prop editor dialog in FRED --- code/mission/missionparse.cpp | 31 ++++- code/mission/missionparse.h | 3 + code/prop/prop.cpp | 6 +- code/prop/prop.h | 4 +- fred2/CMakeLists.txt | 2 + fred2/fred.cpp | 11 ++ fred2/fred.h | 3 + fred2/fred.rc | 35 +++++ fred2/fredview.cpp | 26 +++- fred2/fredview.h | 1 + fred2/mainfrm.cpp | 10 ++ fred2/mainfrm.h | 4 +- fred2/management.cpp | 35 +++-- fred2/management.h | 1 + fred2/missionsave.cpp | 10 ++ fred2/propdlg.cpp | 236 ++++++++++++++++++++++++++++++++++ fred2/propdlg.h | 36 ++++++ fred2/resource.h | 7 + 18 files changed, 442 insertions(+), 19 deletions(-) create mode 100644 fred2/propdlg.cpp create mode 100644 fred2/propdlg.h diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 0e2d4efbbce..fe206833210 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -584,6 +584,16 @@ parse_object_flag_description Parse_wing_flag_descriptions[] = const size_t Num_parse_wing_flags = sizeof(Parse_wing_flags) / sizeof(flag_def_list_new); +flag_def_list_new Parse_prop_flags[] = { + { "no_collide", Mission::Parse_Object_Flags::OF_No_collide, true, false }, +}; + +parse_object_flag_description Parse_prop_flag_descriptions[] = { + { Mission::Parse_Object_Flags::OF_No_collide, "Prop cannot be collided with."}, +}; + +const size_t Num_parse_prop_flags = sizeof(Parse_prop_flags) / sizeof(flag_def_list_new); + // These are only the flags that are saved to the mission file. See the MEF_ #defines. flag_def_list Mission_event_flags[] = { { "interval & delay use msecs", MEF_USE_MSECS, 0 }, @@ -5126,6 +5136,17 @@ void parse_prop(mission* pm) required_string("$Orientation:"); stuff_matrix(&prop.orientation); + // set flags + if (optional_string("+Flags:")) { + SCP_vector unparsed; + parse_string_flag_list(prop.flags, Parse_prop_flags, Num_parse_prop_flags, &unparsed); + if (!unparsed.empty()) { + for (size_t k = 0; k < unparsed.size(); ++k) { + WarningEx(LOCATION, "Unknown flag in parse prop flags: %s", unparsed[k].c_str()); + } + } + } + Parse_props.emplace_back(prop); } @@ -5237,7 +5258,15 @@ void post_process_props() { for (int i = 0; i < static_cast(Parse_props.size()); i++) { parsed_prop* propp = &Parse_props[i]; - prop_create(&propp->orientation, &propp->position, propp->prop_info_index, propp->name); + int objnum = prop_create(&propp->orientation, &propp->position, propp->prop_info_index, propp->name); + + if (objnum >= 0) { + auto& obj = Objects[objnum]; + + if (propp->flags[Mission::Parse_Object_Flags::OF_No_collide]) { + obj.flags.remove(Object::Object_Flags::Collides); + } + } } } diff --git a/code/mission/missionparse.h b/code/mission/missionparse.h index 2b8f7f37aa8..4f7f6bad4aa 100644 --- a/code/mission/missionparse.h +++ b/code/mission/missionparse.h @@ -326,6 +326,9 @@ extern const size_t Num_parse_object_flags; extern flag_def_list_new Parse_wing_flags[]; extern parse_object_flag_description Parse_wing_flag_descriptions[]; extern const size_t Num_parse_wing_flags; +extern flag_def_list_new Parse_prop_flags[]; +extern parse_object_flag_description Parse_prop_flag_descriptions[]; +extern const size_t Num_parse_prop_flags; extern const char *Icon_names[]; extern const char *Mission_event_log_flags[]; diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 52b41582997..1b7124c72f6 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -154,7 +154,9 @@ void prop_init() { // first parse the default table - parse_prop_table("props.tbl"); + if (cf_exists_full("props.tbl", CF_TYPE_TABLES)) { + parse_prop_table("props.tbl"); + } // parse any modular tables parse_modular_table("*-prp.tbm", parse_prop_table); @@ -196,7 +198,7 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) char base_name[NAME_LENGTH]; char suffix[NAME_LENGTH]; strcpy_s(base_name, Prop_info[prop_type].name); - sprintf(suffix, NOX(" %d"), Props.size()); + sprintf(suffix, NOX(" %d"), static_cast(Props.size())); // start building name strcpy_s(propp->prop_name, base_name); diff --git a/code/prop/prop.h b/code/prop/prop.h index 4bdb51964fe..b06118d0c59 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -2,6 +2,7 @@ #include "globalincs/pstypes.h" +#include "mission/mission_flags.h" #include "object/object.h" #include "prop/prop_flags.h" #include "ship/ship.h" @@ -29,7 +30,7 @@ typedef struct prop { float alpha_mult; // glow points std::deque glow_point_bank_active; - flagset flags; // Render flags + flagset flags; } prop; typedef struct parsed_prop { @@ -37,6 +38,7 @@ typedef struct parsed_prop { int prop_info_index; matrix orientation; vec3d position; + flagset flags; } parsed_prop; // Global prop info array diff --git a/fred2/CMakeLists.txt b/fred2/CMakeLists.txt index 88fed676ca9..6a24990ecd9 100644 --- a/fred2/CMakeLists.txt +++ b/fred2/CMakeLists.txt @@ -103,6 +103,8 @@ set(FRED2_SOURCES playerstarteditor.h prefsdlg.cpp prefsdlg.h + propdlg.cpp + propdlg.h reinforcementeditordlg.cpp reinforcementeditordlg.h resource.h diff --git a/fred2/fred.cpp b/fred2/fred.cpp index 46b1dbb3a38..9b2d906928b 100644 --- a/fred2/fred.cpp +++ b/fred2/fred.cpp @@ -116,6 +116,7 @@ int Show_cpu = 0; CWnd* Prev_window; CShipEditorDlg Ship_editor_dialog; +prop_dlg Prop_editor_dialog; wing_editor Wing_editor_dialog; waypoint_path_dlg Waypoint_editor_dialog; jumpnode_dlg Jumpnode_editor_dialog; @@ -125,6 +126,7 @@ briefing_editor_dlg* Briefing_dialog = NULL; window_data Main_wnd_data; window_data Ship_wnd_data; +window_data Prop_wnd_data; window_data Wing_wnd_data; window_data Object_wnd_data; window_data Mission_goals_wnd_data; @@ -249,6 +251,7 @@ BOOL CFREDApp::InitInstance() { read_window("Main window", &Main_wnd_data); read_window("Ship window", &Ship_wnd_data); + read_window("Prop window", &Prop_wnd_data); read_window("Wing window", &Wing_wnd_data); read_window("Waypoint window", &Waypoint_wnd_data); read_window("Jumpnode window", &Jumpnode_wnd_data); @@ -391,6 +394,7 @@ BOOL CFREDApp::OnIdle(LONG lCount) { if (!app_init) { app_init = 1; theApp.init_window(&Ship_wnd_data, &Ship_editor_dialog, 0, 1); + theApp.init_window(&Prop_wnd_data, &Prop_editor_dialog, 0, 1); theApp.init_window(&Wing_wnd_data, &Wing_editor_dialog, 0, 1); theApp.init_window(&MusPlayer_wnd_data, &Music_player_dialog, 0, 1); theApp.init_window(&Waypoint_wnd_data, &Waypoint_editor_dialog, 0, 1); @@ -427,6 +431,11 @@ BOOL CFREDApp::OnIdle(LONG lCount) { Update_wing = 0; } + if (Update_prop) { + Prop_editor_dialog.initialize_data(1); + Update_prop = 0; + } + Prev_window = CFREDView::GetActiveWindow(); // Find the root window of the active window @@ -546,10 +555,12 @@ void CFREDApp::write_ini_file(int degree) { record_window_data(&MusPlayer_wnd_data, &Music_player_dialog); record_window_data(&Wing_wnd_data, &Wing_editor_dialog); record_window_data(&Ship_wnd_data, &Ship_editor_dialog); + record_window_data(&Prop_wnd_data, &Prop_editor_dialog); record_window_data(&Main_wnd_data, Fred_main_wnd); write_window("Main window", &Main_wnd_data); write_window("Ship window", &Ship_wnd_data); + write_window("Prop window", &Prop_wnd_data); write_window("Wing window", &Wing_wnd_data); write_window("Waypoint window", &Waypoint_wnd_data); write_window("Jumpnode window", &Jumpnode_wnd_data); diff --git a/fred2/fred.h b/fred2/fred.h index 0997b18f5c2..99e240c4932 100644 --- a/fred2/fred.h +++ b/fred2/fred.h @@ -19,6 +19,7 @@ #include "BriefingEditorDlg.h" #include "resource.h" #include "ShipEditorDlg.h" +#include "propdlg.h" #include "WaypointPathDlg.h" #include "JumpNodeDlg.h" #include "wing_editor.h" @@ -180,6 +181,7 @@ extern HCURSOR h_cursor_rotate; //!< Cursor resource (icon) used for rotational extern CWnd* Prev_window; //!< The currently active window extern CShipEditorDlg Ship_editor_dialog; //!< The ship editor instance +extern prop_dlg Prop_editor_dialog; //!< The prop editor instance extern wing_editor Wing_editor_dialog; //!< The wing editor instance extern waypoint_path_dlg Waypoint_editor_dialog; //!< The waypoint editor instance extern jumpnode_dlg Jumpnode_editor_dialog; //!< The jumpnode editor instance @@ -191,6 +193,7 @@ extern CFREDApp theApp; //!< The application instance extern window_data Main_wnd_data; extern window_data Ship_wnd_data; +extern window_data Prop_wnd_data; extern window_data Wing_wnd_data; extern window_data Object_wnd_data; extern window_data Mission_goals_wnd_data; diff --git a/fred2/fred.rc b/fred2/fred.rc index 3e6192b4ce1..676ea0c46db 100644 --- a/fred2/fred.rc +++ b/fred2/fred.rc @@ -555,6 +555,14 @@ BEGIN END END +IDR_PROP_EDIT_MENU MENU +BEGIN + POPUP "&Select Prop" + BEGIN + MENUITEM "PlaceHolder", ID_SELECTSHIP_PLACEHOLDER + END +END + IDR_PLAYER_EDIT_MENU MENU BEGIN POPUP "Select Team" @@ -632,6 +640,7 @@ BEGIN "F", ID_EDITORS_FICTION, VIRTKEY, SHIFT, NOINVERT "G", ID_EDITORS_GOALS, VIRTKEY, SHIFT, NOINVERT "G", ID_VIEW_GRID, VIRTKEY, SHIFT, ALT, NOINVERT + "P", ID_EDITORS_PROPS, VIRTKEY, SHIFT, CONTROL, NOINVERT "H", ID_ERROR_CHECKER, VIRTKEY, SHIFT, NOINVERT "H", ID_SELECT_LIST, VIRTKEY, NOINVERT "H", ID_SHOW_HORIZON, VIRTKEY, SHIFT, ALT, NOINVERT @@ -1099,6 +1108,20 @@ BEGIN EDITTEXT IDC_HELP_BOX,7,421,308,78,ES_MULTILINE | ES_READONLY,WS_EX_TRANSPARENT END +IDD_PROP_EDITOR DIALOGEX 0, 0, 300, 200 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Prop Editor" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + LTEXT "Name",IDC_STATIC,7,12,24,8 + EDITTEXT IDC_PROP_NAME,32,10,90,14,ES_AUTOHSCROLL + PUSHBUTTON "Prev",IDC_PROP_PREV,130,10,20,14 + PUSHBUTTON "Next",IDC_PROP_NEXT,155,10,20,14 + LTEXT "Flags",IDC_STATIC,7,35,30,8 + LISTBOX IDC_PROP_FLAGS,7,45,220,60,LBS_MULTIPLESEL | LBS_NOTIFY | WS_VSCROLL | WS_BORDER +END + + IDD_WEAPON_EDITOR DIALOGEX 0, 0, 564, 79 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Weapon Editor" @@ -2638,6 +2661,13 @@ BEGIN TOPMARGIN, 7 END + IDD_PROP_EDITOR, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 315 + TOPMARGIN, 7 + END + IDD_WEAPON_EDITOR, DIALOG BEGIN LEFTMARGIN, 7 @@ -3288,6 +3318,11 @@ BEGIN 0 END +IDD_PROP_EDITOR AFX_DIALOG_LAYOUT +BEGIN + 0 +END + IDD_WING_EDITOR AFX_DIALOG_LAYOUT BEGIN 0 diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp index 3bed33b275d..cce2eecc39b 100644 --- a/fred2/fredview.cpp +++ b/fred2/fredview.cpp @@ -31,6 +31,7 @@ #include "ai/ai.h" #include "ai/aigoals.h" #include "ship/ship.h" // for ship names +#include "prop/prop.h" // for prop names #include "MissionGoalsDlg.h" #include "MissionCutscenesDlg.h" #include "wing.h" @@ -149,6 +150,7 @@ BEGIN_MESSAGE_MAP(CFREDView, CView) ON_UPDATE_COMMAND_UI(ID_SHOW_WAYPOINTS, OnUpdateViewWaypoints) ON_WM_LBUTTONDOWN() ON_COMMAND(ID_EDITORS_SHIPS, OnEditorsShips) + ON_COMMAND(ID_EDITORS_PROPS, OnEditorsProps) ON_WM_KEYDOWN() ON_WM_KEYUP() ON_WM_SETFOCUS() @@ -1195,6 +1197,18 @@ void CFREDView::OnEditorsShips() Ship_editor_dialog.ShowWindow(SW_RESTORE); } +void CFREDView::OnEditorsProps() +{ + Assert(Prop_editor_dialog.GetSafeHwnd()); + + if (!theApp.init_window(&Prop_wnd_data, &Prop_editor_dialog, 0)) + return; + + Prop_editor_dialog.SetWindowPos(&wndTop, 0, 0, 0, 0, + SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); + Prop_editor_dialog.ShowWindow(SW_RESTORE); +} + void CFREDView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT lParam) { uint lKeyData; @@ -1460,10 +1474,14 @@ void CFREDView::OnContextMenu(CWnd* /*pWnd*/, CPoint point) else { CString str; - if ((Objects[objnum].type == OBJ_START) || (Objects[objnum].type == OBJ_SHIP)) + if ((Objects[objnum].type == OBJ_START) || (Objects[objnum].type == OBJ_SHIP)) { str.Format("Edit %s", Ships[Objects[objnum].instance].ship_name); - else if (Objects[objnum].type == OBJ_JUMP_NODE) { + } else if (Objects[objnum].type == OBJ_PROP) { + id = ID_EDITORS_PROPS; + str.Format("Edit %s", Props[Objects[objnum].instance].prop_name); + + } else if (Objects[objnum].type == OBJ_JUMP_NODE) { auto jnp = jumpnode_get_by_objnum(objnum); Assert(jnp != nullptr); @@ -2001,6 +2019,10 @@ void CFREDView::OnLButtonDblClk(UINT nFlags, CPoint point) OnEditorsShips(); break; + case OBJ_PROP: + OnEditorsProps(); + break; + case OBJ_WAYPOINT: OnEditorsWaypoint(); break; diff --git a/fred2/fredview.h b/fred2/fredview.h index d351a00dc46..ac1cd9f0242 100644 --- a/fred2/fredview.h +++ b/fred2/fredview.h @@ -105,6 +105,7 @@ class CFREDView : public CView afx_msg void OnUpdateViewWaypoints(CCmdUI* pCmdUI); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnEditorsShips(); + afx_msg void OnEditorsProps(); afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnSetFocus(CWnd* pOldWnd); diff --git a/fred2/mainfrm.cpp b/fred2/mainfrm.cpp index 910ae439ad9..a6c75767c1b 100644 --- a/fred2/mainfrm.cpp +++ b/fred2/mainfrm.cpp @@ -82,6 +82,9 @@ size_t prop_type_combo_box_size = 0; int Toggle1_var = 0; CPoint Global_point2; +CStatic m_ship_label; +CStatic m_prop_label; + /** * @brief Launches the default browser to open the given URL */ @@ -136,6 +139,12 @@ void CMainFrame::init_tools() } } + // No valid props, so disable the dropdown + if (prop_type_combo_box_size == 0) { + m_new_prop_type_combo_box.EnableWindow(FALSE); + m_prop_label.EnableWindow(FALSE); + } + Id_select_type_waypoint = ship_type_combo_box_size; Id_select_type_jump_node = ship_type_combo_box_size + 1; @@ -282,6 +291,7 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { Fred_main_wnd = this; Ship_editor_dialog.Create(); + Prop_editor_dialog.Create(); Wing_editor_dialog.Create(); Waypoint_editor_dialog.Create(); Jumpnode_editor_dialog.Create(); diff --git a/fred2/mainfrm.h b/fred2/mainfrm.h index db076a1b473..2c5c1b15dad 100644 --- a/fred2/mainfrm.h +++ b/fred2/mainfrm.h @@ -240,7 +240,7 @@ extern size_t ship_type_combo_box_size; extern color_combo_box_prop m_new_prop_type_combo_box; extern size_t prop_type_combo_box_size; -CStatic m_ship_label; -CStatic m_prop_label; +extern CStatic m_ship_label; +extern CStatic m_prop_label; #endif // _MAINFRM_H diff --git a/fred2/management.cpp b/fred2/management.cpp index c1a10a41b7e..401f9209517 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -98,6 +98,7 @@ int delete_flag; int bypass_update = 0; int Update_ship = 0; int Update_wing = 0; +int Update_prop = 0; char Fred_exe_dir[512] = ""; char Fred_base_dir[512] = ""; @@ -501,8 +502,13 @@ int create_object_on_grid(int waypoint_instance, bool prop) mark_object(obj); FREDDoc_ptr->autosave("object create"); - } else if (obj == -1) - Fred_main_wnd->MessageBox("Maximum object limit reached. Can't add any more object."); + } else if (obj == -1) { + if (prop && Prop_info.empty()) { + Fred_main_wnd->MessageBox("No props defined. Can't create prop object!"); + } else { + Fred_main_wnd->MessageBox("Maximum object limit reached. Can't add any more objects."); + } + } } return obj; @@ -628,9 +634,6 @@ int create_prop(matrix* orient, vec3d* pos, int prop_type) // ok, done with file io, restore the pwd chdir(pwd); - prop* propp = &Props[Objects[obj].instance]; - prop_info* pip = &Prop_info[propp->prop_info_index]; - if (query_prop_name_duplicate(Objects[obj].instance)) fix_prop_name(Objects[obj].instance); @@ -1034,7 +1037,7 @@ void set_cur_object_index(int obj) mark_object(obj); set_cur_indices(obj); // select the new object - Update_ship = Update_wing = 1; + Update_ship = Update_wing = Update_prop = 1; Waypoint_editor_dialog.initialize_data(1); Jumpnode_editor_dialog.initialize_data(1); Update_window = 1; @@ -1150,6 +1153,15 @@ int update_dialog_boxes() return z; } + z = Prop_editor_dialog.update_data(); + if (z) { + nprintf(("Fred routing", "prop dialog save failed\n")); + Prop_editor_dialog.SetWindowPos(&Fred_main_wnd->wndTop, 0, 0, 0, 0, + SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); + + return z; + } + z = Waypoint_editor_dialog.update_data(0); if (z) { nprintf(("Fred routing", "waypoint dialog save failed\n")); @@ -1310,18 +1322,19 @@ int common_object_delete(int obj) invalidate_references(name, sexp_ref_type::SHIP); } - for (i=0; idelete_icon(Objects[obj].instance); @@ -1474,7 +1487,7 @@ void mark_object(int obj) if (cur_object_index == -1){ set_cur_object_index(obj); } - Update_ship = Update_wing = 1; + Update_ship = Update_wing = Update_prop = 1; Waypoint_editor_dialog.initialize_data(1); Jumpnode_editor_dialog.initialize_data(1); } @@ -1502,7 +1515,7 @@ void unmark_object(int obj) set_cur_object_index(-1); // can't find one; nothing is marked. } - Update_ship = Update_wing = 1; + Update_ship = Update_wing = Update_prop = 1; Waypoint_editor_dialog.initialize_data(1); Jumpnode_editor_dialog.initialize_data(1); } diff --git a/fred2/management.h b/fred2/management.h index 81cd7ffb4a4..cba1054f131 100644 --- a/fred2/management.h +++ b/fred2/management.h @@ -29,6 +29,7 @@ extern waypoint* cur_waypoint; extern waypoint_list* cur_waypoint_list; extern int Update_ship; extern int Update_wing; +extern int Update_prop; extern ai_goal_list Ai_goal_list[]; extern int Ai_goal_list_size; diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index eef37773b3b..07d607f4397 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -5301,6 +5301,16 @@ int CFred_mission_save::save_props() parse_comments(); save_matrix(Objects[Props[i].objnum].orient); + if (optional_string_fred("+Flags:", "$Name:")) { + parse_comments(); + fout(" ("); + } else + fout("\n+Flags: ("); + + if (!(Objects[Props[i].objnum].flags[Object::Object_Flags::Collides])) + fout(" \"no_collide\""); + fout(" )"); + fso_comment_pop(); } } diff --git a/fred2/propdlg.cpp b/fred2/propdlg.cpp new file mode 100644 index 00000000000..a2e84925a02 --- /dev/null +++ b/fred2/propdlg.cpp @@ -0,0 +1,236 @@ +#include "stdafx.h" +#include "FRED.h" +#include "propdlg.h" +#include "Management.h" +#include "MainFrm.h" +#include "object/object.h" +#include "prop/prop.h" + +prop_dlg::prop_dlg(CWnd* pParent /*=NULL*/) : CDialog(prop_dlg::IDD, pParent) +{ + m_name = _T(""); + bypass_errors = 0; +} + +BOOL prop_dlg::Create() +{ + BOOL r = CDialog::Create(IDD, Fred_main_wnd); + initialize_data(1); + return r; +} + +void prop_dlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Text(pDX, IDC_PROP_NAME, m_name); + DDX_Control(pDX, IDC_PROP_FLAGS, m_flags_list); +} + +BEGIN_MESSAGE_MAP(prop_dlg, CDialog) +ON_WM_CLOSE() +ON_WM_SIZE() +ON_BN_CLICKED(IDC_PROP_NEXT, OnPropNext) +ON_BN_CLICKED(IDC_PROP_PREV, OnPropPrev) +END_MESSAGE_MAP() + +void prop_dlg::OnClose() +{ + UpdateData(TRUE); + if (update_data()) { + SetWindowPos(&wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); + bypass_errors = 0; + return; + } + + SetWindowPos(Fred_main_wnd, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW); + Fred_main_wnd->SetWindowPos(&wndTop, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); +} + +BOOL prop_dlg::OnCommand(WPARAM wParam, LPARAM lParam) +{ + // In the future, handle prop-specific menu commands here. + + return CDialog::OnCommand(wParam, lParam); +} + +void prop_dlg::OnSize(UINT nType, int cx, int cy) +{ + CDialog::OnSize(nType, cx, cy); + + if (!GetSafeHwnd()) + return; + + // Resize/move the flag list box to fill new space + CWnd* flagList = GetDlgItem(IDC_PROP_FLAGS); + if (flagList) { + CRect r; + flagList->GetWindowRect(&r); + ScreenToClient(&r); + r.right = cx - 10; + r.bottom = cy - 10; + flagList->MoveWindow(&r); + } +} + + +void prop_dlg::initialize_data(int full_update) +{ + int enable = TRUE; + + if (!GetSafeHwnd()) + return; + + // Check if we have a selected prop + if (query_valid_object() && Objects[cur_object_index].type == OBJ_PROP) { + auto& prp = Props[Objects[cur_object_index].instance]; + m_name = _T(prp.prop_name); + } else { + // No valid prop selected; disable editing fields + m_name = _T(""); + enable = FALSE; + } + + m_flags_list.ResetContent(); + + for (size_t i = 0; i < Num_parse_prop_flags; ++i) { + auto& def = Parse_prop_flags[i]; + auto& desc = Parse_prop_flag_descriptions[i]; + + CString display_str; + display_str.Format("%s (%s)", def.name, desc.flag_desc); + + int idx = m_flags_list.AddString(display_str); + m_flags_list.SetItemData(idx, static_cast(i)); + } + + // Set selection states if a valid prop is selected + if (query_valid_object() && Objects[cur_object_index].type == OBJ_PROP) { + + for (int i = 0; i < m_flags_list.GetCount(); ++i) { + size_t flag_index = static_cast(m_flags_list.GetItemData(i)); + if (flag_index >= Num_parse_prop_flags) + continue; + auto& def = Parse_prop_flags[flag_index]; + + // Handle each flag manually since they may not always match 1:1 from mission object flags to parsed object or true object flags + if (!stricmp(def.name, "no_collide")) { + if (!Objects[cur_object_index].flags[Object::Object_Flags::Collides]) { + m_flags_list.SetSel(i); + } + } + } + } + + if (full_update) + UpdateData(FALSE); + + GetDlgItem(IDC_PROP_NAME)->EnableWindow(enable); + m_flags_list.EnableWindow(enable); +} + +int prop_dlg::update_data() +{ + if (!GetSafeHwnd()) + return 0; + + if (query_valid_object() && Objects[cur_object_index].type == OBJ_PROP) { + int this_instance = Objects[cur_object_index].instance; + auto& prp = Props[this_instance]; + + m_name.TrimLeft(); + m_name.TrimRight(); + + for (size_t i = 0; i < Props.size(); ++i) { + if ((int)i == this_instance) + continue; // skip self + + if (!stricmp(m_name, Props[i].prop_name)) { + if (bypass_errors) + return 1; + + bypass_errors = 1; + int z = MessageBox("This prop name is already being used by another prop\n" + "Press OK to restore old name", "Error", MB_ICONEXCLAMATION | MB_OKCANCEL); + + if (z == IDCANCEL) + return -1; + + m_name = _T(prp.prop_name); + UpdateData(FALSE); + return 1; + } + } + + // Passed name validation + strcpy_s(prp.prop_name, m_name); + + prp.flags.reset(); // Clear all flags + + for (int i = 0; i < m_flags_list.GetCount(); ++i) { + size_t flag_index = static_cast(m_flags_list.GetItemData(i)); + if (flag_index >= Num_parse_prop_flags) + continue; + auto& def = Parse_prop_flags[flag_index]; + + bool selected = m_flags_list.GetSel(i); + + // Handle each flag manually + if (!stricmp(def.name, "no_collide")) { + Objects[cur_object_index].flags.set(Object::Object_Flags::Collides, !selected); + } + } + } + + update_map_window(); + return 0; +} + +void prop_dlg::select_prop_from_object_list(object* start, bool forward) +{ + object* ptr = start; + + // Search forward or backward + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_PROP) { + unmark_all(); + mark_object(OBJ_INDEX(ptr)); + set_cur_object_index(OBJ_INDEX(ptr)); + initialize_data(1); + return; + } + ptr = forward ? GET_NEXT(ptr) : GET_PREV(ptr); + } + + // Wraparound search + ptr = forward ? GET_FIRST(&obj_used_list) : GET_LAST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_PROP) { + unmark_all(); + mark_object(OBJ_INDEX(ptr)); + set_cur_object_index(OBJ_INDEX(ptr)); + initialize_data(1); + return; + } + ptr = forward ? GET_NEXT(ptr) : GET_PREV(ptr); + } +} + +void prop_dlg::OnPropNext() +{ + UpdateData(TRUE); + if (update_data() < 0) + return; // Only abort if user cancelled (e.g. name conflict they didn't resolve) + + select_prop_from_object_list(GET_NEXT(&Objects[cur_object_index]), true); +} + +void prop_dlg::OnPropPrev() +{ + UpdateData(TRUE); + if (update_data() < 0) + return; // Only abort if user cancelled (e.g. name conflict they didn't resolve) + + select_prop_from_object_list(GET_PREV(&Objects[cur_object_index]), false); +} + +void prop_dlg::OnOK() {} diff --git a/fred2/propdlg.h b/fred2/propdlg.h new file mode 100644 index 00000000000..0e0bf36d2a3 --- /dev/null +++ b/fred2/propdlg.h @@ -0,0 +1,36 @@ +#ifndef _PROPDLG_H +#define _PROPDLG_H + +class prop_dlg : public CDialog { + // Construction + public: + int bypass_errors; + BOOL Create(); + int update_data(); + void initialize_data(int full_update); + void OnOK(); + prop_dlg(CWnd* pParent = NULL); // standard constructor + + // Dialog Data + enum { + IDD = IDD_PROP_EDITOR + }; // You’ll define this in the resource editor + CString m_name; + CListBox m_flags_list; + + // Overrides + protected: + virtual void DoDataExchange(CDataExchange* pDX); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + void select_prop_from_object_list(object* start, bool forward); + + // Implementation + protected: + afx_msg void OnClose(); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnPropNext(); + afx_msg void OnPropPrev(); + DECLARE_MESSAGE_MAP() +}; + +#endif diff --git a/fred2/resource.h b/fred2/resource.h index 9c286ffd139..0ba948f0bd9 100644 --- a/fred2/resource.h +++ b/fred2/resource.h @@ -73,12 +73,14 @@ #define IDB_OPERATOR 226 #define IDB_DATA 227 #define IDB_ROOT 228 +#define IDD_PROP_EDITOR 229 #define IDD_ADJUST_GRID 230 #define IDD_SHIELD_SYS 231 #define IDR_ASTEROID_FIELD_MENU 232 #define IDR_CPGN_VIEW_OFF 233 #define IDR_CPGN_VIEW_ON 234 #define IDD_CALC_RELATIVE_COORDS 235 +#define IDR_PROP_EDIT_MENU 236 #define IDD_INITIAL_SHIPS 238 #define IDB_CHAINED 239 #define IDB_STOP 240 @@ -1265,6 +1267,10 @@ #define IDC_SELECT_ASTEROID 1706 #define IDC_SMOOTHING 1707 #define IDC_SPIN_SMOOTHING 1708 +#define IDC_PROP_NAME 1709 +#define IDC_PROP_PREV 1710 +#define IDC_PROP_NEXT 1711 +#define IDC_PROP_FLAGS 1712 #define IDC_SEXP_POPUP_LIST 32770 #define ID_FILE_MISSIONNOTES 32771 #define ID_DUPLICATE 32774 @@ -1300,6 +1306,7 @@ #define ID_VIEW_ELEVATIONS 32806 #define ID_VIEW_WAYPOINTS 32807 #define ID_VIEW_GRID 32808 +#define ID_EDITORS_PROPS 32809 #define ID_MIKE_GRIDCONTROL 32811 #define ID_PROPERTIES_ONE 32812 #define ID_PROPERTIES_TWO 32813 From 90f449ab1ae1df11d5268e34c94d7d01cd1f3eb4 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Thu, 12 Jun 2025 13:27:15 -0500 Subject: [PATCH 03/25] prop sexps --- code/parse/sexp.cpp | 281 ++++++++++++++++++++++++++++ code/parse/sexp.h | 8 + code/prop/prop.cpp | 8 +- code/prop/prop.h | 3 + code/prop/prop_flags.h | 2 +- fred2/mainfrm.cpp | 6 +- fred2/mainfrm.h | 6 +- fred2/sexp_tree.cpp | 51 +++++ fred2/sexp_tree.h | 2 + qtfred/src/mission/missionsave.cpp | 50 ++++- qtfred/src/mission/missionsave.h | 10 + qtfred/src/ui/widgets/sexp_tree.cpp | 52 +++++ qtfred/src/ui/widgets/sexp_tree.h | 2 + 13 files changed, 471 insertions(+), 10 deletions(-) diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 10510d160c0..ed4d4b203a1 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -86,6 +86,7 @@ #include "parse/sexp.h" #include "parse/sexp_container.h" #include "playerman/player.h" +#include "prop/prop.h" #include "render/3d.h" #include "scripting/global_hooks.h" #include "ship/afterburner.h" @@ -617,6 +618,9 @@ SCP_vector Operators = { { "add-to-collision-group-new", OP_ADD_TO_COLGROUP_NEW, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "remove-from-collision-group-new", OP_REMOVE_FROM_COLGROUP_NEW, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "get-collision-group", OP_GET_COLGROUP_ID, 1, 1, SEXP_ACTION_OPERATOR, }, + { "add-to-collision-group-prop", OP_ADD_TO_COLGROUP_PROP, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael + { "remove-from-collision-group-prop", OP_REMOVE_FROM_COLGROUP_PROP, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael + { "get-collision-group-prop", OP_GET_COLGROUP_ID_PROP, 1, 1, SEXP_ACTION_OPERATOR, }, // MjnMixael { "change-team-color", OP_CHANGE_TEAM_COLOR, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // The E { "replace-texture", OP_REPLACE_TEXTURE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Lafiel { "replace-skybox-texture", OP_REPLACE_TEXTURE_SKYBOX, 2, 2, SEXP_ACTION_OPERATOR, }, // Lafiel @@ -796,8 +800,10 @@ SCP_vector Operators = { { "reset-post-effects", OP_RESET_POST_EFFECTS, 0, 0, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-effect", OP_SHIP_EFFECT, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Valathil { "ship-create", OP_SHIP_CREATE, 5, 10, SEXP_ACTION_OPERATOR, }, // WMC + { "prop-create", OP_PROP_CREATE, 5, 8, SEXP_ACTION_OPERATOR, }, // MjnMixael { "weapon-create", OP_WEAPON_CREATE, 5, 10, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-vanish", OP_SHIP_VANISH, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, + { "prop-vanish", OP_PROP_VANISH, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "ship-vaporize", OP_SHIP_VAPORIZE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-no-vaporize", OP_SHIP_NO_VAPORIZE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-explosion-option", OP_SET_EXPLOSION_OPTION, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 @@ -2494,6 +2500,15 @@ int check_sexp_syntax(int node, int return_type, int recursive, int *bad_node, s break; + case OPF_PROP: + if (type2 != SEXP_ATOM_STRING) { + return SEXP_CHECK_TYPE_MISMATCH; + } + if (prop_name_lookup(CTEXT(node)) < 0) { + return SEXP_CHECK_INVALID_PROP; + } + break; + case OPF_WING: if (type2 != SEXP_ATOM_STRING){ return SEXP_CHECK_TYPE_MISMATCH; @@ -4460,6 +4475,23 @@ void preload_debris_class(const char* text) asteroid_load(idx, 0); } +// MjnMixael +void preload_change_prop_class(const char* text) +{ + int idx; + prop_info* pip; + + idx = prop_info_lookup(text); + if (idx < 0) + return; + + pip = &Prop_info[idx]; + pip->model_num = model_load(pip->pof_file); + + if (pip->model_num >= 0) + model_page_in_textures(pip->model_num, idx); +} + // Goober5000 void preload_turret_change_weapon(const char *text) { @@ -4846,6 +4878,13 @@ int get_sexp() do_preload_for_arguments(preload_change_ship_class, n, arg_handler); break; + case OP_PROP_CREATE: + // prop class is argument #2 + n = CDDR(start); + // page in prop classes of dynamically created props + do_preload_for_arguments(preload_change_prop_class, n, arg_handler); + break; + case OP_SET_SPECIAL_WARPOUT_NAME: // set flag for taylor Knossos_warp_ani_used = true; @@ -5784,6 +5823,49 @@ const ship_registry_entry *eval_ship(int node) return nullptr; } +/** + * Gets a prop from a sexp node. Returns the prop entry, or nullptr if the prop is unknown. + * May be overkill for props. Research required. + */ +const prop *eval_prop(int node) +{ + if (node < 0) + return nullptr; + + // check cache + if (Sexp_nodes[node].cache) + { + // have we cached something else? + if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_PROP) + return nullptr; + + return &Props[Sexp_nodes[node].cache->ship_registry_index]; + } + + // maybe forward to a special-arg node + if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) + { + auto current_argument = Sexp_replacement_arguments.back(); + int arg_node = current_argument.second; + + if (arg_node >= 0) + return eval_prop(arg_node); + } + + auto prop_idx = prop_name_lookup(CTEXT(node)); + if (prop_idx >= 0) + { + // cache the value if it can't change later + if (!is_node_value_dynamic(node)) + Sexp_nodes[node].cache = new sexp_cached_data(OPF_PROP, -1, prop_idx); + + return &Props[prop_idx]; + } + + // we know nothing about this prop, apparently + return nullptr; +} + /** * Gets a wing from a sexp node. Returns the wing pointer, or NULL if the wing is unknown. */ @@ -19506,6 +19588,55 @@ void sexp_ship_create(int n) } } +// MjnMixael +void sexp_prop_create(int n) +{ + int new_prop_class, angle_count; + vec3d new_prop_pos; + angles new_prop_ang; + matrix new_prop_ori; + bool is_nan, is_nan_forever; + + Assert( n >= 0 ); + + // get ship name + auto new_ship_name = CTEXT(n); + n = CDR(n); + + // none means don't specify it + // if ship with this name already exists, ship_create will respond appropriately + if (!stricmp(new_ship_name, SEXP_NONE_STRING)) + new_ship_name = nullptr; + + //Get ship class + new_prop_class = prop_info_lookup(CTEXT(n)); + if (new_prop_class < 0) + { + Warning(LOCATION, "Invalid prop class passed to prop-create; prop type '%s' does not exist", CTEXT(n)); + return; + } + n = CDR(n); + + eval_vec3d(&new_prop_pos, n, is_nan, is_nan_forever); + if (is_nan || is_nan_forever) + return; + + angle_count = eval_angles(&new_prop_ang, n, is_nan, is_nan_forever); + if (is_nan || is_nan_forever) + return; + + //This is a costly function, so only do it if needed + if (angle_count > 0) + vm_angles_2_matrix(&new_prop_ori, &new_prop_ang); + else + new_prop_ori = vmd_identity_matrix; + + int objnum = prop_create(&new_prop_ori, &new_prop_pos, new_prop_class, new_ship_name); + Assert(objnum != -1); + + // note: model_page_in_textures was called via the sexp preloader +} + // Goober5000 void sexp_weapon_create(int n) { @@ -19595,6 +19726,17 @@ void sexp_ship_vanish(int n) } } +// make prop vanish without a trace +void sexp_prop_vanish(int n) +{ + for (; n != -1; n = CDR(n)) { + auto prop_entry = eval_prop(n); + if (prop_entry != nullptr) { + Objects[prop_entry->objnum].flags.set(Object::Object_Flags::Should_be_dead); + } + } +} + void sexp_destroy_instantly(int n, bool with_debris) { if (MULTIPLAYER_MASTER) @@ -27361,6 +27503,46 @@ int sexp_get_colgroup(int node) return ship_entry->p_objp()->collision_group_id; } +void sexp_manipulate_colgroup_prop(int node, bool add_to_group) +{ + bool is_nan, is_nan_forever; + int group = eval_num(node, is_nan, is_nan_forever); + if (is_nan || is_nan_forever) + return; + node = CDR(node); + + if (group < 0 || group > 31) + { + WarningEx(LOCATION, "Invalid collision group id %d specified. Valid IDs range from 0 to 31.\n", group); + return; + } + + for (; node != -1; node = CDR(node)) + { + auto prop_entry = eval_prop(node); + + if (prop_entry != nullptr) { + object& obj = Objects[eval_prop(node)->objnum]; + + if (add_to_group) + obj.collision_group_id |= (1 << group); + else + obj.collision_group_id &= ~(1 << group); + } + } +} + +int sexp_get_colgroup_prop(int node) +{ + auto prop_entry = eval_prop(node); + if (!prop_entry) + return SEXP_NAN; + + object& obj = Objects[eval_prop(node)->objnum]; + + return obj.collision_group_id; +} + int get_effect_from_name(const char* name) { int i = 0; @@ -28690,6 +28872,11 @@ int eval_sexp(int cur_node, int referenced_node) sexp_val = SEXP_TRUE; break; + case OP_PROP_CREATE: + sexp_prop_create(node); + sexp_val = SEXP_TRUE; + break; + case OP_WEAPON_CREATE: sexp_weapon_create(node); sexp_val = SEXP_TRUE; @@ -28700,6 +28887,11 @@ int eval_sexp(int cur_node, int referenced_node) sexp_val = SEXP_TRUE; break; + case OP_PROP_VANISH: + sexp_prop_vanish(node); + sexp_val = SEXP_TRUE; + break; + case OP_DESTROY_INSTANTLY: case OP_DESTROY_INSTANTLY_WITH_DEBRIS: sexp_destroy_instantly(node, (op_num == OP_DESTROY_INSTANTLY_WITH_DEBRIS)); @@ -30270,6 +30462,16 @@ int eval_sexp(int cur_node, int referenced_node) sexp_val = sexp_get_colgroup(node); break; + case OP_ADD_TO_COLGROUP_PROP: + case OP_REMOVE_FROM_COLGROUP_PROP: + sexp_manipulate_colgroup_prop(node, op_num == OP_ADD_TO_COLGROUP_PROP); + sexp_val = SEXP_TRUE; + break; + + case OP_GET_COLGROUP_ID_PROP: + sexp_val = sexp_get_colgroup_prop(node); + break; + case OP_SHIP_EFFECT: sexp_val = SEXP_TRUE; sexp_ship_effect(node); @@ -31199,6 +31401,7 @@ int query_operator_return_type(int op) case OP_GET_THROTTLE_SPEED: case OP_GET_VARIABLE_BY_INDEX: case OP_GET_COLGROUP_ID: + case OP_GET_COLGROUP_ID_PROP: case OP_FUNCTIONAL_IF_THEN_ELSE: case OP_FUNCTIONAL_SWITCH: case OP_GET_HOTKEY: @@ -31373,6 +31576,7 @@ int query_operator_return_type(int op) case OP_SHIP_GUARDIAN_THRESHOLD: case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD: case OP_SHIP_VANISH: + case OP_PROP_VANISH: case OP_DESTROY_INSTANTLY: case OP_DESTROY_INSTANTLY_WITH_DEBRIS: case OP_SHIELDS_ON: @@ -31563,6 +31767,7 @@ int query_operator_return_type(int op) case OP_SET_OBJECT_SPEED_Y: case OP_SET_OBJECT_SPEED_Z: case OP_SHIP_CREATE: + case OP_PROP_CREATE: case OP_WEAPON_CREATE: case OP_MISSION_SET_NEBULA: case OP_CHANGE_BACKGROUND: @@ -31637,6 +31842,8 @@ int query_operator_return_type(int op) case OP_REMOVE_FROM_COLGROUP: case OP_ADD_TO_COLGROUP_NEW: case OP_REMOVE_FROM_COLGROUP_NEW: + case OP_ADD_TO_COLGROUP_PROP: + case OP_REMOVE_FROM_COLGROUP_PROP: case OP_SHIP_EFFECT: case OP_CLEAR_SUBTITLES: case OP_SET_THRUSTERS: @@ -32001,6 +32208,9 @@ int query_operator_argument_type(int op, int argnum) case OP_ABORT_REARM: return OPF_SHIP; + case OP_PROP_VANISH: + return OPF_PROP; + case OP_ALTER_SHIP_FLAG: if(argnum == 0) return OPF_SHIP_FLAG; @@ -32038,6 +32248,14 @@ int query_operator_argument_type(int op, int argnum) else return OPF_NUMBER; + case OP_PROP_CREATE: + if (argnum == 0) + return OPF_STRING; + else if (argnum == 1) + return OPF_PROP_CLASS_NAME; + else + return OPF_NUMBER; + case OP_WEAPON_CREATE: if (argnum == 0) return OPF_SHIP_OR_NONE; @@ -34504,6 +34722,16 @@ int query_operator_argument_type(int op, int argnum) else return OPF_SHIP; + case OP_ADD_TO_COLGROUP_PROP: + case OP_REMOVE_FROM_COLGROUP_PROP: + if (argnum == 0) + return OPF_POSITIVE; + else + return OPF_PROP; + + case OP_GET_COLGROUP_ID_PROP: + return OPF_PROP; + case OP_SHIP_EFFECT: if (argnum == 0) return OPF_SHIP_EFFECT; @@ -34997,6 +35225,9 @@ const char *sexp_error_message(int num) case SEXP_CHECK_INVALID_SHIP: return "Invalid ship name"; + case SEXP_CHECK_INVALID_PROP: + return "Invalid prop name"; + case SEXP_CHECK_INVALID_WING: return "Invalid wing name"; @@ -36560,6 +36791,7 @@ int get_category(int op_id) case OP_CARGO_NO_DEPLETE: case OP_SET_SPECIAL_WARPOUT_NAME: case OP_SHIP_VANISH: + case OP_PROP_VANISH: case OP_SHIELDS_ON: case OP_SHIELDS_OFF: case OP_CHANGE_AI_LEVEL: @@ -36652,6 +36884,7 @@ int get_category(int op_id) case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD: case OP_SET_SKYBOX_MODEL: case OP_SHIP_CREATE: + case OP_PROP_CREATE: case OP_WEAPON_CREATE: case OP_SET_OBJECT_SPEED_X: case OP_SET_OBJECT_SPEED_Y: @@ -36773,6 +37006,7 @@ int get_category(int op_id) case OP_ADD_TO_COLGROUP: case OP_REMOVE_FROM_COLGROUP: case OP_GET_COLGROUP_ID: + case OP_GET_COLGROUP_ID_PROP: case OP_SHIP_EFFECT: case OP_CLEAR_SUBTITLES: case OP_BEAM_FIRE_COORDS: @@ -36825,6 +37059,8 @@ int get_category(int op_id) case OP_SET_TRAITOR_OVERRIDE: case OP_ADD_TO_COLGROUP_NEW: case OP_REMOVE_FROM_COLGROUP_NEW: + case OP_ADD_TO_COLGROUP_PROP: + case OP_REMOVE_FROM_COLGROUP_PROP: case OP_GET_POWER_OUTPUT: case OP_TURRET_SET_FORCED_TARGET: case OP_TURRET_SET_FORCED_SUBSYS_TARGET: @@ -37165,6 +37401,9 @@ int get_subcategory(int op_id) case OP_ADD_TO_COLGROUP_NEW: case OP_REMOVE_FROM_COLGROUP_NEW: case OP_GET_COLGROUP_ID: + case OP_ADD_TO_COLGROUP_PROP: + case OP_REMOVE_FROM_COLGROUP_PROP: + case OP_GET_COLGROUP_ID_PROP: case OP_CHANGE_TEAM_COLOR: case OP_REPLACE_TEXTURE: case OP_REPLACE_TEXTURE_SKYBOX: @@ -37338,8 +37577,10 @@ int get_subcategory(int op_id) case OP_RESET_POST_EFFECTS: case OP_SHIP_EFFECT: case OP_SHIP_CREATE: + case OP_PROP_CREATE: case OP_WEAPON_CREATE: case OP_SHIP_VANISH: + case OP_PROP_VANISH: case OP_SHIP_VAPORIZE: case OP_SHIP_NO_VAPORIZE: case OP_SET_EXPLOSION_OPTION: @@ -41028,6 +41269,11 @@ SCP_vector Sexp_help = { "\tSingle Player Only! Warning: This will cause ship exit not to be logged, so 'has-departed', etc. will not work\r\n" "\t1: List of ship names to vanish (ship must be in-mission)\r\n"}, + { OP_PROP_VANISH, "prop-vanish\r\n" + "\tMakes the named prop vanish\r\n" + "\tSingle Player Only!\r\n" + "\t1: List of prop names to vanish (prop must be in-mission)\r\n"}, + { OP_DESTROY_INSTANTLY, "destroy-instantly\r\n" "\tSelf-destructs the named ship without explosion, death roll, or debris. That is, the ship is instantly gone from the mission and the only indication of what happened is a mission log entry.\r\n" "\tNon-player ship only!\r\n" @@ -41053,6 +41299,19 @@ SCP_vector Sexp_help = { "\t10: Show in mission log (optional; defaults to true)\r\n" }, + { OP_PROP_CREATE, "prop-create\r\n" + "\tCreates a new prop\r\n" + "\tTakes 5 to 8 arguments...\r\n" + "\t1: Name of new prop (use \"" SEXP_NONE_STRING "\" for a default name)\r\n" + "\t2: Class of new prop\r\n" + "\t3: X position\r\n" + "\t4: Y position\r\n" + "\t5: Z position\r\n" + "\t6: Pitch (optional)\r\n" + "\t7: Bank (optional)\r\n" + "\t8: Heading (optional)\r\n" + }, + // Goober5000 { OP_WEAPON_CREATE, "weapon-create\r\n" "\tCreates a new weapon\r\n" @@ -42307,6 +42566,28 @@ SCP_vector Sexp_help = { "\t1:\tObject name\r\n" }, + {OP_ADD_TO_COLGROUP_PROP, "add-to-collision-group-prop\r\n" + "\tAdds one or more props to the specified collision group. There are 32 collision groups, " + "and an object may be in several collision groups at the same time.\r\n" + "Takes 2 or more arguments...\r\n" + "\t1:\tGroup ID. Valid IDs are 0 through 31 inclusive.\r\n" + "\t2+:\tProp to add (props do need to be in-mission).\r\n" + }, + + {OP_REMOVE_FROM_COLGROUP_PROP, "remove-from-collision-group-prop\r\n" + "\tRemoves one or more props from the specified collision group. There are 32 collision groups, " + "and an object may be in several collision groups at the same time.\r\n" + "Takes 2 or more arguments...\r\n" + "\t1:\tGroup ID. Valid IDs are 0 through 31 inclusive.\r\n" + "\t2+:\tProp to remove (props do need to be in-mission).\r\n" + }, + + {OP_GET_COLGROUP_ID_PROP, "get-collision-group-prop\r\n" + "\tReturns an prop's collision group ID. Note that this ID is a bitfield.\r\n" + "Takes 1 Argument...\r\n" + "\t1:\tProp name\r\n" + }, + //Valathil {OP_SHIP_EFFECT, "ship-effect\r\n" "\tPlays an animated shader effect on the ship(s) or wing(s).\r\n" diff --git a/code/parse/sexp.h b/code/parse/sexp.h index 7b5b9ed5829..ae209a8ab35 100644 --- a/code/parse/sexp.h +++ b/code/parse/sexp.h @@ -38,6 +38,7 @@ enum sexp_opf_t : int { OPF_BOOL, OPF_NUMBER, OPF_SHIP, + OPF_PROP, OPF_WING, OPF_SUBSYSTEM, OPF_POINT, // either a 3d point in space, or a waypoint name @@ -66,6 +67,7 @@ enum sexp_opf_t : int { OPF_MEDAL_NAME, // name of medals OPF_WEAPON_NAME, // name of a weapon OPF_SHIP_CLASS_NAME, // name of a ship class + OPF_PROP_CLASS_NAME, // name of a prop class OPF_CUSTOM_HUD_GAUGE, // name of custom HUD gauge OPF_HUGE_WEAPON, // name of a secondary bomb type weapon OPF_SHIP_NOT_PLAYER, // a ship, but not a player ship @@ -594,6 +596,7 @@ enum : int { OP_CARGO_NO_DEPLETE, OP_SET_SPECIAL_WARPOUT_NAME, OP_SHIP_VANISH, + OP_PROP_VANISH, // MjnMixael OP_SHIELDS_ON, //-Sesquipedalian OP_SHIELDS_OFF, //-Sesquipedalian @@ -692,6 +695,7 @@ enum : int { OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD, // Goober5000 OP_SET_SKYBOX_MODEL, // taylor OP_SHIP_CREATE, + OP_PROP_CREATE, // MjnMixael OP_WEAPON_CREATE, // Goober5000 OP_SET_OBJECT_SPEED_X, // Deprecated by wookieejedi OP_SET_OBJECT_SPEED_Y, // Deprecated by wookieejedi @@ -819,6 +823,7 @@ enum : int { OP_ADD_TO_COLGROUP, // The E OP_REMOVE_FROM_COLGROUP, // The E OP_GET_COLGROUP_ID, // The E + OP_GET_COLGROUP_ID_PROP, // MjnMixael OP_SHIP_EFFECT, // Valathil OP_CLEAR_SUBTITLES, // The E OP_BEAM_FIRE_COORDS, // Goober5000 @@ -875,6 +880,8 @@ enum : int { OP_SET_TRAITOR_OVERRIDE, //MjnMixael OP_ADD_TO_COLGROUP_NEW, // Goober5000 OP_REMOVE_FROM_COLGROUP_NEW, // Goober5000 + OP_ADD_TO_COLGROUP_PROP, // MjnMixael + OP_REMOVE_FROM_COLGROUP_PROP, // MjnMixael OP_GET_POWER_OUTPUT, // The E OP_TURRET_SET_FORCED_TARGET, // Asteroth OP_TURRET_SET_FORCED_SUBSYS_TARGET, // Asteroth @@ -1186,6 +1193,7 @@ enum sexp_error_check SEXP_CHECK_INVALID_NUM = 101, // number is not valid SEXP_CHECK_INVALID_SHIP, // invalid ship name + SEXP_CHECK_INVALID_PROP, // invalid prop name SEXP_CHECK_INVALID_WING, // invalid wing name SEXP_CHECK_INVALID_SUBSYS, // invalid subsystem SEXP_CHECK_INVALID_IFF, // invalid iff string diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 1b7124c72f6..36e7b354b01 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -17,9 +17,9 @@ static SCP_vector Removed_props; /** * Return the index of Prop_info[].name that is *token. */ -static int prop_info_lookup_sub(const char* token) +int prop_info_lookup(const char* token) { - Assertion(token != nullptr, "NULL token passed to prop_info_lookup_sub"); + Assertion(token != nullptr, "NULL token passed to prop_info_lookup"); for (auto it = Prop_info.cbegin(); it != Prop_info.cend(); ++it) if (!stricmp(token, it->name)) @@ -88,7 +88,7 @@ void parse_prop_table(const char* filename) } // Check if prop exists already - int prop_id = prop_info_lookup_sub(fname); + int prop_id = prop_info_lookup(fname); // maybe remove it if (remove_prop) { @@ -416,7 +416,7 @@ void prop_render(object* obj, model_draw_list* scene) void spawn_test_prop() { - int prop_idx = prop_info_lookup_sub("TestProp"); // whatever’s in your props.tbl + int prop_idx = prop_info_lookup("TestProp"); // whatever’s in your props.tbl if (prop_idx < 0) { mprintf(("TEST: Prop not found!\n")); return; diff --git a/code/prop/prop.h b/code/prop/prop.h index b06118d0c59..10e633c0ace 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -56,4 +56,7 @@ void prop_render(object* obj, model_draw_list* scene); void props_level_close(); +int prop_info_lookup(const char* token); +int prop_name_lookup(const char* name); + void spawn_test_prop(); \ No newline at end of file diff --git a/code/prop/prop_flags.h b/code/prop/prop_flags.h index e0de9be515d..43ac7a606b4 100644 --- a/code/prop/prop_flags.h +++ b/code/prop/prop_flags.h @@ -24,7 +24,7 @@ FLAG_LIST(Prop_Flags){ FLAG_LIST(Info_Flags){ No_collide = 0, // No collisions No_fred, // not available in fred - No_impact_debris, // wookieejedi - Don't spawn the small debris on impact + //No_impact_debris, No_lighting, NUM_VALUES}; diff --git a/fred2/mainfrm.cpp b/fred2/mainfrm.cpp index a6c75767c1b..0f46ce0ae43 100644 --- a/fred2/mainfrm.cpp +++ b/fred2/mainfrm.cpp @@ -494,7 +494,11 @@ void color_combo_box_prop::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) if (pip != nullptr) { strText = _T(pip->name); } else { - strText = _T("Invalid index!"); + if (prop_type_combo_box_size == 0) { + strText = _T("No props available"); + } else { + strText = _T("Invalid index!"); + } } pDC->ExtTextOut(lpDrawItemStruct->rcItem.left, diff --git a/fred2/mainfrm.h b/fred2/mainfrm.h index 2c5c1b15dad..39cf1eebc2e 100644 --- a/fred2/mainfrm.h +++ b/fred2/mainfrm.h @@ -16,14 +16,14 @@ class color_combo_box_prop : public CComboBox { public: /** - * Gets the ship class corresponding to the item index + * Gets the prop class corresponding to the item index */ int GetPropClass(int item_index); /** - * Gets the item index corresponding to the ship class + * Gets the item index corresponding to the prop class */ - int GetItemIndex(int ship_class); + int GetItemIndex(int prop_class); private: /** diff --git a/fred2/sexp_tree.cpp b/fred2/sexp_tree.cpp index 437886ca36b..530c561815c 100644 --- a/fred2/sexp_tree.cpp +++ b/fred2/sexp_tree.cpp @@ -36,6 +36,7 @@ #include "nebula/neb.h" #include "nebula/neblightning.h" #include "jumpnode/jumpnode.h" +#include "prop/prop.h" #include "AddVariableDlg.h" #include "ModifyVariableDlg.h" #include "gamesnd/eventmusic.h" // for change-soundtrack @@ -3322,6 +3323,10 @@ int sexp_tree::get_default_value(sexp_list_item *item, char *text_buf, int op, i str = ""; break; + case OPF_PROP: + str = ""; + break; + case OPF_ORDER_RECIPIENT: str = ""; break; @@ -3498,6 +3503,7 @@ int sexp_tree::query_default_argument_available(int op, int i) case OPF_WEAPON_NAME: case OPF_INTEL_NAME: case OPF_SHIP_CLASS_NAME: + case OPF_PROP_CLASS_NAME: case OPF_HUGE_WEAPON: case OPF_JUMP_NODE_NAME: case OPF_AMBIGUOUS: @@ -3574,6 +3580,16 @@ int sexp_tree::query_default_argument_available(int op, int i) return 0; + case OPF_PROP: + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_PROP) + return 1; + + ptr = GET_NEXT(ptr); + } + return 0; + case OPF_SHIP_NOT_PLAYER: case OPF_ORDER_RECIPIENT: ptr = GET_FIRST(&obj_used_list); @@ -5400,6 +5416,10 @@ sexp_list_item *sexp_tree::get_listing_opf(int opf, int parent_node, int arg_ind list = get_listing_opf_ship(parent_node); break; + case OPF_PROP: + list = get_listing_opf_prop(parent_node); + break; + case OPF_WING: list = get_listing_opf_wing(); break; @@ -5571,6 +5591,10 @@ sexp_list_item *sexp_tree::get_listing_opf(int opf, int parent_node, int arg_ind list = get_listing_opf_ship_class_name(); break; + case OPF_PROP_CLASS_NAME: + list = get_listing_opf_prop_class_name(); + break; + case OPF_HUGE_WEAPON: list = get_listing_opf_huge_weapon(); break; @@ -6133,6 +6157,23 @@ sexp_list_item *sexp_tree::get_listing_opf_ship(int parent_node) return head.next; } +sexp_list_item *sexp_tree::get_listing_opf_prop(int parent_node) +{ + object *ptr; + sexp_list_item head; + + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_PROP) { + head.add_data(Props[ptr->instance].prop_name); + } + + ptr = GET_NEXT(ptr); + } + + return head.next; +} + sexp_list_item *sexp_tree::get_listing_opf_wing() { int i; @@ -7229,6 +7270,16 @@ sexp_list_item *sexp_tree::get_listing_opf_ship_class_name() return head.next; } +sexp_list_item* sexp_tree::get_listing_opf_prop_class_name() +{ + sexp_list_item head; + + for (auto& pi : Prop_info) + head.add_data(pi.name); + + return head.next; +} + sexp_list_item *sexp_tree::get_listing_opf_huge_weapon() { sexp_list_item head; diff --git a/fred2/sexp_tree.h b/fred2/sexp_tree.h index 965f6430b08..81d1b4fc21f 100644 --- a/fred2/sexp_tree.h +++ b/fred2/sexp_tree.h @@ -220,6 +220,7 @@ class sexp_tree : public CTreeCtrl sexp_list_item *get_listing_opf_positive(); sexp_list_item *get_listing_opf_number(); sexp_list_item *get_listing_opf_ship(int parent_node = -1); + sexp_list_item *get_listing_opf_prop(int parent_node = -1); sexp_list_item *get_listing_opf_wing(); sexp_list_item *get_listing_opf_subsystem(int parent_node, int arg_index); sexp_list_item *get_listing_opf_subsystem_type(int parent_node); @@ -249,6 +250,7 @@ class sexp_tree : public CTreeCtrl sexp_list_item *get_listing_opf_medal_name(); sexp_list_item *get_listing_opf_weapon_name(); sexp_list_item *get_listing_opf_ship_class_name(); + sexp_list_item *get_listing_opf_prop_class_name(); sexp_list_item *get_listing_opf_huge_weapon(); sexp_list_item *get_listing_opf_ship_not_player(); sexp_list_item *get_listing_opf_jump_nodes(); diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index d5a86a1346a..d4a3424d5f5 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include "missionsave.h" #include "util.h" @@ -3174,6 +3175,8 @@ void CFred_mission_save::save_mission_internal(const char* pathname) err = -7; } else if (save_wings()) { err = -8; + } else if (save_props()) { + err = -18; } else if (save_events()) { err = -9; } else if (save_goals()) { @@ -5180,7 +5183,7 @@ int CFred_mission_save::save_wings() } count++; - required_string_either_fred("$Name:", "#Events"); + required_string_one_of_fred(3, "$Name:", "#Events", "#Props"); required_string_fred("$Name:"); parse_comments(2); fout(" %s", Wings[i].name); @@ -5465,5 +5468,50 @@ int CFred_mission_save::save_wings() return err; } +int CFred_mission_save::save_props() +{ + if (save_format != MissionFormat::RETAIL) { + fred_parse_flag = 0; + required_string_fred("#Props"); + parse_comments(2); + fout("\t\t;! %d total", static_cast(Props.size())); + + for (int i = 0; i < static_cast(Props.size()); i++) { + required_string_either_fred("$Name:", "#Events"); + required_string_fred("$Name:"); + parse_comments(2); + fout(" %s", Props[i].prop_name); + + required_string_fred("$Class:"); + parse_comments(2); + fout(" %d", Props[i].prop_info_index); + + required_string_fred("$Location:"); + parse_comments(); + save_vector(Objects[Props[i].objnum].pos); + + required_string_fred("$Orientation:"); + parse_comments(); + save_matrix(Objects[Props[i].objnum].orient); + + if (optional_string_fred("+Flags:", "$Name:")) { + parse_comments(); + fout(" ("); + } else + fout("\n+Flags: ("); + + if (!(Objects[Props[i].objnum].flags[Object::Object_Flags::Collides])) + fout(" \"no_collide\""); + fout(" )"); + + fso_comment_pop(); + } + } + + fso_comment_pop(true); + // Assert(count == Num_props); + return err; +} + } } diff --git a/qtfred/src/mission/missionsave.h b/qtfred/src/mission/missionsave.h index 470853d8345..2a126cee381 100644 --- a/qtfred/src/mission/missionsave.h +++ b/qtfred/src/mission/missionsave.h @@ -516,6 +516,16 @@ class CFred_mission_save { */ int save_wings(); + /** + * @brief Saves the prop entries to file + * + * @details Returns the value of CFred_mission_save::err, which is: + * + * @returns 0 for no error, or + * @returns A negative value if an error occurred + */ + int save_props(); + /** * @brief Utility function to save a raw comment, the start of which precedes the current raw_ptr, to a file while handling newlines properly */ diff --git a/qtfred/src/ui/widgets/sexp_tree.cpp b/qtfred/src/ui/widgets/sexp_tree.cpp index 3562760b57a..835c42105c2 100644 --- a/qtfred/src/ui/widgets/sexp_tree.cpp +++ b/qtfred/src/ui/widgets/sexp_tree.cpp @@ -43,6 +43,7 @@ #include "localization/localize.h" #include "mission/missiongoals.h" #include "ship/ship.h" +#include "prop/prop.h" #include #include @@ -1358,6 +1359,10 @@ int sexp_tree::get_default_value(sexp_list_item* item, char* text_buf, int op, i str = ""; break; + case OPF_PROP: + str = ""; + break; + case OPF_ORDER_RECIPIENT: str = ""; break; @@ -1534,6 +1539,7 @@ int sexp_tree::query_default_argument_available(int op, int i) { case OPF_WEAPON_NAME: case OPF_INTEL_NAME: case OPF_SHIP_CLASS_NAME: + case OPF_PROP_CLASS_NAME: case OPF_HUGE_WEAPON: case OPF_JUMP_NODE_NAME: case OPF_AMBIGUOUS: @@ -1611,6 +1617,16 @@ int sexp_tree::query_default_argument_available(int op, int i) { return 0; + case OPF_PROP: + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_PROP) + return 1; + + ptr = GET_NEXT(ptr); + } + return 0; + case OPF_SHIP_NOT_PLAYER: case OPF_ORDER_RECIPIENT: ptr = GET_FIRST(&obj_used_list); @@ -3341,6 +3357,10 @@ sexp_list_item* sexp_tree::get_listing_opf(int opf, int parent_node, int arg_ind list = get_listing_opf_ship(parent_node); break; + case OPF_PROP: + list = get_listing_opf_prop(parent_node); + break; + case OPF_WING: list = get_listing_opf_wing(); break; @@ -3512,6 +3532,10 @@ sexp_list_item* sexp_tree::get_listing_opf(int opf, int parent_node, int arg_ind list = get_listing_opf_ship_class_name(); break; + case OPF_PROP_CLASS_NAME: + list = get_listing_opf_prop_class_name(); + break; + case OPF_HUGE_WEAPON: list = get_listing_opf_huge_weapon(); break; @@ -4037,6 +4061,23 @@ sexp_list_item* sexp_tree::get_listing_opf_ship(int parent_node) { return head.next; } +sexp_list_item *sexp_tree::get_listing_opf_prop(int parent_node) +{ + object *ptr; + sexp_list_item head; + + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_PROP) { + head.add_data(Props[ptr->instance].prop_name); + } + + ptr = GET_NEXT(ptr); + } + + return head.next; +} + sexp_list_item* sexp_tree::get_listing_opf_wing() { int i; sexp_list_item head; @@ -5102,6 +5143,17 @@ sexp_list_item* sexp_tree::get_listing_opf_ship_class_name() { return head.next; } +sexp_list_item* sexp_tree::get_listing_opf_prop_class_name() +{ + sexp_list_item head; + + for (auto& pi : Prop_info) { + head.add_data(pi.name); + } + + return head.next; +} + sexp_list_item* sexp_tree::get_listing_opf_huge_weapon() { sexp_list_item head; diff --git a/qtfred/src/ui/widgets/sexp_tree.h b/qtfred/src/ui/widgets/sexp_tree.h index 4f45110aad5..d07c934e693 100644 --- a/qtfred/src/ui/widgets/sexp_tree.h +++ b/qtfred/src/ui/widgets/sexp_tree.h @@ -277,6 +277,7 @@ class sexp_tree: public QTreeWidget { sexp_list_item* get_listing_opf_positive(); sexp_list_item* get_listing_opf_number(); sexp_list_item* get_listing_opf_ship(int parent_node = -1); + sexp_list_item* get_listing_opf_prop(int parent_node = -1); sexp_list_item* get_listing_opf_wing(); sexp_list_item* get_listing_opf_subsystem(int parent_node, int arg_index); sexp_list_item* get_listing_opf_subsystem_type(int parent_node); @@ -306,6 +307,7 @@ class sexp_tree: public QTreeWidget { sexp_list_item* get_listing_opf_medal_name(); sexp_list_item* get_listing_opf_weapon_name(); sexp_list_item* get_listing_opf_ship_class_name(); + sexp_list_item* get_listing_opf_prop_class_name(); sexp_list_item* get_listing_opf_huge_weapon(); sexp_list_item* get_listing_opf_ship_not_player(); sexp_list_item* get_listing_opf_jump_nodes(); From 8026eaa5c956ad51cfab0ad471de00d46652d609 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Thu, 12 Jun 2025 16:56:50 -0500 Subject: [PATCH 04/25] lua table prop support --- code/model/modelrender.cpp | 17 ++ code/model/modelrender.h | 3 +- code/parse/sexp.h | 2 + code/parse/sexp/LuaSEXP.cpp | 33 +++ code/prop/prop.cpp | 181 ++++++++++++++ code/prop/prop.h | 28 ++- code/scripting/api/libs/tables.cpp | 40 +++ code/scripting/api/objs/prop.cpp | 130 ++++++++++ code/scripting/api/objs/prop.h | 12 + code/scripting/api/objs/propclass.cpp | 341 ++++++++++++++++++++++++++ code/scripting/api/objs/propclass.h | 11 + code/source_groups.cmake | 4 + 12 files changed, 793 insertions(+), 9 deletions(-) create mode 100644 code/scripting/api/objs/prop.cpp create mode 100644 code/scripting/api/objs/prop.h create mode 100644 code/scripting/api/objs/propclass.cpp create mode 100644 code/scripting/api/objs/propclass.h diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index 3099ba74a38..54fcb1a997c 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -25,6 +25,7 @@ #include "mod_table/mod_table.h" #include "nebula/neb.h" #include "particle/particle.h" +#include "prop/prop.h" #include "render/3dinternal.h" #include "render/batching.h" #include "ship/ship.h" @@ -3049,6 +3050,22 @@ bool render_tech_model(tech_render_type model_type, int x1, int y1, int x2, int break; + case TECH_PROP: + prop_info* pip; + pip = &Prop_info[class_idx]; + + closeup_pos = &pip->closeup_pos; + closeup_zoom = pip->closeup_zoom; + + if (pip->flags[Prop::Info_Flags::No_lighting]) { + model_lighting = false; + } + + // Make sure model is loaded + model_num = model_load(pip->pof_file); + + break; + case TECH_WEAPON: weapon_info* wip; wip = &Weapon_info[class_idx]; diff --git a/code/model/modelrender.h b/code/model/modelrender.h index c516eb7ba0a..f43cf10ddd9 100644 --- a/code/model/modelrender.h +++ b/code/model/modelrender.h @@ -33,7 +33,8 @@ typedef enum { TECH_SHIP, TECH_WEAPON, TECH_POF, - TECH_JUMP_NODE + TECH_JUMP_NODE, + TECH_PROP, } tech_render_type; inline int in_box(const vec3d *min, const vec3d *max, const vec3d *pos, const vec3d *view_position) diff --git a/code/parse/sexp.h b/code/parse/sexp.h index ae209a8ab35..0fb22e5d5e9 100644 --- a/code/parse/sexp.h +++ b/code/parse/sexp.h @@ -19,6 +19,7 @@ class ship_subsys; class ship; +class prop; class waypoint_list; class object; class waypoint; @@ -1487,6 +1488,7 @@ struct wing; // Goober5000 - stuff with caching // (included in the header file because Lua uses the first three) extern const ship_registry_entry *eval_ship(int node); +extern const prop* eval_prop(int node); extern wing *eval_wing(int node); extern int sexp_get_variable_index(int node); extern int sexp_atoi(int node); diff --git a/code/parse/sexp/LuaSEXP.cpp b/code/parse/sexp/LuaSEXP.cpp index 2acc45932d1..72f0f8d9ba0 100644 --- a/code/parse/sexp/LuaSEXP.cpp +++ b/code/parse/sexp/LuaSEXP.cpp @@ -17,6 +17,8 @@ #include "scripting/api/objs/message.h" #include "scripting/api/objs/model.h" #include "scripting/api/objs/oswpt.h" +#include "scripting/api/objs/prop.h" +#include "scripting/api/objs/propclass.h" #include "scripting/api/objs/sexpvar.h" #include "scripting/api/objs/ship.h" #include "scripting/api/objs/shipclass.h" @@ -28,6 +30,7 @@ #include "scripting/api/objs/wing.h" #include "scripting/scripting.h" #include "ship/ship.h" +#include "prop/prop.h" #include "utils/string_utils.h" #include "weapon/weapon.h" @@ -41,6 +44,8 @@ static SCP_unordered_map parameter_type_mapping { { "string", OPF_STRING }, { "ship", OPF_SHIP }, { "shipname", OPF_SHIP }, + { "prop", OPF_PROP }, + { "propname", OPF_PROP }, { "team", OPF_IFF }, { "waypointpath", OPF_WAYPOINT_PATH }, { "waypoint", OPF_POINT }, @@ -48,6 +53,7 @@ static SCP_unordered_map parameter_type_mapping { { "message", OPF_MESSAGE }, { "wing", OPF_WING }, { "shipclass", OPF_SHIP_CLASS_NAME }, + { "propclass", OPF_PROP_CLASS_NAME }, { "weaponclass", OPF_WEAPON_NAME }, { "soundentry", OPF_GAME_SND }, { "ship+waypoint",OPF_SHIP_POINT }, @@ -199,6 +205,29 @@ luacpp::LuaValue LuaSEXP::sexpToLua(int node, int argnum, int parent_node) const return LuaValue::createValue(_action.getLuaState(), l_Ship.Set(object_h(objp))); } + case OPF_PROP: { + auto ship_entry = eval_prop(node); + + // if this is a shipname type, we want the name of a valid ship but not the ship itself + // (if the ship is not valid, return an empty string) + if (argtype.first == "propname") { + return LuaValue::createValue(_action.getLuaState(), ship_entry ? ship_entry->prop_name : ""); + } + + if (!ship_entry || (ship_entry->objnum >= 0)) { + // Name is invalid + return LuaValue::createValue(_action.getLuaState(), l_Prop.Set(object_h())); + } + + auto objp = &Objects[ship_entry->objnum]; + + // The other SEXP code does not validate the object type so this should be safe + Assertion(objp->type == OBJ_PROP, + "Prop '%s' was found in the Props array but has a different object type in the Objects array. Get a coder!", + CTEXT(node)); + + return LuaValue::createValue(_action.getLuaState(), l_Prop.Set(object_h(objp))); + } case OPF_MESSAGE: { auto name = CTEXT(node); @@ -224,6 +253,10 @@ luacpp::LuaValue LuaSEXP::sexpToLua(int node, int argnum, int parent_node) const auto name = CTEXT(node); return LuaValue::createValue(_action.getLuaState(), l_Shipclass.Set(ship_info_lookup(name))); } + case OPF_PROP_CLASS_NAME: { + auto name = CTEXT(node); + return LuaValue::createValue(_action.getLuaState(), l_Propclass.Set(prop_info_lookup(name))); + } case OPF_WEAPON_NAME: { auto name = CTEXT(node); return LuaValue::createValue(_action.getLuaState(), l_Weaponclass.Set(weapon_info_lookup(name))); diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 36e7b354b01..43fddb24e1a 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -8,6 +8,8 @@ MONITOR(NumPropsRend) +bool Props_inited = false; + SCP_vector Prop_info; SCP_vector Props; @@ -142,9 +144,67 @@ void parse_prop_table(const char* filename) required_string("+POF file:"); stuff_string(pip->pof_file, F_NAME, MAX_FILENAME_LEN); + if (optional_string("$Closeup_pos:")) { + stuff_vec3d(&pip->closeup_pos); + } else if (first_time && VALID_FNAME(pip->pof_file)) { + // Calculate from the model file. This is inefficient, but whatever + int model_idx = model_load(pip->pof_file); + polymodel* pm = model_get(model_idx); + + // Go through, find best + pip->closeup_pos.xyz.z = fabsf(pm->maxs.xyz.z); + + float temp = fabsf(pm->mins.xyz.z); + if (temp > pip->closeup_pos.xyz.z) + pip->closeup_pos.xyz.z = temp; + + // Now multiply by 2 + pip->closeup_pos.xyz.z *= -2.0f; + + // We're done with the model. + model_unload(model_idx); + } + + if (optional_string("$Closeup_zoom:")) { + stuff_float(&pip->closeup_zoom); + + if (pip->closeup_zoom <= 0.0f) { + mprintf(("Warning! Prop '%s' has a $Closeup_zoom value that is less than or equal to 0 (%f). Setting " + "to default value.\n", + pip->name, + pip->closeup_zoom)); + pip->closeup_zoom = 0.5f; + } + } + if(optional_string("$Detail distance:")) { pip->num_detail_levels = (int)stuff_int_list(pip->detail_distance, MAX_PROP_DETAIL_LEVELS, RAW_INTEGER_TYPE); } + + if (optional_string("$Custom data:")) { + parse_string_map(pip->custom_data, "$end_custom_data", "+Val:"); + } + + if (optional_string("$Custom Strings")) { + while (optional_string("$Name:")) { + custom_string cs; + + // The name of the string + stuff_string(cs.name, F_NAME); + + // Arbitrary string value used for grouping strings together + required_string("+Value:"); + stuff_string(cs.value, F_NAME); + + // The string text itself + required_string("+String:"); + stuff_string(cs.text, F_MULTITEXT); + + pip->custom_strings.push_back(cs); + } + + required_string("$end_custom_strings"); + } } required_string("#END"); @@ -160,6 +220,8 @@ void prop_init() // parse any modular tables parse_modular_table("*-prp.tbm", parse_prop_table); + + Props_inited = true; } /** @@ -327,6 +389,125 @@ void prop_delete(object* obj) Props.erase(Props.begin() + num); } +/** + * Change the prop model for a prop to that for prop class 'prop_type' + * + * @param n index of prop in ::Props[] array + * @param prop_type prop class (index into ::Prop_info vector) + */ +static void prop_model_change(int n, int prop_type) +{ + Assert( n >= 0 && n < MAX_PROPS ); + prop* sp = &Props[n]; + prop_info* sip = &(Prop_info[prop_type]); + object* objp = &Objects[sp->objnum]; + polymodel_instance* pmi = model_get_instance(sp->model_instance_num); + + // get new model + if (sip->model_num == -1) { + sip->model_num = model_load(sip->pof_file); + } + + polymodel* pm = model_get(sip->model_num); + Objects[sp->objnum].radius = model_get_radius(pm->id); + + // page in nondims in game + if ( !Fred_running ) + model_page_in_textures(sip->model_num, prop_type); + + // allocate memory for keeping glow point bank status (enabled/disabled) + { + bool val = true; // default value, enabled + + // clear out any old gpb's first, then add new ones if needed + sp->glow_point_bank_active.clear(); + + if (pm->n_glow_point_banks) + sp->glow_point_bank_active.resize( pm->n_glow_point_banks, val ); + + // set any default off banks to off + for (int bank = 0; bank < pm->n_glow_point_banks; bank++) { + glow_point_bank_override* gpo = nullptr; + + SCP_unordered_map::iterator gpoi = sip->glowpoint_bank_override_map.find(bank); + if (gpoi != sip->glowpoint_bank_override_map.end()) { + gpo = (glow_point_bank_override*)sip->glowpoint_bank_override_map[bank]; + } + + if (gpo) { + if (gpo->default_off) { + sp->glow_point_bank_active[bank] = false; + } + } + } + } + + if ( sip->num_detail_levels != pm->n_detail_levels ) + { + if ( !Is_standalone ) + { + // just log to file for standalone servers + Warning(LOCATION, "For prop '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels ); + } + else + { + nprintf(("Warning", "For prop '%s', detail level mismatch. Table has %d, POF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels )); + } + } + for (int i=0; in_detail_levels; i++ ) + pm->detail_depth[i] = (i < sip->num_detail_levels) ? i2fl(sip->detail_distance[i]) : 0.0f; + + // reset texture animations + //sp->base_texture_anim_timestamp = _timestamp(); + + model_delete_instance(sp->model_instance_num); + + // create new model instance data + // note: this is needed for both subsystem stuff and submodel animation stuff + sp->model_instance_num = model_create_instance(OBJ_INDEX(objp), sip->model_num); + pmi = model_get_instance(sp->model_instance_num); +} + +/** + * Change the prop class on a prop, and changing all required information + * for consistency + * + * @param n index of prop in ::Props[] array + * @param prop_type prop class (index into ::Prop_info vector) + * @param by_sexp SEXP reference + */ +void change_prop_type(int n, int prop_type) +{ + Assert( n >= 0 && n < MAX_PROPS ); + prop* sp = &Props[n]; + + // do a quick out if we're already using the new ship class + if (sp->prop_info_index == prop_type) + return; + + int objnum = sp->objnum; + //auto prop_entry = ship_registry_get(sp->prop_name); + + prop_info* sip = &(Prop_info[prop_type]); + prop_info* sip_orig = &Prop_info[sp->prop_info_index]; + object* objp = &Objects[objnum]; + + // point to new ship data + prop_model_change(n, prop_type); + sp->prop_info_index = prop_type; + + // get the before and after models (the new model may have only been loaded in ship_model_change) + auto pm = model_get(sip->model_num); + auto pm_orig = model_get(sip_orig->model_num); + + // check class-specific flags + + if (sip->flags[Prop::Info_Flags::No_collide]) // changing TO a no-collision ship class + obj_set_flags(objp, objp->flags - Object::Object_Flags::Collides); + else if (sip_orig->flags[Prop::Info_Flags::No_collide]) // changing FROM a no-collision ship class + obj_set_flags(objp, objp->flags + Object::Object_Flags::Collides); +} + void prop_render(object* obj, model_draw_list* scene) { int num = obj->instance; diff --git a/code/prop/prop.h b/code/prop/prop.h index 10e633c0ace..babfafca997 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -10,14 +10,17 @@ #define MAX_PROP_DETAIL_LEVELS MAX_SHIP_DETAIL_LEVELS typedef struct prop_info { - char name[NAME_LENGTH]; // Prop name - char pof_file[MAX_FILENAME_LEN]; // Pof filename - int model_num; // The model number of the loaded POF - //int model_instance; // The model instance - int num_detail_levels; // Detail levels of the model - int detail_distance[MAX_PROP_DETAIL_LEVELS]; // distance to change detail levels at - SCP_unordered_map glowpoint_bank_override_map; - flagset flags; // Info flags + char name[NAME_LENGTH]; // Prop name + char pof_file[MAX_FILENAME_LEN]; // Pof filename + vec3d closeup_pos; // position for camera when using ship in closeup view (eg briefing and techroom) + float closeup_zoom; // zoom when using ship in closeup view (eg briefing and techroom) + int model_num; // The model number of the loaded POF + int num_detail_levels; // Detail levels of the model + int detail_distance[MAX_PROP_DETAIL_LEVELS]; // distance to change detail levels at + SCP_unordered_map glowpoint_bank_override_map; // Glow point bank overrides currently unused + flagset flags; // Info flags + SCP_map custom_data; // Custom data for this prop + SCP_vector custom_strings; // Custom strings for this prop } prop_info; typedef struct prop { @@ -41,11 +44,18 @@ typedef struct parsed_prop { flagset flags; } parsed_prop; +extern bool Props_inited; + // Global prop info array extern SCP_vector Prop_info; extern SCP_vector Props; +inline int prop_info_size() +{ + return static_cast(Prop_info.size()); +} + // Load all props from table void prop_init(); @@ -59,4 +69,6 @@ void props_level_close(); int prop_info_lookup(const char* token); int prop_name_lookup(const char* name); +void change_prop_type(int n, int prop_type); + void spawn_test_prop(); \ No newline at end of file diff --git a/code/scripting/api/libs/tables.cpp b/code/scripting/api/libs/tables.cpp index 572f752e205..dec43339e48 100644 --- a/code/scripting/api/libs/tables.cpp +++ b/code/scripting/api/libs/tables.cpp @@ -6,6 +6,7 @@ #include "scripting/api/objs/decaldefinition.h" #include "scripting/api/objs/fireballclass.h" #include "scripting/api/objs/intelentry.h" +#include "scripting/api/objs/propclass.h" #include "scripting/api/objs/shipclass.h" #include "scripting/api/objs/shiptype.h" #include "scripting/api/objs/team_colors.h" @@ -17,6 +18,7 @@ #include "fireball/fireballs.h" #include "menuui/techmenu.h" #include "mission/missionmessage.h" +#include "prop/prop.h" #include "ship/ship.h" #include "weapon/weapon.h" #include "particle/ParticleManager.h" @@ -110,6 +112,44 @@ ADE_FUNC(__len, l_Tables_ShipTypes, nullptr, "Number of ship types", "number", " return ade_set_args(L, "i", Ship_types.size()); } +//*****SUBLIBRARY: Tables/ShipClasses +ADE_LIB_DERIV(l_Tables_PropClasses, "PropClasses", NULL, NULL, l_Tables); +ADE_INDEXER(l_Tables_PropClasses, "number/string IndexOrName", "Array of prop classes", "propclass", "Prop class handle, or invalid handle if index is invalid") +{ + if(!Props_inited) + return ade_set_error(L, "o", l_Propclass.Set(-1)); + + const char* name; + if(!ade_get_args(L, "*s", &name)) + return ade_set_error(L, "o", l_Propclass.Set(-1)); + + int idx = prop_info_lookup(name); + + if(idx < 0) { + try { + idx = std::stoi(name); + idx--; // Lua->FS2 + } catch (const std::exception&) { + // Not a number + return ade_set_error(L, "o", l_Propclass.Set(-1)); + } + + if (idx < 0 || idx >= prop_info_size()) { + return ade_set_error(L, "o", l_Propclass.Set(-1)); + } + } + + return ade_set_args(L, "o", l_Propclass.Set(idx)); +} + +ADE_FUNC(__len, l_Tables_PropClasses, NULL, "Number of prop classes", "number", "Number of prop classes, or 0 if prop classes haven't been loaded yet") +{ + if(!Props_inited) + return ade_set_args(L, "i", 0); //No props loaded...should be 0 + + return ade_set_args(L, "i", Prop_info.size()); +} + //*****SUBLIBRARY: Tables/WeaponClasses ADE_LIB_DERIV(l_Tables_WeaponClasses, "WeaponClasses", NULL, NULL, l_Tables); diff --git a/code/scripting/api/objs/prop.cpp b/code/scripting/api/objs/prop.cpp new file mode 100644 index 00000000000..7ca0b2808af --- /dev/null +++ b/code/scripting/api/objs/prop.cpp @@ -0,0 +1,130 @@ +// +// + +#include "globalincs/utility.h" + +#include "animation_handle.h" +#include "cockpit_display.h" +#include "enums.h" +#include "message.h" +#include "modelinstance.h" +#include "object.h" +#include "order.h" +#include "parse_object.h" +#include "ship.h" +#include "ship_bank.h" +#include "shipclass.h" +#include "subsystem.h" +#include "team.h" +#include "texture.h" +#include "vecmath.h" +#include "waypoint.h" +#include "weaponclass.h" +#include "wing.h" + +#include "prop.h" +#include "propclass.h" + +#include "ai/aigoals.h" +#include "hud/hudets.h" +#include "hud/hudshield.h" +#include "mission/missionlog.h" +#include "mission/missionmessage.h" +#include "model/model.h" +#include "network/multiutil.h" +#include "object/object.h" +#include "object/objectdock.h" +#include "parse/parselo.h" +#include "playerman/player.h" +#include "scripting/api/objs/message.h" +#include "ship/ship.h" +#include "ship/shipfx.h" +#include "ship/shiphit.h" + +#include "prop/prop.h" + +namespace scripting { +namespace api { + +//**********HANDLE: Prop +ADE_OBJ_DERIV(l_Prop, object_h, "prop", "Prop handle", l_Object); + +ADE_VIRTVAR(Name, + l_Prop, + "string", + "Prop name. This is the actual name of the prop.", + "string", + "Prop name, or empty string if handle is invalid") +{ + object_h* objh; + const char* s = nullptr; + if (!ade_get_args(L, "o|s", l_Prop.GetPtr(&objh), &s)) + return ade_set_error(L, "s", ""); + + if (!objh->isValid()) + return ade_set_error(L, "s", ""); + + prop* propp = &Props[objh->objp()->instance]; + + if (ADE_SETTING_VAR && s != nullptr) { + auto len = sizeof(propp->prop_name); + strncpy(propp->prop_name, s, len); + propp->prop_name[len - 1] = 0; + } + + return ade_set_args(L, "s", propp->prop_name); +} + +ADE_VIRTVAR(Class, + l_Prop, + "propclass", + "Prop class", + "propclass", + "Prop class, or invalid propclass handle if prop handle is invalid") +{ + object_h* objh; + int idx = -1; + if (!ade_get_args(L, "o|o", l_Prop.GetPtr(&objh), l_Propclass.Get(&idx))) + return ade_set_error(L, "o", l_Propclass.Set(-1)); + + if (!objh->isValid()) + return ade_set_error(L, "o", l_Propclass.Set(-1)); + + prop* propp = &Props[objh->objp()->instance]; + + if (ADE_SETTING_VAR && idx > -1) { + change_prop_type(objh->objp()->instance, idx); + } + + if (propp->prop_info_index < 0) + return ade_set_error(L, "o", l_Propclass.Set(-1)); + + return ade_set_args(L, "o", l_Propclass.Set(propp->prop_info_index)); +} + +ADE_VIRTVAR(Textures, + l_Prop, + "modelinstancetextures", + "Gets prop textures", + "modelinstancetextures", + "Prop textures, or invalid proptextures handle if prop handle is invalid") +{ + object_h* sh = nullptr; + object_h* dh; + if (!ade_get_args(L, "o|o", l_Prop.GetPtr(&dh), l_Prop.GetPtr(&sh))) + return ade_set_error(L, "o", l_ModelInstanceTextures.Set(modelinstance_h())); + + if (!dh->isValid()) + return ade_set_error(L, "o", l_ModelInstanceTextures.Set(modelinstance_h())); + + polymodel_instance* dest = model_get_instance(Props[dh->objp()->instance].model_instance_num); + + if (ADE_SETTING_VAR && sh && sh->isValid()) { + dest->texture_replace = model_get_instance(Props[sh->objp()->instance].model_instance_num)->texture_replace; + } + + return ade_set_args(L, "o", l_ModelInstanceTextures.Set(modelinstance_h(dest))); +} + +} // namespace api +} // namespace scripting diff --git a/code/scripting/api/objs/prop.h b/code/scripting/api/objs/prop.h new file mode 100644 index 00000000000..4412f39174a --- /dev/null +++ b/code/scripting/api/objs/prop.h @@ -0,0 +1,12 @@ +#pragma once + +#include "object/object.h" +#include "scripting/ade_api.h" + +namespace scripting { +namespace api { + +//**********HANDLE: Prop +DECLARE_ADE_OBJ(l_Prop, object_h); +} // namespace api +} // namespace scripting diff --git a/code/scripting/api/objs/propclass.cpp b/code/scripting/api/objs/propclass.cpp new file mode 100644 index 00000000000..46dc5ec09f0 --- /dev/null +++ b/code/scripting/api/objs/propclass.cpp @@ -0,0 +1,341 @@ +#include "propclass.h" +#include "model.h" +#include "vecmath.h" +#include "prop/prop.h" + +namespace scripting { +namespace api { + +//**********HANDLE: Propclass +ADE_OBJ(l_Propclass, int, "propclass", "Prop class handle"); + +ADE_FUNC(__tostring, + l_Propclass, + NULL, + "Prop class name", + "string", + "Prop class name, or an empty string if handle is invalid") +{ + int idx; + const char* s = nullptr; + if (!ade_get_args(L, "o|s", l_Propclass.Get(&idx), &s)) + return ade_set_error(L, "s", ""); + + if (idx < 0 || idx >= prop_info_size()) + return ade_set_error(L, "s", ""); + + return ade_set_args(L, "s", Prop_info[idx].name); +} + +ADE_FUNC(__eq, + l_Propclass, + "propclass, propclass", + "Checks if the two classes are equal", + "boolean", + "true if equal, false otherwise") +{ + int idx1, idx2; + if (!ade_get_args(L, "oo", l_Propclass.Get(&idx1), l_Propclass.Get(&idx2))) + return ade_set_error(L, "b", false); + + if (idx1 < 0 || idx1 >= prop_info_size()) + return ade_set_error(L, "b", false); + + if (idx2 < 0 || idx2 >= prop_info_size()) + return ade_set_error(L, "b", false); + + return ade_set_args(L, "b", idx1 == idx2); +} + +ADE_VIRTVAR(Name, + l_Propclass, + "string", + "Prop class name", + "string", + "Prop class name, or an empty string if handle is invalid") +{ + int idx; + const char* s = nullptr; + if (!ade_get_args(L, "o|s", l_Propclass.Get(&idx), &s)) + return ade_set_error(L, "s", ""); + + if (idx < 0 || idx >= prop_info_size()) + return ade_set_error(L, "s", ""); + + if (ADE_SETTING_VAR) { + LuaError(L, "Setting prop class name is not supported"); + } + + return ade_set_args(L, "s", Prop_info[idx].name); +} + +ADE_VIRTVAR(Model, + l_Propclass, + "model", + "Model", + "model", + "Prop class model, or invalid model handle if propclass handle is invalid") +{ + int idx = -1; + model_h* mdl = NULL; + if (!ade_get_args(L, "o|o", l_Propclass.Get(&idx), l_Model.GetPtr(&mdl))) + return ade_set_error(L, "o", l_Model.Set(model_h(-1))); + + if (idx < 0 || idx >= prop_info_size()) + return ade_set_error(L, "o", l_Model.Set(model_h(-1))); + + prop_info* pip = &Prop_info[idx]; + + if (ADE_SETTING_VAR) { + LuaError(L, "Setting prop class model is not supported"); + } + + return ade_set_args(L, "o", l_Model.Set(model_h(pip->model_num))); +} + +ADE_VIRTVAR(CustomData, + l_Propclass, + nullptr, + "Gets the custom data table for this prop class", + "table", + "The prop class's custom data table") +{ + int idx; + if (!ade_get_args(L, "o", l_Propclass.Get(&idx))) + return ADE_RETURN_NIL; + + if (idx < 0 || idx >= prop_info_size()) + return ADE_RETURN_NIL; + + auto table = luacpp::LuaTable::create(L); + + prop_info* pip = &Prop_info[idx]; + + for (const auto& pair : pip->custom_data) { + table.addValue(pair.first, pair.second); + } + + return ade_set_args(L, "t", &table); +} + +ADE_FUNC(hasCustomData, + l_Propclass, + nullptr, + "Detects whether the prop class has any custom data", + "boolean", + "true if the propclass's custom_data is not empty, false otherwise") +{ + int idx; + if (!ade_get_args(L, "o", l_Propclass.Get(&idx))) + return ADE_RETURN_NIL; + + if (idx < 0 || idx >= prop_info_size()) + return ADE_RETURN_NIL; + + prop_info* pip = &Prop_info[idx]; + + bool result = !pip->custom_data.empty(); + return ade_set_args(L, "b", result); +} + +ADE_VIRTVAR(CustomStrings, + l_Propclass, + nullptr, + "Gets the indexed custom string table for this prop. Each item in the table is a table with the following values: " + "Name - the name of the custom string, Value - the value associated with the custom string, String - the custom " + "string itself.", + "table", + "The prop's custom data table") +{ + int idx; + if (!ade_get_args(L, "o", l_Propclass.Get(&idx))) + return ADE_RETURN_NIL; + + if (idx < 0 || idx >= prop_info_size()) + return ADE_RETURN_NIL; + + prop_info* pip = &Prop_info[idx]; + + if (ADE_SETTING_VAR) { + LuaError(L, "Setting Custom Data is not supported"); + } + + auto table = luacpp::LuaTable::create(L); + + int cnt = 0; + + for (const auto& cs : pip->custom_strings) { + cnt++; + auto item = luacpp::LuaTable::create(L); + + item.addValue("Name", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs.name)); + item.addValue("Value", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs.value)); + item.addValue("String", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs.text)); + + table.addValue(cnt, item); + } + + return ade_set_args(L, "t", &table); +} + +ADE_FUNC(hasCustomStrings, + l_Propclass, + nullptr, + "Detects whether the prop has any custom strings", + "boolean", + "true if the prop's custom_strings is not empty, false otherwise") +{ + int idx; + if (!ade_get_args(L, "o", l_Propclass.Get(&idx))) + return ADE_RETURN_NIL; + + if (idx < 0 || idx >= prop_info_size()) + return ADE_RETURN_NIL; + + prop_info* pip = &Prop_info[idx]; + + bool result = !pip->custom_strings.empty(); + return ade_set_args(L, "b", result); +} + +ADE_FUNC(isValid, + l_Propclass, + NULL, + "Detects whether handle is valid", + "boolean", + "true if valid, false if handle is invalid, nil if a syntax/type error occurs") +{ + int idx; + if (!ade_get_args(L, "o", l_Propclass.Get(&idx))) + return ADE_RETURN_NIL; + + if (idx < 0 || idx >= prop_info_size()) + return ADE_RETURN_FALSE; + + return ADE_RETURN_TRUE; +} + +ADE_FUNC(renderTechModel, + l_Propclass, + "number X1, number Y1, number X2, number Y2, [number RotationPercent =0, number PitchPercent =0, number " + "BankPercent=40, number Zoom=1.3, boolean Lighting=true]", + "Draws prop model as if in techroom. True for regular lighting, false for flat lighting.", + "boolean", + "Whether prop was rendered") +{ + int x1, y1, x2, y2; + angles rot_angles = {0.0f, 0.0f, 40.0f}; + int idx; + float zoom = 1.3f; + bool lighting = true; + if (!ade_get_args(L, + "oiiii|ffffb", + l_Propclass.Get(&idx), + &x1, + &y1, + &x2, + &y2, + &rot_angles.h, + &rot_angles.p, + &rot_angles.b, + &zoom, + &lighting)) + return ade_set_error(L, "b", false); + + if (idx < 0 || idx >= ship_info_size()) + return ade_set_args(L, "b", false); + + if (x2 < x1 || y2 < y1) + return ade_set_args(L, "b", false); + + CLAMP(rot_angles.p, 0.0f, 100.0f); + CLAMP(rot_angles.b, 0.0f, 100.0f); + CLAMP(rot_angles.h, 0.0f, 100.0f); + + // Handle angles + matrix orient = vmd_identity_matrix; + angles view_angles = {-0.6f, 0.0f, 0.0f}; + vm_angles_2_matrix(&orient, &view_angles); + + rot_angles.p = (rot_angles.p * 0.01f) * PI2; + rot_angles.b = (rot_angles.b * 0.01f) * PI2; + rot_angles.h = (rot_angles.h * 0.01f) * PI2; + vm_rotate_matrix_by_angles(&orient, &rot_angles); + + return ade_set_args(L, "b", render_tech_model(TECH_PROP, x1, y1, x2, y2, zoom, lighting, idx, &orient)); +} + +// Nuke's alternate tech model rendering function +ADE_FUNC(renderTechModel2, + l_Propclass, + "number X1, number Y1, number X2, number Y2, [orientation Orientation=nil, number Zoom=1.3]", + "Draws prop model as if in techroom", + "boolean", + "Whether prop was rendered") +{ + int x1, y1, x2, y2; + int idx; + float zoom = 1.3f; + matrix_h* mh = NULL; + if (!ade_get_args(L, "oiiiio|f", l_Propclass.Get(&idx), &x1, &y1, &x2, &y2, l_Matrix.GetPtr(&mh), &zoom)) + return ade_set_error(L, "b", false); + + if (idx < 0 || idx >= ship_info_size()) + return ade_set_args(L, "b", false); + + if (x2 < x1 || y2 < y1) + return ade_set_args(L, "b", false); + + // Handle angles + matrix* orient = mh->GetMatrix(); + + return ade_set_args(L, "b", render_tech_model(TECH_PROP, x1, y1, x2, y2, zoom, true, idx, orient)); +} + +ADE_FUNC(isModelLoaded, + l_Propclass, + "[boolean Load = false]", + "Checks if the model used for this propclass is loaded or not and optionally loads the model, which might be a " + "slow operation.", + "boolean", + "If the model is loaded or not") +{ + int idx; + bool load_check = false; + if (!ade_get_args(L, "o|b", l_Propclass.Get(&idx), &load_check)) + return ADE_RETURN_FALSE; + + prop_info* pip = &Prop_info[idx]; + + if (pip == nullptr) + return ADE_RETURN_FALSE; + + if (load_check) { + pip->model_num = model_load(pip->pof_file); + } + + if (pip->model_num > -1) + return ADE_RETURN_TRUE; + else + return ADE_RETURN_FALSE; +} + +ADE_FUNC(getPropClassIndex, + l_Propclass, + nullptr, + "Gets the index value of the prop class", + "number", + "index value of the prop class") +{ + int idx; + if (!ade_get_args(L, "o", l_Propclass.Get(&idx))) + return ade_set_args(L, "i", -1); + + if (idx < 0 || idx >= prop_info_size()) + return ade_set_args(L, "i", -1); + + return ade_set_args(L, "i", idx + 1); // Lua is 1-based +} + +} // namespace api +} // namespace scripting diff --git a/code/scripting/api/objs/propclass.h b/code/scripting/api/objs/propclass.h new file mode 100644 index 00000000000..23679293df6 --- /dev/null +++ b/code/scripting/api/objs/propclass.h @@ -0,0 +1,11 @@ +#pragma once + +#include "scripting/ade_api.h" + +namespace scripting { +namespace api { + +DECLARE_ADE_OBJ(l_Propclass, int); + +} +} // namespace scripting diff --git a/code/source_groups.cmake b/code/source_groups.cmake index 738bf797a3e..65cc4341e67 100644 --- a/code/source_groups.cmake +++ b/code/source_groups.cmake @@ -1484,6 +1484,10 @@ add_file_folder("Scripting\\\\Api\\\\Objs" scripting/api/objs/player.h scripting/api/objs/promise.cpp scripting/api/objs/promise.h + scripting/api/objs/prop.cpp + scripting/api/objs/prop.h + scripting/api/objs/propclass.cpp + scripting/api/objs/propclass.h scripting/api/objs/rank.cpp scripting/api/objs/rank.h scripting/api/objs/redalert.cpp From 21685f3db63942ea0dfa2d80b43a238059b2b70d Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Thu, 12 Jun 2025 17:21:15 -0500 Subject: [PATCH 05/25] lab support --- code/lab/dialogs/lab_ui.cpp | 53 +++++++++++- code/lab/dialogs/lab_ui.h | 1 + code/lab/dialogs/lab_ui_helpers.cpp | 122 ++++++++++++++++++++++++++++ code/lab/dialogs/lab_ui_helpers.h | 3 + code/lab/labv2.h | 1 + code/lab/manager/lab_manager.cpp | 7 ++ code/lab/manager/lab_manager.h | 4 + code/lab/renderer/lab_renderer.cpp | 15 ++++ code/parse/sexp.h | 2 +- 9 files changed, 206 insertions(+), 2 deletions(-) diff --git a/code/lab/dialogs/lab_ui.cpp b/code/lab/dialogs/lab_ui.cpp index 2770deab4c4..5a13f4c8e20 100644 --- a/code/lab/dialogs/lab_ui.cpp +++ b/code/lab/dialogs/lab_ui.cpp @@ -11,6 +11,7 @@ #include "ship/shiphit.h" #include "weapon/weapon.h" #include "mission/missionload.h" +#include "prop/prop.h" using namespace ImGui; @@ -171,6 +172,27 @@ void LabUi::build_object_list() } } +void LabUi::build_prop_list() +{ + with_TreeNode("Prop Classes") + { + int prop_info_idx = 0; + + for (auto const& class_def : Prop_info) { + SCP_string node_label; + sprintf(node_label, "##PropClassIndex%i", prop_info_idx); + TreeNodeEx(node_label.c_str(), + ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen, + "%s", + class_def.name); + if (IsItemClicked() && !IsItemToggledOpen()) { + getLabManager()->changeDisplayedObject(LabMode::Prop, prop_info_idx); + } + prop_info_idx++; + } + } +} + void LabUi::build_background_list() const { SCP_vector t_missions; @@ -273,6 +295,8 @@ void LabUi::show_object_selector() const build_weapon_list(); build_object_list(); + + build_prop_list(); } } } @@ -418,7 +442,8 @@ void LabUi::show_render_options() Checkbox("Rotate/Translate Subsystems", &animate_subsystems); } Checkbox("Show full detail", &show_full_detail); - if (getLabManager()->CurrentMode != LabMode::Asteroid) { + if (getLabManager()->CurrentMode == LabMode::Ship || + getLabManager()->CurrentMode == LabMode::Weapon) { Checkbox("Show thrusters", &show_thrusters); if (getLabManager()->CurrentMode == LabMode::Ship) { Checkbox("Show afterburners", &show_afterburners); @@ -1482,6 +1507,32 @@ void LabUi::show_object_options() const } } } + } else if (getLabManager()->CurrentMode == LabMode::Prop && getLabManager()->CurrentClass >= 0) { + const auto& info = Prop_info[getLabManager()->CurrentClass]; + + with_CollapsingHeader("Object Info") + { + static SCP_string table_text; + static int old_class = -1; + + if (table_text.empty() || old_class != getLabManager()->CurrentClass) { + table_text = get_prop_table_text(&info); + old_class = getLabManager()->CurrentClass; + } + + InputTextMultiline("##prop_table_text", + const_cast(table_text.c_str()), + table_text.length(), + ImVec2(-FLT_MIN, GetTextLineHeight() * 16), + ImGuiInputTextFlags_ReadOnly); + } + + with_CollapsingHeader("Object actions") + { + if (getLabManager()->isSafeForAsteroids()) { + // No actions yet + } + } } } } diff --git a/code/lab/dialogs/lab_ui.h b/code/lab/dialogs/lab_ui.h index 34560fe474b..9bcb46753de 100644 --- a/code/lab/dialogs/lab_ui.h +++ b/code/lab/dialogs/lab_ui.h @@ -32,6 +32,7 @@ class LabUi { static void build_object_list(); static void build_asteroid_list(); static void build_debris_list(); + static void build_prop_list(); void build_background_list() const; void show_render_options(); void show_object_options() const; diff --git a/code/lab/dialogs/lab_ui_helpers.cpp b/code/lab/dialogs/lab_ui_helpers.cpp index 9bb45c57bca..137f07550ff 100644 --- a/code/lab/dialogs/lab_ui_helpers.cpp +++ b/code/lab/dialogs/lab_ui_helpers.cpp @@ -411,6 +411,128 @@ SCP_string get_asteroid_table_text(const asteroid_info* aip) return result; } +SCP_string get_prop_table_text(const prop_info* pip) +{ + char line[256], line2[256], file_text[82]; + int i, j, n, found = 0, comment = 0, num_files = 0; + SCP_vector tbl_file_names; + SCP_string result; + + auto fp = cfopen("props.tbl", "r"); + if (!fp) + return "No props.tbl found.\r\n"; + + while (cfgets(line, 255, fp)) { + while (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = 0; + + for (i = j = 0; line[i]; i++) { + if (line[i] == '/' && line[i + 1] == '/') + break; + if (line[i] == '/' && line[i + 1] == '*') { + comment = 1; + i++; + continue; + } + if (line[i] == '*' && line[i + 1] == '/') { + comment = 0; + i++; + continue; + } + if (!comment) + line2[j++] = line[i]; + } + + line2[j] = 0; + if (!strnicmp(line2, "$Name:", 6)) { + drop_trailing_white_space(line2); + found = 0; + i = 6; + + while (line2[i] == ' ' || line2[i] == '\t' || line2[i] == '@') + i++; + + if (!stricmp(line2 + i, pip->name)) { + result += "-- props.tbl -------------------------------\r\n"; + found = 1; + } + } + + if (found) { + result += line; + result += "\r\n"; + } + } + + cfclose(fp); + + num_files = cf_get_file_list(tbl_file_names, CF_TYPE_TABLES, NOX("*-prp.tbm"), CF_SORT_REVERSE); + + for (n = 0; n < num_files; n++) { + tbl_file_names[n] += ".tbm"; + + fp = cfopen(tbl_file_names[n].c_str(), "r"); + if (!fp) + continue; + + memset(line, 0, sizeof(line)); + memset(line2, 0, sizeof(line2)); + found = 0; + comment = 0; + + while (cfgets(line, 255, fp)) { + while (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = 0; + + for (i = j = 0; line[i]; i++) { + if (line[i] == '/' && line[i + 1] == '/') + break; + if (line[i] == '/' && line[i + 1] == '*') { + comment = 1; + i++; + continue; + } + if (line[i] == '*' && line[i + 1] == '/') { + comment = 0; + i++; + continue; + } + if (!comment) + line2[j++] = line[i]; + } + + line2[j] = 0; + if (!strnicmp(line2, "$Name:", 6)) { + drop_trailing_white_space(line2); + found = 0; + i = 6; + + while (line2[i] == ' ' || line2[i] == '\t' || line2[i] == '@') + i++; + + if (!stricmp(line2 + i, pip->name)) { + memset(file_text, 0, sizeof(file_text)); + snprintf(file_text, + sizeof(file_text) - 1, + "-- %s -------------------------------\r\n", + tbl_file_names[n].c_str()); + result += file_text; + found = 1; + } + } + + if (found) { + result += line; + result += "\r\n"; + } + } + + cfclose(fp); + } + + return result; +} + SCP_string get_directory_or_vp(const char* path) { SCP_string result(path); diff --git a/code/lab/dialogs/lab_ui_helpers.h b/code/lab/dialogs/lab_ui_helpers.h index 808827dc02b..17cbfadb52e 100644 --- a/code/lab/dialogs/lab_ui_helpers.h +++ b/code/lab/dialogs/lab_ui_helpers.h @@ -2,6 +2,7 @@ #include "asteroid/asteroid.h" #include "ship/ship.h" +#include "prop/prop.h" SCP_map get_docking_point_map(int model_index); @@ -13,6 +14,8 @@ SCP_string get_weapon_table_text(weapon_info* wip); SCP_string get_asteroid_table_text(const asteroid_info* aip); +SCP_string get_prop_table_text(const prop_info* pip); + SCP_string get_directory_or_vp(const char* path); bool graphics_options_changed(); diff --git a/code/lab/labv2.h b/code/lab/labv2.h index 39a72d4624b..8b0d822c4a1 100644 --- a/code/lab/labv2.h +++ b/code/lab/labv2.h @@ -4,6 +4,7 @@ enum class LabMode { Asteroid, Ship, Weapon, + Prop, None }; diff --git a/code/lab/manager/lab_manager.cpp b/code/lab/manager/lab_manager.cpp index c72a9c4aa56..3280a41b492 100644 --- a/code/lab/manager/lab_manager.cpp +++ b/code/lab/manager/lab_manager.cpp @@ -14,6 +14,7 @@ #include "weapon/muzzleflash.h" #include "weapon/beam.h" #include "ai/aigoals.h" +#include "prop/prop.h" #include "freespace.h" @@ -748,6 +749,12 @@ void LabManager::changeDisplayedObject(LabMode mode, int info_index, int subtype ai_add_ship_goal_scripting(AI_GOAL_PLAY_DEAD_PERSISTENT, -1, 100, nullptr, &Ai_info[Player_ship->ai_index], 0, 0); } break; + case LabMode::Prop: + CurrentObject = prop_create(&CurrentOrientation, &CurrentPosition, CurrentClass); + if (isSafeForShips()) { + ModelFilename = Prop_info[CurrentClass].pof_file; + } + break; case LabMode::Weapon: if (ShowingTechModel && VALID_FNAME(Weapon_info[CurrentClass].tech_model)) { ModelFilename = Weapon_info[CurrentClass].tech_model; diff --git a/code/lab/manager/lab_manager.h b/code/lab/manager/lab_manager.h index 6d0418a2a66..03ad8eda57b 100644 --- a/code/lab/manager/lab_manager.h +++ b/code/lab/manager/lab_manager.h @@ -107,6 +107,10 @@ class LabManager { return CurrentMode == LabMode::Ship && CurrentObject != -1 && Objects[CurrentObject].type == OBJ_SHIP; } + bool isSafeForProps() { + return CurrentMode == LabMode::Prop && CurrentObject != -1 && Objects[CurrentObject].type == OBJ_PROP; + } + bool isSafeForWeapons() { bool valid = CurrentObject != -1 && (Objects[CurrentObject].type == OBJ_WEAPON || Objects[CurrentObject].type == OBJ_BEAM); return CurrentMode == LabMode::Weapon && valid; diff --git a/code/lab/renderer/lab_renderer.cpp b/code/lab/renderer/lab_renderer.cpp index a3032ea6acf..94ed8a2bfe0 100644 --- a/code/lab/renderer/lab_renderer.cpp +++ b/code/lab/renderer/lab_renderer.cpp @@ -13,6 +13,7 @@ #include "particle/particle.h" #include "starfield/starfield.h" #include "starfield/nebula.h" +#include "prop/prop.h" #include "missionui/missionscreencommon.h" #include "tracing/tracing.h" @@ -114,6 +115,20 @@ void LabRenderer::renderModel(float frametime) { } } + if (obj->type == OBJ_PROP) { + Props[obj->instance].flags.set(Prop::Prop_Flags::Draw_as_wireframe, renderFlags[LabRenderFlag::ShowWireframe]); + Props[obj->instance].flags.set(Prop::Prop_Flags::Render_full_detail, renderFlags[LabRenderFlag::ShowFullDetail]); + Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_light, + renderFlags[LabRenderFlag::NoLighting] || currentMissionBackground == LAB_MISSION_NONE_STRING); + Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_diffuse, renderFlags[LabRenderFlag::NoDiffuseMap]); + Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_glowmap, renderFlags[LabRenderFlag::NoGlowMap]); + Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_normalmap, renderFlags[LabRenderFlag::NoNormalMap]); + Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_specmap, renderFlags[LabRenderFlag::NoSpecularMap]); + Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_reflectmap, renderFlags[LabRenderFlag::NoReflectMap]); + Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_heightmap, renderFlags[LabRenderFlag::NoHeightMap]); + Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_ambientmap, renderFlags[LabRenderFlag::NoAOMap]); + } + if (obj->type == OBJ_WEAPON) { Weapons[obj->instance].weapon_flags.set(Weapon::Weapon_Flags::Draw_as_wireframe, renderFlags[LabRenderFlag::ShowWireframe]); Weapons[obj->instance].weapon_flags.set(Weapon::Weapon_Flags::Render_full_detail, renderFlags[LabRenderFlag::ShowFullDetail]); diff --git a/code/parse/sexp.h b/code/parse/sexp.h index 0fb22e5d5e9..faa454303f7 100644 --- a/code/parse/sexp.h +++ b/code/parse/sexp.h @@ -19,7 +19,7 @@ class ship_subsys; class ship; -class prop; +struct prop; class waypoint_list; class object; class waypoint; From b7e182a184f17c4c8794e64ee8e0d76d2c80e7b6 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Fri, 13 Jun 2025 09:38:58 -0500 Subject: [PATCH 06/25] lua mission access to props --- code/scripting/api/libs/mission.cpp | 81 +++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/code/scripting/api/libs/mission.cpp b/code/scripting/api/libs/mission.cpp index dc89fb9a190..8a7ced08c1a 100644 --- a/code/scripting/api/libs/mission.cpp +++ b/code/scripting/api/libs/mission.cpp @@ -36,6 +36,7 @@ #include "parse/sexp/LuaAISEXP.h" #include "parse/sexp/sexp_lookup.h" #include "playerman/player.h" +#include "prop/prop.h" #include "scripting/api/LuaPromise.h" #include "scripting/api/objs/LuaEnum.h" #include "scripting/api/objs/LuaSEXP.h" @@ -57,6 +58,8 @@ #include "scripting/api/objs/object.h" #include "scripting/api/objs/parse_object.h" #include "scripting/api/objs/promise.h" +#include "scripting/api/objs/prop.h" +#include "scripting/api/objs/propclass.h" #include "scripting/api/objs/sexpvar.h" #include "scripting/api/objs/ship_registry_entry.h" #include "scripting/api/objs/ship.h" @@ -549,6 +552,43 @@ ADE_FUNC(__len, l_Mission_ParsedShips, NULL, return ade_set_args(L, "i", static_cast(Parse_objects.size())); } +//****SUBLIBRARY: Mission/Props +ADE_LIB_DERIV(l_Mission_Props, "Props", nullptr, "Props in the mission", l_Mission); + +ADE_INDEXER(l_Mission_Props, "number/string IndexOrName", "Gets prop", "prop", "Prop handle, or invalid prop handle if index was invalid") +{ + const char* name; + if(!ade_get_args(L, "*s", &name)) + return ade_set_error(L, "o", l_Prop.Set(object_h())); + + int idx = ship_name_lookup(name); + + if (idx >= 0) + { + return ade_set_args(L, "o", l_Prop.Set(object_h(&Objects[Props[idx].objnum]))); + } + else + { + idx = atoi(name); + + int objnum = -1; + if (idx > 0) + objnum = object_subclass_at_index(Props, MAX_PROPS, idx); + + return ade_set_args(L, "o", l_Prop.Set(object_h(objnum))); + } +} + +ADE_FUNC(__len, l_Mission_Props, NULL, + "Number of props in the mission. " + "This function is somewhat slow, and should be set to a variable for use in looping situations. " + "Note that props can be vanished, and so this value cannot be relied on for more than one frame.", + "number", + "Number of props in the mission, or 0 if props haven't been initialized yet") +{ + return ade_set_args(L, "i", object_subclass_count(Props, MAX_PROPS)); +} + //****SUBLIBRARY: Mission/Waypoints ADE_LIB_DERIV(l_Mission_Waypoints, "Waypoints", NULL, NULL, l_Mission); @@ -1340,6 +1380,47 @@ ADE_FUNC(createShip, return ade_set_error(L, "o", l_Ship.Set(object_h())); } +ADE_FUNC(createProp, + l_Mission, + "[string Name, propclass Class /* First prop class by default */, orientation Orientation=null, vector Position /* null vector by default */]", + "Creates a prop and returns a handle to it using the specified name, class, world orientation, and world position.", + "prop", + "Prop handle, or invalid prop handle if prop couldn't be created") +{ + const char* name = nullptr; + int pclass = -1; + matrix_h* orient = nullptr; + vec3d pos = vmd_zero_vector; + ade_get_args(L, "|sooo", &name, l_Propclass.Get(&pclass), l_Matrix.GetPtr(&orient), l_Vector.Get(&pos)); + + if (Prop_info.size() == 0) { + return ade_set_error(L, "o", l_Prop.Set(object_h())); + } + + matrix *real_orient = &vmd_identity_matrix; + if(orient != NULL) + { + real_orient = orient->GetMatrix(); + } + + if (pclass == -1) { + return ade_set_error(L, "o", l_Prop.Set(object_h())); + } + + int obj_idx = prop_create(real_orient, &pos, pclass, name); + + if(obj_idx >= 0) { + auto propp = &Props[Objects[obj_idx].instance]; + + prop_info* pip = &Prop_info[pclass]; + + model_page_in_textures(pip->model_num, pclass); + + return ade_set_args(L, "o", l_Prop.Set(object_h(&Objects[obj_idx]))); + } else + return ade_set_error(L, "o", l_Prop.Set(object_h())); +} + ADE_FUNC(createDebris, l_Mission, "[ship | shipclass | model | submodel | nil source, string | nil submodel_index_or_name, vector position, orientation, enumeration create_flags, " From 8c5a23fb147008f2d006202597a7098220bd6b8a Mon Sep 17 00:00:00 2001 From: Asteroth Date: Sat, 14 Jun 2025 10:05:11 -0400 Subject: [PATCH 07/25] prop collision support --- code/object/collidedebrisship.cpp | 264 +++++++++++++++++++++++ code/object/collidepropdebris.cpp | 1 - code/object/collidepropship.cpp | 1 - code/object/collidepropweapon.cpp | 1 - code/object/collideshipship.cpp | 345 ++++++++++++++++++++++++++++-- code/object/collideshipweapon.cpp | 202 ++++++++++++++++- code/object/objcollide.cpp | 64 ++++-- code/object/objcollide.h | 11 +- code/prop/prop.cpp | 318 +++++++++++++++++++++++++++ code/prop/prop.h | 3 + code/source_groups.cmake | 3 - code/weapon/beam.cpp | 177 +++++++++++++++ code/weapon/beam.h | 3 + code/weapon/weapons.cpp | 1 + 14 files changed, 1341 insertions(+), 53 deletions(-) delete mode 100644 code/object/collidepropdebris.cpp delete mode 100644 code/object/collidepropship.cpp delete mode 100644 code/object/collidepropweapon.cpp diff --git a/code/object/collidedebrisship.cpp b/code/object/collidedebrisship.cpp index afc4d6c025b..b5899c2b54e 100644 --- a/code/object/collidedebrisship.cpp +++ b/code/object/collidedebrisship.cpp @@ -20,6 +20,7 @@ #include "scripting/api/objs/model.h" #include "scripting/api/objs/vecmath.h" #include "playerman/player.h" +#include "prop/prop.h" #include "ship/ship.h" #include "ship/shiphit.h" @@ -413,3 +414,266 @@ int collide_asteroid_ship( obj_pair * pair ) return 0; } } + +/** + * Checks debris-prop collisions. + * @param pair obj_pair pointer to the two objects. pair->a is debris and pair->b is prop. + * @return 1 if all future collisions between these can be ignored + */ +int collide_debris_prop(obj_pair* pair) +{ + float dist; + object* debris_objp = pair->a; + object* prop_objp = pair->b; + + Assert(debris_objp->type == OBJ_DEBRIS); + Assert(prop_objp->type == OBJ_PROP); + + if (reject_due_collision_groups(debris_objp, prop_objp)) + return 0; + + dist = vm_vec_dist(&debris_objp->pos, &prop_objp->pos); + if (dist < debris_objp->radius + prop_objp->radius) { + int hit; + vec3d hitpos; + // create and initialize ship_ship_hit_info struct + collision_info_struct debris_hit_info; + init_collision_info_struct(&debris_hit_info); + + if (debris_objp->radius > prop_objp->radius) { + debris_hit_info.heavy = debris_objp; + debris_hit_info.light = prop_objp; + } + else { + debris_hit_info.heavy = prop_objp; + debris_hit_info.light = debris_objp; + } + + hit = prop_check_collision(prop_objp, debris_objp, &hitpos, &debris_hit_info); + if (hit) + { + bool ship_override = false, debris_override = false; + + // get submodel handle if scripting needs it + bool has_submodel = (debris_hit_info.heavy_submodel_num >= 0); + scripting::api::submodel_h smh(debris_hit_info.heavy_model_num, debris_hit_info.heavy_submodel_num); + + // TODO PROP + /* + if (scripting::hooks::OnDebrisCollision->isActive()) { + ship_override = scripting::hooks::OnDebrisCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, debris_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + scripting::hook_param("Object", 'o', debris_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Debris", 'o', debris_objp), + scripting::hook_param("Hitpos", 'o', hitpos))); + } + + if (scripting::hooks::OnShipCollision->isActive()) { + debris_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, debris_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', debris_objp), + scripting::hook_param("Object", 'o', ship_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Debris", 'o', debris_objp), + scripting::hook_param("Hitpos", 'o', hitpos), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (debris_hit_info.heavy == ship_objp)))); + }*/ + + if (!ship_override && !debris_override) + { + // do collision physics + calculate_ship_ship_collision_physics(&debris_hit_info); + + if (debris_hit_info.impulse < 0.5f) + return 0; + + float debris_damage = debris_hit_info.impulse / debris_objp->phys_info.mass; // ie, delta velocity of debris + + // apply damage to debris + // no need for force, already handled in calculate_ship_ship_collision_physics + debris_hit(debris_objp, prop_objp, &hitpos, debris_damage, nullptr); // speed => damage + + collide_ship_ship_do_sound(&hitpos, prop_objp, debris_objp,false ); + } + + if (scripting::hooks::OnDebrisCollision->isActive() && !(debris_override && !ship_override)) { + // TODO PROP + /* + scripting::hooks::OnDebrisCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, debris_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + scripting::hook_param("Object", 'o', debris_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Debris", 'o', debris_objp), + scripting::hook_param("Hitpos", 'o', hitpos))); + */ + } + if (scripting::hooks::OnShipCollision->isActive() && ((debris_override && !ship_override) || (!debris_override && !ship_override))) + { + // TODO PROP + /* + scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, debris_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', debris_objp), + scripting::hook_param("Object", 'o', ship_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Debris", 'o', debris_objp), + scripting::hook_param("Hitpos", 'o', hitpos), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (debris_hit_info.heavy == ship_objp)))); + */ + } + + return 0; + } + } + else { // Bounding spheres don't intersect, set timestamp for next collision check. + float debris_speed; + float time; + + debris_speed = debris_objp->phys_info.speed; + + time = 1000.0f * (dist - prop_objp->radius - debris_objp->radius - 10.0f) / (debris_speed); // 10.0f is a safety factor + time -= 200.0f; // allow one frame slow frame at ~5 fps + + if (time > 100) { + pair->next_check_time = timestamp(fl2i(time)); + } + else { + pair->next_check_time = timestamp(0); // check next time + } + } + + return 0; +} + +/** + * Checks asteroid-prop collisions. + * @param pair obj_pair pointer to the two objects. pair->a is asteroid and pair->b is prop. + * @return 1 if all future collisions between these can be ignored + */ +int collide_asteroid_prop(obj_pair* pair) +{ + if (!Asteroids_enabled) + return 0; + + float dist; + object* asteroid_objp = pair->a; + object* prop_objp = pair->b; + + if (asteroid_objp->hull_strength < 0.0f) + return 0; + + Assert(asteroid_objp->type == OBJ_ASTEROID); + Assert(prop_objp->type == OBJ_PROP); + + dist = vm_vec_dist(&asteroid_objp->pos, &prop_objp->pos); + + if (dist < asteroid_objp->radius + prop_objp->radius) { + int hit; + vec3d hitpos; + // create and initialize ship_ship_hit_info struct + collision_info_struct asteroid_hit_info; + init_collision_info_struct(&asteroid_hit_info); + + if (asteroid_objp->radius > prop_objp->radius) { + asteroid_hit_info.heavy = asteroid_objp; + asteroid_hit_info.light = prop_objp; + } + else { + asteroid_hit_info.heavy = prop_objp; + asteroid_hit_info.light = asteroid_objp; + } + + hit = prop_check_collision(prop_objp, prop_objp, &hitpos, &asteroid_hit_info); + if (hit) + { + bool ship_override = false, asteroid_override = false; + + // get submodel handle if scripting needs it + bool has_submodel = (asteroid_hit_info.heavy_submodel_num >= 0); + scripting::api::submodel_h smh(asteroid_hit_info.heavy_model_num, asteroid_hit_info.heavy_submodel_num); + + //Scripting support (WMC) + // TODO PROP + /* + if (scripting::hooks::OnAsteroidCollision->isActive()) { + ship_override = scripting::hooks::OnAsteroidCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, asteroid_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + scripting::hook_param("Object", 'o', asteroid_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Asteroid", 'o', asteroid_objp), + scripting::hook_param("Hitpos", 'o', hitpos))); + } + if (scripting::hooks::OnShipCollision->isActive()) { + asteroid_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, asteroid_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', asteroid_objp), + scripting::hook_param("Object", 'o', ship_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Asteroid", 'o', asteroid_objp), + scripting::hook_param("Hitpos", 'o', hitpos), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (asteroid_hit_info.heavy == ship_objp)))); + }*/ + + if (!ship_override && !asteroid_override) + { + float asteroid_damage; + + vec3d asteroid_vel = asteroid_objp->phys_info.vel; + + // do collision physics + calculate_ship_ship_collision_physics(&asteroid_hit_info); + + if (asteroid_hit_info.impulse < 0.5f) + return 0; + + asteroid_damage = asteroid_hit_info.impulse / asteroid_objp->phys_info.mass; // ie, delta velocity of asteroid + + // apply damage to asteroid + asteroid_hit(asteroid_objp, prop_objp, &hitpos, asteroid_damage, nullptr); // speed => damage + + collide_ship_ship_do_sound(&hitpos, prop_objp, asteroid_objp, false); + } + + // TODO PROP + /* + if (scripting::hooks::OnAsteroidCollision->isActive() && !(asteroid_override && !ship_override)) { + scripting::hooks::OnAsteroidCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, asteroid_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + scripting::hook_param("Object", 'o', asteroid_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Asteroid", 'o', asteroid_objp), + scripting::hook_param("Hitpos", 'o', hitpos))); + } + if (scripting::hooks::OnShipCollision->isActive() && ((asteroid_override && !ship_override) || (!asteroid_override && !ship_override))) + { + scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, asteroid_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', asteroid_objp), + scripting::hook_param("Object", 'o', ship_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Asteroid", 'o', asteroid_objp), + scripting::hook_param("Hitpos", 'o', hitpos), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (asteroid_hit_info.heavy == ship_objp)))); + }*/ + + return 0; + } + + return 0; + } + else { + // estimate earliest time at which pair can hit + float asteroid_max_speed, time; + + asteroid_max_speed = vm_vec_mag(&asteroid_objp->phys_info.vel); // Asteroid... vel gets reset, not max vel.z + asteroid_max_speed = MAX(asteroid_max_speed, 10.0f); + + time = 1000.0f * (dist - prop_objp->radius - asteroid_objp->radius - 10.0f) / (asteroid_max_speed); // 10.0f is a safety factor + time -= 200.0f; // allow one frame slow frame at ~5 fps + + if (time > 100) { + pair->next_check_time = timestamp(fl2i(time)); + } + else { + pair->next_check_time = timestamp(0); // check next time + } + return 0; + } +} \ No newline at end of file diff --git a/code/object/collidepropdebris.cpp b/code/object/collidepropdebris.cpp deleted file mode 100644 index 928df3cb886..00000000000 --- a/code/object/collidepropdebris.cpp +++ /dev/null @@ -1 +0,0 @@ -// STUB \ No newline at end of file diff --git a/code/object/collidepropship.cpp b/code/object/collidepropship.cpp deleted file mode 100644 index 5b1ff7dfc63..00000000000 --- a/code/object/collidepropship.cpp +++ /dev/null @@ -1 +0,0 @@ -//STUB \ No newline at end of file diff --git a/code/object/collidepropweapon.cpp b/code/object/collidepropweapon.cpp deleted file mode 100644 index 928df3cb886..00000000000 --- a/code/object/collidepropweapon.cpp +++ /dev/null @@ -1 +0,0 @@ -// STUB \ No newline at end of file diff --git a/code/object/collideshipship.cpp b/code/object/collideshipship.cpp index 5fcaf880d5f..b94c4baaadf 100644 --- a/code/object/collideshipship.cpp +++ b/code/object/collideshipship.cpp @@ -28,6 +28,7 @@ #include "scripting/global_hooks.h" #include "scripting/api/objs/model.h" #include "playerman/player.h" +#include "prop/prop.h" #include "render/3d.h" // needed for View_position, which is used when playing 3d sound #include "ship/ship.h" #include "ship/shipfx.h" @@ -420,8 +421,7 @@ static int check_special_cruiser_asteroid_collision(object *heavy, object *light int asteroid_type; if (heavy->type == OBJ_ASTEROID) { - Assert(lighter->type == OBJ_SHIP); - if (Ship_info[Ships[lighter->instance].ship_info_index].is_big_or_huge()) { + if (lighter->type == OBJ_SHIP && Ship_info[Ships[lighter->instance].ship_info_index].is_big_or_huge()) { asteroid_type = Asteroids[heavy->instance].asteroid_type; if (asteroid_type == 0) { @@ -438,8 +438,7 @@ static int check_special_cruiser_asteroid_collision(object *heavy, object *light } } } else if (lighter->type == OBJ_ASTEROID) { - Assert(heavy->type == OBJ_SHIP); - if (Ship_info[Ships[heavy->instance].ship_info_index].is_big_or_huge()) { + if (heavy->type == OBJ_SHIP && Ship_info[Ships[heavy->instance].ship_info_index].is_big_or_huge()) { asteroid_type = Asteroids[lighter->instance].asteroid_type; if (asteroid_type == 0) { @@ -529,8 +528,8 @@ void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_ object *lighter = ship_ship_hit_info->light; // gurgh... this includes asteroids and debris too - Assert(heavy->type == OBJ_SHIP || heavy->type == OBJ_ASTEROID || heavy->type == OBJ_DEBRIS); - Assert(lighter->type == OBJ_SHIP || lighter->type == OBJ_ASTEROID || lighter->type == OBJ_DEBRIS); + Assert(heavy->type == OBJ_SHIP || heavy->type == OBJ_ASTEROID || heavy->type == OBJ_DEBRIS || heavy->type == OBJ_PROP); + Assert(lighter->type == OBJ_SHIP || lighter->type == OBJ_ASTEROID || lighter->type == OBJ_DEBRIS || lighter->type == OBJ_PROP); ship_info *light_sip = (lighter->type == OBJ_SHIP) ? &Ship_info[Ships[lighter->instance].ship_info_index] : NULL; ship_info *heavy_sip = (heavy->type == OBJ_SHIP) ? &Ship_info[Ships[heavy->instance].ship_info_index] : NULL; @@ -591,6 +590,10 @@ void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_ pmi = model_get_instance(model_instance_num); } else if (heavy->type == OBJ_DEBRIS) { pm = model_get(Debris[heavy->instance].model_num); + } else if (heavy->type == OBJ_PROP) { + pm = model_get(Prop_info[Props[heavy->instance].prop_info_index].model_num); + model_instance_num = Props[heavy->instance].model_instance_num; + pmi = model_get_instance(model_instance_num); } else { // we should have caught this already Int3(); @@ -706,7 +709,9 @@ void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_ // sanity check heavy_denom = 0.0f; } - heavy_denom += 1.0f / heavy->phys_info.mass; + if (heavy->type != OBJ_PROP) { + heavy_denom += 1.0f / heavy->phys_info.mass; + } // calculate the effect on the velocity of the collison point per unit impulse // first find the effect thru change in rotvel @@ -730,7 +735,9 @@ void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_ // sanity check light_denom = 0.0f; } - light_denom += 1.0f / lighter->phys_info.mass; + if (lighter->type != OBJ_PROP) { + light_denom += 1.0f / lighter->phys_info.mass; + } // calculate the necessary impulse to achieved the desired relative velocity after the collision // update damage info in mc @@ -802,12 +809,18 @@ void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_ vm_vec_sub(&direction_light, &ship_ship_hit_info->light_rel_vel, &local_vel_from_submodel); vm_vec_normalize_safe(&direction_light); - if (should_collide){ + float heavy_light_mass_ratio = heavy->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass); + if (heavy->type == OBJ_PROP) { + heavy_light_mass_ratio = 1.0f; + } else if (lighter->type == OBJ_PROP) { + heavy_light_mass_ratio = 0.0f; + } + if (should_collide){ if (!heavy->flags[Object::Object_Flags::Dont_change_position, Object::Object_Flags::Immobile]) { Assert(!vm_is_vec_nan(&direction_light)); - vm_vec_scale_add2(&heavy->pos, &direction_light, 0.2f * lighter->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass)); - vm_vec_scale_add2(&heavy->pos, &ship_ship_hit_info->collision_normal, -0.1f * lighter->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass)); + vm_vec_scale_add2(&heavy->pos, &direction_light, 0.2f * (1.0f - heavy_light_mass_ratio)); + vm_vec_scale_add2(&heavy->pos, &ship_ship_hit_info->collision_normal, -0.1f * (1.0f - heavy_light_mass_ratio)); } // while we are in a block that has already checked if we should collide, set the MP client timestamps @@ -825,8 +838,8 @@ void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_ vm_vec_scale_add2(&lighter->pos, &ship_ship_hit_info->collision_normal, LANDING_POS_OFFSET); } else if (!lighter->flags[Object::Object_Flags::Dont_change_position, Object::Object_Flags::Immobile]) { - vm_vec_scale_add2(&lighter->pos, &direction_light, -0.2f * heavy->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass)); - vm_vec_scale_add2(&lighter->pos, &ship_ship_hit_info->collision_normal, 0.1f * heavy->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass)); + vm_vec_scale_add2(&lighter->pos, &direction_light, -0.2f * heavy_light_mass_ratio); + vm_vec_scale_add2(&lighter->pos, &ship_ship_hit_info->collision_normal, 0.1f * heavy_light_mass_ratio); } // restore mass in case of special cruiser / asteroid collision @@ -1569,3 +1582,309 @@ int collide_ship_ship( obj_pair * pair ) { return never_check_again ? 1 : 0; } + + +/** + * Checks prop-ship collisions. + * @return 1 if all future collisions between these can be ignored + */ +int collide_prop_ship(obj_pair* pair) +{ + float dist; + object* prop_objp = pair->a; + object* ship_objp = pair->b; + + Assert(prop_objp->type == OBJ_PROP); + Assert(ship_objp->type == OBJ_SHIP); + + // Cyborg17 - no ship-ship collisions when doing multiplayer rollback + if ((Game_mode & GM_MULTIPLAYER) && multi_ship_record_get_rollback_wep_mode()) { + return 0; + } + + if (ship_objp != Player_obj) { + // This is the most convenient place to do this check. Clients should *not* be doing anything + // collision related. Yes, from time to time that will look strange, but there are too many + // side effects if we allow it. + if (MULTIPLAYER_CLIENT) { + return 0; + } + } + + // Don't check collisions for warping out player if past stage 1. + if (ship_objp == Player_obj && (Player->control_mode > PCM_WARPOUT_STAGE1)) { + return 0; + } + + ship* shipp = &Ships[ship_objp->instance]; + dist = vm_vec_dist(&prop_objp->pos, &ship_objp->pos); + + if (dist < prop_objp->radius + ship_objp->radius) { + int hit; + + object* heavy_obj, * light_obj; + if (prop_objp->radius > ship_objp->radius) { + heavy_obj = prop_objp; + light_obj = ship_objp; + } + else { + heavy_obj = ship_objp; + light_obj = prop_objp; + } + + + ship_info* sip = &Ship_info[shipp->ship_info_index]; + + collision_info_struct prop_ship_hit_info; + init_collision_info_struct(&prop_ship_hit_info); + + prop_ship_hit_info.heavy = heavy_obj; // heavy object, generally slower moving + prop_ship_hit_info.light = light_obj; // light object, generally faster moving + + vec3d world_hit_pos; + hit = prop_check_collision(prop_objp, ship_objp, &world_hit_pos, &prop_ship_hit_info); + + pair->next_check_time = timestamp(0); + + if (hit) + { + bool a_override = false, b_override = false; + + // get submodel handle if scripting needs it + bool has_submodel = (prop_ship_hit_info.heavy_submodel_num >= 0); + scripting::api::submodel_h smh(prop_ship_hit_info.heavy_model_num, prop_ship_hit_info.heavy_submodel_num); + + // TODO PROP + /* + if (scripting::hooks::OnShipCollision->isActive()) { + a_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{{A, B}}, + scripting::hook_param_list(scripting::hook_param("Self", 'o', A), + scripting::hook_param("Object", 'o', B), + scripting::hook_param("Ship", 'o', A), + scripting::hook_param("ShipB", 'o', B), + scripting::hook_param("Hitpos", 'o', world_hit_pos), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)), + scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)))); + + // Yes, this should be reversed. + b_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {A, B} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', B), + scripting::hook_param("Object", 'o', A), + scripting::hook_param("Ship", 'o', B), + scripting::hook_param("ShipB", 'o', A), + scripting::hook_param("Hitpos", 'o', world_hit_pos), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)), + scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)))); + + } + */ + + if (!a_override && !b_override) + { + + // SET PHYSICS PARAMETERS + // already have (hitpos - heavy) and light_cm_pos + + // get r_heavy and r_light + prop_ship_hit_info.r_heavy = prop_ship_hit_info.hit_pos; + vm_vec_sub(&prop_ship_hit_info.r_light, &prop_ship_hit_info.hit_pos, &prop_ship_hit_info.light_collision_cm_pos); + + // set normal for edge hit + if (prop_ship_hit_info.edge_hit) { + vm_vec_copy_normalize(&prop_ship_hit_info.collision_normal, &prop_ship_hit_info.r_light); + vm_vec_negate(&prop_ship_hit_info.collision_normal); + } + + // do physics + calculate_ship_ship_collision_physics(&prop_ship_hit_info); + + // If a couple of small ships, just move them apart. + + if (sip->is_small_ship()) { + if (ship_objp == Player_obj) { + vec3d h_to_l_vec; + vec3d perp_rel_vel; + + vm_vec_sub(&h_to_l_vec, &heavy_obj->pos, &light_obj->pos); + + // get comp of rel_vel perp to h_to_l_vec; + float mag = vm_vec_dot(&h_to_l_vec, &ship_objp->phys_info.vel) / vm_vec_mag_squared(&h_to_l_vec); + vm_vec_scale_add(&perp_rel_vel, &ship_objp->phys_info.vel, &h_to_l_vec, -mag); + vm_vec_normalize(&perp_rel_vel); + + ship_objp->phys_info.vel -= perp_rel_vel * sip->collision_physics.bounce; + + vm_vec_rotate(&ship_objp->phys_info.prev_ramp_vel, &ship_objp->phys_info.vel, &ship_objp->orient); + } + } else if (ship_objp == light_obj) { + // add extra velocity to separate the two objects, backing up the direction we came in. + // TODO: add effect of velocity from rotating submodel + float rel_vel = vm_vec_mag_quick(&prop_ship_hit_info.light_rel_vel); + if (rel_vel < 1) { + rel_vel = 1.0f; + } + + light_obj->phys_info.vel -= prop_ship_hit_info.light_rel_vel * sip->collision_physics.bounce; + + vm_vec_rotate(&light_obj->phys_info.prev_ramp_vel, &light_obj->phys_info.vel, &light_obj->orient); + } + + + float damage; + + if (ship_objp == Player_obj && (Player->control_mode == PCM_WARPOUT_STAGE1)) { + gameseq_post_event(GS_EVENT_PLAYER_WARPOUT_STOP); + HUD_printf("%s", XSTR("Warpout sequence aborted.", 466)); + } + + damage = 0.005f * prop_ship_hit_info.impulse; // Cut collision-based damage in half. + + if (damage > 5.0f) { + damage = 5.0f + (damage - 5.0f) / 2.0f; + } + + if (prop_ship_hit_info.impulse > 0 && ship_objp == Player_obj) { + hud_start_text_flash(XSTR("Collision", 1431), 2000); + } + + collide_ship_ship_do_sound(&world_hit_pos, prop_objp, ship_objp, ship_objp == Player_obj); + + // check if we should do force feedback stuff + if (ship_objp == Player_obj && (prop_ship_hit_info.impulse > 0)) { + float scaler; + vec3d v; + + scaler = -prop_ship_hit_info.impulse / Player_obj->phys_info.mass * 300; + vm_vec_copy_normalize(&v, &world_hit_pos); + joy_ff_play_vector_effect(&v, scaler); + } + + //Only do damage if not a landing + // Scale damage based on skill level for player. + if ((ship_objp->flags[Object::Object_Flags::Player_ship])) { + + // Cyborg17 - Pretty hackish, but it's our best option, limit the amount of times a collision can + // happen to multiplayer clients, because otherwise the server can kill clients far too quickly. + // So here it goes, first only do this on the master (has an intrinsic multiplayer check) + if (MULTIPLAYER_MASTER) { + + // iterate through each player + for (net_player& current_player : Net_players) { + // check that this player's ship is valid, and that it's not the server ship. + if ((current_player.m_player != nullptr) && !(current_player.flags & NETINFO_FLAG_AM_MASTER) && (current_player.m_player->objnum > 0) && current_player.m_player->objnum < MAX_OBJECTS) { + // check that the colliding ship is this player's ship + if (ship_objp == &Objects[current_player.m_player->objnum]) { + // finally if the host is also a player, ignore making these adjustments for him because he is in a pure simulation. + if (&Ships[Objects[current_player.m_player->objnum].instance] != Player_ship) { + Assertion(Interp_info.find(current_player.m_player->objnum) != Interp_info.end(), "Somehow the collision code thinks there is not a player ship interp record in multi when there really *should* be. This is a coder mistake, please report!"); + + // temp set this as an uninterpolated ship, to make the collision look more natural until the next update comes in. + Interp_info[current_player.m_player->objnum].force_interpolation_mode(); + + // check to see if it has been long enough since the last collision, if not, negate the damage + if (!timestamp_elapsed(current_player.s_info.player_collision_timestamp)) { + damage = 0.0f; + } + else { + // make the usual adjustments + damage *= (float)(Game_skill_level * Game_skill_level + 1) / (NUM_SKILL_LEVELS + 1); + // if everything is good to go, set the timestamp for the next collision + current_player.s_info.player_collision_timestamp = _timestamp(PLAYER_COLLISION_TIMESTAMP); + } + break; + } + } + } + } + // if not in multiplayer, just do the damage adjustment. + } + else { + damage *= (float)(Game_skill_level * Game_skill_level + 1) / (NUM_SKILL_LEVELS + 1); + } + } + + // don't draw sparks (using sphere hitpos) + damage = (100.0f * damage / ship_objp->phys_info.mass); + ship_apply_local_damage(ship_objp, prop_objp, &world_hit_pos, damage, -1, MISS_SHIELDS, CREATE_SPARKS, -1, &prop_ship_hit_info.collision_normal); + + hud_shield_quadrant_hit(ship_objp, -1); + } + + //FIX + if (!scripting::hooks::OnShipCollision->isActive()) { + return 0; + } + + if (!(b_override && !a_override)) + { + // TODO PROP + /* + scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {A, B} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', A), + scripting::hook_param("Object", 'o', B), + scripting::hook_param("Ship", 'o', A), + scripting::hook_param("ShipB", 'o', B), + scripting::hook_param("Hitpos", 'o', world_hit_pos), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)), + scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)))); + */ + } + if ((b_override && !a_override) || (!b_override && !a_override)) + { + // Yes, this should be reversed. + // TODO PROP + /* + scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {A, B} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', B), + scripting::hook_param("Object", 'o', A), + scripting::hook_param("Ship", 'o', B), + scripting::hook_param("ShipB", 'o', A), + scripting::hook_param("Hitpos", 'o', world_hit_pos), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)), + scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)))); + */ + } + + return 0; + } + } + else { + // estimate earliest time at which pair can hit + + // cap ships warping in/out can exceed ship's expected velocity + // if ship is warping in, in stage 1, its velocity is 0, so make ship try to collide next frame + + // if ship is huge and warping in or out + if (shipp->is_arriving(ship::warpstage::STAGE1, false) && Ship_info[shipp->ship_info_index].is_big_or_huge()) { + pair->next_check_time = timestamp(0); // check next time + return 0; + } + + // get max of (1) max_vel.z, (2) 10, (3) afterburner_max_vel.z, (4) vel.z (for warping in ships exceeding expected max vel) + float ship_max_speed, time; + + // get shipA max speed + if (ship_is_beginning_warpout_speedup(ship_objp)) { + ship_max_speed = MAX(ship_get_max_speed(shipp), ship_get_warpout_speed(ship_objp)); + } + else { + ship_max_speed = ship_get_max_speed(shipp); + } + + // Maybe warping in or finished warping in with excessive speed + ship_max_speed = MAX(ship_max_speed, vm_vec_mag(&ship_objp->phys_info.vel)); + ship_max_speed = MAX(ship_max_speed, 10.0f); + + time = 1000.0f * (dist - ship_objp->radius - prop_objp->radius) / (ship_max_speed); + time -= 200.0f; // allow one frame slow frame at ~5 fps + + if (time > 0) { + pair->next_check_time = timestamp(fl2i(time)); + } + else { + pair->next_check_time = timestamp(0); // check next time + } + } + + return 0; +} \ No newline at end of file diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index e3a8cfc92f4..2ec017ef2b3 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -23,6 +23,7 @@ #include "scripting/api/objs/model.h" #include "scripting/api/objs/vecmath.h" #include "playerman/player.h" +#include "prop/prop.h" #include "ship/ship.h" #include "ship/shipfx.h" #include "ship/shiphit.h" @@ -35,6 +36,7 @@ using ship_weapon_collision_data = std::tuple, int, bool, extern int Game_skill_level; extern float ai_endangered_time(const object *ship_objp, const object *weapon_objp); +static int check_inside_radius_for_big_objects( object *ship, object *weapon_obj, obj_pair *pair ); static std::tuple check_inside_radius_for_big_ships( object *ship, object *weapon_obj, obj_pair *pair ); extern float flFrametime; @@ -617,6 +619,135 @@ static void ship_weapon_process_collision(obj_pair* pair, const std::any& collis } + +static int prop_weapon_check_collision(object* prop_objp, object* weapon_objp, float time_limit = 0.0f, int* next_hit = nullptr) +{ + weapon* wp; + weapon_info* wip; + + Assert(prop_objp != nullptr); + Assert(prop_objp->type == OBJ_PROP); + Assert(prop_objp->instance >= 0); + + prop* propp = &Props[prop_objp->instance]; + prop_info* prinfo = &Prop_info[propp->prop_info_index]; + + Assert(weapon_objp != nullptr); + Assert(weapon_objp->type == OBJ_WEAPON); + Assert(weapon_objp->instance >= 0); + + wp = &Weapons[weapon_objp->instance]; + wip = &Weapon_info[wp->weapon_info_index]; + + Assert(propp->objnum == OBJ_INDEX(prop_objp)); + + int valid_hit_occurred = 0; // If this is set, then hitpos is set + polymodel* pm = model_get(prinfo->model_num); + + // total time is flFrametime + time_limit (time_limit used to predict collisions into the future) + vec3d weapon_start_pos = weapon_objp->last_pos; + vec3d weapon_end_pos; + vm_vec_scale_add(&weapon_end_pos, &weapon_objp->pos, &weapon_objp->phys_info.vel, time_limit); + + if (!IS_VEC_NULL(&The_mission.gravity) && wip->gravity_const != 0.0f) { + // subtle point about simulating collisions against a parabola + // every simulated point, the positions every frame, are perfectly on the correct parabola + // however this means collision is checking *the lines inbetween* those points, which will always be undernearth the parabola + // the grater the frametime, the greater the discrepancy, and can cause erroneous misses + // So just for collision purposes, we offset these points slightly in the opposite direction of gravity + // at least to ensure the *average* position at all interpolated points is on the parabola + weapon_start_pos -= The_mission.gravity * flFrametime * flFrametime * (1.f / 12); + weapon_end_pos -= The_mission.gravity * flFrametime * flFrametime * (1.f / 12); + } + + + // Goober5000 - I tried to make collision code much saner... here begin the (major) changes + + // set up collision struct + mc_info mc; + mc.model_instance_num = propp->model_instance_num; + mc.model_num = prinfo->model_num; + mc.submodel_num = -1; + mc.orient = &prop_objp->orient; + mc.pos = &prop_objp->pos; + mc.p0 = &weapon_start_pos; + mc.p1 = &weapon_end_pos; + //mc.lod = prinfo->collision_lod; + + + mc.flags = MC_CHECK_MODEL; + bool hit = model_collide(&mc); + + // deal with predictive collisions. Find their actual hit time and see if they occured in current frame + if (next_hit && hit) { + // find hit time + *next_hit = (int)(1000.0f * (mc.hit_dist * (flFrametime + time_limit) - flFrametime)); + if (*next_hit > 0) + // if hit occurs outside of this frame, do not do damage + return 1; + } + + if (hit) + { + wp->collisionInfo = new mc_info; // The weapon will free this memory later + *wp->collisionInfo = mc; + + bool ship_override = false, weapon_override = false; + + // get submodel handle if scripting needs it + bool has_submodel = (mc.hit_submodel >= 0); + scripting::api::submodel_h smh(mc.model_num, mc.hit_submodel); + + + // TODO PROP + /* + if (scripting::hooks::OnWeaponCollision->isActive()) { + ship_override = scripting::hooks::OnWeaponCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + scripting::hook_param("Object", 'o', weapon_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Weapon", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc->hit_point_world))); + } + if (scripting::hooks::OnShipCollision->isActive()) { + weapon_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), + scripting::hook_param("Object", 'o', ship_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Weapon", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc->hit_point_world), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + }*/ + + if (!ship_override && !weapon_override) { + weapon_hit(weapon_objp, prop_objp, &mc.hit_point_world, MISS_SHIELDS, &mc.hit_normal, &mc.hit_point, mc.hit_submodel); + } + + // TODO PROP + /* + if (scripting::hooks::OnWeaponCollision->isActive() && !(weapon_override && !ship_override)) { + scripting::hooks::OnWeaponCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + scripting::hook_param("Object", 'o', weapon_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Weapon", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc->hit_point_world))); + } + if (scripting::hooks::OnShipCollision->isActive() && !ship_override) { + scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), + scripting::hook_param("Object", 'o', ship_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Weapon", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc->hit_point_world), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + }*/ + } + + return valid_hit_occurred; +} + + /** * Checks ship-weapon collisions. * @param pair obj_pair pointer to the two objects. pair->a is ship and pair->b is weapon. @@ -724,6 +855,55 @@ collision_result collide_ship_weapon_check( obj_pair * pair ) return {never_hits, do_postproc ? collision_data : std::any(), &ship_weapon_process_collision}; } +/** + * Checks prop-weapon collisions. + * @param pair obj_pair pointer to the two objects. pair->a is ship and pair->b is weapon. + * @return 1 if all future collisions between these can be ignored + */ +int collide_prop_weapon(obj_pair* pair) +{ + int did_hit; + object* prop = pair->a; + object* weapon_obj = pair->b; + + Assert(prop->type == OBJ_PROP); + Assert(weapon_obj->type == OBJ_WEAPON); + + // Cyborg17 - no ship-ship collisions when doing multiplayer rollback + //Asteroth - should this be kept? + /*if ((Game_mode & GM_MULTIPLAYER) && multi_ship_record_get_rollback_wep_mode() && (weapon_obj->parent_sig == OBJ_INDEX(ship))) { + return 0; + }*/ + + if (reject_due_collision_groups(prop, weapon_obj)) + return 0; + + // Cull lasers within big prop spheres by casting a vector forward for (1) exit sphere or (2) lifetime of laser + // If it does hit, don't check the pair until about 200 ms before collision. + // If it does not hit and is within error tolerance, cull the pair. + + if (prop->radius > 500.0f && (weapon_obj->phys_info.flags & PF_CONST_VEL)) { + // Check when within ~1.1 radii. + // This allows good transition between sphere checking (leaving the laser about 200 ms from radius) and checking + // within the sphere with little time between. There may be some time for "small" big ships + // Note: culling ships with auto spread shields seems to waste more performance than it saves, + // so we're not doing that here + if (vm_vec_dist_squared(&prop->pos, &weapon_obj->pos) < (1.2f * prop->radius * prop->radius)) { + return check_inside_radius_for_big_objects(prop, weapon_obj, pair); + } + } + + did_hit = prop_weapon_check_collision(prop, weapon_obj); + + if (!did_hit) { + // Since we didn't hit, check to see if we can disable all future collisions + // between these two. + return weapon_will_never_hit(weapon_obj, prop, pair); + } + + return 0; +} + /** * Upper limit estimate ship speed at end of time */ @@ -751,7 +931,7 @@ static float estimate_ship_speed_upper_limit( object *ship, float time ) #define ERROR_STD 2 /** - * When inside radius of big ship, check if we can cull collision pair determine the time when pair should next be checked + * When inside radius of big ship or prop, check if we can cull collision pair determine the time when pair should next be checked * @return 1 if pair can be culled * @return 0 if pair can not be culled */ @@ -761,20 +941,20 @@ static std::tuple check_inside_radius_fo float error_vel_mag; // magnitude of error_vel float time_to_max_error, time_to_exit_sphere; float ship_speed_at_exit_sphere, error_at_exit_sphere; - float max_error = (float) ERROR_STD / 150.0f * ship->radius; + float max_error = (float) ERROR_STD / 150.0f * big_obj->radius; if (max_error < 2) max_error = 2.0f; float weapon_max_vel = MAX(weapon_obj->phys_info.max_vel.xyz.z, vm_vec_mag(&weapon_obj->phys_info.vel)); - Assertion(IS_VEC_NULL(&The_mission.gravity) || weapon_obj->phys_info.gravity_const == 0.0f, "check_inside_radius_for_big_ships being used for a ballistic weapon"); + Assertion(IS_VEC_NULL(&The_mission.gravity) || weapon_obj->phys_info.gravity_const == 0.0f, "check_inside_radius_for_big_objects being used for a ballistic weapon"); - time_to_exit_sphere = (ship->radius + vm_vec_dist(&ship->pos, &weapon_obj->pos)) / (weapon_max_vel - ship->phys_info.max_vel.xyz.z); - ship_speed_at_exit_sphere = estimate_ship_speed_upper_limit( ship, time_to_exit_sphere ); + time_to_exit_sphere = (big_obj->radius + vm_vec_dist(&big_obj->pos, &weapon_obj->pos)) / (weapon_max_vel - big_obj->phys_info.max_vel.xyz.z); + ship_speed_at_exit_sphere = estimate_ship_speed_upper_limit(big_obj, time_to_exit_sphere ); // update estimated time to exit sphere - time_to_exit_sphere = (ship->radius + vm_vec_dist(&ship->pos, &weapon_obj->pos)) / (weapon_max_vel - ship_speed_at_exit_sphere); - vm_vec_scale_add( &error_vel, &ship->phys_info.vel, &weapon_obj->orient.vec.fvec, -vm_vec_dot(&ship->phys_info.vel, &weapon_obj->orient.vec.fvec) ); + time_to_exit_sphere = (big_obj->radius + vm_vec_dist(&big_obj->pos, &weapon_obj->pos)) / (weapon_max_vel - ship_speed_at_exit_sphere); + vm_vec_scale_add( &error_vel, &big_obj->phys_info.vel, &weapon_obj->orient.vec.fvec, -vm_vec_dot(&big_obj->phys_info.vel, &weapon_obj->orient.vec.fvec) ); error_vel_mag = vm_vec_mag_quick( &error_vel ); - error_vel_mag += 0.5f * (ship->phys_info.max_vel.xyz.z - error_vel_mag)*(time_to_exit_sphere/ship->phys_info.forward_accel_time_const); + error_vel_mag += 0.5f * (big_obj->phys_info.max_vel.xyz.z - error_vel_mag)*(time_to_exit_sphere/big_obj->phys_info.forward_accel_time_const); // error_vel_mag is now average velocity over period error_at_exit_sphere = error_vel_mag * time_to_exit_sphere; time_to_max_error = max_error / error_at_exit_sphere * time_to_exit_sphere; @@ -792,6 +972,12 @@ static std::tuple check_inside_radius_fo // Note: when estimated hit time is less than 200 ms, look at every frame int hit_time; // estimated time of hit in ms + // modify the collision check to do damage if hit_time is negative (ie, hit occurs in this frame) + bool hit = false; + if (big_obj->type == OBJ_SHIP) + hit = ship_weapon_check_collision(big_obj, weapon_obj, limit_time, &hit_time); + else // OBJ_PROP + hit = prop_weapon_check_collision(big_obj, weapon_obj, limit_time, &hit_time); const auto& [do_postproc, does_not_hit, collision_data] = ship_weapon_check_collision( ship, weapon_obj, limit_time, &hit_time ); // modify ship_weapon_check_collision to do damage if hit_time is negative (ie, hit occurs in this frame) diff --git a/code/object/objcollide.cpp b/code/object/objcollide.cpp index 121878cc0e2..cae1a17fc4b 100644 --- a/code/object/objcollide.cpp +++ b/code/object/objcollide.cpp @@ -883,6 +883,13 @@ void obj_collide_pair(object *A, object *B) check_collision = collide_debris_ship; swapped = 1; break; + case COLLISION_OF(OBJ_DEBRIS, OBJ_PROP): + check_collision = collide_debris_prop; + break; + case COLLISION_OF(OBJ_PROP, OBJ_DEBRIS): + check_collision = collide_debris_prop; + swapped = 1; + break; case COLLISION_OF(OBJ_ASTEROID, OBJ_WEAPON): check_collision = collide_asteroid_weapon; break; @@ -897,6 +904,13 @@ void obj_collide_pair(object *A, object *B) check_collision = collide_asteroid_ship; swapped = 1; break; + case COLLISION_OF(OBJ_ASTEROID, OBJ_PROP): + check_collision = collide_asteroid_prop; + break; + case COLLISION_OF(OBJ_PROP, OBJ_ASTEROID): + check_collision = collide_asteroid_prop; + swapped = 1; + break; case COLLISION_OF(OBJ_SHIP,OBJ_SHIP): check_collision = collide_ship_ship; #ifdef NDEBUG @@ -904,7 +918,20 @@ void obj_collide_pair(object *A, object *B) support_mp = true; #endif break; - + case COLLISION_OF(OBJ_PROP, OBJ_SHIP): + check_collision = collide_prop_ship; + break; + case COLLISION_OF(OBJ_SHIP, OBJ_PROP): + check_collision = collide_prop_ship; + swapped = 1; + break; + case COLLISION_OF(OBJ_PROP, OBJ_WEAPON): + check_collision = collide_prop_weapon; + break; + case COLLISION_OF(OBJ_WEAPON, OBJ_PROP): + check_collision = collide_prop_weapon; + swapped = 1; + break; case COLLISION_OF(OBJ_SHIP, OBJ_BEAM): if(beam_collide_early_out(B, A)){ return; @@ -961,6 +988,20 @@ void obj_collide_pair(object *A, object *B) } check_collision = beam_collide_missile; break; + case COLLISION_OF(OBJ_PROP, OBJ_BEAM): + if (beam_collide_early_out(B, A)) { + return; + } + swapped = 1; + check_collision = beam_collide_prop; + break; + + case COLLISION_OF(OBJ_BEAM, OBJ_PROP): + if (beam_collide_early_out(A, B)) { + return; + } + check_collision = beam_collide_prop; + break; case COLLISION_OF(OBJ_WEAPON, OBJ_WEAPON): { weapon_info* awip = &Weapon_info[Weapons[A->instance].weapon_info_index]; @@ -977,27 +1018,6 @@ void obj_collide_pair(object *A, object *B) break; } - /* case COLLISION_OF(OBJ_SHIP, OBJ_PROP): - check_collision = collide_prop_ship; - break; - case COLLISION_OF(OBJ_PROP, OBJ_SHIP): - check_collision = collide_prop_ship; - swapped = 1; - break; - case COLLISION_OF(OBJ_WEAPON, OBJ_PROP): - check_collision = collide_prop_weapon; - break; - case COLLISION_OF(OBJ_PROP, OBJ_WEAPON): - check_collision = collide_prop_weapon; - swapped = 1; - break; - case COLLISION_OF(OBJ_DEBRIS, OBJ_PROP): - check_collision = collide_prop_debris; - break; - case COLLISION_OF(OBJ_PROP, OBJ_DEBRIS): - check_collision = collide_prop_debris; - swapped = 1; - break; */ default: return; diff --git a/code/object/objcollide.h b/code/object/objcollide.h index 2ae1dcbfc14..ac85382a9ac 100644 --- a/code/object/objcollide.h +++ b/code/object/objcollide.h @@ -20,7 +20,7 @@ class object; struct CFILE; struct mc_info; -// used for ship:ship and ship:debris +// used for ship:ship and ship:debris and ship:prop struct collision_info_struct { object *heavy; object *light; @@ -120,6 +120,12 @@ int collide_debris_weapon( obj_pair * pair ); // CODE is locatated in CollideDebrisShip.cpp int collide_debris_ship( obj_pair * pair ); +// Checks debris-prop collisions. pair->a is debris and pair->b is prop. +// Returns 1 if all future collisions between these can be ignored +// CODE is locatated in CollideDebrisShip.cpp +int collide_debris_prop(obj_pair* pair); + +int collide_asteroid_prop(obj_pair* pair); int collide_asteroid_ship(obj_pair *pair); int collide_asteroid_weapon(obj_pair *pair); @@ -138,9 +144,6 @@ int collide_prop_ship(obj_pair* pair); // Checks prop-ship collisions. int collide_prop_weapon(obj_pair* pair); -// Checks prop-debris collisions -int collide_prop_debris(obj_pair* pair); - // Predictive functions. // Returns true if vector from curpos to goalpos with radius radius will collide with object goalobjp int pp_collide(vec3d *curpos, vec3d *goalpos, object *goalobjp, float radius); diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 43fddb24e1a..c3a46892d6d 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -1,8 +1,13 @@ #include "prop.h" +#include "asteroid/asteroid.h" +#include "debris/debris.h" +#include "freespace.h" #include "model/model.h" #include "parse/parselo.h" #include "render/3d.h" +#include "ship/shipfx.h" +#include "object/objcollide.h" #include "tracing/Monitor.h" @@ -308,6 +313,7 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) flagset default_ship_object_flags; default_ship_object_flags.set(Object::Object_Flags::Renders); default_ship_object_flags.set(Object::Object_Flags::Physics); + default_ship_object_flags.set(Object::Object_Flags::Immobile); // JAS: Nav buoys don't need to do collisions! // G5K: Corrected to apply specifically for ships with the no-collide flag. (In retail, navbuoys already have this // flag, so this doesn't break anything.) @@ -326,6 +332,19 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) propp->model_instance_num = model_create_instance(objnum, pip->model_num); + object* objp = &Objects[objnum]; + objp->hull_strength = 0.0f; + objp->phys_info.max_vel = vmd_zero_vector; + objp->phys_info.desired_vel = vmd_zero_vector; + objp->phys_info.forward_accel_time_const = 1.0; + objp->phys_info.flags = PF_CONST_VEL; + + // this is a little 'dangerous', values like this can cause the physics to do wierd things + // but they are the most 'honest', and will help to indicate when the physics is trying to use these values in ways they shouldn't + objp->phys_info.mass = INFINITY; + objp->phys_info.I_body_inv = vmd_zero_matrix; + + // allocate memory for keeping glow point bank status (enabled/disabled) { bool val = true; // default value, enabled @@ -621,4 +640,303 @@ void props_level_close() while (!Props.empty()) { prop_delete(&Objects[Props.back().objnum]); } +} + + +// handles prop-ship/debris/asteroid/weapon collisions +int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, collision_info_struct* prop_hit_info) +{ + mc_info mc; + + Assert(prop_obj->type == OBJ_PROP); + + int num = prop_obj->instance; + + Assert(num >= 0 && num < (int)Props.size()); + Assert(Props[num].objnum == OBJ_INDEX(prop_obj)); + + prop* propp = &Props[num]; + prop_info* prinfo = &Prop_info[propp->prop_info_index]; + + // debris_hit_info NULL - so debris-weapon collision + if (prop_hit_info == NULL) { + // debris weapon collision + Assert(other_obj->type == OBJ_WEAPON); + mc.model_instance_num = propp->model_instance_num; + mc.model_num = prinfo->model_num; // Fill in the model to check + mc.orient = &prop_obj->orient; // The object's orient + mc.pos = &prop_obj->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; + + if (model_collide(&mc)) { + *hitpos = mc.hit_point_world; + } + + 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; + } + + object* heavy_obj = prop_hit_info->heavy; + object* light_obj = prop_hit_info->light; + + vec3d zero; + vm_vec_zero(&zero); + vec3d p0 = light_obj->last_pos - heavy_obj->last_pos; + vec3d p1 = light_obj->pos - heavy_obj->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 (heavy_obj->type == OBJ_SHIP && (vm_vec_mag_squared(&heavy_obj->phys_info.rotvel) * flFrametime * flFrametime) < (PI * PI / 36)) { + // collide_rotate calculate (1) start position and (2) relative velocity + prop_hit_info->collide_rotate = true; + vm_vec_rotate(&p0_temp, &p0, &heavy_obj->last_orient); + vm_vec_unrotate(&p0_rotated, &p0_temp, &heavy_obj->orient); + mc.p0 = &p0_rotated; // Point 1 of ray to check + vm_vec_sub(&prop_hit_info->light_rel_vel, &p1, &p0_rotated); + vm_vec_scale(&prop_hit_info->light_rel_vel, 1 / flFrametime); + } + else { + prop_hit_info->collide_rotate = false; + vm_vec_sub(&prop_hit_info->light_rel_vel, &light_obj->phys_info.vel, &heavy_obj->phys_info.vel); + } + + int mc_ret_val = 0; + + if (prop_hit_info->heavy == other_obj && other_obj->type == OBJ_SHIP) { // ship is heavier, so prop is sphere. Check sphere collision against ship poly model + mc.model_instance_num = Ships[other_obj->instance].model_instance_num; + mc.model_num = Ship_info[Ships[other_obj->instance].ship_info_index].model_num; // Fill in the model to check + mc.orient = &other_obj->orient; // The object's orient + mc.radius = model_get_core_radius(prinfo->model_num); + 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 + prop_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 (prop_hit_info->collide_rotate) { + // We collide with the sphere, find the list of moving submodels and test one at a time + SCP_vector 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[other_obj->instance].ship_info_index].collision_lod > -1) { + mc.lod = Ship_info[Ships[other_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 < prop_hit_info->hit_time) { + mc_ret_val = 1; + + // set up debris_hit_info common + set_hit_struct_info(prop_hit_info, &mc, true); + model_instance_local_to_global_point(&prop_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 (!prop_hit_info->edge_hit) { + model_instance_local_to_global_dir(&prop_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 = *mc.p1 - *mc.p0; + vec3d int_light_pos = *mc.p0 + diff * mc.hit_dist; + model_instance_local_to_global_point(&prop_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 < prop_hit_info->hit_time) { + mc_ret_val = 1; + + set_hit_struct_info(prop_hit_info, &mc, false); + + // get collision normal if not edge hit + if (!prop_hit_info->edge_hit) { + model_instance_local_to_global_dir(&prop_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 = *mc.p1 - *mc.p0; + prop_hit_info->light_collision_cm_pos = *mc.p0 + diff * mc.hit_dist; + } + } + } + } else { // any case OTHER than big ship small prop, simpler code + + if (prop_obj == light_obj) { + mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE); + int instance = heavy_obj->instance; + + // fill the appropriate model data + if (heavy_obj->type == OBJ_DEBRIS) { + mc.model_instance_num = -1; + mc.model_num = Debris[instance].model_num; + mc.submodel_num = Debris[instance].submodel_num; + mc.flags |= MC_SUBMODEL; + } else if (heavy_obj->type == OBJ_ASTEROID) { + asteroid* astp = &Asteroids[instance]; + mc.model_instance_num = astp->model_instance_num; + mc.model_num = Asteroid_info[astp->asteroid_type].subtypes[astp->asteroid_subtype].model_number; + mc.submodel_num - 1; + } + + mc.orient = &heavy_obj->orient; // The object's orient + mc.radius = model_get_core_radius(prinfo->model_num); + + mc_ret_val = model_collide(&mc); + + if (mc_ret_val) { + set_hit_struct_info(prop_hit_info, &mc, false); + + // set normal if not edge hit + if (!prop_hit_info->edge_hit) { + vm_vec_unrotate(&prop_hit_info->collision_normal, &mc.hit_normal, &heavy_obj->orient); + } + + // find position in submodel RF of light object at collison + vec3d diff = *mc.p1 - *mc.p0; + prop_hit_info->light_collision_cm_pos = *mc.p0 + diff * mc.hit_dist; + + } + } else { // prop is the heavy object + mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE); + int instance = heavy_obj->instance; + + // fill the appropriate model data + mc.model_instance_num = propp->model_instance_num; + mc.model_num = prinfo->model_num; + + mc.orient = &heavy_obj->orient; + + switch (light_obj->type) { + case OBJ_SHIP: + mc.radius = model_get_core_radius(Ship_info[Ships[light_obj->instance].ship_info_index].model_num); + break; + case OBJ_ASTEROID: // FALLTHROUGH + case OBJ_DEBRIS: + mc.radius = light_obj->radius; + break; + default: + UNREACHABLE("Unknown object type in prop_check_collision"); + }; + + mc_ret_val = model_collide(&mc); + + if (mc_ret_val) { + set_hit_struct_info(prop_hit_info, &mc, false); + + // set normal if not edge hit + if (!prop_hit_info->edge_hit) { + vm_vec_unrotate(&prop_hit_info->collision_normal, &mc.hit_normal, &heavy_obj->orient); + } + + // find position in submodel RF of light object at collison + vec3d diff = *mc.p1 - *mc.p0; + prop_hit_info->light_collision_cm_pos = *mc.p0 + diff * mc.hit_dist; + } + } + } + + if (mc_ret_val && other_obj->type == OBJ_SHIP) { + WarpEffect* warp_effect = nullptr; + ship* shipp = &Ships[other_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 + prop_hit_info->r_heavy = prop_hit_info->hit_pos; + prop_hit_info->r_light = prop_hit_info->hit_pos - prop_hit_info->light_collision_cm_pos; + + // set normal for edge hit + if (prop_hit_info->edge_hit) { + vm_vec_copy_normalize(&prop_hit_info->collision_normal, &prop_hit_info->r_light); + vm_vec_negate(&prop_hit_info->collision_normal); + } + + // get world hitpos + vm_vec_add(hitpos, &prop_hit_info->heavy->pos, &prop_hit_info->r_heavy); + + return 1; + } + else { + // no hit + return 0; + } } \ No newline at end of file diff --git a/code/prop/prop.h b/code/prop/prop.h index babfafca997..fad64e38045 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -4,6 +4,7 @@ #include "mission/mission_flags.h" #include "object/object.h" +#include "object/objcollide.h" #include "prop/prop_flags.h" #include "ship/ship.h" @@ -71,4 +72,6 @@ int prop_name_lookup(const char* name); void change_prop_type(int n, int prop_type); +int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, collision_info_struct* prop_hit_info); + void spawn_test_prop(); \ No newline at end of file diff --git a/code/source_groups.cmake b/code/source_groups.cmake index 65cc4341e67..b9faf4f6d37 100644 --- a/code/source_groups.cmake +++ b/code/source_groups.cmake @@ -1013,9 +1013,6 @@ ENDIF(WIN32) add_file_folder("Object" object/collidedebrisship.cpp object/collidedebrisweapon.cpp - object/collidepropdebris.cpp - object/collidepropship.cpp - object/collidepropweapon.cpp object/collideshipship.cpp object/collideshipweapon.cpp object/collideweaponweapon.cpp diff --git a/code/weapon/beam.cpp b/code/weapon/beam.cpp index 4a0a25e13f6..75fc6e833e2 100644 --- a/code/weapon/beam.cpp +++ b/code/weapon/beam.cpp @@ -38,6 +38,7 @@ #include "object/object.h" #include "object/objectshield.h" #include "parse/parselo.h" +#include "prop/prop.h" #include "scripting/global_hooks.h" #include "scripting/scripting.h" #include "scripting/api/objs/model.h" @@ -2196,6 +2197,9 @@ int beam_get_model(object *objp) } return Asteroid_info[Asteroids[objp->instance].asteroid_type].subtypes[pof].model_number; + case OBJ_PROP: + return Prop_info[Props[objp->instance].prop_info_index].model_num; + default: // this shouldn't happen too often mprintf(("Beam couldn't find a good object model/type!! (%d)\n", objp->type)); @@ -3349,6 +3353,169 @@ int beam_collide_ship(obj_pair *pair) } +// collide a beam with a prop, returns 1 if we can ignore all future collisions between the 2 objects +int beam_collide_prop(obj_pair* pair) +{ + beam* a_beam; + object* weapon_objp; + object* prop_objp; + weapon_info* bwi; + mc_info mc; + int model_num; + float width; + + // bogus + if (pair == NULL) { + return 0; + } + + if (reject_due_collision_groups(pair->a, pair->b)) + return 0; + + // get the beam + Assert(pair->a->instance >= 0); + Assert(pair->a->type == OBJ_BEAM); + Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a)); + weapon_objp = pair->a; + a_beam = &Beams[pair->a->instance]; + + // if the "warming up" timestamp has not expired + if ((a_beam->warmup_stamp != -1) || (a_beam->warmdown_stamp != -1)) { + return 0; + } + + // if the beam is on "safety", don't collide with anything + if (a_beam->flags & BF_SAFETY) { + return 0; + } + + // try and get a model + model_num = beam_get_model(pair->b); + if (model_num < 0) { + return 1; + } + +#ifndef NDEBUG + Beam_test_ints++; +#endif + + // get the ship + Assert(pair->b->instance >= 0); + Assert(pair->b->type == OBJ_PROP); + Assert(Props[pair->b->instance].objnum == OBJ_INDEX(pair->b)); + if ((pair->b->type != OBJ_PROP) || (pair->b->instance < 0)) + return 1; + prop_objp = pair->b; + prop* propp = &Props[prop_objp->instance]; + + bwi = &Weapon_info[a_beam->weapon_info_index]; + + polymodel* pm = model_get(model_num); + + // get the width of the beam + width = a_beam->beam_collide_width * a_beam->current_width_factor; + + // set up collision struct + mc.model_instance_num = propp->model_instance_num; + mc.model_num = model_num; + mc.submodel_num = -1; + mc.orient = &prop_objp->orient; + mc.pos = &prop_objp->pos; + mc.p0 = &a_beam->last_start; + mc.p1 = &a_beam->last_shot; + + // maybe do a sphereline + if (width > prop_objp->radius * BEAM_AREA_PERCENT) { + mc.radius = width * 0.5f; + mc.flags = MC_CHECK_SPHERELINE; + } + else { + mc.flags = MC_CHECK_RAY; + } + + mc.flags |= MC_CHECK_MODEL; + bool hit = model_collide(&mc); + + // If we have a range less than the "far" range, check if the ray actually hit within the range + if (a_beam->range < BEAM_FAR_LENGTH && hit) + { + // We can't use hit_dist as "1" is the distance between p0 and p1 + float rangeSq = a_beam->range * a_beam->range; + + if (hit && vm_vec_dist_squared(&a_beam->last_start, &mc.hit_point_world) > rangeSq) + { + hit = false; + } + } + + // if we got a hit + if (hit) + { + + bool ship_override = false, weapon_override = false; + + // get submodel handle if scripting needs it + bool has_submodel = (mc.hit_submodel >= 0); + scripting::api::submodel_h smh(mc.model_num, mc.hit_submodel); + + // TODO PROP + /* + if (scripting::hooks::OnBeamCollision->isActive()) { + ship_override = scripting::hooks::OnBeamCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + scripting::hook_param("Object", 'o', weapon_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Beam", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc_array[i]->hit_point_world))); + } + + if (scripting::hooks::OnShipCollision->isActive()) { + weapon_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), + scripting::hook_param("Object", 'o', ship_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Beam", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc_array[i]->hit_point_world), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + }*/ + + if (!ship_override && !weapon_override) + { + // add to the collision_list + // if we got "tooled", add an exit hole too + beam_add_collision(a_beam, prop_objp, &mc, MISS_SHIELDS, false); + } + + //TODO PROP + /* + if (scripting::hooks::OnBeamCollision->isActive() && !(weapon_override && !ship_override)) { + scripting::hooks::OnBeamCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + scripting::hook_param("Object", 'o', weapon_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Beam", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc_array[i]->hit_point_world))); + } + if (scripting::hooks::OnShipCollision->isActive() && ((weapon_override && !ship_override) || (!weapon_override && !ship_override))) + { + scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), + scripting::hook_param("Object", 'o', ship_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Beam", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc_array[i]->hit_point_world), + scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + } + */ + } + + // reset timestamp to timeout immediately + pair->next_check_time = timestamp(0); + + return 0; +} + + // collide a beam with an asteroid, returns 1 if we can ignore all future collisions between the 2 objects int beam_collide_asteroid(obj_pair *pair) { @@ -3713,6 +3880,12 @@ int beam_collide_early_out(object *a, object *b) switch(b->type){ case OBJ_SHIP: break; + case OBJ_PROP: + // targeting lasers only hit ships +/* if(bwi->b_info.beam_type == BEAM_TYPE_C){ + return 1; + }*/ + break; case OBJ_ASTEROID: // targeting lasers only hit ships /* if(bwi->b_info.beam_type == BEAM_TYPE_C){ @@ -4126,6 +4299,10 @@ void beam_handle_collisions(beam *b) beam_apply_whack(b, &Objects[target], &b->f_collisions[idx].cinfo.hit_point_world); } break; + case OBJ_PROP: + // nothing! + // this case is normal and expected, but props do not move and cannot take damage + break; } } diff --git a/code/weapon/beam.h b/code/weapon/beam.h index 56057cd35ea..c34181eaa0f 100644 --- a/code/weapon/beam.h +++ b/code/weapon/beam.h @@ -279,6 +279,9 @@ int beam_collide_missile(obj_pair *pair); // collide a beam with debris, returns 1 if we can ignore all future collisions between the 2 objects int beam_collide_debris(obj_pair *pair); +// collide a beam with a prop, returns 1 if we can ignore all future collisions between the 2 objects +int beam_collide_prop(obj_pair* pair); + // pre-move (before collision checking - but AFTER ALL OTHER OBJECTS HAVE BEEN MOVED) void beam_move_all_pre(); diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index c5c2a79258c..773cb9a436f 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -7396,6 +7396,7 @@ void weapon_hit_do_sound(const object *hit_obj, const weapon_info *wip, const ve // do nothing break; + case OBJ_PROP: case OBJ_ASTEROID: if ( timestamp_elapsed(Weapon_impact_timer) ) { weapon_play_impact_sound(wip, hitpos, is_armed); From 2debeaea6e1fd5f9387eb96d665132e5af3e10b9 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sat, 14 Jun 2025 13:22:45 -0500 Subject: [PATCH 08/25] use std::optional so the object->instance id is always valid --- code/lab/renderer/lab_renderer.cpp | 21 ++++---- code/object/collideshipship.cpp | 5 +- code/object/collideshipweapon.cpp | 2 +- code/object/object.cpp | 2 +- code/object/objectsort.cpp | 4 +- code/parse/sexp.cpp | 4 +- code/prop/prop.cpp | 82 +++++++++++++++++------------ code/prop/prop.h | 3 +- code/scripting/api/libs/mission.cpp | 47 +++++++++++++++-- code/scripting/api/objs/prop.cpp | 10 ++-- code/weapon/beam.cpp | 6 +-- fred2/fredrender.cpp | 8 +-- fred2/fredview.cpp | 2 +- fred2/management.cpp | 18 ++++--- fred2/missionsave.cpp | 47 +++++++++-------- fred2/propdlg.cpp | 20 ++++--- fred2/sexp_tree.cpp | 2 +- qtfred/src/mission/missionsave.cpp | 47 +++++++++-------- qtfred/src/ui/widgets/sexp_tree.cpp | 2 +- 19 files changed, 204 insertions(+), 128 deletions(-) diff --git a/code/lab/renderer/lab_renderer.cpp b/code/lab/renderer/lab_renderer.cpp index 94ed8a2bfe0..2b7d2bedadd 100644 --- a/code/lab/renderer/lab_renderer.cpp +++ b/code/lab/renderer/lab_renderer.cpp @@ -116,17 +116,18 @@ void LabRenderer::renderModel(float frametime) { } if (obj->type == OBJ_PROP) { - Props[obj->instance].flags.set(Prop::Prop_Flags::Draw_as_wireframe, renderFlags[LabRenderFlag::ShowWireframe]); - Props[obj->instance].flags.set(Prop::Prop_Flags::Render_full_detail, renderFlags[LabRenderFlag::ShowFullDetail]); - Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_light, + prop* propp = prop_id_lookup(obj->instance); + propp->flags.set(Prop::Prop_Flags::Draw_as_wireframe, renderFlags[LabRenderFlag::ShowWireframe]); + propp->flags.set(Prop::Prop_Flags::Render_full_detail, renderFlags[LabRenderFlag::ShowFullDetail]); + propp->flags.set(Prop::Prop_Flags::Render_without_light, renderFlags[LabRenderFlag::NoLighting] || currentMissionBackground == LAB_MISSION_NONE_STRING); - Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_diffuse, renderFlags[LabRenderFlag::NoDiffuseMap]); - Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_glowmap, renderFlags[LabRenderFlag::NoGlowMap]); - Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_normalmap, renderFlags[LabRenderFlag::NoNormalMap]); - Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_specmap, renderFlags[LabRenderFlag::NoSpecularMap]); - Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_reflectmap, renderFlags[LabRenderFlag::NoReflectMap]); - Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_heightmap, renderFlags[LabRenderFlag::NoHeightMap]); - Props[obj->instance].flags.set(Prop::Prop_Flags::Render_without_ambientmap, renderFlags[LabRenderFlag::NoAOMap]); + propp->flags.set(Prop::Prop_Flags::Render_without_diffuse, renderFlags[LabRenderFlag::NoDiffuseMap]); + propp->flags.set(Prop::Prop_Flags::Render_without_glowmap, renderFlags[LabRenderFlag::NoGlowMap]); + propp->flags.set(Prop::Prop_Flags::Render_without_normalmap, renderFlags[LabRenderFlag::NoNormalMap]); + propp->flags.set(Prop::Prop_Flags::Render_without_specmap, renderFlags[LabRenderFlag::NoSpecularMap]); + propp->flags.set(Prop::Prop_Flags::Render_without_reflectmap, renderFlags[LabRenderFlag::NoReflectMap]); + propp->flags.set(Prop::Prop_Flags::Render_without_heightmap, renderFlags[LabRenderFlag::NoHeightMap]); + propp->flags.set(Prop::Prop_Flags::Render_without_ambientmap, renderFlags[LabRenderFlag::NoAOMap]); } if (obj->type == OBJ_WEAPON) { diff --git a/code/object/collideshipship.cpp b/code/object/collideshipship.cpp index b94c4baaadf..07da5fa2fb8 100644 --- a/code/object/collideshipship.cpp +++ b/code/object/collideshipship.cpp @@ -591,8 +591,9 @@ void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_ } else if (heavy->type == OBJ_DEBRIS) { pm = model_get(Debris[heavy->instance].model_num); } else if (heavy->type == OBJ_PROP) { - pm = model_get(Prop_info[Props[heavy->instance].prop_info_index].model_num); - model_instance_num = Props[heavy->instance].model_instance_num; + prop* propp = prop_id_lookup(heavy->instance); + pm = model_get(Prop_info[propp->prop_info_index].model_num); + model_instance_num = propp->model_instance_num; pmi = model_get_instance(model_instance_num); } else { // we should have caught this already diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index 2ec017ef2b3..7eedeeda536 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -629,7 +629,7 @@ static int prop_weapon_check_collision(object* prop_objp, object* weapon_objp, f Assert(prop_objp->type == OBJ_PROP); Assert(prop_objp->instance >= 0); - prop* propp = &Props[prop_objp->instance]; + prop* propp = prop_id_lookup(prop_objp->instance); prop_info* prinfo = &Prop_info[propp->prop_info_index]; Assert(weapon_objp != nullptr); diff --git a/code/object/object.cpp b/code/object/object.cpp index d9ded4fefe7..ef6b3092b8b 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -2277,7 +2277,7 @@ int object_get_model_instance_num(const object *objp) case OBJ_RAW_POF: return Pof_objects[objp->instance].model_instance; case OBJ_PROP: - return Props[objp->instance].model_instance_num; + return prop_id_lookup(objp->instance)->model_instance_num; default: break; } diff --git a/code/object/objectsort.cpp b/code/object/objectsort.cpp index 9f0159afca2..a5eec1d120a 100644 --- a/code/object/objectsort.cpp +++ b/code/object/objectsort.cpp @@ -77,7 +77,7 @@ inline bool sorted_obj::operator < (const sorted_obj &other) const } else if (obj->type == OBJ_RAW_POF) { model_num_a = Pof_objects[obj->instance].model_num; } else if (obj->type == OBJ_PROP) { - model_num_a = Props[obj->instance].model_instance_num; + model_num_a = prop_id_lookup(obj->instance)->model_instance_num; } if ( other.obj->type == OBJ_SHIP ) { @@ -105,7 +105,7 @@ inline bool sorted_obj::operator < (const sorted_obj &other) const } else if (other.obj->type == OBJ_RAW_POF) { model_num_b = Pof_objects[other.obj->instance].model_num; } else if (other.obj->type == OBJ_PROP) { - model_num_b = Props[other.obj->instance].model_instance_num; + model_num_b = prop_id_lookup(other.obj->instance)->model_instance_num; } if ( model_num_a == model_num_b ) { diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index ed4d4b203a1..10532eed0ff 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -5839,7 +5839,7 @@ const prop *eval_prop(int node) if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_PROP) return nullptr; - return &Props[Sexp_nodes[node].cache->ship_registry_index]; + return prop_id_lookup(Sexp_nodes[node].cache->ship_registry_index); } // maybe forward to a special-arg node @@ -5859,7 +5859,7 @@ const prop *eval_prop(int node) if (!is_node_value_dynamic(node)) Sexp_nodes[node].cache = new sexp_cached_data(OPF_PROP, -1, prop_idx); - return &Props[prop_idx]; + return prop_id_lookup(prop_idx); } // we know nothing about this prop, apparently diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index c3a46892d6d..624dcd6bf1b 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -17,7 +17,7 @@ bool Props_inited = false; SCP_vector Prop_info; -SCP_vector Props; +SCP_vector> Props; static SCP_vector Removed_props; @@ -40,9 +40,13 @@ int prop_name_lookup(const char *name) Assertion(name != nullptr, "NULL name passed to prop_name_lookup"); for (int i=0; i(Props.size()); i++){ - if (Props[i].objnum >= 0){ - if (Objects[Props[i].objnum].type == OBJ_PROP){ - if (!stricmp(name, Props[i].prop_name)){ + auto prop = Props[i] ? &Props[i].value() : nullptr; + if (prop == nullptr) { + continue; + } + if (prop->objnum >= 0){ + if (Objects[prop->objnum].type == OBJ_PROP) { + if (!stricmp(name, prop->prop_name)) { return i; } } @@ -53,6 +57,15 @@ int prop_name_lookup(const char *name) return -1; } +prop* prop_id_lookup(int id) +{ + if (id < 0 || id >= static_cast(Props.size()) || !Props[id].has_value()) { + Assertion(false, "Could not find prop for id %d", id); + return nullptr; + } + return &Props[id].value(); +} + void parse_prop_table(const char* filename) { read_file_text(filename, CF_TYPE_TABLES); @@ -257,7 +270,10 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) pip = &(Prop_info[prop_type]); Props.emplace_back(prop()); - propp = &Props.back(); + int new_id = static_cast(Props.size()) - 1; + propp = prop_id_lookup(new_id); + Assertion(propp != nullptr, "Could not create prop!"); + propp->prop_info_index = prop_type; if ((name == nullptr) || (prop_name_lookup(name) >= 0)) { @@ -321,7 +337,7 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) int objnum = obj_create(OBJ_PROP, -1, - static_cast(Props.size() - 1), + new_id, orient, pos, model_get_radius(pip->model_num), @@ -389,23 +405,25 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) void prop_delete(object* obj) { int num = obj->instance; - Assert(num >= 0 && num <= static_cast(Props.size())); int objnum = OBJ_INDEX(obj); - Assert(Props[num].objnum == objnum); + Assertion(Props[num].has_value(), "Props[%d] is already nullopt!", num); - prop* propp = &Props[num]; + prop& propp = *Props[num]; + Assertion(propp.objnum == objnum, "Prop object id does not match passed object!"); - propp->objnum = -1; + propp.objnum = -1; //animation::ModelAnimationSet::stopAnimations(model_get_instance(propp->model_instance_num)); // glow point banks - propp->glow_point_bank_active.clear(); + propp.glow_point_bank_active.clear(); - model_delete_instance(propp->model_instance_num); + model_delete_instance(propp.model_instance_num); - Props.erase(Props.begin() + num); + // Leave the slot empty for the duration of the scene + // The Props array will be compacted at the end of the level + Props[num] = std::nullopt; } /** @@ -416,8 +434,7 @@ void prop_delete(object* obj) */ static void prop_model_change(int n, int prop_type) { - Assert( n >= 0 && n < MAX_PROPS ); - prop* sp = &Props[n]; + prop* sp = prop_id_lookup(n); prop_info* sip = &(Prop_info[prop_type]); object* objp = &Objects[sp->objnum]; polymodel_instance* pmi = model_get_instance(sp->model_instance_num); @@ -497,8 +514,7 @@ static void prop_model_change(int n, int prop_type) */ void change_prop_type(int n, int prop_type) { - Assert( n >= 0 && n < MAX_PROPS ); - prop* sp = &Props[n]; + prop* sp = prop_id_lookup(n); // do a quick out if we're already using the new ship class if (sp->prop_info_index == prop_type) @@ -515,10 +531,6 @@ void change_prop_type(int n, int prop_type) prop_model_change(n, prop_type); sp->prop_info_index = prop_type; - // get the before and after models (the new model may have only been loaded in ship_model_change) - auto pm = model_get(sip->model_num); - auto pm_orig = model_get(sip_orig->model_num); - // check class-specific flags if (sip->flags[Prop::Info_Flags::No_collide]) // changing TO a no-collision ship class @@ -530,10 +542,10 @@ void change_prop_type(int n, int prop_type) void prop_render(object* obj, model_draw_list* scene) { int num = obj->instance; - Assert(num >= 0 && num <= static_cast(Props.size())); - prop* propp = &Props[num]; - prop_info* pip = &Prop_info[Props[num].prop_info_index]; + prop* propp = prop_id_lookup(num); + + prop_info* pip = &Prop_info[propp->prop_info_index]; MONITOR_INC(NumPropsRend, 1); @@ -637,9 +649,14 @@ void spawn_test_prop() void props_level_close() { - while (!Props.empty()) { - prop_delete(&Objects[Props.back().objnum]); + for (auto& opt_prop : Props) { + if (opt_prop.has_value()) { + prop_delete(&Objects[opt_prop->objnum]); + } } + + // Clear all props and empty prop slots + Props.clear(); } @@ -648,14 +665,13 @@ int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, col { mc_info mc; - Assert(prop_obj->type == OBJ_PROP); + Assertion(prop_obj->type == OBJ_PROP, "Object is not a prop!"); int num = prop_obj->instance; - Assert(num >= 0 && num < (int)Props.size()); - Assert(Props[num].objnum == OBJ_INDEX(prop_obj)); - - prop* propp = &Props[num]; + prop* propp = prop_id_lookup(num); + Assertion(propp->objnum == OBJ_INDEX(prop_obj), "Prop object id does not match passed object!"); + prop_info* prinfo = &Prop_info[propp->prop_info_index]; // debris_hit_info NULL - so debris-weapon collision @@ -839,7 +855,7 @@ int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, col asteroid* astp = &Asteroids[instance]; mc.model_instance_num = astp->model_instance_num; mc.model_num = Asteroid_info[astp->asteroid_type].subtypes[astp->asteroid_subtype].model_number; - mc.submodel_num - 1; + mc.submodel_num - 1; // Typo from asteroth? } mc.orient = &heavy_obj->orient; // The object's orient @@ -862,7 +878,7 @@ int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, col } } else { // prop is the heavy object mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE); - int instance = heavy_obj->instance; + //int instance = heavy_obj->instance; // fill the appropriate model data mc.model_instance_num = propp->model_instance_num; diff --git a/code/prop/prop.h b/code/prop/prop.h index fad64e38045..2e0576b7bd3 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -50,7 +50,7 @@ extern bool Props_inited; // Global prop info array extern SCP_vector Prop_info; -extern SCP_vector Props; +extern SCP_vector> Props; inline int prop_info_size() { @@ -69,6 +69,7 @@ void props_level_close(); int prop_info_lookup(const char* token); int prop_name_lookup(const char* name); +prop* prop_id_lookup(int id); void change_prop_type(int n, int prop_type); diff --git a/code/scripting/api/libs/mission.cpp b/code/scripting/api/libs/mission.cpp index 8a7ced08c1a..3dd647c1d81 100644 --- a/code/scripting/api/libs/mission.cpp +++ b/code/scripting/api/libs/mission.cpp @@ -129,6 +129,45 @@ int object_subclass_count(A& object_subclass_array, size_t array_size) return object_subclass_at_index(object_subclass_array, array_size, COUNT_OBJECTS); } +// Overload for a vector of std::optional objects +template +int object_subclass_at_index(const SCP_vector>& vec, int index) +{ + int count = 0; + + for (const auto& opt_obj : vec) { + + if (!opt_obj.has_value()) { + continue; + } + + + const T& obj = *opt_obj; + + int objnum = obj.objnum; + if (objnum < 0 || objnum >= MAX_OBJECTS) + continue; + if (Objects[objnum].flags[Object::Object_Flags::Should_be_dead]) + continue; + + ++count; + + if (count == index) { + return obj.objnum; + } + } + + if (index == COUNT_OBJECTS) + return count; + else + return -1; +} + +template +int object_subclass_count(A& object_subclass_array) +{ + return object_subclass_at_index(object_subclass_array, COUNT_OBJECTS); +} namespace scripting { namespace api { @@ -565,7 +604,7 @@ ADE_INDEXER(l_Mission_Props, "number/string IndexOrName", "Gets prop", "prop", " if (idx >= 0) { - return ade_set_args(L, "o", l_Prop.Set(object_h(&Objects[Props[idx].objnum]))); + return ade_set_args(L, "o", l_Prop.Set(object_h(&Objects[prop_id_lookup(idx)->objnum]))); } else { @@ -573,7 +612,7 @@ ADE_INDEXER(l_Mission_Props, "number/string IndexOrName", "Gets prop", "prop", " int objnum = -1; if (idx > 0) - objnum = object_subclass_at_index(Props, MAX_PROPS, idx); + objnum = object_subclass_at_index(Props, idx); return ade_set_args(L, "o", l_Prop.Set(object_h(objnum))); } @@ -586,7 +625,7 @@ ADE_FUNC(__len, l_Mission_Props, NULL, "number", "Number of props in the mission, or 0 if props haven't been initialized yet") { - return ade_set_args(L, "i", object_subclass_count(Props, MAX_PROPS)); + return ade_set_args(L, "i", object_subclass_count(Props)); } //****SUBLIBRARY: Mission/Waypoints @@ -1410,8 +1449,6 @@ ADE_FUNC(createProp, int obj_idx = prop_create(real_orient, &pos, pclass, name); if(obj_idx >= 0) { - auto propp = &Props[Objects[obj_idx].instance]; - prop_info* pip = &Prop_info[pclass]; model_page_in_textures(pip->model_num, pclass); diff --git a/code/scripting/api/objs/prop.cpp b/code/scripting/api/objs/prop.cpp index 7ca0b2808af..53903cce2c5 100644 --- a/code/scripting/api/objs/prop.cpp +++ b/code/scripting/api/objs/prop.cpp @@ -64,7 +64,7 @@ ADE_VIRTVAR(Name, if (!objh->isValid()) return ade_set_error(L, "s", ""); - prop* propp = &Props[objh->objp()->instance]; + prop* propp = prop_id_lookup(objh->objp()->instance); if (ADE_SETTING_VAR && s != nullptr) { auto len = sizeof(propp->prop_name); @@ -90,7 +90,7 @@ ADE_VIRTVAR(Class, if (!objh->isValid()) return ade_set_error(L, "o", l_Propclass.Set(-1)); - prop* propp = &Props[objh->objp()->instance]; + prop* propp = prop_id_lookup(objh->objp()->instance); if (ADE_SETTING_VAR && idx > -1) { change_prop_type(objh->objp()->instance, idx); @@ -117,10 +117,12 @@ ADE_VIRTVAR(Textures, if (!dh->isValid()) return ade_set_error(L, "o", l_ModelInstanceTextures.Set(modelinstance_h())); - polymodel_instance* dest = model_get_instance(Props[dh->objp()->instance].model_instance_num); + prop* propp = prop_id_lookup(dh->objp()->instance); + + polymodel_instance* dest = model_get_instance(propp->model_instance_num); if (ADE_SETTING_VAR && sh && sh->isValid()) { - dest->texture_replace = model_get_instance(Props[sh->objp()->instance].model_instance_num)->texture_replace; + dest->texture_replace = model_get_instance(propp->model_instance_num)->texture_replace; } return ade_set_args(L, "o", l_ModelInstanceTextures.Set(modelinstance_h(dest))); diff --git a/code/weapon/beam.cpp b/code/weapon/beam.cpp index 75fc6e833e2..50166e04f36 100644 --- a/code/weapon/beam.cpp +++ b/code/weapon/beam.cpp @@ -2198,7 +2198,7 @@ int beam_get_model(object *objp) return Asteroid_info[Asteroids[objp->instance].asteroid_type].subtypes[pof].model_number; case OBJ_PROP: - return Prop_info[Props[objp->instance].prop_info_index].model_num; + return Prop_info[prop_id_lookup(objp->instance)->prop_info_index].model_num; default: // this shouldn't happen too often @@ -3402,11 +3402,11 @@ int beam_collide_prop(obj_pair* pair) // get the ship Assert(pair->b->instance >= 0); Assert(pair->b->type == OBJ_PROP); - Assert(Props[pair->b->instance].objnum == OBJ_INDEX(pair->b)); + Assert(prop_id_lookup(pair->b->instance)->objnum == OBJ_INDEX(pair->b)); if ((pair->b->type != OBJ_PROP) || (pair->b->instance < 0)) return 1; prop_objp = pair->b; - prop* propp = &Props[prop_objp->instance]; + prop* propp = prop_id_lookup(prop_objp->instance); bwi = &Weapon_info[a_beam->weapon_info_index]; diff --git a/fred2/fredrender.cpp b/fred2/fredrender.cpp index 2d4a590cce6..52f487f7118 100644 --- a/fred2/fredrender.cpp +++ b/fred2/fredrender.cpp @@ -546,7 +546,7 @@ void display_ship_info() { prop* propp; int prop_type; - propp = &Props[objp->instance]; + propp = prop_id_lookup(objp->instance); prop_type = propp->prop_info_index; ASSERT(prop_type >= 0); sprintf(buf, "%s\n", propp->prop_name); @@ -1880,14 +1880,16 @@ void render_one_model_htl(object *objp) { render_info.set_debug_flags(debug_flags); + prop* propp = prop_id_lookup(z); + if (Fred_outline) { render_info.set_color(Fred_outline >> 16, (Fred_outline >> 8) & 0xff, Fred_outline & 0xff); render_info.set_flags(flags | MR_SHOW_OUTLINE_HTL | MR_NO_LIGHTING | MR_NO_POLYS | MR_NO_TEXTURING); - model_render_immediate(&render_info, Prop_info[Props[z].prop_info_index].model_num, Props[z].model_instance_num, &objp->orient, &objp->pos); + model_render_immediate(&render_info, Prop_info[propp->prop_info_index].model_num, propp->model_instance_num, &objp->orient, &objp->pos); } // render_info.set_flags(flags); - model_render_immediate(&render_info, Prop_info[Props[z].prop_info_index].model_num, Props[z].model_instance_num, &objp->orient, &objp->pos); + model_render_immediate(&render_info, Prop_info[propp->prop_info_index].model_num, propp->model_instance_num, &objp->orient, &objp->pos); } // build flags diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp index cce2eecc39b..ec2636113e6 100644 --- a/fred2/fredview.cpp +++ b/fred2/fredview.cpp @@ -1479,7 +1479,7 @@ void CFREDView::OnContextMenu(CWnd* /*pWnd*/, CPoint point) } else if (Objects[objnum].type == OBJ_PROP) { id = ID_EDITORS_PROPS; - str.Format("Edit %s", Props[Objects[objnum].instance].prop_name); + str.Format("Edit %s", prop_id_lookup(Objects[objnum].instance)->prop_name); } else if (Objects[objnum].type == OBJ_JUMP_NODE) { auto jnp = jumpnode_get_by_objnum(objnum); diff --git a/fred2/management.cpp b/fred2/management.cpp index 401f9209517..321e709b7f9 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -519,7 +519,7 @@ void fix_prop_name(int prop) int i = 1; do { - sprintf(Props[prop].prop_name, "U.R.A. Dummy %d", i++); + sprintf(prop_id_lookup(prop)->prop_name, "U.R.A. Dummy %d", i++); } while (query_prop_name_duplicate(prop)); } @@ -642,13 +642,19 @@ int create_prop(matrix* orient, vec3d* pos, int prop_type) int query_prop_name_duplicate(int prop) { - int i; + const auto& target = prop_id_lookup(prop)->prop_name; - for (i = 0; i < static_cast(Props.size()); i++) - if ((i != prop) && (Props[i].objnum != -1)) - if (!stricmp(Props[i].prop_name, Props[prop].prop_name)) - return 1; + for (size_t i = 0; i < Props.size(); ++i) { + if (i == static_cast(prop)) + continue; + const auto& other = Props[i]; + if (other.has_value() && other->objnum != -1) { + if (!stricmp(other->prop_name, target)) { + return 1; + } + } + } return 0; } diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index 07d607f4397..39091d2c3cb 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -5284,34 +5284,37 @@ int CFred_mission_save::save_props() fout("\t\t;! %d total", static_cast(Props.size())); for (int i = 0; i < static_cast(Props.size()); i++) { - required_string_either_fred("$Name:", "#Events"); - required_string_fred("$Name:"); - parse_comments(2); - fout(" %s", Props[i].prop_name); - - required_string_fred("$Class:"); - parse_comments(2); - fout(" %d", Props[i].prop_info_index); + const auto& p = Props[i]; + if (p.has_value()) { + required_string_either_fred("$Name:", "#Events"); + required_string_fred("$Name:"); + parse_comments(2); + fout(" %s", p->prop_name); - required_string_fred("$Location:"); - parse_comments(); - save_vector(Objects[Props[i].objnum].pos); + required_string_fred("$Class:"); + parse_comments(2); + fout(" %d", p->prop_info_index); - required_string_fred("$Orientation:"); - parse_comments(); - save_matrix(Objects[Props[i].objnum].orient); + required_string_fred("$Location:"); + parse_comments(); + save_vector(Objects[p->objnum].pos); - if (optional_string_fred("+Flags:", "$Name:")) { + required_string_fred("$Orientation:"); parse_comments(); - fout(" ("); - } else - fout("\n+Flags: ("); + save_matrix(Objects[p->objnum].orient); - if (!(Objects[Props[i].objnum].flags[Object::Object_Flags::Collides])) - fout(" \"no_collide\""); - fout(" )"); + if (optional_string_fred("+Flags:", "$Name:")) { + parse_comments(); + fout(" ("); + } else + fout("\n+Flags: ("); - fso_comment_pop(); + if (!(Objects[p->objnum].flags[Object::Object_Flags::Collides])) + fout(" \"no_collide\""); + fout(" )"); + + fso_comment_pop(); + } } } diff --git a/fred2/propdlg.cpp b/fred2/propdlg.cpp index a2e84925a02..8f6dfa0b646 100644 --- a/fred2/propdlg.cpp +++ b/fred2/propdlg.cpp @@ -82,8 +82,8 @@ void prop_dlg::initialize_data(int full_update) // Check if we have a selected prop if (query_valid_object() && Objects[cur_object_index].type == OBJ_PROP) { - auto& prp = Props[Objects[cur_object_index].instance]; - m_name = _T(prp.prop_name); + auto prp = prop_id_lookup(Objects[cur_object_index].instance); + m_name = _T(prp->prop_name); } else { // No valid prop selected; disable editing fields m_name = _T(""); @@ -135,16 +135,20 @@ int prop_dlg::update_data() if (query_valid_object() && Objects[cur_object_index].type == OBJ_PROP) { int this_instance = Objects[cur_object_index].instance; - auto& prp = Props[this_instance]; + auto prp = prop_id_lookup(this_instance); m_name.TrimLeft(); m_name.TrimRight(); for (size_t i = 0; i < Props.size(); ++i) { - if ((int)i == this_instance) + if (static_cast(i) == this_instance) continue; // skip self - if (!stricmp(m_name, Props[i].prop_name)) { + if (!Props[i].has_value()) { + continue; + } + + if (!stricmp(m_name, Props[i].value().prop_name)) { if (bypass_errors) return 1; @@ -155,16 +159,16 @@ int prop_dlg::update_data() if (z == IDCANCEL) return -1; - m_name = _T(prp.prop_name); + m_name = _T(prp->prop_name); UpdateData(FALSE); return 1; } } // Passed name validation - strcpy_s(prp.prop_name, m_name); + strcpy_s(prp->prop_name, m_name); - prp.flags.reset(); // Clear all flags + prp->flags.reset(); // Clear all flags for (int i = 0; i < m_flags_list.GetCount(); ++i) { size_t flag_index = static_cast(m_flags_list.GetItemData(i)); diff --git a/fred2/sexp_tree.cpp b/fred2/sexp_tree.cpp index 530c561815c..96276930e09 100644 --- a/fred2/sexp_tree.cpp +++ b/fred2/sexp_tree.cpp @@ -6165,7 +6165,7 @@ sexp_list_item *sexp_tree::get_listing_opf_prop(int parent_node) ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { if (ptr->type == OBJ_PROP) { - head.add_data(Props[ptr->instance].prop_name); + head.add_data(prop_id_lookup(ptr->instance)->prop_name); } ptr = GET_NEXT(ptr); diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index d4a3424d5f5..e15c7d45c57 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -5477,34 +5477,37 @@ int CFred_mission_save::save_props() fout("\t\t;! %d total", static_cast(Props.size())); for (int i = 0; i < static_cast(Props.size()); i++) { - required_string_either_fred("$Name:", "#Events"); - required_string_fred("$Name:"); - parse_comments(2); - fout(" %s", Props[i].prop_name); - - required_string_fred("$Class:"); - parse_comments(2); - fout(" %d", Props[i].prop_info_index); + const auto& p = Props[i]; + if (p.has_value()) { + required_string_either_fred("$Name:", "#Events"); + required_string_fred("$Name:"); + parse_comments(2); + fout(" %s", p->prop_name); - required_string_fred("$Location:"); - parse_comments(); - save_vector(Objects[Props[i].objnum].pos); + required_string_fred("$Class:"); + parse_comments(2); + fout(" %d", p->prop_info_index); - required_string_fred("$Orientation:"); - parse_comments(); - save_matrix(Objects[Props[i].objnum].orient); + required_string_fred("$Location:"); + parse_comments(); + save_vector(Objects[p->objnum].pos); - if (optional_string_fred("+Flags:", "$Name:")) { + required_string_fred("$Orientation:"); parse_comments(); - fout(" ("); - } else - fout("\n+Flags: ("); + save_matrix(Objects[p->objnum].orient); - if (!(Objects[Props[i].objnum].flags[Object::Object_Flags::Collides])) - fout(" \"no_collide\""); - fout(" )"); + if (optional_string_fred("+Flags:", "$Name:")) { + parse_comments(); + fout(" ("); + } else + fout("\n+Flags: ("); - fso_comment_pop(); + if (!(Objects[p->objnum].flags[Object::Object_Flags::Collides])) + fout(" \"no_collide\""); + fout(" )"); + + fso_comment_pop(); + } } } diff --git a/qtfred/src/ui/widgets/sexp_tree.cpp b/qtfred/src/ui/widgets/sexp_tree.cpp index 835c42105c2..e3b02f83df9 100644 --- a/qtfred/src/ui/widgets/sexp_tree.cpp +++ b/qtfred/src/ui/widgets/sexp_tree.cpp @@ -4069,7 +4069,7 @@ sexp_list_item *sexp_tree::get_listing_opf_prop(int parent_node) ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { if (ptr->type == OBJ_PROP) { - head.add_data(Props[ptr->instance].prop_name); + head.add_data(prop_id_lookup(ptr->instance)->prop_name); } ptr = GET_NEXT(ptr); From a0908ddac9f54d341638c0e59d992eef94a7418e Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sat, 14 Jun 2025 22:25:00 -0500 Subject: [PATCH 09/25] allow props to block suns --- code/prop/prop.cpp | 21 --------------------- code/prop/prop.h | 4 +--- code/ship/shipfx.cpp | 25 +++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 624dcd6bf1b..7561db5c7c6 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -626,27 +626,6 @@ void prop_render(object* obj, model_draw_list* scene) model_render_queue(&render_info, scene, pip->model_num, &obj->orient, &obj->pos); } -void spawn_test_prop() -{ - int prop_idx = prop_info_lookup("TestProp"); // whatever’s in your props.tbl - if (prop_idx < 0) { - mprintf(("TEST: Prop not found!\n")); - return; - } - - matrix mtx = vmd_identity_matrix; - vec3d pos = ZERO_VECTOR; - pos.xyz.z = -2000.0f; - - int objnum = prop_create(&mtx, &pos, prop_idx, "Test Prop"); - - if (objnum >= 0) { - mprintf(("TEST: Spawned prop '%s' at objnum %d\n", Prop_info[prop_idx].name, objnum)); - } else { - mprintf(("TEST: Failed to create prop\n")); - } -} - void props_level_close() { for (auto& opt_prop : Props) { diff --git a/code/prop/prop.h b/code/prop/prop.h index 2e0576b7bd3..41e19b4345e 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -73,6 +73,4 @@ prop* prop_id_lookup(int id); void change_prop_type(int n, int prop_type); -int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, collision_info_struct* prop_hit_info); - -void spawn_test_prop(); \ No newline at end of file +int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, collision_info_struct* prop_hit_info); \ No newline at end of file diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index ea3aac21d30..3ae2e1c32dc 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -33,6 +33,7 @@ #include "object/objectsnd.h" #include "parse/parselo.h" #include "playerman/player.h" +#include "prop/prop.h" #include "render/3d.h" // needed for View_position, which is used when playing a 3D sound #include "render/batching.h" #include "scripting/hook_api.h" @@ -842,6 +843,30 @@ bool shipfx_eye_in_shadow( vec3d *eye_pos, object * src_obj, int light_n ) } } + for (const auto& prop : Props) { + if (prop.has_value()) { + objp = &Objects[prop->objnum]; + if (objp->flags[Object::Object_Flags::Should_be_dead]) + continue; + + if (src_obj != objp) { + vm_vec_scale_add(&rp1, &rp0, &light_dir, objp->radius * 10.0f); + + mc.model_instance_num = prop->model_instance_num; + mc.model_num = Prop_info[prop->prop_info_index].model_num; + mc.orient = &objp->orient; + mc.pos = &objp->pos; + mc.p0 = &rp0; + mc.p1 = &rp1; + mc.flags = MC_CHECK_MODEL; + + if (model_collide(&mc)) { + return true; + } + } + } + } + // Check all the big hull debris pieces. for (auto &db: Debris) { if ( !(db.flags[Debris_Flags::Used]) || !db.is_hull ){ From faa0beb247fad571a2fed98ef201bb2fbf32c5c9 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sun, 15 Jun 2025 00:01:16 -0500 Subject: [PATCH 10/25] minor cleanup and remove arbitrary limit. Still limited to 500 objects. --- code/globalincs/globals.h | 3 --- code/mission/missionparse.cpp | 1 - code/prop/prop.cpp | 13 +------------ code/prop/prop.h | 6 +++++- 4 files changed, 6 insertions(+), 17 deletions(-) diff --git a/code/globalincs/globals.h b/code/globalincs/globals.h index 141e9c2e20f..6c1fcb3dca5 100644 --- a/code/globalincs/globals.h +++ b/code/globalincs/globals.h @@ -34,9 +34,6 @@ #define MAX_SHIPS 500 // max number of ship instances there can be.DTP; bumped from 200 to 400, then to 500 in 2022 #define SHIPS_LIMIT 500 // what MAX_SHIPS will be at release time (for error checking in debug mode); dtp Bumped from 200 to 400, then to 500 in 2022 -// from prop.h -#define MAX_PROPS 200 // Arbitrary guess - // from missionparse.h and then redefined to the same value in sexp.h #define TOKEN_LENGTH 32 diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index fe206833210..932f633b349 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -5169,7 +5169,6 @@ void parse_props(mission* pm) { if (optional_string("#Props")) { while (required_string_either("#Events", "$Name:")) { - Assert(Parse_props.size() < MAX_PROPS); parse_prop(pm); } } diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 7561db5c7c6..0f400943703 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -251,17 +251,6 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) prop_info* pip; prop* propp; - if (Props.size() >= MAX_PROPS) { - if (!Fred_running) { - Error(LOCATION, - "There is a limit of %d props in the mission at once. Please be sure that you do not have more " - "than %d props present in the mission at the same time.", - MAX_PROPS, - MAX_PROPS); - } - return -1; - } - Assertion((prop_type >= 0) && (prop_type < static_cast(Prop_info.size())), "Invalid prop_type %d passed to prop_create() (expected value in the range 0-%d)\n", prop_type, @@ -834,7 +823,7 @@ int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, col asteroid* astp = &Asteroids[instance]; mc.model_instance_num = astp->model_instance_num; mc.model_num = Asteroid_info[astp->asteroid_type].subtypes[astp->asteroid_subtype].model_number; - mc.submodel_num - 1; // Typo from asteroth? + mc.submodel_num = -1; } mc.orient = &heavy_obj->orient; // The object's orient diff --git a/code/prop/prop.h b/code/prop/prop.h index 41e19b4345e..5b512502bf0 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -47,9 +47,13 @@ typedef struct parsed_prop { extern bool Props_inited; -// Global prop info array +// Global prop info extern SCP_vector Prop_info; +// Global prop objects. Vector of optionals so that we can have stable indices +// and still be able to remove props. Deleted props are set to std::nullopt +// so any access should check if the optional has a value first. +// The vector is cleared at the end of each mission, never during. extern SCP_vector> Props; inline int prop_info_size() From f7fb8c813ca5c1b5f9cc18e1bba783c9fa19d153 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sun, 15 Jun 2025 20:23:23 -0500 Subject: [PATCH 11/25] Sort props by category --- code/lab/dialogs/lab_ui.cpp | 47 +++++--- code/lab/dialogs/lab_ui.h | 3 +- code/lab/dialogs/lab_ui_helpers.cpp | 4 +- code/mission/missionparse.cpp | 4 +- code/model/modelrender.cpp | 2 +- code/parse/sexp.cpp | 2 +- code/prop/prop.cpp | 155 ++++++++++++++++++++++---- code/prop/prop.h | 15 ++- code/scripting/api/objs/propclass.cpp | 2 +- fred2/mainfrm.cpp | 15 ++- fred2/sexp_tree.cpp | 2 +- qtfred/src/ui/widgets/sexp_tree.cpp | 2 +- 12 files changed, 201 insertions(+), 52 deletions(-) diff --git a/code/lab/dialogs/lab_ui.cpp b/code/lab/dialogs/lab_ui.cpp index 5a13f4c8e20..49028b924a6 100644 --- a/code/lab/dialogs/lab_ui.cpp +++ b/code/lab/dialogs/lab_ui.cpp @@ -93,6 +93,32 @@ void LabUi::build_weapon_subtype_list() const } } +void LabUi::build_prop_subtype_list() const +{ + for (size_t i = 0; i < Prop_categories.size(); i++) { + with_TreeNode(Prop_categories[i].name.c_str()) + { + int prop_idx = 0; + + for (auto const& class_def : Prop_info) { + if (lcase_equal(class_def.category, Prop_categories[i].name)) { + SCP_string node_label; + sprintf(node_label, "##PropClassIndex%i", prop_idx); + TreeNodeEx(node_label.c_str(), + ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen, + "%s", + class_def.name.c_str()); + + if (IsItemClicked() && !IsItemToggledOpen()) { + getLabManager()->changeDisplayedObject(LabMode::Prop, prop_idx); + } + } + prop_idx++; + } + } + } +} + void LabUi::build_asteroid_list() { with_TreeNode("Asteroids") @@ -172,24 +198,11 @@ void LabUi::build_object_list() } } -void LabUi::build_prop_list() +void LabUi::build_prop_list() const { with_TreeNode("Prop Classes") { - int prop_info_idx = 0; - - for (auto const& class_def : Prop_info) { - SCP_string node_label; - sprintf(node_label, "##PropClassIndex%i", prop_info_idx); - TreeNodeEx(node_label.c_str(), - ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen, - "%s", - class_def.name); - if (IsItemClicked() && !IsItemToggledOpen()) { - getLabManager()->changeDisplayedObject(LabMode::Prop, prop_info_idx); - } - prop_info_idx++; - } + build_prop_subtype_list(); } } @@ -294,9 +307,9 @@ void LabUi::show_object_selector() const build_weapon_list(); - build_object_list(); - build_prop_list(); + + build_object_list(); } } } diff --git a/code/lab/dialogs/lab_ui.h b/code/lab/dialogs/lab_ui.h index 9bcb46753de..9610cf76d28 100644 --- a/code/lab/dialogs/lab_ui.h +++ b/code/lab/dialogs/lab_ui.h @@ -32,7 +32,8 @@ class LabUi { static void build_object_list(); static void build_asteroid_list(); static void build_debris_list(); - static void build_prop_list(); + void build_prop_list() const; + void build_prop_subtype_list() const; void build_background_list() const; void show_render_options(); void show_object_options() const; diff --git a/code/lab/dialogs/lab_ui_helpers.cpp b/code/lab/dialogs/lab_ui_helpers.cpp index 137f07550ff..ea373707ae0 100644 --- a/code/lab/dialogs/lab_ui_helpers.cpp +++ b/code/lab/dialogs/lab_ui_helpers.cpp @@ -452,7 +452,7 @@ SCP_string get_prop_table_text(const prop_info* pip) while (line2[i] == ' ' || line2[i] == '\t' || line2[i] == '@') i++; - if (!stricmp(line2 + i, pip->name)) { + if (!stricmp(line2 + i, pip->name.c_str())) { result += "-- props.tbl -------------------------------\r\n"; found = 1; } @@ -510,7 +510,7 @@ SCP_string get_prop_table_text(const prop_info* pip) while (line2[i] == ' ' || line2[i] == '\t' || line2[i] == '@') i++; - if (!stricmp(line2 + i, pip->name)) { + if (!stricmp(line2 + i, pip->name.c_str())) { memset(file_text, 0, sizeof(file_text)); snprintf(file_text, sizeof(file_text) - 1, diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 932f633b349..8748d732f8e 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -5253,7 +5253,7 @@ void post_process_path_stuff() } // MjnMixael -void post_process_props() +void post_process_mission_props() { for (int i = 0; i < static_cast(Parse_props.size()); i++) { parsed_prop* propp = &Parse_props[i]; @@ -6629,7 +6629,7 @@ bool post_process_mission(mission *pm) ship_weapon *swp; ship_obj *so; - post_process_props(); + post_process_mission_props(); // Goober5000 - this must be done even before post_process_ships_wings because it is a prerequisite ship_clear_ship_type_counts(); diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index 54fcb1a997c..b1436866ba8 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -3062,7 +3062,7 @@ bool render_tech_model(tech_render_type model_type, int x1, int y1, int x2, int } // Make sure model is loaded - model_num = model_load(pip->pof_file); + model_num = model_load(pip->pof_file.c_str()); break; diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 10532eed0ff..01b0f3a863b 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -4486,7 +4486,7 @@ void preload_change_prop_class(const char* text) return; pip = &Prop_info[idx]; - pip->model_num = model_load(pip->pof_file); + pip->model_num = model_load(pip->pof_file.c_str()); if (pip->model_num >= 0) model_page_in_textures(pip->model_num, idx); diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 0f400943703..e2e04a44d2a 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -4,6 +4,7 @@ #include "debris/debris.h" #include "freespace.h" #include "model/model.h" +#include "model/modelreplace.h" #include "parse/parselo.h" #include "render/3d.h" #include "ship/shipfx.h" @@ -19,6 +20,8 @@ SCP_vector Prop_info; SCP_vector> Props; +SCP_vector Prop_categories; + static SCP_vector Removed_props; /** @@ -29,7 +32,7 @@ int prop_info_lookup(const char* token) Assertion(token != nullptr, "NULL token passed to prop_info_lookup"); for (auto it = Prop_info.cbegin(); it != Prop_info.cend(); ++it) - if (!stricmp(token, it->name)) + if (!stricmp(token, it->name.c_str())) return (int)std::distance(Prop_info.cbegin(), it); return -1; @@ -66,11 +69,47 @@ prop* prop_id_lookup(int id) return &Props[id].value(); } +prop_category* prop_get_category(const SCP_string& name) +{ + for (auto it = Prop_categories.begin(); it != Prop_categories.end(); ++it) { + if (lcase_equal(name, it->name)) { + return &(*it); + } + } + return nullptr; +} + void parse_prop_table(const char* filename) { read_file_text(filename, CF_TYPE_TABLES); reset_parse(); + if (optional_string("#PROP CATEGORIES")) { + while (optional_string("$Name:")) { + prop_category pc; + stuff_string(pc.name, F_NAME); + + required_string("+Color:"); + int rgb[3]; + stuff_int_list(rgb, 3, RAW_INTEGER_TYPE); + gr_init_color(&pc.color, rgb[0], rgb[1], rgb[2]); + + Prop_categories.push_back(pc); + + // Replace existing category if name matches (case-insensitive) + auto existing = + std::find_if(Prop_categories.begin(), Prop_categories.end(), [&pc](const prop_category& existing_pc) { + return !stricmp(existing_pc.name.c_str(), pc.name.c_str()); + }); + + if (existing != Prop_categories.end()) { + *existing = pc; // Replace + } else { + Prop_categories.push_back(pc); // Add new + } + } + } + required_string("#PROPS"); while (optional_string("$Name:")) { @@ -147,26 +186,41 @@ void parse_prop_table(const char* filename) continue; } - // Check if there are too many ship classes - //if (Prop_info.size() >= MAX_SHIP_CLASSES) { - //Error(LOCATION, "Too many prop classes before '%s'; maximum is %d.\n", fname, MAX_SHIP_CLASSES); - //} - Prop_info.push_back(prop_info()); pip = &Prop_info.back(); first_time = true; - strcpy_s(pip->name, fname); + pip->name = fname; } - required_string("+POF file:"); - stuff_string(pip->pof_file, F_NAME, MAX_FILENAME_LEN); + if (optional_string("+POF file:")) { + char temp[32]; + stuff_string(temp, F_NAME, MAX_FILENAME_LEN); + + bool valid = true; + + // if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it + if (Parsing_modular_table) { + if (VALID_FNAME(pip->pof_file)) { + if (!model_exists(temp)) { + valid = false; + } + } + } + + + if (valid) { + pip->pof_file = temp; + } else { + WarningEx(LOCATION, "Ship %s\nPOF file \"%s\" invalid!", pip->name.c_str(), temp); + } + } if (optional_string("$Closeup_pos:")) { stuff_vec3d(&pip->closeup_pos); } else if (first_time && VALID_FNAME(pip->pof_file)) { // Calculate from the model file. This is inefficient, but whatever - int model_idx = model_load(pip->pof_file); + int model_idx = model_load(pip->pof_file.c_str()); polymodel* pm = model_get(model_idx); // Go through, find best @@ -189,7 +243,7 @@ void parse_prop_table(const char* filename) if (pip->closeup_zoom <= 0.0f) { mprintf(("Warning! Prop '%s' has a $Closeup_zoom value that is less than or equal to 0 (%f). Setting " "to default value.\n", - pip->name, + pip->name.c_str(), pip->closeup_zoom)); pip->closeup_zoom = 0.5f; } @@ -199,6 +253,10 @@ void parse_prop_table(const char* filename) pip->num_detail_levels = (int)stuff_int_list(pip->detail_distance, MAX_PROP_DETAIL_LEVELS, RAW_INTEGER_TYPE); } + if (optional_string("$Category:")) { + stuff_string(pip->category, F_NAME); + } + if (optional_string("$Custom data:")) { parse_string_map(pip->custom_data, "$end_custom_data", "+Val:"); } @@ -228,6 +286,63 @@ void parse_prop_table(const char* filename) required_string("#END"); } +void post_process_props() +{ + constexpr auto UnknownCategory = "Unknown Category"; + bool create_unknown_category = false; + + // check for missing data + for (auto& pi : Prop_info) { + if (!VALID_FNAME(pi.pof_file)) { + Warning(LOCATION, "Prop '%s' has no POF file specified!", pi.name.c_str()); + } + if (!VALID_FNAME(pi.category)) { + Warning(LOCATION, "Prop '%s' has no category specified!", pi.name.c_str()); + pi.category = UnknownCategory; + create_unknown_category = true; + continue; + } else { + bool found = false; + for (const auto& pc : Prop_categories) { + if (!stricmp(pi.category.c_str(), pc.name.c_str())) { + found = true; + break; + } + } + if (!found) { + Warning(LOCATION, "Prop '%s' has unknown category '%s'", pi.name.c_str(), pi.category.c_str()); + pi.category = UnknownCategory; + create_unknown_category = true; + } + } + } + + if (create_unknown_category) { + prop_category pc; + pc.name = "Unknown Category"; + gr_init_color(&pc.color, 128, 128, 128); + Prop_categories.push_back(pc); + } + + // Sort props by category order from Prop_categories, preserving internal order + std::stable_sort(Prop_info.begin(), Prop_info.end(), [](const prop_info& a, const prop_info& b) { + // Get index of a's category in Prop_categories + auto a_it = std::find_if(Prop_categories.begin(), Prop_categories.end(), [&](const prop_category& cat) { + return !stricmp(a.category.c_str(), cat.name.c_str()); + }); + int a_index = (a_it != Prop_categories.end()) ? static_cast(std::distance(Prop_categories.begin(), a_it)) : INT_MAX; + + // Get index of b's category in Prop_categories + auto b_it = std::find_if(Prop_categories.begin(), Prop_categories.end(), [&](const prop_category& cat) { + return !stricmp(b.category.c_str(), cat.name.c_str()); + }); + int b_index = (b_it != Prop_categories.end()) ? static_cast(std::distance(Prop_categories.begin(), b_it)) : INT_MAX; + + // Sort by category index + return a_index < b_index; + }); +} + void prop_init() { @@ -239,6 +354,8 @@ void prop_init() // parse any modular tables parse_modular_table("*-prp.tbm", parse_prop_table); + post_process_props(); + Props_inited = true; } @@ -269,7 +386,7 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) // regular name, regular suffix char base_name[NAME_LENGTH]; char suffix[NAME_LENGTH]; - strcpy_s(base_name, Prop_info[prop_type].name); + strcpy_s(base_name, Prop_info[prop_type].name.c_str()); sprintf(suffix, NOX(" %d"), static_cast(Props.size())); // start building name @@ -289,10 +406,10 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) } if (!VALID_FNAME(pip->pof_file)) { - Error(LOCATION, "Cannot create prop %s; pof file is not valid", pip->name); + Error(LOCATION, "Cannot create prop %s; pof file is not valid", pip->name.c_str()); return -1; } - pip->model_num = model_load(pip->pof_file); + pip->model_num = model_load(pip->pof_file.c_str()); polymodel* pm = model_get(pip->model_num); @@ -301,13 +418,13 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) // just log to file for standalone servers Warning(LOCATION, "For prop '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", - pip->name, + pip->name.c_str(), pip->num_detail_levels, pm->n_detail_levels); } else { nprintf(("Warning", "For prop '%s', detail level mismatch. Table has %d, POF has %d.", - pip->name, + pip->name.c_str(), pip->num_detail_levels, pm->n_detail_levels)); } @@ -430,7 +547,7 @@ static void prop_model_change(int n, int prop_type) // get new model if (sip->model_num == -1) { - sip->model_num = model_load(sip->pof_file); + sip->model_num = model_load(sip->pof_file.c_str()); } polymodel* pm = model_get(sip->model_num); @@ -472,11 +589,11 @@ static void prop_model_change(int n, int prop_type) if ( !Is_standalone ) { // just log to file for standalone servers - Warning(LOCATION, "For prop '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels ); + Warning(LOCATION, "For prop '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", sip->name.c_str(), sip->num_detail_levels, pm->n_detail_levels ); } else { - nprintf(("Warning", "For prop '%s', detail level mismatch. Table has %d, POF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels )); + nprintf(("Warning", "For prop '%s', detail level mismatch. Table has %d, POF has %d.", sip->name.c_str(), sip->num_detail_levels, pm->n_detail_levels )); } } for (int i=0; in_detail_levels; i++ ) diff --git a/code/prop/prop.h b/code/prop/prop.h index 5b512502bf0..4b3048ba58c 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -11,8 +11,9 @@ #define MAX_PROP_DETAIL_LEVELS MAX_SHIP_DETAIL_LEVELS typedef struct prop_info { - char name[NAME_LENGTH]; // Prop name - char pof_file[MAX_FILENAME_LEN]; // Pof filename + SCP_string name; // Prop name + SCP_string category; // Category name + SCP_string pof_file; // Pof filename vec3d closeup_pos; // position for camera when using ship in closeup view (eg briefing and techroom) float closeup_zoom; // zoom when using ship in closeup view (eg briefing and techroom) int model_num; // The model number of the loaded POF @@ -37,6 +38,11 @@ typedef struct prop { flagset flags; } prop; +typedef struct prop_category { + SCP_string name; + color color; +} prop_category; + typedef struct parsed_prop { char name[NAME_LENGTH]; int prop_info_index; @@ -50,6 +56,9 @@ extern bool Props_inited; // Global prop info extern SCP_vector Prop_info; +// Global prop categories +extern SCP_vector Prop_categories; + // Global prop objects. Vector of optionals so that we can have stable indices // and still be able to remove props. Deleted props are set to std::nullopt // so any access should check if the optional has a value first. @@ -77,4 +86,6 @@ prop* prop_id_lookup(int id); void change_prop_type(int n, int prop_type); +prop_category* prop_get_category(const SCP_string& name); + int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, collision_info_struct* prop_hit_info); \ No newline at end of file diff --git a/code/scripting/api/objs/propclass.cpp b/code/scripting/api/objs/propclass.cpp index 46dc5ec09f0..359b1401361 100644 --- a/code/scripting/api/objs/propclass.cpp +++ b/code/scripting/api/objs/propclass.cpp @@ -311,7 +311,7 @@ ADE_FUNC(isModelLoaded, return ADE_RETURN_FALSE; if (load_check) { - pip->model_num = model_load(pip->pof_file); + pip->model_num = model_load(pip->pof_file.c_str()); } if (pip->model_num > -1) diff --git a/fred2/mainfrm.cpp b/fred2/mainfrm.cpp index 0f46ce0ae43..1cfe58225a9 100644 --- a/fred2/mainfrm.cpp +++ b/fred2/mainfrm.cpp @@ -133,7 +133,7 @@ void CMainFrame::init_tools() if (it->flags[Prop::Info_Flags::No_fred]) { continue; } else { - m_new_prop_type_combo_box.AddString(it->name); + m_new_prop_type_combo_box.AddString(it->name.c_str()); m_new_prop_type_combo_box.SetItemData((int)prop_type_combo_box_size, std::distance(Prop_info.begin(), it)); prop_type_combo_box_size++; } @@ -470,8 +470,15 @@ void color_combo_box_prop::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) BOOL fDisabled = !IsWindowEnabled(); COLORREF newTextColor = RGB(0x80, 0x80, 0x80); // light gray - if (!fDisabled && pip == nullptr) { - newTextColor = RGB(0, 0, 0); + if (!fDisabled) { + if (pip == nullptr) + newTextColor = RGB(0, 0, 0); + else { + auto cinfo = prop_get_category(pip->category); + if (cinfo != nullptr) { + newTextColor = RGB(cinfo->color.red, cinfo->color.green, cinfo->color.blue); + } + } } COLORREF oldTextColor = pDC->SetTextColor(newTextColor); @@ -492,7 +499,7 @@ void color_combo_box_prop::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) } if (pip != nullptr) { - strText = _T(pip->name); + strText = _T(pip->name.c_str()); } else { if (prop_type_combo_box_size == 0) { strText = _T("No props available"); diff --git a/fred2/sexp_tree.cpp b/fred2/sexp_tree.cpp index 96276930e09..c59d6096d21 100644 --- a/fred2/sexp_tree.cpp +++ b/fred2/sexp_tree.cpp @@ -7275,7 +7275,7 @@ sexp_list_item* sexp_tree::get_listing_opf_prop_class_name() sexp_list_item head; for (auto& pi : Prop_info) - head.add_data(pi.name); + head.add_data(pi.name.c_str()); return head.next; } diff --git a/qtfred/src/ui/widgets/sexp_tree.cpp b/qtfred/src/ui/widgets/sexp_tree.cpp index e3b02f83df9..51824e5a5e0 100644 --- a/qtfred/src/ui/widgets/sexp_tree.cpp +++ b/qtfred/src/ui/widgets/sexp_tree.cpp @@ -5148,7 +5148,7 @@ sexp_list_item* sexp_tree::get_listing_opf_prop_class_name() sexp_list_item head; for (auto& pi : Prop_info) { - head.add_data(pi.name); + head.add_data(pi.name.c_str()); } return head.next; From 282bf5589347a81c4c5db2220d99b2660221055a Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 16 Jun 2025 15:33:43 -0500 Subject: [PATCH 12/25] cleanup --- code/prop/prop.cpp | 43 +++++++++++---------------- code/prop/prop.h | 6 ++-- code/scripting/api/objs/prop.cpp | 35 +--------------------- code/scripting/api/objs/propclass.cpp | 4 +-- 4 files changed, 23 insertions(+), 65 deletions(-) diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index e2e04a44d2a..98519aec811 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -178,7 +178,7 @@ void parse_prop_table(const char* filename) } // an entry does not exist else { - // Don't create ship if it has +nocreate and is in a modular table. + // Don't create prop if it has +nocreate and is in a modular table. if (!create_if_not_found && Parsing_modular_table) { if (!skip_to_start_of_string_either("$Name:", "#End")) { error_display(1, "Missing [#End] or [$Name] after prop class %s", fname); @@ -212,7 +212,7 @@ void parse_prop_table(const char* filename) if (valid) { pip->pof_file = temp; } else { - WarningEx(LOCATION, "Ship %s\nPOF file \"%s\" invalid!", pip->name.c_str(), temp); + WarningEx(LOCATION, "Prop %s\nPOF file \"%s\" invalid!", pip->name.c_str(), temp); } } @@ -360,7 +360,7 @@ void prop_init() } /** - * Returns object index of ship. + * Returns object index of prop. * @return -1 means failed. */ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) @@ -432,14 +432,11 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) for (int i = 0; i < pm->n_detail_levels; i++) pm->detail_depth[i] = (i < pip->num_detail_levels) ? i2fl(pip->detail_distance[i]) : 0.0f; - flagset default_ship_object_flags; - default_ship_object_flags.set(Object::Object_Flags::Renders); - default_ship_object_flags.set(Object::Object_Flags::Physics); - default_ship_object_flags.set(Object::Object_Flags::Immobile); - // JAS: Nav buoys don't need to do collisions! - // G5K: Corrected to apply specifically for ships with the no-collide flag. (In retail, navbuoys already have this - // flag, so this doesn't break anything.) - default_ship_object_flags.set(Object::Object_Flags::Collides, !pip->flags[Prop::Info_Flags::No_collide]); + flagset default_prop_object_flags; + default_prop_object_flags.set(Object::Object_Flags::Renders); + default_prop_object_flags.set(Object::Object_Flags::Physics); + default_prop_object_flags.set(Object::Object_Flags::Immobile); + default_prop_object_flags.set(Object::Object_Flags::Collides, !pip->flags[Prop::Info_Flags::No_collide]); int objnum = obj_create(OBJ_PROP, -1, @@ -447,7 +444,7 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) orient, pos, model_get_radius(pip->model_num), - default_ship_object_flags); + default_prop_object_flags); Assert(objnum >= 0); propp->objnum = objnum; @@ -493,16 +490,14 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) //animation::anim_set_initial_states(propp); - // Start up stracking for this ship in multi. + // Start up stracking for this prop in multi. //if (Game_mode & (GM_MULTIPLAYER)) { //multi_rollback_ship_record_add_ship(objnum); //} - // Set time when ship is created + // Set time when prop is created propp->create_time = timer_get_milliseconds(); - //ship_make_create_time_unique(shipp); - propp->time_created = Missiontime; return objnum; @@ -622,26 +617,25 @@ void change_prop_type(int n, int prop_type) { prop* sp = prop_id_lookup(n); - // do a quick out if we're already using the new ship class + // do a quick out if we're already using the new prop class if (sp->prop_info_index == prop_type) return; int objnum = sp->objnum; - //auto prop_entry = ship_registry_get(sp->prop_name); - prop_info* sip = &(Prop_info[prop_type]); - prop_info* sip_orig = &Prop_info[sp->prop_info_index]; + prop_info* pip = &(Prop_info[prop_type]); + prop_info* pip_orig = &Prop_info[sp->prop_info_index]; object* objp = &Objects[objnum]; - // point to new ship data + // point to new prop data prop_model_change(n, prop_type); sp->prop_info_index = prop_type; // check class-specific flags - if (sip->flags[Prop::Info_Flags::No_collide]) // changing TO a no-collision ship class + if (pip->flags[Prop::Info_Flags::No_collide]) // changing TO a no-collision prop class obj_set_flags(objp, objp->flags - Object::Object_Flags::Collides); - else if (sip_orig->flags[Prop::Info_Flags::No_collide]) // changing FROM a no-collision ship class + else if (pip_orig->flags[Prop::Info_Flags::No_collide]) // changing FROM a no-collision prop class obj_set_flags(objp, objp->flags + Object::Object_Flags::Collides); } @@ -669,9 +663,6 @@ void prop_render(object* obj, model_draw_list* scene) render_info.set_alpha_mult(propp->alpha_mult); } - // Valathil - maybe do a scripting hook here to do some scriptable effects? - //ship_render_set_animated_effect(&render_info, shipp, &render_flags); - if (pip->flags[Prop::Info_Flags::No_lighting]) { render_flags |= MR_NO_LIGHTING; } diff --git a/code/prop/prop.h b/code/prop/prop.h index 4b3048ba58c..f66d76b2d81 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -14,8 +14,8 @@ typedef struct prop_info { SCP_string name; // Prop name SCP_string category; // Category name SCP_string pof_file; // Pof filename - vec3d closeup_pos; // position for camera when using ship in closeup view (eg briefing and techroom) - float closeup_zoom; // zoom when using ship in closeup view (eg briefing and techroom) + vec3d closeup_pos; // position for camera when using prop in closeup view (eg briefing and techroom) + float closeup_zoom; // zoom when using prop in closeup view (eg briefing and techroom) int model_num; // The model number of the loaded POF int num_detail_levels; // Detail levels of the model int detail_distance[MAX_PROP_DETAIL_LEVELS]; // distance to change detail levels at @@ -74,7 +74,7 @@ inline int prop_info_size() void prop_init(); // Object management -int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* ship_name = nullptr); +int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name = nullptr); void prop_delete(object* obj); void prop_render(object* obj, model_draw_list* scene); diff --git a/code/scripting/api/objs/prop.cpp b/code/scripting/api/objs/prop.cpp index 53903cce2c5..65ac5e1e98c 100644 --- a/code/scripting/api/objs/prop.cpp +++ b/code/scripting/api/objs/prop.cpp @@ -3,44 +3,11 @@ #include "globalincs/utility.h" -#include "animation_handle.h" -#include "cockpit_display.h" -#include "enums.h" -#include "message.h" #include "modelinstance.h" #include "object.h" -#include "order.h" -#include "parse_object.h" -#include "ship.h" -#include "ship_bank.h" -#include "shipclass.h" -#include "subsystem.h" -#include "team.h" -#include "texture.h" -#include "vecmath.h" -#include "waypoint.h" -#include "weaponclass.h" -#include "wing.h" - #include "prop.h" #include "propclass.h" -#include "ai/aigoals.h" -#include "hud/hudets.h" -#include "hud/hudshield.h" -#include "mission/missionlog.h" -#include "mission/missionmessage.h" -#include "model/model.h" -#include "network/multiutil.h" -#include "object/object.h" -#include "object/objectdock.h" -#include "parse/parselo.h" -#include "playerman/player.h" -#include "scripting/api/objs/message.h" -#include "ship/ship.h" -#include "ship/shipfx.h" -#include "ship/shiphit.h" - #include "prop/prop.h" namespace scripting { @@ -92,7 +59,7 @@ ADE_VIRTVAR(Class, prop* propp = prop_id_lookup(objh->objp()->instance); - if (ADE_SETTING_VAR && idx > -1) { + if (ADE_SETTING_VAR && SCP_vector_inbounds(Prop_info, idx)) { change_prop_type(objh->objp()->instance, idx); } diff --git a/code/scripting/api/objs/propclass.cpp b/code/scripting/api/objs/propclass.cpp index 359b1401361..563cf732d2a 100644 --- a/code/scripting/api/objs/propclass.cpp +++ b/code/scripting/api/objs/propclass.cpp @@ -242,7 +242,7 @@ ADE_FUNC(renderTechModel, &lighting)) return ade_set_error(L, "b", false); - if (idx < 0 || idx >= ship_info_size()) + if (idx < 0 || idx >= prop_info_size()) return ade_set_args(L, "b", false); if (x2 < x1 || y2 < y1) @@ -280,7 +280,7 @@ ADE_FUNC(renderTechModel2, if (!ade_get_args(L, "oiiiio|f", l_Propclass.Get(&idx), &x1, &y1, &x2, &y2, l_Matrix.GetPtr(&mh), &zoom)) return ade_set_error(L, "b", false); - if (idx < 0 || idx >= ship_info_size()) + if (idx < 0 || idx >= prop_info_size()) return ade_set_args(L, "b", false); if (x2 < x1 || y2 < y1) From ca56b860385b637f42b7608cdd1b917f59d9429a Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 16 Jun 2025 16:16:21 -0500 Subject: [PATCH 13/25] implement collision hooks --- code/object/collidedebrisship.cpp | 40 ++++++++++------------ code/object/collideshipship.cpp | 29 +++++++--------- code/object/collideshipweapon.cpp | 55 ++++++++++++++----------------- code/scripting/global_hooks.cpp | 21 ++++++++++++ code/scripting/global_hooks.h | 1 + code/weapon/beam.cpp | 55 ++++++++++++++----------------- 6 files changed, 101 insertions(+), 100 deletions(-) diff --git a/code/object/collidedebrisship.cpp b/code/object/collidedebrisship.cpp index b5899c2b54e..2db89a107c8 100644 --- a/code/object/collidedebrisship.cpp +++ b/code/object/collidedebrisship.cpp @@ -458,26 +458,24 @@ int collide_debris_prop(obj_pair* pair) bool has_submodel = (debris_hit_info.heavy_submodel_num >= 0); scripting::api::submodel_h smh(debris_hit_info.heavy_model_num, debris_hit_info.heavy_submodel_num); - // TODO PROP - /* if (scripting::hooks::OnDebrisCollision->isActive()) { - ship_override = scripting::hooks::OnDebrisCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, debris_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + ship_override = scripting::hooks::OnDebrisCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, debris_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), scripting::hook_param("Object", 'o', debris_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Ship", 'o', prop_objp), scripting::hook_param("Debris", 'o', debris_objp), scripting::hook_param("Hitpos", 'o', hitpos))); } - if (scripting::hooks::OnShipCollision->isActive()) { - debris_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, debris_objp} }, + if (scripting::hooks::OnPropCollision->isActive()) { + debris_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, debris_objp} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', debris_objp), - scripting::hook_param("Object", 'o', ship_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Object", 'o', prop_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Debris", 'o', debris_objp), scripting::hook_param("Hitpos", 'o', hitpos), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (debris_hit_info.heavy == ship_objp)))); - }*/ + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (debris_hit_info.heavy == prop_objp)))); + } if (!ship_override && !debris_override) { @@ -591,26 +589,24 @@ int collide_asteroid_prop(obj_pair* pair) bool has_submodel = (asteroid_hit_info.heavy_submodel_num >= 0); scripting::api::submodel_h smh(asteroid_hit_info.heavy_model_num, asteroid_hit_info.heavy_submodel_num); - //Scripting support (WMC) - // TODO PROP - /* + //Scripting support if (scripting::hooks::OnAsteroidCollision->isActive()) { - ship_override = scripting::hooks::OnAsteroidCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, asteroid_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + ship_override = scripting::hooks::OnAsteroidCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, asteroid_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), scripting::hook_param("Object", 'o', asteroid_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Asteroid", 'o', asteroid_objp), scripting::hook_param("Hitpos", 'o', hitpos))); } if (scripting::hooks::OnShipCollision->isActive()) { - asteroid_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, asteroid_objp} }, + asteroid_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, asteroid_objp} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', asteroid_objp), - scripting::hook_param("Object", 'o', ship_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Object", 'o', prop_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Asteroid", 'o', asteroid_objp), scripting::hook_param("Hitpos", 'o', hitpos), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (asteroid_hit_info.heavy == ship_objp)))); - }*/ + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (asteroid_hit_info.heavy == prop_objp)))); + } if (!ship_override && !asteroid_override) { diff --git a/code/object/collideshipship.cpp b/code/object/collideshipship.cpp index 07da5fa2fb8..eb99698e268 100644 --- a/code/object/collideshipship.cpp +++ b/code/object/collideshipship.cpp @@ -1655,30 +1655,25 @@ int collide_prop_ship(obj_pair* pair) bool has_submodel = (prop_ship_hit_info.heavy_submodel_num >= 0); scripting::api::submodel_h smh(prop_ship_hit_info.heavy_model_num, prop_ship_hit_info.heavy_submodel_num); - // TODO PROP - /* if (scripting::hooks::OnShipCollision->isActive()) { - a_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{{A, B}}, - scripting::hook_param_list(scripting::hook_param("Self", 'o', A), - scripting::hook_param("Object", 'o', B), - scripting::hook_param("Ship", 'o', A), - scripting::hook_param("ShipB", 'o', B), + a_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{{prop_objp, ship_objp}}, + scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), + scripting::hook_param("Object", 'o', ship_objp), + scripting::hook_param("Prop", 'o', prop_objp), + scripting::hook_param("Ship", 'o', ship_objp), scripting::hook_param("Hitpos", 'o', world_hit_pos), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)), - scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)))); + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (prop_ship_hit_info.heavy == prop_objp)))); // Yes, this should be reversed. - b_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {A, B} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', B), - scripting::hook_param("Object", 'o', A), - scripting::hook_param("Ship", 'o', B), - scripting::hook_param("ShipB", 'o', A), + b_override = scripting::hooks::OnPropCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, ship_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + scripting::hook_param("Object", 'o', prop_objp), + scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Hitpos", 'o', world_hit_pos), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)), - scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)))); + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (prop_ship_hit_info.heavy == ship_objp)))); } - */ if (!a_override && !b_override) { diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index 7eedeeda536..6df89111d23 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -642,7 +642,6 @@ static int prop_weapon_check_collision(object* prop_objp, object* weapon_objp, f Assert(propp->objnum == OBJ_INDEX(prop_objp)); int valid_hit_occurred = 0; // If this is set, then hitpos is set - polymodel* pm = model_get(prinfo->model_num); // total time is flFrametime + time_limit (time_limit used to predict collisions into the future) vec3d weapon_start_pos = weapon_objp->last_pos; @@ -692,56 +691,52 @@ static int prop_weapon_check_collision(object* prop_objp, object* weapon_objp, f wp->collisionInfo = new mc_info; // The weapon will free this memory later *wp->collisionInfo = mc; - bool ship_override = false, weapon_override = false; + bool prop_override = false, weapon_override = false; // get submodel handle if scripting needs it bool has_submodel = (mc.hit_submodel >= 0); scripting::api::submodel_h smh(mc.model_num, mc.hit_submodel); - // TODO PROP - /* if (scripting::hooks::OnWeaponCollision->isActive()) { - ship_override = scripting::hooks::OnWeaponCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + prop_override = scripting::hooks::OnWeaponCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), scripting::hook_param("Object", 'o', weapon_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Weapon", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc->hit_point_world))); + scripting::hook_param("Hitpos", 'o', mc.hit_point_world))); } - if (scripting::hooks::OnShipCollision->isActive()) { - weapon_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + if (scripting::hooks::OnPropCollision->isActive()) { + weapon_override = scripting::hooks::OnPropCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), - scripting::hook_param("Object", 'o', ship_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Object", 'o', prop_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Weapon", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc->hit_point_world), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); - }*/ + scripting::hook_param("Hitpos", 'o', mc.hit_point_world), + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + } - if (!ship_override && !weapon_override) { + if (!prop_override && !weapon_override) { weapon_hit(weapon_objp, prop_objp, &mc.hit_point_world, MISS_SHIELDS, &mc.hit_normal, &mc.hit_point, mc.hit_submodel); } - // TODO PROP - /* - if (scripting::hooks::OnWeaponCollision->isActive() && !(weapon_override && !ship_override)) { - scripting::hooks::OnWeaponCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + if (scripting::hooks::OnWeaponCollision->isActive() && !(weapon_override && !prop_override)) { + scripting::hooks::OnWeaponCollision->run(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), scripting::hook_param("Object", 'o', weapon_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Weapon", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc->hit_point_world))); + scripting::hook_param("Hitpos", 'o', mc.hit_point_world))); } - if (scripting::hooks::OnShipCollision->isActive() && !ship_override) { - scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + if (scripting::hooks::OnPropCollision->isActive() && !prop_override) { + scripting::hooks::OnPropCollision->run(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), - scripting::hook_param("Object", 'o', ship_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Object", 'o', prop_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Weapon", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc->hit_point_world), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); - }*/ + scripting::hook_param("Hitpos", 'o', mc.hit_point_world), + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + } } return valid_hit_occurred; diff --git a/code/scripting/global_hooks.cpp b/code/scripting/global_hooks.cpp index 231489f5a4f..a45ad3f95c2 100644 --- a/code/scripting/global_hooks.cpp +++ b/code/scripting/global_hooks.cpp @@ -165,6 +165,23 @@ const std::shared_ptr> OnShipCollision = Ov {"Asteroid", "object", "The asteroid object with which the ship collided (only set for asteroid collisions)"}, {"ShipB", "ship", "For ship-on-ship collisions, the same as \"Object\" (only set for ship-on-ship collisions)"}, {"ShipBSubmodel", "submodel", "For ship-on-ship collisions, the submodel of \"ShipB\" involved in the collision, if \"ShipB\" was the heavier object"}, + {"Prop", "prop", "The prop object with which the ship collided (only set for prop collisions)."}, + {"PropSubmodel", "submodel", "The submodel of \"Prop\" involved in the collision."}, + {"Weapon", "weapon", "The weapon object with which the ship collided (only set for weapon collisions)"}, + {"Beam", "weapon", "The beam object with which the ship collided (only set for beam collisions)"}}); + +const std::shared_ptr> OnPropCollision = OverridableHook::Factory("On Prop Collision", + "Invoked when a prop collides with another object. Note: When two props collide this will be called twice, once " + "with each prop as the \"Self\" parameter.", + {{"Self", "object", "The object the prop collided with."}, + {"Object", "prop", + "The prop that collided with \"Self\". Provided for consistency with other collision hooks."}, + {"Prop", "prop", "FFor prop-on-object collisions, the same as \"Object\"."}, + {"Hitpos", "vector", "The world position where the collision was detected"}, + {"PropSubmodel", "submodel", "The submodel of \"Prop\" involved in the collision, if \"Prop\" was the heavier object"}, + {"Debris", "object", "The debris object with which the prop collided (only set for debris collisions)"}, + {"Asteroid", "object", "The asteroid object with which the prop collided (only set for asteroid collisions)"}, + {"Ship", "ship", "The ship object with which the prop collided (only set for ship collisions)"}, {"Weapon", "weapon", "The weapon object with which the ship collided (only set for weapon collisions)"}, {"Beam", "weapon", "The beam object with which the ship collided (only set for beam collisions)"}}); @@ -181,6 +198,7 @@ const std::shared_ptr> OnWeaponCollision = {"Debris", "object", "The debris object with which the weapon collided (only set for debris collisions)"}, {"Asteroid", "object", "The asteroid object with which the weapon collided (only set for asteroid collisions)"}, {"Ship", "ship", "The ship object with which the weapon collided (only set for ship collisions)."}, + {"Prop", "prop", "The prop object with which the weapon collided (only set for prop collisions)."}, {"WeaponB", "weapon", "For weapon on weapon collisions, the \"other\" weapon."}, {"Beam", "weapon", "The beam object with which the weapon collided (only set for beam collisions)"} }); @@ -195,6 +213,7 @@ const std::shared_ptr> OnDebrisCollision = {"Hitpos", "vector", "The world position where the collision was detected"}, {"Asteroid", "object", "The asteroid object with which the debris collided (only set for asteroid collisions)"}, {"Ship", "ship", "The ship object with which the debris collided (only set for ship collisions)."}, + {"Prop", "prop", "The prop object with which the debris collided (only set for prop collisions)."}, {"Weapon", "weapon", "The weapon object with which the debris collided (only set for weapon collisions)"}, {"Beam", "weapon", "The beam object with which the debris collided (only set for beam collisions)"} }); @@ -209,6 +228,7 @@ const std::shared_ptr> OnAsteroidCollision {"Hitpos", "vector", "The world position where the collision was detected"}, {"Debris", "object", "The debris object with which the asteroid collided (only set for debris collisions)"}, {"Ship", "ship", "The ship object with which the asteroid collided (only set for ship collisions)"}, + {"Prop", "prop", "The prop object with which the asteroid collided (only set for prop collisions)."}, {"Weapon", "weapon", "The weapon object with which the asteroid collided (only set for weapon collisions)"}, {"Beam", "weapon", "The beam object with which the asteroid collided (only set for beam collisions)"} }); @@ -223,6 +243,7 @@ const std::shared_ptr> OnBeamCollision = Ov {"Hitpos", "vector", "The world position where the collision was detected"}, {"Debris", "object", "The debris object with which the beam collided (only set for debris collisions)"}, {"Ship", "ship", "The ship object with which the beam collided (only set for ship collisions)"}, + {"Prop", "prop", "The prop object with which the beam collided (only set for prop collisions)."}, {"Asteroid", "object", "The asteroid object with which the beam collided (only set for asteroid collisions)"}, {"Weapon", "weapon", "The weapon object with which the beam collided (only set for weapon collisions)"}}); diff --git a/code/scripting/global_hooks.h b/code/scripting/global_hooks.h index d99e11e2236..d797f54b2dc 100644 --- a/code/scripting/global_hooks.h +++ b/code/scripting/global_hooks.h @@ -40,6 +40,7 @@ extern const std::shared_ptr> OnDebrisCreated; extern const std::shared_ptr> OnAsteroidCreated; extern const std::shared_ptr> OnShipCollision; +extern const std::shared_ptr> OnPropCollision; extern const std::shared_ptr> OnWeaponCollision; extern const std::shared_ptr> OnBeamCollision; extern const std::shared_ptr> OnDebrisCollision; diff --git a/code/weapon/beam.cpp b/code/weapon/beam.cpp index 50166e04f36..670708c6603 100644 --- a/code/weapon/beam.cpp +++ b/code/weapon/beam.cpp @@ -3410,8 +3410,6 @@ int beam_collide_prop(obj_pair* pair) bwi = &Weapon_info[a_beam->weapon_info_index]; - polymodel* pm = model_get(model_num); - // get the width of the beam width = a_beam->beam_collide_width * a_beam->current_width_factor; @@ -3452,61 +3450,56 @@ int beam_collide_prop(obj_pair* pair) if (hit) { - bool ship_override = false, weapon_override = false; + bool prop_override = false, weapon_override = false; // get submodel handle if scripting needs it bool has_submodel = (mc.hit_submodel >= 0); scripting::api::submodel_h smh(mc.model_num, mc.hit_submodel); - // TODO PROP - /* if (scripting::hooks::OnBeamCollision->isActive()) { - ship_override = scripting::hooks::OnBeamCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + prop_override = scripting::hooks::OnBeamCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, + scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), scripting::hook_param("Object", 'o', weapon_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Beam", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc_array[i]->hit_point_world))); + scripting::hook_param("Hitpos", 'o', mc.hit_point_world))); } - if (scripting::hooks::OnShipCollision->isActive()) { - weapon_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + if (scripting::hooks::OnPropCollision->isActive()) { + weapon_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), - scripting::hook_param("Object", 'o', ship_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Object", 'o', prop_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Beam", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc_array[i]->hit_point_world), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); - }*/ + scripting::hook_param("Hitpos", 'o', mc.hit_point_world), + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + } - if (!ship_override && !weapon_override) + if (!prop_override && !weapon_override) { // add to the collision_list // if we got "tooled", add an exit hole too beam_add_collision(a_beam, prop_objp, &mc, MISS_SHIELDS, false); } - //TODO PROP - /* - if (scripting::hooks::OnBeamCollision->isActive() && !(weapon_override && !ship_override)) { - scripting::hooks::OnBeamCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), + if (scripting::hooks::OnBeamCollision->isActive() && !(weapon_override && !prop_override)) { + scripting::hooks::OnBeamCollision->run(scripting::hooks::CollisionConditions{{prop_objp, weapon_objp}}, + scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), scripting::hook_param("Object", 'o', weapon_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Beam", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc_array[i]->hit_point_world))); + scripting::hook_param("Hitpos", 'o', mc.hit_point_world))); } - if (scripting::hooks::OnShipCollision->isActive() && ((weapon_override && !ship_override) || (!weapon_override && !ship_override))) + if (scripting::hooks::OnShipCollision->isActive() && ((weapon_override && !prop_override) || (!weapon_override && !prop_override))) { - scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, weapon_objp} }, + scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{{prop_objp, weapon_objp}}, scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), - scripting::hook_param("Object", 'o', ship_objp), - scripting::hook_param("Ship", 'o', ship_objp), + scripting::hook_param("Object", 'o', prop_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Beam", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc_array[i]->hit_point_world), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + scripting::hook_param("Hitpos", 'o', mc.hit_point_world), + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); } - */ } // reset timestamp to timeout immediately From 78c607c2245000bc20b1b1ac72c4972fb31a7033 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 16 Jun 2025 20:12:10 -0500 Subject: [PATCH 14/25] parse prop flags and further ensure props are inited and closed correctly --- code/lab/manager/lab_manager.cpp | 2 + code/lab/manager/lab_manager.h | 3 ++ code/object/object.cpp | 4 ++ code/prop/prop.cpp | 85 +++++++++++++++++++++++++++++++- code/prop/prop.h | 1 + code/prop/prop_flags.h | 2 +- fred2/management.cpp | 1 + freespace2/freespace.cpp | 2 + qtfred/src/mission/Editor.cpp | 2 + 9 files changed, 99 insertions(+), 3 deletions(-) diff --git a/code/lab/manager/lab_manager.cpp b/code/lab/manager/lab_manager.cpp index 3280a41b492..58347f332e5 100644 --- a/code/lab/manager/lab_manager.cpp +++ b/code/lab/manager/lab_manager.cpp @@ -11,6 +11,7 @@ #include "ship/ship.h" #include "ship/shipfx.h" #include "particle/particle.h" +#include "prop/prop.h" #include "weapon/muzzleflash.h" #include "weapon/beam.h" #include "ai/aigoals.h" @@ -46,6 +47,7 @@ LabManager::LabManager() { debris_init(); extern void debris_page_in(); debris_page_in(); + props_level_init(); asteroid_level_init(); shockwave_level_init(); ship_level_init(); diff --git a/code/lab/manager/lab_manager.h b/code/lab/manager/lab_manager.h index 03ad8eda57b..a72c0cd0108 100644 --- a/code/lab/manager/lab_manager.h +++ b/code/lab/manager/lab_manager.h @@ -72,6 +72,9 @@ class LabManager { // Unload any asteroids that were loaded asteroid_level_close(); + // Unload any props that were loaded + props_level_close(); + // Lab can only be entered from the Mainhall so this should be safe model_free_all(); diff --git a/code/object/object.cpp b/code/object/object.cpp index ef6b3092b8b..ac9215a2b8a 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -672,6 +672,10 @@ void obj_delete_all() obj_delete(i); } + // If we've removed all objects then we can safely + // clear the Props vector TODO maybe remove this? + Props.clear(); + mprintf(("Cleanup: Deleted %i objects\n", counter)); } diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 98519aec811..2eedf6447c5 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -6,6 +6,7 @@ #include "model/model.h" #include "model/modelreplace.h" #include "parse/parselo.h" +#include "prop/prop_flags.h" #include "render/3d.h" #include "ship/shipfx.h" #include "object/objcollide.h" @@ -24,6 +25,15 @@ SCP_vector Prop_categories; static SCP_vector Removed_props; +flag_def_list_new Prop_flags[] = { + { "no_collide", Prop::Info_Flags::No_collide, true, false }, + { "no_fred", Prop::Info_Flags::No_fred, true, false }, + { "no lighting", Prop::Info_Flags::No_lighting, true, false } + //{ "no impact debris", Prop::Info_Flags::No_impact_debris, true, false }, +}; + +const size_t Num_prop_flags = sizeof(Prop_flags) / sizeof(flag_def_list_new); + /** * Return the index of Prop_info[].name that is *token. */ @@ -193,7 +203,7 @@ void parse_prop_table(const char* filename) pip->name = fname; } - if (optional_string("+POF file:")) { + if (optional_string("$POF file:")) { char temp[32]; stuff_string(temp, F_NAME, MAX_FILENAME_LEN); @@ -257,6 +267,44 @@ void parse_prop_table(const char* filename) stuff_string(pip->category, F_NAME); } + if (optional_string("$Flags:")) { + SCP_vector prop_strings; + stuff_string_list(prop_strings); + + for (const auto& flag : prop_strings) { + // get ship type from ship flags + const char* cur_flag = flag.c_str(); + bool flag_found = false; + + // check various ship flags + for (size_t idx = 0; idx < Num_prop_flags; idx++) { + if (!stricmp(Prop_flags[idx].name, cur_flag)) { + flag_found = true; + + if (!Prop_flags[idx].in_use) + Warning(LOCATION, + "Use of '%s' flag for Prop Class '%s' - this flag is no longer needed.", + Prop_flags[idx].name, + pip->name.c_str()); + else + pip->flags.set(Prop_flags[idx].def); + + break; + } + } + + // catch typos or deprecations + if (!stricmp(cur_flag, "no-collide") || !stricmp(cur_flag, "no_collide")) { + flag_found = true; + pip->flags.set(Prop::Info_Flags::No_collide); + } + + if (!flag_found) { + Warning(LOCATION, "Bogus string in ship flags: %s\n", cur_flag); + } + } + } + if (optional_string("$Custom data:")) { parse_string_map(pip->custom_data, "$end_custom_data", "+Val:"); } @@ -723,6 +771,40 @@ void prop_render(object* obj, model_draw_list* scene) model_render_queue(&render_info, scene, pip->model_num, &obj->orient, &obj->pos); } +// Draft. Props vector uses std::optional to allow for empty slots for deleted props so the indices of the remaining +// props do not change. In long FRED sessions without saving and loading, this can lead to a lot of empty slots. However, +// saving and loading the level will naturally compact the props vector by way of clearing and re-adding props. +void compact_props_vector() +{ + SCP_vector> new_props; + SCP_unordered_map index_remap; + + for (size_t i = 0; i < Props.size(); ++i) { + if (Props[i].has_value()) { + index_remap[static_cast(i)] = static_cast(new_props.size()); + new_props.push_back(std::move(Props[i])); + } + } + + Props = std::move(new_props); + + // Remap object instances + for (object* obj = GET_FIRST(&obj_used_list); obj != END_OF_LIST(&obj_used_list); obj = GET_NEXT(obj)) { + if (obj->type == OBJ_PROP && obj->instance >= 0) { + auto it = index_remap.find(obj->instance); + Assertion(it != index_remap.end(), "Object is pointing to invalid prop index %d! Get a coder!", obj->instance); + + if (it != index_remap.end()) { + obj->instance = it->second; + } + } + } +} + +void props_level_init() { + Props.clear(); +} + void props_level_close() { for (auto& opt_prop : Props) { @@ -735,7 +817,6 @@ void props_level_close() Props.clear(); } - // handles prop-ship/debris/asteroid/weapon collisions int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, collision_info_struct* prop_hit_info) { diff --git a/code/prop/prop.h b/code/prop/prop.h index f66d76b2d81..df18a926971 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -78,6 +78,7 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name = nu void prop_delete(object* obj); void prop_render(object* obj, model_draw_list* scene); +void props_level_init(); void props_level_close(); int prop_info_lookup(const char* token); diff --git a/code/prop/prop_flags.h b/code/prop/prop_flags.h index 43ac7a606b4..ba4649e3bd2 100644 --- a/code/prop/prop_flags.h +++ b/code/prop/prop_flags.h @@ -24,8 +24,8 @@ FLAG_LIST(Prop_Flags){ FLAG_LIST(Info_Flags){ No_collide = 0, // No collisions No_fred, // not available in fred - //No_impact_debris, No_lighting, + //No_impact_debris, NUM_VALUES}; diff --git a/fred2/management.cpp b/fred2/management.cpp index 321e709b7f9..40366596bd3 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -878,6 +878,7 @@ void clear_mission(bool fast_reload) model_free_all(); // Free all existing models ai_init(); + props_level_init(); asteroid_level_init(); ship_level_init(); nebula_init(Nebula_index, Nebula_pitch, Nebula_bank, Nebula_heading); diff --git a/freespace2/freespace.cpp b/freespace2/freespace.cpp index 666b25b5ede..5f51116dfbe 100644 --- a/freespace2/freespace.cpp +++ b/freespace2/freespace.cpp @@ -935,6 +935,7 @@ void game_level_close() shockwave_level_close(); fireball_close(); shield_hit_close(); + props_level_close(); asteroid_level_close(); jumpnode_level_close(); waypoint_level_close(); @@ -1068,6 +1069,7 @@ void game_level_init() NavSystem_Init(); // zero out the nav system ai_level_init(); // Call this before ship_init() because it reads ai.tbl. + props_level_init(); multi_init_oo_and_ship_tracker(); // Inits/resets multiplayer ship tracking system. Has to be done before creating any ships. ship_level_init(); player_level_init(); diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index f27b730aa48..4f357ccf3cd 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -483,6 +484,7 @@ void Editor::clearMission(bool fast_reload) { model_free_all(); // Free all existing models ai_init(); + props_level_init(); asteroid_level_init(); ship_level_init(); nebula_init(Nebula_index, Nebula_pitch, Nebula_bank, Nebula_heading); From 1f7d2ad010100345dafdc0ebbccd8be667cad87e Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Tue, 17 Jun 2025 09:01:00 -0500 Subject: [PATCH 15/25] Add Cyborg's multi suggestions --- code/object/collideshipweapon.cpp | 5 ++--- code/ship/shiphit.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index 6df89111d23..da88f3f6e3e 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -865,10 +865,9 @@ int collide_prop_weapon(obj_pair* pair) Assert(weapon_obj->type == OBJ_WEAPON); // Cyborg17 - no ship-ship collisions when doing multiplayer rollback - //Asteroth - should this be kept? - /*if ((Game_mode & GM_MULTIPLAYER) && multi_ship_record_get_rollback_wep_mode() && (weapon_obj->parent_sig == OBJ_INDEX(ship))) { + if ((Game_mode & GM_MULTIPLAYER) && multi_ship_record_get_rollback_wep_mode() && (weapon_obj->parent_sig == OBJ_INDEX(prop))) { return 0; - }*/ + } if (reject_due_collision_groups(prop, weapon_obj)) return 0; diff --git a/code/ship/shiphit.cpp b/code/ship/shiphit.cpp index e74af316329..d4acd9c3a4b 100755 --- a/code/ship/shiphit.cpp +++ b/code/ship/shiphit.cpp @@ -1196,7 +1196,9 @@ static void shiphit_record_player_killer(const object *killer_objp, player *p) break; case OBJ_PROP: - // handle this + strcpy_s(p->killer_parent_name, ""); + p->killer_species = -1; + p->killer_parent_name[0] = '\0'; break; case OBJ_NONE: @@ -1605,6 +1607,7 @@ static void player_died_start(const object *killer_objp) case OBJ_SHIP: case OBJ_DEBRIS: case OBJ_ASTEROID: + case OBJ_PROP: case OBJ_NONE: // Something that just got deleted due to also dying -- it happened to me! --MK. other_objp = killer_objp; break; @@ -1618,9 +1621,6 @@ static void player_died_start(const object *killer_objp) other_objp = &Objects[beam_obj_parent]; } break; - case OBJ_PROP: - //hmmmmmm - break; default: UNREACHABLE("Unhandled object type %d in player_died_start()", killer_objp->type); // Killed by an object of a peculiar type. What is it? From 9a024fab413f810e19f18a50912fee48814079c8 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Tue, 17 Jun 2025 12:40:35 -0500 Subject: [PATCH 16/25] fix missing header --- code/lab/manager/lab_manager.h | 1 + 1 file changed, 1 insertion(+) diff --git a/code/lab/manager/lab_manager.h b/code/lab/manager/lab_manager.h index a72c0cd0108..fdde4b07db3 100644 --- a/code/lab/manager/lab_manager.h +++ b/code/lab/manager/lab_manager.h @@ -11,6 +11,7 @@ #include "asteroid/asteroid.h" #include "ship/ship.h" #include "weapon/weapon.h" +#include "prop/prop.h" enum class LabRotationMode { Both, Yaw, Pitch, Roll }; From bfb8dba0e0fddea938adb0fd2a0a7150eeaa6ac1 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Tue, 17 Jun 2025 14:25:00 -0500 Subject: [PATCH 17/25] add props to empty slots if available --- code/prop/prop.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 2eedf6447c5..f67a87f2e8a 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -52,7 +52,7 @@ int prop_name_lookup(const char *name) { Assertion(name != nullptr, "NULL name passed to prop_name_lookup"); - for (int i=0; i(Props.size()); i++){ + for (size_t i=0; i < Props.size(); i++){ auto prop = Props[i] ? &Props[i].value() : nullptr; if (prop == nullptr) { continue; @@ -60,7 +60,7 @@ int prop_name_lookup(const char *name) if (prop->objnum >= 0){ if (Objects[prop->objnum].type == OBJ_PROP) { if (!stricmp(name, prop->prop_name)) { - return i; + return static_cast(i); } } } @@ -70,6 +70,16 @@ int prop_name_lookup(const char *name) return -1; } +int find_prop_empty_slot() { + for (size_t i = 0; i < Props.size(); i++) { + if (!Props[i].has_value()) { + return static_cast(i); + } + } + + return -1; +} + prop* prop_id_lookup(int id) { if (id < 0 || id >= static_cast(Props.size()) || !Props[id].has_value()) { @@ -423,8 +433,15 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) pip = &(Prop_info[prop_type]); - Props.emplace_back(prop()); - int new_id = static_cast(Props.size()) - 1; + int new_id = find_prop_empty_slot(); + + if (new_id < 0) { + Props.emplace_back(prop()); + new_id = static_cast(Props.size()) - 1; + } else { + Props[new_id] = prop(); + } + propp = prop_id_lookup(new_id); Assertion(propp != nullptr, "Could not create prop!"); From 4aa9ed3eedb5a396d76d794c0afeebb2debb892d Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 18 Jun 2025 10:15:41 -0500 Subject: [PATCH 18/25] add multi signature --- code/prop/prop.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index f67a87f2e8a..2bd8d18a546 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -5,6 +5,7 @@ #include "freespace.h" #include "model/model.h" #include "model/modelreplace.h" +#include "network/multiutil.h" #include "parse/parselo.h" #include "prop/prop_flags.h" #include "render/3d.h" @@ -523,6 +524,11 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) objp->phys_info.forward_accel_time_const = 1.0; objp->phys_info.flags = PF_CONST_VEL; + // For now use the asteroid signature for props as requested by Cyborg. + if (Game_mode & GM_MULTIPLAYER) { + objp->net_signature = multi_assign_network_signature(MULTI_SIG_ASTEROID); + } + // this is a little 'dangerous', values like this can cause the physics to do wierd things // but they are the most 'honest', and will help to indicate when the physics is trying to use these values in ways they shouldn't objp->phys_info.mass = INFINITY; From 3053e66db1b36f43a72354641602e03a74e68b42 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 14 Jul 2025 12:54:13 -0500 Subject: [PATCH 19/25] fix rebase issues and match collisions to the new multithread style --- code/object/collideshipweapon.cpp | 137 +++++++++++++++++++++++++----- fred2/missionsave.h | 1 - 2 files changed, 117 insertions(+), 21 deletions(-) diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index da88f3f6e3e..19b6ef5ce3b 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -36,7 +36,6 @@ using ship_weapon_collision_data = std::tuple, int, bool, extern int Game_skill_level; extern float ai_endangered_time(const object *ship_objp, const object *weapon_objp); -static int check_inside_radius_for_big_objects( object *ship, object *weapon_obj, obj_pair *pair ); static std::tuple check_inside_radius_for_big_ships( object *ship, object *weapon_obj, obj_pair *pair ); extern float flFrametime; @@ -618,9 +617,7 @@ static void ship_weapon_process_collision(obj_pair* pair, const std::any& collis ship_weapon_process_collision(pair, std::any_cast(collision_data)); } - - -static int prop_weapon_check_collision(object* prop_objp, object* weapon_objp, float time_limit = 0.0f, int* next_hit = nullptr) +static std::tuple prop_weapon_check_collision(object* prop_objp, object* weapon_objp, float time_limit = 0.0f, int* next_hit = nullptr) { weapon* wp; weapon_info* wip; @@ -683,12 +680,24 @@ static int prop_weapon_check_collision(object* prop_objp, object* weapon_objp, f *next_hit = (int)(1000.0f * (mc.hit_dist * (flFrametime + time_limit) - flFrametime)); if (*next_hit > 0) // if hit occurs outside of this frame, do not do damage - return 1; + valid_hit_occurred = 1; + bool postproc = true; + bool recheck = false; + // most of this data is irrevelant for props but let's match it to make it easy + ship_weapon_collision_data cd{ + mc, + -1, + postproc, + -1, + -1, + ZERO_VECTOR + }; + return {postproc, recheck, cd}; } if (hit) { - wp->collisionInfo = new mc_info; // The weapon will free this memory later + /*wp->collisionInfo = new mc_info; // The weapon will free this memory later *wp->collisionInfo = mc; bool prop_override = false, weapon_override = false; @@ -717,7 +726,7 @@ static int prop_weapon_check_collision(object* prop_objp, object* weapon_objp, f } if (!prop_override && !weapon_override) { - weapon_hit(weapon_objp, prop_objp, &mc.hit_point_world, MISS_SHIELDS, &mc.hit_normal, &mc.hit_point, mc.hit_submodel); + weapon_hit(weapon_objp, prop_objp, &mc.hit_point_world, MISS_SHIELDS); //, &mc.hit_normal, &mc.hit_point, mc.hit_submodel); This was changed by PR 6785 and the changes were not documented } if (scripting::hooks::OnWeaponCollision->isActive() && !(weapon_override && !prop_override)) { @@ -736,10 +745,87 @@ static int prop_weapon_check_collision(object* prop_objp, object* weapon_objp, f scripting::hook_param("Weapon", 'o', weapon_objp), scripting::hook_param("Hitpos", 'o', mc.hit_point_world), scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); - } + }*/ + valid_hit_occurred = 1; } - return valid_hit_occurred; + bool postproc = (valid_hit_occurred != 0); + bool recheck = (valid_hit_occurred == 0); + // most of this data is irrevelant for props but let's match it to make it easy + ship_weapon_collision_data cd{ + (valid_hit_occurred ? mc : mc_info{}), + -1, + postproc, + -1, + -1, + ZERO_VECTOR + }; + + return{postproc, recheck, cd}; +} + +static void prop_weapon_process_collision(obj_pair* pair, const ship_weapon_collision_data& cd) +{ + object* prop_objp = pair->a; + object* weapon_objp = pair->b; + + auto mc_opt = std::get<0>(cd); + if (!mc_opt) { + return; + } + const mc_info& mc = *mc_opt; + + weapon* wp = &Weapons[weapon_objp->instance]; + wp->collisionInfo = new mc_info(mc); + + bool prop_override = false; + bool weapon_override = false; + + bool has_submodel = (mc.hit_submodel >= 0); + scripting::api::submodel_h smh(mc.model_num, mc.hit_submodel); + + if (scripting::hooks::OnWeaponCollision->isActive()) { + prop_override = scripting::hooks::OnWeaponCollision->isOverride( + scripting::hooks::CollisionConditions{{prop_objp, weapon_objp}}, + scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), + scripting::hook_param("Object", 'o', weapon_objp), + scripting::hook_param("Prop", 'o', prop_objp), + scripting::hook_param("Weapon", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc.hit_point_world))); + } + + if (scripting::hooks::OnPropCollision->isActive()) { + weapon_override = scripting::hooks::OnPropCollision->isOverride( + scripting::hooks::CollisionConditions{{prop_objp, weapon_objp}}, + scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), + scripting::hook_param("Object", 'o', prop_objp), + scripting::hook_param("Prop", 'o', prop_objp), + scripting::hook_param("Weapon", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc.hit_point_world), + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + } + + if (!prop_override && !weapon_override) { + weapon_hit(weapon_objp, prop_objp, &mc.hit_point_world, MISS_SHIELDS); + } + + if (scripting::hooks::OnWeaponCollision->isActive() && !(weapon_override && !prop_override)) { + scripting::hooks::OnWeaponCollision->run(scripting::hooks::CollisionConditions{{prop_objp, weapon_objp}}, + scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), + scripting::hook_param("Object", 'o', weapon_objp), + scripting::hook_param("Prop", 'o', prop_objp), + scripting::hook_param("Weapon", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc.hit_point_world))); + } + if (scripting::hooks::OnPropCollision->isActive() && !prop_override) { + scripting::hooks::OnPropCollision->run(scripting::hooks::CollisionConditions{{prop_objp, weapon_objp}}, + scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), + scripting::hook_param("Object", 'o', prop_objp), + scripting::hook_param("Prop", 'o', prop_objp), + scripting::hook_param("Weapon", 'o', weapon_objp), + scripting::hook_param("Hitpos", 'o', mc.hit_point_world), + scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); + } } @@ -857,7 +943,6 @@ collision_result collide_ship_weapon_check( obj_pair * pair ) */ int collide_prop_weapon(obj_pair* pair) { - int did_hit; object* prop = pair->a; object* weapon_obj = pair->b; @@ -883,13 +968,23 @@ int collide_prop_weapon(obj_pair* pair) // Note: culling ships with auto spread shields seems to waste more performance than it saves, // so we're not doing that here if (vm_vec_dist_squared(&prop->pos, &weapon_obj->pos) < (1.2f * prop->radius * prop->radius)) { - return check_inside_radius_for_big_objects(prop, weapon_obj, pair); + auto [do_postproc, never_hits, collision_data] = + check_inside_radius_for_big_ships(prop, weapon_obj, pair); + + if (do_postproc) { + prop_weapon_process_collision(pair, collision_data); + } + return never_hits; } } - did_hit = prop_weapon_check_collision(prop, weapon_obj); + auto [do_postproc, does_not_hit, collision_data] = prop_weapon_check_collision(prop, weapon_obj); + + if (do_postproc) { + prop_weapon_process_collision(pair, collision_data); + } - if (!did_hit) { + if (does_not_hit) { // Since we didn't hit, check to see if we can disable all future collisions // between these two. return weapon_will_never_hit(weapon_obj, prop, pair); @@ -929,7 +1024,7 @@ static float estimate_ship_speed_upper_limit( object *ship, float time ) * @return 1 if pair can be culled * @return 0 if pair can not be culled */ -static std::tuple check_inside_radius_for_big_ships( object *ship, object *weapon_obj, obj_pair *pair ) +static std::tuple check_inside_radius_for_big_ships( object *big_obj, object *weapon_obj, obj_pair *pair ) { vec3d error_vel; // vel perpendicular to laser float error_vel_mag; // magnitude of error_vel @@ -967,13 +1062,15 @@ static std::tuple check_inside_radius_fo // Note: when estimated hit time is less than 200 ms, look at every frame int hit_time; // estimated time of hit in ms // modify the collision check to do damage if hit_time is negative (ie, hit occurs in this frame) - bool hit = false; - if (big_obj->type == OBJ_SHIP) - hit = ship_weapon_check_collision(big_obj, weapon_obj, limit_time, &hit_time); - else // OBJ_PROP - hit = prop_weapon_check_collision(big_obj, weapon_obj, limit_time, &hit_time); + bool do_postproc, does_not_hit; + ship_weapon_collision_data collision_data; + + if (big_obj->type == OBJ_PROP) { + std::tie(do_postproc, does_not_hit, collision_data) = prop_weapon_check_collision(big_obj, weapon_obj, limit_time, &hit_time); + } else { + std::tie(do_postproc, does_not_hit, collision_data) = ship_weapon_check_collision(big_obj, weapon_obj, limit_time, &hit_time); + } - const auto& [do_postproc, does_not_hit, collision_data] = ship_weapon_check_collision( ship, weapon_obj, limit_time, &hit_time ); // modify ship_weapon_check_collision to do damage if hit_time is negative (ie, hit occurs in this frame) if ( !does_not_hit ) { // hit occured in while in sphere diff --git a/fred2/missionsave.h b/fred2/missionsave.h index f2656282a68..3a34e98b11a 100644 --- a/fred2/missionsave.h +++ b/fred2/missionsave.h @@ -515,7 +515,6 @@ class CFred_mission_save */ int save_props(); - char *raw_ptr; /** * @brief Utility function to save a raw comment, the start of which precedes the current raw_ptr, to a file while handling newlines properly */ From 2d0b820bc54785e8572ba5cf2e2353e6811cee8d Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 14 Jul 2025 13:26:13 -0500 Subject: [PATCH 20/25] appease Clang the Conquerer --- code/lab/dialogs/lab_ui.cpp | 10 +-- code/lab/dialogs/lab_ui.h | 4 +- code/lab/manager/lab_manager.cpp | 1 - code/lab/manager/lab_manager.h | 6 +- code/mission/missionparse.cpp | 27 ++++---- code/object/collidedebrisship.cpp | 50 +------------- code/object/collideshipship.cpp | 35 ---------- code/object/collideshipweapon.cpp | 97 +++++---------------------- code/parse/parselo.cpp | 2 +- code/parse/sexp.cpp | 2 +- code/prop/prop.cpp | 51 +++++++------- code/prop/prop.h | 2 +- code/scripting/api/libs/mission.cpp | 6 +- code/scripting/api/libs/tables.cpp | 4 +- code/scripting/api/objs/prop.cpp | 6 +- code/scripting/api/objs/prop.h | 7 +- code/scripting/api/objs/propclass.cpp | 14 ++-- code/scripting/api/objs/propclass.h | 6 +- code/ship/shipfx.cpp | 10 +-- code/weapon/beam.cpp | 7 +- fred2/mainfrm.cpp | 2 +- fred2/missionsave.cpp | 3 +- fred2/sexp_tree.cpp | 4 +- fred2/sexp_tree.h | 2 +- qtfred/src/mission/missionsave.cpp | 3 +- qtfred/src/ui/widgets/sexp_tree.cpp | 4 +- qtfred/src/ui/widgets/sexp_tree.h | 4 +- 27 files changed, 105 insertions(+), 264 deletions(-) diff --git a/code/lab/dialogs/lab_ui.cpp b/code/lab/dialogs/lab_ui.cpp index 49028b924a6..cc10928c0a2 100644 --- a/code/lab/dialogs/lab_ui.cpp +++ b/code/lab/dialogs/lab_ui.cpp @@ -93,15 +93,15 @@ void LabUi::build_weapon_subtype_list() const } } -void LabUi::build_prop_subtype_list() const +void LabUi::build_prop_subtype_list() { - for (size_t i = 0; i < Prop_categories.size(); i++) { - with_TreeNode(Prop_categories[i].name.c_str()) + for (auto& propc : Prop_categories) { + with_TreeNode(propc.name.c_str()) { int prop_idx = 0; for (auto const& class_def : Prop_info) { - if (lcase_equal(class_def.category, Prop_categories[i].name)) { + if (lcase_equal(class_def.category, propc.name)) { SCP_string node_label; sprintf(node_label, "##PropClassIndex%i", prop_idx); TreeNodeEx(node_label.c_str(), @@ -198,7 +198,7 @@ void LabUi::build_object_list() } } -void LabUi::build_prop_list() const +void LabUi::build_prop_list() { with_TreeNode("Prop Classes") { diff --git a/code/lab/dialogs/lab_ui.h b/code/lab/dialogs/lab_ui.h index 9610cf76d28..cb3d7237f40 100644 --- a/code/lab/dialogs/lab_ui.h +++ b/code/lab/dialogs/lab_ui.h @@ -32,8 +32,8 @@ class LabUi { static void build_object_list(); static void build_asteroid_list(); static void build_debris_list(); - void build_prop_list() const; - void build_prop_subtype_list() const; + static void build_prop_list(); + static void build_prop_subtype_list(); void build_background_list() const; void show_render_options(); void show_object_options() const; diff --git a/code/lab/manager/lab_manager.cpp b/code/lab/manager/lab_manager.cpp index 58347f332e5..5feb1e31579 100644 --- a/code/lab/manager/lab_manager.cpp +++ b/code/lab/manager/lab_manager.cpp @@ -15,7 +15,6 @@ #include "weapon/muzzleflash.h" #include "weapon/beam.h" #include "ai/aigoals.h" -#include "prop/prop.h" #include "freespace.h" diff --git a/code/lab/manager/lab_manager.h b/code/lab/manager/lab_manager.h index fdde4b07db3..2ff8758d054 100644 --- a/code/lab/manager/lab_manager.h +++ b/code/lab/manager/lab_manager.h @@ -107,15 +107,15 @@ class LabManager { int Saved_cmdline_collisions_value; - bool isSafeForShips() { + bool isSafeForShips() const { return CurrentMode == LabMode::Ship && CurrentObject != -1 && Objects[CurrentObject].type == OBJ_SHIP; } - bool isSafeForProps() { + bool isSafeForProps() const { return CurrentMode == LabMode::Prop && CurrentObject != -1 && Objects[CurrentObject].type == OBJ_PROP; } - bool isSafeForWeapons() { + bool isSafeForWeapons() const { bool valid = CurrentObject != -1 && (Objects[CurrentObject].type == OBJ_WEAPON || Objects[CurrentObject].type == OBJ_BEAM); return CurrentMode == LabMode::Weapon && valid; } diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 8748d732f8e..2ee77ce389b 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -5120,34 +5120,34 @@ void parse_wing(mission *pm) // Goober5000 - wing creation stuff moved to post_process_ships_wings } -void parse_prop(mission* pm) +void parse_prop(mission* /*pm*/) { - parsed_prop prop; + parsed_prop p; required_string("$Name:"); - stuff_string(prop.name, F_NAME, NAME_LENGTH); + stuff_string(p.name, F_NAME, NAME_LENGTH); // Maybe do this by name instead? required_string("$Class:"); - stuff_int(&prop.prop_info_index); + stuff_int(&p.prop_info_index); required_string("$Location:"); - stuff_vec3d(&prop.position); + stuff_vec3d(&p.position); required_string("$Orientation:"); - stuff_matrix(&prop.orientation); + stuff_matrix(&p.orientation); // set flags if (optional_string("+Flags:")) { SCP_vector unparsed; - parse_string_flag_list(prop.flags, Parse_prop_flags, Num_parse_prop_flags, &unparsed); + parse_string_flag_list(p.flags, Parse_prop_flags, Num_parse_prop_flags, &unparsed); if (!unparsed.empty()) { - for (size_t k = 0; k < unparsed.size(); ++k) { - WarningEx(LOCATION, "Unknown flag in parse prop flags: %s", unparsed[k].c_str()); + for (const auto& f : unparsed) { + WarningEx(LOCATION, "Unknown flag in parse prop flags: %s", f.c_str()); } } } - Parse_props.emplace_back(prop); + Parse_props.emplace_back(p); } void parse_wings(mission* pm) @@ -5255,14 +5255,13 @@ void post_process_path_stuff() // MjnMixael void post_process_mission_props() { - for (int i = 0; i < static_cast(Parse_props.size()); i++) { - parsed_prop* propp = &Parse_props[i]; - int objnum = prop_create(&propp->orientation, &propp->position, propp->prop_info_index, propp->name); + for (auto& propp : Parse_props) { + int objnum = prop_create(&propp.orientation, &propp.position, propp.prop_info_index, propp.name); if (objnum >= 0) { auto& obj = Objects[objnum]; - if (propp->flags[Mission::Parse_Object_Flags::OF_No_collide]) { + if (propp.flags[Mission::Parse_Object_Flags::OF_No_collide]) { obj.flags.remove(Object::Object_Flags::Collides); } } diff --git a/code/object/collidedebrisship.cpp b/code/object/collidedebrisship.cpp index 2db89a107c8..983d8be4a27 100644 --- a/code/object/collidedebrisship.cpp +++ b/code/object/collidedebrisship.cpp @@ -462,7 +462,7 @@ int collide_debris_prop(obj_pair* pair) ship_override = scripting::hooks::OnDebrisCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, debris_objp} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), scripting::hook_param("Object", 'o', debris_objp), - scripting::hook_param("Ship", 'o', prop_objp), + scripting::hook_param("Prop", 'o', prop_objp), scripting::hook_param("Debris", 'o', debris_objp), scripting::hook_param("Hitpos", 'o', hitpos))); } @@ -494,31 +494,6 @@ int collide_debris_prop(obj_pair* pair) collide_ship_ship_do_sound(&hitpos, prop_objp, debris_objp,false ); } - if (scripting::hooks::OnDebrisCollision->isActive() && !(debris_override && !ship_override)) { - // TODO PROP - /* - scripting::hooks::OnDebrisCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, debris_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), - scripting::hook_param("Object", 'o', debris_objp), - scripting::hook_param("Ship", 'o', ship_objp), - scripting::hook_param("Debris", 'o', debris_objp), - scripting::hook_param("Hitpos", 'o', hitpos))); - */ - } - if (scripting::hooks::OnShipCollision->isActive() && ((debris_override && !ship_override) || (!debris_override && !ship_override))) - { - // TODO PROP - /* - scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, debris_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', debris_objp), - scripting::hook_param("Object", 'o', ship_objp), - scripting::hook_param("Ship", 'o', ship_objp), - scripting::hook_param("Debris", 'o', debris_objp), - scripting::hook_param("Hitpos", 'o', hitpos), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (debris_hit_info.heavy == ship_objp)))); - */ - } - return 0; } } @@ -612,7 +587,7 @@ int collide_asteroid_prop(obj_pair* pair) { float asteroid_damage; - vec3d asteroid_vel = asteroid_objp->phys_info.vel; + //vec3d asteroid_vel = asteroid_objp->phys_info.vel; // do collision physics calculate_ship_ship_collision_physics(&asteroid_hit_info); @@ -628,27 +603,6 @@ int collide_asteroid_prop(obj_pair* pair) collide_ship_ship_do_sound(&hitpos, prop_objp, asteroid_objp, false); } - // TODO PROP - /* - if (scripting::hooks::OnAsteroidCollision->isActive() && !(asteroid_override && !ship_override)) { - scripting::hooks::OnAsteroidCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, asteroid_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp), - scripting::hook_param("Object", 'o', asteroid_objp), - scripting::hook_param("Ship", 'o', ship_objp), - scripting::hook_param("Asteroid", 'o', asteroid_objp), - scripting::hook_param("Hitpos", 'o', hitpos))); - } - if (scripting::hooks::OnShipCollision->isActive() && ((asteroid_override && !ship_override) || (!asteroid_override && !ship_override))) - { - scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {ship_objp, asteroid_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', asteroid_objp), - scripting::hook_param("Object", 'o', ship_objp), - scripting::hook_param("Ship", 'o', ship_objp), - scripting::hook_param("Asteroid", 'o', asteroid_objp), - scripting::hook_param("Hitpos", 'o', hitpos), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (asteroid_hit_info.heavy == ship_objp)))); - }*/ - return 0; } diff --git a/code/object/collideshipship.cpp b/code/object/collideshipship.cpp index eb99698e268..821ad0eca08 100644 --- a/code/object/collideshipship.cpp +++ b/code/object/collideshipship.cpp @@ -1806,41 +1806,6 @@ int collide_prop_ship(obj_pair* pair) hud_shield_quadrant_hit(ship_objp, -1); } - //FIX - if (!scripting::hooks::OnShipCollision->isActive()) { - return 0; - } - - if (!(b_override && !a_override)) - { - // TODO PROP - /* - scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {A, B} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', A), - scripting::hook_param("Object", 'o', B), - scripting::hook_param("Ship", 'o', A), - scripting::hook_param("ShipB", 'o', B), - scripting::hook_param("Hitpos", 'o', world_hit_pos), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)), - scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)))); - */ - } - if ((b_override && !a_override) || (!b_override && !a_override)) - { - // Yes, this should be reversed. - // TODO PROP - /* - scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {A, B} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', B), - scripting::hook_param("Object", 'o', A), - scripting::hook_param("Ship", 'o', B), - scripting::hook_param("ShipB", 'o', A), - scripting::hook_param("Hitpos", 'o', world_hit_pos), - scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)), - scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)))); - */ - } - return 0; } } diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index 19b6ef5ce3b..1705a4ea61c 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -678,88 +678,23 @@ static std::tuple prop_weapon_check_coll if (next_hit && hit) { // find hit time *next_hit = (int)(1000.0f * (mc.hit_dist * (flFrametime + time_limit) - flFrametime)); - if (*next_hit > 0) - // if hit occurs outside of this frame, do not do damage + if (*next_hit > 0) { + // if hit occurs outside of this frame, do not do damage valid_hit_occurred = 1; bool postproc = true; bool recheck = false; // most of this data is irrevelant for props but let's match it to make it easy - ship_weapon_collision_data cd{ - mc, - -1, - postproc, - -1, - -1, - ZERO_VECTOR - }; + ship_weapon_collision_data cd{mc, -1, postproc, -1, -1, ZERO_VECTOR}; return {postproc, recheck, cd}; - } - - if (hit) - { - /*wp->collisionInfo = new mc_info; // The weapon will free this memory later - *wp->collisionInfo = mc; - - bool prop_override = false, weapon_override = false; - - // get submodel handle if scripting needs it - bool has_submodel = (mc.hit_submodel >= 0); - scripting::api::submodel_h smh(mc.model_num, mc.hit_submodel); - - - if (scripting::hooks::OnWeaponCollision->isActive()) { - prop_override = scripting::hooks::OnWeaponCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), - scripting::hook_param("Object", 'o', weapon_objp), - scripting::hook_param("Prop", 'o', prop_objp), - scripting::hook_param("Weapon", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc.hit_point_world))); - } - if (scripting::hooks::OnPropCollision->isActive()) { - weapon_override = scripting::hooks::OnPropCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), - scripting::hook_param("Object", 'o', prop_objp), - scripting::hook_param("Prop", 'o', prop_objp), - scripting::hook_param("Weapon", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc.hit_point_world), - scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); - } - - if (!prop_override && !weapon_override) { - weapon_hit(weapon_objp, prop_objp, &mc.hit_point_world, MISS_SHIELDS); //, &mc.hit_normal, &mc.hit_point, mc.hit_submodel); This was changed by PR 6785 and the changes were not documented } - - if (scripting::hooks::OnWeaponCollision->isActive() && !(weapon_override && !prop_override)) { - scripting::hooks::OnWeaponCollision->run(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), - scripting::hook_param("Object", 'o', weapon_objp), - scripting::hook_param("Prop", 'o', prop_objp), - scripting::hook_param("Weapon", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc.hit_point_world))); - } - if (scripting::hooks::OnPropCollision->isActive() && !prop_override) { - scripting::hooks::OnPropCollision->run(scripting::hooks::CollisionConditions{ {prop_objp, weapon_objp} }, - scripting::hook_param_list(scripting::hook_param("Self", 'o', weapon_objp), - scripting::hook_param("Object", 'o', prop_objp), - scripting::hook_param("Prop", 'o', prop_objp), - scripting::hook_param("Weapon", 'o', weapon_objp), - scripting::hook_param("Hitpos", 'o', mc.hit_point_world), - scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel))); - }*/ - valid_hit_occurred = 1; } + valid_hit_occurred = hit ? 1 : 0; + bool postproc = (valid_hit_occurred != 0); bool recheck = (valid_hit_occurred == 0); // most of this data is irrevelant for props but let's match it to make it easy - ship_weapon_collision_data cd{ - (valid_hit_occurred ? mc : mc_info{}), - -1, - postproc, - -1, - -1, - ZERO_VECTOR - }; + ship_weapon_collision_data cd{(valid_hit_occurred ? mc : mc_info{}), -1, postproc, -1, -1, ZERO_VECTOR}; return{postproc, recheck, cd}; } @@ -809,7 +744,7 @@ static void prop_weapon_process_collision(obj_pair* pair, const ship_weapon_coll weapon_hit(weapon_objp, prop_objp, &mc.hit_point_world, MISS_SHIELDS); } - if (scripting::hooks::OnWeaponCollision->isActive() && !(weapon_override && !prop_override)) { + if (scripting::hooks::OnWeaponCollision->isActive() && (!weapon_override || prop_override)) { scripting::hooks::OnWeaponCollision->run(scripting::hooks::CollisionConditions{{prop_objp, weapon_objp}}, scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), scripting::hook_param("Object", 'o', weapon_objp), @@ -943,33 +878,33 @@ collision_result collide_ship_weapon_check( obj_pair * pair ) */ int collide_prop_weapon(obj_pair* pair) { - object* prop = pair->a; + object* prop_obj = pair->a; object* weapon_obj = pair->b; - Assert(prop->type == OBJ_PROP); + Assert(prop_obj->type == OBJ_PROP); Assert(weapon_obj->type == OBJ_WEAPON); // Cyborg17 - no ship-ship collisions when doing multiplayer rollback - if ((Game_mode & GM_MULTIPLAYER) && multi_ship_record_get_rollback_wep_mode() && (weapon_obj->parent_sig == OBJ_INDEX(prop))) { + if ((Game_mode & GM_MULTIPLAYER) && multi_ship_record_get_rollback_wep_mode() && (weapon_obj->parent_sig == OBJ_INDEX(prop_obj))) { return 0; } - if (reject_due_collision_groups(prop, weapon_obj)) + if (reject_due_collision_groups(prop_obj, weapon_obj)) return 0; // Cull lasers within big prop spheres by casting a vector forward for (1) exit sphere or (2) lifetime of laser // If it does hit, don't check the pair until about 200 ms before collision. // If it does not hit and is within error tolerance, cull the pair. - if (prop->radius > 500.0f && (weapon_obj->phys_info.flags & PF_CONST_VEL)) { + if (prop_obj->radius > 500.0f && (weapon_obj->phys_info.flags & PF_CONST_VEL)) { // Check when within ~1.1 radii. // This allows good transition between sphere checking (leaving the laser about 200 ms from radius) and checking // within the sphere with little time between. There may be some time for "small" big ships // Note: culling ships with auto spread shields seems to waste more performance than it saves, // so we're not doing that here - if (vm_vec_dist_squared(&prop->pos, &weapon_obj->pos) < (1.2f * prop->radius * prop->radius)) { + if (vm_vec_dist_squared(&prop_obj->pos, &weapon_obj->pos) < (1.2f * prop_obj->radius * prop_obj->radius)) { auto [do_postproc, never_hits, collision_data] = - check_inside_radius_for_big_ships(prop, weapon_obj, pair); + check_inside_radius_for_big_ships(prop_obj, weapon_obj, pair); if (do_postproc) { prop_weapon_process_collision(pair, collision_data); @@ -978,7 +913,7 @@ int collide_prop_weapon(obj_pair* pair) } } - auto [do_postproc, does_not_hit, collision_data] = prop_weapon_check_collision(prop, weapon_obj); + auto [do_postproc, does_not_hit, collision_data] = prop_weapon_check_collision(prop_obj, weapon_obj); if (do_postproc) { prop_weapon_process_collision(pair, collision_data); @@ -987,7 +922,7 @@ int collide_prop_weapon(obj_pair* pair) if (does_not_hit) { // Since we didn't hit, check to see if we can disable all future collisions // between these two. - return weapon_will_never_hit(weapon_obj, prop, pair); + return weapon_will_never_hit(weapon_obj, prop_obj, pair); } return 0; diff --git a/code/parse/parselo.cpp b/code/parse/parselo.cpp index 761b9e7cb74..afc43e121ff 100644 --- a/code/parse/parselo.cpp +++ b/code/parse/parselo.cpp @@ -850,7 +850,7 @@ int required_string_one_of_fred(int arg_count, ...) } va_end(vl); - advance_to_eoln(NULL); + advance_to_eoln(nullptr); ignore_white_space(); } diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 01b0f3a863b..3a22584a5f9 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -27538,7 +27538,7 @@ int sexp_get_colgroup_prop(int node) if (!prop_entry) return SEXP_NAN; - object& obj = Objects[eval_prop(node)->objnum]; + const object& obj = Objects[eval_prop(node)->objnum]; return obj.collision_group_id; } diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index 2bd8d18a546..e11f39a9476 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -33,7 +33,7 @@ flag_def_list_new Prop_flags[] = { //{ "no impact debris", Prop::Info_Flags::No_impact_debris, true, false }, }; -const size_t Num_prop_flags = sizeof(Prop_flags) / sizeof(flag_def_list_new); +//const size_t Num_prop_flags = sizeof(Prop_flags) / sizeof(flag_def_list_new); /** * Return the index of Prop_info[].name that is *token. @@ -54,13 +54,13 @@ int prop_name_lookup(const char *name) Assertion(name != nullptr, "NULL name passed to prop_name_lookup"); for (size_t i=0; i < Props.size(); i++){ - auto prop = Props[i] ? &Props[i].value() : nullptr; - if (prop == nullptr) { + auto p = Props[i] ? &Props[i].value() : nullptr; + if (p == nullptr) { continue; } - if (prop->objnum >= 0){ - if (Objects[prop->objnum].type == OBJ_PROP) { - if (!stricmp(name, prop->prop_name)) { + if (p->objnum >= 0){ + if (Objects[p->objnum].type == OBJ_PROP) { + if (!stricmp(name, p->prop_name)) { return static_cast(i); } } @@ -92,9 +92,9 @@ prop* prop_id_lookup(int id) prop_category* prop_get_category(const SCP_string& name) { - for (auto it = Prop_categories.begin(); it != Prop_categories.end(); ++it) { - if (lcase_equal(name, it->name)) { - return &(*it); + for (auto& category : Prop_categories) { + if (lcase_equal(name, category.name)) { + return &category; } } return nullptr; @@ -113,7 +113,7 @@ void parse_prop_table(const char* filename) required_string("+Color:"); int rgb[3]; stuff_int_list(rgb, 3, RAW_INTEGER_TYPE); - gr_init_color(&pc.color, rgb[0], rgb[1], rgb[2]); + gr_init_color(&pc.list_color, rgb[0], rgb[1], rgb[2]); Prop_categories.push_back(pc); @@ -162,7 +162,7 @@ void parse_prop_table(const char* filename) Warning(LOCATION, "+remove flag used for prop in non-modular table"); } if (!remove_prop) { - Removed_props.push_back(fname); + Removed_props.emplace_back(fname); remove_prop = true; } } @@ -207,7 +207,7 @@ void parse_prop_table(const char* filename) continue; } - Prop_info.push_back(prop_info()); + Prop_info.emplace_back(prop_info()); pip = &Prop_info.back(); first_time = true; @@ -288,17 +288,18 @@ void parse_prop_table(const char* filename) bool flag_found = false; // check various ship flags - for (size_t idx = 0; idx < Num_prop_flags; idx++) { - if (!stricmp(Prop_flags[idx].name, cur_flag)) { + for (auto& pflag : Prop_flags) { + if (!stricmp(pflag.name, cur_flag)) { flag_found = true; - if (!Prop_flags[idx].in_use) + if (!pflag.in_use) { Warning(LOCATION, "Use of '%s' flag for Prop Class '%s' - this flag is no longer needed.", - Prop_flags[idx].name, + pflag.name, pip->name.c_str()); - else - pip->flags.set(Prop_flags[idx].def); + } else { + pip->flags.set(pflag.def); + } break; } @@ -379,7 +380,7 @@ void post_process_props() if (create_unknown_category) { prop_category pc; pc.name = "Unknown Category"; - gr_init_color(&pc.color, 128, 128, 128); + gr_init_color(&pc.list_color, 128, 128, 128); Prop_categories.push_back(pc); } @@ -546,7 +547,7 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) for (int bank = 0; bank < pm->n_glow_point_banks; bank++) { glow_point_bank_override* gpo = nullptr; - SCP_unordered_map::iterator gpoi = pip->glowpoint_bank_override_map.find(bank); + auto gpoi = pip->glowpoint_bank_override_map.find(bank); if (gpoi != pip->glowpoint_bank_override_map.end()) { gpo = (glow_point_bank_override*)pip->glowpoint_bank_override_map[bank]; } @@ -609,7 +610,7 @@ static void prop_model_change(int n, int prop_type) prop* sp = prop_id_lookup(n); prop_info* sip = &(Prop_info[prop_type]); object* objp = &Objects[sp->objnum]; - polymodel_instance* pmi = model_get_instance(sp->model_instance_num); + //polymodel_instance* pmi = model_get_instance(sp->model_instance_num); // get new model if (sip->model_num == -1) { @@ -637,7 +638,7 @@ static void prop_model_change(int n, int prop_type) for (int bank = 0; bank < pm->n_glow_point_banks; bank++) { glow_point_bank_override* gpo = nullptr; - SCP_unordered_map::iterator gpoi = sip->glowpoint_bank_override_map.find(bank); + auto gpoi = sip->glowpoint_bank_override_map.find(bank); if (gpoi != sip->glowpoint_bank_override_map.end()) { gpo = (glow_point_bank_override*)sip->glowpoint_bank_override_map[bank]; } @@ -673,7 +674,7 @@ static void prop_model_change(int n, int prop_type) // create new model instance data // note: this is needed for both subsystem stuff and submodel animation stuff sp->model_instance_num = model_create_instance(OBJ_INDEX(objp), sip->model_num); - pmi = model_get_instance(sp->model_instance_num); + //pmi = model_get_instance(sp->model_instance_num); } /** @@ -854,8 +855,8 @@ int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, col prop_info* prinfo = &Prop_info[propp->prop_info_index]; - // debris_hit_info NULL - so debris-weapon collision - if (prop_hit_info == NULL) { + // debris_hit_info nullptr - so debris-weapon collision + if (prop_hit_info == nullptr) { // debris weapon collision Assert(other_obj->type == OBJ_WEAPON); mc.model_instance_num = propp->model_instance_num; diff --git a/code/prop/prop.h b/code/prop/prop.h index df18a926971..9b720a29125 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -40,7 +40,7 @@ typedef struct prop { typedef struct prop_category { SCP_string name; - color color; + color list_color; } prop_category; typedef struct parsed_prop { diff --git a/code/scripting/api/libs/mission.cpp b/code/scripting/api/libs/mission.cpp index 3dd647c1d81..f65648349c5 100644 --- a/code/scripting/api/libs/mission.cpp +++ b/code/scripting/api/libs/mission.cpp @@ -618,7 +618,7 @@ ADE_INDEXER(l_Mission_Props, "number/string IndexOrName", "Gets prop", "prop", " } } -ADE_FUNC(__len, l_Mission_Props, NULL, +ADE_FUNC(__len, l_Mission_Props, nullptr, "Number of props in the mission. " "This function is somewhat slow, and should be set to a variable for use in looping situations. " "Note that props can be vanished, and so this value cannot be relied on for more than one frame.", @@ -1432,12 +1432,12 @@ ADE_FUNC(createProp, vec3d pos = vmd_zero_vector; ade_get_args(L, "|sooo", &name, l_Propclass.Get(&pclass), l_Matrix.GetPtr(&orient), l_Vector.Get(&pos)); - if (Prop_info.size() == 0) { + if (Prop_info.empty()) { return ade_set_error(L, "o", l_Prop.Set(object_h())); } matrix *real_orient = &vmd_identity_matrix; - if(orient != NULL) + if(orient != nullptr) { real_orient = orient->GetMatrix(); } diff --git a/code/scripting/api/libs/tables.cpp b/code/scripting/api/libs/tables.cpp index dec43339e48..c26b5223d48 100644 --- a/code/scripting/api/libs/tables.cpp +++ b/code/scripting/api/libs/tables.cpp @@ -113,7 +113,7 @@ ADE_FUNC(__len, l_Tables_ShipTypes, nullptr, "Number of ship types", "number", " } //*****SUBLIBRARY: Tables/ShipClasses -ADE_LIB_DERIV(l_Tables_PropClasses, "PropClasses", NULL, NULL, l_Tables); +ADE_LIB_DERIV(l_Tables_PropClasses, "PropClasses", nullptr, nullptr, l_Tables); ADE_INDEXER(l_Tables_PropClasses, "number/string IndexOrName", "Array of prop classes", "propclass", "Prop class handle, or invalid handle if index is invalid") { if(!Props_inited) @@ -142,7 +142,7 @@ ADE_INDEXER(l_Tables_PropClasses, "number/string IndexOrName", "Array of prop cl return ade_set_args(L, "o", l_Propclass.Set(idx)); } -ADE_FUNC(__len, l_Tables_PropClasses, NULL, "Number of prop classes", "number", "Number of prop classes, or 0 if prop classes haven't been loaded yet") +ADE_FUNC(__len, l_Tables_PropClasses, nullptr, "Number of prop classes", "number", "Number of prop classes, or 0 if prop classes haven't been loaded yet") { if(!Props_inited) return ade_set_args(L, "i", 0); //No props loaded...should be 0 diff --git a/code/scripting/api/objs/prop.cpp b/code/scripting/api/objs/prop.cpp index 65ac5e1e98c..ae93e10b925 100644 --- a/code/scripting/api/objs/prop.cpp +++ b/code/scripting/api/objs/prop.cpp @@ -10,8 +10,7 @@ #include "prop/prop.h" -namespace scripting { -namespace api { +namespace scripting::api { //**********HANDLE: Prop ADE_OBJ_DERIV(l_Prop, object_h, "prop", "Prop handle", l_Object); @@ -95,5 +94,4 @@ ADE_VIRTVAR(Textures, return ade_set_args(L, "o", l_ModelInstanceTextures.Set(modelinstance_h(dest))); } -} // namespace api -} // namespace scripting +} // namespace scripting::api diff --git a/code/scripting/api/objs/prop.h b/code/scripting/api/objs/prop.h index 4412f39174a..4ccb566614b 100644 --- a/code/scripting/api/objs/prop.h +++ b/code/scripting/api/objs/prop.h @@ -3,10 +3,9 @@ #include "object/object.h" #include "scripting/ade_api.h" -namespace scripting { -namespace api { +namespace scripting::api { //**********HANDLE: Prop DECLARE_ADE_OBJ(l_Prop, object_h); -} // namespace api -} // namespace scripting + +} // namespace scripting::api diff --git a/code/scripting/api/objs/propclass.cpp b/code/scripting/api/objs/propclass.cpp index 563cf732d2a..00532d6c33e 100644 --- a/code/scripting/api/objs/propclass.cpp +++ b/code/scripting/api/objs/propclass.cpp @@ -3,15 +3,14 @@ #include "vecmath.h" #include "prop/prop.h" -namespace scripting { -namespace api { +namespace scripting::api { //**********HANDLE: Propclass ADE_OBJ(l_Propclass, int, "propclass", "Prop class handle"); ADE_FUNC(__tostring, l_Propclass, - NULL, + nullptr, "Prop class name", "string", "Prop class name, or an empty string if handle is invalid") @@ -77,7 +76,7 @@ ADE_VIRTVAR(Model, "Prop class model, or invalid model handle if propclass handle is invalid") { int idx = -1; - model_h* mdl = NULL; + model_h* mdl = nullptr; if (!ade_get_args(L, "o|o", l_Propclass.Get(&idx), l_Model.GetPtr(&mdl))) return ade_set_error(L, "o", l_Model.Set(model_h(-1))); @@ -200,7 +199,7 @@ ADE_FUNC(hasCustomStrings, ADE_FUNC(isValid, l_Propclass, - NULL, + nullptr, "Detects whether handle is valid", "boolean", "true if valid, false if handle is invalid, nil if a syntax/type error occurs") @@ -276,7 +275,7 @@ ADE_FUNC(renderTechModel2, int x1, y1, x2, y2; int idx; float zoom = 1.3f; - matrix_h* mh = NULL; + matrix_h* mh = nullptr; if (!ade_get_args(L, "oiiiio|f", l_Propclass.Get(&idx), &x1, &y1, &x2, &y2, l_Matrix.GetPtr(&mh), &zoom)) return ade_set_error(L, "b", false); @@ -337,5 +336,4 @@ ADE_FUNC(getPropClassIndex, return ade_set_args(L, "i", idx + 1); // Lua is 1-based } -} // namespace api -} // namespace scripting +} // namespace scripting::api diff --git a/code/scripting/api/objs/propclass.h b/code/scripting/api/objs/propclass.h index 23679293df6..e341ce3857d 100644 --- a/code/scripting/api/objs/propclass.h +++ b/code/scripting/api/objs/propclass.h @@ -2,10 +2,8 @@ #include "scripting/ade_api.h" -namespace scripting { -namespace api { +namespace scripting::api { DECLARE_ADE_OBJ(l_Propclass, int); -} -} // namespace scripting +} // namespace scripting::api diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index 3ae2e1c32dc..927eeb4defe 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -843,17 +843,17 @@ bool shipfx_eye_in_shadow( vec3d *eye_pos, object * src_obj, int light_n ) } } - for (const auto& prop : Props) { - if (prop.has_value()) { - objp = &Objects[prop->objnum]; + for (const auto& p : Props) { + if (p.has_value()) { + objp = &Objects[p->objnum]; if (objp->flags[Object::Object_Flags::Should_be_dead]) continue; if (src_obj != objp) { vm_vec_scale_add(&rp1, &rp0, &light_dir, objp->radius * 10.0f); - mc.model_instance_num = prop->model_instance_num; - mc.model_num = Prop_info[prop->prop_info_index].model_num; + mc.model_instance_num = p->model_instance_num; + mc.model_num = Prop_info[p->prop_info_index].model_num; mc.orient = &objp->orient; mc.pos = &objp->pos; mc.p0 = &rp0; diff --git a/code/weapon/beam.cpp b/code/weapon/beam.cpp index 670708c6603..6c4b7bf6079 100644 --- a/code/weapon/beam.cpp +++ b/code/weapon/beam.cpp @@ -3359,13 +3359,12 @@ int beam_collide_prop(obj_pair* pair) beam* a_beam; object* weapon_objp; object* prop_objp; - weapon_info* bwi; mc_info mc; int model_num; float width; // bogus - if (pair == NULL) { + if (pair == nullptr) { return 0; } @@ -3408,8 +3407,6 @@ int beam_collide_prop(obj_pair* pair) prop_objp = pair->b; prop* propp = prop_id_lookup(prop_objp->instance); - bwi = &Weapon_info[a_beam->weapon_info_index]; - // get the width of the beam width = a_beam->beam_collide_width * a_beam->current_width_factor; @@ -3482,7 +3479,7 @@ int beam_collide_prop(obj_pair* pair) beam_add_collision(a_beam, prop_objp, &mc, MISS_SHIELDS, false); } - if (scripting::hooks::OnBeamCollision->isActive() && !(weapon_override && !prop_override)) { + if (scripting::hooks::OnBeamCollision->isActive() && (!weapon_override || prop_override)) { scripting::hooks::OnBeamCollision->run(scripting::hooks::CollisionConditions{{prop_objp, weapon_objp}}, scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp), scripting::hook_param("Object", 'o', weapon_objp), diff --git a/fred2/mainfrm.cpp b/fred2/mainfrm.cpp index 1cfe58225a9..31ab5e2e65c 100644 --- a/fred2/mainfrm.cpp +++ b/fred2/mainfrm.cpp @@ -476,7 +476,7 @@ void color_combo_box_prop::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) else { auto cinfo = prop_get_category(pip->category); if (cinfo != nullptr) { - newTextColor = RGB(cinfo->color.red, cinfo->color.green, cinfo->color.blue); + newTextColor = RGB(cinfo->list_color.red, cinfo->list_color.green, cinfo->list_color.blue); } } } diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index 39091d2c3cb..b903c4ea752 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -5283,8 +5283,7 @@ int CFred_mission_save::save_props() parse_comments(2); fout("\t\t;! %d total", static_cast(Props.size())); - for (int i = 0; i < static_cast(Props.size()); i++) { - const auto& p = Props[i]; + for (const auto& p : Props) { if (p.has_value()) { required_string_either_fred("$Name:", "#Events"); required_string_fred("$Name:"); diff --git a/fred2/sexp_tree.cpp b/fred2/sexp_tree.cpp index c59d6096d21..cb6594468fa 100644 --- a/fred2/sexp_tree.cpp +++ b/fred2/sexp_tree.cpp @@ -5417,7 +5417,7 @@ sexp_list_item *sexp_tree::get_listing_opf(int opf, int parent_node, int arg_ind break; case OPF_PROP: - list = get_listing_opf_prop(parent_node); + list = get_listing_opf_prop(); break; case OPF_WING: @@ -6157,7 +6157,7 @@ sexp_list_item *sexp_tree::get_listing_opf_ship(int parent_node) return head.next; } -sexp_list_item *sexp_tree::get_listing_opf_prop(int parent_node) +sexp_list_item *sexp_tree::get_listing_opf_prop() { object *ptr; sexp_list_item head; diff --git a/fred2/sexp_tree.h b/fred2/sexp_tree.h index 81d1b4fc21f..30260095d49 100644 --- a/fred2/sexp_tree.h +++ b/fred2/sexp_tree.h @@ -220,7 +220,7 @@ class sexp_tree : public CTreeCtrl sexp_list_item *get_listing_opf_positive(); sexp_list_item *get_listing_opf_number(); sexp_list_item *get_listing_opf_ship(int parent_node = -1); - sexp_list_item *get_listing_opf_prop(int parent_node = -1); + sexp_list_item *get_listing_opf_prop(); sexp_list_item *get_listing_opf_wing(); sexp_list_item *get_listing_opf_subsystem(int parent_node, int arg_index); sexp_list_item *get_listing_opf_subsystem_type(int parent_node); diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index e15c7d45c57..c398948f02e 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -5476,8 +5476,7 @@ int CFred_mission_save::save_props() parse_comments(2); fout("\t\t;! %d total", static_cast(Props.size())); - for (int i = 0; i < static_cast(Props.size()); i++) { - const auto& p = Props[i]; + for (const auto& p : Props) { if (p.has_value()) { required_string_either_fred("$Name:", "#Events"); required_string_fred("$Name:"); diff --git a/qtfred/src/ui/widgets/sexp_tree.cpp b/qtfred/src/ui/widgets/sexp_tree.cpp index 51824e5a5e0..8e394d45b58 100644 --- a/qtfred/src/ui/widgets/sexp_tree.cpp +++ b/qtfred/src/ui/widgets/sexp_tree.cpp @@ -3358,7 +3358,7 @@ sexp_list_item* sexp_tree::get_listing_opf(int opf, int parent_node, int arg_ind break; case OPF_PROP: - list = get_listing_opf_prop(parent_node); + list = get_listing_opf_prop(); break; case OPF_WING: @@ -4061,7 +4061,7 @@ sexp_list_item* sexp_tree::get_listing_opf_ship(int parent_node) { return head.next; } -sexp_list_item *sexp_tree::get_listing_opf_prop(int parent_node) +sexp_list_item *sexp_tree::get_listing_opf_prop() { object *ptr; sexp_list_item head; diff --git a/qtfred/src/ui/widgets/sexp_tree.h b/qtfred/src/ui/widgets/sexp_tree.h index d07c934e693..f962cb4e39c 100644 --- a/qtfred/src/ui/widgets/sexp_tree.h +++ b/qtfred/src/ui/widgets/sexp_tree.h @@ -277,7 +277,7 @@ class sexp_tree: public QTreeWidget { sexp_list_item* get_listing_opf_positive(); sexp_list_item* get_listing_opf_number(); sexp_list_item* get_listing_opf_ship(int parent_node = -1); - sexp_list_item* get_listing_opf_prop(int parent_node = -1); + static sexp_list_item* get_listing_opf_prop(); sexp_list_item* get_listing_opf_wing(); sexp_list_item* get_listing_opf_subsystem(int parent_node, int arg_index); sexp_list_item* get_listing_opf_subsystem_type(int parent_node); @@ -307,7 +307,7 @@ class sexp_tree: public QTreeWidget { sexp_list_item* get_listing_opf_medal_name(); sexp_list_item* get_listing_opf_weapon_name(); sexp_list_item* get_listing_opf_ship_class_name(); - sexp_list_item* get_listing_opf_prop_class_name(); + static sexp_list_item* get_listing_opf_prop_class_name(); sexp_list_item* get_listing_opf_huge_weapon(); sexp_list_item* get_listing_opf_ship_not_player(); sexp_list_item* get_listing_opf_jump_nodes(); From 6b0a1bcd9e1fd5c58f18477333c67a3cb1cda248 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 30 Jul 2025 10:25:29 -0500 Subject: [PATCH 21/25] save/parse mission prop classes by name --- code/mission/missionparse.cpp | 39 +++++++++++++++++++++++++++--- code/mission/missionparse.h | 1 + fred2/freddoc.cpp | 6 ++--- fred2/missionsave.cpp | 2 +- qtfred/src/mission/Editor.cpp | 10 ++++---- qtfred/src/mission/missionsave.cpp | 2 +- 6 files changed, 47 insertions(+), 13 deletions(-) diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 2ee77ce389b..2f4907b6af7 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -115,6 +115,7 @@ int Num_teams; fix Entry_delay_time = 0; int Num_unknown_ship_classes; +int Num_unknown_prop_classes; int Num_unknown_weapon_classes; int Num_unknown_loadout_classes; @@ -5128,7 +5129,29 @@ void parse_prop(mission* /*pm*/) // Maybe do this by name instead? required_string("$Class:"); - stuff_int(&p.prop_info_index); + SCP_string class_name; + stuff_string(class_name, F_NAME); + int idx = prop_info_lookup(class_name.c_str()); + if (idx < 0) { + SCP_string text; + sprintf(text, "Prop \"%s\" has an invalid prop type (props.tbl probably changed).", p.name); + + if (Prop_info.empty()) { + text += " No props.tbl is loaded. Prop will not be added to the mission!"; + } else { + text += " Prop will be added to the mission with type 0."; + idx = 0; + } + + if (Fred_running) { + Warning(LOCATION, text.c_str()); + } else { + mprintf(("MISSIONS: %s", text.c_str())); + } + + Num_unknown_prop_classes++; + } + p.prop_info_index = idx; required_string("$Location:"); stuff_vec3d(&p.position); @@ -5147,6 +5170,12 @@ void parse_prop(mission* /*pm*/) } } + // if idx is still -1 then we have an empty props.tbl so we parse + // everything here and just discard it. A warning has already been generated above. + if (idx < 0) { + return; + } + Parse_props.emplace_back(p); } @@ -6515,6 +6544,7 @@ bool parse_mission(mission *pm, int flags) // reset parse error stuff Num_unknown_ship_classes = 0; + Num_unknown_prop_classes = 0; Num_unknown_weapon_classes = 0; Num_unknown_loadout_classes = 0; @@ -6554,7 +6584,7 @@ bool parse_mission(mission *pm, int flags) parse_custom_data(pm); // if we couldn't load some mod data - if ((Num_unknown_ship_classes > 0) || ( Num_unknown_loadout_classes > 0 )) { + if ((Num_unknown_ship_classes > 0) || (Num_unknown_prop_classes > 0) || ( Num_unknown_loadout_classes > 0 )) { // if running on standalone server, just print to the log if (Game_mode & GM_STANDALONE_SERVER) { mprintf(("Warning! Could not load %d ship classes!\n", Num_unknown_ship_classes)); @@ -6568,7 +6598,10 @@ bool parse_mission(mission *pm, int flags) if (Num_unknown_ship_classes > 0) { sprintf(text, "Warning!\n\nFreeSpace was unable to find %d ship class%s while loading this mission. This can happen if you try to play a %s that is incompatible with the current mod.\n\n", Num_unknown_ship_classes, (Num_unknown_ship_classes > 1) ? "es" : "", (Game_mode & GM_CAMPAIGN_MODE) ? "campaign" : "mission"); } - else { + else if (Num_unknown_prop_classes > 0) { + sprintf(text, "Warning!\n\nFreeSpace was unable to find %d prop class%s while loading this mission. This can happen if you try to play a %s that is incompatible with the current mod.\n\n", Num_unknown_prop_classes, (Num_unknown_prop_classes > 1) ? "es" : "", (Game_mode & GM_CAMPAIGN_MODE) ? "campaign" : "mission"); + } + else if (Num_unknown_loadout_classes > 0) { sprintf(text, "Warning!\n\nFreeSpace was unable to find %d weapon class%s while loading this mission. This can happen if you try to play a %s that is incompatible with the current mod.\n\n", Num_unknown_loadout_classes, (Num_unknown_loadout_classes > 1) ? "es" : "", (Game_mode & GM_CAMPAIGN_MODE) ? "campaign" : "mission"); } diff --git a/code/mission/missionparse.h b/code/mission/missionparse.h index 4f7f6bad4aa..1df2a0f54d5 100644 --- a/code/mission/missionparse.h +++ b/code/mission/missionparse.h @@ -352,6 +352,7 @@ extern fix Entry_delay_time; extern int Loading_screen_bm_index; extern int Num_unknown_ship_classes; +extern int Num_unknown_prop_classes; extern int Num_unknown_weapon_classes; extern int Num_unknown_loadout_classes; diff --git a/fred2/freddoc.cpp b/fred2/freddoc.cpp index 4e61c451cf4..d2ee5ee0757 100644 --- a/fred2/freddoc.cpp +++ b/fred2/freddoc.cpp @@ -246,13 +246,13 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) { } // message 2: unknown classes - if ((Num_unknown_ship_classes > 0) || (Num_unknown_weapon_classes > 0) || (Num_unknown_loadout_classes > 0)) { + if ((Num_unknown_ship_classes > 0) || (Num_unknown_prop_classes > 0) || (Num_unknown_weapon_classes > 0) || (Num_unknown_loadout_classes > 0)) { if (flags & MPF_IMPORT_FSM) { char msg[256]; - sprintf(msg, "Fred encountered unknown ship/weapon classes when importing \"%s\" (path \"%s\"). You will have to manually edit the converted mission to correct this.", The_mission.name, pathname); + sprintf(msg, "Fred encountered unknown ship/prop/weapon classes when importing \"%s\" (path \"%s\"). You will have to manually edit the converted mission to correct this.", The_mission.name, pathname); Fred_view_wnd->MessageBox(msg); } else { - Fred_view_wnd->MessageBox("Fred encountered unknown ship/weapon classes when parsing the mission file. This may be due to mission disk data you do not have."); + Fred_view_wnd->MessageBox("Fred encountered unknown ship/prop/weapon classes when parsing the mission file. This may be due to mission disk data you do not have."); } } diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index b903c4ea752..651fad5b82a 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -5292,7 +5292,7 @@ int CFred_mission_save::save_props() required_string_fred("$Class:"); parse_comments(2); - fout(" %d", p->prop_info_index); + fout(" %s", Prop_info[p->prop_info_index].name); required_string_fred("$Location:"); parse_comments(); diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index 4f357ccf3cd..a442a549596 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -238,22 +238,22 @@ bool Editor::loadMission(const std::string& mission_name, int flags) { } // message 2: unknown classes - if ((Num_unknown_ship_classes > 0) || (Num_unknown_weapon_classes > 0) || (Num_unknown_loadout_classes > 0)) { + if ((Num_unknown_ship_classes > 0) || (Num_unknown_prop_classes > 0) || (Num_unknown_weapon_classes > 0) || (Num_unknown_loadout_classes > 0)) { if (flags & MPF_IMPORT_FSM) { SCP_string msg; sprintf(msg, - "Fred encountered unknown ship/weapon classes when importing \"%s\" (path \"%s\"). You will have to manually edit the converted mission to correct this.", + "Fred encountered unknown ship/prop/weapon classes when importing \"%s\" (path \"%s\"). You will have to manually edit the converted mission to correct this.", The_mission.name, filepath.c_str()); _lastActiveViewport->dialogProvider->showButtonDialog(DialogType::Warning, - "Unknown Ship classes", + "Unknown object classes", msg, { DialogButton::Ok }); } else { _lastActiveViewport->dialogProvider->showButtonDialog(DialogType::Warning, - "Unknown Ship classes", - "Fred encountered unknown ship/weapon classes when parsing the mission file. This may be due to mission disk data you do not have.", + "Unknown object classes", + "Fred encountered unknown ship/prop/weapon classes when parsing the mission file. This may be due to mission disk data you do not have.", { DialogButton::Ok }); } } diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index c398948f02e..c4800d303fb 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -5485,7 +5485,7 @@ int CFred_mission_save::save_props() required_string_fred("$Class:"); parse_comments(2); - fout(" %d", p->prop_info_index); + fout(" %s", Prop_info[p->prop_info_index].name); required_string_fred("$Location:"); parse_comments(); From 718152dcdceea9ae9310a10ef58a9f13d888b058 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 30 Jul 2025 11:07:10 -0500 Subject: [PATCH 22/25] Clean up comments and finish TODOs --- code/model/animation/modelanimation_driver.cpp | 1 - code/model/modelread.cpp | 2 +- code/model/modelrender.cpp | 3 --- code/nebula/neb.cpp | 4 +--- code/object/collidedebrisship.cpp | 2 -- code/object/collideshipweapon.cpp | 2 -- code/object/objcollide.cpp | 2 -- code/object/object.cpp | 9 +++------ code/prop/prop.cpp | 5 +++-- code/scripting/api/objs/object.cpp | 8 ++++++-- code/ship/shiphit.cpp | 2 -- 11 files changed, 14 insertions(+), 26 deletions(-) diff --git a/code/model/animation/modelanimation_driver.cpp b/code/model/animation/modelanimation_driver.cpp index 596b6c32fd7..bf59530689c 100644 --- a/code/model/animation/modelanimation_driver.cpp +++ b/code/model/animation/modelanimation_driver.cpp @@ -98,7 +98,6 @@ namespace animation { } } - // ADD PROP HERE template static float get_ship_subproperty_float(polymodel_instance* pmi){ int objnum = get_pmi_objnum(pmi); diff --git a/code/model/modelread.cpp b/code/model/modelread.cpp index 622b775c386..bf614750e65 100644 --- a/code/model/modelread.cpp +++ b/code/model/modelread.cpp @@ -4925,7 +4925,7 @@ void model_get_moving_submodel_list(SCP_vector &submodel_vector, const obje int model_instance_num; int model_num; - // ADD PROP HERE + if (objp->type == OBJ_SHIP) { model_instance_num = Ships[objp->instance].model_instance_num; model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index b1436866ba8..9c91020d5f7 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -1330,7 +1330,6 @@ int model_render_determine_elapsed_time(int objnum, uint64_t flags) if ( objnum >= 0 ) { object *objp = &Objects[objnum]; - // ADD PROP HERE if ( objp->type == OBJ_SHIP ) { return timestamp_since(Ships[objp->instance].base_texture_anim_timestamp); } @@ -1927,7 +1926,6 @@ void model_render_set_glow_points(const polymodel *pm, int objnum) if ( objnum > -1 ) { object *objp = &Objects[objnum]; - // ADD PROP HERE if ( objp != NULL && objp->type == OBJ_SHIP ) { shipp = &Ships[Objects[objnum].instance]; sip = &Ship_info[shipp->ship_info_index]; @@ -2612,7 +2610,6 @@ void model_render_queue(const model_render_params* interp, model_draw_list* scen objp = &Objects[objnum]; int tentative_num = -1; - // ADD PROP HERE if (objp->type == OBJ_SHIP) { shipp = &Ships[objp->instance]; tentative_num = shipp->model_instance_num; diff --git a/code/nebula/neb.cpp b/code/nebula/neb.cpp index c4d86b75288..1d427ace5cd 100644 --- a/code/nebula/neb.cpp +++ b/code/nebula/neb.cpp @@ -716,8 +716,6 @@ float neb2_get_lod_scale(int objnum) ship *shipp; ship_info *sip; - // ADD PROP HERE - // bogus if ( (objnum < 0) || (objnum >= MAX_OBJECTS) @@ -1104,7 +1102,7 @@ void neb2_get_fog_values(float *fnear, float *ffar, object *objp) return; } - // ADD PROP HERE + // Future TODO: Add fog_start_dist and fog_complete_dist to Props // determine what fog index to use if(objp->type == OBJ_SHIP) { Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS)); diff --git a/code/object/collidedebrisship.cpp b/code/object/collidedebrisship.cpp index 983d8be4a27..b856e265081 100644 --- a/code/object/collidedebrisship.cpp +++ b/code/object/collidedebrisship.cpp @@ -24,8 +24,6 @@ #include "ship/ship.h" #include "ship/shiphit.h" -// Need a version of this for PROPS - void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_info); diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index 1705a4ea61c..35663e09e55 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -29,8 +29,6 @@ #include "ship/shiphit.h" #include "weapon/weapon.h" -// Need a version of this for PROPS - //mc, notify_ai_shield_down, shield_collision, quadrant_num, shield_tri_hit, shield_hitpoint using ship_weapon_collision_data = std::tuple, int, bool, int, int, vec3d>; diff --git a/code/object/objcollide.cpp b/code/object/objcollide.cpp index cae1a17fc4b..8a7bbec614e 100644 --- a/code/object/objcollide.cpp +++ b/code/object/objcollide.cpp @@ -429,8 +429,6 @@ int cpls_aux(vec3d *goal_pos, object *objp2, object *objp) return 0; } -// Maybe consider PROP here too - // Return true if objp will collide with some large object. // Don't check for an object this ship is docked to. int collide_predict_large_ship(object *objp, float distance) diff --git a/code/object/object.cpp b/code/object/object.cpp index ac9215a2b8a..fa724c5d193 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -672,8 +672,7 @@ void obj_delete_all() obj_delete(i); } - // If we've removed all objects then we can safely - // clear the Props vector TODO maybe remove this? + // If we've removed all objects then we can safely clear the Props vector Props.clear(); mprintf(("Cleanup: Deleted %i objects\n", counter)); @@ -1194,7 +1193,7 @@ void obj_set_flags( object *obj, const flagset& new_flags if ( obj->type == OBJ_OBSERVER ) { return; } - // Maybe add PROP here + // sanity checks if ( (obj->type != OBJ_SHIP) || (obj->instance < 0) ) { return; // return because we really don't want to set the flag @@ -1279,7 +1278,6 @@ void obj_move_all_pre(object *objp, float frametime) case OBJ_RAW_POF: break; case OBJ_PROP: - // Handle moving submodels maybe? break; case OBJ_NONE: Int3(); @@ -1544,7 +1542,6 @@ void obj_move_all_post(object *objp, float frametime) break; case OBJ_PROP: - // Not sure if anything will be needed here break; case OBJ_NONE: @@ -1696,7 +1693,7 @@ void obj_move_all(float frametime) // and look_at needs to happen last or the angle may be off by a frame) model_do_intrinsic_motions(objp); - // PROP probably will need something like this + // 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); diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index e11f39a9476..f43fccda0e3 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -798,7 +798,8 @@ void prop_render(object* obj, model_draw_list* scene) // Draft. Props vector uses std::optional to allow for empty slots for deleted props so the indices of the remaining // props do not change. In long FRED sessions without saving and loading, this can lead to a lot of empty slots. However, // saving and loading the level will naturally compact the props vector by way of clearing and re-adding props. -void compact_props_vector() +// EDIT: Now that creating props will use empty prop slots, this may not be needed. Keeping it here for posterity. +/*void compact_props_vector() { SCP_vector> new_props; SCP_unordered_map index_remap; @@ -823,7 +824,7 @@ void compact_props_vector() } } } -} +}*/ void props_level_init() { Props.clear(); diff --git a/code/scripting/api/objs/object.cpp b/code/scripting/api/objs/object.cpp index 014b70e7d94..01dbc90c0c0 100644 --- a/code/scripting/api/objs/object.cpp +++ b/code/scripting/api/objs/object.cpp @@ -18,6 +18,7 @@ #include "object/objcollide.h" #include "object/objectshield.h" #include "object/objectsnd.h" +#include "prop/prop.h" #include "scripting/api/LuaEventCallback.h" #include "scripting/api/objs/color.h" #include "scripting/lua/LuaFunction.h" @@ -544,7 +545,10 @@ ADE_FUNC( flags = (MC_CHECK_MODEL | MC_CHECK_RAY); break; case OBJ_PROP: - // do this + if (Props[obj->instance].has_value()) { + model_num = Prop_info[Props[obj->instance].value().prop_info_index].model_num; + flags = (MC_CHECK_MODEL | MC_CHECK_RAY); + } break; default: return ADE_RETURN_NIL; @@ -573,7 +577,7 @@ ADE_FUNC( } else if (obj->type == OBJ_ASTEROID) { model_instance_num = Asteroids[obj->instance].model_instance_num; } else if (obj->type == OBJ_PROP) { - model_instance_num = -1; // YOUR MOM + model_instance_num = Props[obj->instance].value().model_instance_num; } mc_info hull_check; diff --git a/code/ship/shiphit.cpp b/code/ship/shiphit.cpp index d4acd9c3a4b..7fdac5e4b40 100755 --- a/code/ship/shiphit.cpp +++ b/code/ship/shiphit.cpp @@ -1358,8 +1358,6 @@ void ship_hit_sparks_no_rotate(object *ship_objp, vec3d *hitpos) } } -// Need a prop version? Maybe, maybe not - // find the max number of sparks allowed for ship // limited for fighter by hull % others by radius. int get_max_sparks(const object* ship_objp) From 07b9c21e8f4fe75bf5193c096247be21b15e909b Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 30 Jul 2025 13:16:12 -0500 Subject: [PATCH 23/25] fix string errors --- code/mission/missionparse.cpp | 2 +- fred2/missionsave.cpp | 2 +- qtfred/src/mission/missionsave.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 2f4907b6af7..669ffa5cb46 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -5144,7 +5144,7 @@ void parse_prop(mission* /*pm*/) } if (Fred_running) { - Warning(LOCATION, text.c_str()); + Warning(LOCATION, "%s", text.c_str()); } else { mprintf(("MISSIONS: %s", text.c_str())); } diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index 651fad5b82a..01ee11d241c 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -5292,7 +5292,7 @@ int CFred_mission_save::save_props() required_string_fred("$Class:"); parse_comments(2); - fout(" %s", Prop_info[p->prop_info_index].name); + fout(" %s", Prop_info[p->prop_info_index].name.c_str()); required_string_fred("$Location:"); parse_comments(); diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index c4800d303fb..121e3ff1be4 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -5485,7 +5485,7 @@ int CFred_mission_save::save_props() required_string_fred("$Class:"); parse_comments(2); - fout(" %s", Prop_info[p->prop_info_index].name); + fout(" %s", Prop_info[p->prop_info_index].name.c_str()); required_string_fred("$Location:"); parse_comments(); From 6223688937573dc4c37dd67f84e0dd1c4fd02dc9 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 30 Jul 2025 13:56:44 -0500 Subject: [PATCH 24/25] clang wants this to be static now --- code/lab/dialogs/lab_ui.cpp | 2 +- code/lab/dialogs/lab_ui.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/lab/dialogs/lab_ui.cpp b/code/lab/dialogs/lab_ui.cpp index cc10928c0a2..cf1cf2bf57d 100644 --- a/code/lab/dialogs/lab_ui.cpp +++ b/code/lab/dialogs/lab_ui.cpp @@ -206,7 +206,7 @@ void LabUi::build_prop_list() } } -void LabUi::build_background_list() const +void LabUi::build_background_list() { SCP_vector t_missions; diff --git a/code/lab/dialogs/lab_ui.h b/code/lab/dialogs/lab_ui.h index cb3d7237f40..299a705b4b7 100644 --- a/code/lab/dialogs/lab_ui.h +++ b/code/lab/dialogs/lab_ui.h @@ -34,7 +34,7 @@ class LabUi { static void build_debris_list(); static void build_prop_list(); static void build_prop_subtype_list(); - void build_background_list() const; + static void build_background_list(); void show_render_options(); void show_object_options() const; void show_object_selector() const; From 2a0a78abee2ddbf4581d9c32a3727db426228f05 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Fri, 17 Oct 2025 09:55:59 -0500 Subject: [PATCH 25/25] address feedback --- code/lab/dialogs/lab_ui.cpp | 2 +- code/lab/manager/lab_manager.cpp | 2 +- code/mission/missionparse.cpp | 2 +- code/model/modelrender.cpp | 2 +- code/object/collideshipweapon.cpp | 2 +- code/prop/prop.cpp | 8 ++++---- code/prop/prop.h | 4 ++-- code/scripting/api/libs/mission.cpp | 8 ++------ code/scripting/api/objs/object.cpp | 2 +- qtfred/src/mission/management.cpp | 4 ++-- 10 files changed, 16 insertions(+), 20 deletions(-) diff --git a/code/lab/dialogs/lab_ui.cpp b/code/lab/dialogs/lab_ui.cpp index cf1cf2bf57d..a41c41531f2 100644 --- a/code/lab/dialogs/lab_ui.cpp +++ b/code/lab/dialogs/lab_ui.cpp @@ -1542,7 +1542,7 @@ void LabUi::show_object_options() const with_CollapsingHeader("Object actions") { - if (getLabManager()->isSafeForAsteroids()) { + if (getLabManager()->isSafeForProps()) { // No actions yet } } diff --git a/code/lab/manager/lab_manager.cpp b/code/lab/manager/lab_manager.cpp index 5feb1e31579..b43ebc234db 100644 --- a/code/lab/manager/lab_manager.cpp +++ b/code/lab/manager/lab_manager.cpp @@ -752,7 +752,7 @@ void LabManager::changeDisplayedObject(LabMode mode, int info_index, int subtype break; case LabMode::Prop: CurrentObject = prop_create(&CurrentOrientation, &CurrentPosition, CurrentClass); - if (isSafeForShips()) { + if (isSafeForProps()) { ModelFilename = Prop_info[CurrentClass].pof_file; } break; diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 669ffa5cb46..d2b14a80deb 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -5284,7 +5284,7 @@ void post_process_path_stuff() // MjnMixael void post_process_mission_props() { - for (auto& propp : Parse_props) { + for (const auto& propp : Parse_props) { int objnum = prop_create(&propp.orientation, &propp.position, propp.prop_info_index, propp.name); if (objnum >= 0) { diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index 9c91020d5f7..606cb75ec01 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -2933,7 +2933,7 @@ void model_render_only_glowpoint_lights(const model_render_params* interp, int m objp = &Objects[objnum]; int tentative_num = -1; - // ADD PROP HERE + // TODO: Add Prop support here if (objp->type == OBJ_SHIP) { shipp = &Ships[objp->instance]; tentative_num = shipp->model_instance_num; diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index 35663e09e55..d7743f4b517 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -901,7 +901,7 @@ int collide_prop_weapon(obj_pair* pair) // Note: culling ships with auto spread shields seems to waste more performance than it saves, // so we're not doing that here if (vm_vec_dist_squared(&prop_obj->pos, &weapon_obj->pos) < (1.2f * prop_obj->radius * prop_obj->radius)) { - auto [do_postproc, never_hits, collision_data] = + const auto& [do_postproc, never_hits, collision_data] = check_inside_radius_for_big_ships(prop_obj, weapon_obj, pair); if (do_postproc) { diff --git a/code/prop/prop.cpp b/code/prop/prop.cpp index f43fccda0e3..aa0bf6e5045 100644 --- a/code/prop/prop.cpp +++ b/code/prop/prop.cpp @@ -112,7 +112,7 @@ void parse_prop_table(const char* filename) required_string("+Color:"); int rgb[3]; - stuff_int_list(rgb, 3, RAW_INTEGER_TYPE); + stuff_int_list(rgb, 3, ParseLookupType::RAW_INTEGER_TYPE); gr_init_color(&pc.list_color, rgb[0], rgb[1], rgb[2]); Prop_categories.push_back(pc); @@ -271,7 +271,7 @@ void parse_prop_table(const char* filename) } if(optional_string("$Detail distance:")) { - pip->num_detail_levels = (int)stuff_int_list(pip->detail_distance, MAX_PROP_DETAIL_LEVELS, RAW_INTEGER_TYPE); + pip->num_detail_levels = (int)stuff_int_list(pip->detail_distance, MAX_PROP_DETAIL_LEVELS, ParseLookupType::RAW_INTEGER_TYPE); } if (optional_string("$Category:")) { @@ -423,7 +423,7 @@ void prop_init() * Returns object index of prop. * @return -1 means failed. */ -int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) +int prop_create(const matrix* orient, const vec3d* pos, int prop_type, const char* name) { prop_info* pip; prop* propp; @@ -454,7 +454,7 @@ int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name) char base_name[NAME_LENGTH]; char suffix[NAME_LENGTH]; strcpy_s(base_name, Prop_info[prop_type].name.c_str()); - sprintf(suffix, NOX(" %d"), static_cast(Props.size())); + sprintf(suffix, NOX(" %d"), new_id); // start building name strcpy_s(propp->prop_name, base_name); diff --git a/code/prop/prop.h b/code/prop/prop.h index 9b720a29125..9eaa399db9e 100644 --- a/code/prop/prop.h +++ b/code/prop/prop.h @@ -34,7 +34,7 @@ typedef struct prop { fix time_created; float alpha_mult; // glow points - std::deque glow_point_bank_active; + SCP_deque glow_point_bank_active; flagset flags; } prop; @@ -74,7 +74,7 @@ inline int prop_info_size() void prop_init(); // Object management -int prop_create(matrix* orient, vec3d* pos, int prop_type, const char* name = nullptr); +int prop_create(const matrix* orient, const vec3d* pos, int prop_type, const char* name = nullptr); void prop_delete(object* obj); void prop_render(object* obj, model_draw_list* scene); diff --git a/code/scripting/api/libs/mission.cpp b/code/scripting/api/libs/mission.cpp index f65648349c5..1ad1bdc492b 100644 --- a/code/scripting/api/libs/mission.cpp +++ b/code/scripting/api/libs/mission.cpp @@ -1427,12 +1427,12 @@ ADE_FUNC(createProp, "Prop handle, or invalid prop handle if prop couldn't be created") { const char* name = nullptr; - int pclass = -1; + int pclass = 0; matrix_h* orient = nullptr; vec3d pos = vmd_zero_vector; ade_get_args(L, "|sooo", &name, l_Propclass.Get(&pclass), l_Matrix.GetPtr(&orient), l_Vector.Get(&pos)); - if (Prop_info.empty()) { + if (!SCP_vector_inbounds(Prop_info, pclass)) { return ade_set_error(L, "o", l_Prop.Set(object_h())); } @@ -1442,10 +1442,6 @@ ADE_FUNC(createProp, real_orient = orient->GetMatrix(); } - if (pclass == -1) { - return ade_set_error(L, "o", l_Prop.Set(object_h())); - } - int obj_idx = prop_create(real_orient, &pos, pclass, name); if(obj_idx >= 0) { diff --git a/code/scripting/api/objs/object.cpp b/code/scripting/api/objs/object.cpp index 01dbc90c0c0..cdda7a2715b 100644 --- a/code/scripting/api/objs/object.cpp +++ b/code/scripting/api/objs/object.cpp @@ -107,7 +107,7 @@ ADE_FUNC(__tostring, l_Object, NULL, "Returns name of object (if any)", "string" sprintf(buf, "%s beam", Weapon_info[Beams[objh->objp()->instance].weapon_info_index].name); break; case OBJ_PROP: - sprintf(buf, "%s prop", "TEMP"); + sprintf(buf, "%s prop", Props[Objects[objh->objnum].instance]->prop_name); break; default: sprintf(buf, "object num=%d sig=%d type=%d instance=%d", objh->objnum, objh->sig, objh->objp()->type, objh->objp()->instance); diff --git a/qtfred/src/mission/management.cpp b/qtfred/src/mission/management.cpp index 45b935cf8c2..6db702bed5e 100644 --- a/qtfred/src/mission/management.cpp +++ b/qtfred/src/mission/management.cpp @@ -219,8 +219,8 @@ initialize(const std::string& cfilepath, int argc, char* argv[], Editor* editor, listener(SubSystem::Ships); ship_init(); - //listener(subsystem::Props); - //prop_init(); + //listener(Subsystem::Props); + prop_init(); listener(SubSystem::TechroomIntel); techroom_intel_init();