2019-09-29 00:30:53 +00:00
|
|
|
#include "Runtime/GuiSys/CAuiImagePane.hpp"
|
|
|
|
|
|
|
|
#include "Runtime/CSimplePool.hpp"
|
|
|
|
#include "Runtime/Camera/CCameraFilter.hpp"
|
|
|
|
#include "Runtime/Graphics/CTexture.hpp"
|
2022-03-27 05:00:53 +00:00
|
|
|
#include "Runtime/Graphics/CGX.hpp"
|
2019-09-29 00:30:53 +00:00
|
|
|
#include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp"
|
2017-01-22 01:40:12 +00:00
|
|
|
|
2021-04-10 08:42:06 +00:00
|
|
|
namespace metaforce {
|
2017-01-22 01:40:12 +00:00
|
|
|
|
2017-08-13 05:26:14 +00:00
|
|
|
CAuiImagePane::CAuiImagePane(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId tex0, CAssetId tex1,
|
2017-01-22 01:40:12 +00:00
|
|
|
rstl::reserved_vector<zeus::CVector3f, 4>&& coords,
|
2017-05-14 19:58:44 +00:00
|
|
|
rstl::reserved_vector<zeus::CVector2f, 4>&& uvs, bool initTex)
|
2018-12-08 05:30:43 +00:00
|
|
|
: CGuiWidget(parms), xc8_tex0(tex0), xcc_tex1(tex1), xe0_coords(std::move(coords)), x114_uvs(std::move(uvs)) {
|
|
|
|
if (initTex)
|
|
|
|
SetTextureID0(tex0, sp);
|
2017-01-22 01:40:12 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
std::shared_ptr<CGuiWidget> CAuiImagePane::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) {
|
|
|
|
CGuiWidgetParms parms = ReadWidgetHeader(frame, in);
|
2022-02-18 07:37:54 +00:00
|
|
|
in.ReadLong();
|
|
|
|
in.ReadLong();
|
|
|
|
in.ReadLong();
|
|
|
|
u32 coordCount = in.ReadLong();
|
2018-12-08 05:30:43 +00:00
|
|
|
rstl::reserved_vector<zeus::CVector3f, 4> coords;
|
2019-06-12 02:05:17 +00:00
|
|
|
for (u32 i = 0; i < coordCount; ++i)
|
2022-02-18 07:37:54 +00:00
|
|
|
coords.push_back(in.Get<zeus::CVector3f>());
|
|
|
|
u32 uvCount = in.ReadLong();
|
2018-12-08 05:30:43 +00:00
|
|
|
rstl::reserved_vector<zeus::CVector2f, 4> uvs;
|
2019-06-12 02:05:17 +00:00
|
|
|
for (u32 i = 0; i < uvCount; ++i)
|
2022-02-18 07:37:54 +00:00
|
|
|
uvs.push_back(in.Get<zeus::CVector2f>());
|
2018-12-08 05:30:43 +00:00
|
|
|
std::shared_ptr<CGuiWidget> ret =
|
2022-02-19 13:04:45 +00:00
|
|
|
std::make_shared<CAuiImagePane>(parms, sp, CAssetId(), CAssetId(), std::move(coords), std::move(uvs), true);
|
2018-12-08 05:30:43 +00:00
|
|
|
ret->ParseBaseInfo(frame, in, parms);
|
|
|
|
return ret;
|
2017-01-22 01:40:12 +00:00
|
|
|
}
|
|
|
|
|
2019-03-10 09:37:36 +00:00
|
|
|
void CAuiImagePane::Reset(ETraversalMode mode) {
|
|
|
|
xc8_tex0 = CAssetId();
|
|
|
|
xb8_tex0Tok = TLockedToken<CTexture>();
|
|
|
|
CGuiWidget::Reset(mode);
|
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CAuiImagePane::Update(float dt) {
|
|
|
|
xd0_uvBias0.x() = std::fmod(xd0_uvBias0.x(), 1.f);
|
|
|
|
xd0_uvBias0.y() = std::fmod(xd0_uvBias0.y(), 1.f);
|
2019-02-24 07:15:54 +00:00
|
|
|
if (x138_tileSize != zeus::skZero2f && xb8_tex0Tok.IsLoaded()) {
|
2018-12-08 05:30:43 +00:00
|
|
|
zeus::CVector2f tmp = zeus::CVector2f(xb8_tex0Tok->GetWidth(), xb8_tex0Tok->GetHeight()) / x138_tileSize;
|
|
|
|
x144_frameTimer = std::fmod(x144_frameTimer + dt * x140_interval, std::floor(tmp.x()) * std::floor(tmp.y()));
|
|
|
|
}
|
|
|
|
|
|
|
|
CGuiWidget::Update(dt);
|
2017-05-18 07:07:49 +00:00
|
|
|
}
|
|
|
|
|
2022-03-05 21:46:53 +00:00
|
|
|
void CAuiImagePane::DoDrawImagePane(const zeus::CColor& color, CTexture& tex, int frame, float alpha,
|
|
|
|
bool noBlur) const {
|
2018-12-08 05:30:43 +00:00
|
|
|
zeus::CColor useColor = color;
|
|
|
|
useColor.a() *= alpha;
|
|
|
|
|
|
|
|
rstl::reserved_vector<zeus::CVector2f, 4> vec;
|
|
|
|
const rstl::reserved_vector<zeus::CVector2f, 4>* useUVs;
|
2019-02-24 07:15:54 +00:00
|
|
|
if (x138_tileSize != zeus::skZero2f) {
|
2022-03-27 05:00:53 +00:00
|
|
|
const zeus::CVector2f res(xb8_tex0Tok->GetWidth(), xb8_tex0Tok->GetHeight());
|
2021-06-04 09:53:12 +00:00
|
|
|
const zeus::CVector2f tmp = res / x138_tileSize;
|
2020-03-18 01:18:52 +00:00
|
|
|
const zeus::CVector2f tmpRecip = x138_tileSize / res;
|
2021-06-04 09:53:12 +00:00
|
|
|
const float x0 = tmpRecip.x() * static_cast<float>(frame % static_cast<int>(tmp.x()));
|
2020-03-18 01:18:52 +00:00
|
|
|
const float x1 = x0 + tmpRecip.x();
|
2021-06-04 09:53:12 +00:00
|
|
|
const float y0 = tmpRecip.y() * static_cast<float>(frame % static_cast<int>(tmp.y()));
|
2022-03-27 03:51:50 +00:00
|
|
|
const float y1 = y0 - tmpRecip.y();
|
2018-12-08 05:30:43 +00:00
|
|
|
vec.push_back(zeus::CVector2f(x0, y0));
|
|
|
|
vec.push_back(zeus::CVector2f(x0, y1));
|
2021-06-04 09:06:24 +00:00
|
|
|
vec.push_back(zeus::CVector2f(x1, y0));
|
2022-03-27 03:51:50 +00:00
|
|
|
vec.push_back(zeus::CVector2f(x1, y1));
|
2018-12-08 05:30:43 +00:00
|
|
|
useUVs = &vec;
|
|
|
|
} else {
|
|
|
|
useUVs = &x114_uvs;
|
|
|
|
}
|
|
|
|
|
2022-03-27 05:00:53 +00:00
|
|
|
if (!noBlur) {
|
|
|
|
if ((x14c_deResFactor == 0.f && alpha == 1.f) || tex.GetNumberOfMipMaps() == 1) {
|
2022-08-15 22:50:20 +00:00
|
|
|
CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate);
|
|
|
|
CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);
|
2022-08-09 06:10:51 +00:00
|
|
|
tex.LoadMipLevel(0, GX_TEXMAP0, EClampMode::Repeat);
|
2022-07-29 20:16:55 +00:00
|
|
|
CGraphics::StreamBegin(GX_TRIANGLESTRIP);
|
2022-03-27 05:00:53 +00:00
|
|
|
CGraphics::StreamColor(useColor);
|
|
|
|
for (u32 i = 0; i < useUVs->size(); ++i) {
|
|
|
|
CGraphics::StreamTexcoord((*useUVs)[i] + xd0_uvBias0);
|
|
|
|
CGraphics::StreamVertex(xe0_coords[i]);
|
|
|
|
}
|
|
|
|
CGraphics::StreamEnd();
|
|
|
|
} else {
|
|
|
|
u32 mipCount = tex.GetNumberOfMipMaps() - 1;
|
|
|
|
float fadeFactor = (1.f - x14c_deResFactor) * alpha;
|
|
|
|
float fadeQ = -(fadeFactor * fadeFactor * fadeFactor - 1.f);
|
|
|
|
fadeFactor = fadeQ * static_cast<float>(mipCount);
|
|
|
|
u32 mip1 = fadeFactor;
|
|
|
|
u32 mip2 = mip1;
|
|
|
|
if (fadeQ != static_cast<float>(mip1 / mipCount)) {
|
|
|
|
mip2 = mip1 + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
float rgba1 = (fadeFactor - static_cast<float>(mip1));
|
|
|
|
float rgba2 = 1.f - rgba1;
|
2022-07-29 20:16:55 +00:00
|
|
|
tex.LoadMipLevel(mip1, GX_TEXMAP0, EClampMode::Repeat);
|
|
|
|
tex.LoadMipLevel(mip2, GX_TEXMAP1, EClampMode::Repeat);
|
|
|
|
std::array<GXVtxDescList, 3> list{{
|
|
|
|
{GX_VA_POS, GX_DIRECT},
|
|
|
|
{GX_VA_TEX0, GX_DIRECT},
|
|
|
|
{GX_VA_NULL, GX_NONE},
|
2022-03-27 21:07:50 +00:00
|
|
|
}};
|
2022-03-27 05:00:53 +00:00
|
|
|
|
2022-03-27 21:07:50 +00:00
|
|
|
CGX::SetVtxDescv(list.data());
|
2022-03-27 05:00:53 +00:00
|
|
|
CGX::SetNumChans(0);
|
|
|
|
CGX::SetNumTexGens(2);
|
|
|
|
CGX::SetNumTevStages(2);
|
2022-07-29 20:16:55 +00:00
|
|
|
GXTevStageID stage = GX_TEVSTAGE0;
|
|
|
|
while (stage < GX_TEVSTAGE2) {
|
|
|
|
GXTevColorArg colorD = stage == GX_TEVSTAGE0 ? GX_CC_ZERO : GX_CC_CPREV;
|
|
|
|
CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, colorD);
|
|
|
|
GXTevAlphaArg alphaD = stage == GX_TEVSTAGE0 ? GX_CA_ZERO : GX_CA_APREV;
|
|
|
|
CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, alphaD);
|
|
|
|
CGX::SetTevColorOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);
|
|
|
|
CGX::SetTevAlphaOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV);
|
|
|
|
stage = static_cast<GXTevStageID>(stage + GX_TEVSTAGE1);
|
2022-03-27 05:00:53 +00:00
|
|
|
}
|
2022-07-29 20:16:55 +00:00
|
|
|
CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);
|
|
|
|
CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0);
|
|
|
|
CGX::SetTevKAlphaSel(GX_TEVSTAGE1, GX_TEV_KASEL_K1_A);
|
|
|
|
CGX::SetTevKColorSel(GX_TEVSTAGE1, GX_TEV_KCSEL_K1);
|
2022-03-27 05:00:53 +00:00
|
|
|
zeus::CColor col1 = useColor * zeus::CColor(rgba2, rgba2, rgba2, rgba2);
|
|
|
|
zeus::CColor col2 = useColor * zeus::CColor(rgba1, rgba1, rgba1, rgba1);
|
2022-07-29 20:16:55 +00:00
|
|
|
CGX::SetTevKColor(GX_KCOLOR0, col1);
|
|
|
|
CGX::SetTevKColor(GX_KCOLOR1, col2);
|
|
|
|
CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
|
|
|
|
CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL);
|
|
|
|
CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);
|
|
|
|
CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY);
|
|
|
|
CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);
|
2022-03-27 05:00:53 +00:00
|
|
|
for (u32 idx = 0; const auto& coord : xe0_coords) {
|
|
|
|
GXPosition3f32(coord);
|
|
|
|
GXTexCoord2f32((*useUVs)[idx] + xd0_uvBias0);
|
|
|
|
++idx;
|
|
|
|
}
|
|
|
|
CGX::End();
|
|
|
|
}
|
|
|
|
} else {
|
2022-08-15 22:50:20 +00:00
|
|
|
CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulateAlpha);
|
|
|
|
CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru);
|
2022-07-29 20:16:55 +00:00
|
|
|
tex.Load(GX_TEXMAP0, EClampMode::Repeat);
|
|
|
|
CGraphics::StreamBegin(GX_TRIANGLESTRIP);
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::StreamColor(useColor);
|
2022-03-09 07:48:22 +00:00
|
|
|
for (u32 i = 0; i < useUVs->size(); ++i) {
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::StreamTexcoord((*useUVs)[i]);
|
2022-03-27 05:00:53 +00:00
|
|
|
CGraphics::StreamVertex(xe0_coords[i] + xd0_uvBias0);
|
2022-03-05 21:46:53 +00:00
|
|
|
}
|
|
|
|
CGraphics::StreamEnd();
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2017-05-18 07:07:49 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 05:14:36 +00:00
|
|
|
void CAuiImagePane::Draw(const CGuiWidgetDrawParms& params) {
|
2018-12-08 05:30:43 +00:00
|
|
|
CGraphics::SetModelMatrix(x34_worldXF);
|
2020-03-18 05:14:36 +00:00
|
|
|
if (!GetIsVisible() || !xb8_tex0Tok.IsLoaded()) {
|
2018-12-08 05:30:43 +00:00
|
|
|
return;
|
2020-03-18 05:14:36 +00:00
|
|
|
}
|
2020-04-11 22:51:39 +00:00
|
|
|
SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(FMT_STRING("CAuiImagePane::Draw {}"), m_name).c_str(), zeus::skCyan);
|
2018-12-08 05:30:43 +00:00
|
|
|
GetIsFinishedLoadingWidgetSpecific();
|
|
|
|
zeus::CColor color = xa8_color2;
|
|
|
|
color.a() *= params.x0_alphaMod;
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual,
|
|
|
|
xac_drawFlags == EGuiModelDrawFlags::Shadeless ||
|
|
|
|
xac_drawFlags == EGuiModelDrawFlags::Opaque);
|
2018-12-08 05:30:43 +00:00
|
|
|
float blur0 = 1.f;
|
|
|
|
float blur1 = 0.f;
|
2020-04-20 02:06:11 +00:00
|
|
|
const int frame0 = static_cast<int>(x144_frameTimer);
|
2018-12-08 05:30:43 +00:00
|
|
|
int frame1 = 0;
|
|
|
|
if (x140_interval < 1.f && x140_interval > 0.f) {
|
|
|
|
zeus::CVector2f tmp = zeus::CVector2f(xb8_tex0Tok->GetWidth(), xb8_tex0Tok->GetHeight()) / x138_tileSize;
|
2020-04-20 02:06:11 +00:00
|
|
|
frame1 = (frame0 + 1) % static_cast<int>(tmp.x() * tmp.y());
|
2018-12-08 05:30:43 +00:00
|
|
|
if (x148_fadeDuration == 0.f)
|
|
|
|
blur1 = 1.f;
|
2017-05-18 07:07:49 +00:00
|
|
|
else
|
2018-12-08 05:30:43 +00:00
|
|
|
blur1 = std::min(std::fmod(x144_frameTimer, 1.f) / x148_fadeDuration, 1.f);
|
|
|
|
blur0 = 1.f - blur1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Alpha blend
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,
|
|
|
|
ERglLogicOp::Clear);
|
|
|
|
DoDrawImagePane(color * zeus::CColor(0.f, 0.5f), *xb8_tex0Tok, frame0, 1.f, true);
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
if (x150_flashFactor > 0.f) {
|
|
|
|
// Additive blend
|
|
|
|
zeus::CColor color2 = xa8_color2;
|
|
|
|
color2.a() = x150_flashFactor;
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);
|
|
|
|
DoDrawImagePane(color2, *xb8_tex0Tok, frame0, blur0, false);
|
2018-12-08 05:30:43 +00:00
|
|
|
if (blur1 > 0.f)
|
2022-03-05 21:46:53 +00:00
|
|
|
DoDrawImagePane(color2, *xb8_tex0Tok, frame1, blur1, false);
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (xac_drawFlags) {
|
|
|
|
case EGuiModelDrawFlags::Shadeless:
|
|
|
|
case EGuiModelDrawFlags::Opaque:
|
|
|
|
// Opaque blend
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,
|
|
|
|
ERglLogicOp::Clear);
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);
|
|
|
|
if (blur1 > 0.f) {
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);
|
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
break;
|
|
|
|
case EGuiModelDrawFlags::Alpha:
|
2017-05-18 07:07:49 +00:00
|
|
|
// Alpha blend
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,
|
|
|
|
ERglLogicOp::Clear);
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);
|
|
|
|
if (blur1 > 0.f) {
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);
|
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
break;
|
|
|
|
case EGuiModelDrawFlags::Additive:
|
|
|
|
// Additive blend
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear);
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);
|
|
|
|
if (blur1 > 0.f) {
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);
|
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
break;
|
|
|
|
case EGuiModelDrawFlags::AlphaAdditiveOverdraw:
|
|
|
|
// Alpha blend
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha,
|
|
|
|
ERglLogicOp::Clear);
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);
|
|
|
|
if (blur1 > 0.f) {
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);
|
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
// Full additive blend
|
2022-03-05 21:46:53 +00:00
|
|
|
CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear);
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false);
|
|
|
|
if (blur1 > 0.f) {
|
|
|
|
DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false);
|
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2017-05-18 07:07:49 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 05:14:36 +00:00
|
|
|
bool CAuiImagePane::GetIsFinishedLoadingWidgetSpecific() { return !xb8_tex0Tok || xb8_tex0Tok.IsLoaded(); }
|
2017-05-18 07:07:49 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CAuiImagePane::SetTextureID0(CAssetId tex, CSimplePool* sp) {
|
|
|
|
xc8_tex0 = tex;
|
|
|
|
if (!sp)
|
|
|
|
return;
|
|
|
|
if (xc8_tex0.IsValid())
|
|
|
|
xb8_tex0Tok = sp->GetObj({FOURCC('TXTR'), xc8_tex0});
|
|
|
|
else
|
|
|
|
xb8_tex0Tok = TLockedToken<CTexture>();
|
2017-05-14 19:58:44 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CAuiImagePane::SetAnimationParms(const zeus::CVector2f& tileSize, float interval, float fadeDuration) {
|
|
|
|
x138_tileSize = tileSize;
|
|
|
|
x140_interval = interval;
|
|
|
|
x144_frameTimer = 0.f;
|
|
|
|
x148_fadeDuration = fadeDuration;
|
2017-05-14 19:58:44 +00:00
|
|
|
}
|
|
|
|
|
2021-04-10 08:42:06 +00:00
|
|
|
} // namespace metaforce
|