//
//

#include "mission.h"

#include "globalincs/linklist.h"

#include "freespace.h"

#include "asteroid/asteroid.h"
#include "debris/debris.h"
#include "executor/GameStateExecutionContext.h"
#include "executor/global_executors.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/eventmusic.h"
#include "hud/hudescort.h"
#include "hud/hudmessage.h"
#include "hud/hudsquadmsg.h"
#include "iff_defs/iff_defs.h"
#include "mission/missioncampaign.h"
#include "mission/missiongoals.h"
#include "mission/missionload.h"
#include "mission/missionlog.h"
#include "mission/missionmessage.h"
#include "mission/missiontraining.h"
#include "missionui/missionbrief.h"
#include "missionui/missioncmdbrief.h"
#include "missionui/redalert.h"
#include "nebula/neb.h"
#include "nebula/neblightning.h"
#include "object/objcollide.h"
#include "parse/parselo.h"
#include "parse/sexp.h"
#include "parse/sexp/DynamicSEXP.h"
#include "parse/sexp/LuaSEXP.h"
#include "parse/sexp/LuaAISEXP.h"
#include "parse/sexp/sexp_lookup.h"
#include "playerman/player.h"
#include "prop/prop.h"
#include "scripting/api/LuaPromise.h"
#include "scripting/api/objs/LuaEnum.h"
#include "scripting/api/objs/LuaSEXP.h"
#include "scripting/api/objs/luaaisexp.h"
#include "scripting/api/objs/asteroid.h"
#include "scripting/api/objs/animation_handle.h"
#include "scripting/api/objs/background_element.h"
#include "scripting/api/objs/beam.h"
#include "scripting/api/objs/comm_order.h"
#include "scripting/api/objs/debris.h"
#include "scripting/api/objs/enums.h"
#include "scripting/api/objs/event.h"
#include "scripting/api/objs/fireball.h"
#include "scripting/api/objs/fireballclass.h"
#include "scripting/api/objs/goal.h"
#include "scripting/api/objs/message.h"
#include "scripting/api/objs/model.h"
#include "scripting/api/objs/modelinstance.h"
#include "scripting/api/objs/object.h"
#include "scripting/api/objs/parse_object.h"
#include "scripting/api/objs/promise.h"
#include "scripting/api/objs/prop.h"
#include "scripting/api/objs/propclass.h"
#include "scripting/api/objs/sexpvar.h"
#include "scripting/api/objs/ship_registry_entry.h"
#include "scripting/api/objs/ship.h"
#include "scripting/api/objs/shipclass.h"
#include "scripting/api/objs/sound.h"
#include "scripting/api/objs/support_rearm_pool.h"
#include "scripting/api/objs/team.h"
#include "scripting/api/objs/vecmath.h"
#include "scripting/api/objs/volumetric.h"
#include "scripting/api/objs/waypoint.h"
#include "scripting/api/objs/weapon.h"
#include "scripting/api/objs/weaponclass.h"
#include "scripting/api/objs/wing.h"
#include "scripting/lua/LuaConvert.h"
#include "scripting/lua/LuaFunction.h"
#include "scripting/lua/LuaTable.h"
#include "scripting/global_hooks.h"
#include "scripting/scripting.h"
#include "ship/ship.h"
#include "ship/shipfx.h"
#include "starfield/starfield.h"
#include "weapon/beam.h"
#include "weapon/weapon.h"

#include <utility>

extern bool Ships_inited;

extern bool Game_shudder_perpetual;
extern bool Game_shudder_everywhere;
extern TIMESTAMP Game_shudder_time;
extern int Game_shudder_total;
extern float Game_shudder_intensity;

constexpr int COUNT_OBJECTS = -1000;

// Returns the indexth object subclass
template <typename A>
int object_subclass_at_index(A& object_subclass_array, size_t array_size, int index)
{
	int count = 0;

	for (size_t i = 0; i < array_size; ++i)
	{
		int objnum = object_subclass_array[i].objnum;
		if (objnum < 0 || objnum >= MAX_OBJECTS)
			continue;
		if (Objects[objnum].flags[Object::Object_Flags::Should_be_dead])
			continue;

		++count;

		if (count == index)
		{
			return object_subclass_array[i].objnum;
		}
	}

	if (index == COUNT_OBJECTS)
		return count;
	else
		return -1;
}

// Counts the number of indexable objects
template <typename A>
int object_subclass_count(A& object_subclass_array, size_t array_size)
{
	return object_subclass_at_index(object_subclass_array, array_size, COUNT_OBJECTS);
}

// Overload for a vector of std::optional objects
template <typename T>
int object_subclass_at_index(const SCP_vector<std::optional<T>>& vec, int index)
{
	int count = 0;

	for (const auto& opt_obj : vec) {

		if (!opt_obj.has_value()) {
			continue;
		}


		const T& obj = *opt_obj;

		int objnum = obj.objnum;
		if (objnum < 0 || objnum >= MAX_OBJECTS)
			continue;
		if (Objects[objnum].flags[Object::Object_Flags::Should_be_dead])
			continue;

		++count;

		if (count == index) {
			return obj.objnum;
		}
	}

	if (index == COUNT_OBJECTS)
		return count;
	else
		return -1;
}

template <typename A>
int object_subclass_count(A& object_subclass_array)
{
	return object_subclass_at_index(object_subclass_array, COUNT_OBJECTS);
}

namespace scripting {
namespace api {

//**********LIBRARY: Mission
ADE_LIB(l_Mission, "Mission", "mn", "Mission library");

// for use in creating faster metadata systems, use in conjunction with getSignature()
ADE_FUNC(getObjectFromSignature, l_Mission, "number Signature", "Gets a handle of an object from its signature", "object", "Handle of object with signaure, invalid handle if signature is not in use")
{
	int sig = -1;
	int objnum;
	if(!ade_get_args(L, "i", &sig))
		return ade_set_error(L, "o", l_Object.Set(object_h()));

	if (sig == -1) {
		return ade_set_error(L, "o", l_Object.Set(object_h()));
	}

	objnum = obj_get_by_signature(sig);

	return ade_set_object_with_breed(L, objnum);
}

ADE_FUNC(evaluateSEXP, l_Mission, "string", "Runs the defined SEXP script, and returns the result as a boolean", "boolean", "true if the SEXP returned SEXP_TRUE or SEXP_KNOWN_TRUE; false if the SEXP returned anything else (even a number)")
{
	const char* s;
	int r_val;

	if(!ade_get_args(L, "s", &s))
		return ADE_RETURN_FALSE;

	r_val = run_sexp(s);

	if (r_val == SEXP_TRUE)
		return ADE_RETURN_TRUE;
	else
		return ADE_RETURN_FALSE;
}

ADE_FUNC(evaluateNumericSEXP, l_Mission, "string", "Runs the defined SEXP script, and returns the result as a number", "number", "the value of the SEXP result (or NaN if the SEXP returned SEXP_NAN or SEXP_NAN_FOREVER)")
{
	const char* s;
	int r_val;
	bool got_nan;

	if (!ade_get_args(L, "s", &s))
		return ade_set_args(L, "i", 0);

	r_val = run_sexp(s, true, &got_nan);

	if (got_nan)
		return ade_set_args(L, "f", std::numeric_limits<float>::quiet_NaN());
	else
		return ade_set_args(L, "i", r_val);
}

ADE_FUNC(runSEXP, l_Mission, "string", "Runs the defined SEXP script within a `when` operator", "boolean", "if the operation was successful")
{
	const char* s;
	int r_val;
	SCP_string buf;

	if(!ade_get_args(L, "s", &s))
		return ADE_RETURN_FALSE;

	while (is_white_space(*s))
		s++;
	if (*s != '(')
	{
		static bool Warned_about_runSEXP_parentheses = false;
		if (!Warned_about_runSEXP_parentheses)
		{
			Warned_about_runSEXP_parentheses = true;
			Warning(LOCATION, "Invalid SEXP syntax: SEXPs must be surrounded by parentheses.  For backwards compatibility, the string has been enclosed in parentheses.  This may not be correct in all use cases.\n\nrunSEXP string:\n%s\n", s);
		}
		// this is the old sexp handling method, which is incorrect
		sprintf(buf, "( when ( true ) ( %s ) )", s);
	}
	else
	{
		// this is correct usage
		sprintf(buf, "( when ( true ) %s )", s);
	}

	r_val = run_sexp(buf.c_str());

	if (r_val == SEXP_TRUE)
		return ADE_RETURN_TRUE;
	else
		return ADE_RETURN_FALSE;
}

//****SUBLIBRARY: Mission/SupportRearmPools
ADE_LIB_DERIV(l_Mission_SupportRearmPools,
	"SupportRearmPools",
	nullptr,
	"Per-team mission support rearm pools (team indexed).",
	l_Mission);

ADE_INDEXER(l_Mission_SupportRearmPools,
	"number TeamIndex",
	"Gets support rearm pool handle for a specific team.",
	"support_rearm_pool_team",
	"Support rearm pool team handle, or invalid handle if index is out of range.")
{
	int idx = -1;
	if (!ade_get_args(L, "*i", &idx)) {
		return ade_set_error(L, "o", l_SupportRearmPoolTeam.Set(-1));
	}

	idx--; // Lua to C++ index
	if (idx < 0 || idx >= Num_teams || idx >= MAX_TVT_TEAMS) {
		return ade_set_error(L, "o", l_SupportRearmPoolTeam.Set(-1));
	}

	return ade_set_args(L, "o", l_SupportRearmPoolTeam.Set(idx));
}

ADE_FUNC(__len,
	l_Mission_SupportRearmPools,
	nullptr,
	"The number of support rearm pool teams.",
	"number",
	"The number of TVT/loadout teams with support rearm pools.")
{
	return ade_set_args(L, "i", MIN(Num_teams, MAX_TVT_TEAMS));
}

//****SUBLIBRARY: Mission/Asteroids
ADE_LIB_DERIV(l_Mission_Asteroids, "Asteroids", NULL, "Asteroids in the mission", l_Mission);

ADE_INDEXER(l_Mission_Asteroids, "number Index", "Gets asteroid", "asteroid", "Asteroid handle, or invalid handle if invalid index specified")
{
	int idx = -1;
	if( !ade_get_args(L, "*i", &idx) ) {
		return ade_set_error( L, "o", l_Asteroid.Set( object_h() ) );
	}

	int objnum = -1;
	if (idx > 0)
		objnum = object_subclass_at_index(Asteroids, MAX_ASTEROIDS, idx);

	return ade_set_args(L, "o", l_Asteroid.Set( object_h(objnum) ) );
}

ADE_FUNC(__len, l_Mission_Asteroids, NULL,
		 "Number of asteroids in mission. Note that the value returned is only good until an asteroid is destroyed, and so cannot be relied on for more than one frame.",
		 "number",
		 "Number of asteroids in the mission, or 0 if asteroids are not enabled")
{
	if(Asteroids_enabled) {
		return ade_set_args(L, "i", object_subclass_count(Asteroids, MAX_ASTEROIDS));
	}
	return ade_set_args(L, "i", 0);
}

//****SUBLIBRARY: Mission/Comm Items
ADE_LIB_DERIV(l_Mission_Comm_Items, "CommItems", nullptr, "Comm Items in the mission", l_Mission);

ADE_INDEXER(l_Mission_Comm_Items,
	"number Index",
	"Gets comm items",
	"comm_item",
	"Comm Item handle, or invalid handle if invalid index specified")
{
	int idx;
	if (!ade_get_args(L, "*i", &idx))
		return ade_set_error(L, "s", "");

	// convert from lua index
	idx--;

	if ((idx < 0) || idx >= MAX_MENU_ITEMS)
		return ade_set_args(L, "o", l_Comm_Item.Set(-1));

	return ade_set_args(L, "o", l_Comm_Item.Set(idx));
}

ADE_FUNC(__len,
	l_Mission_Comm_Items,
	nullptr,
	"Number of comm orders in mission currently. Note that the value will change when an order is selected or the open/closed state of the comm menu is changed.",
	"number",
	"Number of comm orders in the mission. 0 if comm menu is closed")
{
	return ade_set_args(L, "i", Num_menu_items);
}

//****SUBLIBRARY: Mission/Debris
ADE_LIB_DERIV(l_Mission_Debris, "Debris", NULL, "debris in the mission", l_Mission);

ADE_INDEXER(l_Mission_Debris, "number Index", "Array of debris in the current mission", "debris", "Debris handle, or invalid debris handle if index wasn't valid")
{
	int idx = -1;
	if( !ade_get_args( L, "*i", &idx ) ) {
		return ade_set_error(L, "o", l_Debris.Set(object_h()));
	}

	idx--; // Lua -> C

	if( idx >= 0 && idx < (int)Debris.size() ) {
		if (Debris[idx].objnum == -1) //Somehow accessed an invalid debris piece
			return ade_set_error(L, "o", l_Debris.Set(object_h()));
		return ade_set_args(L, "o", l_Debris.Set(object_h(Debris[idx].objnum)));
	}

	return ade_set_error(L, "o", l_Debris.Set(object_h()));
}

ADE_FUNC(__len, l_Mission_Debris, NULL,
		 "Number of debris pieces in the mission. "
			 "Note that the value returned is only good until a piece of debris is destroyed, and so cannot be relied on for more than one frame.",
		 "number",
		 "Current number of debris particles")
{
	return ade_set_args(L, "i", (int)Debris.size());
}

//****SUBLIBRARY: Mission/EscortShips
ADE_LIB_DERIV(l_Mission_EscortShips, "EscortShips", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_EscortShips, "number Index", "Gets escort ship at specified index on escort list", "ship", "Specified ship, or invalid ship handle if invalid index")
{
	int idx;
	if(!ade_get_args(L, "*i", &idx))
		return ade_set_error(L, "o", l_Ship.Set(object_h()));

	if(idx < 1 || idx > hud_escort_num_ships_on_list())
		return ade_set_error(L, "o", l_Ship.Set(object_h()));

	//Lua->FS2
	idx--;

	idx = hud_escort_return_objnum(idx);

	if(idx < 0)
		return ade_set_error(L, "o", l_Ship.Set(object_h()));

	return ade_set_args(L, "o", l_Ship.Set(object_h(idx)));
}

ADE_FUNC(__len, l_Mission_EscortShips, NULL, "Current number of escort ships", "number", "Current number of escort ships")
{
	return ade_set_args(L, "i", hud_escort_num_ships_on_list());
}

//****SUBLIBRARY: Mission/Events
ADE_LIB_DERIV(l_Mission_Events, "Events", nullptr, "Events", l_Mission);

ADE_INDEXER(l_Mission_Events, "number/string IndexOrName", "Indexes mission events list", "mission_event", "Event handle, or invalid event handle if index was invalid")
{
	const char* s;
	if(!ade_get_args(L, "*s", &s))
		return ade_set_error(L, "o", l_Event.Set(-1));

	int i = mission_event_lookup(s);
	if (i >= 0)
		return ade_set_args(L, "o", l_Event.Set(i));

	//Now try as a number
	i = atoi(s);
	//Lua-->FS2
	i--;

	if(!SCP_vector_inbounds(Mission_events, i))
		return ade_set_error(L, "o", l_Event.Set(-1));

	return ade_set_args(L, "o", l_Event.Set(i));
}

ADE_FUNC(__len, l_Mission_Events, nullptr, "Number of events in mission", "number", "Number of events in mission")
{
	return ade_set_args(L, "i", static_cast<int>(Mission_events.size()));
}

//****SUBLIBRARY: Mission/Goals
ADE_LIB_DERIV(l_Mission_Goals, "Goals", nullptr, "Goals", l_Mission);

ADE_INDEXER(l_Mission_Goals, "number/string IndexOrName", "Indexes mission goals list", "mission_goal", "Goal handle, or invalid goal handle if index was invalid")
{
	const char* s;
	if(!ade_get_args(L, "*s", &s))
		return ade_set_error(L, "o", l_Goal.Set(-1));

	int i = mission_goal_lookup(s);
	if (i >= 0)
		return ade_set_args(L, "o", l_Goal.Set(i));

	//Now try as a number
	i = atoi(s);
	//Lua-->FS2
	i--;

	if(!SCP_vector_inbounds(Mission_goals, i))
		return ade_set_error(L, "o", l_Goal.Set(-1));


	return ade_set_args(L, "o", l_Goal.Set(i));
}

ADE_FUNC(__len, l_Mission_Goals, nullptr, "Number of goals in mission", "number", "Number of goals in mission")
{
	return ade_set_args(L, "i", static_cast<int>(Mission_goals.size()));
}

//****SUBLIBRARY: Mission/SEXPVariables
ADE_LIB_DERIV(l_Mission_SEXPVariables, "SEXPVariables", NULL, "SEXP Variables", l_Mission);

ADE_INDEXER(l_Mission_SEXPVariables, "number/string IndexOrName", "Array of SEXP variables. Note that you can set a sexp variable using the array, eg \'SEXPVariables[\"newvariable\"] = \"newvalue\"\'", "sexpvariable", "Handle to SEXP variable, or invalid sexpvariable handle if index was invalid")
{
	const char* name   = nullptr;
	const char* newval = nullptr;
	if(!ade_get_args(L, "*s|s", &name, &newval))
		return ade_set_error(L, "o", l_SEXPVariable.Set(sexpvar_h()));

	int idx = get_index_sexp_variable_name(name);
	if(idx < 0)
	{
		idx = atoi(name);

		//Lua-->FS2
		idx--;
	}

	if(idx < 0 || idx >= MAX_SEXP_VARIABLES)
	{
		if(ADE_SETTING_VAR && newval != NULL)
		{
			idx = sexp_add_variable(newval, name, lua_type(L, 2) == LUA_TNUMBER ? SEXP_VARIABLE_NUMBER : SEXP_VARIABLE_STRING);
		}

		//We have failed.
		if(idx < 0)
		{
			return ade_set_error(L, "o", l_SEXPVariable.Set(sexpvar_h()));
		}
	}
	else
	{
		if(ADE_SETTING_VAR && newval != NULL)
		{
			sexp_modify_variable(newval, idx, false);
		}
	}

	return ade_set_args(L, "o", l_SEXPVariable.Set(sexpvar_h(idx)));
}

ADE_FUNC(__len, l_Mission_SEXPVariables, NULL, "Current number of SEXP variables", "number", "Counts number of loaded SEXP Variables. May be slow.")
{
	return ade_set_args(L, "i", sexp_variable_count());
}

//****SUBLIBRARY: Mission/ShipRegistry
ADE_LIB_DERIV(l_Mission_ShipRegistry, "ShipRegistry", nullptr, "The mission's ship registry: all ships parsed, created, or exited that the mission knows about", l_Mission);

ADE_INDEXER(l_Mission_ShipRegistry, "number/string IndexOrName", "Gets ship registry entry", "ship_registry_entry", "Ship registry entry handle, or invalid handle if index was invalid")
{
	const char* name;
	if(!ade_get_args(L, "*s", &name))
		return ade_set_error(L, "o", l_ShipRegistryEntry.Set(-1));

	int idx = ship_registry_get_index(name);
	if (idx < 0)
	{
		idx = atoi(name);	// will return 0 if not parseable

		//Lua-->FS2
		idx--;
	}

	return ade_set_args(L, "o", l_ShipRegistryEntry.Set(idx));
}

ADE_FUNC(__len, l_Mission_ShipRegistry, nullptr,
		 "Number of ship registry entries in the mission.  The value returned is generally stable but will change if a ship is created using ship-create or if additional wing waves arrive.",
		 "number",
		 "Number of ship registry entries in the mission")
{
	return ade_set_args(L, "i", (int)Ship_registry.size());
}

//****SUBLIBRARY: Mission/Ships
ADE_LIB_DERIV(l_Mission_Ships, "Ships", NULL, "Ships in the mission", l_Mission);

ADE_INDEXER(l_Mission_Ships, "number/string IndexOrName", "Gets ship", "ship", "Ship handle, or invalid ship handle if index was invalid")
{
	const char* name;
	if(!ade_get_args(L, "*s", &name))
		return ade_set_error(L, "o", l_Ship.Set(object_h()));

	int objnum = -1;
	auto entry = ship_registry_get(name);
	if (entry)
	{
		if (entry->has_objp())
			objnum = entry->objnum;
	}
	else
	{
		int idx = atoi(name);
		if (idx > 0)
			objnum = object_subclass_at_index(Ships, MAX_SHIPS, idx);
	}

	return ade_set_args(L, "o", l_Ship.Set(object_h(objnum)));
}

ADE_FUNC(__len, l_Mission_Ships, NULL,
		 "Number of ships in the mission. "
			 "This function is somewhat slow, and should be set to a variable for use in looping situations. "
			 "Note that the value returned is only good until a ship is destroyed, and so cannot be relied on for more than one frame.",
		 "number",
		 "Number of ships in the mission, or 0 if ships haven't been initialized yet")
{
	return ade_set_args(L, "i", object_subclass_count(Ships, MAX_SHIPS));
}

//****SUBLIBRARY: Mission/ParsedShips
ADE_LIB_DERIV(l_Mission_ParsedShips, "ParsedShips", nullptr, "Parsed ships (aka parse objects) in the mission", l_Mission);

ADE_INDEXER(l_Mission_ParsedShips, "number/string IndexOrName", "Gets parsed ship", "parse_object", "Parsed ship handle, or invalid handle if index was invalid")
{
	const char *name;
	if (!ade_get_args(L, "*s", &name))
		return ade_set_error(L, "o", l_ParseObject.Set(parse_object_h(nullptr)));

	auto ship_entry = ship_registry_get(name);
	if (ship_entry)
	{
		return ade_set_args(L, "o", l_ParseObject.Set(parse_object_h(ship_entry->p_objp_or_null())));
	}
	else
	{
		auto idx = atoi(name);
		if (idx > 0)
		{
			idx--;	// Lua -> C++

			if (idx < static_cast<int>(Parse_objects.size()))
			{
				return ade_set_args(L, "o", l_ParseObject.Set(parse_object_h(&Parse_objects[idx])));
			}
		}
	}

	return ade_set_error(L, "o", l_ParseObject.Set(parse_object_h(nullptr)));
}

ADE_FUNC(__len, l_Mission_ParsedShips, NULL,
		 "Number of parsed ships in the mission. This function is quick and the value returned can be relied on to be stable for the entire mission.",
		 "number",
		 "Number of parsed ships in the most recently loaded mission, or 0 if no mission has been parsed yet")
{
	return ade_set_args(L, "i", static_cast<int>(Parse_objects.size()));
}

//****SUBLIBRARY: Mission/Props
ADE_LIB_DERIV(l_Mission_Props, "Props", nullptr, "Props in the mission", l_Mission);

ADE_INDEXER(l_Mission_Props, "number/string IndexOrName", "Gets prop", "prop", "Prop handle, or invalid prop handle if index was invalid")
{
	const char* name;
	if(!ade_get_args(L, "*s", &name))
		return ade_set_error(L, "o", l_Prop.Set(object_h()));

	int idx = prop_name_lookup(name);

	if (idx >= 0)
	{
		return ade_set_args(L, "o", l_Prop.Set(object_h(&Objects[prop_id_lookup(idx)->objnum])));
	}
	else
	{
		idx = atoi(name);

		int objnum = -1;
		if (idx > 0)
			objnum = object_subclass_at_index(Props, idx);

		return ade_set_args(L, "o", l_Prop.Set(object_h(objnum)));
	}
}

ADE_FUNC(__len, l_Mission_Props, nullptr,
		 "Number of props in the mission. "
			 "This function is somewhat slow, and should be set to a variable for use in looping situations. "
			 "Note that props can be vanished, and so this value cannot be relied on for more than one frame.",
		 "number",
		 "Number of props in the mission, or 0 if props haven't been initialized yet")
{
	return ade_set_args(L, "i", object_subclass_count(Props));
}

//****SUBLIBRARY: Mission/Waypoints
ADE_LIB_DERIV(l_Mission_Waypoints, "Waypoints", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_Waypoints, "number Index", "Array of waypoints in the current mission", "waypoint", "Waypoint handle, or invalid waypoint handle if index was invalid")
{
	int idx;
	if(!ade_get_args(L, "*i", &idx))
		return ade_set_error(L, "o", l_Waypoint.Set(object_h()));

	//Remember, Lua indices start at 0.
	int count=0;

	object *ptr = GET_FIRST(&obj_used_list);
	while (ptr != END_OF_LIST(&obj_used_list))
	{
		if (ptr->type == OBJ_WAYPOINT && !ptr->flags[Object::Object_Flags::Should_be_dead])
			count++;

		if(count == idx) {
			return ade_set_args(L, "o", l_Waypoint.Set(object_h(ptr)));
		}

		ptr = GET_NEXT(ptr);
	}

	return ade_set_error(L, "o", l_Waypoint.Set(object_h()));
}

ADE_FUNC(__len, l_Mission_Waypoints, NULL, "Gets number of waypoints in mission. Note that this is only accurate for one frame.", "number", "Number of waypoints in the mission")
{
	int count=0;

	object *ptr = GET_FIRST(&obj_used_list);
	while (ptr != END_OF_LIST(&obj_used_list))
	{
		if (ptr->type == OBJ_WAYPOINT && !ptr->flags[Object::Object_Flags::Should_be_dead])
			count++;

		ptr = GET_NEXT(ptr);
	}

	return ade_set_args(L, "i", count);
}

//****SUBLIBRARY: Mission/WaypointLists
ADE_LIB_DERIV(l_Mission_WaypointLists, "WaypointLists", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_WaypointLists, "number/string IndexOrWaypointListName", "Array of waypoint lists", "waypointlist", "Gets waypointlist handle")
{
	waypointlist_h wpl;
	const char* name;
	if(!ade_get_args(L, "*s", &name))
		return ade_set_error(L, "o", l_WaypointList.Set(waypointlist_h()));

	wpl = waypointlist_h(name);

	if (!wpl.isValid()) {
		char* end_ptr;
		auto idx = (int)strtol(name, &end_ptr, 10);
		if (end_ptr != name && idx >= 1) {
			// The string is a valid number and the number has a valid value
			wpl = waypointlist_h(find_waypoint_list_at_index(idx - 1));
		}
	}

	if (wpl.isValid()) {
		return ade_set_args(L, "o", l_WaypointList.Set(wpl));
	}

	return ade_set_error(L, "o", l_WaypointList.Set(waypointlist_h()));
}

ADE_FUNC(__len, l_Mission_WaypointLists, NULL, "Number of waypoint lists in mission. Note that this is only accurate for one frame.", "number", "Number of waypoint lists in the mission")
{
	return ade_set_args(L, "i", Waypoint_lists.size());
}

//****SUBLIBRARY: Mission/Weapons
ADE_LIB_DERIV(l_Mission_Weapons, "Weapons", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_Weapons, "number Index", "Gets handle to a weapon object in the mission.", "weapon", "Weapon handle, or invalid weapon handle if index is invalid")
{
	int idx;
	if(!ade_get_args(L, "*i", &idx))
		return ade_set_error(L, "o", l_Weapon.Set(object_h()));

	int objnum = -1;
	if (idx > 0)
		objnum = object_subclass_at_index(Weapons, MAX_WEAPONS, idx);

	return ade_set_args(L, "o", l_Weapon.Set(object_h(objnum)));
}
ADE_FUNC(__len, l_Mission_Weapons, NULL, "Number of weapon objects in mission. Note that this is only accurate for one frame.", "number", "Number of weapon objects in mission")
{
	return ade_set_args(L, "i", object_subclass_count(Weapons, MAX_WEAPONS));
}

//****SUBLIBRARY: Mission/Beams
ADE_LIB_DERIV(l_Mission_Beams, "Beams", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_Beams, "number Index", "Gets handle to a beam object in the mission.", "beam", "Beam handle, or invalid beam handle if index is invalid")
{
	int idx;
	if(!ade_get_args(L, "*i", &idx))
		return ade_set_error(L, "o", l_Beam.Set(object_h()));

	int objnum = -1;
	if (idx > 0)
		objnum = object_subclass_at_index(Beams, MAX_BEAMS, idx);

	return ade_set_args(L, "o", l_Beam.Set(object_h(objnum)));
}
ADE_FUNC(__len, l_Mission_Beams, NULL, "Number of beam objects in mission. Note that this is only accurate for one frame.", "number", "Number of beam objects in mission")
{
	return ade_set_args(L, "i", object_subclass_count(Beams, MAX_BEAMS));
}

//****SUBLIBRARY: Mission/Wings
ADE_LIB_DERIV(l_Mission_Wings, "Wings", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_Wings, "number/string IndexOrWingName", "Wings in the mission", "wing", "Wing handle, or invalid wing handle if index or name was invalid")
{
	const char* name;
	if(!ade_get_args(L, "*s", &name))
		return ade_set_error(L, "o", l_Wing.Set(-1));

	//MageKing17 - Make the count-ignoring version of the lookup and leave checking if the wing has any ships to the scripter
	int idx = wing_lookup(name);

	if(idx < 0)
	{
		idx = atoi(name);
		if(idx < 1 || idx > Num_wings)
			return ade_set_error(L, "o", l_Wing.Set(-1));

		idx--;	//Lua->FS2
	}

	return ade_set_args(L, "o", l_Wing.Set(idx));
}

ADE_FUNC(__len, l_Mission_Wings, NULL, "Number of wings in mission", "number", "Number of wings in mission")
{
	return ade_set_args(L, "i", Num_wings);
}

//****SUBLIBRARY: Mission/Teams
ADE_LIB_DERIV(l_Mission_Teams, "Teams", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_Teams, "number/string IndexOrTeamName", "Teams in the mission", "team", "Team handle or invalid team handle if the requested team could not be found")
{
	const char* name;
	if(!ade_get_args(L, "*s", &name))
		return ade_set_error(L, "o", l_Team.Set(-1));

	int idx = iff_lookup(name);

	if(idx < 0)
	{
		idx = atoi(name);

		idx--;	//Lua->FS2
	}

	if(idx < 0 || idx >= (int)Iff_info.size())
		return ade_set_error(L, "o", l_Team.Set(-1));

	return ade_set_args(L, "o", l_Team.Set(idx));
}

ADE_FUNC(__len, l_Mission_Teams, NULL, "Number of teams in mission", "number", "Number of teams in mission")
{
	return ade_set_args(L, "i", Iff_info.size());
}

//****SUBLIBRARY: Mission/Messages
ADE_LIB_DERIV(l_Mission_Messages, "Messages", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_Messages, "number/string IndexOrMessageName", "Messages of the mission", "message", "Message handle or invalid handle on error")
{
	int idx = -1;

	if (lua_isnumber(L, 2))
	{
		if (!ade_get_args(L, "*i", &idx))
			return ade_set_args(L, "o", l_Message.Set(-1));

		idx--; // Lua --> FS2

		idx += Num_builtin_messages;
	}
	else
	{
		const char* name = nullptr;

		if (!ade_get_args(L, "*s", &name))
			return ade_set_args(L, "o", l_Message.Set(-1));

		if (name == NULL)
			return ade_set_args(L, "o", l_Message.Set(-1));

		for (int i = Num_builtin_messages; i < (int) Messages.size(); i++)
		{
			if (!stricmp(Messages[i].name, name))
			{
				idx = i;
				break;
			}
		}
	}

	if (idx < Num_builtin_messages || idx >= (int) Messages.size())
		return ade_set_args(L, "o", l_Message.Set(-1));
	else
		return ade_set_args(L, "o", l_Message.Set(idx));
}

ADE_FUNC(__len, l_Mission_Messages, NULL, "Number of messages in the mission", "number", "Number of messages in mission")
{
	return ade_set_args(L, "i", (int) Messages.size() - Num_builtin_messages);
}

//****SUBLIBRARY: Mission/BuiltinMessages
ADE_LIB_DERIV(l_Mission_BuiltinMessages, "BuiltinMessages", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_BuiltinMessages, "number/string IndexOrMessageName", "Built-in messages of the mission", "message", "Message handle or invalid handle on error")
{
	int idx = -1;

	if (lua_isnumber(L, 2))
	{
		if (!ade_get_args(L, "*i", &idx))
			return ade_set_args(L, "o", l_Message.Set(-1));

		idx--; // Lua --> FS2
	}
	else
	{
		const char* name = nullptr;

		if (!ade_get_args(L, "*s", &name))
			return ade_set_args(L, "o", l_Message.Set(-1));

		if (name == NULL)
			return ade_set_args(L, "o", l_Message.Set(-1));

		for (int i = 0; i < Num_builtin_messages; i++)
		{
			if (!stricmp(Messages[i].name, name))
			{
				idx = i;
				break;
			}
		}
	}

	if (idx < 0 || idx >= Num_builtin_messages)
		return ade_set_args(L, "o", l_Message.Set(-1));
	else
		return ade_set_args(L, "o", l_Message.Set(idx));
}

ADE_FUNC(__len, l_Mission_BuiltinMessages, NULL, "Number of built-in messages in the mission", "number", "Number of messages in mission")
{
	return ade_set_args(L, "i", Num_builtin_messages);
}

//****SUBLIBRARY: Mission/Personas
ADE_LIB_DERIV(l_Mission_Personas, "Personas", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_Personas, "number/string IndexOrName", "Personas of the mission", "persona", "Persona handle or invalid handle on error")
{
	int idx = -1;

	if (lua_isnumber(L, 2))
	{
		if (!ade_get_args(L, "*i", &idx))
			return ade_set_args(L, "o", l_Persona.Set(-1));

		idx--; // Lua --> FS2
	}
	else
	{
		const char* name = nullptr;

		if (!ade_get_args(L, "*s", &name))
			return ade_set_args(L, "o", l_Persona.Set(-1));

		if (name == NULL)
			return ade_set_args(L, "o", l_Persona.Set(-1));

		idx = message_persona_name_lookup(name);
	}

	if (idx < 0 || idx >= (int)Personas.size())
		return ade_set_args(L, "o", l_Persona.Set(-1));
	else
		return ade_set_args(L, "o", l_Persona.Set(idx));
}

ADE_FUNC(__len, l_Mission_Personas, NULL, "Number of personas in the mission", "number", "Number of messages in mission")
{
	return ade_set_args(L, "i", (int)Personas.size());
}

//****SUBLIBRARY: Mission/Fireballs
ADE_LIB_DERIV(l_Mission_Fireballs, "Fireballs", NULL, NULL, l_Mission);

ADE_INDEXER(l_Mission_Fireballs, "number Index", "Gets handle to a fireball object in the mission.", "fireball", "Fireball handle, or invalid fireball handle if index is invalid")
{
	int idx;
	if (!ade_get_args(L, "*i", &idx))
		return ade_set_error(L, "o", l_Fireball.Set(object_h()));

	int objnum = -1;
	if (idx > 0)
		objnum = object_subclass_at_index(Fireballs, Fireballs.size(), idx);

	return ade_set_args(L, "o", l_Fireball.Set(object_h(objnum)));
}
ADE_FUNC(__len, l_Mission_Fireballs, NULL, "Number of fireball objects in mission. Note that this is only accurate for one frame.", "number", "Number of fireball objects in mission")
{
	return ade_set_args(L, "i", object_subclass_count(Fireballs, Fireballs.size()));
}

ADE_FUNC(addMessage, l_Mission, "string name, string text, [persona persona]", "Adds a message", "message", "The new message or invalid handle on error")
{
	const char* name = nullptr;
	const char* text = nullptr;
	int personaIdx = -1;

	if (!ade_get_args(L, "ss|o", &name, &text, l_Persona.Get(&personaIdx)))
		return ade_set_error(L, "o", l_Message.Set(-1));

	if (name == NULL || text == NULL)
		return ade_set_error(L, "o", l_Message.Set(-1));

	if (personaIdx < 0 || personaIdx >= (int)Personas.size())
		personaIdx = -1;

	add_message(name, text, personaIdx, 0);

	return ade_set_error(L, "o", l_Message.Set((int) Messages.size() - 1));
}

int sendMessage_sub(lua_State* L, const void* sender, int messageSource, int messageIdx, float delay, enum_h* ehp)
{
	if (messageIdx < 0 || messageIdx >= (int)Messages.size())
		return ADE_RETURN_FALSE;

	if (messageIdx < Num_builtin_messages)
	{
		LuaError(L, "Cannot send built-in messages!");
		return ADE_RETURN_FALSE;
	}

	if (delay < 0.0f)
	{
		LuaError(L, "Invalid negative delay of %f!", delay);
		return ADE_RETURN_FALSE;
	}

	int priority = MESSAGE_PRIORITY_NORMAL;

	if (ehp != nullptr)
	{
		switch (ehp->index)
		{
			case LE_MESSAGE_PRIORITY_HIGH:
				priority = MESSAGE_PRIORITY_HIGH;
				break;
			case LE_MESSAGE_PRIORITY_NORMAL:
				priority = MESSAGE_PRIORITY_NORMAL;
				break;
			case LE_MESSAGE_PRIORITY_LOW:
				priority = MESSAGE_PRIORITY_LOW;
				break;
			default:
				LuaError(L, "Invalid enumeration used! Must be one of MESSAGE_PRIORITY_*.");
				return ADE_RETURN_FALSE;
		}
	}

	message_send_unique(Messages[messageIdx].name, sender, messageSource, priority, 0, fl2i(delay * MILLISECONDS_PER_SECOND));

	return ADE_RETURN_TRUE;
}

ADE_FUNC(sendMessage,
	l_Mission,
	"string|ship sender, message message, [number delay=0.0, enumeration priority = MESSAGE_PRIORITY_NORMAL /* MESSAGE_PRIORITY_* */, boolean "
	"fromCommand = false]",
	"Sends a message from the given source or ship with the given MESSAGE_PRIORITY_* priority, or optionally sends it from the "
	"mission's command source.<br>"
	"If delay is specified, the message will be delayed by the specified time in seconds.<br>"
	"If sender is <i>nil</i> the message will not have a sender.  If sender is a ship object the message will be sent from the ship; "
	"if sender is a string the message will have a non-ship source even if the string is a ship name.",
	"boolean",
	"true if successful, false otherwise")
{
	const void* sender = nullptr;
	int messageIdx = -1;
	bool fromCommand = false;
	int messageSource = MESSAGE_SOURCE_SPECIAL;
	float delay = 0.0f;

	enum_h* ehp = nullptr;

	// if first is nil then use no source
	if (lua_isnil(L, 1))
	{
		if (!ade_get_args(L, "*o|fob", l_Message.Get(&messageIdx), &delay, l_Enum.GetPtr(&ehp), &fromCommand))
			return ADE_RETURN_FALSE;

		messageSource = MESSAGE_SOURCE_NONE;
	}
	// if first is a string then treat it as such
	else if (lua_isstring(L, 1))
	{
		const char* sender_string = nullptr;

		if (!ade_get_args(L, "so|fob", &sender_string, l_Message.Get(&messageIdx), &delay, l_Enum.GetPtr(&ehp), &fromCommand))
			return ADE_RETURN_FALSE;

		if (sender_string == nullptr)
			return ADE_RETURN_FALSE;

		sender = sender_string;
	}
	// assume it's a ship
	else
	{
		object_h* ship_h = nullptr;

		if (!ade_get_args(L, "oo|fob", l_Ship.GetPtr(&ship_h), l_Message.Get(&messageIdx), &delay, l_Enum.GetPtr(&ehp), &fromCommand))
			return ADE_RETURN_FALSE;

		if (ship_h == nullptr || !ship_h->isValid())
			return ADE_RETURN_FALSE;

		sender = &Ships[ship_h->objp()->instance];
		messageSource = MESSAGE_SOURCE_SHIP;
	}

	if (fromCommand)
		messageSource = MESSAGE_SOURCE_COMMAND;

	return sendMessage_sub(L, sender, messageSource, messageIdx, delay, ehp);
}

ADE_FUNC(sendTrainingMessage, l_Mission, "message message, number time, [number delay=0.0]",
		 "Sends a training message to the player. <i>time</i> is the amount in seconds to display the message, only whole seconds are used!",
		 "boolean", "true if successful, false otherwise")
{
	int messageIdx = -1;
	float delay = 0.0f;
	int time = -1;

	if (!ade_get_args(L, "oi|f", l_Message.Get(&messageIdx), &time, &delay))
		return ADE_RETURN_FALSE;

	if (messageIdx < 0 || messageIdx >= (int) Messages.size())
		return ADE_RETURN_FALSE;

	if (delay < 0.0f)
	{
		LuaError(L, "Got invalid delay of %f seconds!", delay);
		return ADE_RETURN_FALSE;
	}

	if (time < 0) {
		LuaError(L, "Got invalid time of %d seconds!", time);
		return ADE_RETURN_FALSE;
	}

	message_training_queue(Messages[messageIdx].name, _timestamp(fl2i(delay * MILLISECONDS_PER_SECOND)), time);

	return ADE_RETURN_TRUE;
}

ADE_FUNC(sendPlainMessage,
	l_Mission,
	"string message",
	"Sends a plain text message without it being present in the mission message list",
	"boolean",
	"true if successful false otherwise")
{
	const char* message = nullptr;

	if (!ade_get_args(L, "s", &message))
		return ADE_RETURN_FALSE;

	if (message == nullptr)
		return ADE_RETURN_FALSE;

	HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", message);

	return ADE_RETURN_TRUE;
}

// Map from the Lua enums to the message type enums
int getBuiltinMessageType(const enum_h* enumValue)
{
	switch (enumValue->index) {
	case LE_BUILTIN_MESSAGE_ATTACK_TARGET:
		return MESSAGE_ATTACK_TARGET;
	case LE_BUILTIN_MESSAGE_DISABLE_TARGET:
		return MESSAGE_DISABLE_TARGET;
	case LE_BUILTIN_MESSAGE_DISARM_TARGET:
		return MESSAGE_DISARM_TARGET;
	case LE_BUILTIN_MESSAGE_ATTACK_SUBSYSTEM:
		return MESSAGE_ATTACK_SUBSYSTEM;
	case LE_BUILTIN_MESSAGE_PROTECT_TARGET:
		return MESSAGE_PROTECT_TARGET;
	case LE_BUILTIN_MESSAGE_FORM_ON_MY_WING:
		return MESSAGE_FORM_ON_MY_WING;
	case LE_BUILTIN_MESSAGE_COVER_ME:
		return MESSAGE_COVER_ME;
	case LE_BUILTIN_MESSAGE_IGNORE:
		return MESSAGE_IGNORE;
	case LE_BUILTIN_MESSAGE_ENGAGE:
		return MESSAGE_ENGAGE;
	case LE_BUILTIN_MESSAGE_WARP_OUT:
		return MESSAGE_WARP_OUT;
	case LE_BUILTIN_MESSAGE_DOCK_YES:
		return MESSAGE_DOCK_YES;
	case LE_BUILTIN_MESSAGE_YESSIR:
		return MESSAGE_YESSIR;
	case LE_BUILTIN_MESSAGE_NOSIR:
		return MESSAGE_NOSIR;
	case LE_BUILTIN_MESSAGE_NO_TARGET:
		return MESSAGE_NO_TARGET;
	case LE_BUILTIN_MESSAGE_CHECK_6:
		return MESSAGE_CHECK_6;
	case LE_BUILTIN_MESSAGE_PLAYER_DIED:
		return MESSAGE_PLAYER_DIED;
	case LE_BUILTIN_MESSAGE_PRAISE:
		return MESSAGE_PRAISE;
	case LE_BUILTIN_MESSAGE_HIGH_PRAISE:
		return MESSAGE_HIGH_PRAISE;
	case LE_BUILTIN_MESSAGE_BACKUP:
		return MESSAGE_BACKUP;
	case LE_BUILTIN_MESSAGE_HELP:
		return MESSAGE_HELP;
	case LE_BUILTIN_MESSAGE_WINGMAN_SCREAM:
		return MESSAGE_WINGMAN_SCREAM;
	case LE_BUILTIN_MESSAGE_PRAISE_SELF:
		return MESSAGE_PRAISE_SELF;
	case LE_BUILTIN_MESSAGE_REARM_REQUEST:
		return MESSAGE_REARM_REQUEST;
	case LE_BUILTIN_MESSAGE_REPAIR_REQUEST:
		return MESSAGE_REPAIR_REQUEST;
	case LE_BUILTIN_MESSAGE_PRIMARIES_LOW:
		return MESSAGE_PRIMARIES_LOW;
	case LE_BUILTIN_MESSAGE_REARM_PRIMARIES:
		return MESSAGE_REARM_PRIMARIES;
	case LE_BUILTIN_MESSAGE_REARM_WARP:
		return MESSAGE_REARM_WARP;
	case LE_BUILTIN_MESSAGE_ON_WAY:
		return MESSAGE_ON_WAY;
	case LE_BUILTIN_MESSAGE_ALREADY_ON_WAY:
		return MESSAGE_ALREADY_ON_WAY;
	case LE_BUILTIN_MESSAGE_REPAIR_DONE:
		return MESSAGE_REPAIR_DONE;
	case LE_BUILTIN_MESSAGE_REPAIR_ABORTED:
		return MESSAGE_REPAIR_ABORTED;
	case LE_BUILTIN_MESSAGE_SUPPORT_KILLED:
		return MESSAGE_SUPPORT_KILLED;
	case LE_BUILTIN_MESSAGE_ALL_ALONE:
		return MESSAGE_ALL_ALONE;
	case LE_BUILTIN_MESSAGE_ARRIVE_ENEMY:
		return MESSAGE_ARRIVE_ENEMY;
	case LE_BUILTIN_MESSAGE_OOPS:
		return MESSAGE_OOPS;
	case LE_BUILTIN_MESSAGE_HAMMER_SWINE:
		return MESSAGE_HAMMER_SWINE;
	case LE_BUILTIN_MESSAGE_AWACS_75:
		return MESSAGE_AWACS_75;
	case LE_BUILTIN_MESSAGE_AWACS_25:
		return MESSAGE_AWACS_25;
	case LE_BUILTIN_MESSAGE_STRAY_WARNING:
		return MESSAGE_STRAY_WARNING;
	case LE_BUILTIN_MESSAGE_STRAY_WARNING_FINAL:
		return MESSAGE_STRAY_WARNING_FINAL;
	case LE_BUILTIN_MESSAGE_INSTRUCTOR_HIT:
		return MESSAGE_INSTRUCTOR_HIT;
	case LE_BUILTIN_MESSAGE_INSTRUCTOR_ATTACK:
		return MESSAGE_INSTRUCTOR_ATTACK;
	case LE_BUILTIN_MESSAGE_ALL_CLEAR:
		return MESSAGE_ALL_CLEAR;
	case LE_BUILTIN_MESSAGE_PERMISSION:
		return MESSAGE_PERMISSION;
	case LE_BUILTIN_MESSAGE_STRAY:
		return MESSAGE_STRAY;
	default:
		return -1;
	}
}

ADE_FUNC(sendBuiltinMessage,
	l_Mission,
	"ship sender, ship subject, enumeration|string type_or_type_name /* BUILTIN_MESSAGE_* */",
	"Sends one of the builtin messages from the given source or ship, taking the message subject into account."
	"The subject can be nil or it can be the target of the message like a response to a destroy order."
	"The type must be one of the BUILTIN_MESSAGE_* enumerations or a string matching a custom built-in message defined in messages.tbl.",
	"boolean",
	"true if successful, false otherwise")
{
	ship* sender = nullptr;
	ship* subject = nullptr;
	int messageType = -1;

	enum_h* ehp = nullptr;

	object_h* sender_ship_h = nullptr;
	object_h* subject_ship_h = nullptr;

	if (lua_isstring(L, 3)) {
		// If the type is a string, it could be a custom message type defined in messages.tbl.
		const char* type_str = nullptr;
		if (!ade_get_args(L, "oos", l_Ship.GetPtr(&sender_ship_h), l_Ship.GetPtr(&subject_ship_h), &type_str))
			return ADE_RETURN_FALSE;
		
		// Get the builtin message type from the string.
		messageType = get_builtin_message_type(type_str);
	} else {
		if (!ade_get_args(L, "ooo", l_Ship.GetPtr(&sender_ship_h), l_Ship.GetPtr(&subject_ship_h), l_Enum.GetPtr(&ehp)))
			return ADE_RETURN_FALSE;

		// I don't love this method of error checking the enums becuase if someone inserts an enum accidentally in this
		// range it could fail but the way we do all our LUA Enums is prone to that kind of mess at a foundational
		// level...
		if (ehp == nullptr || ehp->index < LE_BUILTIN_MESSAGE_ATTACK_TARGET || ehp->index > LE_BUILTIN_MESSAGE_STRAY) {
			Warning(LOCATION, "Invalid message type %d passed to sendBuiltinMessage!\n", (ehp != nullptr) ? ehp->index : -1);
			return ADE_RETURN_FALSE;
		}

		messageType = getBuiltinMessageType(ehp);
	}

	if (sender_ship_h == nullptr || !sender_ship_h->isValid())
		return ADE_RETURN_FALSE;

	sender = &Ships[sender_ship_h->objp()->instance];

	if (subject_ship_h != nullptr && subject_ship_h->isValid())
		subject = &Ships[subject_ship_h->objp()->instance];

	if (messageType < 0 || messageType >= static_cast<int>(Builtin_messages.size())) {
		LuaError(L, "Message type is invalid!");
		return ADE_RETURN_FALSE;
	}

	return message_send_builtin(messageType, sender, subject);
}

ADE_FUNC(addMessageToScrollback,
	l_Mission,
	"string message, [team|enumeration source = SCROLLBACK_SOURCE_COMPUTER /* SCROLLBACK_* */]",
	"Adds a string to the message log scrollback without sending it as a message first. Source should be either the team handle "
	"or one of the SCROLLBACK_* enumerations.",
	"boolean",
	"true if successful, false otherwise")
{
	const char* message = nullptr;
	int team = -1;
	enum_h* esp = nullptr;

	int source = HUD_SOURCE_COMPUTER;
	if (luacpp::convert::ade_odata_is_userdata_type(L, 2, l_Team)) {
		if (!ade_get_args(L, "s|o", &message, l_Team.Get(&team))) {
			return ADE_RETURN_FALSE;
		} else {
			source = HUD_team_get_source(team);
			if (source < HUD_SOURCE_TEAM_OFFSET) {
				LuaError(L, "Got team index %i. Team source may be invalid!", source);
				return ADE_RETURN_FALSE;
			}
		}
	} else {
		if (!ade_get_args(L, "s|o", &message, l_Enum.GetPtr(&esp))) {
			return ADE_RETURN_FALSE;
		} else {
			if (esp != nullptr) {
				switch (esp->index) {
				case LE_SCROLLBACK_SOURCE_COMPUTER:
					source = HUD_SOURCE_COMPUTER;
					break;
				case LE_SCROLLBACK_SOURCE_TRAINING:
					source = HUD_SOURCE_TRAINING;
					break;
				case LE_SCROLLBACK_SOURCE_HIDDEN:
					source = HUD_SOURCE_HIDDEN;
					break;
				case LE_SCROLLBACK_SOURCE_IMPORTANT:
					source = HUD_SOURCE_IMPORTANT;
					break;
				case LE_SCROLLBACK_SOURCE_FAILED:
					source = HUD_SOURCE_FAILED;
					break;
				case LE_SCROLLBACK_SOURCE_SATISFIED:
					source = HUD_SOURCE_SATISFIED;
					break;
				case LE_SCROLLBACK_SOURCE_COMMAND:
					source = HUD_SOURCE_TERRAN_CMD;
					break;
				case LE_SCROLLBACK_SOURCE_NETPLAYER:
					source = HUD_SOURCE_NETPLAYER;
					break;
				default:
					LuaError(L, "Invalid enumeration used! Must be one of SCROLLBACK_SOURCE_*.");
					return ADE_RETURN_FALSE;
				}
			}
		}
	}

	if (message == nullptr)
		return ADE_RETURN_FALSE;

	HUD_add_to_scrollback(message, source);

	return ADE_RETURN_TRUE;
}

ADE_FUNC(createShip,
	l_Mission,
	"[string Name, shipclass Class /* First ship class by default */, orientation Orientation=null, vector Position /* "
	"null vector by default */, team Team, boolean ShowInMissionLog /* true by default */]",
	"Creates a ship and returns a handle to it using the specified name, class, world orientation, and world position; and logs it in the mission log unless specified otherwise",
	"ship",
	"Ship handle, or invalid ship handle if ship couldn't be created")
{
	const char* name = nullptr;
	int sclass       = -1;
	matrix_h* orient = nullptr;
	vec3d pos        = vmd_zero_vector;
	int team         = -1;
	bool show_in_log = true;
	ade_get_args(L, "|soooob", &name, l_Shipclass.Get(&sclass), l_Matrix.GetPtr(&orient), l_Vector.Get(&pos), l_Team.Get(&team), &show_in_log);

	matrix *real_orient = &vmd_identity_matrix;
	if(orient != NULL)
	{
		real_orient = orient->GetMatrix();
	}

	if (sclass == -1) {
		return ade_set_error(L, "o", l_Ship.Set(object_h()));
	}

	int obj_idx = ship_create(real_orient, &pos, sclass, name);

	if(obj_idx >= 0) {
		auto shipp = &Ships[Objects[obj_idx].instance];

		if (team >= 0) {
			shipp->team = team;
		}

		ship_info* sip = &Ship_info[sclass];

		model_page_in_textures(sip->model_num, sclass);

		ship_set_warp_effects(&Objects[obj_idx]);

		// if this name has a hash, create a default display name
		if (get_pointer_to_first_hash_symbol(shipp->ship_name)) {
			shipp->display_name = shipp->ship_name;
			end_string_at_first_hash_symbol(shipp->display_name);
			shipp->flags.set(Ship::Ship_Flags::Has_display_name);
		}

		if (sip->flags[Ship::Info_Flags::Intrinsic_no_shields]) {
			Objects[obj_idx].flags.set(Object::Object_Flags::No_shields);
		}

		mission_log_add_entry(LOG_SHIP_ARRIVED, shipp->ship_name, nullptr, -1, show_in_log ? 0 : MLF_HIDDEN);

		if (scripting::hooks::OnShipArrive->isActive()) {
			scripting::hooks::OnShipArrive->run(scripting::hooks::ShipArriveConditions{ shipp, ArrivalLocation::AT_LOCATION, nullptr },
				scripting::hook_param_list(
					scripting::hook_param("Ship", 'o', &Objects[obj_idx])
				));
		}

		return ade_set_args(L, "o", l_Ship.Set(object_h(obj_idx)));
	} else
		return ade_set_error(L, "o", l_Ship.Set(object_h()));
}

ADE_FUNC(createProp,
	l_Mission,
	"[string Name, propclass Class /* First prop class by default */, orientation Orientation=null, vector Position /* null vector by default */]",
	"Creates a prop and returns a handle to it using the specified name, class, world orientation, and world position.",
	"prop",
	"Prop handle, or invalid prop handle if prop couldn't be created")
{
	const char* name = nullptr;
	int pclass       = 0;
	matrix_h* orient = nullptr;
	vec3d pos        = vmd_zero_vector;
	ade_get_args(L, "|sooo", &name, l_Propclass.Get(&pclass), l_Matrix.GetPtr(&orient), l_Vector.Get(&pos));

	if (!SCP_vector_inbounds(Prop_info, pclass)) {
		return ade_set_error(L, "o", l_Prop.Set(object_h()));
	}

	matrix *real_orient = &vmd_identity_matrix;
	if(orient != nullptr)
	{
		real_orient = orient->GetMatrix();
	}

	int obj_idx = prop_create(real_orient, &pos, pclass, name);

	if(obj_idx >= 0) {
		prop_info* pip = &Prop_info[pclass];

		model_page_in_textures(pip->model_num, pclass);

		return ade_set_args(L, "o", l_Prop.Set(object_h(&Objects[obj_idx])));
	} else
		return ade_set_error(L, "o", l_Prop.Set(object_h()));
}

ADE_FUNC(createDebris,
	l_Mission,
	"[ship | shipclass | model | submodel | nil source, string | nil submodel_index_or_name, vector position, orientation, enumeration create_flags, "
	"number hitpoints, number spark_timeout_seconds, team, vector explosion_center, number explosion_force_multiplier]",
	"Creates a chunk or shard of debris with the specified parameters.  Vectors are in world coordinates.  Any parameter can be nil or negative to specify defaults.  "
	"A nil source will create generic or vaporized debris; submodel_index_or_name will be disregarded if source is submodel and can be nil to spawn random generic or "
	"vaporized debris; position defaults to 0,0,0; orientation defaults to the source orientation or a random orientation for non-ship sources or for generic/vaporized "
	"debris; create_flags can be any combination of DC_IS_HULL, DC_VAPORIZE, DC_SET_VELOCITY, or DC_FIRE_HOOK; hitpoints defaults to 1/8 source ship hitpoints or 10 "
	"hitpoints if there is no source ship; explosion_center and explosion_force_multiplier are only applicable for DC_SET_VELOCITY",
	"debris",
	"Debris handle, or invalid handle if the debris couldn't be created")
{
	model_h *mh = nullptr;
	submodel_h *smh = nullptr;
	vec3d *pos = nullptr, *explosion_center = nullptr;
	matrix_h *orient_h = nullptr;
	enum_h *create_flags = nullptr;
	float hitpoints = -1.0f, spark_timeout = -1.0f, explosion_force_multiplier = 1.0f;
	int team = -1, source_class = -1;
	object_h *source_ship = nullptr;
	ship *source_shipp = nullptr;
	bool is_hull = false;
	bool vaporize = false;
	bool set_velocity = false;
	bool fire_hook = false;

	int source_objnum = -1, model_num = -1, submodel_num = -1;

	// get first variant parameter
	if (luacpp::convert::ade_odata_is_userdata_type(L, 1, l_Ship))
	{
		ade_get_args(L, "|o", l_Ship.GetPtr(&source_ship));

		if (source_ship == nullptr || !source_ship->isValid())
			return ade_set_args(L, "o", l_Debris.Set(object_h()));

		source_shipp = &Ships[source_ship->objp()->instance];
		source_objnum = source_shipp->objnum;
		source_class = source_shipp->ship_info_index;
		model_num = Ship_info[source_class].model_num;
	}
	else if (luacpp::convert::ade_odata_is_userdata_type(L, 1, l_Shipclass))
	{
		ade_get_args(L, "|o", l_Shipclass.Get(&source_class));

		if (source_class < 0 || source_class >= ship_info_size())
			return ade_set_args(L, "o", l_Debris.Set(object_h()));

		model_num = Ship_info[source_class].model_num;
	}
	else if (luacpp::convert::ade_odata_is_userdata_type(L, 1, l_Model))
	{
		ade_get_args(L, "|o", l_Model.GetPtr(&mh));

		if (mh == nullptr || !mh->isValid())
			return ade_set_args(L, "o", l_Debris.Set(object_h()));

		model_num = mh->GetID();
	}
	else if (luacpp::convert::ade_odata_is_userdata_type(L, 1, l_Submodel))
	{
		ade_get_args(L, "|o", l_Submodel.GetPtr(&smh));

		if (smh == nullptr || !smh->isValid())
			return ade_set_args(L, "o", l_Debris.Set(object_h()));

		model_num = smh->GetModelID();
		submodel_num = smh->GetSubmodelIndex();
	}

	// get second variant parameter
	if (submodel_num < 0 && model_num >= 0)
	{
		if (lua_isnumber(L, 2))
		{
			ade_get_args(L, "|*i", &submodel_num);
			submodel_num--; // Lua --> C/C++
		}
		else if (lua_isstring(L, 2))
		{
			const char *name = nullptr;
			ade_get_args(L, "|*s", &name);
			submodel_num = model_find_submodel_index(model_num, name);
		}
	}

	ade_get_args(L, "|**oooffoof",
		l_Vector.GetPtr(&pos),
		l_Matrix.GetPtr(&orient_h),
		l_Enum.GetPtr(&create_flags),
		&hitpoints,
		&spark_timeout,
		l_Team.Get(&team),
		l_Vector.GetPtr(&explosion_center),
		&explosion_force_multiplier);

	// validate some arguments

	matrix *orient = orient_h == nullptr ? nullptr : orient_h->GetMatrix();

	if (create_flags != nullptr)
	{
		if (!create_flags->isValid() || !create_flags->value)
			return ade_set_args(L, "o", l_Debris.Set(object_h()));

		is_hull = (*create_flags->value & LE_DC_IS_HULL);
		vaporize = (*create_flags->value & LE_DC_VAPORIZE);
		set_velocity = (*create_flags->value & LE_DC_SET_VELOCITY);
		fire_hook = (*create_flags->value & LE_DC_FIRE_HOOK);
	}

	int spark_timeout_millis = spark_timeout < 0.0f ? -1 : fl2i(spark_timeout * MILLISECONDS_PER_SECOND);

	// a submodel and a ship class imply hull debris
	if (submodel_num >= 0 && source_class >= 0)
		is_hull = true;

	// now call the relevant functions

	auto obj = debris_create_only(source_objnum, source_class, -1, team, hitpoints, spark_timeout_millis, model_num, submodel_num, pos, orient, is_hull, vaporize, -1);
	if (obj == nullptr)
		return ade_set_args(L, "o", l_Debris.Set(object_h()));

	if (set_velocity)
		debris_create_set_velocity(&Debris[obj->instance], source_shipp, explosion_center, explosion_force_multiplier);

	if (fire_hook)
		debris_create_fire_hook(obj, source_objnum < 0 ? nullptr : &Objects[source_objnum]);

	return ade_set_args(L, "o", l_Debris.Set(object_h(obj)));
}

ADE_FUNC(createWaypointList, l_Mission, "[string name]",
	"Creates a waypoint list.  If a name is not specified, the list will have a default name.  Be sure to populate the list with at least one waypoint before using it!",
	"waypointlist",
	"Waypoint list handle, or invalid handle if waypoint list couldn't be created")
{
	char buf[NAME_LENGTH];
	const char* name = nullptr;
	if (!ade_get_args(L, "|s", &name))
		return ade_set_error(L, "o", l_WaypointList.Set(waypointlist_h()));

	// use either a specified name...
	if (name)
	{
		auto list = find_matching_waypoint_list(name);
		if (list)
		{
			Warning(LOCATION, "Waypoint list '%s' already exists!", name);
			return ade_set_error(L, "o", l_WaypointList.Set(waypointlist_h()));
		}
		if (strlen(name) > NAME_LENGTH - 1)
		{
			Warning(LOCATION, "Waypoint list name '%s' must be at most %d characters!", name, NAME_LENGTH - 1);
			return ade_set_error(L, "o", l_WaypointList.Set(waypointlist_h()));
		}
		strcpy_s(buf, name);
	}
	// ...or a generated name
	else
	{
		waypoint_find_unique_name(buf, static_cast<int>(Waypoint_lists.size()) + 1);
	}

	// add new list with that name
	Waypoint_lists.emplace_back(buf);
	return ade_set_args(L, "o", l_WaypointList.Set(waypointlist_h(static_cast<int>(Waypoint_lists.size()) - 1)));
}

ADE_FUNC(createWaypoint, l_Mission, "[vector Position, waypointlist List]",
		 "Creates a waypoint.  If Position is not specified, the waypoint will be at (0,0,0).  If List is not specified, a new list will be created and the waypoint will be added to it.",
		 "waypoint",
		 "Waypoint handle, or invalid waypoint handle if waypoint couldn't be created")
{
	vec3d *v3 = nullptr;
	waypointlist_h *wlh = nullptr;
	if(!ade_get_args(L, "|oo", l_Vector.GetPtr(&v3), l_WaypointList.GetPtr(&wlh)))
		return ade_set_error(L, "o", l_Waypoint.Set(object_h()));

	// determine where we need to create it - if we were given a waypoint list, put it at the end
	// (unless the list is empty, in which case put it at the beginning)
	int waypoint_instance = -1;
	bool first_waypoint_in_list = false;
	if (wlh && wlh->isValid())
	{
		int wp_list_index = wlh->wl_index;
		int wp_index;
		if (wlh->getList()->get_waypoints().empty())
		{
			wp_index = 0;
			first_waypoint_in_list = true;
		}
		else
			wp_index = static_cast<int>(wlh->getList()->get_waypoints().size()) - 1;
		waypoint_instance = calc_waypoint_instance(wp_list_index, wp_index);
	}
	int obj_idx = waypoint_add(v3 != nullptr ? v3 : &vmd_zero_vector, waypoint_instance, first_waypoint_in_list);

	if(obj_idx >= 0)
		return ade_set_args(L, "o", l_Waypoint.Set(object_h(obj_idx)));
	else
		return ade_set_args(L, "o", l_Waypoint.Set(object_h()));
}

ADE_FUNC(createWeapon,
	l_Mission,
	"[weaponclass Class /* first weapon in table by default*/, orientation Orientation=identity, vector "
	"WorldPosition/* null vector by default */, object Parent = "
	"nil, number GroupId = -1]",
	"Creates a weapon and returns a handle to it. 'Group' is used for lighting grouping purposes;"
	" for example, quad lasers would only need to act as one light source.  Use generateWeaponGroupId() if you need a group.",
	"weapon",
	"Weapon handle, or invalid weapon handle if weapon couldn't be created.")
{
	int wclass = -1;
	object_h *parent = NULL;
	int group = -1;
	matrix_h *orient = NULL;
	vec3d pos = vmd_zero_vector;
	ade_get_args(L, "|ooooi", l_Weaponclass.Get(&wclass), l_Matrix.GetPtr(&orient), l_Vector.Get(&pos), l_Object.GetPtr(&parent), &group);

	matrix *real_orient = &vmd_identity_matrix;
	if(orient != NULL)
	{
		real_orient = orient->GetMatrix();
	}

	int parent_idx = (parent && parent->isValid()) ? parent->objnum : -1;

	int obj_idx = weapon_create(&pos, real_orient, wclass, parent_idx, group);

	if(obj_idx > -1)
		return ade_set_args(L, "o", l_Weapon.Set(object_h(obj_idx)));
	else
		return ade_set_error(L, "o", l_Weapon.Set(object_h()));
}

ADE_FUNC(generateWeaponGroupId,
	l_Mission,
	nullptr,
	"Generates a weapon group ID to be used with createWeapon.  This is only needed for weapons that should share a light source, such as quad lasers.  Group IDs may be reused by the engine.",
	"number",
	"the group ID")
{
	return ade_set_args(L, "i", weapon_create_group_id());
}

ADE_FUNC(createWarpeffect,
	l_Mission,
	"vector WorldPosition, vector PointTo, number radius, number duration /* Must be >= 4*/, fireballclass Class, "
	"soundentry WarpOpenSound, soundentry WarpCloseSound, "
	"[number WarpOpenDuration = -1, number WarpCloseDuration = -1, vector Velocity /* null vector by default*/, "
	"boolean Use3DModel = false]",
	"Creates a warp-effect fireball and returns a handle to it.",
	"fireball",
	"Fireball handle, or invalid fireball handle if fireball couldn't be created.")
{
	vec3d pos = vmd_zero_vector;
	vec3d point_to = vmd_zero_vector;
	float radius = 0.0f;
	float duration = 4.0f;
	int fireballclass = -1;
	sound_entry_h *opensound = NULL;
	sound_entry_h *closesound = NULL;

	float opentime = -1.0f;
	float closetime = -1.0f;
	vec3d velocity = vmd_zero_vector;
	bool warp3d = false;

	if (!ade_get_args(L, "ooffooo|ffob", l_Vector.Get(&pos), l_Vector.Get(&point_to), &radius, &duration, l_Fireballclass.Get(&fireballclass), l_SoundEntry.GetPtr(&opensound), l_SoundEntry.GetPtr(&closesound), &opentime, &closetime, l_Vector.Get(&velocity), &warp3d)) {
		return ade_set_error(L, "o", l_Fireball.Set(object_h()));
	}

	int flags = warp3d ? FBF_WARP_VIA_SEXP | FBF_WARP_3D : FBF_WARP_VIA_SEXP;

	if (duration < 4.0f) {
		duration = 4.0f;
		LuaError(L, "The duration of the warp effect must be at least 4 seconds");
	}

	// sanity check, if these were specified
	if (duration < opentime + closetime)
	{
		//Both warp opening and warp closing must occur within the duration of the warp effect.
		opentime = closetime = duration / 2.0f;
		LuaError(L, "The duration of the warp effect must be higher than the sum of the opening and close durations");
	}

	// calculate orientation matrix ----------------

	vec3d v_orient;
	matrix m_orient;

	vm_vec_sub(&v_orient, &point_to, &pos);

	if (IS_VEC_NULL_SQ_SAFE(&v_orient))
	{
		//error in warp-effect: warp can't point to itself
		LuaError(L, "The warp effect cannot be pointing at itself");
		return ade_set_error(L, "o", l_Fireball.Set(object_h()));
	}

	vm_vector_2_matrix(&m_orient, &v_orient, nullptr, nullptr);

	int obj_idx = fireball_create(&pos, fireballclass, FIREBALL_WARP_EFFECT, -1, radius, false, &velocity, duration, -1, &m_orient, 0, flags, opensound->idx, closesound->idx, opentime, closetime);

	if (obj_idx > -1)
		return ade_set_args(L, "o", l_Fireball.Set(object_h(obj_idx)));
	else
		return ade_set_error(L, "o", l_Fireball.Set(object_h()));
}

ADE_FUNC(createExplosion,
	l_Mission,
	"vector WorldPosition, number radius, fireballclass Class, "
	"[boolean LargeExplosion = false, vector Velocity /* null vector by default*/, object parent = nil]",
	"Creates an explosion-effect fireball and returns a handle to it.",
	"fireball",
	"Fireball handle, or invalid fireball handle if fireball couldn't be created.")
{
	vec3d pos = vmd_zero_vector;
	float radius = 0.0f;
	int fireballclass = -1;
	bool big = false;

	vec3d velocity = vmd_zero_vector;
	object_h* parent = NULL;

	if (!ade_get_args(L, "ofo|boo", l_Vector.Get(&pos), &radius, l_Fireballclass.Get(&fireballclass), &big, l_Vector.Get(&velocity), l_Object.GetPtr(&parent))) {
		return ade_set_error(L, "o", l_Fireball.Set(object_h()));
	}

	int type = big ? FIREBALL_LARGE_EXPLOSION : FIREBALL_MEDIUM_EXPLOSION;

	int parent_idx = (parent && parent->isValid()) ? parent->objnum : -1;

	int obj_idx = fireball_create(&pos, fireballclass, type, parent_idx, radius, false, &velocity);

	if (obj_idx > -1)
		return ade_set_args(L, "o", l_Fireball.Set(object_h(obj_idx)));
	else
		return ade_set_error(L, "o", l_Fireball.Set(object_h()));
}

ADE_FUNC(createBolt,
	l_Mission,
	"string BoltName, vector Origin, vector Target, [boolean PlaySound = true]",
	"Creates a lightning bolt between the origin and target vectors. BoltName is name of a bolt from lightning.tbl",
	"boolean",
	"True if successful, false if the bolt couldn't be created.")
{
	const char* boltname;
	vec3d origin = vmd_zero_vector;
	vec3d dest = vmd_zero_vector;
	bool playSound = true;

	if (!ade_get_args(L, "soo|b", &boltname, l_Vector.Get(&origin), l_Vector.Get(&dest), &playSound)) {
		return ADE_RETURN_FALSE;
	}
	
	int boltclass = get_bolt_type_by_name(boltname);

	if (boltclass < 0) {
		return ADE_RETURN_FALSE;
	}

	return ade_set_args(L, "b", nebl_bolt(boltclass, &origin, &dest, playSound));
}

ADE_FUNC(getSupportAllowed,
	l_Mission,
	"[boolean SimpleCheck = true]",
	"Get whether or not the player's call for support will be successful. If simple check is false, the code will do a much more "
	"expensive, but accurate check.",
	"boolean",
	"true if support can be called, false if not or not in a mission")
{
	bool simple_check = true;
	ade_get_args(L, "|b", &simple_check);

	// we should be in a mission and the player object must exist
	if (((Game_mode & GM_IN_MISSION) == 0) && (Player_obj != nullptr)) {
		return ade_set_args(L, "b", (bool)is_support_allowed(Player_obj, simple_check));
	}

	return ADE_RETURN_FALSE;
}

ADE_FUNC(getMissionFilename, l_Mission, NULL, "Gets mission filename", "string", "Mission filename, or empty string if game is not in a mission")
{
	char temp[MAX_FILENAME_LEN];

	// per Axem, mn.getMissionFilename() sometimes returns the filename with the .fs2 extension, and sometimes not
	// depending if you are in a campaign or tech room
	strcpy_s(temp, Game_current_mission_filename);
	drop_extension(temp);

	return ade_set_args(L, "s", temp);
}

ADE_FUNC(startMission,
	l_Mission,
	"string|enumeration mission /* MISSION_* */, [boolean Briefing = true]",
	"Starts the defined mission. Pass a filename string or a MISSION_* enumeration.",
	"boolean",
	"True, or false if the function fails")
{
	bool b = true;
	char s[MAX_FILENAME_LEN];
	const char* str = s;

	if(lua_isstring(L, 1))
	{
		if (!ade_get_args(L, "s|b", &str, &b))
			return ade_set_args(L, "b", false);

	} else {
		enum_h *e = NULL;

		if (!ade_get_args(L, "o|b", l_Enum.GetPtr(&e), &b))
			return ade_set_args(L, "b", false);

		if (e->index == LE_MISSION_REPEAT) {
			if (Num_recent_missions > 0)  {
				strcpy_s( s, Recent_missions[0] );
			} else {
				return ade_set_args(L, "b", false);
			}
		} else {
			return ade_set_args(L, "b", false);
		}
	}

	// no filename... bail
	if (str == NULL)
		return ade_set_args(L, "b", false);

	char name_copy[MAX_FILENAME_LEN];
	strcpy_s(name_copy, str);

	// if mission name has extension... it needs to be removed...
	auto file_ext = strrchr(name_copy, '.');
	if (file_ext)
		*file_ext = 0;

	// game is in MP mode... or if the file does not exist... bail
	if ((Game_mode & GM_MULTIPLAYER) || (cf_exists_full(name_copy, CF_TYPE_MISSIONS) != 0))
		return ade_set_args(L, "b", false);

	// mission is already running...
	if (Game_mode & GM_IN_MISSION) {
		// TO DO... All the things needed if this function is called in any state of the game while mission is running.
		//    most likely all require 'stricmp(str, Game_current_mission_filename)' to make sure missions arent mixed
		//    but after that it might be possible to imprement method for jumping directly into already running
		//    missions.
		return ade_set_args(L, "b", false);
		// if mission is not running
	} else {
		// due safety checks of the game_start_mission() function allow only main menu for now.
		if ((gameseq_get_state(gameseq_get_depth()) == GS_STATE_MAIN_MENU) || (gameseq_get_state(gameseq_get_depth()) == GS_STATE_SIMULATOR_ROOM)) {
			strcpy_s(Game_current_mission_filename, name_copy);
			if (b == true) {
				// start mission - go via briefing screen
				gameseq_post_event(GS_EVENT_START_GAME);
			} else {
				// start mission - enter the game directly
				gameseq_post_event(GS_EVENT_START_GAME_QUICK);
			}
			return ade_set_args(L, "b", true);
		}
	}
	return ade_set_args(L, "b", false);
}

ADE_FUNC(getMissionTime, l_Mission, nullptr, "Game time in seconds since the mission was started; is affected by time compression", "number", "Mission time (seconds) of the current or most recently played mission.")
{
	return ade_set_args(L, "x", Missiontime);
}

ADE_VIRTVAR(MissionHUDTimerPadding,
	l_Mission,
	"number",
	"Gets or sets padding currently applied to the HUD mission timer.",
	"number",
	"the padding in seconds")
{
	int pad;

	if (ADE_SETTING_VAR && ade_get_args(L, "*i", &pad)) {
		The_mission.HUD_timer_padding = pad;
	}

	return ade_set_args(L, "i", The_mission.HUD_timer_padding);
}

//WMC - These are in freespace.cpp
ADE_FUNC(loadMission, l_Mission, "string missionName", "Loads a mission", "boolean", "True if mission was loaded, otherwise false")
{
	const char* s;
	if(!ade_get_args(L, "s", &s))
		return ade_set_error(L, "b", false);

	// clear post processing settings
	gr_post_process_set_defaults();

	//NOW do the loading stuff
	if (get_mission_info(s, &The_mission, false))
		return ADE_RETURN_FALSE;

	game_level_init();

	if(!mission_load(s))
		return ADE_RETURN_FALSE;

	game_post_level_init();

	Game_mode |= GM_IN_MISSION;

	return ADE_RETURN_TRUE;
}

ADE_FUNC(unloadMission, l_Mission, "[boolean forceUnload]", "Stops the current mission and unloads it. If forceUnload is true "
	"then the mission unload logic will run regardless of if a mission is loaded or not. Use with caution.", nullptr, nullptr)
{
	bool forceUnload = false;
	ade_get_args(L, "|b", &forceUnload);

	if(forceUnload || Game_mode & GM_IN_MISSION)
	{
		game_level_close();
		Game_mode &= ~GM_IN_MISSION;
		strcpy_s(Game_current_mission_filename, "");
	}

	return ADE_RETURN_NIL;
}

ADE_FUNC(simulateFrame, l_Mission, NULL, "Simulates mission frame", NULL, NULL)
{
	game_update_missiontime();
	game_simulation_frame();

	return ADE_RETURN_TRUE;
}

ADE_FUNC(renderFrame, l_Mission, NULL, "Renders mission frame, but does not move anything", NULL, NULL)
{
	camid cid = game_render_frame_setup();
	game_render_frame( cid );
	game_render_post_frame();

	return ADE_RETURN_TRUE;
}

ADE_FUNC(applyShudder, l_Mission, "number time, number intensity, [boolean perpetual, boolean everywhere]", "Applies a shudder effect to the camera. Time is in seconds. Intensity specifies the shudder effect strength; the Maxim has a value of 1440. If perpetual is true, the shudder does not decay. If everywhere is true, the shudder is applied regardless of view.", "boolean", "true if successful, false otherwise")
{
	float time = -1.0f;
	float intensity = -1.0f;
	bool perpetual = false;
	bool everywhere = false;

	if (!ade_get_args(L, "ff|bb", &time, &intensity, &perpetual, &everywhere))
		return ADE_RETURN_FALSE;

	if (time < 0.0f || intensity < 0.0f)
	{
		LuaError(L, "Illegal shudder values given. Must be bigger than zero; got time of %f and intensity of %f.", time, intensity);
		return ADE_RETURN_FALSE;
	}

	int int_time = fl2i(time * 1000.0f);

	game_shudder_apply(int_time, intensity * 0.01f, perpetual, everywhere);

	return ADE_RETURN_TRUE;
}

ADE_VIRTVAR(ShudderPerpetual, l_Mission, "boolean", "Gets or sets whether the shudder is perpetual, i.e. with a constant intensity that does not decay.", "boolean", "the shudder perpetual flag")
{
	bool perpetual = Game_shudder_perpetual;

	if (ADE_SETTING_VAR && ade_get_args(L, "*b", &perpetual))
	{
		Game_shudder_perpetual = perpetual;
	}

	return ade_set_args(L, "b", perpetual);
}

ADE_VIRTVAR(ShudderEverywhere, l_Mission, "boolean", "Gets or sets whether the shudder is applied everywhere regardless of camera view.", "boolean", "the shudder everywhere flag")
{
	bool everywhere = Game_shudder_everywhere;

	if (ADE_SETTING_VAR && ade_get_args(L, "*b", &everywhere))
	{
		Game_shudder_everywhere = everywhere;
	}

	return ade_set_args(L, "b", everywhere);
}

ADE_VIRTVAR(ShudderTimeLeft, l_Mission, "number", "Gets or sets the number of seconds until the shudder stops.  This is independent of the decay time.", "number", "the shudder time left variable")
{
	float time_left = Game_shudder_time.isValid()
		? timestamp_until(Game_shudder_time) / static_cast<float>(MILLISECONDS_PER_SECOND)
		: 0.0f;

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &time_left))
	{
		if (time_left < 0.0f)
		{
			LuaError(L, "Illegal shudder time left.  Must be bigger than zero; got %f.", time_left);
			time_left = 0.0f;
		}

		Game_shudder_time = _timestamp(static_cast<int>(time_left * MILLISECONDS_PER_SECOND));
	}

	return ade_set_args(L, "f", time_left);
}

ADE_VIRTVAR(ShudderDecayTime, l_Mission, "number", "Gets or sets the shudder decay time in seconds.  This can be zero in which case the shudder will not decay.", "number", "the shudder decay time variable")
{
	float total = Game_shudder_total / static_cast<float>(MILLISECONDS_PER_SECOND);

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &total))
	{
		if (total < 0.0f)
		{
			LuaError(L, "Illegal shudder decay time.  Must be bigger than zero; got %f.", total);
			total = 0.0f;
		}

		Game_shudder_total = static_cast<int>(total * MILLISECONDS_PER_SECOND);
	}

	return ade_set_args(L, "f", total);
}

ADE_VIRTVAR(ShudderIntensity, l_Mission, "number", "Gets or sets the shudder intensity variable.  For comparison, the Maxim has a value of 1440.", "number", "the shudder intensity variable")
{
	float intensity = Game_shudder_intensity * 100.0f;

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &intensity))
	{
		if (intensity < 0.0f)
		{
			LuaError(L, "Illegal shudder intensity.  Must be bigger than zero; got %f.", intensity);
			intensity = 0.0f;
		}

		Game_shudder_intensity = intensity * 0.01f;
	}

	return ade_set_args(L, "f", intensity);
}

ADE_VIRTVAR(Gravity, l_Mission, "vector", "Gravity acceleration vector in meters / second^2", "vector", "gravity vector")
{
	vec3d* gravity_vec = &The_mission.gravity;

	if (ADE_SETTING_VAR && ade_get_args(L, "*o", l_Vector.GetPtr(&gravity_vec)))
	{
		vec3d old_gravity = The_mission.gravity;
		The_mission.gravity = *gravity_vec;

		if ((IS_VEC_NULL(&old_gravity) && !IS_VEC_NULL(&The_mission.gravity)) || 
			(!IS_VEC_NULL(&old_gravity) && IS_VEC_NULL(&The_mission.gravity))) {
			// gravity was turned on or turned off
			collide_apply_gravity_flags_weapons();
		}
	}

	return ade_set_args(L, "o", l_Vector.Set(The_mission.gravity));
}

ADE_VIRTVAR(CustomData, l_Mission, nullptr, "Gets the custom data table for this mission", "table", "The mission's custom data table") 
{
	if (ADE_SETTING_VAR) {
		LuaError(L, "Setting Custom Data is not supported");
	}

	auto table = luacpp::LuaTable::create(L);

	for (const auto& pair : The_mission.custom_data)
	{
		table.addValue(pair.first, pair.second);
	}

	return ade_set_args(L, "t", &table);	
}

ADE_FUNC(hasCustomData, l_Mission, nullptr, "Detects whether the mission has any custom data", "boolean", "true if the mission's custom_data is not empty, false otherwise") 
{

	bool result = !The_mission.custom_data.empty();
	return ade_set_args(L, "b", result);
}

ADE_FUNC(addDefaultCustomData,
	l_Mission,
	"string key, string value, string description",
	"Adds a custom data pair with the given key if it's unique. Only works in FRED! The description will be displayed in the FRED custom data editor.",
	"boolean",
	"returns true if sucessful, false otherwise. Returns nil if not running in FRED.")
{
	const char* key;
	const char* value;
	const char* desc;
	if (!ade_get_args(L, "sss", &key, &value, &desc))
		return ade_set_error(L, "o", l_LuaEnum.Set(lua_enum_h()));

	// defaults are not ever used in gameplay, but only as a convenience for FREDers
	// so if we're not in FRED we can skip all of this.
	if (!Fred_running) {
		return ADE_RETURN_NIL;
	}

	for (const auto& pair : Default_custom_data) {
		if (key == pair.key) {
			return ADE_RETURN_FALSE;
		}
	}

	mission_default_custom_data data = {key, value, desc};

	Default_custom_data.push_back(data);

	return ADE_RETURN_TRUE;
}

ADE_VIRTVAR(CustomStrings, l_Mission, nullptr, "Gets the indexed custom data table for this mission. Each item in the table is a table with the following values: "
	"Name - the name of the custom string, Value - the value associated with the custom string, String - the custom string itself.", "table", "The mission's custom data table") 
{
	if (ADE_SETTING_VAR) {
		LuaError(L, "Setting Custom Data is not supported");
	}

	auto table = luacpp::LuaTable::create(L);

	int cnt = 0;

	for (const auto& cs : The_mission.custom_strings)
	{
		cnt++;
		auto item = luacpp::LuaTable::create(L);

		item.addValue("Name", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs.name));
		item.addValue("Value", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs.value));
		item.addValue("String", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs.text));


		table.addValue(cnt, item);
	}

	return ade_set_args(L, "t", &table);	
}

ADE_FUNC(hasCustomStrings, l_Mission, nullptr, "Detects whether the mission has any custom strings", "boolean", "true if the mission's custom_strings is not empty, false otherwise") 
{

	bool result = !The_mission.custom_strings.empty();
	return ade_set_args(L, "b", result);
}

ADE_FUNC(isInMission, l_Mission, nullptr, "get whether or not a mission is currently being played", "boolean", "true if in mission, false otherwise")
{
	return ade_set_args(L, "b", (Game_mode & GM_IN_MISSION) != 0);
}

ADE_FUNC(isPrePlayerEntry, l_Mission, nullptr, "get whether the mission is currently in the pre-player-entry state", "boolean", "true if in pre-player-entry, false otherwise")
{
	return ade_set_args(L, "b", Pre_player_entry);
}

ADE_FUNC(isInCampaign, l_Mission, NULL, "Get whether or not the current mission being played in a campaign (as opposed to the tech room's simulator)", "boolean", "true if in campaign, false if not")
{
	bool b = false;

	if (Game_mode & GM_CAMPAIGN_MODE) {
		b = true;
	}

	return ade_set_args(L, "b", b);
}

ADE_FUNC(isInCampaignLoop, l_Mission, nullptr, "Get whether or not the current mission being played is a loop mission in the context of a campaign", "boolean", "true if in loop and campaign, false if not")
{
	return ade_set_args(L, "b", (Campaign.loop_enabled) && (Game_mode & GM_CAMPAIGN_MODE));
}

ADE_FUNC(isTraining, l_Mission, nullptr, "Get whether or not the current mission being played is a training mission", "boolean", "true if in training, false if not")
{
	return ade_set_args(L, "b", (The_mission.game_type & MISSION_TYPE_TRAINING) != 0);
}

ADE_FUNC(isScramble, l_Mission, nullptr, "Get whether or not the current mission being played is a scramble mission", "boolean", "true if scramble, false if not")
{
	return ade_set_args(L, "b", (brief_only_allow_briefing() == 1));
}

ADE_FUNC(isMissionSkipAllowed, l_Mission, nullptr, "Get whether or not the player has reached the failure limit", "boolean", "true if limit reached, false if not")
{
	return ade_set_args(L, "b", (Player->failures_this_session >= PLAYER_MISSION_FAILURE_LIMIT));
}

ADE_FUNC(hasNoBriefing, l_Mission, nullptr, "Get whether or not the mission is set to skip the briefing", "boolean", "true if it should be skipped, false if not")
{
	return ade_set_args(L, "b", (The_mission.flags[Mission::Mission_Flags::No_briefing]));
}

ADE_FUNC(isNebula, l_Mission, nullptr, "Get whether or not the current mission being played is set in a nebula", "boolean", "true if in nebula, false if not")
{
	return ade_set_args(L, "b", The_mission.flags[Mission::Mission_Flags::Fullneb]);
}

ADE_FUNC(hasVolumetricNebula, l_Mission, nullptr, "Get whether or not the current mission being played contains a volumetric nebula", "boolean", "true if has a volumetric nebula, false if not")
{
	return ade_set_args(L, "b", static_cast<bool>(The_mission.volumetrics));
}

ADE_VIRTVAR(VolumetricNebula, l_Mission, nullptr, "Gets the mission volumetric nebula handle if present.", "volumetric_nebula", "Volumetric nebula handle, or invalid handle if no volumetric nebula is present")
{
	if (!The_mission.volumetrics) {
		return ade_set_error(L, "o", l_Volumetric.Set(volumetric_h()));
	}

	return ade_set_args(L, "o", l_Volumetric.Set(volumetric_h(1)));
}

ADE_VIRTVAR(NebulaSensorRange, l_Mission, "number", "Gets or sets the Neb2_awacs variable.  This is multiplied by a species-specific factor to get the \"scan range\".  Within the scan range, a ship is at least partially targetable (fuzzy blip); within half the scan range, a ship is fully targetable.  Beyond the scan range, a ship is not targetable.", "number", "the Neb2_awacs variable")
{
	float range = -1.0f;

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &range))
		Neb2_awacs = range;

	return ade_set_args(L, "f", Neb2_awacs);
}

ADE_VIRTVAR_DEPRECATED(NebulaNearMult, l_Mission, "number", "Gets or sets the multiplier of the near plane of the current nebula.", "number", "The multiplier of the near plane.", gameversion::version(26, 0), "Deprecated in favor of NebulaNearDistance.")
{
	float fog_near = 0.0f;

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &fog_near))
		Neb2_fog_near_distance = fog_near * 10.f;

	return ade_set_args(L, "f", Neb2_fog_near_distance / 10.f);
}

ADE_VIRTVAR_DEPRECATED(NebulaFarMult, l_Mission, "number", "Gets or sets the multiplier of the far plane of the current nebula.", "number", "The multiplier of the far plane.", gameversion::version(26, 0), "Deprecated. Had no effect.")
{
	float fog_far = 0.0f;

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &fog_far)) {}

	return ade_set_args(L, "f", 1.f);
}

ADE_VIRTVAR(NebulaNearDistance, l_Mission, "number", "Gets or sets the distance of the near plane of the current nebula.", "number", "The distance of the near plane.")
{
	float fog_near = 0.0f;

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &fog_near))
		Neb2_fog_near_distance = fog_near;

	return ade_set_args(L, "f", Neb2_fog_near_distance);
}

ADE_VIRTVAR(NebulaClipDistance, l_Mission, "number", "Gets or sets the distance after which the nebula does not get thicker.", "number", "The clip distance of the nebula.")
{
	float fog_clip = 0.0f;

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &fog_clip))
		Neb2_fog_clip_distance = std::max(0.f, fog_clip);

	return ade_set_args(L, "f", Neb2_fog_clip_distance);
}

ADE_VIRTVAR(NebulaSkyboxClipDistance, l_Mission, "number", "Gets or sets the distance of the skybox to the camera for the purposes of fogging.", "number", "The clip distance of the nebula for skyboxes.")
{
	float fog_clip = 0.0f;

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &fog_clip))
		Neb2_fog_skybox_clip_distance = std::max(0.f, fog_clip);

	return ade_set_args(L, "f", Neb2_fog_skybox_clip_distance);
}

ADE_VIRTVAR(NebulaVisibility, l_Mission, "number", "Gets or sets the visibility of the nebula at 1000m nebula depth. 0 is fully opaque, 1 is fully transparent.", "number", "The nebula visibility.")
{
	float fog_visibility = 0.0f;

	if (ADE_SETTING_VAR && ade_get_args(L, "*f", &fog_visibility))
		Neb2_fog_1000m_visibility = std::clamp(fog_visibility, 0.0f, 1.0f);

	return ade_set_args(L, "f", Neb2_fog_1000m_visibility);
}

ADE_FUNC(isSubspace, l_Mission, nullptr, "Get whether or not the current mission being played is set in subspace", "boolean", "true if in subspace, false if not")
{
	return ade_set_args(L, "b", The_mission.flags[Mission::Mission_Flags::Subspace]);
}

ADE_FUNC(getMissionTitle, l_Mission, NULL, "Get the title of the current mission", "string", "The mission title or an empty string if currently not in mission") {
	return ade_set_args(L, "s", The_mission.name);
}

ADE_FUNC(getMissionModifiedDate, l_Mission, NULL, "Get the modified date of the current mission", "string", "The mission modified date or an empty string if currently not in mission") {
	return ade_set_args(L, "s", The_mission.modified);
}

//****SUBLIBRARY: Mission/BackgroundSuns
ADE_LIB_DERIV(l_Mission_BackgroundSuns, "BackgroundSuns", nullptr, "Suns in the current background", l_Mission);

ADE_INDEXER(l_Mission_BackgroundSuns, "number Index", "Gets background sun at specified index in current background", "background_element", "Specified background element, or invalid handle if invalid index")
{
	int idx;
	if (!ade_get_args(L, "*i", &idx))
		return ade_set_error(L, "o", l_BackgroundElement.Set(background_el_h()));

	if (idx < 1 || idx > stars_get_num_suns())
		return ade_set_error(L, "o", l_BackgroundElement.Set(background_el_h()));

	//Lua->FS2
	idx--;

	return ade_set_args(L, "o", l_BackgroundElement.Set(background_el_h(BackgroundType::Sun, idx)));
}

ADE_FUNC(__len, l_Mission_BackgroundSuns, nullptr, "Number of suns in the current background", "number", "Number of suns")
{
	return ade_set_args(L, "i", stars_get_num_suns());
}

//****SUBLIBRARY: Mission/BackgroundBitmaps
ADE_LIB_DERIV(l_Mission_BackgroundBitmaps, "BackgroundBitmaps", nullptr, "Bitmaps in the current background", l_Mission);

ADE_INDEXER(l_Mission_BackgroundBitmaps, "number Index", "Gets background bitmap at specified index in current background", "background_element", "Specified background element, or invalid handle if invalid index")
{
	int idx;
	if (!ade_get_args(L, "*i", &idx))
		return ade_set_error(L, "o", l_BackgroundElement.Set(background_el_h()));

	if (idx < 1 || idx > stars_get_num_bitmaps())
		return ade_set_error(L, "o", l_BackgroundElement.Set(background_el_h()));

	//Lua->FS2
	idx--;

	return ade_set_args(L, "o", l_BackgroundElement.Set(background_el_h(BackgroundType::Bitmap, idx)));
}

ADE_FUNC(__len, l_Mission_BackgroundBitmaps, nullptr, "Number of bitmaps in the current background", "number", "Number of bitmaps")
{
	return ade_set_args(L, "i", stars_get_num_bitmaps());
}

static int addBackgroundBitmap_sub(bool uses_correct_angles, lua_State* L)
{
	const char* filename = nullptr;
	float scale_x        = 1.0f;
	float scale_y        = 1.0f;
	int div_x            = 1;
	int div_y            = 1;
	matrix_h orient(&vmd_identity_matrix);

	if (!ade_get_args(L, "s|offii", &filename, l_Matrix.Get(&orient), &scale_x, &scale_y, &div_x, &div_y)) {
		return ade_set_error(L, "o", l_BackgroundElement.Set(background_el_h()));
	}

	starfield_list_entry sle;
	strcpy_s(sle.filename, filename);

	// sanity checking
	if (stars_find_bitmap(sle.filename) < 0) {
		LuaError(L, "Background bitmap %s not found!", sle.filename);
		return ade_set_error(L, "o", l_BackgroundElement.Set(background_el_h()));
	}

	sle.ang     = *orient.GetAngles();
	if (!uses_correct_angles)
		stars_correct_background_bitmap_angles(&sle.ang);

	sle.scale_x = scale_x;
	sle.scale_y = scale_y;
	sle.div_x   = div_x;
	sle.div_y   = div_y;

	// restrict parameters (same code as used by FRED)
	if (sle.scale_x > 18)
		sle.scale_x = 18;
	if (sle.scale_x < 0.1f)
		sle.scale_x = 0.1f;
	if (sle.scale_y > 18)
		sle.scale_y = 18;
	if (sle.scale_y < 0.1f)
		sle.scale_y = 0.1f;
	if (sle.div_x > 5)
		sle.div_x = 5;
	if (sle.div_x < 1)
		sle.div_x = 1;
	if (sle.div_y > 5)
		sle.div_y = 5;
	if (sle.div_y < 1)
		sle.div_y = 1;

	auto idx = stars_add_bitmap_entry(&sle);
	return ade_set_args(L, "o", l_BackgroundElement.Set(background_el_h(BackgroundType::Bitmap, idx)));
}

ADE_FUNC_DEPRECATED(addBackgroundBitmap,
	l_Mission,
	"string name, orientation orientation = identity, number scaleX = 1.0, number scale_y = 1.0, number div_x = 1.0, "
	"number div_y = 1.0",
	"Adds a background bitmap to the mission with the specified parameters, but using the old incorrectly-calculated angle math.",
	"background_element",
	"A handle to the background element, or invalid handle if the function failed.",
	gameversion::version(22, 2),
	"addBackgroundBitmap uses the old incorrectly-calculated angle math; use addBackgroundBitmapNew instead")
{
	return addBackgroundBitmap_sub(false, L);
}

ADE_FUNC(addBackgroundBitmapNew,
	l_Mission,
	"string name, orientation orientation = identity, number scaleX = 1.0, number scale_y = 1.0, number div_x = 1.0, "
	"number div_y = 1.0",
	"Adds a background bitmap to the mission with the specified parameters, treating the angles as correctly calculated.",
	"background_element",
	"A handle to the background element, or invalid handle if the function failed.")
{
	return addBackgroundBitmap_sub(true, L);
}

static int addSunBitmap_sub(bool uses_correct_angles, lua_State* L)
{
	const char* filename = nullptr;
	float scale_x        = 1.0f;
	float scale_y        = 1.0f;
	matrix_h orient(&vmd_identity_matrix);

	if (!ade_get_args(L, "s|off", &filename, l_Matrix.Get(&orient), &scale_x, &scale_y)) {
		return ade_set_error(L, "o", l_BackgroundElement.Set(background_el_h()));
	}

	starfield_list_entry sle;
	strcpy_s(sle.filename, filename);

	// sanity checking
	if (stars_find_sun(sle.filename) < 0) {
		LuaError(L, "Background bitmap %s not found!", sle.filename);
		return ade_set_error(L, "o", l_BackgroundElement.Set(background_el_h()));
	}

	sle.ang     = *orient.GetAngles();
	if (!uses_correct_angles)
		stars_correct_background_sun_angles(&sle.ang);

	sle.scale_x = scale_x;
	sle.scale_y = scale_y;
	sle.div_x   = 1;
	sle.div_y   = 1;

	// restrict parameters (same code as used by FRED)
	if (sle.scale_x > 18)
		sle.scale_x = 18;
	if (sle.scale_x < 0.1f)
		sle.scale_x = 0.1f;
	if (sle.scale_y > 18)
		sle.scale_y = 18;
	if (sle.scale_y < 0.1f)
		sle.scale_y = 0.1f;

	auto idx = stars_add_sun_entry(&sle);
	return ade_set_args(L, "o", l_BackgroundElement.Set(background_el_h(BackgroundType::Sun, idx)));
}

ADE_FUNC_DEPRECATED(addSunBitmap,
	l_Mission,
	"string name, orientation orientation = identity, number scaleX = 1.0, number scale_y = 1.0",
	"Adds a sun bitmap to the mission with the specified parameters, but using the old incorrectly-calculated angle math.",
	"background_element",
	"A handle to the background element, or invalid handle if the function failed.",
	gameversion::version(22, 2),
	"addSunBitmap uses the old incorrectly-calculated angle math; use addSunBitmapNew instead")
{
	return addSunBitmap_sub(false, L);
}

ADE_FUNC(addSunBitmapNew,
	l_Mission,
	"string name, orientation orientation = identity, number scaleX = 1.0, number scale_y = 1.0",
	"Adds a sun bitmap to the mission with the specified parameters, treating the angles as correctly calculated.",
	"background_element",
	"A handle to the background element, or invalid handle if the function failed.")
{
	return addSunBitmap_sub(true, L);
}

ADE_FUNC(removeBackgroundElement, l_Mission, "background_element el",
         "Removes the background element specified by the handle. The handle must have been returned by either "
         "addBackgroundBitmap or addBackgroundSun. This handle will be invalidated by this function.",
         "boolean", "true if successful")
{
	background_el_h* el = nullptr;
	if (!ade_get_args(L, "o", l_BackgroundElement.GetPtr(&el))) {
		return ADE_RETURN_FALSE;
	}

	if (!el->isValid()) {
		return ADE_RETURN_FALSE;
	}

	if (el->type == BackgroundType::Bitmap) {
		int instances = stars_get_num_bitmaps();
		if (instances > el->id) {
			stars_mark_bitmap_unused(el->id);
		} else {
			LuaError(L, "Background slot %d does not exist. Slot must be less than %d.", el->id, instances);
			return ADE_RETURN_FALSE;
		}
		return ADE_RETURN_TRUE;
	} else if (el->type == BackgroundType::Sun) {
		int instances = stars_get_num_suns();
		if (instances > el->id) {
			stars_mark_sun_unused(el->id);
		} else {
			LuaError(L, "Background slot %d does not exist. Slot must be less than %d.", el->id, instances);
			return ADE_RETURN_FALSE;
		}
		return ADE_RETURN_TRUE;
	} else {
		return ADE_RETURN_FALSE;
	}
}

ADE_VIRTVAR(SkyboxOrientation, l_Mission, "orientation", "Sets or returns the current skybox orientation", "orientation", "the orientation")
{
	matrix_h* orient_h = nullptr;
	if (ADE_SETTING_VAR && ade_get_args(L, "*|o", l_Matrix.GetPtr(&orient_h)))
		stars_set_background_orientation(orient_h->GetMatrix());

	return ade_set_args(L, "o", l_Matrix.Set(matrix_h(&Nmodel_orient)));
}

ADE_VIRTVAR(SkyboxAlpha, l_Mission, "number", "Sets or returns the current skybox alpha", "number", "the alpha")
{
	float alpha = 1.0f;
	if (ADE_SETTING_VAR && ade_get_args(L, "*|f", &alpha))
		stars_set_background_alpha(alpha);

	return ade_set_args(L, "f", Nmodel_alpha);
}

ADE_VIRTVAR(Skybox, l_Mission, "model", "Sets or returns the current skybox model", "model", "The skybox model")
{
	model_h* model = nullptr;
	if (!ade_get_args(L, "*|o", l_Model.GetPtr(&model)))
		return ade_set_error(L, "o", l_Model.Set(model_h()));

	if (ADE_SETTING_VAR && model && model->isValid()) {
		stars_set_background_model(model->GetID(), -1, Nmodel_flags, Nmodel_alpha);
	}

	return ade_set_args(L, "o", l_Model.Set(model_h(Nmodel_num)));
}

ADE_FUNC(getSkyboxInstance, l_Mission, nullptr, "Returns the current skybox model instance", "model_instance", "The skybox model instance")
{
	return ade_set_args(L, "o", l_ModelInstance.Set(modelinstance_h(Nmodel_instance_num)));
}

ADE_FUNC(isRedAlertMission,
	l_Mission,
	nullptr,
	"Determines if the current mission is a red alert mission",
	"boolean",
	"true if red alert mission, false otherwise.")
{
	return ade_set_args(L, "b", red_alert_mission() != 0);
}

ADE_FUNC(hasCommandBriefing,
	l_Mission,
	nullptr,
	"Determines if the current mission has a command briefing",
	"boolean",
	"true if command briefing, false otherwise.")
{
	return ade_set_args(L, "b", mission_has_cmd_brief() != 0);
}

ADE_FUNC(hasGoalsStage,
	l_Mission,
	nullptr,
	"Determines if the current mission will show a Goals briefing stage",
	"boolean",
	"true if stage is active, false otherwise.")
{
	bool goals = The_mission.flags[Mission::Mission_Flags::Toggle_showing_goals] == !!(The_mission.game_type & MISSION_TYPE_TRAINING);
	
	return ade_set_args(L, "b", goals);
}

ADE_FUNC(hasDebriefing,
	l_Mission,
	nullptr,
	"Determines if the current mission has a debriefing",
	"boolean",
	"true if debriefing, false otherwise.")
{
	return ade_set_args(L, "b", !(The_mission.flags[Mission::Mission_Flags::Toggle_debriefing]));
}

ADE_FUNC(getMusicScore, l_Mission, "enumeration score /* SCORE_* */", "Returns the music.tbl entry name for the specified mission music score", "string", "The name, or nil if the score is invalid")
{
	enum_h score;
	if (!ade_get_args(L, "o", l_Enum.Get(&score)))
		return ADE_RETURN_NIL;

	if (!score.isValid() || score.index < LE_SCORE_BRIEFING || score.index > LE_SCORE_FICTION_VIEWER)
	{
		Warning(LOCATION, "Invalid music score index %d", score.index);
		return ADE_RETURN_NIL;
	}

	int spooled_index = Mission_music[score.index - LE_SCORE_BRIEFING];

	const char *name = nullptr;
	if (spooled_index >= 0 && (size_t)spooled_index < Spooled_music.size())
		name = Spooled_music[spooled_index].name;

	return ade_set_args(L, "s", name);
}

ADE_FUNC(setMusicScore, l_Mission, "enumeration score /* SCORE_* */, string name", "Sets the music.tbl entry for the specified mission music score", nullptr, nullptr)
{
	enum_h score;
	const char *name;
	if (!ade_get_args(L, "os", l_Enum.Get(&score), &name))
		return ADE_RETURN_NIL;

	if (!score.isValid() || score.index < LE_SCORE_BRIEFING || score.index > LE_SCORE_FICTION_VIEWER)
	{
		Warning(LOCATION, "Invalid music score index %d", score.index);
		return ADE_RETURN_NIL;
	}

	event_music_set_score(score.index - LE_SCORE_BRIEFING, name);

	return ADE_RETURN_TRUE;
}

int testLineOfSight_internal(lua_State* L, bool returnDist_and_Obj) {
	vec3d from, to;
	luacpp::LuaTable excludedObjects;
	bool testForShields = false, testForHull = true;
	float threshold = 10.0f;

	float distStore = 0.0f;
	float* dist = returnDist_and_Obj ? &distStore : nullptr;
	object* intersecting_obj = nullptr;

	if (!ade_get_args(L, "oo|tbbf", l_Vector.Get(&from), l_Vector.Get(&to), &excludedObjects, &testForShields, &testForHull, &threshold)) {
		return ADE_RETURN_FALSE;
	}
	if (!(testForHull || testForShields)) {
		LuaError(L, "Cannot test line of sight if neither hull nor shields are set to be tested for.");
		//Though it's technically a "line of sight", so return true
		return ADE_RETURN_TRUE;
	}

	SCP_unordered_set<int> excludedObjectIDs;

	if (excludedObjects.isValid()) {
		for (const auto& object : excludedObjects) {
			if (object.second.is(luacpp::ValueType::USERDATA)) {
				// This'll lua-error internally if it's not fed only objects. Additionally, catch the lua exception and then carry on
				try {
					object_h obj;
					object.second.getValue(l_Object.Get(&obj));
					excludedObjectIDs.emplace(obj.objnum);
				}
				catch (const luacpp::LuaException& /*e*/) {
					// We were likely fed a userdata that was not an object. 
					// Since we can't actually tell whether that's the case before we try to get the value, and the attempt to get the value is printing a LuaError itself, just eat the exception here and return
					return ADE_RETURN_FALSE;
				}
			}
			else {
				//This happens on a non-userdata value, i.e. a number
				LuaError(L, "Table with objects to be excluded contained non-userdata values! Aborting...");
				return ADE_RETURN_FALSE;
			}
		}
	}

	bool hasLoS = test_line_of_sight(&from, &to, std::move(excludedObjectIDs), threshold, testForShields, testForHull, dist, &intersecting_obj);

	if (returnDist_and_Obj)
		return ade_set_args(L, "bfo", hasLoS, *dist, ade_object_to_odata(OBJ_INDEX(intersecting_obj)));
	else
		return ade_set_args(L, "b", hasLoS);
}

ADE_FUNC(hasLineOfSight, l_Mission, "vector from, vector to, [table excludedObjects /* expects list of objects, empty by default */, boolean testForShields = false, boolean testForHull = true, number threshold = 10.0]", "Checks whether the to-position is in line of sight from the from-position, disregarding specific excluded objects and objects with a radius of less then threshold.", "boolean", "true if there is line of sight, false otherwise.")
{
	return testLineOfSight_internal(L, false);
}

ADE_FUNC(getLineOfSightFirstIntersect, l_Mission, "vector from, vector to, [table excludedObjects /* expects list of objects, empty by default */, boolean testForShields = false, boolean testForHull = true, number threshold = 10.0]", "Checks whether the to-position is in line of sight from the from-position and returns the distance and intersecting object to the first interruption of the line of sight, disregarding specific excluded objects and objects with a radius of less then threshold.", "boolean, number, object", "true and zero and nil if there is line of sight, false and the distance and intersecting object otherwise.")
{
	return testLineOfSight_internal(L, true);
}

ADE_FUNC(getSpecialSubmodelAnimation, l_Mission, "string target, string type, string triggeredBy",
	"Gets an animation handle. Target is the object that should be animated (one of \"cockpit\", \"skybox\"), type is the string name of the animation type, "
	"triggeredBy is a closer specification which animation should trigger. See *-anim.tbm specifications. ",
	"animation_handle",
	"The animation handle for the specified animation, nil if invalid arguments.")
{
	const char* target = nullptr;
	const char* type = nullptr;
	const char* trigger = nullptr;

	if (!ade_get_args(L, "sss", &target, &type, &trigger))
		return ADE_RETURN_NIL;

	polymodel_instance* pmi;
	animation::ModelAnimationSet* set;

	if (stricmp(target, "cockpit") == 0) {
		if (Player_ship == nullptr || Player_ship->cockpit_model_instance < 0)
			return ade_set_args(L, "o", l_AnimationHandle.Set(animation::ModelAnimationSet::AnimationList{}));
		pmi = model_get_instance(Player_ship->cockpit_model_instance);
		set = &Ship_info[Player_ship->ship_info_index].cockpit_animations;
	}
	else if (stricmp(target, "skybox") == 0) {
		if(Nmodel_instance_num < 0)
			return ade_set_args(L, "o", l_AnimationHandle.Set(animation::ModelAnimationSet::AnimationList{}));
		pmi = model_get_instance(Nmodel_instance_num);
		set = &The_mission.skybox_model_animations;
	}
	else {
		return ADE_RETURN_NIL;
	}

	auto animtype = animation::anim_match_type(type);
	if (animtype == animation::ModelAnimationTriggerType::None)
		return ade_set_args(L, "o", l_AnimationHandle.Set(animation::ModelAnimationSet::AnimationList{}));

	return ade_set_args(L, "o", l_AnimationHandle.Set(set->parseScripted(pmi, animtype, trigger)));
}

ADE_FUNC(updateSpecialSubmodelMoveable, l_Mission, "string target, string name, table values",
	"Updates a moveable animation. Name is the name of the moveable. For what values needs to contain, please refer to the table below, depending on the type of the moveable:"
	"Orientation:\r\n"
	"\tThree numbers, x, y, z rotation respectively, in degrees\r\n"
	"Rotation:\r\n"
	"\tThree numbers, x, y, z rotation respectively, in degrees\r\n"
	"Axis Rotation:\r\n"
	"\tOne number, rotation angle in degrees\r\n"
	"Inverse Kinematics:\r\n"
	"\tThree required numbers: x, y, z position target relative to base, in 1/100th meters\r\n"
	"\tThree optional numbers: x, y, z rotation target relative to base, in degrees\r\n",
	"boolean",
	"True if successful, false or nil otherwise")
{
	const char* target = nullptr;
	const char* name = nullptr;
	luacpp::LuaTable values;

	if (!ade_get_args(L, "sst", &target, &name, &values))
		return ADE_RETURN_NIL;

	polymodel_instance* pmi;
	animation::ModelAnimationSet* set;

	if (stricmp(target, "cockpit") == 0) {
		if (Player_ship == nullptr || Player_ship->cockpit_model_instance < 0)
			return ADE_RETURN_NIL;
		pmi = model_get_instance(Player_ship->cockpit_model_instance);
		set = &Ship_info[Player_ship->ship_info_index].cockpit_animations;
	}
	else if (stricmp(target, "skybox") == 0) {
		if (Nmodel_instance_num < 0)
			return ADE_RETURN_NIL;
		pmi = model_get_instance(Nmodel_instance_num);
		set = &The_mission.skybox_model_animations;
	}
	else {
		return ADE_RETURN_NIL;
	}

	SCP_vector<std::any> valuesMoveable;

	if (values.isValid()) {
		for (const auto& object : values) {
			if (object.second.is(luacpp::ValueType::NUMBER)) {
				// This'll lua-error internally if it's not fed only objects. Additionally, catch the lua exception and then carry on
				try {
					valuesMoveable.emplace_back(object.second.getValue<int>());
				}
				catch (const luacpp::LuaException& /*e*/) {
					// We were likely fed a float. 
					// Since we can't actually tell whether that's the case before we try to get the value, and the attempt to get the value is printing a LuaError itself, just eat the exception here and return
					return ADE_RETURN_FALSE;
				}
			}
			else {
				//This happens on a non-userdata value, i.e. a number
				LuaError(L, "Value table contained non-numbers! Aborting...");
				return ADE_RETURN_FALSE;
			}
		}
	}

	return set->updateMoveable(pmi, name, valuesMoveable) ? ADE_RETURN_TRUE : ADE_RETURN_FALSE;
}

ADE_LIB_DERIV(l_Mission_LuaEnums, "LuaEnums", nullptr, "Lua Enums", l_Mission);

ADE_INDEXER(l_Mission_LuaEnums,
	"string/number NameOrIndex",
	"Gets a handle of a Lua Enum",
	"LuaEnum",
	"Lua Enum handle or invalid handle on error")
{
	const char* name = nullptr;
	if (!ade_get_args(L, "*s", &name)) {
		return ade_set_error(L, "o", l_LuaEnum.Set(lua_enum_h()));
	}

	if (name == nullptr) {
		return ade_set_error(L, "o", l_LuaEnum.Set(lua_enum_h()));
	}

	int idx = get_dynamic_enum_position(name);

	if (idx > -1) {
		return ade_set_args(L, "o", l_LuaEnum.Set(lua_enum_h(idx)));
	} else {
		idx = atoi(name);

		if ((idx > 0) && (idx < (int)Dynamic_enums.size())) {
			return ade_set_args(L, "o", l_LuaEnum.Set(lua_enum_h(idx)));
		}
	}

	return ade_set_args(L, "o", l_LuaEnum.Set(lua_enum_h()));
}

ADE_FUNC(__len, l_Mission_LuaEnums, nullptr, "The number of Lua enums", "number", "The number of Lua enums.")
{
	return ade_set_args(L, "i", (int)Dynamic_enums.size());
}

ADE_FUNC(addLuaEnum,
	l_Mission,
	"string name",
	"Adds an enum with the given name if it's unique.",
	"LuaEnum",
	"Returns the enum handle or an invalid handle if the name was not unique.")
{
	const char* enum_name;
	if (!ade_get_args(L, "s", &enum_name))
		return ade_set_error(L, "o", l_LuaEnum.Set(lua_enum_h()));

	int idx = get_dynamic_enum_position(enum_name);

	if (idx > -1) {
		return ade_set_error(L, "o", l_LuaEnum.Set(lua_enum_h()));
	} else {
		dynamic_sexp_enum_list this_list;
		this_list.name = enum_name;

		Dynamic_enums.push_back(std::move(this_list));

		idx = get_dynamic_enum_position(enum_name);

		return ade_set_args(L, "o", l_LuaEnum.Set(lua_enum_h(idx)));
	}
}

ADE_LIB_DERIV(l_Mission_LuaSEXPs, "LuaSEXPs", nullptr, "Lua SEXPs", l_Mission);

ADE_INDEXER(l_Mission_LuaSEXPs, "string Name", "Gets a handle of a Lua SEXP", "LuaSEXP", "Lua SEXP handle or invalid handle on error")
{
	const char* name = nullptr;
	if( !ade_get_args(L, "*s", &name) ) {
		return ade_set_error( L, "o", l_LuaSEXP.Set( lua_sexp_h() ) );
	}

	if (name == nullptr) {
		return ade_set_error(L, "o", l_LuaSEXP.Set(lua_sexp_h()));
	}

	if (ADE_SETTING_VAR) {
		LuaError(L, "Setting of Lua SEXPs is not supported!");
	}

	auto op = get_operator_const(name);

	if (op == 0) {
		LuaError(L, "SEXP '%s' is not known to the SEXP system!", name);
		return ade_set_args(L, "o", l_LuaSEXP.Set(lua_sexp_h()));
	}

	auto dynamicSEXP = sexp::get_dynamic_sexp(op);

	if (dynamicSEXP == nullptr) {
		return ade_set_args(L, "o", l_LuaSEXP.Set(lua_sexp_h()));
	}

	if (typeid(*dynamicSEXP) != typeid(sexp::LuaSEXP)) {
		LuaError(L, "Specified dynamic SEXP name does not refer to a Lua SEXP!");
		return ade_set_error(L, "o", l_LuaSEXP.Set(lua_sexp_h()));
	}

	return ade_set_args(L, "o", l_LuaSEXP.Set(lua_sexp_h(static_cast<sexp::LuaSEXP*>(dynamicSEXP))));
}

ADE_LIB_DERIV(l_Mission_LuaAISEXPs, "LuaAISEXPs", nullptr, "Lua AI SEXPs", l_Mission);

ADE_INDEXER(l_Mission_LuaAISEXPs, "string Name", "Gets a handle of a Lua SEXP", "LuaAISEXP", "Lua AI SEXP handle or invalid handle on error")
{
	const char* name = nullptr;
	if (!ade_get_args(L, "*s", &name)) {
		return ade_set_error(L, "o", l_LuaAISEXP.Set(lua_ai_sexp_h()));
	}

	if (name == nullptr) {
		return ade_set_error(L, "o", l_LuaAISEXP.Set(lua_ai_sexp_h()));
	}

	if (ADE_SETTING_VAR) {
		LuaError(L, "Setting of Lua AI SEXPs is not supported!");
	}

	auto op = get_operator_const(name);

	if (op == 0) {
		LuaError(L, "SEXP '%s' is not known to the SEXP system!", name);
		return ade_set_args(L, "o", l_LuaAISEXP.Set(lua_ai_sexp_h()));
	}

	auto dynamicSEXP = sexp::get_dynamic_sexp(op);

	if (dynamicSEXP == nullptr) {
		return ade_set_args(L, "o", l_LuaAISEXP.Set(lua_ai_sexp_h()));
	}

	if (typeid(*dynamicSEXP) != typeid(sexp::LuaAISEXP)) {
		LuaError(L, "Specified dynamic SEXP name does not refer to a Lua SEXP!");
		return ade_set_error(L, "o", l_LuaAISEXP.Set(lua_ai_sexp_h()));
	}

	return ade_set_args(L, "o", l_LuaAISEXP.Set(lua_ai_sexp_h(static_cast<sexp::LuaAISEXP*>(dynamicSEXP))));
}

static int arrivalListIter(lua_State* L)
{
	parse_object_h* poh = nullptr;
	if (!ade_get_args(L, "*o", l_ParseObject.GetPtr(&poh))) {
		return ADE_RETURN_NIL;
	}

	const auto next = GET_NEXT(poh->getObject());

	if (next == END_OF_LIST(&Ship_arrival_list) || next == nullptr) {
		return ADE_RETURN_NIL;
	}

	return ade_set_args(L, "o", l_ParseObject.Set(parse_object_h(next)));
}

ADE_FUNC(getArrivalList,
	l_Mission,
	nullptr,
	"Get the list of yet to arrive ships for this mission",
	"iterator<parse_object>",
	"An iterator across all the yet to arrive ships. Can be used in a for .. in loop. Is not valid for more than one frame.")
{
	return ade_set_args(L, "u*o", luacpp::LuaFunction::createFromCFunction(L, arrivalListIter),
	                    l_ParseObject.Set(parse_object_h(&Ship_arrival_list)));
}

ADE_FUNC(getShipList,
	l_Mission,
	nullptr,
	"Get an iterator to the list of ships in this mission",
	"iterator<ship>",
	"An iterator across all ships in the mission. Can be used in a for .. in loop. Is not valid for more than one frame.")
{
	ship_obj* so = &Ship_obj_list;

	return ade_set_args(L, "u", luacpp::LuaFunction::createFromStdFunction(L, [so](lua_State* LInner, const luacpp::LuaValueList& /*params*/) mutable -> luacpp::LuaValueList {
		//Since the first element of a list is the next element from the head, and we start this function with the the captured "so" object being the head, this GET_NEXT will return the first element on first call of this lambda.
		//Similarly, an empty list is defined by the head's next element being itself, hence an empty list will immediately return nil just fine
		so = GET_NEXT(so);

		// skip should-be-dead ships
		if (so != nullptr) {
			while (so != END_OF_LIST(&Ship_obj_list)) {
				if (!Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) {
					break;
				}
				so = GET_NEXT(so);
			}
		}

		if (so == END_OF_LIST(&Ship_obj_list) || so == nullptr) {
			return luacpp::LuaValueList{ luacpp::LuaValue::createNil(LInner) };
		}

		return luacpp::LuaValueList{ luacpp::LuaValue::createValue(LInner, l_Ship.Set(object_h(so->objnum))) };
	}));
}

ADE_FUNC(getMissileList,
	l_Mission,
	nullptr,
	"Get an iterator to the list of missiles in this mission",
	"iterator<weapon>",
	"An iterator across all missiles in the mission. Can be used in a for .. in loop. Is not valid for more than one frame.")
{
	missile_obj* mo = &Missile_obj_list;

	return ade_set_args(L, "u", luacpp::LuaFunction::createFromStdFunction(L, [mo](lua_State* LInner, const luacpp::LuaValueList& /*params*/) mutable -> luacpp::LuaValueList {
		//Since the first element of a list is the next element from the head, and we start this function with the the captured "mo" object being the head, this GET_NEXT will return the first element on first call of this lambda.
		//Similarly, an empty list is defined by the head's next element being itself, hence an empty list will immediately return nil just fine
		mo = GET_NEXT(mo);

		// skip should-be-dead missiles
		if (mo != nullptr) {
			while (mo != END_OF_LIST(&Missile_obj_list)) {
				if (!Objects[mo->objnum].flags[Object::Object_Flags::Should_be_dead]) {
					break;
				}
				mo = GET_NEXT(mo);
			}
		}

		if (mo == END_OF_LIST(&Missile_obj_list) || mo == nullptr) {
			return luacpp::LuaValueList{ luacpp::LuaValue::createNil(LInner) };
		}

		return luacpp::LuaValueList{ luacpp::LuaValue::createValue(LInner, l_Weapon.Set(object_h(mo->objnum))) };
	}));
}

ADE_FUNC(getAsteroidList,
	l_Mission,
	nullptr,
	"Get an iterator to the list of asteroids in this mission",
	"iterator<asteroid>",
	"An iterator across all asteroids in the mission. Can be used in a for .. in loop. Is not valid for more than one frame.")
{
	asteroid_obj* ao = &Asteroid_obj_list;

	return ade_set_args(L, "u", luacpp::LuaFunction::createFromStdFunction(L, [ao](lua_State* LInner, const luacpp::LuaValueList& /*params*/) mutable -> luacpp::LuaValueList {
		//Since the first element of a list is the next element from the head, and we start this function with the the captured "ao" object being the head, this GET_NEXT will return the first element on first call of this lambda.
		//Similarly, an empty list is defined by the head's next element being itself, hence an empty list will immediately return nil just fine
		ao = GET_NEXT(ao);

		// skip should-be-dead asteroids
		if (ao != nullptr) {
			while (ao != END_OF_LIST(&Asteroid_obj_list)) {
				if (!Objects[ao->objnum].flags[Object::Object_Flags::Should_be_dead]) {
					break;
				}
				ao = GET_NEXT(ao);
			}
		}

		if (ao == END_OF_LIST(&Asteroid_obj_list) || ao == nullptr) {
			return luacpp::LuaValueList{ luacpp::LuaValue::createNil(LInner) };
		}

		return luacpp::LuaValueList{ luacpp::LuaValue::createValue(LInner, l_Asteroid.Set(object_h(ao->objnum))) };
	}));
}

ADE_FUNC(getDebrisList,
	l_Mission,
	nullptr,
	"Get an iterator to the list of debris in this mission",
	"iterator<debris>",
	"An iterator across all debris in the mission. Can be used in a for .. in loop. Is not valid for more than one frame.")
{
	size_t idx = 0;

	return ade_set_args(L, "u", luacpp::LuaFunction::createFromStdFunction(L, [idx](lua_State* LInner, const luacpp::LuaValueList& /*params*/) mutable -> luacpp::LuaValueList {
		// iterate through the Debris vector to find the next valid debris piece
		while (idx < Debris.size()) {
			const debris& db = Debris[idx];
			idx++;

			// debris must be Used, must have a valid objnum, and must not be should-be-dead
			if (db.flags[Debris_Flags::Used] && db.objnum != -1) {
				if (!Objects[db.objnum].flags[Object::Object_Flags::Should_be_dead]) {
					return luacpp::LuaValueList{ luacpp::LuaValue::createValue(LInner, l_Debris.Set(object_h(db.objnum))) };
				}
			}
		}

		// end of list or no more valid debris found
		return luacpp::LuaValueList{ luacpp::LuaValue::createNil(LInner) };
	}));
}

ADE_FUNC(waitAsync,
	l_Mission,
	"number seconds",
	"Performs an asynchronous wait until the specified amount of mission time has passed.",
	"promise",
	"A promise with no return value that resolves when the specified time has passed")
{
	float time = -1.0f;
	if (!ade_get_args(L, "f", &time)) {
		return ADE_RETURN_NIL;
	}

	if (time <= 0.0f) {
		LuaError(L, "Invalid wait time %f specified. Must be greater than zero.", time);
		return ADE_RETURN_NIL;
	}

	class time_resolve_context : public resolve_context, public std::enable_shared_from_this<time_resolve_context> {
	  public:
		time_resolve_context(int timestamp) : m_timestamp(timestamp) {
			static int unique_id_counter = 0;
			m_unique_id = unique_id_counter++;
			nprintf(("scripting", "waitAsync: Creating asynchronous context %d.\n", m_unique_id));
		}
		void setResolver(Resolver resolver) override
		{
			// Keep checking the time until the timestamp is elapsed
			auto self = shared_from_this();
			auto cb = [this, self, resolver = std::move(resolver)](
						  executor::IExecutionContext::State contextState) {
				if (contextState == executor::IExecutionContext::State::Invalid) {
					mprintf(("waitAsync: Context is invalid, possibly due to a game state change (current state is %s).  Aborting asynchronous context %d.\n", GS_state_text[gameseq_get_state()], m_unique_id));
					resolver(true, luacpp::LuaValueList());
					return executor::Executor::CallbackResult::Done;
				}

				if (timestamp_elapsed(m_timestamp)) {
					nprintf(("scripting", "waitAsync: Timestamp has elapsed for asynchronous context %d.\n", m_unique_id));
					resolver(false, luacpp::LuaValueList());
					return executor::Executor::CallbackResult::Done;
				}

				return executor::Executor::CallbackResult::Reschedule;
			};

			// Use an game state execution context here to clean up references to this as soon as possible
			executor::OnSimulationExecutor->post(
				executor::runInContext(executor::GameStateExecutionContext::captureContext(), std::move(cb)));
		}

	  private:
		int m_timestamp = -1;
		int m_unique_id = -1;
	};
	return ade_set_args(L,
		"o",
		l_Promise.Set(LuaPromise(std::make_shared<time_resolve_context>(timestamp(fl2i(time * 1000.f))))));
}

//****LIBRARY: Campaign
ADE_LIB(l_Campaign, "Campaign", "ca", "Campaign Library");

ADE_FUNC(getNextMissionFilename, l_Campaign, NULL, "Gets next mission filename", "string", "Next mission filename, or nil if the next mission is invalid")
{
	if (Campaign.next_mission < 0 || Campaign.next_mission >= MAX_CAMPAIGN_MISSIONS) {
		return ADE_RETURN_NIL;
	}
	return ade_set_args(L, "s", Campaign.missions[Campaign.next_mission].name);
}

ADE_FUNC(getPrevMissionFilename, l_Campaign, NULL, "Gets previous mission filename", "string", "Previous mission filename, or nil if the previous mission is invalid")
{
	if (Campaign.prev_mission < 0 || Campaign.prev_mission >= MAX_CAMPAIGN_MISSIONS) {
		return ADE_RETURN_NIL;
	}
	return ade_set_args(L, "s", Campaign.missions[Campaign.prev_mission].name);
}

// DahBlount - This jumps to a mission, the reason it accepts a boolean value is so that players can return to campaign maps
ADE_FUNC(jumpToMission, l_Campaign, "string filename, [boolean hub, boolean preserve]", "Jumps to a mission based on the filename. Optionally, the player can be sent to a hub mission without setting missions to skipped or preserve loadout.", "boolean", "Jumps to a mission, returning true if successful, false if unsuccessful (e.g. the mission could not be found in the campaign), or nil if no mission was specified.")
{
	const char* filename = nullptr;
	bool hub = false;
	bool preserve = false;
	if (!ade_get_args(L, "s|bb", &filename, &hub, &preserve))
		return ADE_RETURN_NIL;

	bool success = mission_campaign_jump_to_mission(filename, hub, preserve);
	return ade_set_args(L, "b", success);
}

ADE_FUNC(getValidNextMissions, l_Campaign, nullptr, "Gets all valid next mission filenames for the current campaign.", "table", "A list of mission filenames, or an empty table if none are valid.")
{
	auto table = luacpp::LuaTable::create(L);
	auto valid_missions = mission_campaign_get_valid_next_missions();
	for (size_t i = 0; i < valid_missions.size(); ++i) {
		table.addValue(static_cast<int>(i + 1), valid_missions[i]);
	}

	return ade_set_args(L, "t", &table);
}

ADE_VIRTVAR(CustomData, l_Campaign, nullptr, "Gets the custom data table for this campaign", "table", "The campaign's custom data table") 
{
	if (ADE_SETTING_VAR) {
		LuaError(L, "Setting Custom Data is not supported");
	}

	auto table = luacpp::LuaTable::create(L);

	for (const auto& pair : Campaign.custom_data)
	{
		table.addValue(pair.first, pair.second);
	}

	return ade_set_args(L, "t", &table);	
}

ADE_FUNC(hasCustomData, l_Campaign, nullptr, "Detects whether the campaign has any custom data", "boolean", "true if the campaign's custom_data is not empty, false otherwise") 
{

	bool result = !Campaign.custom_data.empty();
	return ade_set_args(L, "b", result);
}

// TODO: add a proper indexer type that returns a handle
// something like ca.Mission[filename/index]



}
}
