Skip to content

Commit e1810ed

Browse files
authored
Allow curves to be tabled inline (scp-fs2open#6674)
* Add inline curve definitions * Fix comma parsing * Fix cast * Incorporate feedback * Fix parsing call
1 parent dd62bc2 commit e1810ed

9 files changed

Lines changed: 95 additions & 98 deletions

File tree

code/math/curve.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,65 @@ int curve_get_by_name(const SCP_string& in_name) {
99
return find_item_with_field(Curves, &Curve::name, in_name);
1010
}
1111

12+
static int curve_inline_def(){
13+
//The curve needs a unique identifier. Build this with semicolon + filename + semicolon + character in file
14+
SCP_string curve_name = ';' + SCP_string(Current_filename) + ';' + std::to_string(Mp - Parse_text);
15+
16+
int resulting_curve = static_cast<int>(Curves.size());
17+
Curve& new_curve = Curves.emplace_back(curve_name);
18+
19+
do {
20+
curve_keyframe& new_keyframe = new_curve.keyframes.emplace_back();
21+
stuff_float(&new_keyframe.pos.x);
22+
stuff_float(&new_keyframe.pos.y);
23+
required_string(")");
24+
25+
bool found_interpolation = true;
26+
int interpolation_mode = optional_string_one_of(4, "--", "-|", "-/", "/-");
27+
switch (interpolation_mode) {
28+
case 0:
29+
new_keyframe.interp_func = CurveInterpFunction::Linear;
30+
break;
31+
case 1:
32+
new_keyframe.interp_func = CurveInterpFunction::Constant;
33+
break;
34+
case 2:
35+
new_keyframe.interp_func = CurveInterpFunction::Polynomial;
36+
new_keyframe.param1 = 2.0f;
37+
new_keyframe.param2 = 1.0f;
38+
break;
39+
case 3:
40+
new_keyframe.interp_func = CurveInterpFunction::Polynomial;
41+
new_keyframe.param1 = 2.0f;
42+
new_keyframe.param2 = -1.0f;
43+
break;
44+
default:
45+
new_keyframe.interp_func = CurveInterpFunction::Linear;
46+
found_interpolation = false;
47+
break;
48+
}
49+
if (!found_interpolation)
50+
break;
51+
} while (optional_string("("));
52+
53+
return resulting_curve;
54+
}
55+
56+
int curve_parse(const char *err_msg) {
57+
if(optional_string("(")) {
58+
return curve_inline_def();
59+
}
60+
else {
61+
SCP_string curve_name;
62+
stuff_string(curve_name, F_NAME);
63+
int curve_id = curve_get_by_name(curve_name);
64+
if (curve_id < 0) {
65+
error_display(0, "Curve %s not found!%s", curve_name.c_str(), err_msg);
66+
}
67+
return curve_id;
68+
}
69+
}
70+
1271
void parse_curve_table(const char* filename) {
1372
try
1473
{

code/math/curve.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,6 @@ public :
4949
extern SCP_vector<Curve> Curves;
5050

5151
extern int curve_get_by_name(const SCP_string& in_name);
52+
extern int curve_parse(const char* err_msg);
5253
extern void curves_init();
5354

code/model/animation/modelanimation.cpp

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,12 +1474,8 @@ namespace animation {
14741474

14751475
std::optional<Curve> curve = std::nullopt;
14761476
if (optional_string("+Curve:")) {
1477-
SCP_string curve_name;
1478-
stuff_string(curve_name, F_NAME);
1479-
int curve_id = curve_get_by_name(curve_name);
1480-
if (curve_id < 0)
1481-
error_display(0, "Unknown curve specified! The driver will not use a curve.");
1482-
else
1477+
int curve_id = curve_parse(" The driver will not use a curve.");
1478+
if (curve_id >= 0)
14831479
curve = Curves[curve_id];
14841480
}
14851481

@@ -1503,12 +1499,8 @@ namespace animation {
15031499

15041500
std::optional<Curve> curve = std::nullopt;
15051501
if (optional_string("+Curve:")) {
1506-
SCP_string curve_name;
1507-
stuff_string(curve_name, F_NAME);
1508-
int curve_id = curve_get_by_name(curve_name);
1509-
if (curve_id < 0)
1510-
error_display(0, "Unknown curve specified! The driver will not use a curve.");
1511-
else
1502+
int curve_id = curve_parse(" The driver will not use a curve.");
1503+
if (curve_id >= 0)
15121504
curve = Curves[curve_id];
15131505
}
15141506

@@ -1533,12 +1525,8 @@ namespace animation {
15331525

15341526
std::optional<Curve> curve = std::nullopt;
15351527
if (optional_string("+Curve:")) {
1536-
SCP_string curve_name;
1537-
stuff_string(curve_name, F_NAME);
1538-
int curve_id = curve_get_by_name(curve_name);
1539-
if (curve_id < 0)
1540-
error_display(0, "Unknown curve specified! The driver will not use a curve.");
1541-
else
1528+
int curve_id = curve_parse(" The driver will not use a curve.");
1529+
if (curve_id >= 0)
15421530
curve = Curves[curve_id];
15431531
}
15441532

code/parse/parselo.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -756,17 +756,19 @@ int optional_string_fred(const char *pstr, const char *end, const char *end2)
756756
* @details Advances the Mp until a string is found or exceeds RS_MAX_TRIES. Once a string is found, Mp is located at
757757
* the start of the found string.
758758
*/
759-
int required_string_either(const char *str1, const char *str2)
759+
int required_string_either(const char *str1, const char *str2, bool advance)
760760
{
761761
ignore_white_space();
762762

763763
for (int count = 0; count < RS_MAX_TRIES; ++count) {
764764
if (strnicmp(str1, Mp, strlen(str1)) == 0) {
765-
// Mp += strlen(str1);
765+
if (advance)
766+
Mp += strlen(str1);
766767
diag_printf("Found required string [%s]\n", token_found = str1);
767768
return 0;
768769
} else if (strnicmp(str2, Mp, strlen(str2)) == 0) {
769-
// Mp += strlen(str2);
770+
if (advance)
771+
Mp += strlen(str2);
770772
diag_printf("Found required string [%s]\n", token_found = str2);
771773
return 1;
772774
}

code/parse/parselo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ extern int optional_string_one_of(int arg_count, ...);
128128

129129
// required
130130
extern int required_string(const char *pstr);
131-
extern int required_string_either(const char *str1, const char *str2);
131+
extern int required_string_either(const char *str1, const char *str2, bool advance = false);
132132
extern int required_string_one_of(int arg_count, ...);
133133

134134
// stuff

code/particle/ParticleParse.cpp

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -265,14 +265,8 @@ namespace particle {
265265
required_string(output == 0 ? "Radius" : "Velocity");
266266
int& curve = output == 0 ? effect.m_size_lifetime_curve : effect.m_vel_lifetime_curve;
267267

268-
required_string("+Curve Name:");
269-
SCP_string buf;
270-
stuff_string(buf, F_NAME);
271-
curve = curve_get_by_name(buf);
272-
273-
if (curve < 0) {
274-
error_display(0, "Could not find curve '%s'", buf.c_str());
275-
}
268+
required_string_either("+Curve Name:", "+Curve:", true);
269+
curve = curve_parse(" Unknown curve requested for modular curves!");
276270
}
277271
}
278272

@@ -344,25 +338,13 @@ namespace particle {
344338

345339
static void parseSizeLifetimeCurve(ParticleEffect &effect) {
346340
if (optional_string("+Size over lifetime curve:")) {
347-
SCP_string buf;
348-
stuff_string(buf, F_NAME);
349-
effect.m_size_lifetime_curve = curve_get_by_name(buf);
350-
351-
if (effect.m_size_lifetime_curve < 0) {
352-
error_display(0, "Could not find curve '%s'", buf.c_str());
353-
}
341+
effect.m_size_lifetime_curve = curve_parse("");
354342
}
355343
}
356344

357345
static void parseVelocityLifetimeCurve(ParticleEffect &effect) {
358346
if (optional_string("+Velocity scalar over lifetime curve:")) {
359-
SCP_string buf;
360-
stuff_string(buf, F_NAME);
361-
effect.m_vel_lifetime_curve = curve_get_by_name(buf);
362-
363-
if (effect.m_vel_lifetime_curve < 0) {
364-
error_display(0, "Could not find curve '%s'", buf.c_str());
365-
}
347+
effect.m_vel_lifetime_curve = curve_parse("");
366348
}
367349
}
368350

code/utils/RandomRange.h

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -469,17 +469,14 @@ using CurveFloatRange = RandomRange<float, CurveNumberDistribution, std::minstd_
469469
inline CurveFloatRange parseCurveFloatRange(float min = std::numeric_limits<float>::lowest()/2.1f, float max = std::numeric_limits<float>::max()/2.1f) {
470470
CurveNumberDistribution::param_type curve_params;
471471

472-
SCP_string curve_name;
473-
stuff_string(curve_name, F_NAME);
474-
curve_params.curve = curve_get_by_name(curve_name);
472+
curve_params.curve = curve_parse(" Random distributions using this curve will return 0.");
475473

476474
optional_string("(");
477475
stuff_float_optional(&curve_params.min);
478476
stuff_float_optional(&curve_params.max);
479477
optional_string(")");
480478

481479
if (curve_params.curve < 0) {
482-
error_display(0, "Curve %s not found! Random distributions using this curve will return 0.", curve_name.c_str());
483480
return CurveFloatRange{curve_params};
484481
} else {
485482
bool y_below_0 = false;
@@ -496,21 +493,21 @@ inline CurveFloatRange parseCurveFloatRange(float min = std::numeric_limits<floa
496493

497494
if (y_below_0) {
498495
error_display(0,
499-
"Curve %s goes below zero along the Y axis. Random distributions using this curve will return 0.", curve_name.c_str());
496+
"Curve %s goes below zero along the Y axis. Random distributions using this curve will return 0.", Curves[curve_params.curve].name.c_str());
500497
curve_params.curve = -1;
501498
return CurveFloatRange{curve_params};
502499
}
503500
if (no_y_above_0) {
504501
error_display(0,
505-
"Curve %s has no values above zero along the Y axis. Random distributions using this curve will return 0.", curve_name.c_str());
502+
"Curve %s has no values above zero along the Y axis. Random distributions using this curve will return 0.", Curves[curve_params.curve].name.c_str());
506503
curve_params.curve = -1;
507504
return CurveFloatRange{curve_params};
508505
}
509506
}
510507

511508
if (fl_is_nan(curve_params.min) || fl_is_nan(curve_params.max)) {
512509
if (!fl_is_nan(curve_params.min)) {
513-
error_display(0, "Minimum value but no maximum value specified for curve distribution %s!", curve_name.c_str());
510+
error_display(0, "Minimum value but no maximum value specified for curve distribution %s!", Curves[curve_params.curve].name.c_str());
514511
}
515512
curve_params.min = Curves[curve_params.curve].keyframes.front().pos.x;
516513
curve_params.max = Curves[curve_params.curve].keyframes.back().pos.x;

code/utils/modular_curves.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ struct modular_curves_submember_input {
105105
template<typename result_type>
106106
static inline float number_to_float(const result_type& number) {
107107
// if constexpr(std::is_same_v<std::decay_t<result_type>, fix>) // TODO: Make sure we can differentiate fixes from ints.
108-
// return f2fl(number);
108+
// return f2fl(number);
109109
// else
110110
if constexpr(std::is_integral_v<std::decay_t<result_type>>)
111111
return static_cast<float>(number);
@@ -338,12 +338,10 @@ struct modular_curves_definition {
338338

339339
modular_curves_entry curve_entry;
340340

341-
required_string("+Curve Name:");
342-
SCP_string curve;
343-
stuff_string(curve, F_NAME);
344-
curve_entry.curve_idx = curve_get_by_name(curve);
341+
required_string_either("+Curve Name:", "+Curve:", true);
342+
curve_entry.curve_idx = curve_parse(" Unknown curve requested for modular curves!");
345343
if (curve_entry.curve_idx < 0){
346-
error_display(1, "Unknown curve %s requested for modular curves!", curve.c_str());
344+
error_display(1, "Unknown curve requested for modular curves!");
347345
}
348346

349347
if (optional_string("+Random Scaling Factor:")) {

code/weapon/weapons.cpp

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -819,11 +819,7 @@ void parse_shockwave_info(shockwave_create_info *sci, const char *pre_char)
819819

820820
sprintf(buf, "%sShockwave Radius Multiplier over Lifetime Curve:", pre_char);
821821
if (optional_string(buf.c_str())) {
822-
SCP_string curve_name;
823-
stuff_string(curve_name, F_NAME);
824-
sci->radius_curve_idx = curve_get_by_name(curve_name);
825-
if (sci->radius_curve_idx < 0)
826-
Warning(LOCATION, "Unrecognized shockwave radius curve '%s'", curve_name.c_str());
822+
sci->radius_curve_idx = curve_parse(" Shockwave will not use a curve.");
827823
}
828824

829825
sprintf(buf, "%sShockwave Speed:", pre_char);
@@ -1233,9 +1229,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
12331229
}
12341230

12351231
if (optional_string("@Laser Length Multiplier over Lifetime Curve:")) {
1236-
SCP_string curve_name;
1237-
stuff_string(curve_name, F_NAME);
1238-
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::LASER_LENGTH_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
1232+
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::LASER_LENGTH_MULT, modular_curves_entry{curve_parse(" Laser Length will not be modified.")});
12391233
}
12401234

12411235
if(optional_string("@Laser Head Radius:")) {
@@ -1247,9 +1241,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
12471241
}
12481242

12491243
if (optional_string("@Laser Radius Multiplier over Lifetime Curve:")) {
1250-
SCP_string curve_name;
1251-
stuff_string(curve_name, F_NAME);
1252-
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::LASER_RADIUS_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
1244+
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::LASER_RADIUS_MULT, modular_curves_entry{curve_parse(" Laser Radius will not be modified.")});
12531245
}
12541246
if (optional_string("@Laser Glow Length Scale:")) {
12551247
stuff_float(&wip->laser_glow_length_scale);
@@ -1270,9 +1262,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
12701262
parse_optional_float_into("@Laser Min Pixel Size:", &wip->laser_min_pixel_size);
12711263

12721264
if (optional_string("@Laser Opacity over Lifetime Curve:")) {
1273-
SCP_string curve_name;
1274-
stuff_string(curve_name, F_NAME);
1275-
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::LASER_ALPHA_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
1265+
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::LASER_ALPHA_MULT, modular_curves_entry{curve_parse(" Laser Opacity will not be modified.")});
12761266
}
12771267

12781268
if (parse_optional_color3i_into("$Light color:", &wip->light_color)) {
@@ -1363,13 +1353,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
13631353

13641354
if (optional_string("$Damage Multiplier over Lifetime Curve:")) {
13651355
//Legacy table. Just populates the modular curve set!
1366-
SCP_string curve_name;
1367-
stuff_string(curve_name, F_NAME);
1368-
int curve = curve_get_by_name(curve_name);
1369-
if (curve < 0)
1370-
Warning(LOCATION, "Unrecognized damage curve '%s' for weapon %s", curve_name.c_str(), wip->name);
1371-
1372-
damage_mult_curve.emplace(modular_curves_entry{curve});
1356+
damage_mult_curve.emplace(modular_curves_entry{curve_parse(" Weapon Damage will not be modified.")});
13731357
}
13741358

13751359
if(optional_string("$Damage Type:")) {
@@ -1601,9 +1585,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
16011585
}
16021586

16031587
if (optional_string("+Turn Rate Multiplier over Lifetime Curve:")) {
1604-
SCP_string curve_name;
1605-
stuff_string(curve_name, F_NAME);
1606-
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::TURN_RATE_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
1588+
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::TURN_RATE_MULT, modular_curves_entry{curve_parse(" Turn Rate will not be modified.")});
16071589
}
16081590

16091591
if(optional_string("+View Cone:")) {
@@ -1660,9 +1642,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
16601642
}
16611643

16621644
if (optional_string("+Turn Rate Multiplier over Lifetime Curve:")) {
1663-
SCP_string curve_name;
1664-
stuff_string(curve_name, F_NAME);
1665-
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::TURN_RATE_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
1645+
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::TURN_RATE_MULT, modular_curves_entry{curve_parse(" Turn Rate will not be modified.")});
16661646
}
16671647

16681648
if(optional_string("+View Cone:")) {
@@ -2989,9 +2969,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
29892969
}
29902970

29912971
if (optional_string("+Opacity over Lifetime Curve:")) {
2992-
SCP_string curve_name;
2993-
stuff_string(curve_name, F_NAME);
2994-
wip->beam_curves.add_curve("Beam Lifetime", weapon_info::BeamCurveOutputs::BEAM_ALPHA_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
2972+
wip->beam_curves.add_curve("Beam Lifetime", weapon_info::BeamCurveOutputs::BEAM_ALPHA_MULT, modular_curves_entry{curve_parse(" Beam Lifetime will not be modified.")});
29952973
}
29962974

29972975
// # of shots (only used for type D beams)
@@ -3289,13 +3267,9 @@ int parse_weapon(int subtype, bool replace, const char *filename)
32893267
}
32903268

32913269
if (optional_string("+Slash position over beam lifetime curve:")) {
3292-
SCP_string curve_name;
3293-
stuff_string(curve_name, F_NAME);
3294-
t5info->slash_pos_curve_idx = curve_get_by_name(curve_name);
3295-
if (t5info->slash_pos_curve_idx < 0)
3296-
Warning(LOCATION, "Unrecognized slash position curve '%s' for weapon %s", curve_name.c_str(), wip->name);
3270+
t5info->slash_pos_curve_idx = curve_parse(" Slash Position will not be modified.");
32973271
if (t5info->no_translate)
3298-
Warning(LOCATION, "Beam weapon %s has a slash position curve defined, but doesn't slash!", wip->name);
3272+
error_display(0, "Beam weapon %s has a slash position curve defined, but doesn't slash!", wip->name);
32993273
}
33003274

33013275
if (optional_string("+Orient Offsets to Target:")) {
@@ -3312,11 +3286,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
33123286
}
33133287

33143288
if (optional_string("+Rotation over beam lifetime curve:")) {
3315-
SCP_string curve_name;
3316-
stuff_string(curve_name, F_NAME);
3317-
t5info->rot_curve_idx = curve_get_by_name(curve_name);
3318-
if (t5info->rot_curve_idx < 0)
3319-
Warning(LOCATION, "Unrecognized rotation curve '%s' for weapon %s", curve_name.c_str(), wip->name);
3289+
t5info->rot_curve_idx = curve_parse(" Beam Rotation will not be modified.");
33203290
}
33213291

33223292
if (optional_string("+Continuous Rotation Axis:")) {

0 commit comments

Comments
 (0)