Move scripting source files into new filder. Obviously, these won't work yet or even compile.

This commit is contained in:
hispidence 2021-12-14 21:10:21 +00:00
parent a9d8b78c96
commit 69de6e4502
24 changed files with 3 additions and 3 deletions

View file

@ -0,0 +1,315 @@
#include "frameworkandsol.h"
#include "GameFlowScript.h"
#include "Sound/sound.h"
#include "Game/savegame.h"
#include "GameScriptInventoryObject.h"
#include "InventorySlots.h"
#include "Game/gui.h"
/***
Scripts that will be run on game startup.
@files Pre-game
@pragma nostrip
*/
using std::string;
using std::vector;
using std::unordered_map;
GameFlow* g_GameFlow;
ScriptInterfaceGame* g_GameScript;
GameFlow::GameFlow(sol::state* lua) : LuaHandler{ lua }
{
GameScriptLevel::Register(m_lua);
GameScriptSkyLayer::Register(m_lua);
GameScriptFog::Register(m_lua);
GameScriptMirror::Register(m_lua);
GameScriptInventoryObject::Register(m_lua);
GameScriptSettings::Register(m_lua);
GameScriptAnimations::Register(m_lua);
GameScriptAudioTrack::Register(m_lua);
GameScriptColor::Register(m_lua);
GameScriptRotation::Register(m_lua);
/*** gameflow.lua.
These functions are called in gameflow.lua, a file loosely equivalent to winroomedit's SCRIPT.DAT.
They handle a game's 'metadata'; i.e., things such as level titles, loading screen paths, and default
ambient tracks.
@section gameflowlua
*/
/***
Add a level to the gameflow.
@function AddLevel
@tparam Level level a level object
*/
m_lua->set_function("AddLevel", &GameFlow::AddLevel, this);
/*** Image to show when loading the game.
Must be a .jpg or .png image.
@function SetIntroImagePath
@tparam string path the path to the image, relative to the TombEngine exe
*/
m_lua->set_function("SetIntroImagePath", &GameFlow::SetIntroImagePath, this);
/*** Image to show in the background of the title screen.
Must be a .jpg or .png image.
__(not yet implemented)__
@function SetTitleScreenImagePath
@tparam string path the path to the image, relative to the TombEngine exe
*/
m_lua->set_function("SetTitleScreenImagePath", &GameFlow::SetTitleScreenImagePath, this);
/*** Maximum draw distance.
The maximum draw distance, in sectors (blocks), of any level in the game.
This is equivalent to TRNG's WorldFarView variable.
__(not yet implemented)__
@function SetGameFarView
@tparam byte farview Number of sectors. Must be in the range [1, 127].
*/
m_lua->set_function("SetGameFarView", &GameFlow::SetGameFarView, this);
/*** settings.lua.
These functions are called in settings.lua, a file which holds your local settings.
settings.lua shouldn't be bundled with any finished levels/games.
@section settingslua
*/
/***
@function SetSettings
@tparam Settings settings a settings object
*/
m_lua->set_function("SetSettings", &GameFlow::SetSettings, this);
/***
@function SetSettings
@tparam Settings settings a settings object
*/
m_lua->set_function("SetAnimations", &GameFlow::SetAnimations, this);
/*** tracks.lua.
__TODO CONFIRM PROPER BEHAVIOUR__
@section trackslua
*/
/***
@function SetAudioTracks
@tparam table table array-style table with @{AudioTrack} objects
*/
//TODO confirm proper behaviour
m_lua->set_function("SetAudioTracks", &GameFlow::SetAudioTracks, this);
/*** strings.lua.
These functions used in strings.lua, which is generated by TombIDE.
You will not need to call them manually.
@section stringslua
*/
/*** Set string variable keys and their translations.
@function SetStrings
@tparam tab table array-style table with strings
*/
m_lua->set_function("SetStrings", &GameFlow::SetStrings, this);
/*** Set language names for translations.
Specify which translations in the strings table correspond to which languages.
@function SetLanguageNames
@tparam tab table array-style table with language names
*/
m_lua->set_function("SetLanguageNames", &GameFlow::SetLanguageNames, this);
MakeReadOnlyTable("WeatherType", kWeatherTypes);
MakeReadOnlyTable("LaraType", kLaraTypes);
MakeReadOnlyTable("InvItem", kInventorySlots);
MakeReadOnlyTable("RotationAxis", kRotAxes);
MakeReadOnlyTable("ItemAction", kItemActions);
MakeReadOnlyTable("ErrorMode", kErrorModes);
}
GameFlow::~GameFlow()
{
for (auto& lev : Levels)
{
delete lev;
}
}
void GameFlow::SetLanguageNames(sol::as_table_t<std::vector<std::string>> && src)
{
m_languageNames = std::move(src);
}
void GameFlow::SetStrings(sol::nested<std::unordered_map<std::string, std::vector<std::string>>> && src)
{
m_translationsMap = std::move(src);
}
void GameFlow::SetSettings(GameScriptSettings const & src)
{
m_settings = src;
}
void GameFlow::SetAnimations(GameScriptAnimations const& src)
{
Animations = src;
}
void GameFlow::AddLevel(GameScriptLevel const& level)
{
Levels.push_back(new GameScriptLevel{ level });
}
void GameFlow::SetIntroImagePath(std::string const& path)
{
IntroImagePath = path;
}
void GameFlow::SetTitleScreenImagePath(std::string const& path)
{
TitleScreenImagePath = path;
}
void GameFlow::SetGameFarView(byte val)
{
bool cond = val <= 127 && val >= 1;
std::string msg{ "Game far view value must be in the range [1, 127]." };
if (!ScriptAssert(cond, msg))
{
ScriptWarn("Setting game far view to 32.");
GameFarView = 32;
}
else
{
GameFarView = val;
}
}
void GameFlow::SetAudioTracks(sol::as_table_t<std::vector<GameScriptAudioTrack>>&& src)
{
std::vector<GameScriptAudioTrack> tracks = std::move(src);
SoundTracks.clear();
for (auto t : tracks) {
SoundTrackInfo track;
track.Name = t.trackName;
track.Mask = 0;
track.Mode = t.looped ? SOUNDTRACK_PLAYTYPE::BGM : SOUNDTRACK_PLAYTYPE::OneShot;
SoundTracks.push_back(track);
}
}
void GameFlow::LoadGameFlowScript()
{
ExecuteScript("Scripts/Enums.lua");
ExecuteScript("Scripts/Tracks.lua");
ExecuteScript("Scripts/Gameflow.lua");
ExecuteScript("Scripts/Strings.lua");
ExecuteScript("Scripts/Settings.lua");
SetScriptErrorMode(GetSettings()->ErrorMode);
}
char const * GameFlow::GetString(const char* id) const
{
if (!ScriptAssert(m_translationsMap.find(id) != m_translationsMap.end(), std::string{ "Couldn't find string " } + id))
{
return "String not found";
}
else
return m_translationsMap.at(string(id)).at(0).c_str();
}
GameScriptSettings* GameFlow::GetSettings()
{
return &m_settings;
}
GameScriptLevel* GameFlow::GetLevel(int id)
{
return Levels[id];
}
int GameFlow::GetNumLevels() const
{
return Levels.size();
}
bool GameFlow::DoGameflow()
{
// We start with the title level
CurrentLevel = 0;
SelectedLevelForNewGame = 0;
SelectedSaveGame = 0;
SaveGameHeader header;
// We loop indefinitely, looking for return values of DoTitle or DoLevel
bool loadFromSavegame = false;
while (true)
{
// First we need to fill some legacy variables in PCTomb5.exe
GameScriptLevel* level = Levels[CurrentLevel];
GAME_STATUS status;
if (CurrentLevel == 0)
{
status = DoTitle(0);
}
else
{
// Prepare inventory objects table
for (size_t i = 0; i < level->InventoryObjects.size(); i++)
{
GameScriptInventoryObject* obj = &level->InventoryObjects[i];
if (obj->slot >= 0 && obj->slot < INVENTORY_TABLE_SIZE)
{
InventoryObject* invObj = &inventry_objects_list[obj->slot];
invObj->objname = obj->name.c_str();
invObj->scale1 = obj->scale;
invObj->yoff = obj->yOffset;
invObj->xrot = FROM_DEGREES(obj->rot.x);
invObj->yrot = FROM_DEGREES(obj->rot.y);
invObj->zrot = FROM_DEGREES(obj->rot.z);
invObj->meshbits = obj->meshBits;
invObj->opts = obj->action;
invObj->rot_flags = obj->rotationFlags;
}
}
status = DoLevel(CurrentLevel, level->AmbientTrack, loadFromSavegame);
loadFromSavegame = false;
}
switch (status)
{
case GAME_STATUS::GAME_STATUS_EXIT_GAME:
return true;
case GAME_STATUS::GAME_STATUS_EXIT_TO_TITLE:
CurrentLevel = 0;
break;
case GAME_STATUS::GAME_STATUS_NEW_GAME:
CurrentLevel = (SelectedLevelForNewGame != 0 ? SelectedLevelForNewGame : 1);
SelectedLevelForNewGame = 0;
InitialiseGame = true;
break;
case GAME_STATUS::GAME_STATUS_LOAD_GAME:
// Load the header of the savegame for getting the level to load
SaveGame::LoadHeader(SelectedSaveGame, &header);
// Load level
CurrentLevel = header.Level;
loadFromSavegame = true;
break;
case GAME_STATUS::GAME_STATUS_LEVEL_COMPLETED:
if (LevelComplete == Levels.size())
{
// TODO: final credits
}
else
CurrentLevel++;
break;
}
}
return true;
}

View file

@ -0,0 +1,823 @@
#include "frameworkandsol.h"
#include "GameLogicScript.h"
#include "ScriptAssert.h"
#include "Game/items.h"
#include "Game/control/box.h"
#include "Game/Lara/lara.h"
#include "Game/savegame.h"
#include "Game/control/lot.h"
#include "Sound/sound.h"
#include "Specific/setup.h"
#include "Specific/level.h"
#include "Game/effects/tomb4fx.h"
#include "Game/effects/effects.h"
#include "Game/pickup/pickup.h"
#include "Game/gui.h"
#include "ObjectIDs.h"
#include "GameScriptDisplayString.h"
#include "ReservedScriptNames.h"
#include "Game/camera.h"
#include <Renderer/Renderer11Enums.h>
#include "Game/effects/lightning.h"
using namespace TEN::Effects::Lightning;
/***
Functions and callbacks for level-specific logic scripts.
@files Level-specific
@pragma nostrip
*/
static void PlayAudioTrack(std::string const & trackName, sol::optional<bool> looped)
{
auto mode = looped.value_or(false) ? SOUNDTRACK_PLAYTYPE::OneShot : SOUNDTRACK_PLAYTYPE::BGM;
PlaySoundTrack(trackName, mode);
}
static void PlaySoundEffect(int id, GameScriptPosition p, int flags)
{
PHD_3DPOS pos;
pos.xPos = p.x;
pos.yPos = p.y;
pos.zPos = p.z;
pos.xRot = 0;
pos.yRot = 0;
pos.zRot = 0;
SoundEffect(id, &pos, flags);
}
static void PlaySoundEffect(int id, int flags)
{
SoundEffect(id, NULL, flags);
}
static void SetAmbientTrack(std::string const & trackName)
{
PlaySoundTrack(trackName, SOUNDTRACK_PLAYTYPE::BGM);
}
static int FindRoomNumber(GameScriptPosition pos)
{
return 0;
}
static void AddLightningArc(GameScriptPosition src, GameScriptPosition dest, GameScriptColor color, int lifetime, int amplitude, int beamWidth, int segments, int flags)
{
PHD_VECTOR p1;
p1.x = src.x;
p1.y = src.y;
p1.z = src.z;
PHD_VECTOR p2;
p2.x = dest.x;
p2.y = dest.y;
p2.z = dest.z;
TriggerLightning(&p1, &p2, amplitude, color.GetR(), color.GetG(), color.GetB(), lifetime, flags, beamWidth, segments);
}
static void AddShockwave(GameScriptPosition pos, int innerRadius, int outerRadius, GameScriptColor color, int lifetime, int speed, int angle, int flags)
{
PHD_3DPOS p;
p.xPos = pos.x;
p.yPos = pos.y;
p.zPos = pos.z;
TriggerShockwave(&p, innerRadius, outerRadius, speed, color.GetR(), color.GetG(), color.GetB(), lifetime, FROM_DEGREES(angle), flags);
}
static void AddDynamicLight(GameScriptPosition pos, GameScriptColor color, int radius, int lifetime)
{
TriggerDynamicLight(pos.x, pos.y, pos.z, radius, color.GetR(), color.GetG(), color.GetB());
}
static void AddBlood(GameScriptPosition pos, int num)
{
TriggerBlood(pos.x, pos.y, pos.z, -1, num);
}
static void AddFireFlame(GameScriptPosition pos, int size)
{
AddFire(pos.x, pos.y, pos.z, size, FindRoomNumber(pos), true);
}
static void Earthquake(int strength)
{
Camera.bounce = -strength;
}
static void InventoryAdd(ItemEnumPair slot, sol::optional<int> count)
{
// If 0 is passed in, then the amount added will be the default amount
// for that pickup - i.e. the amount you would get from picking up the
// item in-game (e.g. 1 for medipacks, 12 for flares).
PickedUpObject(slot.m_pair.first, count.value_or(0));
}
static void InventoryRemove(ItemEnumPair slot, sol::optional<int> count)
{
// 0 is default for the same reason as in InventoryAdd.
RemoveObjectFromInventory(slot.m_pair.first, count.value_or(0));
}
static int InventoryGetCount(ItemEnumPair slot)
{
return GetInventoryCount(slot.m_pair.first);
}
static void InventorySetCount(ItemEnumPair slot, int count)
{
// add the amount we'd need to add to get to count
int currAmt = GetInventoryCount(slot.m_pair.first);
InventoryAdd(slot, count - currAmt);
}
static void InventoryCombine(int slot1, int slot2)
{
}
static void InventorySeparate(int slot)
{
}
static int CalculateDistance(GameScriptPosition const & pos1, GameScriptPosition const & pos2)
{
auto result = sqrt(SQUARE(pos1.x - pos2.x) + SQUARE(pos1.y - pos2.y) + SQUARE(pos1.z - pos2.z));
return static_cast<int>(round(result));
}
static int CalculateHorizontalDistance(GameScriptPosition const & pos1, GameScriptPosition const & pos2)
{
auto result = sqrt(SQUARE(pos1.x - pos2.x) + SQUARE(pos1.z - pos2.z));
return static_cast<int>(round(result));
}
// A "special" table is essentially one that TEN reserves and does things with.
template <typename funcIndex, typename funcNewindex, typename obj>
static void MakeSpecialTable(sol::state * state, std::string const & name, funcIndex const & fi, funcNewindex const & fni, obj objPtr)
{
std::string metaName{ name + "Meta" };
auto meta = sol::table{ *state, sol::create };
state->set(metaName, meta);
meta.set("__metatable", "\"metatable is protected\"");
auto tab = state->create_named_table(name);
tab[sol::metatable_key] = meta;
state->set(metaName, sol::nil);
meta.set_function("__index", fi, objPtr);
meta.set_function("__newindex", fni, objPtr);
}
static std::tuple<int, int> PercentToScreen(double x, double y)
{
auto fWidth = static_cast<double>(g_Configuration.Width);
auto fHeight = static_cast<double>(g_Configuration.Height);
int resX = std::round(fWidth / 100.0 * x);
int resY = std::round(fHeight / 100.0 * y);
//todo this still assumes a resolution of 800/600. account for this somehow
return std::make_tuple(resX, resY);
}
static std::tuple<double, double> ScreenToPercent(int x, int y)
{
auto fWidth = static_cast<double>(g_Configuration.Width);
auto fHeight = static_cast<double>(g_Configuration.Height);
double resX = x/fWidth * 100.0;
double resY = y/fHeight * 100.0;
return std::make_tuple(resX, resY);
}
GameScript::GameScript(sol::state* lua) : LuaHandler{ lua }
{
/*** Ambience and music
@section Music
*/
/*** Set and play an ambient track
@function SetAmbientTrack
@tparam string name of track (without file extension) to play
*/
m_lua->set_function(ScriptReserved_SetAmbientTrack, &SetAmbientTrack);
/*** Play an audio track
@function PlayAudioTrack
@tparam string name of track (without file extension) to play
@tparam bool loop if true, the track will loop; if false, it won't (default: false)
*/
m_lua->set_function(ScriptReserved_PlayAudioTrack, &PlayAudioTrack);
/*** Player inventory management
@section Inventory
*/
/*** Add x of an item to the inventory.
A count of 0 will add the "default" amount of that item
(i.e. the amount the player would get from a pickup of that type).
For example, giving "zero" crossbow ammo would give the player
10 instead, whereas giving "zero" medkits would give the player 1 medkit.
@function GiveInvItem
@tparam InvItem item the item to be added
@tparam int count the number of items to add (default: 0)
*/
m_lua->set_function(ScriptReserved_GiveInvItem, &InventoryAdd);
/***
Remove x of a certain item from the inventory.
As in @{GiveInvItem}, a count of 0 will remove the "default" amount of that item.
@function TakeInvItem
@tparam InvItem item the item to be removed
@tparam int count the number of items to remove (default: 0)
*/
m_lua->set_function(ScriptReserved_TakeInvItem, &InventoryRemove);
/***
Get the amount the player holds of an item.
@function GetInvItemCount
@tparam InvItem item the item to check
@treturn int the amount of the item the player has in the inventory
*/
m_lua->set_function(ScriptReserved_GetInvItemCount, &InventoryGetCount);
/***
Set the amount of a certain item the player has in the inventory.
Similar to @{GiveInvItem} but replaces with the new amount instead of adding it.
@function SetInvItemCount
@tparam @{InvItem} item the item to be set
@tparam int count the number of items the player will have
*/
m_lua->set_function(ScriptReserved_SetInvItemCount, &InventorySetCount);
/*** Game entity getters.
All Lua variables created with these functions will be non-owning.
This means that the actual in-game entity (object/camera/sink/whatever)
will _not_ be removed from the game if the Lua variable goes out of scope
or is destroyed in some other way.
@section getters
*/
/***
Get an ItemInfo by its name.
@function GetItemByName
@tparam string name the unique name of the item as set in, or generated by, Tomb Editor
@treturn ItemInfo a non-owning ItemInfo referencing the item.
*/
m_lua->set_function(ScriptReserved_GetItemByName, &GameScript::GetByName<GameScriptItemInfo, ScriptReserved_ItemInfo>, this);
/***
Get a MeshInfo by its name.
@function GetMeshByName
@tparam string name the unique name of the mesh as set in, or generated by, Tomb Editor
@treturn MeshInfo a non-owning MeshInfo referencing the mesh.
*/
m_lua->set_function(ScriptReserved_GetMeshByName, &GameScript::GetByName<GameScriptMeshInfo, ScriptReserved_MeshInfo>, this);
/***
Get a CameraInfo by its name.
@function GetCameraByName
@tparam string name the unique name of the camera as set in, or generated by, Tomb Editor
@treturn CameraInfo a non-owning CameraInfo referencing the camera.
*/
m_lua->set_function(ScriptReserved_GetCameraByName, &GameScript::GetByName<GameScriptCameraInfo, ScriptReserved_CameraInfo>, this);
/***
Get a SinkInfo by its name.
@function GetSinkByName
@tparam string name the unique name of the sink as set in, or generated by, Tomb Editor
@treturn SinkInfo a non-owning SinkInfo referencing the sink.
*/
m_lua->set_function(ScriptReserved_GetSinkByName, &GameScript::GetByName<GameScriptSinkInfo, ScriptReserved_SinkInfo>, this);
/***
Get a SoundSourceInfo by its name.
@function GetSoundSourceByName
@tparam string name the unique name of the sink as set in, or generated by, Tomb Editor
@treturn SoundSourceInfo a non-owning SoundSourceInfo referencing the sink.
*/
m_lua->set_function(ScriptReserved_GetSoundSourceByName, &GameScript::GetByName<GameScriptSoundSourceInfo, ScriptReserved_SoundSourceInfo>, this);
/***
Calculate the distance between two positions.
@function CalculateDistance
@tparam Position posA first position
@tparam Position posB second position
@treturn int the direct distance from one position to the other
*/
m_lua->set_function(ScriptReserved_CalculateDistance, &CalculateDistance);
/***
Calculate the horizontal distance between two positions.
@function CalculateHorizontalDistance
@tparam Position posA first position
@tparam Position posB second position
@treturn int the direct distance on the XZ plane from one position to the other
*/
m_lua->set_function(ScriptReserved_CalculateHorizontalDistance, &CalculateHorizontalDistance);
/***
Show some text on-screen.
@function ShowString
@tparam DisplayString str the string object to draw
@tparam float time the time in seconds for which to show the string.
If not given, the string will have an "infinite" life, and will show
until @{HideString} is called or until the level is finished.
Default: nil (i.e. infinite)
*/
m_lua->set_function(ScriptReserved_ShowString, &GameScript::ShowString, this);
/***
Hide some on-screen text.
@function HideString
@tparam DisplayString str the string object to hide. Must previously have been shown
with a call to @{ShowString}, or this function will have no effect.
*/
m_lua->set_function(ScriptReserved_HideString, [this](GameScriptDisplayString const& s) {ShowString(s, 0.0f); });
/***
Translate a pair of percentages to screen-space pixel coordinates.
To be used with @{DisplayString:SetPos} and @{DisplayString.new}.
@function PercentToScreen
@tparam float x percent value to translate to x-coordinate
@tparam float y percent value to translate to y-coordinate
@treturn int x x coordinate in pixels
@treturn int y y coordinate in pixels
*/
m_lua->set_function(ScriptReserved_PercentToScreen, &PercentToScreen);
/***
Translate a pair of coordinates to percentages of window dimensions.
To be used with @{DisplayString:GetPos}.
@function ScreenToPercent
@tparam int x pixel value to translate to a percentage of the window width
@tparam int y pixel value to translate to a percentage of the window height
@treturn float x coordinate as percentage
@treturn float y coordinate as percentage
*/
m_lua->set_function(ScriptReserved_ScreenToPercent, &ScreenToPercent);
MakeReadOnlyTable(ScriptReserved_ObjID, kObjIDs);
MakeReadOnlyTable(ScriptReserved_DisplayStringOption, kDisplayStringOptionNames);
ResetLevelTables();
MakeSpecialTable(m_lua, ScriptReserved_GameVars, &LuaVariables::GetVariable, &LuaVariables::SetVariable, &m_globals);
GameScriptItemInfo::Register(m_lua);
GameScriptItemInfo::SetNameCallbacks(
[this](auto && ... param) { return AddName(std::forward<decltype(param)>(param)...); },
[this](auto && ... param) { return RemoveName(std::forward<decltype(param)>(param)...); }
);
GameScriptMeshInfo::Register(m_lua);
GameScriptMeshInfo::SetNameCallbacks(
[this](auto && ... param) { return AddName(std::forward<decltype(param)>(param)...); },
[this](auto && ... param) { return RemoveName(std::forward<decltype(param)>(param)...); }
);
GameScriptCameraInfo::Register(m_lua);
GameScriptCameraInfo::SetNameCallbacks(
[this](auto && ... param) { return AddName(std::forward<decltype(param)>(param)...); },
[this](auto && ... param) { return RemoveName(std::forward<decltype(param)>(param)...); }
);
GameScriptSinkInfo::Register(m_lua);
GameScriptSinkInfo::SetNameCallbacks(
[this](auto && ... param) { return AddName(std::forward<decltype(param)>(param)...); },
[this](auto && ... param) { return RemoveName(std::forward<decltype(param)>(param)...); }
);
GameScriptAIObject::Register(m_lua);
GameScriptAIObject::SetNameCallbacks(
[this](auto && ... param) { return AddName(std::forward<decltype(param)>(param)...); },
[this](auto && ... param) { return RemoveName(std::forward<decltype(param)>(param)...); }
);
GameScriptSoundSourceInfo::Register(m_lua);
GameScriptSoundSourceInfo::SetNameCallbacks(
[this](auto && ... param) { return AddName(std::forward<decltype(param)>(param)...); },
[this](auto && ... param) { return RemoveName(std::forward<decltype(param)>(param)...); }
);
GameScriptDisplayString::Register(m_lua);
GameScriptDisplayString::SetCallbacks(
[this](auto && ... param) {return SetDisplayString(std::forward<decltype(param)>(param)...); },
[this](auto && ... param) {return ScheduleRemoveDisplayString(std::forward<decltype(param)>(param)...); },
[this](auto && ... param) {return GetDisplayString(std::forward<decltype(param)>(param)...); }
);
GameScriptPosition::Register(m_lua);
m_lua->new_enum<GAME_OBJECT_ID>("Object", {
{"LARA", ID_LARA}
});
}
void GameScript::ResetLevelTables()
{
MakeSpecialTable(m_lua, ScriptReserved_LevelFuncs, &GameScript::GetLevelFunc, &GameScript::SetLevelFunc, this);
MakeSpecialTable(m_lua, ScriptReserved_LevelVars, &LuaVariables::GetVariable, &LuaVariables::SetVariable, &m_locals);
}
sol::protected_function GameScript::GetLevelFunc(sol::table tab, std::string const& luaName)
{
if (m_levelFuncs.find(luaName) == m_levelFuncs.end())
return sol::lua_nil;
return m_levelFuncs.at(luaName);
}
bool GameScript::SetLevelFunc(sol::table tab, std::string const& luaName, sol::object value)
{
switch (value.get_type())
{
case sol::type::lua_nil:
m_levelFuncs.erase(luaName);
break;
case sol::type::function:
m_levelFuncs.insert_or_assign(luaName, value.as<sol::protected_function>());
break;
default:
//todo When we save the game, do we save the functions or just the names?
//todo It may be better just to save the names so that we can load the callbacks
//todo from the level script each time (vital if the builder updates their
//todo scripts after release -- squidshire, 31/08/2021
std::string error{ "Could not assign LevelFuncs." };
error += luaName + "; it must be a function (or nil).";
return ScriptAssert(false, error);
}
return true;
}
std::optional<std::reference_wrapper<UserDisplayString>> GameScript::GetDisplayString(DisplayStringIDType id)
{
auto it = m_userDisplayStrings.find(id);
if (std::cend(m_userDisplayStrings) == it)
return std::nullopt;
return std::ref(m_userDisplayStrings.at(id));
}
bool GameScript::ScheduleRemoveDisplayString(DisplayStringIDType id)
{
auto it = m_userDisplayStrings.find(id);
if (std::cend(m_userDisplayStrings) == it)
return false;
it->second.m_deleteWhenZero = true;
return true;
}
bool GameScript::SetDisplayString(DisplayStringIDType id, UserDisplayString const & ds)
{
return m_userDisplayStrings.insert_or_assign(id, ds).second;
}
void GameScript::SetCallbackDrawString(CallbackDrawString cb)
{
m_callbackDrawSring = cb;
}
void GameScript::FreeLevelScripts()
{
m_nameMap.clear();
m_levelFuncs.clear();
m_locals = LuaVariables{};
ResetLevelTables();
m_onStart = sol::nil;
m_onLoad = sol::nil;
m_onControlPhase = sol::nil;
m_onSave = sol::nil;
m_onEnd = sol::nil;
m_lua->collect_garbage();
}
void JumpToLevel(int levelNum)
{
if (levelNum >= g_GameFlow->GetNumLevels())
return;
LevelComplete = levelNum;
}
int GetSecretsCount()
{
return Statistics.Level.Secrets;
}
void SetSecretsCount(int secretsNum)
{
if (secretsNum > 255)
return;
Statistics.Level.Secrets = secretsNum;
}
void AddOneSecret()
{
if (Statistics.Level.Secrets >= 255)
return;
Statistics.Level.Secrets++;
PlaySecretTrack();
}
/*
void GameScript::MakeItemInvisible(short id)
{
if (m_itemsMap.find(id) == m_itemsMap.end())
return;
short itemNum = m_itemsMap[id];
ITEM_INFO* item = &g_Level.Items[itemNum];
if (item->active)
{
if (Objects[item->objectNumber].intelligent)
{
if (item->status == ITEM_ACTIVE)
{
item->touchBits = 0;
item->status = ITEM_INVISIBLE;
DisableBaddieAI(itemNum);
}
}
else
{
item->touchBits = 0;
item->status = ITEM_INVISIBLE;
}
}
}
*/
template <typename T>
void GameScript::GetVariables(std::map<std::string, T>& locals, std::map<std::string, T>& globals)
{
for (const auto& it : m_locals.variables)
{
if (it.second.is<T>())
locals.insert(std::pair<std::string, T>(it.first, it.second.as<T>()));
}
for (const auto& it : m_globals.variables)
{
if (it.second.is<T>())
globals.insert(std::pair<std::string, T>(it.first, it.second.as<T>()));
}
}
template void GameScript::GetVariables<bool>(std::map<std::string, bool>& locals, std::map<std::string, bool>& globals);
template void GameScript::GetVariables<float>(std::map<std::string, float>& locals, std::map<std::string, float>& globals);
template void GameScript::GetVariables<std::string>(std::map<std::string, std::string>& locals, std::map<std::string, std::string>& globals);
template <typename T>
void GameScript::SetVariables(std::map<std::string, T>& locals, std::map<std::string, T>& globals)
{
//TODO Look into serialising tables from these maps, too -- squidshire, 24/08/2021
m_locals.variables.clear();
for (const auto& it : locals)
{
m_locals.variables.insert(std::pair<std::string, sol::object>(it.first, sol::object(m_lua->lua_state(), sol::in_place, it.second)));
}
for (const auto& it : globals)
{
m_globals.variables.insert(std::pair<std::string, sol::object>(it.first, sol::object(m_lua->lua_state(), sol::in_place, it.second)));
}
}
template void GameScript::SetVariables<bool>(std::map<std::string, bool>& locals, std::map<std::string, bool>& globals);
template void GameScript::SetVariables<float>(std::map<std::string, float>& locals, std::map<std::string, float>& globals);
template void GameScript::SetVariables<std::string>(std::map<std::string, std::string>& locals, std::map<std::string, std::string>& globals);
template <typename R, char const * S, typename mapType>
std::unique_ptr<R> GetByName(std::string const & type, std::string const & name, mapType const & map)
{
ScriptAssert(map.find(name) != map.end(), std::string{ type + " name not found: " + name }, ERROR_MODE::TERMINATE);
return std::make_unique<R>(map.at(name), false);
}
void GameScript::AssignItemsAndLara()
{
m_lua->set("Lara", GameScriptItemInfo(Lara.itemNumber, false));
}
/*** Special objects
@section specialobjects
*/
/*** An @{ItemInfo} representing Lara herself.
@table Lara
*/
void GameScript::ResetVariables()
{
(*m_lua)["Lara"] = NULL;
}
void GameScript::ShowString(GameScriptDisplayString const & str, sol::optional<float> nSeconds)
{
auto it = m_userDisplayStrings.find(str.GetID());
it->second.m_timeRemaining = nSeconds.value_or(0.0f);
it->second.m_isInfinite = !nSeconds.has_value();
}
void GameScript::ProcessDisplayStrings(float dt)
{
auto it = std::begin(m_userDisplayStrings);
while (it != std::end(m_userDisplayStrings))
{
auto& str = it->second;
bool endOfLife = (0.0f >= str.m_timeRemaining);
if (str.m_deleteWhenZero && endOfLife)
{
ScriptAssertF(!str.m_isInfinite, "The infinite string {} (key \"{}\") went out of scope without being hidden.", it->first, str.m_key);
it = m_userDisplayStrings.erase(it);
}
else
{
if (!endOfLife || str.m_isInfinite)
{
char const* cstr = str.m_isTranslated ? g_GameFlow->GetString(str.m_key.c_str()) : str.m_key.c_str();
int flags = 0;
if (str.m_flags[static_cast<size_t>(DisplayStringOptions::CENTER)])
flags |= PRINTSTRING_CENTER;
if (str.m_flags[static_cast<size_t>(DisplayStringOptions::OUTLINE)])
flags |= PRINTSTRING_OUTLINE;
m_callbackDrawSring(cstr, str.m_color, str.m_x, str.m_y, flags);
str.m_timeRemaining -= dt;
}
++it;
}
}
}
sol::object LuaVariables::GetVariable(sol::table tab, std::string key)
{
if (variables.find(key) == variables.end())
return sol::lua_nil;
return variables[key];
}
void LuaVariables::SetVariable(sol::table tab, std::string key, sol::object value)
{
switch (value.get_type())
{
case sol::type::lua_nil:
variables.erase(key);
break;
case sol::type::boolean:
case sol::type::number:
case sol::type::string:
variables[key] = value;
break;
default:
ScriptAssert(false, "Variable " + key + " has an unsupported type.", ERROR_MODE::TERMINATE);
break;
}
}
void GameScript::ExecuteScriptFile(const std::string & luaFilename)
{
ExecuteScript(luaFilename);
}
void GameScript::ExecuteFunction(std::string const & name)
{
sol::protected_function func = (*m_lua)["LevelFuncs"][name.c_str()];
auto r = func();
if (!r.valid())
{
sol::error err = r;
ScriptAssertF(false, "Could not execute function {}: {}", name, err.what());
}
}
static void doCallback(sol::protected_function const & func, std::optional<float> dt = std::nullopt) {
auto r = dt.has_value() ? func(dt) : func();
if (!r.valid())
{
sol::error err = r;
ScriptAssert(false, err.what(), ERROR_MODE::TERMINATE);
}
}
void GameScript::OnStart()
{
if (m_onStart.valid())
doCallback(m_onStart);
}
void GameScript::OnLoad()
{
if(m_onLoad.valid())
doCallback(m_onLoad);
}
void GameScript::OnControlPhase(float dt)
{
if(m_onControlPhase.valid())
doCallback(m_onControlPhase, dt);
}
void GameScript::OnSave()
{
if(m_onSave.valid())
doCallback(m_onSave);
}
void GameScript::OnEnd()
{
if(m_onEnd.valid())
doCallback(m_onEnd);
}
/*** 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.
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.
@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.
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.playerSnoopedInDraws = true
And in the script file for the level with the boss, you could write:
if GameVars.playerSnoopedInDraws then
PlayAudioTrack("how_dare_you.wav")
end
Unlike @{LevelVars}, this table will remain intact for the entirety of the game.
@table GameVars
*/
/*** A table with level-specific functions.
This serves two 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
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`.
@tfield function OnStart Will be called when a level is loaded
@tfield function OnLoad Will be called when a saved game is loaded
@tfield function(float) OnControlPhase 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
@tfield function OnEnd Will be called when leaving a level. This includes finishing it, exiting to the menu, or loading a save in a different level.
@table LevelFuncs
*/
void GameScript::InitCallbacks()
{
auto assignCB = [this](sol::protected_function& func, std::string const & luaFunc) {
std::string fullName = "LevelFuncs." + luaFunc;
func = (*m_lua)["LevelFuncs"][luaFunc];
std::string err{ "Level's script does not define callback " + fullName};
if (!ScriptAssert(func.valid(), err)) {
ScriptWarn("Defaulting to no " + fullName + " behaviour.");
}
};
assignCB(m_onStart, "OnStart");
assignCB(m_onLoad, "OnLoad");
assignCB(m_onControlPhase, "OnControlPhase");
assignCB(m_onSave, "OnSave");
assignCB(m_onEnd, "OnEnd");
}

View file

@ -0,0 +1,164 @@
#pragma once
#include "frameworkandsol.h"
#include "GameScriptAIObject.h"
#include "ScriptAssert.h"
#include "GameScriptPosition.h"
#include "ScriptUtil.h"
/***
AI object
@entityclass AIObject
@pragma nostrip
*/
constexpr auto LUA_CLASS_NAME{ "AIObject" };
static auto index_error = index_error_maker(GameScriptAIObject, LUA_CLASS_NAME);
static auto newindex_error = newindex_error_maker(GameScriptAIObject, LUA_CLASS_NAME);
GameScriptAIObject::GameScriptAIObject(AI_OBJECT & ref, bool temp) : m_aiObject{ref}, m_temporary{ temp }
{};
GameScriptAIObject::~GameScriptAIObject() {
if (m_temporary)
{
s_callbackRemoveName(m_aiObject.luaName);
}
}
void GameScriptAIObject::Register(sol::state* state)
{
state->new_usertype<GameScriptAIObject>(LUA_CLASS_NAME,
sol::meta_function::index, index_error,
sol::meta_function::new_index, newindex_error,
/// (@{Position}) position in level
// @mem pos
"pos", sol::property(&GameScriptAIObject::GetPos, &GameScriptAIObject::SetPos),
/// (int) y-axis rotation
// @mem yRot
"yRot", sol::property(&GameScriptAIObject::GetYRot, &GameScriptAIObject::SetYRot),
/// (string) unique string identifier.
// e.g. "door_back_room" or "cracked_greek_statue"
// @mem name
"name", sol::property(&GameScriptAIObject::GetName, &GameScriptAIObject::SetName),
/// (int) room number
// @mem room
"room", sol::property(&GameScriptAIObject::GetRoom, &GameScriptAIObject::SetRoom),
/// (@{ObjID}) object ID
// @mem objID
"objID", sol::property(&GameScriptAIObject::GetObjID, &GameScriptAIObject::SetObjID),
/// (short) flags
// @mem flags
"flags", sol::property(&GameScriptAIObject::GetFlags, &GameScriptAIObject::SetFlags),
/// (short) trigger flags
// @mem triggerFlags
"triggerFlags", sol::property(&GameScriptAIObject::GetTriggerFlags, &GameScriptAIObject::SetTriggerFlags),
/// (short) box number
// @mem boxNumber
"boxNumber", sol::property(&GameScriptAIObject::GetBoxNumber, &GameScriptAIObject::SetBoxNumber)
);
}
GameScriptPosition GameScriptAIObject::GetPos() const
{
return GameScriptPosition{ m_aiObject.x, m_aiObject.y, m_aiObject.z };
}
void GameScriptAIObject::SetPos(GameScriptPosition const& pos)
{
m_aiObject.x = pos.x;
m_aiObject.y = pos.y;
m_aiObject.z = pos.z;
}
GAME_OBJECT_ID GameScriptAIObject::GetObjID() const
{
return m_aiObject.objectNumber;
}
void GameScriptAIObject::SetObjID(GAME_OBJECT_ID objNum)
{
m_aiObject.objectNumber = objNum;
}
short GameScriptAIObject::GetYRot() const
{
return m_aiObject.yRot;
}
void GameScriptAIObject::SetYRot(short yRot)
{
m_aiObject.yRot = yRot;
}
std::string GameScriptAIObject::GetName() const
{
return m_aiObject.luaName;
}
void GameScriptAIObject::SetName(std::string const & id)
{
ScriptAssert(!id.empty(), "Name cannot be blank", ERROR_MODE::TERMINATE);
// remove the old name if we have one
s_callbackRemoveName(m_aiObject.luaName);
// un-register any other objects using this name.
// maybe we should throw an error if another object
// already uses the name...
s_callbackRemoveName(id);
m_aiObject.luaName = id;
// todo add error checking
s_callbackSetName(id, m_aiObject);
}
short GameScriptAIObject::GetRoom() const
{
return m_aiObject.roomNumber;
}
void GameScriptAIObject::SetRoom(short room)
{
m_aiObject.roomNumber = room;
}
short GameScriptAIObject::GetTriggerFlags() const
{
return m_aiObject.triggerFlags;
}
void GameScriptAIObject::SetTriggerFlags(short tf)
{
m_aiObject.triggerFlags = tf;
}
short GameScriptAIObject::GetFlags() const
{
return m_aiObject.flags;
}
void GameScriptAIObject::SetFlags(short tf)
{
m_aiObject.flags = tf;
}
short GameScriptAIObject::GetBoxNumber() const
{
return m_aiObject.boxNumber;
}
void GameScriptAIObject::SetBoxNumber(short bn)
{
m_aiObject.boxNumber = bn;
}

View file

@ -0,0 +1,21 @@
#include "framework.h"
#include "GameScriptAnimations.h"
/***
New custom animations which Lara can perform.
@pregameclass Animations
@pragma nostrip
*/
void GameScriptAnimations::Register(sol::state* lua)
{
lua->new_usertype<GameScriptAnimations>("Animations",
"crawlExtended", &GameScriptAnimations::CrawlExtended,
"crouchRoll", &GameScriptAnimations::CrouchRoll,
"crawlspaceSwandive", &GameScriptAnimations::CrawlspaceSwandive,
"monkeyTurn180", &GameScriptAnimations::MonkeyTurn180,
"monkeyAutoJump", &GameScriptAnimations::MonkeyAutoJump,
"oscillateHang", &GameScriptAnimations::OscillateHang,
"pose", &GameScriptAnimations::Pose
);
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "ScriptAssert.h"
#include <string>
namespace sol {
class state;
}
struct GameScriptAnimations
{
bool CrawlExtended; // Extended crawl moveset
bool CrouchRoll; // Crouch roll
bool CrawlspaceSwandive; // Swandive into crawlspaces
bool MonkeyTurn180; // 180 degree turn on monkey swing
bool MonkeyAutoJump; // Auto jump to monkey swing when pressing UP + ACTION beneath
bool OscillateHang; // Grab thin ledge animation from TR1 and 2
bool Pose; // Crossed arms AFK posing
static void Register(sol::state* lua);
};

View file

@ -0,0 +1,27 @@
#include "frameworkandsol.h"
#include "GameScriptAudioTrack.h"
/***
Metadata about audio tracks (music and ambience).
__In progress__
@pregameclass AudioTrack
@pragma nostrip
*/
// TODO FIXME find out what is meant to happen and whether we need this or not
GameScriptAudioTrack::GameScriptAudioTrack(std::string const & trackName, bool looped)
{
this->trackName = trackName;
this->looped = looped;
}
void GameScriptAudioTrack::Register(sol::state* lua)
{
lua->new_usertype<GameScriptAudioTrack>("AudioTrack",
sol::constructors<GameScriptAudioTrack(std::string, bool)>(),
"trackName", &GameScriptAudioTrack::trackName,
"looped", &GameScriptAudioTrack::looped
);
}

View file

@ -0,0 +1,91 @@
#include "frameworkandsol.h"
#include "ScriptAssert.h"
#include "GameScriptCameraInfo.h"
#include "GameScriptPosition.h"
#include "ScriptUtil.h"
/***
Camera info
@entityclass CameraInfo
@pragma nostrip
*/
static constexpr auto LUA_CLASS_NAME{ "CameraInfo" };
static auto index_error = index_error_maker(GameScriptCameraInfo, LUA_CLASS_NAME);
static auto newindex_error = newindex_error_maker(GameScriptCameraInfo, LUA_CLASS_NAME);
GameScriptCameraInfo::GameScriptCameraInfo(LEVEL_CAMERA_INFO & ref, bool temp) : m_camera{ref}, m_temporary{ temp }
{};
GameScriptCameraInfo::~GameScriptCameraInfo() {
if (m_temporary)
{
s_callbackRemoveName(m_camera.luaName);
}
}
void GameScriptCameraInfo::Register(sol::state* state)
{
state->new_usertype<GameScriptCameraInfo>(LUA_CLASS_NAME,
sol::meta_function::index, index_error,
sol::meta_function::new_index, newindex_error,
/// (@{Position}) position in level
// @mem pos
"pos", sol::property(&GameScriptCameraInfo::GetPos, &GameScriptCameraInfo::SetPos),
/// (string) unique string identifier.
// e.g. "flyby\_start" or "big\_door\_hint"
// @mem name
"name", sol::property(&GameScriptCameraInfo::GetName, &GameScriptCameraInfo::SetName),
/// (string) room number
// @mem room
"room", sol::property(&GameScriptCameraInfo::GetRoom, &GameScriptCameraInfo::SetRoom)
);
}
GameScriptPosition GameScriptCameraInfo::GetPos() const
{
return GameScriptPosition{ m_camera.x, m_camera.y, m_camera.z };
}
void GameScriptCameraInfo::SetPos(GameScriptPosition const& pos)
{
m_camera.x = pos.x;
m_camera.y = pos.y;
m_camera.z = pos.z;
}
std::string GameScriptCameraInfo::GetName() const
{
return m_camera.luaName;
}
void GameScriptCameraInfo::SetName(std::string const & id)
{
ScriptAssert(!id.empty(), "Name cannot be blank", ERROR_MODE::TERMINATE);
// remove the old name if we have one
s_callbackRemoveName(m_camera.luaName);
// un-register any other objects using this name.
// maybe we should throw an error if another object
// already uses the name...
s_callbackRemoveName(id);
m_camera.luaName = id;
// todo add error checking
s_callbackSetName(id, m_camera);
}
short GameScriptCameraInfo::GetRoom() const
{
return m_camera.roomNumber;
}
void GameScriptCameraInfo::SetRoom(short room)
{
m_camera.roomNumber = room;
}

View file

@ -0,0 +1,151 @@
#include "frameworkandsol.h"
#include "GameScriptColor.h"
/***
An RGBA or RGB color.
Components are specified in bytes; all values are clamped to [0, 255].
@miscclass Color
@pragma nostrip
*/
void GameScriptColor::Register(sol::state* state)
{
state->new_usertype<GameScriptColor>("Color",
sol::constructors<GameScriptColor(byte, byte, byte), GameScriptColor(byte, byte, byte, byte)>(),
sol::meta_function::to_string, &GameScriptColor::ToString,
/// (int) red component
//@mem r
"r", sol::property(&GameScriptColor::GetR, &GameScriptColor::SetR),
/// (int) green component
//@mem g
"g", sol::property(&GameScriptColor::GetG, &GameScriptColor::SetG),
/// (int) blue component
//@mem b
"b", sol::property(&GameScriptColor::GetB, &GameScriptColor::SetB),
/// (int) alpha component (255 is opaque, 0 is invisible)
//@mem a
"a", sol::property(&GameScriptColor::GetA, &GameScriptColor::SetA)
);
}
/***
@int R red component
@int G green component
@int B blue component
@return A Color object.
@function Color.new
*/
GameScriptColor::GameScriptColor(byte r, byte g, byte b)
{
SetR(r);
SetG(g);
SetB(b);
}
/***
@int R red component
@int G green component
@int B blue component
@int A alpha component (255 is opaque, 0 is invisible)
@return A Color object.
@function Color.new
*/
GameScriptColor::GameScriptColor(byte r, byte g, byte b, byte a) : GameScriptColor(r, g, b)
{
SetA(a);
}
GameScriptColor::GameScriptColor(Vector3 const& col) : GameScriptColor{ col.x, col.y, col.z } {}
GameScriptColor::GameScriptColor(Vector4 const& col) : GameScriptColor{ col.x, col.y, col.z, col.w } {}
GameScriptColor::GameScriptColor(D3DCOLOR col)
{
b = col & 0xFF;
col >>= 8;
g = col & 0xFF;
col >>= 8;
r = col & 0xFF;
col >>= 8;
a = col & 0xFF;
}
GameScriptColor::operator Vector3() const
{
return Vector3{ float(r), float(g), float(b) };
}
GameScriptColor::operator Vector4() const
{
return Vector4{ float(r), float(g), float(b), float(a) };
}
// D3DCOLOR is 32 bits and is layed out as ARGB.
GameScriptColor::operator D3DCOLOR() const
{
D3DCOLOR col = a;
col <<= 8;
col += r;
col <<= 8;
col += g;
col <<= 8;
col += b;
return col;
}
byte GameScriptColor::GetR() const
{
return r;
}
void GameScriptColor::SetR(byte v)
{
r = std::clamp<byte>(v, 0, 255);
}
byte GameScriptColor::GetG() const
{
return g;
}
void GameScriptColor::SetG(byte v)
{
g = std::clamp<byte>(v, 0, 255);
}
byte GameScriptColor::GetB() const
{
return b;
}
void GameScriptColor::SetB(byte v)
{
b = std::clamp<byte>(v, 0, 255);
}
byte GameScriptColor::GetA() const
{
return a;
}
void GameScriptColor::SetA(byte v)
{
a = std::clamp<byte>(v, 0, 255);
}
/***
@tparam Color color this color
@treturn string A string showing the r, g, b, and a values of the color
@function __tostring
*/
std::string GameScriptColor::ToString() const
{
return "{" + std::to_string(r) + ", " + std::to_string(g) + ", " + std::to_string(b) + ", " + std::to_string(a) + "}";
}

View file

@ -0,0 +1,208 @@
#include "frameworkandsol.h"
#include "GameScriptDisplayString.h"
#include "ScriptAssert.h"
#include "ReservedScriptNames.h"
/*** A string appearing on the screen.
Can be used for subtitles and "2001, somewhere in Egypt"-style messages.
Uses screen-space coordinates, with x values specifying the number of pixels from the left of the window,
and y values specifying the number of pixels from the top of the window.
Since different players will have different resolutions, you should work in terms of percentages where possible,
and use @{Level-specific.ScreenToPercent|ScreenToPercent} and @{Level-specific.PercentToScreen|PercentToScreen}
when you need to use screen-space coordinates.
@miscclass DisplayString
@pragma nostrip
*/
UserDisplayString::UserDisplayString(std::string const& key, int x, int y, D3DCOLOR col, FlagArray const & flags, bool translated) :
m_key{ key },
m_x{ x },
m_y{ y },
m_color{ col },
m_flags{ flags },
m_isTranslated{ translated }
{
}
GameScriptDisplayString::GameScriptDisplayString() {
// We don't ever dereference this pointer; it's just
// a handy way to get a unique key for a hash map.
//TODO Make sure to reset this when loading a save,
//TODO because this key will have a chance to no longer
//TODO be unique. -- squidshire, 28/08/2021
m_id = reinterpret_cast<DisplayStringIDType>(this);
}
// Helper type to allow us to more easily specify "give a value of type X or just give nil" parameters.
// Sol doesn't (at the time of writing) have any mechanisms to do this kind of optional argument without
// drawbacks, or at least no mechanisms that I could find.
//
// sol::optional doesn't distinguish between nil values and values of the wrong type
// (so we can't provide the user with an error message to tell them they messed up).
//
// std::variant works better, providing an error if the user passes in an arg of the wrong type, but
// the error isn't too helpful and exposes a lot of C++ code which will not help them fix the error.
//
// sol::object lets us check that the user has given the right type, but takes valuable type information
// away from the function's C++ signature, giving us things like void func(sol::object, sol::object, sol::object),
// even if the function's actual expected parameter types are (for example) float, sol::table, SomeUserType.
//
// This alias is an effort to avoid the above problems.
template <typename ... Ts> using TypeOrNil = std::variant<Ts..., sol::nil_t, sol::object>;
/*** Create a DisplayString.
For use in @{Level-specific.ShowString|ShowString} and @{Level-specific.HideString|HideString}.
@function DisplayString.new
@tparam string str string to print or key of translated string
@tparam int x x-coordinate of top-left of string (or the center if DisplayStringOption.CENTER is given)
@tparam int y y-coordinate of top-left of string (or the center if DisplayStringOption.CENTER is given)
@tparam Color color the color of the text
@tparam table flags a table of display options. Can be empty or omitted. The possible values and their effects are...
DisplayStringOption.CENTER -- see x and y parameters
DisplayStringOption.SHADOW -- will give the text a small shadow
__Default: empty__
@tparam bool translated if false or omitted, the str argument will be printed.
If true, the str argument will be the key of a translated string specified in
strings.lua. __Default: false__.
@return A new DisplayString object.
*/
std::unique_ptr<GameScriptDisplayString> CreateString(std::string const & key, int x, int y, GameScriptColor col, TypeOrNil<sol::table> flags, TypeOrNil<bool> maybeTranslated)
{
auto ptr = std::make_unique<GameScriptDisplayString>();
auto id = ptr->GetID();
FlagArray f{};
if (std::holds_alternative<sol::table>(flags))
{
auto tab = std::get<sol::table>(flags);
for (auto& e : tab)
{
auto i = e.second.as<size_t>();
f[i] = true;
}
}
else if (!std::holds_alternative<sol::nil_t>(flags))
{
ScriptAssertF(false, "Wrong argument type for {}.new \"flags\" argument; must be a table or nil.", ScriptReserved_DisplayString);
}
bool translated = false;
if (std::holds_alternative<bool>(maybeTranslated))
translated = std::get<bool>(maybeTranslated);
else if (!std::holds_alternative<sol::nil_t>(maybeTranslated))
ScriptAssertF(false, "Wrong argument type for {}.new \"translated\" argument; must be a bool or nil.", ScriptReserved_DisplayString);
UserDisplayString ds{ key, x, y, col, f, translated};
GameScriptDisplayString::s_setItemCallback(id, ds);
return ptr;
}
GameScriptDisplayString::~GameScriptDisplayString()
{
s_removeItemCallback(m_id);
}
void GameScriptDisplayString::Register(sol::state* state)
{
state->new_usertype<GameScriptDisplayString>(
"DisplayString",
"new", &CreateString,
/// (@{Color}) RBG color
// @mem col
"col", sol::property(&GameScriptDisplayString::GetCol, &GameScriptDisplayString::SetCol),
/// (string) String key to use. If `translated` is true when @{DisplayString.new}
// is called, this will be the string key for the translation that will be displayed.
// If false or omitted, this will be the string that's displayed.
// @mem key
"key", sol::property(&GameScriptDisplayString::SetKey, &GameScriptDisplayString::GetKey),
/// Set the position of the string.
// Screen-space coordinates are expected.
// @function DisplayString:SetPos
// @tparam int x x-coordinate of the string
// @tparam int y y-coordinate of the string
"SetPos", &GameScriptDisplayString::SetPos,
/// Get the position of the string.
// Screen-space coordinates are returned.
// @function DisplayString:GetPos
// @treturn int x x-coordinate of the string
// @treturn int y y-coordinate of the string
"GetPos", &GameScriptDisplayString::GetPos
);
}
DisplayStringIDType GameScriptDisplayString::GetID() const
{
return m_id;
}
void GameScriptDisplayString::SetPos(int x, int y)
{
UserDisplayString& s = s_getItemCallback(m_id).value();
s.m_x = x;
s.m_y = y;
}
std::tuple<int, int> GameScriptDisplayString::GetPos() const
{
UserDisplayString& s = s_getItemCallback(m_id).value();
return std::make_tuple(s.m_x, s.m_y);
}
void GameScriptDisplayString::SetCol(GameScriptColor const & col)
{
UserDisplayString& s = s_getItemCallback(m_id).value();
s.m_color = col;
//todo maybe change getItemCallback to return a ref instead? or move its
//todo UserDisplayString object? and then move back?
//s_addItemCallback(m_id, s);
}
GameScriptColor GameScriptDisplayString::GetCol()
{
UserDisplayString& s = s_getItemCallback(m_id).value();
return s.m_color;
}
void GameScriptDisplayString::SetKey(std::string const & key)
{
UserDisplayString& s = s_getItemCallback(m_id).value();
s.m_key = key;
}
std::string GameScriptDisplayString::GetKey() const
{
UserDisplayString& s = s_getItemCallback(m_id).value();
return s.m_key;
}
SetItemCallback GameScriptDisplayString::s_setItemCallback = [](DisplayStringIDType, UserDisplayString)
{
std::string err = "\"Set string\" callback is not set.";
throw TENScriptException(err);
return false;
};
// This is called by a destructor (or will be if we forget to assign it during a refactor)
// and destructors "must never throw", so we terminate instead.
RemoveItemCallback GameScriptDisplayString::s_removeItemCallback = [](DisplayStringIDType)
{
TENLog("\"Remove string\" callback is not set.", LogLevel::Error);
std::terminate();
return false;
};
GetItemCallback GameScriptDisplayString::s_getItemCallback = [](DisplayStringIDType)
{
std::string err = "\"Get string\" callback is not set.";
throw TENScriptException(err);
return std::nullopt;
};

View file

@ -0,0 +1,56 @@
#include "framework.h"
#include "GameScriptFog.h"
/*** Describes a layer of moving clouds.
As seen in TR4's City of the Dead.
@pregameclass SkyLayer
@pragma nostrip
*/
void GameScriptFog::Register(sol::state* lua)
{
lua->new_usertype<GameScriptFog>("Fog",
sol::constructors<GameScriptFog(GameScriptColor const&, short, short)>(),
/// (@{Color}) RGB sky color
//@mem color
"color", sol::property(&GameScriptFog::SetColor),
/*** (int) min distance.
This is the distance at which the fog starts
@mem minDistance*/
"minDistance", &GameScriptFog::MinDistance,
/*** (int) max distance.
This is the distance at which the fog reaches the maximum strength
@mem maxDistance*/
"maxDistance", & GameScriptFog::MaxDistance
);
}
/***
@tparam Color color RGB color
@tparam int speed cloud speed
@return A SkyLayer object.
@function SkyLayer.new
*/
GameScriptFog::GameScriptFog(GameScriptColor const& col, short minDistance, short maxDistance)
{
SetColor(col);
MinDistance = minDistance;
MaxDistance = maxDistance;
Enabled = true;
}
void GameScriptFog::SetColor(GameScriptColor const& col)
{
R = col.GetR();
G = col.GetG();
B = col.GetB();
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "GameScriptColor.h"
namespace sol {
class state;
}
struct GameScriptFog
{
bool Enabled{ false };
byte R{ 0 };
byte G{ 0 };
byte B{ 0 };
short MinDistance{ 0 };
short MaxDistance{ 0 };
GameScriptFog() = default;
GameScriptFog(GameScriptColor const& col, short minDistance, short maxDistance);
void SetColor(GameScriptColor const& col);
static void Register(sol::state*);
};

View file

@ -0,0 +1,123 @@
#include "frameworkandsol.h"
#include "GameScriptInventoryObject.h"
#include "ScriptAssert.h"
#include <string>
/***
Represents the properties of an object as it appears in the inventory.
@pregameclass InventoryObject
@pragma nostrip
*/
/*** Create an inventoryObject item. Use this if you want to specify property values later later.
The default property values are not disclosed here, since at the time of writing, they are subject to change.
@function InventoryObject.new
@return an InventoryObject
*/
/*** For more information on each parameter, see the
associated getters and setters.
@function InventoryObject.new
@tparam string nameKey name key
@tparam InvItem slot slot of inventory object to change
@tparam int yOffset y-axis offset (positive values move the item down)
@tparam float scale item size (1 being standard size)
@tparam Rotation rot rotation about x, y, and z axes
@tparam RotationAxis rotAxisWhenCurrent axis to rotate around in inventory
@tparam int meshBits not currently implemented
@tparam ItemAction action is this usable, equippable, or examinable?
@return an InventoryObject
*/
GameScriptInventoryObject::GameScriptInventoryObject(std::string const& a_name, ItemEnumPair a_slot, short a_yOffset, float a_scale, GameScriptRotation const & a_rot, RotationFlags a_rotationFlags, int a_meshBits, ItemOptions a_action) :
name{ a_name },
slot{ a_slot.m_pair.second },
yOffset{ a_yOffset },
scale{ a_scale },
rot{ a_rot },
rotationFlags{ a_rotationFlags },
meshBits{ a_meshBits }
{
SetAction(a_action);
}
void GameScriptInventoryObject::Register(sol::state * lua)
{
lua->new_usertype<GameScriptInventoryObject>("InventoryObject",
sol::constructors<GameScriptInventoryObject(std::string const &, ItemEnumPair, short, float, GameScriptRotation const &, RotationFlags, int, ItemOptions), GameScriptInventoryObject()>(),
/*** (string) string key for the item's (localised) name. Corresponds to an entry in strings.lua.
@mem nameKey
*/
"nameKey", &GameScriptInventoryObject::name,
/*** (@{InvItem}) slot of item whose inventory display properties you wish to change
@mem slot
*/
"slot", sol::property(&GameScriptInventoryObject::SetSlot),
/*** (float) y-axis offset (positive values will move the item lower).
A value of about 100 will cause the item to display directly below its usual position.
@mem yOffset
*/
"yOffset", &GameScriptInventoryObject::yOffset,
/*** (float) Item's size when displayed in the inventory as a multiple of its "regular" size.
A value of 0.5 will cause the item to render at half the size,
and a value of 2 will cause the item to render at twice the size.
@mem scale
*/
"scale", &GameScriptInventoryObject::scale,
/*** (@{Rotation}) Item's rotation about its origin when displayed in the inventory.
@mem rot
*/
"rot", &GameScriptInventoryObject::rot,
/*** (RotationAxis) Axis to rotate about when the item is being looked at in the inventory.
Note that this is entirely separate from the `rot` field described above.
Must be RotationAxis.X, RotationAxis.Y or RotationAxis.Z.
e.g. `myItem.rotAxisWhenCurrent = RotationAxis.X`
@mem rotAxisWhenCurrent
*/
"rotAxisWhenCurrent", &GameScriptInventoryObject::rotationFlags,
/*** (int) __Not currently implemented__ (will have no effect regardless of what you set it to)
@mem meshBits
*/
"meshBits", &GameScriptInventoryObject::meshBits,
/*** (ItemAction) What can the player do with the item?
Must be one of:
EQUIP
USE
EXAMINE
e.g. `myItem.action = ItemAction.EXAMINE`
@mem action
*/
"action", sol::property(&GameScriptInventoryObject::SetAction)
);
}
// Add validation so the user can't choose something unimplemented
void GameScriptInventoryObject::SetAction(ItemOptions a_action)
{
bool isSupported = (a_action == ItemOptions::OPT_EQUIP) ||
(a_action == ItemOptions::OPT_USE) ||
(a_action == ItemOptions::OPT_EXAMINABLE);
if (!ScriptAssert(isSupported, "Unsupported item action: " + std::to_string(a_action)))
{
ItemOptions def = ItemOptions::OPT_USE;
ScriptWarn("Defaulting to " + std::to_string(def));
action = def;
}
else
{
action = a_action;
}
}
void GameScriptInventoryObject::SetSlot(ItemEnumPair a_slot)
{
slot = a_slot.m_pair.second;
}

View file

@ -0,0 +1,582 @@
#include "frameworkandsol.h"
#include "ScriptAssert.h"
#include "GameScriptItemInfo.h"
#include "ScriptUtil.h"
#include "Game/items.h"
#include "Objects/objectslist.h"
#include "Specific/level.h"
#include "Specific/setup.h"
#include "Game/control/lot.h"
#include "GameScriptPosition.h"
#include "GameScriptRotation.h"
#include "Specific/trmath.h"
/***
Represents any object inside the game world.
Examples include statics, enemies, doors,
pickups, and Lara herself.
@entityclass ItemInfo
@pragma nostrip
*/
constexpr auto LUA_CLASS_NAME{ "ItemInfo" };
static auto index_error = index_error_maker(GameScriptItemInfo, LUA_CLASS_NAME);
static auto newindex_error = newindex_error_maker(GameScriptItemInfo, LUA_CLASS_NAME);
GameScriptItemInfo::GameScriptItemInfo(short num, bool temp) : m_item{ &g_Level.Items[num] }, m_num{ num }, m_initialised{ false }, m_temporary{ temp }
{};
GameScriptItemInfo::GameScriptItemInfo(GameScriptItemInfo&& other) noexcept :
m_item { std::exchange(other.m_item, nullptr) },
m_num{ std::exchange(other.m_num, NO_ITEM) },
m_initialised{ std::exchange(other.m_initialised, false) },
m_temporary{ std::exchange(other.m_temporary, false) }
{};
// todo.. how to check if item is killed outside of script?
GameScriptItemInfo::~GameScriptItemInfo() {
// todo.. see if there's a better default state than -1
if (m_temporary && (m_num > NO_ITEM))
{
s_callbackRemoveName(m_item->luaName);
KillItem(m_num);
}
}
/*** If you create items with this you NEED to give a position, room,
and object number, and then call InitialiseItem before it will work.
@function ItemInfo.new
*/
/*** Like above, but the returned variable controls the
lifetime of the object (it will be destroyed when the variable goes
out of scope).
@function ItemInfo.newTemporary
*/
template <bool temp> std::unique_ptr<GameScriptItemInfo> CreateEmpty()
{
short num = CreateItem();
ITEM_INFO * item = &g_Level.Items[num];
return std::make_unique<GameScriptItemInfo>(num, temp);
}
/*** For more information on each parameter, see the
associated getters and setters. If you do not know what to set for these,
most can just be set to zero (see usage). See also the overload which
takes no arguments.
@function ItemInfo.new
@tparam ObjID object ID
@tparam string name Lua name of the item
@tparam Position position position in level
@tparam Rotation rotation rotation about x, y, and z axes
@tparam int room room ID item is in
@tparam int currentAnimState current animation state
@tparam int requiredAnimState required animation state
@tparam int goalAnimState goal animation state
@tparam int animNumber anim number
@tparam int frameNumber frame number
@tparam int hp HP of item
@tparam int OCB ocb of item
@tparam int itemFlags item flags
@tparam int AIBits byte with AI bits
@tparam int status status of object
@tparam bool active is item active or not?
@tparam bool hitStatus hit status of object
@return reference to new ItemInfo object
@usage
local item = ItemInfo.new(
ObjID.PISTOLS_ITEM, -- object id
"test", -- name
Position.new(18907, 0, 21201),
Rotation.new(0,0,0),
0, -- room
0, -- currentAnimState
0, -- requiredAnimState
0, -- goalAnimState
0, -- animNumber
0, -- frameNumber
0, -- HP
0, -- OCB
{0,0,0,0,0,0,0,0}, -- itemFlags
0, -- AIBits
0, -- status
false, -- active
false, -- hitStatus
)
*/
/*** Like the above, but the returned variable controls the
lifetime of the object (it will be destroyed when the variable goes
out of scope).
@function ItemInfo.newTemporary
@param see_above same as above function
*/
template <bool temp> static std::unique_ptr<GameScriptItemInfo> Create(
GAME_OBJECT_ID objID,
std::string name,
GameScriptPosition pos,
GameScriptRotation rot,
short room,
int currentAnimState,
int requiredAnimState,
int goalAnimState,
int animNumber,
int frameNumber,
short hp,
short ocb,
sol::as_table_t<std::array<short, 8>> flags,
byte aiBits,
short status,
bool active,
bool hitStatus
)
{
short num = CreateItem();
auto ptr = std::make_unique<GameScriptItemInfo>(num, temp);
ITEM_INFO* item = &g_Level.Items[num];
ptr->SetPos(pos);
ptr->SetRot(rot);
ptr->SetRoom(room);
ptr->SetObjectID(objID);
InitialiseItem(num);
ptr->SetName(name);
ptr->SetCurrentAnimState(currentAnimState);
ptr->SetRequiredAnimState(requiredAnimState);
ptr->SetGoalAnimState(goalAnimState);
ptr->SetAnimNumber(animNumber);
ptr->SetFrameNumber(frameNumber);
ptr->SetHP(hp);
ptr->SetOCB(ocb);
ptr->SetItemFlags(flags);
ptr->SetAIBits(aiBits);
ptr->SetStatus(status);
ptr->SetActive(active);
ptr->SetHitStatus(hitStatus);
return ptr;
}
void GameScriptItemInfo::Register(sol::state* state)
{
state->new_usertype<GameScriptItemInfo>(LUA_CLASS_NAME,
"new", sol::overload(Create<false>, CreateEmpty<false>),
"newTemporary", sol::overload(Create<true>, CreateEmpty<true>),
sol::meta_function::index, index_error,
sol::meta_function::new_index, newindex_error,
/// Initialise an item.
//Use this if you called new with no arguments
// @function ItemInfo.Init
"Init", &GameScriptItemInfo::Init,
/// Enable the item
// @function ItemInfo:EnableItem
"Enable", &GameScriptItemInfo::EnableItem,
/// Disable the item
// @function ItemInfo:DisableItem
"Disable", &GameScriptItemInfo::DisableItem,
/// (@{ObjID}) object ID
// @mem objectID
"objectID", sol::property(&GameScriptItemInfo::GetObjectID, &GameScriptItemInfo::SetObjectID),
/*** (int) current animation state
The state number of the animation the object is currently doing.
This corresponds to "state" number shown in the animation editor of WadTool.
@mem currentAnimState
*/
"currentAnimState", sol::property(&GameScriptItemInfo::GetCurrentAnimState, &GameScriptItemInfo::SetCurrentAnimState),
/// (int) State of required animation
// @mem requiredAnimState
"requiredAnimState", sol::property(&GameScriptItemInfo::GetRequiredAnimState, &GameScriptItemInfo::SetRequiredAnimState),
/// (int) State of goal animation
// @mem goalAnimState
"goalAnimState", sol::property(&GameScriptItemInfo::GetGoalAnimState, &GameScriptItemInfo::SetGoalAnimState),
/*** (int) animation number
The index of the animation the object is currently doing.
This corresponds to the number shown in the item's animation list in WadTool.
@mem animNumber
*/
"animNumber", sol::property(&GameScriptItemInfo::GetAnimNumber, &GameScriptItemInfo::SetAnimNumber),
/*** (int) frame number
Current fame of the animation the object is currently doing.
The number of frames in an animation can be seen under the heading "End frame" in
the WadTool animation editor.
@mem frameNumber
*/
"frameNumber", sol::property(&GameScriptItemInfo::GetFrameNumber, &GameScriptItemInfo::SetFrameNumber),
/// (int) HP (hit points/health points) of object
//@raise an exception if the object is intelligent and an invalid
//hp value is given
// @mem HP
"HP", sol::property(&GameScriptItemInfo::GetHP, &GameScriptItemInfo::SetHP),
/// (int) OCB (object code bit) of object
// @mem OCB
"OCB", sol::property(&GameScriptItemInfo::GetOCB, &GameScriptItemInfo::SetOCB),
/// (table) item flags of object (table of 8 ints)
// @mem itemFlags
"itemFlags", sol::property(&GameScriptItemInfo::GetItemFlags, &GameScriptItemInfo::SetItemFlags),
/// (int) AIBits of object. Will be clamped to [0, 255]
// @mem AIBits
"AIBits", sol::property(&GameScriptItemInfo::GetAIBits, &GameScriptItemInfo::SetAIBits),
/// (int) status of object.
// possible values:
// 0 - not active
// 1 - active
// 2 - deactivated
// 3 - invisible
// @mem status
"status", sol::property(&GameScriptItemInfo::GetStatus, &GameScriptItemInfo::SetStatus),
/// (bool) hit status of object
// @mem hitStatus
"hitStatus", sol::property(&GameScriptItemInfo::GetHitStatus, &GameScriptItemInfo::SetHitStatus),
/// (bool) whether or not the object is active
// @mem active
"active", sol::property(&GameScriptItemInfo::GetActive, &GameScriptItemInfo::SetActive),
/// (int) room the item is in
// @mem room
"room", sol::property(&GameScriptItemInfo::GetRoom, &GameScriptItemInfo::SetRoom),
/// (@{Position}) position in level
// @mem pos
"pos", sol::property(&GameScriptItemInfo::GetPos, &GameScriptItemInfo::SetPos),
/// (@{Rotation}) rotation represented as degree angles about X, Y, and Z axes
// @mem rot
"rot", sol::property(&GameScriptItemInfo::GetRot, &GameScriptItemInfo::SetRot),
/// (string) unique string identifier.
// e.g. "door\_back\_room" or "cracked\_greek\_statue"
// @mem name
"name", sol::property(&GameScriptItemInfo::GetName, &GameScriptItemInfo::SetName)
);
}
void GameScriptItemInfo::Init()
{
bool cond = IsPointInRoom(m_item->pos, m_item->roomNumber);
std::string err{ "Position of item \"{}\" does not match its room ID." };
if (!ScriptAssertF(cond, err, m_item->luaName))
{
ScriptWarn("Resetting to the center of the room.");
PHD_3DPOS center = GetRoomCenter(m_item->roomNumber);
// reset position but not rotation
m_item->pos.xPos = center.xPos;
m_item->pos.yPos = center.yPos;
m_item->pos.zPos = center.zPos;
}
InitialiseItem(m_num);
m_initialised = true;
}
GAME_OBJECT_ID GameScriptItemInfo::GetObjectID() const
{
return m_item->objectNumber;
}
void GameScriptItemInfo::SetObjectID(GAME_OBJECT_ID item)
{
m_item->objectNumber = item;
}
std::string GameScriptItemInfo::GetName() const
{
return m_item->luaName;
}
void GameScriptItemInfo::SetName(std::string const & id)
{
ScriptAssert(!id.empty(), "Name cannot be blank", ERROR_MODE::TERMINATE);
// remove the old name if we have one
s_callbackRemoveName(m_item->luaName);
// un-register any other objects using this name.
// maybe we should throw an error if another object
// already uses the name...
s_callbackRemoveName(id);
m_item->luaName = id;
// todo add error checking
s_callbackSetName(id, m_num);
}
GameScriptPosition GameScriptItemInfo::GetPos() const
{
return GameScriptPosition( m_item->pos );
}
void GameScriptItemInfo::SetPos(GameScriptPosition const& pos)
{
pos.StoreInPHDPos(m_item->pos);
}
// This does not guarantee that the returned value will be identical
// to a value written in via SetRot - only that the angle measures
// will be mathematically equal
// (e.g. 90 degrees = -270 degrees = 450 degrees)
GameScriptRotation GameScriptItemInfo::GetRot() const
{
return GameScriptRotation( int(TO_DEGREES(m_item->pos.xRot)) % 360,
int(TO_DEGREES(m_item->pos.yRot)) % 360,
int(TO_DEGREES(m_item->pos.zRot)) % 360);
}
void GameScriptItemInfo::SetRot(GameScriptRotation const& rot)
{
m_item->pos.xRot = FROM_DEGREES(rot.x);
m_item->pos.yRot = FROM_DEGREES(rot.y);
m_item->pos.zRot = FROM_DEGREES(rot.z);
}
short GameScriptItemInfo::GetHP() const
{
return(m_item->hitPoints);
}
void GameScriptItemInfo::SetHP(short hp)
{
if(Objects[m_item->objectNumber].intelligent &&
(hp < 0 || hp > Objects[m_item->objectNumber].hitPoints))
{
ScriptAssert(false, "Invalid HP value: " + std::to_string(hp));
if (hp < 0)
{
hp = 0;
ScriptWarn("Setting HP to 0.");
}
else if (hp > Objects[m_item->objectNumber].hitPoints)
{
hp = Objects[m_item->objectNumber].hitPoints;
ScriptWarn("Setting HP to default value (" + std::to_string(hp) + ")");
}
}
m_item->hitPoints = hp;
}
short GameScriptItemInfo::GetOCB() const
{
return m_item->triggerFlags;
}
void GameScriptItemInfo::SetOCB(short ocb)
{
m_item->triggerFlags = ocb;
}
byte GameScriptItemInfo::GetAIBits() const
{
return m_item->aiBits;
}
void GameScriptItemInfo::SetAIBits(byte bits)
{
m_item->aiBits = bits;
}
sol::as_table_t<std::array<short, 8>> GameScriptItemInfo::GetItemFlags() const
{
std::array<short, 8> ret{};
memcpy(ret.data(), m_item->itemFlags, sizeof(m_item->itemFlags));
return ret;
}
void GameScriptItemInfo::SetItemFlags(sol::as_table_t<std::array<short, 8>> const& arr)
{
memcpy(m_item->itemFlags, arr.value().data(), sizeof(m_item->itemFlags));
}
int GameScriptItemInfo::GetCurrentAnimState() const
{
return m_item->currentAnimState;
}
void GameScriptItemInfo::SetCurrentAnimState(int animState)
{
m_item->currentAnimState = animState;
}
int GameScriptItemInfo::GetRequiredAnimState() const
{
return m_item->requiredAnimState;
}
void GameScriptItemInfo::SetRequiredAnimState(short animState)
{
m_item->requiredAnimState = animState;
}
int GameScriptItemInfo::GetGoalAnimState() const
{
return m_item->goalAnimState;
}
void GameScriptItemInfo::SetGoalAnimState(int state)
{
m_item->goalAnimState = state;
}
int GameScriptItemInfo::GetAnimNumber() const
{
return m_item->animNumber - Objects[m_item->objectNumber].animIndex;
}
void GameScriptItemInfo::SetAnimNumber(int animNumber)
{
//TODO fixme: we need bounds checking with an error message once it's in the level file format
m_item->animNumber = animNumber + Objects[m_item->objectNumber].animIndex;
}
int GameScriptItemInfo::GetFrameNumber() const
{
return m_item->frameNumber - g_Level.Anims[m_item->animNumber].frameBase;
}
void GameScriptItemInfo::SetFrameNumber(int frameNumber)
{
auto const fBase = g_Level.Anims[m_item->animNumber].frameBase;
auto const fEnd = g_Level.Anims[m_item->animNumber].frameEnd;
auto frameCount = fEnd - fBase;
bool cond = (frameNumber < frameCount);
const char* err = "Invalid frame number {}; max frame count for anim {} is {}.";
if (ScriptAssertF(cond, err, frameNumber, m_item->animNumber, frameCount))
{
m_item->frameNumber = frameNumber + fBase;
}
else
{
ScriptWarn("Not setting frame number.");
}
}
short GameScriptItemInfo::GetStatus() const
{
return m_item->status;
}
void GameScriptItemInfo::SetStatus(short status)
{
m_item->status = status;
}
bool GameScriptItemInfo::GetActive() const
{
return m_item->active;
}
void GameScriptItemInfo::SetActive(bool active)
{
m_item->active = active;
}
bool GameScriptItemInfo::GetHitStatus() const
{
return m_item->hitStatus;
}
void GameScriptItemInfo::SetHitStatus(bool hitStatus)
{
m_item->hitStatus = hitStatus;
}
short GameScriptItemInfo::GetRoom() const
{
return m_item->roomNumber;
}
void GameScriptItemInfo::SetRoom(short room)
{
const size_t nRooms = g_Level.Rooms.size();
if (room < 0 || static_cast<size_t>(room) >= nRooms)
{
ScriptAssertF(false, "Invalid room number: {}. Value must be in range [0, {})", room, nRooms);
TENLog("Room number will not be set", LogLevel::Warning, LogConfig::All);
return;
}
if (!m_initialised)
m_item->roomNumber = room;
else
ItemNewRoom(m_num, room);
}
void GameScriptItemInfo::EnableItem()
{
if (!m_item->active)
{
if (Objects[m_item->objectNumber].intelligent)
{
if (m_item->status == ITEM_DEACTIVATED)
{
m_item->touchBits = 0;
m_item->status = ITEM_ACTIVE;
AddActiveItem(m_num);
EnableBaddieAI(m_num, 1);
}
else if (m_item->status == ITEM_INVISIBLE)
{
m_item->touchBits = 0;
if (EnableBaddieAI(m_num, 0))
m_item->status = ITEM_ACTIVE;
else
m_item->status = ITEM_INVISIBLE;
AddActiveItem(m_num);
}
}
else
{
m_item->touchBits = 0;
AddActiveItem(m_num);
m_item->status = ITEM_ACTIVE;
}
}
}
void GameScriptItemInfo::DisableItem()
{
if (m_item->active)
{
if (Objects[m_item->objectNumber].intelligent)
{
if (m_item->status == ITEM_ACTIVE)
{
m_item->touchBits = 0;
m_item->status = ITEM_DEACTIVATED;
RemoveActiveItem(m_num);
DisableBaddieAI(m_num);
}
}
else
{
m_item->touchBits = 0;
RemoveActiveItem(m_num);
m_item->status = ITEM_DEACTIVATED;
}
}
}

View file

@ -0,0 +1,172 @@
#include "frameworkandsol.h"
#include "GameScriptLevel.h"
#include "ScriptAssert.h"
/***
Stores level metadata.
These are things things which aren't present in the compiled level file itself.
@pregameclass Level
@pragma nostrip
*/
/*** Make a new Level object.
@function Level.new
@return a Level object
*/
void GameScriptLevel::Register(sol::state* state)
{
state->new_usertype<GameScriptLevel>("Level",
sol::constructors<GameScriptLevel()>(),
/// (string) string key for the level's (localised) name.
// Corresponds to an entry in strings.lua.
//@mem nameKey
"nameKey", &GameScriptLevel::NameStringKey,
/// (string) Level-specific Lua script file.
// Path of the Lua file holding the level's logic script, relative to the location of the tombengine executable
//@mem scriptFile
"scriptFile", &GameScriptLevel::ScriptFileName,
/// (string) Compiled file path.
// This path is relative to the location of the TombEngine executable.
//@mem levelFile
"levelFile", &GameScriptLevel::FileName,
/// (string) Load screen image.
// Path of the level's load screen file (.png or .jpg), relative to the location of the tombengine executable
//@mem loadScreenFile
"loadScreenFile", &GameScriptLevel::LoadScreenFileName,
/// (string) initial ambient sound track to play.
// This is the filename of the track __without__ the .wav extension.
//@mem ambientTrack
"ambientTrack", &GameScriptLevel::AmbientTrack,
/// (@{SkyLayer}) Primary sky layer
//@mem layer1
"layer1", &GameScriptLevel::Layer1,
/// (@{SkyLayer}) Secondary sky layer
// __(not yet implemented)__
//@mem layer2
"layer2", &GameScriptLevel::Layer2,
/// (@{Fog}) omni fog RGB color and distance.
// As seen in TR4's Desert Railroad.
// If not provided, distance fog will be black.
//
// __(not yet implemented)__
//@mem fog
"fog", &GameScriptLevel::Fog,
/// (bool) Draw sky layer? (default: false)
//@mem horizon
"horizon", &GameScriptLevel::Horizon,
/// (bool) Enable smooth transition from horizon graphic to sky layer.
// If set to false, there will be a black band between the two.
//
// __(not yet implemented)__
//@mem colAddHorizon
"colAddHorizon", &GameScriptLevel::ColAddHorizon,
/// (bool) Enable flickering lightning in the sky.
// Equivalent to classic TRLE's LIGHTNING setting. As in the TRC Ireland levels.
//
//@mem storm
"storm", &GameScriptLevel::Storm,
/// (WeatherType) Choose weather effect.
// Must be one of the values `WeatherType.None`, `WeatherType.Rain`, or `WeatherType.Snow`.
//
//@mem weather
"weather", &GameScriptLevel::Weather,
/// (float) Choose weather strength.
// Must be value between `0.1` and `1.0`.
//
//@mem weatherStrength
"weatherStrength", sol::property(&GameScriptLevel::SetWeatherStrength),
/*** (LaraType) Must be one of the LaraType values.
These are:
Normal
Young
Bunhead
Catsuit
Divesuit
Invisible
e.g. `myLevel.laraType = LaraType.Divesuit`
__(not yet fully implemented)__
@mem laraType*/
"laraType", &GameScriptLevel::LaraType,
/// (bool) Enable occasional screen shake effect.
// As seen in TRC's Sinking Submarine.
//@mem rumble
"rumble", &GameScriptLevel::Rumble,
/// (@{Mirror}) Location and size of the level's mirror, if present.
//
// __(not yet implemented)__
//@mem mirror
"mirror", &GameScriptLevel::Mirror,
/*** (byte) The maximum draw distance for level.
Given in sectors (blocks).
Must be in the range [1, 127], and equal to or less than the value passed to SetGameFarView.
This is equivalent to TRNG's LevelFarView variable.
__(not yet implemented)__
@mem farView
*/
"farView", sol::property(&GameScriptLevel::SetLevelFarView),
/*** (bool) Enable unlimited oxygen supply when in water.
__(not yet implemented)__
@mem unlimitedAir
*/
"unlimitedAir", &GameScriptLevel::UnlimitedAir,
/// (table of @{InventoryObject}s) table of inventory object overrides
//@mem objects
"objects", &GameScriptLevel::InventoryObjects
);
}
void GameScriptLevel::SetWeatherStrength(float val)
{
bool cond = val <= 1.0f && val >= 0.0f;
std::string msg{ "weatherStrength value must be in the range [0.1, 1.0]." };
if (!ScriptAssert(cond, msg))
{
ScriptWarn("Setting weatherStrength view to 1.");
WeatherStrength = 1.0f;
}
else
{
WeatherStrength = val;
}
}
void GameScriptLevel::SetLevelFarView(byte val)
{
bool cond = val <= 127 && val >= 1;
std::string msg{ "levelFarView value must be in the range [1, 127]." };
if (!ScriptAssert(cond, msg))
{
ScriptWarn("Setting levelFarView view to 32.");
LevelFarView = 32;
}
else
{
LevelFarView = val;
}
}

View file

@ -0,0 +1,135 @@
#pragma once
#include "frameworkandsol.h"
#include "ScriptAssert.h"
#include "GameScriptMeshInfo.h"
#include "GameScriptPosition.h"
#include "GameScriptColor.h"
#include "ScriptUtil.h"
/***
Mesh info
@entityclass MeshInfo
@pragma nostrip
*/
constexpr auto LUA_CLASS_NAME{ "MeshInfo" };
static auto index_error = index_error_maker(GameScriptMeshInfo, LUA_CLASS_NAME);
static auto newindex_error = newindex_error_maker(GameScriptMeshInfo, LUA_CLASS_NAME);
GameScriptMeshInfo::GameScriptMeshInfo(MESH_INFO & ref, bool temp) : m_mesh{ref}, m_temporary{ temp }
{};
GameScriptMeshInfo::~GameScriptMeshInfo() {
if (m_temporary)
{
s_callbackRemoveName(m_mesh.luaName);
}
}
void GameScriptMeshInfo::Register(sol::state* state)
{
state->new_usertype<GameScriptMeshInfo>(LUA_CLASS_NAME,
sol::meta_function::index, index_error,
sol::meta_function::new_index, newindex_error,
/// (@{Position}) position in level
// @mem pos
"pos", sol::property(&GameScriptMeshInfo::GetPos, &GameScriptMeshInfo::SetPos),
/// (int) y-axis rotation
// @mem yRot
"yRot", sol::property(&GameScriptMeshInfo::GetRot, &GameScriptMeshInfo::SetRot),
/// (string) unique string identifier.
// e.g. "my\_vase" or "oldrubble"
// @mem name
"name", sol::property(&GameScriptMeshInfo::GetName, &GameScriptMeshInfo::SetName),
/// (int) static number
// @mem staticNumber
"staticNumber", sol::property(&GameScriptMeshInfo::GetStaticNumber, &GameScriptMeshInfo::SetStaticNumber),
/// (@{Color}) color of mesh
// @mem color
"color", sol::property(&GameScriptMeshInfo::GetColor, &GameScriptMeshInfo::SetColor),
/// (int) hp
// @mem HP
"HP", sol::property(&GameScriptMeshInfo::GetHP, &GameScriptMeshInfo::SetHP)
);
}
GameScriptPosition GameScriptMeshInfo::GetPos() const
{
return GameScriptPosition{ m_mesh.pos.xPos, m_mesh.pos.yPos, m_mesh.pos.zPos };
}
void GameScriptMeshInfo::SetPos(GameScriptPosition const& pos)
{
m_mesh.pos.xPos = pos.x;
m_mesh.pos.yPos = pos.y;
m_mesh.pos.zPos = pos.z;
}
int GameScriptMeshInfo::GetRot() const
{
return m_mesh.pos.yRot;
}
void GameScriptMeshInfo::SetRot(int yRot)
{
m_mesh.pos.yRot = yRot;
}
std::string GameScriptMeshInfo::GetName() const
{
return m_mesh.luaName;
}
void GameScriptMeshInfo::SetName(std::string const & id)
{
ScriptAssert(!id.empty(), "Name cannot be blank", ERROR_MODE::TERMINATE);
// remove the old name if we have one
s_callbackRemoveName(m_mesh.luaName);
// un-register any other objects using this name.
// maybe we should throw an error if another object
// already uses the name...
s_callbackRemoveName(id);
m_mesh.luaName = id;
// todo add error checking
s_callbackSetName(id, m_mesh);
}
int GameScriptMeshInfo::GetStaticNumber() const
{
return m_mesh.staticNumber;
}
void GameScriptMeshInfo::SetStaticNumber(int staticNumber)
{
m_mesh.staticNumber = staticNumber;
}
GameScriptColor GameScriptMeshInfo::GetColor() const
{
return GameScriptColor{ m_mesh.color };
}
void GameScriptMeshInfo::SetColor(GameScriptColor const & col)
{
m_mesh.color = col;
}
int GameScriptMeshInfo::GetHP() const
{
return m_mesh.hitPoints;
}
void GameScriptMeshInfo::SetHP(int hp)
{
m_mesh.hitPoints = hp;
}

View file

@ -0,0 +1,33 @@
#include "frameworkandsol.h"
#include "GameScriptMirror.h"
/***
A mirror effect.
As seen in TR4's Coastal Ruins and Sacred Lake levels.
__Not currently implemented.__
@pregameclass Mirror
@pragma nostrip
*/
void GameScriptMirror::Register(sol::state* lua)
{
lua->new_usertype<GameScriptMirror>("Mirror",
sol::constructors<GameScriptMirror(short, int, int, int, int)>(),
"room", &GameScriptMirror::Room,
"startX", &GameScriptMirror::StartX,
"endX", &GameScriptMirror::EndX,
"startZ", &GameScriptMirror::StartZ,
"endZ", &GameScriptMirror::EndZ
);
}
GameScriptMirror::GameScriptMirror(short room, int startX, int endX, int startZ, int endZ)
{
Room = room;
StartX = startX;
EndX = endX;
StartZ = startZ;
EndZ = endZ;
}

View file

@ -0,0 +1,69 @@
#include "frameworkandsol.h"
#include "GameScriptPosition.h"
#include "Specific/phd_global.h"
/***
Represents a position in the game world.
@miscclass Position
@pragma nostrip
*/
void GameScriptPosition::Register(sol::state* state)
{
state->new_usertype<GameScriptPosition>("Position",
sol::constructors<GameScriptPosition(int, int, int)>(),
sol::meta_function::to_string, &GameScriptPosition::ToString,
/// (int) x coordinate
//@mem x
"x", &GameScriptPosition::x,
/// (int) y coordinate
//@mem y
"y", &GameScriptPosition::y,
/// (int) z coordinate
//@mem z
"z", &GameScriptPosition::z
);
}
/***
@int X x coordinate
@int Y y coordinate
@int Z z coordinate
@return A Position object.
@function Position.new
*/
GameScriptPosition::GameScriptPosition(int aX, int aY, int aZ)
{
x = aX;
y = aY;
z = aZ;
}
GameScriptPosition::GameScriptPosition(PHD_3DPOS const& pos)
{
x = pos.xPos;
y = pos.yPos;
z = pos.zPos;
}
void GameScriptPosition::StoreInPHDPos(PHD_3DPOS& pos) const
{
pos.xPos = x;
pos.yPos = y;
pos.zPos = z;
}
/***
@tparam Position position this position
@treturn string A string showing the x, y, and z values of the position
@function __tostring
*/
std::string GameScriptPosition::ToString() const
{
return "{" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + "}";
}

View file

@ -0,0 +1,70 @@
#include "frameworkandsol.h"
#include "GameScriptRotation.h"
#include "Specific/phd_global.h"
/*** Represents a rotation.
Rotations are specifed as a combination of individual
angles, in degrees, about each axis.
All values will be clamped to [-32768, 32767].
@miscclass Rotation
@pragma nostrip
*/
void GameScriptRotation::Register(sol::state* state)
{
state->new_usertype<GameScriptRotation>("Rotation",
sol::constructors<GameScriptRotation(int, int, int)>(),
sol::meta_function::to_string, &GameScriptRotation::ToString,
/// (int) rotation about x axis
//@mem x
"x", &GameScriptRotation::x,
/// (int) rotation about x axis
//@mem y
"y", &GameScriptRotation::y,
/// (int) rotation about x axis
//@mem z
"z", &GameScriptRotation::z
);
}
/***
@int X rotation about x axis
@int Y rotation about y axis
@int Z rotation about z axis
@return A Rotation object.
@function Rotation.new
*/
GameScriptRotation::GameScriptRotation(int aX, int aY, int aZ)
{
x = aX;
y = aY;
z = aZ;
}
void GameScriptRotation::StoreInPHDPos(PHD_3DPOS& pos) const
{
pos.xRot = x;
pos.yRot = y;
pos.zRot = z;
}
GameScriptRotation::GameScriptRotation(PHD_3DPOS const & pos)
{
x = pos.xRot;
y = pos.yRot;
z = pos.zRot;
}
/***
@tparam Rotation rotation this rotation
@treturn string A string showing the x, y, and z values of the rotation
@function __tostring
*/
std::string GameScriptRotation::ToString() const
{
return "{" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + "}";
}

View file

@ -0,0 +1,41 @@
#include "frameworkandsol.h"
#include "GameScriptSettings.h"
/***
Settings that will be run on game startup.
@pregameclass Settings
@pragma nostrip
*/
void GameScriptSettings::Register(sol::state* lua)
{
lua->new_usertype<GameScriptSettings>("Settings",
"screenWidth", &GameScriptSettings::ScreenWidth,
"screenHeight", &GameScriptSettings::ScreenHeight,
"enableDynamicShadows", &GameScriptSettings::EnableDynamicShadows,
"windowed", &GameScriptSettings::Windowed,
"enableWaterCaustics", &GameScriptSettings::EnableWaterCaustics,
"drawingDistance", &GameScriptSettings::DrawingDistance,
"showRendererSteps", &GameScriptSettings::ShowRendererSteps,
"showDebugInfo", &GameScriptSettings::ShowDebugInfo,
/*** How should the application respond to script errors?
Must be one of the following:
`ErrorMode.TERMINATE` - print to the log file and terminate the application when any script error is hit.
This is the one you will want to go for if you want to know IMMEDIATELY if something has gone wrong.
`ErrorMode.WARN` - print to the log file and continue running the application when a recoverable script error is hit.
Choose this one if terminating the application is too much for you. Note that unrecoverable errors will still terminate
the application.
`ErrorMode.SILENT` - do nothing when a recoverable script error is hit.
Think __very__ carefully before using this setting. These error modes are here to help you to keep your scripts
working properly, but if you opt to ignore errors, you won't be alerted if you've misused a function or passed
an invalid argument.
As with `ErrorMode.WARN`, unrecoverable errors will still terminate the application.
@mem errorMode
*/
"errorMode", &GameScriptSettings::ErrorMode
);
}

View file

@ -0,0 +1,109 @@
#pragma once
#include "frameworkandsol.h"
#include "ScriptAssert.h"
#include "GameScriptSinkInfo.h"
#include "GameScriptPosition.h"
#include "ScriptUtil.h"
/***
Sink info
@entityclass SinkInfo
@pragma nostrip
*/
constexpr auto LUA_CLASS_NAME{ "SinkInfo" };
static auto index_error = index_error_maker(GameScriptSinkInfo, LUA_CLASS_NAME);
static auto newindex_error = newindex_error_maker(GameScriptSinkInfo, LUA_CLASS_NAME);
GameScriptSinkInfo::GameScriptSinkInfo(SINK_INFO & ref, bool temp) : m_sink{ref}, m_temporary{ temp }
{};
GameScriptSinkInfo::~GameScriptSinkInfo() {
if (m_temporary)
{
s_callbackRemoveName(m_sink.luaName);
}
}
void GameScriptSinkInfo::Register(sol::state* state)
{
state->new_usertype<GameScriptSinkInfo>(LUA_CLASS_NAME,
sol::meta_function::index, index_error,
sol::meta_function::new_index, newindex_error,
/// (@{Position}) position in level
// @mem pos
"pos", sol::property(&GameScriptSinkInfo::GetPos, &GameScriptSinkInfo::SetPos),
/// (string) unique string identifier.
// e.g. "strong\_river\_current" or "propeller\_death\_sink"
// @mem name
"name", sol::property(&GameScriptSinkInfo::GetName, &GameScriptSinkInfo::SetName),
/// (int) strength.
// Strength of the sink, with higher numbers providing stronger currents. Will be clamped to [1, 32].
// @mem strength
"strength", sol::property(&GameScriptSinkInfo::GetStrength, &GameScriptSinkInfo::SetStrength),
/// (int) box index.
// I don't know what this does and it's not actually in the engine yet
// @mem boxIndex
"boxIndex", sol::property(&GameScriptSinkInfo::GetBoxIndex, &GameScriptSinkInfo::SetBoxIndex)
);
}
GameScriptPosition GameScriptSinkInfo::GetPos() const
{
return GameScriptPosition{ m_sink.x, m_sink.y, m_sink.z };
}
void GameScriptSinkInfo::SetPos(GameScriptPosition const& pos)
{
m_sink.x = pos.x;
m_sink.y = pos.y;
m_sink.z = pos.z;
}
std::string GameScriptSinkInfo::GetName() const
{
return m_sink.luaName;
}
void GameScriptSinkInfo::SetName(std::string const & id)
{
ScriptAssert(!id.empty(), "Name cannot be blank", ERROR_MODE::TERMINATE);
// remove the old name if we have one
s_callbackRemoveName(m_sink.luaName);
// un-register any other sinks using this name.
// maybe we should throw an error if another sink
// already uses the name...
s_callbackRemoveName(id);
m_sink.luaName = id;
// todo add error checking
s_callbackSetName(id, m_sink);
}
int GameScriptSinkInfo::GetStrength() const
{
return m_sink.strength;
}
void GameScriptSinkInfo::SetStrength(int str)
{
m_sink.strength = std::clamp(str, 1, 32);
}
int GameScriptSinkInfo::GetBoxIndex() const
{
return m_sink.boxIndex;
}
void GameScriptSinkInfo::SetBoxIndex(int b)
{
m_sink.boxIndex = b;
}

View file

@ -0,0 +1,53 @@
#include "frameworkandsol.h"
#include "GameScriptSkyLayer.h"
/*** Describes a layer of moving clouds.
As seen in TR4's City of the Dead.
@pregameclass SkyLayer
@pragma nostrip
*/
void GameScriptSkyLayer::Register(sol::state* lua)
{
lua->new_usertype<GameScriptSkyLayer>("SkyLayer",
sol::constructors<GameScriptSkyLayer(GameScriptColor const &, short)>(),
/// (@{Color}) RGB sky color
//@mem color
"color", sol::property(&GameScriptSkyLayer::SetColor),
/*** (int) cloud speed.
Values can be between [-32768, 32767], with positive numbers resulting in a sky that scrolls from
west to east, and negative numbers resulting in one that travels east to west.
Please note that speeds outside of the range of about [-1000, 1000] will cause the
sky to scroll so fast that it will no longer appear as a coherent stream of clouds.
Less is more. City of The Dead, for example, uses a speed value of 16.
@mem speed*/
"speed", &GameScriptSkyLayer::CloudSpeed
);
}
/***
@tparam Color color RGB color
@tparam int speed cloud speed
@return A SkyLayer object.
@function SkyLayer.new
*/
GameScriptSkyLayer::GameScriptSkyLayer(GameScriptColor const& col, short speed)
{
SetColor(col);
CloudSpeed = speed;
Enabled = true;
}
void GameScriptSkyLayer::SetColor(GameScriptColor const & col)
{
R = col.GetR();
G = col.GetG();
B = col.GetB();
}

View file

@ -0,0 +1,104 @@
#include "frameworkandsol.h"
#include "ScriptAssert.h"
#include "GameScriptSoundSourceInfo.h"
#include "GameScriptPosition.h"
#include "ScriptUtil.h"
/***
Sound source info
@entityclass SoundSourceInfo
@pragma nostrip
*/
static constexpr auto LUA_CLASS_NAME{ "SoundSourceInfo" };
static auto index_error = index_error_maker(GameScriptSoundSourceInfo, LUA_CLASS_NAME);
static auto newindex_error = newindex_error_maker(GameScriptSoundSourceInfo, LUA_CLASS_NAME);
GameScriptSoundSourceInfo::GameScriptSoundSourceInfo(SOUND_SOURCE_INFO & ref, bool temp) : m_soundSource{ref}, m_temporary{ temp }
{};
GameScriptSoundSourceInfo::~GameScriptSoundSourceInfo() {
if (m_temporary)
{
s_callbackRemoveName(m_soundSource.luaName);
}
}
void GameScriptSoundSourceInfo::Register(sol::state* state)
{
state->new_usertype<GameScriptSoundSourceInfo>(LUA_CLASS_NAME,
sol::meta_function::index, index_error,
sol::meta_function::new_index, newindex_error,
/// (@{Position}) position in level
// @mem pos
"pos", sol::property(&GameScriptSoundSourceInfo::GetPos, &GameScriptSoundSourceInfo::SetPos),
/// (string) unique string identifier.
// e.g. "machine\_sound\_1" or "discordant\_humming"
// @mem name
"name", sol::property(&GameScriptSoundSourceInfo::GetName, &GameScriptSoundSourceInfo::SetName),
/// (int) sound ID
// @mem soundID
"soundID", sol::property(&GameScriptSoundSourceInfo::GetSoundID, &GameScriptSoundSourceInfo::SetSoundID),
/// (int) flags
// @mem flags
"flags", sol::property(&GameScriptSoundSourceInfo::GetFlags, &GameScriptSoundSourceInfo::SetFlags)
);
}
GameScriptPosition GameScriptSoundSourceInfo::GetPos() const
{
return GameScriptPosition{ m_soundSource.x, m_soundSource.y, m_soundSource.z };
}
void GameScriptSoundSourceInfo::SetPos(GameScriptPosition const& pos)
{
m_soundSource.x = pos.x;
m_soundSource.y = pos.y;
m_soundSource.z = pos.z;
}
std::string GameScriptSoundSourceInfo::GetName() const
{
return m_soundSource.luaName;
}
void GameScriptSoundSourceInfo::SetName(std::string const & id)
{
ScriptAssert(!id.empty(), "Name cannot be blank", ERROR_MODE::TERMINATE);
// remove the old name if we have one
s_callbackRemoveName(m_soundSource.luaName);
// un-register any other objects using this name.
// maybe we should throw an error if another object
// already uses the name...
s_callbackRemoveName(id);
m_soundSource.luaName = id;
// todo add error checking
s_callbackSetName(id, m_soundSource);
}
int GameScriptSoundSourceInfo::GetSoundID() const
{
return m_soundSource.soundId;
}
void GameScriptSoundSourceInfo::SetSoundID(int soundID)
{
m_soundSource.soundId = soundID;
}
int GameScriptSoundSourceInfo::GetFlags() const
{
return m_soundSource.flags;
}
void GameScriptSoundSourceInfo::SetFlags(int flags)
{
m_soundSource.flags = flags;
}

View file

@ -0,0 +1,25 @@
#pragma once
#include "framework.h"
#include "LuaHandler.h"
LuaHandler::LuaHandler(sol::state* lua) {
m_lua = lua;
}
void LuaHandler::ExecuteScript(std::string const& luaFilename) {
auto result = m_lua->safe_script_file(luaFilename, sol::script_pass_on_error);
if (!result.valid())
{
sol::error error = result;
throw TENScriptException{ error.what() };
}
}
void LuaHandler::ExecuteString(std::string const & command) {
auto result = m_lua->safe_script(command, sol::environment(m_lua->lua_state(), sol::create, m_lua->globals()), sol::script_pass_on_error);
if (!result.valid())
{
sol::error error = result;
throw TENScriptException{ error.what() };
}
}

View file

@ -0,0 +1,47 @@
#include "framework.h"
#include "ScriptAssert.h"
static ERROR_MODE ScriptErrorMode = ERROR_MODE::WARN;
void ScriptWarn(std::string const& msg)
{
switch (ScriptErrorMode)
{
case ERROR_MODE::TERMINATE:
case ERROR_MODE::WARN:
TENLog(msg, LogLevel::Warning, LogConfig::All);
break;
}
}
bool ScriptAssert(bool cond, std::string const& msg, std::optional<ERROR_MODE> forceMode)
{
if (!cond)
{
ERROR_MODE mode = forceMode ? *forceMode : ScriptErrorMode;
switch (mode)
{
case ERROR_MODE::WARN:
TENLog(msg, LogLevel::Error, LogConfig::All);
break;
case ERROR_MODE::TERMINATE:
TENLog(msg, LogLevel::Error, LogConfig::All);
throw TENScriptException(msg);
break;
}
}
return cond;
}
void SetScriptErrorMode(ERROR_MODE mode)
{
ScriptErrorMode = mode;
}
ERROR_MODE GetScriptErrorMode()
{
return ScriptErrorMode;
}