Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a1d7a4f
First draft of prop object type
MjnMixael Jun 2, 2025
6000231
Add prop editor dialog in FRED
MjnMixael Jun 11, 2025
0ad1443
prop sexps
MjnMixael Jun 12, 2025
4edc1f9
lua table prop support
MjnMixael Jun 12, 2025
8923980
lab support
MjnMixael Jun 12, 2025
d26ba21
lua mission access to props
MjnMixael Jun 13, 2025
2aa4aed
prop collision support
Baezon Jun 14, 2025
cb79f37
use std::optional so the object->instance id is always valid
MjnMixael Jun 14, 2025
054dd65
allow props to block suns
MjnMixael Jun 15, 2025
d20e680
minor cleanup and remove arbitrary limit. Still limited to 500 objects.
MjnMixael Jun 15, 2025
d6c6eb9
Sort props by category
MjnMixael Jun 16, 2025
bfdeec4
cleanup
MjnMixael Jun 16, 2025
29fd122
implement collision hooks
MjnMixael Jun 16, 2025
6a5043e
parse prop flags and further ensure props are inited and closed corre…
MjnMixael Jun 17, 2025
cb0f650
Add Cyborg's multi suggestions
MjnMixael Jun 17, 2025
27720c7
fix missing header
MjnMixael Jun 17, 2025
204dc44
add props to empty slots if available
MjnMixael Jun 17, 2025
721bccb
add multi signature
MjnMixael Jun 18, 2025
2a78b9f
fix rebase issues and match collisions to the new multithread style
MjnMixael Jul 14, 2025
408404d
appease Clang the Conquerer
MjnMixael Jul 14, 2025
77eb9d0
save/parse mission prop classes by name
MjnMixael Jul 30, 2025
e870c72
Clean up comments and finish TODOs
MjnMixael Jul 30, 2025
81b205d
fix string errors
MjnMixael Jul 30, 2025
7a0a6ec
clang wants this to be static now
MjnMixael Jul 30, 2025
263fd03
address feedback
MjnMixael Oct 17, 2025
daa0857
address feedback
MjnMixael Jan 30, 2026
96a46e1
Use model num from prop info instead
MjnMixael Feb 10, 2026
b9962ba
merge prop and ship collision group sexps together
MjnMixael Feb 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions code/graphics/shadows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
68 changes: 66 additions & 2 deletions code/lab/dialogs/lab_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "ship/shiphit.h"
#include "weapon/weapon.h"
#include "mission/missionload.h"
#include "prop/prop.h"

using namespace ImGui;

Expand Down Expand Up @@ -92,6 +93,32 @@ void LabUi::build_weapon_subtype_list() const
}
}

void LabUi::build_prop_subtype_list()
{
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, propc.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")
Expand Down Expand Up @@ -171,7 +198,15 @@ void LabUi::build_object_list()
}
}

void LabUi::build_background_list() const
void LabUi::build_prop_list()
{
with_TreeNode("Prop Classes")
{
build_prop_subtype_list();
}
}

void LabUi::build_background_list()
{
SCP_vector<SCP_string> t_missions;

Expand Down Expand Up @@ -272,6 +307,8 @@ void LabUi::show_object_selector() const

build_weapon_list();

build_prop_list();

build_object_list();
}
}
Expand Down Expand Up @@ -418,7 +455,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);
Expand Down Expand Up @@ -1485,6 +1523,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<char*>(table_text.c_str()),
table_text.length(),
ImVec2(-FLT_MIN, GetTextLineHeight() * 16),
ImGuiInputTextFlags_ReadOnly);
}

with_CollapsingHeader("Object actions")
{
if (getLabManager()->isSafeForProps()) {
// No actions yet
}
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion code/lab/dialogs/lab_ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ class LabUi {
static void build_object_list();
static void build_asteroid_list();
static void build_debris_list();
void build_background_list() const;
static void build_prop_list();
static void build_prop_subtype_list();
static void build_background_list();
void show_render_options();
void show_object_options() const;
void show_object_selector() const;
Expand Down
122 changes: 122 additions & 0 deletions code/lab/dialogs/lab_ui_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +414 to +417
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're already doing a round 2 here:
Is it possible to consolidate this with the function above, as they basically do the same thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on where you want to draw the line. Right now, iirc, this matches the code structure that ships do. If I update this are you going to want me to update ships also?

Copy link
Contributor Author

@MjnMixael MjnMixael Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this, I'd rather not for now. This whole file could use consolidation and cleanup. However, this Props PR does a ton of changes already. I'd rather keep the change here as additive and later on someone can consolidate get_weapon_table_text, get_ship_table_text, get_asteroid_table_text, and get_prop_table_text as much as makes sense.

SCP_vector<SCP_string> 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.c_str())) {
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.c_str())) {
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);
Expand Down
3 changes: 3 additions & 0 deletions code/lab/dialogs/lab_ui_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "asteroid/asteroid.h"
#include "ship/ship.h"
#include "prop/prop.h"

SCP_map<int, SCP_string> get_docking_point_map(int model_index);

Expand All @@ -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();
1 change: 1 addition & 0 deletions code/lab/labv2.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ enum class LabMode {
Asteroid,
Ship,
Weapon,
Prop,
None
};

Expand Down
8 changes: 8 additions & 0 deletions code/lab/manager/lab_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -45,6 +46,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();
Expand Down Expand Up @@ -748,6 +750,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 (isSafeForProps()) {
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;
Expand Down
12 changes: 10 additions & 2 deletions code/lab/manager/lab_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -72,6 +73,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();

Expand Down Expand Up @@ -103,11 +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 isSafeForWeapons() {
bool isSafeForProps() const {
return CurrentMode == LabMode::Prop && CurrentObject != -1 && Objects[CurrentObject].type == OBJ_PROP;
}

bool isSafeForWeapons() const {
bool valid = CurrentObject != -1 && (Objects[CurrentObject].type == OBJ_WEAPON || Objects[CurrentObject].type == OBJ_BEAM);
return CurrentMode == LabMode::Weapon && valid;
}
Expand Down
16 changes: 16 additions & 0 deletions code/lab/renderer/lab_renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -114,6 +115,21 @@ void LabRenderer::renderModel(float frametime) {
}
}

if (obj->type == OBJ_PROP) {
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);
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) {
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]);
Expand Down
Loading