mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-04-28 15:57:59 +03:00
1218 lines
37 KiB
C++
1218 lines
37 KiB
C++
#include "framework.h"
|
|
#include "LogicHandler.h"
|
|
|
|
#include <filesystem>
|
|
|
|
#include "Game/control/volume.h"
|
|
#include "Game/effects/Electricity.h"
|
|
#include "Game/Lara/lara.h"
|
|
#include "Game/savegame.h"
|
|
#include "Scripting/Internal/ReservedScriptNames.h"
|
|
#include "Scripting/Internal/ScriptAssert.h"
|
|
#include "Scripting/Internal/ScriptUtil.h"
|
|
#include "Scripting/Internal/TEN/Color/Color.h"
|
|
#include "Scripting/Internal/TEN/Logic/LevelFunc.h"
|
|
#include "Scripting/Internal/TEN/Objects/Moveable/MoveableObject.h"
|
|
#include "Scripting/Internal/TEN/Rotation/Rotation.h"
|
|
#include "Scripting/Internal/TEN/Vec2/Vec2.h"
|
|
#include "Scripting/Internal/TEN/Vec3/Vec3.h"
|
|
|
|
using namespace TEN::Effects::Electricity;
|
|
|
|
/***
|
|
Saving data, triggering functions, and callbacks for level-specific scripts.
|
|
@tentable Logic
|
|
@pragma nostrip
|
|
*/
|
|
|
|
enum class CallbackPoint
|
|
{
|
|
PreStart,
|
|
PostStart,
|
|
PreLoad,
|
|
PostLoad,
|
|
PreLoop,
|
|
PostLoop,
|
|
PreSave,
|
|
PostSave,
|
|
PreEnd,
|
|
PostEnd,
|
|
PreUseItem,
|
|
PostUseItem,
|
|
PreFreeze,
|
|
PostFreeze
|
|
};
|
|
|
|
static const std::unordered_map<std::string, CallbackPoint> CALLBACK_POINTS
|
|
{
|
|
{ ScriptReserved_PreStart, CallbackPoint::PreStart },
|
|
{ ScriptReserved_PostStart, CallbackPoint::PostStart },
|
|
{ ScriptReserved_PreLoad, CallbackPoint::PreLoad },
|
|
{ ScriptReserved_PostLoad, CallbackPoint::PostLoad },
|
|
{ ScriptReserved_PreLoop, CallbackPoint::PreLoop },
|
|
{ ScriptReserved_PostLoop, CallbackPoint::PostLoop },
|
|
{ ScriptReserved_PreControlPhase, CallbackPoint::PreLoop }, // DEPRECATED
|
|
{ ScriptReserved_PostControlPhase, CallbackPoint::PostLoop }, // DEPRECATED
|
|
{ ScriptReserved_PreSave, CallbackPoint::PreSave },
|
|
{ ScriptReserved_PostSave, CallbackPoint::PostSave },
|
|
{ ScriptReserved_PreEnd, CallbackPoint::PreEnd },
|
|
{ ScriptReserved_PostEnd, CallbackPoint::PostEnd },
|
|
{ ScriptReserved_PreUseItem, CallbackPoint::PreUseItem },
|
|
{ ScriptReserved_PostUseItem, CallbackPoint::PostUseItem },
|
|
{ ScriptReserved_PreFreeze, CallbackPoint::PreFreeze },
|
|
{ ScriptReserved_PostFreeze, CallbackPoint::PostFreeze }
|
|
};
|
|
|
|
static const std::unordered_map<std::string, EventType> EVENT_TYPES
|
|
{
|
|
{ ScriptReserved_EventOnEnter, EventType::Enter },
|
|
{ ScriptReserved_EventOnInside, EventType::Inside },
|
|
{ ScriptReserved_EventOnLeave, EventType::Leave },
|
|
{ ScriptReserved_EventOnLoop, EventType::Loop },
|
|
{ ScriptReserved_EventOnLoad, EventType::Load },
|
|
{ ScriptReserved_EventOnSave, EventType::Save },
|
|
{ ScriptReserved_EventOnStart, EventType::Start },
|
|
{ ScriptReserved_EventOnEnd, EventType::End },
|
|
{ ScriptReserved_EventOnUseItem, EventType::UseItem },
|
|
{ ScriptReserved_EventOnFreeze, EventType::Freeze }
|
|
};
|
|
|
|
enum class LevelEndReason
|
|
{
|
|
LevelComplete,
|
|
LoadGame,
|
|
ExitToTitle,
|
|
Death,
|
|
Other
|
|
};
|
|
|
|
static const std::unordered_map<std::string, LevelEndReason> LEVEL_END_REASONS
|
|
{
|
|
{ ScriptReserved_EndReasonLevelComplete, LevelEndReason::LevelComplete },
|
|
{ ScriptReserved_EndReasonLoadGame, LevelEndReason::LoadGame },
|
|
{ ScriptReserved_EndReasonExitToTitle, LevelEndReason::ExitToTitle },
|
|
{ ScriptReserved_EndReasonDeath, LevelEndReason::Death },
|
|
{ ScriptReserved_EndReasonOther, LevelEndReason::Other }
|
|
};
|
|
|
|
static constexpr char const* strKey = "__internal_name";
|
|
|
|
void SetVariable(sol::table tab, sol::object key, sol::object value)
|
|
{
|
|
auto PutVar = [](sol::table tab, sol::object key, sol::object value)
|
|
{
|
|
switch (key.get_type())
|
|
{
|
|
case sol::type::number:
|
|
case sol::type::string:
|
|
tab.raw_set(key, value);
|
|
break;
|
|
|
|
default:
|
|
ScriptAssert(false, "Unsupported key type used for special table. Valid types are string and number.", ErrorMode::Terminate);
|
|
break;
|
|
}
|
|
};
|
|
|
|
auto UnsupportedValue = [](sol::table tab, sol::object key)
|
|
{
|
|
key.push();
|
|
|
|
size_t stringLength = 0;
|
|
auto string = std::string(luaL_tolstring(tab.lua_state(), -1, &stringLength));
|
|
|
|
if (!string.empty())
|
|
{
|
|
ScriptAssert(false, "Variable " + string + " has an unsupported type.", ErrorMode::Terminate);
|
|
lua_pop(tab.lua_state(), 1);
|
|
}
|
|
else
|
|
{
|
|
ScriptAssert(false, "Variable has an unsupported type.", ErrorMode::Terminate);
|
|
}
|
|
|
|
key.pop();
|
|
};
|
|
|
|
switch (value.get_type())
|
|
{
|
|
case sol::type::lua_nil:
|
|
case sol::type::boolean:
|
|
case sol::type::number:
|
|
case sol::type::string:
|
|
case sol::type::table:
|
|
PutVar(tab, key, value);
|
|
break;
|
|
|
|
case sol::type::userdata:
|
|
{
|
|
if (value.is<Vec2>() ||
|
|
value.is<Vec3>() ||
|
|
value.is<Rotation>() ||
|
|
value.is<ScriptColor>())
|
|
{
|
|
PutVar(tab, key, value);
|
|
}
|
|
else
|
|
{
|
|
UnsupportedValue(tab, key);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
UnsupportedValue(tab, key);
|
|
}
|
|
}
|
|
|
|
sol::object GetVariable(sol::table tab, sol::object key)
|
|
{
|
|
return tab.raw_get<sol::object>(key);
|
|
}
|
|
|
|
LogicHandler::LogicHandler(sol::state* lua, sol::table & parent) : m_handler{ lua }
|
|
{
|
|
m_handler.GetState()->set_function("print", &LogicHandler::LogPrint, this);
|
|
|
|
sol::table tableLogic{ m_handler.GetState()->lua_state(), sol::create };
|
|
|
|
parent.set(ScriptReserved_Logic, tableLogic);
|
|
|
|
tableLogic.set_function(ScriptReserved_AddCallback, &LogicHandler::AddCallback, this);
|
|
tableLogic.set_function(ScriptReserved_RemoveCallback, &LogicHandler::RemoveCallback, this);
|
|
tableLogic.set_function(ScriptReserved_HandleEvent, &LogicHandler::HandleEvent, this);
|
|
tableLogic.set_function(ScriptReserved_EnableEvent, &LogicHandler::EnableEvent, this);
|
|
tableLogic.set_function(ScriptReserved_DisableEvent, &LogicHandler::DisableEvent, this);
|
|
|
|
m_handler.MakeReadOnlyTable(tableLogic, ScriptReserved_EndReason, LEVEL_END_REASONS);
|
|
m_handler.MakeReadOnlyTable(tableLogic, ScriptReserved_CallbackPoint, CALLBACK_POINTS);
|
|
m_handler.MakeReadOnlyTable(tableLogic, ScriptReserved_EventType, EVENT_TYPES);
|
|
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PreStart, &m_callbacksPreStart));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PostStart, &m_callbacksPostStart));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PreLoad, &m_callbacksPreLoad));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PostLoad, &m_callbacksPostLoad));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PreLoop, &m_callbacksPreLoop));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PostLoop, &m_callbacksPostLoop));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PreSave, &m_callbacksPreSave));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PostSave, &m_callbacksPostSave));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PreEnd, &m_callbacksPreEnd));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PostEnd, &m_callbacksPostEnd));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PreUseItem, &m_callbacksPreUseItem));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PostUseItem, &m_callbacksPostUseItem));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PreFreeze, &m_callbacksPreFreeze));
|
|
m_callbacks.insert(std::make_pair(CallbackPoint::PostFreeze, &m_callbacksPostFreeze));
|
|
|
|
LevelFunc::Register(tableLogic);
|
|
|
|
ResetScripts(true);
|
|
}
|
|
|
|
void LogicHandler::ResetGameTables()
|
|
{
|
|
auto state = m_handler.GetState();
|
|
MakeSpecialTable(state, ScriptReserved_GameVars, &GetVariable, &SetVariable);
|
|
|
|
(*state)[ScriptReserved_GameVars][ScriptReserved_Engine] = sol::table{ *state, sol::create };
|
|
}
|
|
|
|
/*** Register a function as a callback.
|
|
@advancedDesc
|
|
This is intended for module/library developers who want their modules to do
|
|
stuff during level start/load/end/save/control phase, but don't want the level
|
|
designer to add calls to `OnStart`, `OnLoad`, etc. in their level script.
|
|
|
|
Possible values for `point`:
|
|
-- These take functions which accept no arguments
|
|
PRESTART -- will be called immediately before OnStart
|
|
POSTSTART -- will be called immediately after OnStart
|
|
|
|
PRESAVE -- will be called immediately before OnSave
|
|
POSTSAVE -- will be called immediately after OnSave
|
|
|
|
PRELOAD -- will be called immediately before OnLoad
|
|
POSTLOAD -- will be called immediately after OnLoad
|
|
|
|
PREFREEZE -- will be called before entering freeze mode
|
|
POSTFREEZE -- will be called immediately after exiting freeze mode
|
|
|
|
-- These take a LevelEndReason arg, like OnEnd
|
|
PREEND -- will be called immediately before OnEnd
|
|
POSTEND -- will be called immediately after OnEnd
|
|
|
|
-- These take functions which accepts a deltaTime argument
|
|
PRELOOP -- will be called in the beginning of game loop
|
|
POSTLOOP -- will be called at the end of game loop
|
|
|
|
-- These take functions which accepts an objectNumber argument, like OnUseItem
|
|
PREUSEITEM -- will be called immediately before OnUseItem
|
|
POSTUSEITEM -- will be called immediately after OnUseItem
|
|
|
|
The order in which two functions with the same CallbackPoint are called is undefined.
|
|
i.e. if you register `MyFunc` and `MyFunc2` with `PRELOOP`, both will be called in the beginning of game loop, but there is no guarantee that `MyFunc` will be called before `MyFunc2`, or vice-versa.
|
|
|
|
Any returned value will be discarded.
|
|
|
|
@function AddCallback
|
|
@tparam CallbackPoint point When should the callback be called?
|
|
@tparam LevelFunc func The function to be called (must be in the `LevelFuncs` hierarchy). Will receive, as an argument, the time in seconds since the last frame.
|
|
@usage
|
|
LevelFuncs.MyFunc = function(dt) print(dt) end
|
|
TEN.Logic.AddCallback(TEN.Logic.CallbackPoint.PRELOOP, LevelFuncs.MyFunc)
|
|
*/
|
|
void LogicHandler::AddCallback(CallbackPoint point, const LevelFunc& levelFunc)
|
|
{
|
|
auto it = m_callbacks.find(point);
|
|
|
|
if (it == m_callbacks.end())
|
|
{
|
|
TENLog("Error: callback point not found. Attempted to access missing value.", LogLevel::Error, LogConfig::All, false);
|
|
return;
|
|
}
|
|
|
|
if (it->second->find(levelFunc.m_funcName) != it->second->end())
|
|
{
|
|
TENLog("Warning: function " + levelFunc.m_funcName + " already registered in callbacks list.", LogLevel::Warning, LogConfig::All, true);
|
|
}
|
|
else
|
|
{
|
|
it->second->insert(levelFunc.m_funcName);
|
|
}
|
|
}
|
|
|
|
/*** Deregister a function as a callback.
|
|
Will have no effect if the function was not registered as a callback
|
|
|
|
@function RemoveCallback
|
|
@tparam CallbackPoint point The callback point the function was registered with. See @{AddCallback}
|
|
@tparam LevelFunc func The function to remove; must be in the LevelFuncs hierarchy.
|
|
@usage
|
|
TEN.Logic.RemoveCallback(TEN.Logic.CallbackPoint.PRELOOP, LevelFuncs.MyFunc)
|
|
*/
|
|
void LogicHandler::RemoveCallback(CallbackPoint point, const LevelFunc& levelFunc)
|
|
{
|
|
auto it = m_callbacks.find(point);
|
|
if (it == m_callbacks.end())
|
|
{
|
|
TENLog("Error: callback point not found. Attempted to access missing value.", LogLevel::Error, LogConfig::All, false);
|
|
return;
|
|
}
|
|
|
|
it->second->erase(levelFunc.m_funcName);
|
|
}
|
|
|
|
/*** Attempt to find an event set and execute a particular event from it.
|
|
@advancedDesc
|
|
|
|
Possible event type values:
|
|
ENTER
|
|
INSIDE
|
|
LEAVE
|
|
LOAD
|
|
SAVE
|
|
START
|
|
END
|
|
LOOP
|
|
USEITEM
|
|
MENU
|
|
|
|
@function HandleEvent
|
|
@tparam string name Name of the event set to find.
|
|
@tparam EventType type Event to execute.
|
|
@tparam Objects.Moveable activator Optional activator. Default is the player object.
|
|
*/
|
|
void LogicHandler::HandleEvent(const std::string& name, EventType type, sol::optional<Moveable&> activator)
|
|
{
|
|
TEN::Control::Volumes::HandleEvent(name, type, activator.has_value() ? (Activator)activator.value().GetIndex() : (Activator)short(LaraItem->Index));
|
|
}
|
|
|
|
/*** Attempt to find an event set and enable specified event in it.
|
|
|
|
@function EnableEvent
|
|
@tparam string name Name of the event set to find.
|
|
@tparam EventType type Event to enable.
|
|
*/
|
|
void LogicHandler::EnableEvent(const std::string& name, EventType type)
|
|
{
|
|
TEN::Control::Volumes::SetEventState(name, type, true);
|
|
}
|
|
|
|
/*** Attempt to find an event set and disable specified event in it.
|
|
|
|
@function DisableEvent
|
|
@tparam string name Name of the event set to find.
|
|
@tparam EventType type Event to disable.
|
|
*/
|
|
void LogicHandler::DisableEvent(const std::string& name, EventType type)
|
|
{
|
|
TEN::Control::Volumes::SetEventState(name, type, false);
|
|
}
|
|
|
|
void LogicHandler::ResetLevelTables()
|
|
{
|
|
auto state = m_handler.GetState();
|
|
MakeSpecialTable(state, ScriptReserved_LevelVars, &GetVariable, &SetVariable);
|
|
|
|
(*state)[ScriptReserved_LevelVars][ScriptReserved_Engine] = sol::table{ *state, sol::create };
|
|
}
|
|
|
|
sol::object LogicHandler::GetLevelFuncsMember(sol::table tab, const std::string& name)
|
|
{
|
|
std::string partName = tab.raw_get<std::string>(strKey);
|
|
auto& map = m_levelFuncs_tablesOfNames[partName];
|
|
|
|
auto fullNameIt = map.find(name);
|
|
if (fullNameIt != std::cend(map))
|
|
{
|
|
std::string_view key = fullNameIt->second;
|
|
if (m_levelFuncs_levelFuncObjects[key].valid())
|
|
return m_levelFuncs_levelFuncObjects[key];
|
|
}
|
|
|
|
return sol::nil;
|
|
}
|
|
|
|
bool LogicHandler::SetLevelFuncsMember(sol::table tab, const std::string& name, sol::object value)
|
|
{
|
|
if (sol::type::lua_nil == value.get_type())
|
|
{
|
|
std::string error{ "Tried to set " + std::string{ScriptReserved_LevelFuncs} + " member " };
|
|
error += name + " to nil; this not permitted at this time.";
|
|
return ScriptAssert(false, error);
|
|
}
|
|
else if (sol::type::function == value.get_type())
|
|
{
|
|
// Add name to table of names.
|
|
auto partName = tab.raw_get<std::string>(strKey);
|
|
auto fullName = partName + "." + name;
|
|
auto& parentNameTab = m_levelFuncs_tablesOfNames[partName];
|
|
parentNameTab.insert_or_assign(name, fullName);
|
|
|
|
// Create LevelFunc userdata and add that too.
|
|
LevelFunc levelFuncObject;
|
|
levelFuncObject.m_funcName = fullName;
|
|
levelFuncObject.m_handler = this;
|
|
m_levelFuncs_levelFuncObjects[fullName] = levelFuncObject;
|
|
|
|
// Add function itself.
|
|
m_levelFuncs_luaFunctions[fullName] = value;
|
|
}
|
|
else if (sol::type::table == value.get_type())
|
|
{
|
|
// Create and add new name map.
|
|
std::unordered_map<std::string, std::string> newNameMap;
|
|
auto fullName = tab.raw_get<std::string>(strKey) + "." + name;
|
|
m_levelFuncs_tablesOfNames.insert_or_assign(fullName, newNameMap);
|
|
|
|
// Create new table to put in the LevelFuncs hierarchy.
|
|
auto newLevelFuncsTab = MakeSpecialTable(m_handler.GetState(), name, &LogicHandler::GetLevelFuncsMember, &LogicHandler::SetLevelFuncsMember, this);
|
|
newLevelFuncsTab.raw_set(strKey, fullName);
|
|
tab.raw_set(name, newLevelFuncsTab);
|
|
|
|
// "Populate" new table. This will trigger the __newindex metafunction and will
|
|
// thus call this function recursively, handling all subtables and functions.
|
|
for (auto& [key, val] : value.as<sol::table>())
|
|
newLevelFuncsTab[key] = val;
|
|
}
|
|
else
|
|
{
|
|
std::string error{ "Failed to add " };
|
|
error += name + " to " + ScriptReserved_LevelFuncs + " or one of its tables; it must be a function or a table of functions.";
|
|
return ScriptAssert(false, error);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void LogicHandler::LogPrint(sol::variadic_args args)
|
|
{
|
|
std::string str;
|
|
for (const sol::object& o : args)
|
|
{
|
|
auto strPart = (*m_handler.GetState())["tostring"](o).get<std::string>();
|
|
str += strPart;
|
|
str += "\t";
|
|
}
|
|
|
|
TENLog(str, LogLevel::Info, LogConfig::All, true);
|
|
}
|
|
|
|
void LogicHandler::ResetScripts(bool clearGameVars)
|
|
{
|
|
FreeLevelScripts();
|
|
|
|
for (auto& [first, second] : m_callbacks)
|
|
second->clear();
|
|
|
|
auto currentPackage = m_handler.GetState()->get<sol::table>("package");
|
|
auto currentLoaded = currentPackage.get<sol::table>("loaded");
|
|
|
|
for (auto& [first, second] : currentLoaded)
|
|
currentLoaded[first] = sol::nil;
|
|
|
|
if (clearGameVars)
|
|
ResetGameTables();
|
|
|
|
m_handler.ResetGlobals();
|
|
|
|
m_shortenedCalls = false;
|
|
|
|
m_handler.GetState()->collect_garbage();
|
|
}
|
|
|
|
void LogicHandler::FreeLevelScripts()
|
|
{
|
|
m_levelFuncs = MakeSpecialTable(m_handler.GetState(), ScriptReserved_LevelFuncs, &LogicHandler::GetLevelFuncsMember, &LogicHandler::SetLevelFuncsMember, this);
|
|
m_levelFuncs.raw_set(strKey, ScriptReserved_LevelFuncs);
|
|
|
|
m_levelFuncs[ScriptReserved_Engine] = sol::table{ *m_handler.GetState(), sol::create };
|
|
|
|
m_levelFuncs_tablesOfNames.clear();
|
|
m_levelFuncs_luaFunctions.clear();
|
|
m_levelFuncs_levelFuncObjects = sol::table{ *m_handler.GetState(), sol::create };
|
|
|
|
m_levelFuncs_tablesOfNames.emplace(std::make_pair(ScriptReserved_LevelFuncs, std::unordered_map<std::string, std::string>{}));
|
|
|
|
ResetLevelTables();
|
|
m_onStart = sol::nil;
|
|
m_onLoad = sol::nil;
|
|
m_onLoop = sol::nil;
|
|
m_onSave = sol::nil;
|
|
m_onEnd = sol::nil;
|
|
m_onUseItem = sol::nil;
|
|
m_onBreak = sol::nil;
|
|
m_handler.GetState()->collect_garbage();
|
|
}
|
|
|
|
// Used when loading.
|
|
void LogicHandler::SetVariables(const std::vector<SavedVar>& vars, bool onlyLevelVars)
|
|
{
|
|
if (!onlyLevelVars)
|
|
ResetGameTables();
|
|
|
|
ResetLevelTables();
|
|
|
|
std::unordered_map<unsigned int, sol::table> solTables;
|
|
|
|
for(int i = 0; i < vars.size(); ++i)
|
|
{
|
|
if (std::holds_alternative<IndexTable>(vars[i]))
|
|
{
|
|
solTables.try_emplace(i, *m_handler.GetState(), sol::create);
|
|
auto indexTab = std::get<IndexTable>(vars[i]);
|
|
for (auto& [first, second] : indexTab)
|
|
{
|
|
// if we're wanting to reference a table, make sure that table exists
|
|
// create it if need be
|
|
if (std::holds_alternative<IndexTable>(vars[second]))
|
|
{
|
|
solTables.try_emplace(second, *m_handler.GetState(), sol::create);
|
|
solTables[i][vars[first]] = solTables[second];
|
|
}
|
|
else if (std::holds_alternative<double>(vars[second]))
|
|
{
|
|
double theNum = std::get<double>(vars[second]);
|
|
// If this is representable as an integer use an integer.
|
|
// This is to ensure something saved as 1 is not loaded as 1.0
|
|
// which would be confusing for the user.
|
|
// todo: should we throw a warning if the user tries to save or load a value
|
|
// outside of these bounds? - squidshire 30/04/2022
|
|
if (std::trunc(theNum) == theNum && theNum <= INT64_MAX && theNum >= INT64_MIN)
|
|
{
|
|
solTables[i][vars[first]] = (int64_t)theNum;
|
|
}
|
|
else
|
|
{
|
|
solTables[i][vars[first]] = vars[second];
|
|
}
|
|
}
|
|
else if (vars[second].index() == (int)SavedVarType::Vec2)
|
|
{
|
|
auto vec2 = Vec2(std::get<(int)SavedVarType::Vec2>(vars[second]));
|
|
solTables[i][vars[first]] = vec2;
|
|
}
|
|
else if (vars[second].index() == int(SavedVarType::Vec3))
|
|
{
|
|
auto vec3 = Vec3(std::get<int(SavedVarType::Vec3)>(vars[second]));
|
|
solTables[i][vars[first]] = vec3;
|
|
}
|
|
else if (vars[second].index() == int(SavedVarType::Rotation))
|
|
{
|
|
auto vec3 = Rotation(std::get<int(SavedVarType::Rotation)>(vars[second]));
|
|
solTables[i][vars[first]] = vec3;
|
|
}
|
|
else if (vars[second].index() == int(SavedVarType::Color))
|
|
{
|
|
auto color = D3DCOLOR(std::get<int(SavedVarType::Color)>(vars[second]));
|
|
solTables[i][vars[first]] = ScriptColor{color};
|
|
}
|
|
else if (std::holds_alternative<FuncName>(vars[second]))
|
|
{
|
|
LevelFunc levelFunc;
|
|
levelFunc.m_funcName = std::get<FuncName>(vars[second]).name;
|
|
levelFunc.m_handler = this;
|
|
solTables[i][vars[first]] = levelFunc;
|
|
}
|
|
else
|
|
{
|
|
solTables[i][vars[first]] = vars[second];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto rootTable = solTables[0];
|
|
|
|
sol::table levelVars = rootTable[ScriptReserved_LevelVars];
|
|
for (auto& [first, second] : levelVars)
|
|
(*m_handler.GetState())[ScriptReserved_LevelVars][first] = second;
|
|
|
|
if (onlyLevelVars)
|
|
return;
|
|
|
|
sol::table gameVars = rootTable[ScriptReserved_GameVars];
|
|
for (auto& [first, second] : gameVars)
|
|
(*m_handler.GetState())[ScriptReserved_GameVars][first] = second;
|
|
}
|
|
|
|
template<SavedVarType TypeEnum, typename TypeTo, typename TypeFrom, typename MapType>
|
|
int Handle(TypeFrom& var, MapType& varsMap, size_t& numVars, std::vector<SavedVar>& vars)
|
|
{
|
|
auto [first, second] = varsMap.insert(std::make_pair(&var, (int)numVars));
|
|
|
|
if (second)
|
|
{
|
|
SavedVar savedVar;
|
|
TypeTo varTo = (TypeTo)var;
|
|
savedVar.emplace<(int)TypeEnum>(varTo);
|
|
vars.push_back(savedVar);
|
|
++numVars;
|
|
}
|
|
|
|
return first->second;
|
|
}
|
|
|
|
std::string LogicHandler::GetRequestedPath() const
|
|
{
|
|
std::string path;
|
|
for (unsigned int i = 0; i < m_savedVarPath.size(); ++i)
|
|
{
|
|
auto key = m_savedVarPath[i];
|
|
if (std::holds_alternative<unsigned int>(key))
|
|
{
|
|
path += "[" + std::to_string(std::get<unsigned int>(key)) + "]";
|
|
}
|
|
else if (std::holds_alternative<std::string>(key))
|
|
{
|
|
auto part = std::get<std::string>(key);
|
|
if (i > 0)
|
|
path += "." + part;
|
|
else
|
|
path += part;
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
// Used when saving.
|
|
void LogicHandler::GetVariables(std::vector<SavedVar>& vars)
|
|
{
|
|
sol::table tab{ *m_handler.GetState(), sol::create };
|
|
tab[ScriptReserved_LevelVars] = (*m_handler.GetState())[ScriptReserved_LevelVars];
|
|
tab[ScriptReserved_GameVars] = (*m_handler.GetState())[ScriptReserved_GameVars];
|
|
|
|
std::unordered_map<void const*, unsigned int> varsMap;
|
|
std::unordered_map<double, unsigned int> numMap;
|
|
std::unordered_map<bool, unsigned int> boolMap;
|
|
|
|
size_t numVars = 0;
|
|
|
|
// The following functions will all try to put their values in a map. If it succeeds
|
|
// then the value was not already in the map, so we can put it into the var vector.
|
|
// If it fails, the value is in the map, and thus will also be in the var vector.
|
|
// We then return the value's position in the var vector.
|
|
|
|
// The purpose of this is to only store each value once, and to fill our tables with
|
|
// indices to the values rather than copies of the values.
|
|
|
|
auto handleNum = [&](auto num, auto map)
|
|
{
|
|
auto [first, second] = map.insert(std::make_pair(num, (int)numVars));
|
|
|
|
if (second)
|
|
{
|
|
vars.push_back(num);
|
|
++numVars;
|
|
}
|
|
|
|
return first->second;
|
|
};
|
|
|
|
auto handleStr = [&](const sol::object& obj)
|
|
{
|
|
auto str = obj.as<sol::string_view>();
|
|
auto [first, second] = varsMap.insert(std::make_pair(str.data(), (int)numVars));
|
|
|
|
if (second)
|
|
{
|
|
vars.push_back(std::string{ str.data() });
|
|
++numVars;
|
|
}
|
|
|
|
return first->second;
|
|
};
|
|
|
|
auto handleFuncName = [&](const LevelFunc& fnh)
|
|
{
|
|
auto [first, second] = varsMap.insert(std::make_pair(&fnh, (int)numVars));
|
|
|
|
if (second)
|
|
{
|
|
vars.push_back(FuncName{ std::string{ fnh.m_funcName } });
|
|
++numVars;
|
|
}
|
|
|
|
return first->second;
|
|
};
|
|
|
|
std::function<unsigned int(const sol::table&)> populate = [&](const sol::table& obj)
|
|
{
|
|
auto [first, second] = varsMap.insert(std::make_pair(obj.pointer(), (int)numVars));
|
|
|
|
if(second)
|
|
{
|
|
++numVars;
|
|
auto id = first->second;
|
|
|
|
vars.push_back(IndexTable{});
|
|
|
|
for (auto& [first, second] : obj)
|
|
{
|
|
bool validKey = true;
|
|
unsigned int keyIndex = 0;
|
|
std::variant<std::string, unsigned int> key{unsigned int(0)};
|
|
|
|
// Strings and numbers can be keys AND values.
|
|
switch (first.get_type())
|
|
{
|
|
case sol::type::string:
|
|
{
|
|
keyIndex = handleStr(first);
|
|
key = std::string{ first.as<sol::string_view>().data() };
|
|
m_savedVarPath.push_back(key);
|
|
}
|
|
break;
|
|
|
|
case sol::type::number:
|
|
{
|
|
if (double data = first.as<double>(); std::floor(data) != data)
|
|
{
|
|
ScriptAssert(false, "Tried using a non-integer number " + std::to_string(data) + " as a key in table " + GetRequestedPath());
|
|
validKey = false;
|
|
}
|
|
else
|
|
{
|
|
keyIndex = handleNum(data, numMap);
|
|
key = static_cast<unsigned int>(data);
|
|
m_savedVarPath.push_back(key);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
validKey = false;
|
|
ScriptAssert(false, "Tried using an unsupported type as a key in table " + GetRequestedPath());
|
|
}
|
|
|
|
if (!validKey)
|
|
continue;
|
|
|
|
auto putInVars = [&vars, id, keyIndex](unsigned int valIndex)
|
|
{
|
|
std::get<IndexTable>(vars[id]).push_back(std::make_pair(keyIndex, valIndex));
|
|
};
|
|
|
|
switch (second.get_type())
|
|
{
|
|
case sol::type::table:
|
|
putInVars(populate(second.as<sol::table>()));
|
|
break;
|
|
|
|
case sol::type::string:
|
|
putInVars(handleStr(second));
|
|
break;
|
|
|
|
case sol::type::number:
|
|
putInVars(handleNum(second.as<double>(), numMap));
|
|
break;
|
|
|
|
case sol::type::boolean:
|
|
putInVars(handleNum(second.as<bool>(), boolMap));
|
|
break;
|
|
|
|
case sol::type::userdata:
|
|
{
|
|
if (second.is<Vec2>())
|
|
{
|
|
putInVars(Handle<SavedVarType::Vec2, Vector2>(second.as<Vec2>(), varsMap, numVars, vars));
|
|
}
|
|
else if (second.is<Vec3>())
|
|
{
|
|
putInVars(Handle<SavedVarType::Vec3, Vector3>(second.as<Vec3>(), varsMap, numVars, vars));
|
|
}
|
|
else if (second.is<Rotation>())
|
|
{
|
|
putInVars(Handle<SavedVarType::Rotation, Vector3>(second.as<Rotation>(), varsMap, numVars, vars));
|
|
}
|
|
else if (second.is<ScriptColor>())
|
|
{
|
|
putInVars(Handle<SavedVarType::Color, D3DCOLOR>(second.as<ScriptColor>(), varsMap, numVars, vars));
|
|
}
|
|
else if (second.is<LevelFunc>())
|
|
{
|
|
putInVars(handleFuncName(second.as<LevelFunc>()));
|
|
}
|
|
else
|
|
{
|
|
ScriptAssert(false, "Tried saving an unsupported userdata as a value; variable is " + GetRequestedPath());
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ScriptAssert(false, "Tried saving an unsupported type as a value; variable is " + GetRequestedPath());
|
|
}
|
|
|
|
m_savedVarPath.pop_back();
|
|
}
|
|
}
|
|
|
|
return first->second;
|
|
};
|
|
|
|
populate(tab);
|
|
}
|
|
|
|
void LogicHandler::GetCallbackStrings(
|
|
std::vector<std::string>& preStart,
|
|
std::vector<std::string>& postStart,
|
|
std::vector<std::string>& preEnd,
|
|
std::vector<std::string>& postEnd,
|
|
std::vector<std::string>& preSave,
|
|
std::vector<std::string>& postSave,
|
|
std::vector<std::string>& preLoad,
|
|
std::vector<std::string>& postLoad,
|
|
std::vector<std::string>& preLoop,
|
|
std::vector<std::string>& postLoop,
|
|
std::vector<std::string>& preUseItem,
|
|
std::vector<std::string>& postUseItem,
|
|
std::vector<std::string>& preBreak,
|
|
std::vector<std::string>& postBreak) const
|
|
{
|
|
auto populateWith = [](std::vector<std::string>& dest, const std::unordered_set<std::string>& src)
|
|
{
|
|
for (const auto& s : src)
|
|
dest.push_back(s);
|
|
};
|
|
|
|
populateWith(preStart, m_callbacksPreStart);
|
|
populateWith(postStart, m_callbacksPostStart);
|
|
|
|
populateWith(preEnd, m_callbacksPreEnd);
|
|
populateWith(postEnd, m_callbacksPostEnd);
|
|
|
|
populateWith(preSave, m_callbacksPreSave);
|
|
populateWith(postSave, m_callbacksPostSave);
|
|
|
|
populateWith(preLoad, m_callbacksPreLoad);
|
|
populateWith(postLoad, m_callbacksPostLoad);
|
|
|
|
populateWith(preLoop, m_callbacksPreLoop);
|
|
populateWith(postLoop, m_callbacksPostLoop);
|
|
|
|
populateWith(preUseItem, m_callbacksPreUseItem);
|
|
populateWith(postUseItem, m_callbacksPostUseItem);
|
|
|
|
populateWith(preBreak, m_callbacksPreFreeze);
|
|
populateWith(postBreak, m_callbacksPostFreeze);
|
|
}
|
|
|
|
void LogicHandler::SetCallbackStrings(
|
|
const std::vector<std::string>& preStart,
|
|
const std::vector<std::string>& postStart,
|
|
const std::vector<std::string>& preEnd,
|
|
const std::vector<std::string>& postEnd,
|
|
const std::vector<std::string>& preSave,
|
|
const std::vector<std::string>& postSave,
|
|
const std::vector<std::string>& preLoad,
|
|
const std::vector<std::string>& postLoad,
|
|
const std::vector<std::string>& preLoop,
|
|
const std::vector<std::string>& postLoop,
|
|
const std::vector<std::string>& preUseItem,
|
|
const std::vector<std::string>& postUseItem,
|
|
const std::vector<std::string>& preBreak,
|
|
const std::vector<std::string>& postBreak)
|
|
{
|
|
auto populateWith = [](std::unordered_set<std::string>& dest, const std::vector<std::string>& src)
|
|
{
|
|
for (const auto& string : src)
|
|
dest.insert(string);
|
|
};
|
|
|
|
populateWith(m_callbacksPreStart, preStart);
|
|
populateWith(m_callbacksPostStart, postStart);
|
|
|
|
populateWith(m_callbacksPreEnd, preEnd);
|
|
populateWith(m_callbacksPostEnd, postEnd);
|
|
|
|
populateWith(m_callbacksPreSave, preSave);
|
|
populateWith(m_callbacksPostSave, postSave);
|
|
|
|
populateWith(m_callbacksPreLoad, preLoad);
|
|
populateWith(m_callbacksPostLoad, postLoad);
|
|
|
|
populateWith(m_callbacksPreLoop, preLoop);
|
|
populateWith(m_callbacksPostLoop, postLoop);
|
|
|
|
populateWith(m_callbacksPreUseItem, preUseItem);
|
|
populateWith(m_callbacksPostUseItem, postUseItem);
|
|
|
|
populateWith(m_callbacksPreFreeze, preBreak);
|
|
populateWith(m_callbacksPostFreeze, postBreak);
|
|
}
|
|
|
|
template <typename R, char const * S, typename mapType>
|
|
std::unique_ptr<R> GetByName(const std::string& type, const std::string& name, const mapType& map)
|
|
{
|
|
ScriptAssert(map.find(name) != map.end(), std::string{ type + " name not found: " + name }, ErrorMode::Terminate);
|
|
return std::make_unique<R>(map.at(name), false);
|
|
}
|
|
|
|
/*** Special objects
|
|
@section specialobjects
|
|
*/
|
|
|
|
/*** A @{Objects.Moveable} representing Lara herself.
|
|
@table Lara
|
|
*/
|
|
void LogicHandler::ResetVariables()
|
|
{
|
|
(*m_handler.GetState())["Lara"] = nullptr;
|
|
}
|
|
|
|
void LogicHandler::ShortenTENCalls()
|
|
{
|
|
auto str = R"(local ShortenInner
|
|
|
|
ShortenInner = function(tab)
|
|
for k, v in pairs(tab) do
|
|
if _G[k] then
|
|
print("WARNING! Key " .. k .. " already exists in global environment!")
|
|
else
|
|
_G[k] = v
|
|
if "table" == type(v) then
|
|
if nil == v.__type then
|
|
ShortenInner(v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
ShortenInner(TEN))";
|
|
|
|
ExecuteString(str);
|
|
|
|
m_shortenedCalls = true;
|
|
}
|
|
|
|
void LogicHandler::ExecuteScriptFile(const std::string& luaFilename)
|
|
{
|
|
if (!m_shortenedCalls)
|
|
ShortenTENCalls();
|
|
|
|
m_handler.ExecuteScript(luaFilename);
|
|
}
|
|
|
|
void LogicHandler::ExecuteString(const std::string& command)
|
|
{
|
|
m_handler.ExecuteString(command);
|
|
}
|
|
|
|
// These wind up calling CallLevelFunc, which is where all error checking is.
|
|
void LogicHandler::ExecuteFunction(const std::string& name, short idOne, short idTwo)
|
|
{
|
|
auto func = m_levelFuncs_luaFunctions[name];
|
|
|
|
func(std::make_unique<Moveable>(idOne), std::make_unique<Moveable>(idTwo));
|
|
}
|
|
|
|
void LogicHandler::ExecuteFunction(const std::string& name, TEN::Control::Volumes::Activator activator, const std::string& arguments)
|
|
{
|
|
sol::protected_function func = (*m_handler.GetState())[ScriptReserved_LevelFuncs][name.c_str()];
|
|
if (std::holds_alternative<short>(activator))
|
|
{
|
|
func(std::make_unique<Moveable>(std::get<short>(activator), true), arguments);
|
|
}
|
|
else
|
|
{
|
|
func(nullptr, arguments);
|
|
}
|
|
}
|
|
|
|
void LogicHandler::OnStart()
|
|
{
|
|
for (const auto& name : m_callbacksPreStart)
|
|
CallLevelFuncByName(name);
|
|
|
|
if (m_onStart.valid())
|
|
CallLevelFunc(m_onStart);
|
|
|
|
for (const auto& name : m_callbacksPostStart)
|
|
CallLevelFuncByName(name);
|
|
}
|
|
|
|
void LogicHandler::OnLoad()
|
|
{
|
|
for (const auto& name : m_callbacksPreLoad)
|
|
CallLevelFuncByName(name);
|
|
|
|
if (m_onLoad.valid())
|
|
CallLevelFunc(m_onLoad);
|
|
|
|
for (const auto& name : m_callbacksPostLoad)
|
|
CallLevelFuncByName(name);
|
|
}
|
|
|
|
void LogicHandler::OnLoop(float deltaTime, bool postLoop)
|
|
{
|
|
if (!postLoop)
|
|
{
|
|
for (const auto& name : m_callbacksPreLoop)
|
|
CallLevelFuncByName(name, deltaTime);
|
|
|
|
lua_gc(m_handler.GetState()->lua_state(), LUA_GCCOLLECT, 0);
|
|
if (m_onLoop.valid())
|
|
CallLevelFunc(m_onLoop, deltaTime);
|
|
}
|
|
else
|
|
{
|
|
for (const auto& name : m_callbacksPostLoop)
|
|
CallLevelFuncByName(name, deltaTime);
|
|
}
|
|
}
|
|
|
|
void LogicHandler::OnSave()
|
|
{
|
|
for (const auto& name : m_callbacksPreSave)
|
|
CallLevelFuncByName(name);
|
|
|
|
if (m_onSave.valid())
|
|
CallLevelFunc(m_onSave);
|
|
|
|
for (const auto& name : m_callbacksPostSave)
|
|
CallLevelFuncByName(name);
|
|
}
|
|
|
|
void LogicHandler::OnEnd(GameStatus reason)
|
|
{
|
|
auto endReason = LevelEndReason::Other;
|
|
switch (reason)
|
|
{
|
|
case GameStatus::LaraDead:
|
|
endReason = LevelEndReason::Death;
|
|
break;
|
|
|
|
case GameStatus::LevelComplete:
|
|
endReason = LevelEndReason::LevelComplete;
|
|
break;
|
|
|
|
case GameStatus::ExitToTitle:
|
|
endReason = LevelEndReason::ExitToTitle;
|
|
break;
|
|
|
|
case GameStatus::LoadGame:
|
|
endReason = LevelEndReason::LoadGame;
|
|
break;
|
|
}
|
|
|
|
for (const auto& name : m_callbacksPreEnd)
|
|
CallLevelFuncByName(name, endReason);
|
|
|
|
if (m_onEnd.valid())
|
|
CallLevelFunc(m_onEnd, endReason);
|
|
|
|
for (const auto& name : m_callbacksPostEnd)
|
|
CallLevelFuncByName(name, endReason);
|
|
}
|
|
|
|
void LogicHandler::OnUseItem(GAME_OBJECT_ID objectNumber)
|
|
{
|
|
for (const auto& name : m_callbacksPreUseItem)
|
|
CallLevelFuncByName(name, objectNumber);
|
|
|
|
if (m_onUseItem.valid())
|
|
CallLevelFunc(m_onUseItem, objectNumber);
|
|
|
|
for (const auto& name : m_callbacksPostUseItem)
|
|
CallLevelFuncByName(name, objectNumber);
|
|
}
|
|
|
|
void LogicHandler::OnFreeze()
|
|
{
|
|
for (const auto& name : m_callbacksPreFreeze)
|
|
CallLevelFuncByName(name);
|
|
|
|
if (m_onBreak.valid())
|
|
CallLevelFunc(m_onBreak);
|
|
|
|
for (const auto& name : m_callbacksPostFreeze)
|
|
CallLevelFuncByName(name);
|
|
}
|
|
|
|
/*** Special tables
|
|
|
|
TombEngine uses the following tables for specific things.
|
|
|
|
@section levelandgametables
|
|
*/
|
|
|
|
/*** A table with level-specific data which will be saved and loaded.
|
|
This is for level-specific information that you want to store in saved games.
|
|
|
|
@advancedDesc
|
|
For example, you may have a level with a custom puzzle where Lara has
|
|
to kill exactly seven enemies to open a door to a secret. You could use
|
|
the following line each time an enemy is killed:
|
|
|
|
LevelVars.enemiesKilled = LevelVars.enemiesKilled + 1
|
|
|
|
If the player saves the level after killing three, saves, and then reloads the save
|
|
some time later, the values `3` will be put back into `LevelVars.enemiesKilled.`
|
|
|
|
__This table is emptied when a level is finished.__ If the player needs to be able
|
|
to return to the level (like in the Karnak and Alexandria levels in *The Last Revelation*),
|
|
you will need to use the @{GameVars} table, below.
|
|
|
|
__LevelVars.Engine is a reserved table used internally by TombEngine's libs. Do not modify, overwrite, or add to it.__
|
|
|
|
@table LevelVars
|
|
*/
|
|
|
|
/*** A table with game data which will be saved and loaded.
|
|
This is for information not specific to any level, but which concerns your whole
|
|
levelset or game, that you want to store in saved games.
|
|
|
|
@advancedDesc
|
|
For example, you may wish to have a final boss say a specific voice line based on
|
|
a choice the player made in a previous level. In the level with the choice, you could
|
|
write:
|
|
|
|
GameVars.playerSnoopedInDrawers = true
|
|
|
|
And in the script file for the level with the boss, you could write:
|
|
|
|
if GameVars.playerSnoopedInDrawers then
|
|
PlayAudioTrack("how_dare_you.wav")
|
|
end
|
|
|
|
Unlike @{LevelVars}, this table will remain intact for the entirety of the game.
|
|
|
|
__GameVars.Engine is a reserved table used internally by TombEngine's libs. Do not modify, overwrite, or add to it.__
|
|
|
|
@table GameVars
|
|
*/
|
|
|
|
/*** A table nested table system for level-specific functions.
|
|
|
|
@advancedDesc
|
|
This serves a few purposes: it holds the level callbacks (listed below) as well as
|
|
any trigger functions you might have specified. For example, if you give a trigger
|
|
a Lua name of "my_trigger" in Tomb Editor, you will have to implement it as a member
|
|
of this table:
|
|
|
|
LevelFuncs.my_trigger = function()
|
|
-- implementation goes here
|
|
end
|
|
|
|
You can organise functions into tables within the hierarchy:
|
|
|
|
LevelFuncs.enemyFuncs = {}
|
|
|
|
LevelFuncs.enemyFuncs.makeBaddyRunAway = function()
|
|
-- implementation goes here
|
|
end
|
|
|
|
LevelFuncs.enemyFuncs.makeBaddyUseMedkit = function()
|
|
-- implementation goes here
|
|
end
|
|
|
|
There are two special subtables which you should __not__ overwrite:
|
|
|
|
LevelFuncs.Engine -- this is for 'first-party' functions, i.e. ones that come with TombEngine.
|
|
LevelFuncs.External -- this is for 'third-party' functions. If you write a library providing LevelFuncs functions for other builders to use in their levels, put those functions in LevelFuncs.External.YourLibraryNameHere
|
|
|
|
The following are the level callbacks. They are optional; if your level has no special
|
|
behaviour for a particular scenario, you do not need to implement the function. For
|
|
example, if your level does not need any special initialisation when it is loaded,
|
|
you can just leave out `LevelFuncs.OnStart`.
|
|
|
|
__The order of loading is as follows:__
|
|
|
|
1. The level data itself is loaded.
|
|
2. The level script itself is run (i.e. any code you put outside the `LevelFuncs` callbacks is executed).
|
|
3. Save data is loaded, if saving from a saved game (will empty `LevelVars` and `GameVars` and repopulate them with what they contained when the game was saved).
|
|
4. If loading from a save, `OnLoaded` will be called. Otherwise, `OnStart` will be called.
|
|
5. The control loop, in which `OnLoop` will be called once per frame, begins.
|
|
|
|
@tfield function OnStart Will be called when a level is entered by completing a previous level or by selecting it in the menu. Will not be called when loaded from a saved game.
|
|
@tfield function OnLoad Will be called when a saved game is loaded, just *after* data is loaded
|
|
@tfield function(float) OnLoop Will be called during the game's update loop,
|
|
and provides the delta time (a float representing game time since last call) via its argument.
|
|
@tfield function OnSave Will be called when the player saves the game, just *before* data is saved
|
|
@tfield function OnEnd(EndReason) Will be called when leaving a level. This includes finishing it, exiting to the menu, or loading a save in a different level. It can take an `EndReason` arg:
|
|
|
|
EXITTOTITLE
|
|
LEVELCOMPLETE
|
|
LOADGAME
|
|
DEATH
|
|
OTHER
|
|
|
|
For example:
|
|
LevelFuncs.OnEnd = function(reason)
|
|
if(reason == TEN.Logic.EndReason.DEATH) then
|
|
print("death")
|
|
end
|
|
end
|
|
@table LevelFuncs
|
|
*/
|
|
|
|
void LogicHandler::InitCallbacks()
|
|
{
|
|
auto assignCB = [this](sol::protected_function& func, const std::string& luaFunc)
|
|
{
|
|
auto state = m_handler.GetState();
|
|
std::string fullName = std::string{ ScriptReserved_LevelFuncs } + "." + luaFunc;
|
|
|
|
sol::object theData = (*state)[ScriptReserved_LevelFuncs][luaFunc];
|
|
|
|
if (!theData.valid())
|
|
return;
|
|
|
|
LevelFunc fnh = (*state)[ScriptReserved_LevelFuncs][luaFunc];
|
|
|
|
func = m_levelFuncs_luaFunctions[fnh.m_funcName];
|
|
|
|
if (!func.valid())
|
|
TENLog("Level's script does not define callback " + fullName + ". Defaulting to no " + fullName + " behaviour.");
|
|
};
|
|
|
|
assignCB(m_onStart, ScriptReserved_OnStart);
|
|
assignCB(m_onLoad, ScriptReserved_OnLoad);
|
|
assignCB(m_onLoop, ScriptReserved_OnControlPhase);
|
|
assignCB(m_onLoop, ScriptReserved_OnLoop);
|
|
assignCB(m_onSave, ScriptReserved_OnSave);
|
|
assignCB(m_onEnd, ScriptReserved_OnEnd);
|
|
assignCB(m_onUseItem, ScriptReserved_OnUseItem);
|
|
assignCB(m_onBreak, ScriptReserved_OnFreeze);
|
|
}
|