Files
fs2open.github.com/code/parse/sexp/LuaSEXP.cpp

835 lines
25 KiB
C++

//
//
#include "LuaSEXP.h"
#include "iff_defs/iff_defs.h"
#include "localization/localize.h"
#include "mission/missiongoals.h"
#include "mission/missionmessage.h"
#include "object/waypoint.h"
#include "parse/parselo.h"
#include "parse/sexp.h"
#include "parse/sexp/sexp_lookup.h"
#include "scripting/api/objs/enums.h"
#include "scripting/api/objs/event.h"
#include "scripting/api/objs/goal.h"
#include "scripting/api/objs/hudgauge.h"
#include "scripting/api/objs/message.h"
#include "scripting/api/objs/model.h"
#include "scripting/api/objs/oswpt.h"
#include "scripting/api/objs/sexpvar.h"
#include "scripting/api/objs/ship.h"
#include "scripting/api/objs/shipclass.h"
#include "scripting/api/objs/sound.h"
#include "scripting/api/objs/subsystem.h"
#include "scripting/api/objs/team.h"
#include "scripting/api/objs/waypoint.h"
#include "scripting/api/objs/weaponclass.h"
#include "scripting/api/objs/wing.h"
#include "scripting/scripting.h"
#include "ship/ship.h"
#include "utils/string_utils.h"
#include "weapon/weapon.h"
using namespace luacpp;
namespace sexp {
static SCP_unordered_map<SCP_string, int> parameter_type_mapping {
{ "boolean", OPF_BOOL },
{ "number", OPF_NUMBER },
{ "string", OPF_STRING },
{ "ship", OPF_SHIP },
{ "shipname", OPF_SHIP },
{ "team", OPF_IFF },
{ "waypointpath", OPF_WAYPOINT_PATH },
{ "waypoint", OPF_POINT },
{ "variable", OPF_VARIABLE_NAME },
{ "message", OPF_MESSAGE },
{ "wing", OPF_WING },
{ "shipclass", OPF_SHIP_CLASS_NAME },
{ "weaponclass", OPF_WEAPON_NAME },
{ "soundentry", OPF_GAME_SND },
{ "ship+waypoint",OPF_SHIP_POINT },
{ "ship+wing", OPF_SHIP_WING },
{ "ship+wing+team", OPF_SHIP_WING_WHOLETEAM },
{ "ship+wing+ship_on_team+waypoint", OPF_SHIP_WING_SHIPONTEAM_POINT },
{ "ship+wing+waypoint", OPF_SHIP_WING_POINT },
{ "ship+wing+waypoint+none", OPF_SHIP_WING_POINT_OR_NONE },
{ "subsystem", OPF_SUBSYSTEM },
{ "subsystemname", OPF_SUBSYSTEM },
{ "dockpoint", OPF_DOCKER_POINT },
{ "hudgauge", OPF_ANY_HUD_GAUGE },
{ "event", OPF_EVENT_NAME },
{ "goal", OPF_GOAL_NAME },
{ "child_enum", OPF_CHILD_LUA_ENUM },
{ "custom_string",OPF_MISSION_CUSTOM_STRING },
{ "enum", First_available_opf_id } };
// If a parameter requires a parent parameter then it must be listed here!
static SCP_vector<SCP_string> parent_parameter_required{"subsystem", "subsystemname", "dockpoint", "child_enum"};
std::pair<SCP_string, int> LuaSEXP::get_parameter_type(const SCP_string& name)
{
SCP_string copy = name;
SCP_tolower(copy);
auto iter = parameter_type_mapping.find(copy);
if (iter == parameter_type_mapping.end()) {
return std::pair<SCP_string, int>(copy, -1);
} else {
return std::pair<SCP_string, int>(copy, iter->second);
}
}
static SCP_unordered_map<SCP_string, int> return_type_mapping{{ "number", OPR_NUMBER },
{ "boolean", OPR_BOOL },
{ "nothing", OPR_NULL }, };
int LuaSEXP::get_return_type(const SCP_string& name)
{
SCP_string copy = name;
SCP_tolower(copy);
auto iter = return_type_mapping.find(copy);
if (iter == return_type_mapping.end()) {
return -1;
} else {
return iter->second;
}
}
LuaSEXP::LuaSEXP(const SCP_string& name) : DynamicSEXP(name) {
}
void LuaSEXP::initialize() {
// Nothing to do for this type
}
int LuaSEXP::getMinimumArguments() const {
return _min_args;
}
int LuaSEXP::getMaximumArguments() const {
return _max_args;
}
std::pair<SCP_string, int> LuaSEXP::getArgumentInternalType(int argnum) const {
if (argnum < 0) {
return std::pair<SCP_string, int>(SEXP_NONE_STRING, OPF_NONE);
}
if (argnum < (int) _argument_types.size()) {
// Normal, non variable argument types
return _argument_types[argnum];
}
// sanity check in case of bad table data
if (_varargs_type_pattern.empty()) {
Warning(LOCATION, "Not enough parameters specified for Lua SEXP %s!", getName().c_str());
return std::pair<SCP_string, int>(SEXP_NONE_STRING, OPF_NONE);
}
// We are in the variable argument types region
// First, adjust the argnum base index so that our variable index starts at 0
auto varargs_index = argnum - _argument_types.size();
// Then use modulo magic to bring the argument number into the right range
varargs_index = varargs_index % _varargs_type_pattern.size();
// And then use that to get the parameter type
return _varargs_type_pattern[varargs_index];
}
int LuaSEXP::getArgumentType(int argnum) const {
return getArgumentInternalType(argnum).second;
}
luacpp::LuaValue LuaSEXP::sexpToLua(int node, int argnum, int parent_node) const {
using namespace scripting::api;
auto argtype = getArgumentInternalType(argnum);
switch (argtype.second) {
case OPF_BOOL: {
auto value = is_sexp_true(node) != 0;
return LuaValue::createValue(_action.getLuaState(), value);
}
case OPF_NUMBER: {
bool is_nan, is_nan_forever;
auto res = eval_num(node, is_nan, is_nan_forever);
float value;
if (is_nan || is_nan_forever) {
value = std::numeric_limits<float>::quiet_NaN();
} else {
value = (float) res;
}
return LuaValue::createValue(_action.getLuaState(), value);
}
case OPF_VARIABLE_NAME: {
// Variable names work by getting the variable index from the text node
auto sexp_variable_index = sexp_get_variable_index(node);
// Add the variable to the parameter list as a SEXPVariable object handle
return LuaValue::createValue(_action.getLuaState(), l_SEXPVariable.Set(sexpvar_h(sexp_variable_index)));
}
case OPF_IFF: {
auto team_idx = iff_lookup(CTEXT(node));
return LuaValue::createValue(_action.getLuaState(), l_Team.Set(team_idx));
}
case OPF_WAYPOINT_PATH: {
waypoint_list *wp_list = find_matching_waypoint_list(CTEXT(node));
return LuaValue::createValue(_action.getLuaState(), l_WaypointList.Set(waypointlist_h(wp_list)));
}
// The following argument types are all strings
case OPF_SHIP: {
auto ship_entry = eval_ship(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 == "shipname") {
return LuaValue::createValue(_action.getLuaState(), ship_entry ? ship_entry->name : "");
}
if (!ship_entry || !ship_entry->has_objp()) {
// Name is invalid
return LuaValue::createValue(_action.getLuaState(), l_Ship.Set(object_h()));
}
auto objp = ship_entry->objp();
// The other SEXP code does not validate the object type so this should be safe
Assertion(objp->type == OBJ_SHIP,
"Ship '%s' was found in the Ships array but has a different object type in the Objects array. Get a coder!",
CTEXT(node));
return LuaValue::createValue(_action.getLuaState(), l_Ship.Set(object_h(objp)));
}
case OPF_MESSAGE: {
auto name = CTEXT(node);
auto idx = -1;
for (int i = Num_builtin_messages; i < (int) Messages.size(); i++)
{
if (!stricmp(Messages[i].name, name))
{
idx = i;
break;
}
}
return LuaValue::createValue(_action.getLuaState(), l_Message.Set(idx));
}
case OPF_WING: {
auto wingp = eval_wing(node);
int wingnum = WING_INDEX(wingp);
return LuaValue::createValue(_action.getLuaState(), l_Wing.Set(wingnum));
}
case OPF_SHIP_CLASS_NAME: {
auto name = CTEXT(node);
return LuaValue::createValue(_action.getLuaState(), l_Shipclass.Set(ship_info_lookup(name)));
}
case OPF_WEAPON_NAME: {
auto name = CTEXT(node);
return LuaValue::createValue(_action.getLuaState(), l_Weaponclass.Set(weapon_info_lookup(name)));
}
case OPF_GAME_SND: {
auto name = CTEXT(node);
return LuaValue::createValue(_action.getLuaState(), l_SoundEntry.Set(sound_entry_h(gamesnd_get_by_name(name))));
}
case OPF_STRING: {
auto text = CTEXT(node);
return LuaValue::createValue(_action.getLuaState(), text);
}
case OPF_POINT: {
auto wpt = find_matching_waypoint(CTEXT(node));
return LuaValue::createValue(_action.getLuaState(), l_Waypoint.Set(object_h(wpt->get_objnum())));
}
case OPF_SHIP_POINT:
case OPF_SHIP_WING:
case OPF_SHIP_WING_WHOLETEAM:
case OPF_SHIP_WING_SHIPONTEAM_POINT:
case OPF_SHIP_WING_POINT:
case OPF_SHIP_WING_POINT_OR_NONE: {
object_ship_wing_point_team oswpt;
eval_object_ship_wing_point_team(&oswpt, node);
return LuaValue::createValue(_action.getLuaState(), l_OSWPT.Set(oswpt));
}
case OPF_SUBSYSTEM: {
auto name = CTEXT(node);
// if this is a subsystemname type, we want the name of a valid subsystem but not the subsystem itself
if (argtype.first == "subsystemname") {
return LuaValue::createValue(_action.getLuaState(), name);
}
//Use the parent node to get the index of the parameter we should
//look at to find the parent object
int index = get_dynamic_parameter_index(_name, argnum);
if (index < 0)
error_display(1, "Expected to find a dynamic lua parent parameter for node %i in operator %s but found nothing!", argnum, _name.c_str());
int this_node = parent_node;
for (int i = 0; i <= index; i++) {
this_node = CDR(this_node);
}
auto ship_entry = eval_ship(this_node);
if (!ship_entry || !ship_entry->has_shipp()) {
// Name is invalid
return LuaValue::createValue(_action.getLuaState(), l_Ship.Set(object_h()));
}
ship_subsys* ss = ship_get_subsys(ship_entry->shipp(), name);
return LuaValue::createValue(_action.getLuaState(), l_Subsystem.Set(ship_subsys_h(ship_entry->objp(), ss)));
}
case OPF_DOCKER_POINT: {
auto name = CTEXT(node);
// Use the parent node to get the index of the parameter we should
// look at to find the parent object
int index = get_dynamic_parameter_index(_name, argnum);
if (index < 0)
error_display(1,
"Expected to find a dynamic lua parent parameter for node %i in operator %s but found nothing!",
argnum,
_name.c_str());
int this_node = parent_node;
for (int i = 0; i <= index; i++) {
this_node = CDR(this_node);
}
auto ship_entry = eval_ship(this_node);
if (!ship_entry || !ship_entry->has_shipp()) {
// Name is invalid
return LuaValue::createValue(_action.getLuaState(), l_Ship.Set(object_h()));
}
auto docker_pm = model_get(Ship_info[ship_entry->shipp()->ship_info_index].model_num);
auto dockindex = model_find_dock_name_index(Ship_info[ship_entry->shipp()->ship_info_index].model_num, name);
return LuaValue::createValue(_action.getLuaState(), l_Dockingbay.Set(dockingbay_h(docker_pm, dockindex)));
}
case OPF_ANY_HUD_GAUGE: {
auto name = CTEXT(node);
return LuaValue::createValue(_action.getLuaState(), l_HudGauge.Set(hud_get_gauge(name, true)));
}
case OPF_EVENT_NAME: {
auto name = CTEXT(node);
int i = mission_event_lookup(name);
return LuaValue::createValue(_action.getLuaState(), l_Event.Set(i));
}
case OPF_GOAL_NAME: {
auto name = CTEXT(node);
int i = mission_goal_lookup(name);
return LuaValue::createValue(_action.getLuaState(), l_Goal.Set(i));
}
case OPF_CHILD_LUA_ENUM: {
auto text = CTEXT(node);
return LuaValue::createValue(_action.getLuaState(), text);
}
case OPF_MISSION_CUSTOM_STRING: {
auto cs = get_custom_string_by_name(CTEXT(node));
auto table = luacpp::LuaTable::create(_action.getLuaState());
table.addValue("Name", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs->name));
table.addValue("Value", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs->value));
table.addValue("String", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs->text));
return table;
}
default:
if ((strcmp(argtype.first.c_str(), "enum")) == 0) {
auto text = CTEXT(node);
return LuaValue::createValue(_action.getLuaState(), text);
} else {
UNREACHABLE(
"Unhandled argument type! Someone added an argument type but didn't add handling code to execute().");
return LuaValue::createNil(_action.getLuaState());
}
}
}
bool LuaSEXP::maybeExtractSexpSpecialRetVal(const luacpp::LuaValue& value, int& sexp_retval) {
// see if this return value is actually a SEXP_ enum
if (value.getValueType() == ValueType::USERDATA) {
try {
using namespace scripting::api;
enum_h enumeration;
value.getValue(l_Enum.Get(&enumeration)); // this might throw a LuaException
switch (enumeration.index) {
case LE_SEXP_TRUE:
case LE_SEXP_FALSE:
case LE_SEXP_KNOWN_FALSE:
case LE_SEXP_KNOWN_TRUE:
case LE_SEXP_UNKNOWN:
case LE_SEXP_NAN:
case LE_SEXP_NAN_FOREVER:
case LE_SEXP_CANT_EVAL:
sexp_retval = *enumeration.value;
return true;
default:
break;
}
} catch (const LuaException& le) {
// just ignore the exception because we follow a different code path if the conversion failed
SCP_UNUSED(le);
}
}
return false;
}
int LuaSEXP::getSexpReturnValue(const LuaValueList& retVals) const {
switch (_return_type) {
case OPR_NUMBER:
if (retVals.size() != 1) {
Warning(LOCATION,
"Wrong number of return values for Lua SEXP '%s'! Expected 1, got " SIZE_T_ARG ".",
_name.c_str(),
retVals.size());
return 0;
} else if (retVals[0].getValueType() != ValueType::NUMBER) {
int sexp_retval;
if (maybeExtractSexpSpecialRetVal(retVals[0], sexp_retval)) {
return sexp_retval;
}
Warning(LOCATION, "Wrong return type detected for Lua SEXP '%s', expected a number.", _name.c_str());
return 0;
} else {
return retVals[0].getValue<int>();
}
case OPR_BOOL:
if (retVals.size() != 1) {
Warning(LOCATION,
"Wrong number of return values for Lua SEXP '%s'! Expected 1, got " SIZE_T_ARG ".",
_name.c_str(),
retVals.size());
return SEXP_FALSE;
} else if (retVals[0].getValueType() != ValueType::BOOLEAN) {
int sexp_retval;
if (maybeExtractSexpSpecialRetVal(retVals[0], sexp_retval)) {
return sexp_retval;
}
Warning(LOCATION, "Wrong return type detected for Lua SEXP '%s', expected a boolean.", _name.c_str());
return SEXP_FALSE;
} else {
return retVals[0].getValue<bool>() ? SEXP_TRUE : SEXP_FALSE;
}
case OPR_NULL:
if (retVals.size() != 0) {
Warning(LOCATION,
"Wrong number of return values for Lua SEXP '%s'! Expected 0, got " SIZE_T_ARG ".",
_name.c_str(),
retVals.size());
}
return SEXP_TRUE;
default:
return SEXP_TRUE;
}
}
luacpp::LuaValueList LuaSEXP::getSEXPArgumentList(int node, int parent_node) const {
LuaValueList luaParameters;
// We need to adapt how we handle parameters based on their type. We use this variable to keep track of which parameter
// we are currently looking at
int argnum = 0;
while (node != -1) {
if (argnum < (int)_argument_types.size()) {
// This is a parameter in the normal list so we add it to the normal parameter list
luaParameters.push_back(sexpToLua(node, argnum, parent_node));
node = CDR(node);
++argnum;
}
else {
// The varargs part is handled in chunks so that scripts can use the data more easily
// Every repeat pattern instance is put into its own table
LuaTable varargs_part = LuaTable::create(_action.getLuaState());
// Iterate over all parameters in this varargs pattern and put them all in the table
// If we reach the end of the parameter list inside this for loop we just exit since all parameters are optional
for (auto i = 0; i < (int)_varargs_type_pattern.size() && node != -1; ++i) {
// i + 1 since lua arrays are 1-indexed
varargs_part.addValue(i + 1, sexpToLua(node, argnum, parent_node));
node = CDR(node);
++argnum;
}
luaParameters.push_back(varargs_part);
}
}
return luaParameters;
}
int LuaSEXP::execute(int node, int parent_node) {
if (!_action.isValid()) {
Error(LOCATION,
"Lua SEXP %s called without a valid action function! A script probably failed to set the action for some reason.", _name.c_str());
return SEXP_CANT_EVAL;
}
LuaValueList luaParameters = getSEXPArgumentList(node, parent_node);
// All parameters are now in LuaValues, time to call our function
try {
auto retVals = _action.call(Script_system.GetLuaSession(), luaParameters);
return getSexpReturnValue(retVals);
} catch(const LuaException&) {
// This is only caused by an error in the function but those are already handled by our error function
// These values are close to what the existing SEXP code does if errors occur while processing a SEXP
switch(_return_type) {
case OPR_NUMBER:
case OPR_BOOL:
return SEXP_NAN;
case OPR_NULL:
return SEXP_TRUE;
default:
return SEXP_NAN;
}
}
}
int LuaSEXP::getReturnType() {
return _return_type;
}
int LuaSEXP::getSubcategory() {
return _subcategory;
}
int LuaSEXP::getCategory() {
return _category;
}
bool LuaSEXP::parseCheckEndOfDescription() {
// Since we're stuffing strings, this is the best way to "unstuff" a string
// if we determine we're finished with the description - and we also want
// to preserve whitespace (which the parser eats) while building the description
pause_parse();
// look for any token that can follow $Description
auto possible_tokens =
{
"$Operator:",
"#End",
"$Repeat",
"$Parameter:"
};
bool found = false;
for (auto token : possible_tokens)
{
if (optional_string(token))
{
found = true;
break;
}
}
unpause_parse();
return found;
}
void LuaSEXP::parseTable() {
required_string("$Category:");
SCP_string category;
stuff_string(category, F_NAME);
_category = get_category(category);
if (_category == OP_CATEGORY_NONE) {
// Unknown category so we need to add this one
_category = sexp::add_category(category);
}
if (optional_string("$Subcategory:")) {
SCP_string subcategory;
stuff_string(subcategory, F_NAME);
_subcategory = get_subcategory(subcategory, _category);
if (_subcategory == OP_SUBCATEGORY_NONE) {
// Unknown subcategory so we need to add this one
_subcategory = sexp::add_subcategory(_category, subcategory);
}
} else {
_subcategory = OP_SUBCATEGORY_NONE;
}
required_string("$Minimum Arguments:");
stuff_int(&_min_args);
if (_min_args < 0) {
error_display(0, "Minimum argument number must be at least 0! Got %d.", _min_args);
_min_args = 0;
}
if (optional_string("$Maximum Arguments:")) {
stuff_int(&_max_args);
if (_max_args < 0) {
error_display(0, "Maximum argument number must be at least 0! Got %d.", _max_args);
_max_args = 0;
}
} else {
_max_args = INT_MAX;
}
if (_max_args < _min_args) {
error_display(0, "Maximum argument number must be greater or equal to minimum number of arguments!");
std::swap(_min_args, _max_args);
}
if (optional_string("$Return Type:")) {
SCP_string type;
stuff_string(type, F_NAME);
_return_type = get_return_type(type);
if (_return_type < 0) {
error_display(0, "Unknown return type '%s'!", type.c_str());
_return_type = OPR_NULL;
}
} else {
_return_type = OPR_NULL;
}
SCP_stringstream help_text;
help_text << _name << "\r\n";
required_string("$Description:");
SCP_string description, extra;
stuff_string(description, F_NAME);
while (!parseCheckEndOfDescription()) {
while (skip_eoln()) {
description += "\r\n";
}
stuff_string(extra, F_NAME);
description += extra;
}
help_text << "\t" << description << "\r\n\r\n";
// argument string
help_text << "Takes ";
if (_max_args == INT_MAX) {
help_text << _min_args << " or more arguments:";
} else {
if (_max_args == 0) {
help_text << "no arguments.";
} else if (_min_args == _max_args) {
help_text << _min_args << " argument";
if (_min_args > 1) {
help_text << "s";
}
help_text << ":";
} else {
help_text << _min_args << " to " << _max_args << " arguments:";
}
}
help_text << "\r\n";
bool variable_arg_part = false;
if (optional_string("$Repeat")) {
help_text << "Rest: (The following pattern repeats)\r\n";
variable_arg_part = true;
}
int param_index = 0;
while (optional_string("$Parameter:")) {
required_string("+Description:");
SCP_string param_desc;
stuff_string(param_desc, F_NAME);
if (variable_arg_part) {
help_text << "\t" << _varargs_type_pattern.size() + 1;
} else {
help_text << "\t" << _argument_types.size() + 1;
}
help_text << ": " << param_desc << "\r\n";
required_string("+Type:");
SCP_string type_str;
stuff_string(type_str, F_NAME);
auto type = get_parameter_type(type_str);
if (type.second < 0) {
error_display(0, "Unknown parameter type '%s'!", type_str.c_str());
type = get_parameter_type("string");
}
if ((strcmp(type.first.c_str(), "enum")) == 0) {
required_string("+Enum Name:");
SCP_string enum_name;
stuff_string(enum_name, F_NAME);
dynamic_sexp_enum_list thisList;
int list_position = get_dynamic_enum_position(enum_name);
bool new_enum = false;
if (list_position >= 0) {
type.second = list_position + (int)First_available_opf_id;
//Do this just in case we're going to use this list as a template
thisList.name = Dynamic_enums[list_position].name;
for (const SCP_string& enum_item : Dynamic_enums[list_position].list) {
thisList.list.push_back(enum_item);
}
} else {
new_enum = true;
thisList.name = enum_name;
}
while (optional_string("+Enum:")) {
new_enum = true;
//If we're making a new Enum based off another one, let's give it a unique name
if (list_position >= 0) {
SCP_string newName;
newName = enum_name;
newName.append(std::to_string(list_position));
thisList.name = newName;
}
SCP_string item;
stuff_string(item, F_NAME);
// These characters may not appear in an Enum item
constexpr const char* ENUM_INVALID_CHARS = "()\"'\\/";
if (std::strpbrk(item.c_str(), ENUM_INVALID_CHARS) != nullptr) {
error_display(0, "ENUM item '%s' cannot include these characters [(,),\",',\\,/]. Skipping!\n", item.c_str());
// Skip the invalid entry
continue;
}
if (item.length() >= NAME_LENGTH) {
error_display(0, "Enum item '%s' is longer than %i characters. Truncating!\n", item.c_str(), NAME_LENGTH);
item.resize(NAME_LENGTH - 1);
}
bool skip = false;
// Case insensitive check if the item already exists in the list
for (int i = 0; i < (int)thisList.list.size(); i++) {
if (lcase_equal(item, thisList.list[i])) {
error_display(0, "Enum item '%s' already exists in list %s. Skipping!\n", item.c_str(), thisList.name.c_str());
skip = true;
break;
}
}
if (skip)
continue;
thisList.list.push_back(item);
}
if (thisList.list.size() == 0) {
mprintf(("Parsed empty enum list '%s'. Adding <none>.\n", thisList.name.c_str()));
thisList.list.push_back("<none>");
}
if (new_enum) {
type.second = increment_enum_list_id();
Dynamic_enums.push_back(std::move(thisList));
} else {
// Not an error but large mods may lose track of their enum names and I thought this would be helpful -Mjn
mprintf(("Found previously existing Lua Enum '%s'. Using that for sexp '%s'!\n",
enum_name.c_str(),
_name.c_str()));
}
}
bool parent_required = util::isStringOneOf(type.first, parent_parameter_required);
// parse this even if this parameter doesn't need a parent, and we'll validate that next
int parent_param_index = -1;
if (optional_string("+Parent Parameter Index:")) {
stuff_int(&parent_param_index);
parent_param_index--;
}
if (parent_param_index >= (param_index + 1)) {
error_display(1,
"Parent Parameter Index '%i' cannot be greater or equal to %i (the current parameter index)!\n",
parent_param_index,
param_index + 1);
}
if (parent_required)
{
std::pair<int, int> param_map(param_index, parent_param_index);
// check if this operator already has an entry for dynamic parameters
int dyn_index = -1;
for (int i = 0; i < (int)Dynamic_parameters.size(); i++) {
if (lcase_equal(Dynamic_parameters[i].operator_name, _name)) {
dyn_index = i;
}
}
if (dyn_index >= 0) {
Dynamic_parameters[dyn_index].parameter_map.push_back(param_map);
} else {
dynamic_sexp_parameter_list dyn_param;
dyn_param.operator_name = _name;
dyn_param.parameter_map.push_back(param_map);
Dynamic_parameters.push_back(dyn_param);
}
}
else if (parent_param_index >= 0)
{
SCP_string msg = "Parent Parameter Index field was specified in SEXP '";
msg += _name;
msg += "' for a parameter type '";
msg += type.first;
msg += "' that does not need a parent! Parameter types requiring parents are:";
for (const auto &param: parent_parameter_required)
{
msg += "\n\t";
msg += param;
}
error_display(1, "%s", msg.c_str());
}
if (type_str == "child_enum") {
if (optional_string("+Suffix:")) {
SCP_string suffix;
stuff_string(suffix, F_NAME);
Dynamic_enum_suffixes.push_back({_name, param_index, suffix});
}
}
if (variable_arg_part) {
_varargs_type_pattern.push_back(type);
} else {
_argument_types.push_back(type);
}
if (optional_string("$Repeat")) {
if (!variable_arg_part) {
help_text << "Rest: (The following pattern repeats)\r\n";
variable_arg_part = true;
} else {
error_display(0, "A second $Repeat has been encountered! Only one may appear in the parameter list.");
}
}
param_index++;
}
_help_text = help_text.str();
lcl_replace_stuff(_help_text, true);
}
void LuaSEXP::setAction(const luacpp::LuaFunction& action) {
Assertion(action.isValid(), "Invalid function handle supplied!");
_action = action;
}
luacpp::LuaFunction LuaSEXP::getAction() const {
return _action;
}
}