Fixed various physics bugs and added Physics Material to Inspector #249
|
@ -78,7 +78,10 @@ project "SHADE_Application"
|
|||
"26451",
|
||||
"26437",
|
||||
"4275",
|
||||
"4635"
|
||||
"4633",
|
||||
"4634",
|
||||
"4635",
|
||||
"4638"
|
||||
}
|
||||
|
||||
linkoptions { "-IGNORE:4006" }
|
||||
|
@ -87,7 +90,7 @@ project "SHADE_Application"
|
|||
|
||||
filter "configurations:Debug"
|
||||
symbols "On"
|
||||
defines {"_DEBUG"}
|
||||
defines {"_DEBUG", "SHEDITOR"}
|
||||
|
||||
filter "configurations:Release"
|
||||
optimize "On"
|
||||
|
|
|
@ -79,7 +79,10 @@ project "SHADE_Engine"
|
|||
"26451",
|
||||
"26437",
|
||||
"4275",
|
||||
"4635"
|
||||
"4633",
|
||||
"4634",
|
||||
"4635",
|
||||
"4638"
|
||||
}
|
||||
|
||||
linkoptions { "-IGNORE:4006" }
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace SHADE
|
|||
{
|
||||
const bool OPENED = ImGui::CollapsingHeader(title.c_str(), ImGuiTreeNodeFlags_DefaultOpen);
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
return OPENED;
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ namespace SHADE
|
|||
|
||||
void SHEditorUI::EndTooltip()
|
||||
{
|
||||
ImGui::EndTooltip();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
|
@ -146,7 +146,7 @@ namespace SHADE
|
|||
|
||||
bool SHEditorUI::Selectable(const std::string& label)
|
||||
{
|
||||
return ImGui::Selectable(label.data());
|
||||
return ImGui::Selectable(label.data());
|
||||
}
|
||||
|
||||
bool SHEditorUI::Selectable(const std::string& label, const char* icon)
|
||||
|
@ -156,30 +156,41 @@ namespace SHADE
|
|||
|
||||
bool SHEditorUI::InputCheckbox(const std::string& label, bool& value, bool* isHovered)
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
if (!label.empty())
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
return ImGui::Checkbox("##", &value);
|
||||
}
|
||||
bool SHEditorUI::InputInt(const std::string& label, int& value, bool* isHovered)
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
if (!label.empty())
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
return ImGui::DragInt("##", &value, 0.001f,
|
||||
std::numeric_limits<int>::min(),
|
||||
std::numeric_limits<int>::max(),
|
||||
"%d",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
std::numeric_limits<int>::min(),
|
||||
std::numeric_limits<int>::max(),
|
||||
"%d",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
}
|
||||
bool SHEditorUI::InputUnsignedInt(const std::string& label, unsigned int& value, bool* isHovered)
|
||||
{
|
||||
int signedVal = static_cast<int>(value);
|
||||
ImGui::Text(label.c_str());
|
||||
if (!label.empty())
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
const bool CHANGED = InputInt("##", signedVal);
|
||||
if (CHANGED)
|
||||
|
@ -191,15 +202,19 @@ namespace SHADE
|
|||
}
|
||||
bool SHEditorUI::InputFloat(const std::string& label, float& value, bool* isHovered)
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
if (!label.empty())
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
return ImGui::DragFloat("##", &value, 0.001f,
|
||||
std::numeric_limits<float>::lowest(),
|
||||
std::numeric_limits<float>::max(),
|
||||
"%.3f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
std::numeric_limits<float>::lowest(),
|
||||
std::numeric_limits<float>::max(),
|
||||
"%.3f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
}
|
||||
bool SHEditorUI::InputDouble(const std::string& label, double& value, bool* isHovered)
|
||||
{
|
||||
|
@ -213,48 +228,56 @@ namespace SHADE
|
|||
}
|
||||
bool SHEditorUI::InputSlider(const std::string& label, int min, int max, int& value, bool* isHovered /*= nullptr*/)
|
||||
{
|
||||
if (!label.empty())
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
return ImGui::SliderInt("##", &value,
|
||||
static_cast<float>(min), static_cast<float>(max), "%d",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
}
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
return ImGui::SliderInt("##", &value,
|
||||
static_cast<float>(min), static_cast<float>(max), "%d",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
}
|
||||
|
||||
bool SHEditorUI::InputSlider(const std::string& label, unsigned int min, unsigned int max, unsigned int& value, bool* isHovered /*= nullptr*/)
|
||||
{
|
||||
int val = static_cast<int>(value);
|
||||
const bool CHANGED = InputSlider(label, min, max, val, isHovered);
|
||||
if (CHANGED)
|
||||
{
|
||||
value = static_cast<int>(val);
|
||||
}
|
||||
int val = static_cast<int>(value);
|
||||
const bool CHANGED = InputSlider(label, min, max, val, isHovered);
|
||||
if (CHANGED)
|
||||
{
|
||||
value = static_cast<int>(val);
|
||||
}
|
||||
|
||||
return CHANGED;
|
||||
return CHANGED;
|
||||
}
|
||||
|
||||
bool SHEditorUI::InputSlider(const std::string& label, float min, float max, float& value, bool* isHovered)
|
||||
{
|
||||
if (!label.empty())
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
return ImGui::SliderFloat("##", &value,
|
||||
static_cast<float>(min), static_cast<float>(max), "%.3f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
}
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
return ImGui::SliderFloat("##", &value,
|
||||
static_cast<float>(min), static_cast<float>(max), "%.3f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
}
|
||||
|
||||
bool SHEditorUI::InputSlider(const std::string& label, double min, double max, double& value, bool* isHovered /*= nullptr*/)
|
||||
{
|
||||
float val = static_cast<float>(value);
|
||||
const bool CHANGED = InputSlider(label, min, max, val, isHovered);
|
||||
if (CHANGED)
|
||||
{
|
||||
value = static_cast<double>(val);
|
||||
}
|
||||
float val = static_cast<float>(value);
|
||||
const bool CHANGED = InputSlider(label, min, max, val, isHovered);
|
||||
if (CHANGED)
|
||||
{
|
||||
value = static_cast<double>(val);
|
||||
}
|
||||
|
||||
return CHANGED;
|
||||
return CHANGED;
|
||||
}
|
||||
|
||||
bool SHEditorUI::InputVec2(const std::string& label, SHVec2& value, bool* isHovered)
|
||||
|
@ -264,7 +287,7 @@ namespace SHADE
|
|||
}
|
||||
bool SHEditorUI::InputVec3(const std::string& label, SHVec3& value, bool* isHovered)
|
||||
{
|
||||
static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y", "Z"};
|
||||
static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y", "Z" };
|
||||
return SHEditorWidgets::DragN<float, 3>(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, 0.1f, "%.3f", float{}, float{}, 0, isHovered);
|
||||
}
|
||||
|
||||
|
@ -272,9 +295,13 @@ namespace SHADE
|
|||
{
|
||||
std::array<char, TEXT_FIELD_MAX_LENGTH> buffer = { '\0' };
|
||||
strcpy_s(buffer.data(), TEXT_FIELD_MAX_LENGTH, value.c_str());
|
||||
ImGui::Text(label.c_str());
|
||||
if (!label.empty())
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
const bool CHANGED = ImGui::InputText("##", &buffer[0], TEXT_FIELD_MAX_LENGTH);
|
||||
if (CHANGED)
|
||||
|
@ -286,7 +313,11 @@ namespace SHADE
|
|||
|
||||
bool SHEditorUI::InputGameObjectField(const std::string& label, uint32_t& value, bool* isHovered, bool alwaysNull)
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
if (!label.empty())
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
|
@ -326,9 +357,13 @@ namespace SHADE
|
|||
const std::string& INITIAL_NAME = v >= static_cast<int>(enumNames.size()) ? "Unknown" : enumNames[v];
|
||||
bool b = false;
|
||||
|
||||
ImGui::Text(label.c_str());
|
||||
if (!label.empty())
|
||||
{
|
||||
ImGui::Text(label.c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (isHovered)
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
*isHovered = ImGui::IsItemHovered();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::BeginCombo("##", INITIAL_NAME.c_str(), ImGuiComboFlags_None))
|
||||
{
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace SHADE
|
|||
|
||||
void SHSuperBatch::Remove(const SHRenderable* renderable) noexcept
|
||||
{
|
||||
Handle<SHMaterial> baseMat = renderable->GetMaterial()->GetBaseMaterial();
|
||||
Handle<SHMaterial> baseMat = (renderable->HasMaterialChanged() ? renderable->GetPrevMaterial() : renderable->GetMaterial())->GetBaseMaterial();
|
||||
const Handle<SHVkPipeline> PIPELINE = baseMat->HasPipelineChanged() ? baseMat->GetPrevPipeline() : baseMat->GetPipeline();
|
||||
|
||||
// Check if we have a Batch with the same pipeline yet
|
||||
|
|
|
@ -117,6 +117,12 @@ namespace SHADE
|
|||
|
||||
// Header
|
||||
SHEditorUI::PushID(index);
|
||||
bool enabled = script->Enabled;
|
||||
if (SHEditorUI::InputCheckbox("", enabled))
|
||||
{
|
||||
script->Enabled = enabled;
|
||||
}
|
||||
SHEditorUI::SameLine();
|
||||
if (SHEditorUI::CollapsingHeader(LABEL))
|
||||
{
|
||||
SHEditorUI::PushID(LABEL);
|
||||
|
|
|
@ -54,6 +54,14 @@ namespace SHADE
|
|||
return GameObject(ENTITY_ID);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
/* Static Properties */
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
GameObject GameObject::Null::get()
|
||||
{
|
||||
return GameObject();
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
/* Properties */
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
|
|
|
@ -62,6 +62,17 @@ namespace SHADE
|
|||
/// <returns>GameObject that has the specified name. Null if not found.</returns>
|
||||
static System::Nullable<GameObject> Find(System::String^ name);
|
||||
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
/* Static Properties */
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
/// <summary>
|
||||
/// Default empty GameObject.
|
||||
/// </summary>
|
||||
static property GameObject Null
|
||||
{
|
||||
GameObject get();
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
/* Properties */
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
|
|
|
@ -22,6 +22,36 @@ of DigiPen Institute of Technology is prohibited.
|
|||
|
||||
namespace SHADE
|
||||
{
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
/* Properties */
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
GameObject Script::Owner::get()
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
GameObject Script::GameObject::get()
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
bool Script::Enabled::get()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
void Script::Enabled::set(bool value)
|
||||
{
|
||||
// Same, don't set
|
||||
if (value == enabled)
|
||||
return;
|
||||
|
||||
enabled = value;
|
||||
|
||||
// There's a change, so call the appropriate function
|
||||
if (enabled)
|
||||
OnEnable();
|
||||
else
|
||||
OnDisable();
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
/* Component Access Functions */
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
|
@ -104,11 +134,10 @@ namespace SHADE
|
|||
/*---------------------------------------------------------------------------------*/
|
||||
/* "All-time" Lifecycle Functions */
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
void Script::Initialize(GameObject newOwner)
|
||||
void Script::Initialize(SHADE::GameObject newOwner)
|
||||
{
|
||||
owner = newOwner;
|
||||
}
|
||||
|
||||
void Script::OnAttached()
|
||||
{
|
||||
SAFE_NATIVE_CALL_BEGIN
|
||||
|
@ -131,6 +160,12 @@ namespace SHADE
|
|||
awake();
|
||||
SAFE_NATIVE_CALL_END(this)
|
||||
}
|
||||
void Script::OnEnable()
|
||||
{
|
||||
SAFE_NATIVE_CALL_BEGIN
|
||||
onEnable();
|
||||
SAFE_NATIVE_CALL_END(this)
|
||||
}
|
||||
void Script::Start()
|
||||
{
|
||||
SAFE_NATIVE_CALL_BEGIN
|
||||
|
@ -162,6 +197,12 @@ namespace SHADE
|
|||
onDrawGizmos();
|
||||
SAFE_NATIVE_CALL_END(this)
|
||||
}
|
||||
void Script::OnDisable()
|
||||
{
|
||||
SAFE_NATIVE_CALL_BEGIN
|
||||
onDisable();
|
||||
SAFE_NATIVE_CALL_END(this)
|
||||
}
|
||||
void Script::OnDestroy()
|
||||
{
|
||||
SAFE_NATIVE_CALL_BEGIN
|
||||
|
@ -228,6 +269,7 @@ namespace SHADE
|
|||
/* Virtual Lifecycle Functions */
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
void Script::awake() {}
|
||||
void Script::onEnable() {}
|
||||
void Script::start() {}
|
||||
void Script::fixedUpdate() {}
|
||||
void Script::update() {}
|
||||
|
@ -236,6 +278,7 @@ namespace SHADE
|
|||
{
|
||||
OnGizmosDrawOverriden = false;
|
||||
}
|
||||
void Script::onDisable() {}
|
||||
void Script::onDestroy() {}
|
||||
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
|
|
|
@ -38,11 +38,28 @@ namespace SHADE
|
|||
/* Properties */
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
/// <summary>
|
||||
/// GameObject that this Script belongs to. This is a legacy interface, use
|
||||
/// GameObject instead.
|
||||
/// </summary>
|
||||
[System::ObsoleteAttribute("Use GameObject instead.", false)]
|
||||
property SHADE::GameObject Owner
|
||||
{
|
||||
SHADE::GameObject get();
|
||||
}
|
||||
/// <summary>
|
||||
/// GameObject that this Script belongs to.
|
||||
/// </summary>
|
||||
property GameObject Owner
|
||||
property SHADE::GameObject GameObject
|
||||
{
|
||||
GameObject get() { return owner; }
|
||||
SHADE::GameObject get();
|
||||
}
|
||||
/// <summary>
|
||||
/// Whether or not this Script should have it's update functions be executed.
|
||||
/// </summary>
|
||||
property bool Enabled
|
||||
{
|
||||
bool get();
|
||||
void set(bool value);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
|
@ -127,7 +144,7 @@ namespace SHADE
|
|||
/// </summary>
|
||||
/// <typeparam name="T">
|
||||
/// Type of script to get.
|
||||
/// This needs to be a default constructable Script.
|
||||
/// This needs to be a default constructible Script.
|
||||
/// </typeparam>
|
||||
/// <returns>Reference to the script added</returns>
|
||||
generic<typename T> where T : ref class, Script
|
||||
|
@ -206,7 +223,7 @@ namespace SHADE
|
|||
/// <summary>
|
||||
/// Used to initialize a Script with a GameObject.
|
||||
/// </summary>
|
||||
void Initialize(GameObject newOwner);
|
||||
void Initialize(SHADE::GameObject newOwner);
|
||||
/// <summary>
|
||||
/// Used to call onAttached(). This is called immediately when this script is
|
||||
/// attached to a GameObject.
|
||||
|
@ -232,6 +249,11 @@ namespace SHADE
|
|||
/// </summary>
|
||||
void Start();
|
||||
/// <summary>
|
||||
/// Used to call onEnable. This should be called right when a script is enabled
|
||||
/// directly.
|
||||
/// </summary>
|
||||
void OnEnable();
|
||||
/// <summary>
|
||||
/// Used to call fixedUpdate(). This should be called in sync with Physics
|
||||
/// update steps and thus in most cases will execute more than Update() will.
|
||||
/// This will be called immediately before a Physics update step.
|
||||
|
@ -253,6 +275,11 @@ namespace SHADE
|
|||
/// </summary>
|
||||
void OnDrawGizmos();
|
||||
/// <summary>
|
||||
/// Used to call onDisable. This should be called right when a script is disabled
|
||||
/// directly.
|
||||
/// </summary>
|
||||
void OnDisable();
|
||||
/// <summary>
|
||||
/// Used to call onDestroy(). This should be called at the end of the frame
|
||||
/// where the attached GameObject or this script is destroyed directly or
|
||||
/// indirectly due to destruction of the owner.
|
||||
|
@ -329,6 +356,10 @@ namespace SHADE
|
|||
/// </summary>
|
||||
virtual void awake();
|
||||
/// <summary>
|
||||
/// Called when this script is enabled.
|
||||
/// </summary>
|
||||
virtual void onEnable();
|
||||
/// <summary>
|
||||
/// Called on the first frame that the attached GameObject is active but always
|
||||
/// after Awake().
|
||||
/// </summary>
|
||||
|
@ -353,6 +384,10 @@ namespace SHADE
|
|||
/// </summary>
|
||||
virtual void onDrawGizmos();
|
||||
/// <summary>
|
||||
/// Called when this script is disabled.
|
||||
/// </summary>
|
||||
virtual void onDisable();
|
||||
/// <summary>
|
||||
/// Called just before the end of the frame where the attached GameObject or
|
||||
/// this script is destroyed directly or indirectly due to destruction of the
|
||||
/// owner.
|
||||
|
@ -403,7 +438,8 @@ namespace SHADE
|
|||
/*-----------------------------------------------------------------------------*/
|
||||
/* Data Members */
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
GameObject owner;
|
||||
SHADE::GameObject owner;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -528,7 +528,8 @@ namespace SHADE
|
|||
ScriptList^ scripts = entity.Value;
|
||||
for (int i = 0; i < scripts->Count; ++i)
|
||||
{
|
||||
scripts[i]->FixedUpdate();
|
||||
if (scripts[i]->Enabled)
|
||||
scripts[i]->FixedUpdate();
|
||||
}
|
||||
}
|
||||
SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore")
|
||||
|
@ -546,7 +547,8 @@ namespace SHADE
|
|||
ScriptList^ scripts = entity.Value;
|
||||
for (int i = 0; i < scripts->Count; ++i)
|
||||
{
|
||||
scripts[i]->Update();
|
||||
if (scripts[i]->Enabled)
|
||||
scripts[i]->Update();
|
||||
}
|
||||
}
|
||||
SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore")
|
||||
|
@ -564,7 +566,8 @@ namespace SHADE
|
|||
ScriptList^ scripts = entity.Value;
|
||||
for (int i = 0; i < scripts->Count; ++i)
|
||||
{
|
||||
scripts[i]->LateUpdate();
|
||||
if (scripts[i]->Enabled)
|
||||
scripts[i]->LateUpdate();
|
||||
}
|
||||
}
|
||||
SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore")
|
||||
|
@ -583,7 +586,8 @@ namespace SHADE
|
|||
ScriptList^ scripts = entity.Value;
|
||||
for (int i = 0; i < scripts->Count; ++i)
|
||||
{
|
||||
scripts[i]->OnDrawGizmos();
|
||||
if (scripts[i]->Enabled)
|
||||
scripts[i]->OnDrawGizmos();
|
||||
}
|
||||
}
|
||||
SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore")
|
||||
|
|
Loading…
Reference in New Issue