mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 04:49:39 -10:00
488 lines
14 KiB
C++
488 lines
14 KiB
C++
//
|
|
//
|
|
#include "LuaSEXP.h"
|
|
|
|
#include "iff_defs/iff_defs.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/message.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/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 "weapon/weapon.h"
|
|
|
|
using namespace luacpp;
|
|
|
|
namespace {
|
|
|
|
SCP_unordered_map<SCP_string, int> parameter_type_mapping{{ "boolean", OPF_BOOL },
|
|
{ "number", OPF_NUMBER },
|
|
{ "ship", OPF_SHIP },
|
|
{ "shipname", OPF_SHIP },
|
|
{ "string", OPF_STRING },
|
|
{ "team", OPF_IFF },
|
|
{ "waypointpath", OPF_WAYPOINT_PATH },
|
|
{ "variable", OPF_VARIABLE_NAME },
|
|
{ "message", OPF_MESSAGE },
|
|
{ "wing", OPF_WING },
|
|
{ "shipclass", OPF_SHIP_CLASS_NAME },
|
|
{ "weaponclass", OPF_WEAPON_NAME },
|
|
{ "soundentry", OPF_GAME_SND }, };
|
|
std::pair<SCP_string, int> get_parameter_type(const SCP_string& name) {
|
|
SCP_string copy = name;
|
|
std::transform(copy.begin(), copy.end(), copy.begin(), [](char c) { return (char)::tolower(c); });
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
SCP_unordered_map<SCP_string, int> return_type_mapping{{ "number", OPR_NUMBER },
|
|
{ "boolean", OPR_BOOL },
|
|
{ "nothing", OPR_NULL }, };
|
|
int get_return_type(const SCP_string& name) {
|
|
SCP_string copy = name;
|
|
std::transform(copy.begin(), copy.end(), copy.begin(), [](char c) { return (char)::tolower(c); });
|
|
|
|
auto iter = return_type_mapping.find(copy);
|
|
if (iter == return_type_mapping.end()) {
|
|
return -1;
|
|
} else {
|
|
return iter->second;
|
|
}
|
|
}
|
|
|
|
int get_category(const SCP_string& name) {
|
|
for (auto& subcat : op_menu) {
|
|
if (subcat.name == name) {
|
|
return subcat.id;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int get_subcategory(const SCP_string& name) {
|
|
for (auto& subcat : op_submenu) {
|
|
if (subcat.name == name) {
|
|
return subcat.id;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
namespace sexp {
|
|
|
|
LuaSEXP::LuaSEXP(const SCP_string& name) : DynamicSEXP(name) {
|
|
}
|
|
void LuaSEXP::initialize() {
|
|
// Nothing to do for this type
|
|
}
|
|
int LuaSEXP::getMinimumArguments() {
|
|
return _min_args;
|
|
}
|
|
int LuaSEXP::getMaximumArguments() {
|
|
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) 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 (ship_entry && argtype.first == "shipname") {
|
|
return LuaValue::createValue(_action.getLuaState(), ship_entry->name);
|
|
}
|
|
|
|
if (!ship_entry || ship_entry->status != ShipStatus::PRESENT) {
|
|
// 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 = static_cast<int>(wingp - Wings);
|
|
|
|
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);
|
|
}
|
|
default:
|
|
UNREACHABLE("Unhandled argument type! Someone added an argument type but didn't add handling code to execute().");
|
|
return LuaValue::createNil(_action.getLuaState());
|
|
}
|
|
}
|
|
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) {
|
|
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) {
|
|
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;
|
|
}
|
|
}
|
|
int LuaSEXP::execute(int node) {
|
|
if (!_action.isValid()) {
|
|
Error(LOCATION,
|
|
"Lua SEXP called without a valid action function! A script probably failed to set the action for some reason.");
|
|
return SEXP_CANT_EVAL;
|
|
}
|
|
|
|
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));
|
|
|
|
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));
|
|
|
|
node = CDR(node);
|
|
++argnum;
|
|
}
|
|
|
|
luaParameters.push_back(varargs_part);
|
|
}
|
|
}
|
|
|
|
// 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() {
|
|
if (_category == OP_CATEGORY_CHANGE) {
|
|
// "Change" is a special case since we can't add new SEXPs to the primary category
|
|
return OP_CATEGORY_CHANGE2;
|
|
}
|
|
return _category;
|
|
}
|
|
void LuaSEXP::parseTable() {
|
|
required_string("$Category:");
|
|
SCP_string category;
|
|
stuff_string(category, F_NAME);
|
|
|
|
_category = get_category(category);
|
|
if (_category < 0) {
|
|
error_display(0, "Invalid category '%s' found. New main categories can't be added!", category.c_str());
|
|
_category = OP_CATEGORY_CHANGE2; // Default to change2 so we have a valid value later on
|
|
}
|
|
|
|
required_string("$Subcategory:");
|
|
SCP_string subcategory;
|
|
stuff_string(subcategory, F_NAME);
|
|
|
|
_subcategory = get_subcategory(subcategory);
|
|
if (_subcategory < 0) {
|
|
// Unknown subcategory so we need to add this one
|
|
_subcategory = sexp::add_subcategory(_category, subcategory);
|
|
if (_subcategory < 0) {
|
|
// Couldn't add subcategory!
|
|
error_display(0,
|
|
"No more space for subcategory '%s' free in category '%s'!",
|
|
subcategory.c_str(),
|
|
category.c_str());
|
|
|
|
// Default to the first subcategory in our category, hopefully it will exist...
|
|
_subcategory = 0x0000 | _category;
|
|
}
|
|
} else if ((_subcategory & OP_CATEGORY_MASK) != _category) {
|
|
error_display(0,
|
|
"Subcategory '%s' is in a different category than the specified category! Subcategory names must be unique.",
|
|
subcategory.c_str());
|
|
|
|
// Default to the first subcategory in our category, hopefully it will exist...
|
|
_subcategory = 0x0000 | _category;
|
|
}
|
|
|
|
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;
|
|
stuff_string(description, F_NAME);
|
|
|
|
help_text << "\t" << description << "\r\n";
|
|
|
|
bool variable_arg_part = false;
|
|
if (optional_string("$Repeat")) {
|
|
help_text << "Rest: (The following pattern repeats)\r\n";
|
|
variable_arg_part = true;
|
|
}
|
|
|
|
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 (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.");
|
|
}
|
|
}
|
|
}
|
|
|
|
_help_text = help_text.str();
|
|
}
|
|
void LuaSEXP::setAction(const luacpp::LuaFunction& action) {
|
|
Assertion(action.isValid(), "Invalid function handle supplied!");
|
|
_action = action;
|
|
}
|
|
|
|
luacpp::LuaFunction LuaSEXP::getAction() const {
|
|
return _action;
|
|
}
|
|
}
|