diff --git a/TR5Main/Game/Lara/lara.cpp b/TR5Main/Game/Lara/lara.cpp
index 12a0c2c99..5636fe9c3 100644
--- a/TR5Main/Game/Lara/lara.cpp
+++ b/TR5Main/Game/Lara/lara.cpp
@@ -920,6 +920,10 @@ void LaraAboveWater(ITEM_INFO* item, COLL_INFO* coll) //hmmmm
// Check for collision with items
LaraBaddieCollision(item, coll);
+ // FIXME: refresh floor globals because sometimes they are messed by calling GetFloorHeight
+ // all over the place. Needed for monkeyswing. Remove when block flags decoupled from floordata. -- Lwmte 19.08.21
+ RefreshFloorGlobals(item);
+
// Handle Lara collision
if (Lara.Vehicle == NO_ITEM)
lara_collision_routines[item->currentAnimState](item, coll);
diff --git a/TR5Main/Game/collide.cpp b/TR5Main/Game/collide.cpp
index 58f2ebd1a..f83216cd1 100644
--- a/TR5Main/Game/collide.cpp
+++ b/TR5Main/Game/collide.cpp
@@ -1258,6 +1258,13 @@ void DoFloorThings(FLOOR_INFO* floor, int x, int y, int z)
}
}
+void RefreshFloorGlobals(ITEM_INFO* item)
+{
+ auto room = item->roomNumber;
+ auto floor = GetFloor(item->pos.xPos, item->pos.yPos, item->pos.zPos, &room);
+ DoFloorThings(floor, item->pos.xPos, item->pos.yPos, item->pos.zPos);
+}
+
void GetCollisionInfo(COLL_INFO* coll, int xPos, int yPos, int zPos, int roomNumber, int objectHeight)
{
int resetRoom;
diff --git a/TR5Main/Game/collide.h b/TR5Main/Game/collide.h
index 390367a3e..1b35220e2 100644
--- a/TR5Main/Game/collide.h
+++ b/TR5Main/Game/collide.h
@@ -127,6 +127,7 @@ int Move3DPosTo3DPos(PHD_3DPOS* src, PHD_3DPOS* dest, int velocity, short angAdd
int MoveLaraPosition(PHD_VECTOR* pos, ITEM_INFO* item, ITEM_INFO* l);
int TestBoundsCollide(ITEM_INFO* item, ITEM_INFO* l, int radius);
void CreatureCollision(short itemNum, ITEM_INFO* l, COLL_INFO* coll);
+void RefreshFloorGlobals(ITEM_INFO* item);
void GetCollisionInfo(COLL_INFO* coll, int xPos, int yPos, int zPos, int roomNumber, int objectHeight);
void GetObjectCollisionInfo(COLL_INFO* coll, int xPos, int yPos, int zPos, int roomNumber, int objectHeight);
void LaraBaddieCollision(ITEM_INFO* item, COLL_INFO* coll);
diff --git a/TR5Main/Game/control.cpp b/TR5Main/Game/control.cpp
index 6181f8e99..83b796fef 100644
--- a/TR5Main/Game/control.cpp
+++ b/TR5Main/Game/control.cpp
@@ -133,8 +133,6 @@ HEIGHT_TYPES HeightType;
int HeavyTriggered;
short SkyPos1;
short SkyPos2;
-signed char SkyVelocity1;
-signed char SkyVelocity2;
CVECTOR SkyColor1;
CVECTOR SkyColor2;
int CutSeqNum;
diff --git a/TR5Main/Game/control.h b/TR5Main/Game/control.h
index 2b6bde55d..fe6bfc56a 100644
--- a/TR5Main/Game/control.h
+++ b/TR5Main/Game/control.h
@@ -105,8 +105,6 @@ extern HEIGHT_TYPES HeightType;
extern int HeavyTriggered;
extern short SkyPos1;
extern short SkyPos2;
-extern signed char SkyVelocity1;
-extern signed char SkyVelocity2;
extern CVECTOR SkyColor1;
extern CVECTOR SkyColor2;
extern int CutSeqNum;
diff --git a/TR5Main/Scripting/GameFlowScript.cpp b/TR5Main/Scripting/GameFlowScript.cpp
index b26a7cd9f..1ae6a4e34 100644
--- a/TR5Main/Scripting/GameFlowScript.cpp
+++ b/TR5Main/Scripting/GameFlowScript.cpp
@@ -241,12 +241,10 @@ bool GameFlow::DoGameflow()
SkyColor1.r = level->Layer1.R;
SkyColor1.g = level->Layer1.G;
SkyColor1.b = level->Layer1.B;
- SkyVelocity1 = level->Layer1.CloudSpeed;
SkyColor2.r = level->Layer2.R;
SkyColor2.g = level->Layer2.G;
SkyColor2.b = level->Layer2.B;
- SkyVelocity2 = level->Layer2.CloudSpeed;
}
if (level->Storm)
@@ -269,7 +267,7 @@ bool GameFlow::DoGameflow()
else
{
// Prepare inventory objects table
- for (int i = 0; i < level->InventoryObjects.size(); i++)
+ for (size_t i = 0; i < level->InventoryObjects.size(); i++)
{
GameScriptInventoryObject* obj = &level->InventoryObjects[i];
if (obj->slot >= 0 && obj->slot < INVENTORY_TABLE_SIZE)
diff --git a/TR5Main/Scripting/GameScriptAIObject.cpp b/TR5Main/Scripting/GameScriptAIObject.cpp
index 347b95978..bc8aed2a0 100644
--- a/TR5Main/Scripting/GameScriptAIObject.cpp
+++ b/TR5Main/Scripting/GameScriptAIObject.cpp
@@ -3,6 +3,7 @@
#include "GameScriptAIObject.h"
#include "ScriptAssert.h"
#include "GameScriptPosition.h"
+#include "ScriptUtil.h"
#include
/***
AI object
@@ -15,6 +16,7 @@ AI object
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 }
{};
@@ -30,6 +32,7 @@ void GameScriptAIObject::Register(sol::state* state)
{
state->new_usertype(LUA_CLASS_NAME,
sol::meta_function::index, index_error,
+ sol::meta_function::new_index, newindex_error,
/// (@{Position}) position in level
// @mem pos
diff --git a/TR5Main/Scripting/GameScriptCameraInfo.cpp b/TR5Main/Scripting/GameScriptCameraInfo.cpp
index 33c676bfa..2d1af133b 100644
--- a/TR5Main/Scripting/GameScriptCameraInfo.cpp
+++ b/TR5Main/Scripting/GameScriptCameraInfo.cpp
@@ -2,6 +2,7 @@
#include "ScriptAssert.h"
#include "GameScriptCameraInfo.h"
#include "GameScriptPosition.h"
+#include "ScriptUtil.h"
/***
Camera info
@@ -12,6 +13,7 @@ Camera info
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 }
{};
@@ -27,6 +29,7 @@ void GameScriptCameraInfo::Register(sol::state* state)
{
state->new_usertype(LUA_CLASS_NAME,
sol::meta_function::index, index_error,
+ sol::meta_function::new_index, newindex_error,
/// (@{Position}) position in level
// @mem pos
diff --git a/TR5Main/Scripting/GameScriptInventoryObject.cpp b/TR5Main/Scripting/GameScriptInventoryObject.cpp
index 943262dad..be401cabf 100644
--- a/TR5Main/Scripting/GameScriptInventoryObject.cpp
+++ b/TR5Main/Scripting/GameScriptInventoryObject.cpp
@@ -29,7 +29,7 @@ associated getters and setters.
@tparam ItemAction action is this usable, equippable, or examinable?
@return an InventoryObject
*/
-GameScriptInventoryObject::GameScriptInventoryObject(std::string const& a_name, ItemEnumPair a_slot, float a_yOffset, float a_scale, GameScriptRotation const & a_rot, rotflags a_rotationFlags, int a_meshBits, item_options a_action) :
+GameScriptInventoryObject::GameScriptInventoryObject(std::string const& a_name, ItemEnumPair a_slot, short a_yOffset, float a_scale, GameScriptRotation const & a_rot, rotflags a_rotationFlags, int a_meshBits, item_options a_action) :
name{ a_name },
slot{ a_slot.m_pair.second },
yOffset{ a_yOffset },
diff --git a/TR5Main/Scripting/GameScriptInventoryObject.h b/TR5Main/Scripting/GameScriptInventoryObject.h
index 476e9a39f..bce533eaa 100644
--- a/TR5Main/Scripting/GameScriptInventoryObject.h
+++ b/TR5Main/Scripting/GameScriptInventoryObject.h
@@ -24,7 +24,7 @@ struct GameScriptInventoryObject
{
std::string name{};
inv_objects slot{ INV_OBJECT_PISTOLS };
- float yOffset{ 0.0f };
+ short yOffset{ 0 };
float scale{ 1.0f };
GameScriptRotation rot{};
rotflags rotationFlags{ rotflags::INV_ROT_X };
@@ -32,7 +32,7 @@ struct GameScriptInventoryObject
item_options action{ item_options::OPT_USE };
GameScriptInventoryObject() = default;
- GameScriptInventoryObject(std::string const & a_name, ItemEnumPair a_slot, float a_yOffset, float a_scale, GameScriptRotation const & a_rot, rotflags a_rotationFlags, int a_meshBits, item_options a_actions);
+ GameScriptInventoryObject(std::string const & a_name, ItemEnumPair a_slot, short a_yOffset, float a_scale, GameScriptRotation const & a_rot, rotflags a_rotationFlags, int a_meshBits, item_options a_actions);
static void Register(sol::state* lua);
diff --git a/TR5Main/Scripting/GameScriptItemInfo.cpp b/TR5Main/Scripting/GameScriptItemInfo.cpp
index bea9444ac..8d94cbb98 100644
--- a/TR5Main/Scripting/GameScriptItemInfo.cpp
+++ b/TR5Main/Scripting/GameScriptItemInfo.cpp
@@ -1,6 +1,7 @@
#include "framework.h"
#include "ScriptAssert.h"
#include "GameScriptItemInfo.h"
+#include "ScriptUtil.h"
#include "items.h"
#include "objectslist.h"
#include "level.h"
@@ -23,6 +24,7 @@ pickups, and Lara herself.
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 }
{};
@@ -167,6 +169,7 @@ void GameScriptItemInfo::Register(sol::state* state)
"newItem", sol::overload(Create, CreateEmpty),
"newItemTemporary", sol::overload(Create, CreateEmpty),
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
diff --git a/TR5Main/Scripting/GameScriptLevel.cpp b/TR5Main/Scripting/GameScriptLevel.cpp
index d977c3519..f68fdffd9 100644
--- a/TR5Main/Scripting/GameScriptLevel.cpp
+++ b/TR5Main/Scripting/GameScriptLevel.cpp
@@ -128,8 +128,15 @@ This is equivalent to TRNG's LevelFarView variable.
__(not yet implemented)__
@mem farView
*/
-
"farView", sol::property(&GameScriptLevel::SetLevelFarView),
+
+/*** (bool) If true, the player will have an 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
diff --git a/TR5Main/Scripting/GameScriptLevel.h b/TR5Main/Scripting/GameScriptLevel.h
index 5e2fe3e1e..1e6c1e57d 100644
--- a/TR5Main/Scripting/GameScriptLevel.h
+++ b/TR5Main/Scripting/GameScriptLevel.h
@@ -54,9 +54,9 @@ struct GameScriptLevel
bool Rumble{ false };
LARA_TYPE LaraType{ LARA_TYPE::NORMAL };
GameScriptMirror Mirror;
- byte UVRotate{ 0 }; // unused
- int LevelFarView{ 0 }; // unused
- bool UnlimitedAir{ false }; //unused
+ byte UVRotate{ 0 };
+ int LevelFarView{ 0 };
+ bool UnlimitedAir{ false };
std::vector InventoryObjects;
void SetUVRotate(byte val);
diff --git a/TR5Main/Scripting/GameScriptMeshInfo.cpp b/TR5Main/Scripting/GameScriptMeshInfo.cpp
index 16b45127a..01a300b74 100644
--- a/TR5Main/Scripting/GameScriptMeshInfo.cpp
+++ b/TR5Main/Scripting/GameScriptMeshInfo.cpp
@@ -4,6 +4,7 @@
#include "GameScriptMeshInfo.h"
#include "GameScriptPosition.h"
#include "GameScriptColor.h"
+#include "ScriptUtil.h"
#include
/***
Mesh info
@@ -15,6 +16,7 @@ Mesh info
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 }
{};
@@ -30,6 +32,7 @@ void GameScriptMeshInfo::Register(sol::state* state)
{
state->new_usertype(LUA_CLASS_NAME,
sol::meta_function::index, index_error,
+ sol::meta_function::new_index, newindex_error,
/// (@{Position}) position in level
// @mem pos
@@ -40,7 +43,7 @@ void GameScriptMeshInfo::Register(sol::state* state)
"yRot", sol::property(&GameScriptMeshInfo::GetRot, &GameScriptMeshInfo::SetRot),
/// (string) unique string identifier.
- // e.g. "door_back_room" or "cracked_greek_statue"
+ // e.g. "my\_vase" or "oldrubble"
// @mem name
"name", sol::property(&GameScriptMeshInfo::GetName, &GameScriptMeshInfo::SetName),
diff --git a/TR5Main/Scripting/GameScriptNamedBase.h b/TR5Main/Scripting/GameScriptNamedBase.h
index 3c1ee3b63..7469928e9 100644
--- a/TR5Main/Scripting/GameScriptNamedBase.h
+++ b/TR5Main/Scripting/GameScriptNamedBase.h
@@ -3,13 +3,6 @@
#include
#include
-
-#define index_error_maker(CPP_TYPE, LUA_CLASS_NAME) [](CPP_TYPE & item, sol::object key) \
-{ \
- std::string err = "Attempted to read non-existant var \"" + key.as() + "\" from " + LUA_CLASS_NAME; \
- throw TENScriptException(err); \
-}
-
template using callbackSetName = std::function;
using callbackRemoveName = std::function;
diff --git a/TR5Main/Scripting/GameScriptSinkInfo.cpp b/TR5Main/Scripting/GameScriptSinkInfo.cpp
index 94167f366..f63305057 100644
--- a/TR5Main/Scripting/GameScriptSinkInfo.cpp
+++ b/TR5Main/Scripting/GameScriptSinkInfo.cpp
@@ -3,6 +3,7 @@
#include "ScriptAssert.h"
#include "GameScriptSinkInfo.h"
#include "GameScriptPosition.h"
+#include "ScriptUtil.h"
#include
/***
Sink info
@@ -14,6 +15,7 @@ Sink info
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 }
{};
@@ -29,13 +31,14 @@ void GameScriptSinkInfo::Register(sol::state* state)
{
state->new_usertype(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. "door\_back\_room" or "cracked\_greek\_statue"
+ // e.g. "strong\_river\_current" or "propeller\_death\_sink"
// @mem name
"name", sol::property(&GameScriptSinkInfo::GetName, &GameScriptSinkInfo::SetName),
diff --git a/TR5Main/Scripting/GameScriptSoundSourceInfo.cpp b/TR5Main/Scripting/GameScriptSoundSourceInfo.cpp
index 3c0c17389..b6bab0622 100644
--- a/TR5Main/Scripting/GameScriptSoundSourceInfo.cpp
+++ b/TR5Main/Scripting/GameScriptSoundSourceInfo.cpp
@@ -2,6 +2,7 @@
#include "ScriptAssert.h"
#include "GameScriptSoundSourceInfo.h"
#include "GameScriptPosition.h"
+#include "ScriptUtil.h"
/***
Sound source info
@@ -12,6 +13,7 @@ Sound source info
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 }
{};
@@ -27,13 +29,14 @@ void GameScriptSoundSourceInfo::Register(sol::state* state)
{
state->new_usertype(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"
+ // e.g. "machine\_sound\_1" or "discordant\_humming"
// @mem name
"name", sol::property(&GameScriptSoundSourceInfo::GetName, &GameScriptSoundSourceInfo::SetName),
diff --git a/TR5Main/Scripting/LanguageScript.cpp b/TR5Main/Scripting/LanguageScript.cpp
deleted file mode 100644
index f8ecc80e0..000000000
--- a/TR5Main/Scripting/LanguageScript.cpp
+++ /dev/null
@@ -1,12 +0,0 @@
-#include "framework.h"
-#include "LanguageScript.h"
-using std::string;
-LanguageScript::LanguageScript(char* name)
-{
- Name = string(name);
-}
-
-LanguageScript::~LanguageScript()
-{
-
-}
diff --git a/TR5Main/Scripting/LanguageScript.h b/TR5Main/Scripting/LanguageScript.h
index 79d25d1f2..c79c4f3be 100644
--- a/TR5Main/Scripting/LanguageScript.h
+++ b/TR5Main/Scripting/LanguageScript.h
@@ -122,13 +122,3 @@
#define STRING_CLOCKWORK_BEETLE "clockwork_beetle"
#define STRING_CLOCKWORK_BEETLE_COMBO1 "clockwork_beetle_combo1"
#define STRING_CLOCKWORK_BEETLE_COMBO2 "clockwork_beetle_combo2"
-
-class LanguageScript
-{
-public:
- std::string Name;
- std::unordered_map Strings;
- LanguageScript(char* name);
- ~LanguageScript();
-};
-
diff --git a/TR5Main/Scripting/ScriptUtil.h b/TR5Main/Scripting/ScriptUtil.h
new file mode 100644
index 000000000..e9690389d
--- /dev/null
+++ b/TR5Main/Scripting/ScriptUtil.h
@@ -0,0 +1,13 @@
+#pragma once
+#define index_error_maker(CPP_TYPE, LUA_CLASS_NAME) [](CPP_TYPE & item, sol::object key) \
+{ \
+ std::string err = "Attempted to read non-existant var \"" + key.as() + "\" from " + LUA_CLASS_NAME; \
+ ScriptAssert(false, err);\
+}
+
+#define newindex_error_maker(CPP_TYPE, LUA_CLASS_NAME) [](CPP_TYPE & item, sol::object key) \
+{ \
+ std::string err = "Attempted to set non-existant var \"" + key.as() + "\" of " + LUA_CLASS_NAME; \
+ ScriptAssert(false, err);\
+}
+
diff --git a/TR5Main/TR5Main.vcxproj b/TR5Main/TR5Main.vcxproj
index 55db006c4..963ed4372 100644
--- a/TR5Main/TR5Main.vcxproj
+++ b/TR5Main/TR5Main.vcxproj
@@ -410,6 +410,7 @@ xcopy /Y "$(ProjectDir)Shaders\HUD\*.hlsl" "$(TargetDir)\Shaders\HUD\"
+
diff --git a/TR5Main/TR5Main.vcxproj.filters b/TR5Main/TR5Main.vcxproj.filters
index 3af423eec..c496b3aca 100644
--- a/TR5Main/TR5Main.vcxproj.filters
+++ b/TR5Main/TR5Main.vcxproj.filters
@@ -1050,6 +1050,9 @@
File di intestazione
+
+ File di intestazione
+
@@ -1151,9 +1154,6 @@
File di origine
-
- File di origine
-
File di origine
diff --git a/doc/classes/Level.html b/doc/classes/Level.html
index e153e7358..9c0abc189 100644
--- a/doc/classes/Level.html
+++ b/doc/classes/Level.html
@@ -147,6 +147,10 @@ level file itself.
(byte) The maximum draw distance, in sectors (blocks), of this particular level. |
+ Level.unlimitedAir |
+ (bool) If true, the player will have an unlimited oxygen supply when in water. |
+
+
Level.objects |
(table of InventoryObjects) table of inventory object overrides |
@@ -270,8 +274,9 @@ level file itself.
(Color) distance fog RGB color (as seen in TR4's Desert Railroad).
- If not provided, distance fog will be black.
- (not yet implemented)
+ If not provided, distance fog will be black.
+
+ (not yet implemented)
@@ -300,8 +305,9 @@ level file itself.
(bool) if true, the horizon graphic will transition smoothly to the sky layer.
- If set to false, there will be a black band between the two.
- (not yet implemented)
+ If set to false, there will be a black band between the two.
+
+ (not yet implemented)
@@ -316,8 +322,9 @@ level file itself.
(bool) equivalent to classic TRLE's LIGHTNING setting.
- If true, there will be a flickering lightning in the skylayer, as in the TRC Ireland levels.
- (thunder sounds not yet implemented)
+ If true, there will be a flickering lightning in the skylayer, as in the TRC Ireland levels.
+
+ (thunder sounds not yet implemented)
@@ -331,8 +338,9 @@ level file itself.
Level.weather
- (WeatherType) Must be one of the values WeatherType.NORMAL, WeatherType.RAIN, or WeatherType.SNOW.
- (not yet implemented)
+ (WeatherType) Must be one of the values WeatherType.NORMAL, WeatherType.RAIN, or WeatherType.SNOW.
+
+ (not yet implemented)
@@ -442,6 +450,22 @@ number, the faster the scroll will be.
+
+
+
+ Level.unlimitedAir
+
+
+ (bool) If true, the player will have an unlimited oxygen supply when in water.
+
+ (not yet implemented)
+
+
+
+
+
+
+
@@ -487,7 +511,7 @@ number, the faster the scroll will be.
generated by LDoc 1.4.6
-
Last updated 2021-08-17 03:39:52
+
Last updated 2021-08-20 01:47:29