Skip to content

Commit 681e9b7

Browse files
authored
Qtfred misc ship editor fixes (#7402)
* fix hotkey not saving/loading * fix docking setup flow * fix all/none in flags list not updating ui * fix tri-state flag issues with multi selection * fix initial status checkboxes * fix no warp effect * more fixes * fix flag hiding * allow any flag to be skipped for player starts * another fix for no warp effect * smarter context menu * fixes for object orient editor
1 parent be6594d commit 681e9b7

14 files changed

Lines changed: 224 additions & 111 deletions

qtfred/src/mission/dialogs/ObjectOrientEditorDialogModel.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <globalincs/linklist.h>
55
#include <ship/ship.h>
66
#include <math/bitarray.h>
7+
#include <jumpnode/jumpnode.h>
8+
#include <prop/prop.h>
79

810
namespace fso::fred::dialogs {
911

@@ -44,8 +46,17 @@ void ObjectOrientEditorDialogModel::initializeData()
4446
_pointToObjectList.emplace_back(ObjectEntry(text, OBJ_INDEX(ptr)));
4547
break;
4648
}
49+
case OBJ_JUMP_NODE: {
50+
CJumpNode* jnp = jumpnode_get_by_objnum(OBJ_INDEX(ptr));
51+
if (jnp)
52+
_pointToObjectList.emplace_back(ObjectEntry(jnp->GetName(), OBJ_INDEX(ptr)));
53+
break;
54+
}
55+
case OBJ_PROP:
56+
if (Props[ptr->instance].has_value())
57+
_pointToObjectList.emplace_back(ObjectEntry(Props[ptr->instance]->prop_name, OBJ_INDEX(ptr)));
58+
break;
4759
case OBJ_POINT:
48-
case OBJ_JUMP_NODE:
4960
break;
5061
default:
5162
Assertion(false, "Unknown object type in Object Orient Dialog!"); // unknown object type

qtfred/src/mission/dialogs/ShipEditor/ShipEditorDialogModel.cpp

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,12 @@ ShipEditorDialogModel::ShipEditorDialogModel(QObject* parent, EditorViewport* vi
3131

3232
int ShipEditorDialogModel::tristate_set(int val, int cur_state)
3333
{
34-
if (val) {
35-
if (!cur_state) {
36-
return Qt::PartiallyChecked;
37-
}
38-
} else {
39-
if (cur_state) {
40-
return Qt::PartiallyChecked;
41-
}
42-
}
43-
if (cur_state == 1) {
44-
45-
return Qt::Checked;
46-
} else {
47-
return Qt::Unchecked;
48-
}
34+
if (cur_state == Qt::PartiallyChecked)
35+
return Qt::PartiallyChecked;
36+
bool cur_bool = (cur_state == Qt::Checked);
37+
if (static_cast<bool>(val) != cur_bool)
38+
return Qt::PartiallyChecked;
39+
return cur_state;
4940
}
5041
int ShipEditorDialogModel::getSingleShip() const
5142
{
@@ -290,7 +281,7 @@ void ShipEditorDialogModel::initializeData()
290281
if (base_player >= 0) {
291282
_m_ship_class = Ships[i].ship_info_index;
292283
_m_team = Ships[i].team;
293-
pship = (objp->type == OBJ_START) ? 1 : 0;
284+
pship = (objp->type == OBJ_START) ? Qt::Checked : Qt::Unchecked;
294285
base_player = -1;
295286
} else {
296287
if (Ships[i].ship_info_index != _m_ship_class) {

qtfred/src/mission/dialogs/ShipEditor/ShipFlagsDialogModel.cpp

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,22 @@
66
#include <object/object.h>
77

88
namespace fso::fred::dialogs {
9+
10+
// Flags that are not applicable to player start ships and should be hidden/skipped for them.
11+
// Add entries here to extend the list.
12+
static const Ship::Ship_Flags player_start_hidden_flags[] = {
13+
Ship::Ship_Flags::Reinforcement,
14+
Ship::Ship_Flags::Kill_before_mission,
15+
};
916
int ShipFlagsDialogModel::tristate_set(int val, int cur_state)
1017
{
11-
if (val) {
12-
if (!cur_state) {
13-
return CheckState::PartiallyChecked;
14-
}
15-
} else {
16-
if (cur_state) {
17-
return CheckState::PartiallyChecked;
18-
}
19-
}
20-
if (cur_state == 1) {
21-
22-
return CheckState::Checked;
23-
} else {
24-
return CheckState::Unchecked;
25-
}
18+
// cur_state uses Qt::CheckState encoding (0=Unchecked, 1=PartiallyChecked, 2=Checked)
19+
if (cur_state == Qt::PartiallyChecked)
20+
return Qt::PartiallyChecked;
21+
bool cur_bool = (cur_state == Qt::Checked);
22+
if (static_cast<bool>(val) != cur_bool)
23+
return Qt::PartiallyChecked;
24+
return cur_state;
2625
}
2726
std::pair<SCP_string, int>* ShipFlagsDialogModel::getFlag(const SCP_string& flag_name)
2827
{
@@ -81,12 +80,26 @@ void ShipFlagsDialogModel::update_ship(const int shipnum)
8180
ship* shipp = &Ships[shipnum];
8281
object* objp = &Objects[shipp->objnum];
8382
for (const auto& [name, checked] : flags) {
83+
// PartiallyChecked means mixed selection — leave each ship's flag as-is
84+
if (checked == Qt::PartiallyChecked)
85+
continue;
86+
const bool set = (checked == Qt::Checked);
8487
for (size_t i = 0; i < Num_Parse_ship_flags; ++i) {
8588
if (!stricmp(name.c_str(), Parse_ship_flags[i].name)) {
89+
90+
// Skip flags that aren't applicable to player start ships, even if they were shown and edited in multi edit mode
91+
if (objp->type == OBJ_START) {
92+
bool hidden = false;
93+
for (const auto& hf : player_start_hidden_flags) {
94+
if (Parse_ship_flags[i].def == hf) { hidden = true; break; }
95+
}
96+
if (hidden) continue;
97+
}
98+
8699
if (Parse_ship_flags[i].def == Ship::Ship_Flags::Reinforcement) {
87-
_editor->set_reinforcement(shipp->ship_name, checked);
100+
_editor->set_reinforcement(shipp->ship_name, set);
88101
} else {
89-
if (checked) {
102+
if (set) {
90103
shipp->flags.set(Parse_ship_flags[i].def);
91104
} else {
92105
shipp->flags.remove(Parse_ship_flags[i].def);
@@ -97,7 +110,7 @@ void ShipFlagsDialogModel::update_ship(const int shipnum)
97110
}
98111
for (size_t i = 0; i < Num_Parse_ship_ai_flags; ++i) {
99112
if (!stricmp(name.c_str(), Parse_ship_ai_flags[i].name)) {
100-
if (checked) {
113+
if (set) {
101114
Ai_info[shipp->ai_index].ai_flags.set(Parse_ship_ai_flags[i].def);
102115
} else {
103116
Ai_info[shipp->ai_index].ai_flags.remove(Parse_ship_ai_flags[i].def);
@@ -108,13 +121,13 @@ void ShipFlagsDialogModel::update_ship(const int shipnum)
108121
for (size_t i = 0; i < Num_Parse_ship_object_flags; ++i) {
109122
if (!stricmp(name.c_str(), Parse_ship_object_flags[i].name)) {
110123
if (Parse_ship_object_flags[i].def == Object::Object_Flags::Collides) {
111-
if (checked) {
124+
if (set) {
112125
objp->flags.remove(Parse_ship_object_flags[i].def);
113126
} else {
114127
objp->flags.set(Parse_ship_object_flags[i].def);
115128
}
116129
} else {
117-
if (checked) {
130+
if (set) {
118131
objp->flags.set(Parse_ship_object_flags[i].def);
119132
} else {
120133
objp->flags.remove(Parse_ship_object_flags[i].def);
@@ -220,6 +233,21 @@ void ShipFlagsDialogModel::initializeData()
220233

221234
first = 1;
222235

236+
bool all_player_ships = false;
237+
bool any_marked = false;
238+
for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
239+
if (((objp->type == OBJ_START) || (objp->type == OBJ_SHIP)) && objp->flags[Object::Object_Flags::Marked]) {
240+
if (!any_marked) {
241+
all_player_ships = true;
242+
any_marked = true;
243+
}
244+
if (objp->type != OBJ_START) {
245+
all_player_ships = false;
246+
break;
247+
}
248+
}
249+
}
250+
223251
objp = GET_FIRST(&obj_used_list);
224252
while (objp != END_OF_LIST(&obj_used_list)) {
225253
if ((objp->type == OBJ_START) || (objp->type == OBJ_SHIP)) {
@@ -246,13 +274,20 @@ void ShipFlagsDialogModel::initializeData()
246274
flagDef.def == Ship::Ship_Flags::Force_shields_on) {
247275
continue;
248276
}
277+
if (all_player_ships) {
278+
bool hidden = false;
279+
for (const auto& hf : player_start_hidden_flags) {
280+
if (flagDef.def == hf) { hidden = true; break; }
281+
}
282+
if (hidden) continue;
283+
}
249284
bool checked = shipp->flags[flagDef.def];
250-
flags.emplace_back(flagDef.name, checked);
285+
flags.emplace_back(flagDef.name, checked ? Qt::Checked : Qt::Unchecked);
251286
}
252287
for (size_t i = 0; i < Num_Parse_ship_ai_flags; i++) {
253288
auto flagDef = Parse_ship_ai_flags[i];
254289
bool checked = Ai_info[shipp->ai_index].ai_flags[flagDef.def];
255-
flags.emplace_back(flagDef.name, checked);
290+
flags.emplace_back(flagDef.name, checked ? Qt::Checked : Qt::Unchecked);
256291
}
257292
for (size_t i = 0; i < Num_Parse_ship_object_flags; i++) {
258293
auto flagDef = Parse_ship_object_flags[i];
@@ -262,8 +297,9 @@ void ShipFlagsDialogModel::initializeData()
262297
} else {
263298
checked = objp->flags[flagDef.def];
264299
}
265-
flags.emplace_back(flagDef.name, checked);
300+
flags.emplace_back(flagDef.name, checked ? Qt::Checked : Qt::Unchecked);
266301
}
302+
first = 0;
267303
} else {
268304
for (size_t i = 0; i < Num_Parse_ship_flags; i++) {
269305
auto flagDef = Parse_ship_flags[i];
@@ -281,6 +317,13 @@ void ShipFlagsDialogModel::initializeData()
281317
flagDef.def == Ship::Ship_Flags::Force_shields_on) {
282318
continue;
283319
}
320+
if (all_player_ships) {
321+
bool hidden = false;
322+
for (const auto& hf : player_start_hidden_flags) {
323+
if (flagDef.def == hf) { hidden = true; break; }
324+
}
325+
if (hidden) continue;
326+
}
284327
bool checked = shipp->flags[flagDef.def];
285328
getFlag(flagDef.name)->second = tristate_set(checked, getFlag(flagDef.name)->second);
286329
}

qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.cpp

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ void ShipInitialStatusDialogModel::initializeData(bool multi)
120120
Assert(m_ship >= 0);
121121
}
122122

123+
m_move_ships_when_undocking = _viewport->Move_ships_when_undocking;
124+
123125
// initialize dockpoint stuff
124126
if (!m_multi_edit) {
125127
num_dock_points = model_get_num_dock_points(Ship_info[Ships[m_ship].ship_info_index].model_num);
@@ -142,48 +144,48 @@ void ShipInitialStatusDialogModel::initializeData(bool multi)
142144
m_hull = static_cast<int>(Objects[_editor->currentObject].hull_strength);
143145
guardian_threshold = Ships[m_ship].ship_guardian_threshold;
144146
if (Objects[_editor->currentObject].flags[Object::Object_Flags::No_shields])
145-
m_has_shields = 0;
147+
m_has_shields = Qt::Unchecked;
146148
else
147-
m_has_shields = 1;
149+
m_has_shields = Qt::Checked;
148150

149151
if (Ships[m_ship].flags[Ship::Ship_Flags::Force_shields_on])
150-
m_force_shields = 1;
152+
m_force_shields = Qt::Checked;
151153
else
152-
m_force_shields = 0;
154+
m_force_shields = Qt::Unchecked;
153155

154156
if (Ships[m_ship].flags[Ship::Ship_Flags::Ship_locked])
155-
m_ship_locked = 1;
157+
m_ship_locked = Qt::Checked;
156158
else
157-
m_ship_locked = 0;
159+
m_ship_locked = Qt::Unchecked;
158160

159161
if (Ships[m_ship].flags[Ship::Ship_Flags::Weapons_locked])
160-
m_weapons_locked = 1;
162+
m_weapons_locked = Qt::Checked;
161163
else
162-
m_weapons_locked = 0;
164+
m_weapons_locked = Qt::Unchecked;
163165
// Lock primaries
164166
if (Ships[m_ship].flags[Ship::Ship_Flags::Primaries_locked]) {
165-
m_primaries_locked = 1;
167+
m_primaries_locked = Qt::Checked;
166168
} else {
167-
m_primaries_locked = 0;
169+
m_primaries_locked = Qt::Unchecked;
168170
}
169171
// Lock secondaries
170172
if (Ships[m_ship].flags[Ship::Ship_Flags::Secondaries_locked]) {
171-
m_secondaries_locked = 1;
173+
m_secondaries_locked = Qt::Checked;
172174
} else {
173-
m_secondaries_locked = 0;
175+
m_secondaries_locked = Qt::Unchecked;
174176
}
175177

176178
// Lock turrets
177179
if (Ships[m_ship].flags[Ship::Ship_Flags::Lock_all_turrets_initially]) {
178-
m_turrets_locked = 1;
180+
m_turrets_locked = Qt::Checked;
179181
} else {
180-
m_turrets_locked = 0;
182+
m_turrets_locked = Qt::Unchecked;
181183
}
182184

183185
if (Ships[m_ship].flags[Ship::Ship_Flags::Afterburner_locked]) {
184-
m_afterburner_locked = 1;
186+
m_afterburner_locked = Qt::Checked;
185187
} else {
186-
m_afterburner_locked = 0;
188+
m_afterburner_locked = Qt::Unchecked;
187189
}
188190

189191
if (m_multi_edit) {
@@ -199,11 +201,11 @@ void ShipInitialStatusDialogModel::initializeData(bool multi)
199201
hflag = 1;
200202
if (objp->flags[Object::Object_Flags::No_shields]) {
201203
if (m_has_shields)
202-
m_has_shields = 1;
204+
m_has_shields = Qt::PartiallyChecked;
203205

204206
} else {
205207
if (!m_has_shields)
206-
m_has_shields = 1;
208+
m_has_shields = Qt::PartiallyChecked;
207209
}
208210

209211
Assert((objp->type == OBJ_SHIP) || (objp->type == OBJ_START));
@@ -337,7 +339,7 @@ void ShipInitialStatusDialogModel::undock(object* objp1, object* objp2)
337339
ship_num = get_ship_from_obj(OBJ_INDEX(objp1));
338340
other_ship_num = get_ship_from_obj(OBJ_INDEX(objp2));
339341

340-
if (_viewport->Move_ships_when_undocking) {
342+
if (m_move_ships_when_undocking) {
341343
if (ship_class_compare(Ships[ship_num].ship_info_index, Ships[other_ship_num].ship_info_index) <= 0) {
342344
vm_vec_scale_add2(&objp2->pos,
343345
&v,
@@ -592,9 +594,9 @@ bool ShipInitialStatusDialogModel::apply()
592594

593595
modify(objp->hull_strength, (float)m_hull);
594596

595-
if (m_has_shields == 1) {
597+
if (m_has_shields == Qt::Checked) {
596598
objp->flags.remove(Object::Object_Flags::No_shields);
597-
} else if (m_has_shields == 0) {
599+
} else if (m_has_shields == Qt::Unchecked) {
598600
objp->flags.set(Object::Object_Flags::No_shields);
599601
}
600602
auto shipp = &Ships[get_ship_from_obj(objp)];
@@ -637,6 +639,15 @@ bool ShipInitialStatusDialogModel::apply()
637639

638640
void ShipInitialStatusDialogModel::reject() {}
639641

642+
bool ShipInitialStatusDialogModel::getMoveShipsWhenUndocking() const
643+
{
644+
return m_move_ships_when_undocking;
645+
}
646+
void ShipInitialStatusDialogModel::setMoveShipsWhenUndocking(bool value)
647+
{
648+
modify(m_move_ships_when_undocking, value);
649+
}
650+
640651
void ShipInitialStatusDialogModel::setVelocity(const int value)
641652
{
642653
modify(m_velocity, value);

qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class ShipInitialStatusDialogModel : public AbstractDialogModel {
6262
dock_evaluate_tree(object* objp, dock_function_info* infop, void (*function)(object*), ubyte* visited_bitstring);
6363
bool m_multi_edit;
6464
bool m_use_teams = false;
65+
bool m_move_ships_when_undocking = true;
6566

6667
public:
6768
ShipInitialStatusDialogModel(QObject* parent, EditorViewport* viewport, bool multi);
@@ -132,6 +133,9 @@ class ShipInitialStatusDialogModel : public AbstractDialogModel {
132133

133134
int getGuardian() const;
134135
void setGuardian(int);
136+
137+
bool getMoveShipsWhenUndocking() const;
138+
void setMoveShipsWhenUndocking(bool);
135139
};
136140

137141
/**

0 commit comments

Comments
 (0)