mirror of
https://github.com/GTAmodding/re3.git
synced 2024-11-26 10:23:43 +00:00
Merge remote-tracking branch 'upstream/lcs' into lcs
This commit is contained in:
commit
f6c8225dac
13 changed files with 98 additions and 35 deletions
|
@ -71,6 +71,13 @@ workspace "reLCS"
|
||||||
symbols "Full"
|
symbols "Full"
|
||||||
staticruntime "off"
|
staticruntime "off"
|
||||||
|
|
||||||
|
-- for CVECTORHACK
|
||||||
|
configuration { "gmake*" }
|
||||||
|
buildoptions { "-fpermissive" }
|
||||||
|
|
||||||
|
filter { "platforms:macosx*" }
|
||||||
|
buildoptions { "-Wno-address-of-temporary" }
|
||||||
|
|
||||||
if _OPTIONS["with-asan"] then
|
if _OPTIONS["with-asan"] then
|
||||||
buildoptions { "-fsanitize=address -g3 -fno-omit-frame-pointer" }
|
buildoptions { "-fsanitize=address -g3 -fno-omit-frame-pointer" }
|
||||||
linkoptions { "-fsanitize=address" }
|
linkoptions { "-fsanitize=address" }
|
||||||
|
|
|
@ -102,6 +102,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang
|
||||||
if (NOT LIBRW_PLATFORM_PS2)
|
if (NOT LIBRW_PLATFORM_PS2)
|
||||||
target_compile_options(${EXECUTABLE}
|
target_compile_options(${EXECUTABLE}
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
-fpermissive # for CVECTORHACK
|
||||||
|
-Wno-address-of-temporary # for CVECTORHACK
|
||||||
-Wextra
|
-Wextra
|
||||||
-Wdouble-promotion
|
-Wdouble-promotion
|
||||||
-Wpedantic
|
-Wpedantic
|
||||||
|
|
|
@ -4,9 +4,9 @@ struct CColLine
|
||||||
{
|
{
|
||||||
// NB: this has to be compatible with two CVuVectors
|
// NB: this has to be compatible with two CVuVectors
|
||||||
CVector p0;
|
CVector p0;
|
||||||
int pad0;
|
// int pad0;
|
||||||
CVector p1;
|
CVector p1;
|
||||||
int pad1;
|
// int pad1;
|
||||||
|
|
||||||
CColLine(void) { };
|
CColLine(void) { };
|
||||||
CColLine(const CVector &p0, const CVector &p1) { this->p0 = p0; this->p1 = p1; };
|
CColLine(const CVector &p0, const CVector &p1) { this->p0 = p0; this->p1 = p1; };
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
struct CColPoint
|
struct CColPoint
|
||||||
{
|
{
|
||||||
CVector point;
|
CVector point;
|
||||||
int pad1;
|
int pad1; // this is stupid
|
||||||
// the surface normal on the surface of point
|
// the surface normal on the surface of point
|
||||||
CVector normal;
|
CVector normal;
|
||||||
int pad2;
|
//int pad2;
|
||||||
uint8 surfaceA;
|
uint8 surfaceA;
|
||||||
uint8 pieceA;
|
uint8 pieceA;
|
||||||
uint8 surfaceB;
|
uint8 surfaceB;
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
#include "SurfaceTable.h"
|
#include "SurfaceTable.h"
|
||||||
|
|
||||||
struct CSphere
|
// TODO(LCS): maybe this was in a union with CVuVector? or is the alignment manual?
|
||||||
|
struct TYPEALIGN(16) CSphere
|
||||||
{
|
{
|
||||||
// NB: this has to be compatible with a CVuVector
|
// NB: this has to be compatible with a CVuVector
|
||||||
CVector center;
|
RwV3d center;
|
||||||
float radius;
|
float radius;
|
||||||
void Set(float radius, const CVector ¢er) { this->center = center; this->radius = radius; }
|
void Set(float radius, const CVector ¢er) { this->center = center; this->radius = radius; }
|
||||||
};
|
};
|
||||||
|
|
|
@ -62,6 +62,7 @@ struct CColTrianglePlane
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
|
// TODO(LCS): LCS actually uses CompressedVector too
|
||||||
CVector normal;
|
CVector normal;
|
||||||
float dist;
|
float dist;
|
||||||
uint8 dir;
|
uint8 dir;
|
||||||
|
|
|
@ -24,6 +24,10 @@
|
||||||
#include "Camera.h"
|
#include "Camera.h"
|
||||||
#include "ColStore.h"
|
#include "ColStore.h"
|
||||||
|
|
||||||
|
// gotta figure out how they handled CSphere exactly
|
||||||
|
// so using this to remind me to look into it again.
|
||||||
|
#define CVECTORHACK(rwv3d) CVector(rwv3d)
|
||||||
|
|
||||||
#ifdef VU_COLLISION
|
#ifdef VU_COLLISION
|
||||||
#include "VuCollision.h"
|
#include "VuCollision.h"
|
||||||
|
|
||||||
|
@ -391,7 +395,7 @@ CCollision::TestLineSphere(const CColLine &line, const CColSphere &sph)
|
||||||
// The length of the tangent would be this: Sqrt((c-p0)^2 - r^2).
|
// The length of the tangent would be this: Sqrt((c-p0)^2 - r^2).
|
||||||
// Negative if p0 is inside the sphere! This breaks the test!
|
// Negative if p0 is inside the sphere! This breaks the test!
|
||||||
float tansq = 4.0f * linesq *
|
float tansq = 4.0f * linesq *
|
||||||
(sph.center.MagnitudeSqr() - 2.0f*DotProduct(sph.center, line.p0) + line.p0.MagnitudeSqr() - sph.radius*sph.radius);
|
(CVECTORHACK(sph.center).MagnitudeSqr() - 2.0f*DotProduct(sph.center, line.p0) + line.p0.MagnitudeSqr() - sph.radius*sph.radius);
|
||||||
float diffsq = projline*projline - tansq;
|
float diffsq = projline*projline - tansq;
|
||||||
// if diffsq < 0 that means the line is a passant, so no intersection
|
// if diffsq < 0 that means the line is a passant, so no intersection
|
||||||
if(diffsq < 0.0f)
|
if(diffsq < 0.0f)
|
||||||
|
@ -470,9 +474,9 @@ CCollision::TestSphereTriangle(const CColSphere &sphere,
|
||||||
case 2:
|
case 2:
|
||||||
// closest to an edge
|
// closest to an edge
|
||||||
// looks like original game as DistToLine manually inlined
|
// looks like original game as DistToLine manually inlined
|
||||||
if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center);
|
if(!insideAB) dist = DistToLine(&va, &vb, &CVECTORHACK(sphere.center));
|
||||||
else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center);
|
else if(!insideAC) dist = DistToLine(&va, &vc, &CVECTORHACK(sphere.center));
|
||||||
else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center);
|
else if(!insideBC) dist = DistToLine(&vb, &vc, &CVECTORHACK(sphere.center));
|
||||||
else assert(0);
|
else assert(0);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
|
@ -1279,9 +1283,9 @@ CCollision::ProcessSphereTriangle(const CColSphere &sphere,
|
||||||
case 2:
|
case 2:
|
||||||
// closest to an edge
|
// closest to an edge
|
||||||
// looks like original game as DistToLine manually inlined
|
// looks like original game as DistToLine manually inlined
|
||||||
if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center, p);
|
if(!insideAB) dist = DistToLine(&va, &vb, &CVECTORHACK(sphere.center), p);
|
||||||
else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center, p);
|
else if(!insideAC) dist = DistToLine(&va, &vc, &CVECTORHACK(sphere.center), p);
|
||||||
else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center, p);
|
else if(!insideBC) dist = DistToLine(&vb, &vc, &CVECTORHACK(sphere.center), p);
|
||||||
else assert(0);
|
else assert(0);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
|
|
|
@ -287,6 +287,7 @@ enum Config {
|
||||||
// Water & Particle
|
// Water & Particle
|
||||||
// #define PC_WATER
|
// #define PC_WATER
|
||||||
#define WATER_CHEATS
|
#define WATER_CHEATS
|
||||||
|
//#define PSP_WATERCANNON
|
||||||
|
|
||||||
//#define USE_CUTSCENE_SHADOW_FOR_PED
|
//#define USE_CUTSCENE_SHADOW_FOR_PED
|
||||||
#define DISABLE_CUTSCENE_SHADOWS
|
#define DISABLE_CUTSCENE_SHADOWS
|
||||||
|
|
|
@ -44,3 +44,18 @@ operator*(const CMatrix &mat, const CVector &vec)
|
||||||
mat.ry * vec.x + mat.fy * vec.y + mat.uy * vec.z + mat.py,
|
mat.ry * vec.x + mat.fy * vec.y + mat.uy * vec.z + mat.py,
|
||||||
mat.rz * vec.x + mat.fz * vec.y + mat.uz * vec.z + mat.pz);
|
mat.rz * vec.x + mat.fz * vec.y + mat.uz * vec.z + mat.pz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RwV3dTransformPoints(CVector * pointsOut, const CVector * pointsIn, RwInt32 numPoints, const RwMatrix * matrix)
|
||||||
|
{
|
||||||
|
while(numPoints--){
|
||||||
|
float x = pointsIn->x*matrix->right.x + pointsIn->y*matrix->up.x + pointsIn->z*matrix->at.x + matrix->pos.x;
|
||||||
|
float y = pointsIn->x*matrix->right.y + pointsIn->y*matrix->up.y + pointsIn->z*matrix->at.y + matrix->pos.y;
|
||||||
|
float z = pointsIn->x*matrix->right.z + pointsIn->y*matrix->up.z + pointsIn->z*matrix->at.z + matrix->pos.z;
|
||||||
|
pointsOut->x = x;
|
||||||
|
pointsOut->y = y;
|
||||||
|
pointsOut->z = z;
|
||||||
|
pointsOut++;
|
||||||
|
pointsIn++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
// TODO(LCS): this should have 16 byte alignment but VS doesn't like passing aligned values by value
|
||||||
|
// need a solution for this eventually if we ever want to load original assets
|
||||||
class CVector : public RwV3d
|
class CVector : public RwV3d
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
float w;
|
||||||
|
|
||||||
CVector(void) {}
|
CVector(void) {}
|
||||||
CVector(float x, float y, float z)
|
CVector(float x, float y, float z)
|
||||||
{
|
{
|
||||||
|
@ -127,3 +131,6 @@ class CMatrix;
|
||||||
CVector Multiply3x3(const CMatrix &mat, const CVector &vec);
|
CVector Multiply3x3(const CMatrix &mat, const CVector &vec);
|
||||||
CVector Multiply3x3(const CVector &vec, const CMatrix &mat);
|
CVector Multiply3x3(const CVector &vec, const CMatrix &mat);
|
||||||
CVector operator*(const CMatrix &mat, const CVector &vec);
|
CVector operator*(const CMatrix &mat, const CVector &vec);
|
||||||
|
|
||||||
|
// we need this because CVector and RwV3d have different size now
|
||||||
|
void RwV3dTransformPoints(CVector * pointsOut, const CVector * pointsIn, RwInt32 numPoints, const RwMatrix * matrix);
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
class TYPEALIGN(16) CVuVector : public CVector
|
class TYPEALIGN(16) CVuVector : public CVector
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
float w;
|
// float w; // in CVector now
|
||||||
CVuVector(void) {}
|
CVuVector(void) {}
|
||||||
CVuVector(float x, float y, float z) : CVector(x, y, z) {}
|
CVuVector(float x, float y, float z) : CVector(x, y, z) {}
|
||||||
CVuVector(float x, float y, float z, float w) : CVector(x, y, z), w(w) {}
|
CVuVector(float x, float y, float z, float w) : CVector(x, y, z)/*, w(w)*/ { this->w = w;}
|
||||||
CVuVector(const CVector &v) : CVector(v.x, v.y, v.z) {}
|
CVuVector(const CVector &v) : CVector(v.x, v.y, v.z) {}
|
||||||
CVuVector(const RwV3d &v) : CVector(v) {}
|
CVuVector(const RwV3d &v) : CVector(v) {}
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -13,6 +13,16 @@
|
||||||
#include "Camera.h"
|
#include "Camera.h"
|
||||||
#include "Particle.h"
|
#include "Particle.h"
|
||||||
|
|
||||||
|
// --LCS: file done
|
||||||
|
|
||||||
|
#ifdef PSP_WATERCANNON
|
||||||
|
//PSP:
|
||||||
|
#define WATER_COLOR 255
|
||||||
|
#else
|
||||||
|
//PS2:
|
||||||
|
#define WATER_COLOR 127
|
||||||
|
#endif
|
||||||
|
|
||||||
#define WATERCANNONVERTS 4
|
#define WATERCANNONVERTS 4
|
||||||
#define WATERCANNONINDEXES 12
|
#define WATERCANNONINDEXES 12
|
||||||
|
|
||||||
|
@ -115,24 +125,34 @@ void CWaterCannon::Update_NewInput(CVector *pos, CVector *dir)
|
||||||
m_abUsed[m_nCur] = true;
|
m_abUsed[m_nCur] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static float fWaterCannonU = 0.0f;
|
||||||
void CWaterCannon::Render(void)
|
void CWaterCannon::Render(void)
|
||||||
{
|
{
|
||||||
|
extern RwRaster *gpFireHoseRaster;
|
||||||
|
|
||||||
RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE);
|
RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE);
|
||||||
RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
|
RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
|
||||||
RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE);
|
RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE);
|
||||||
RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpWaterRaster);
|
RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpFireHoseRaster);
|
||||||
|
|
||||||
float v = float(CGeneral::GetRandomNumber() & 255) / 256;
|
fWaterCannonU += CTimer::GetTimeStepInSeconds() * 6.0f;
|
||||||
|
|
||||||
RwIm3DVertexSetV(&WaterCannonVertices[0], v);
|
while ( fWaterCannonU >= 1.0f )
|
||||||
RwIm3DVertexSetV(&WaterCannonVertices[1], v);
|
fWaterCannonU -= 1.0f;
|
||||||
RwIm3DVertexSetV(&WaterCannonVertices[2], v);
|
|
||||||
RwIm3DVertexSetV(&WaterCannonVertices[3], v);
|
RwIm3DVertexSetU(&WaterCannonVertices[0], -fWaterCannonU);
|
||||||
|
RwIm3DVertexSetV(&WaterCannonVertices[0], 0.0f);
|
||||||
|
RwIm3DVertexSetU(&WaterCannonVertices[1], -fWaterCannonU);
|
||||||
|
RwIm3DVertexSetV(&WaterCannonVertices[1], 1.0f);
|
||||||
|
RwIm3DVertexSetU(&WaterCannonVertices[2], 1.0f - fWaterCannonU);
|
||||||
|
RwIm3DVertexSetV(&WaterCannonVertices[2], 0.0f);
|
||||||
|
RwIm3DVertexSetU(&WaterCannonVertices[3], 1.0f - fWaterCannonU);
|
||||||
|
RwIm3DVertexSetV(&WaterCannonVertices[3], 1.0f);
|
||||||
|
|
||||||
int16 pointA = m_nCur % NUM_SEGMENTPOINTS;
|
int16 pointA = m_nCur % NUM_SEGMENTPOINTS;
|
||||||
|
|
||||||
int16 pointB = pointA - 1;
|
int16 pointB = pointA - 1;
|
||||||
if ( (pointA - 1) < 0 )
|
int16 pointC = pointA;
|
||||||
|
if ( pointB < 0 )
|
||||||
pointB += NUM_SEGMENTPOINTS;
|
pointB += NUM_SEGMENTPOINTS;
|
||||||
|
|
||||||
bool bInit = false;
|
bool bInit = false;
|
||||||
|
@ -142,6 +162,10 @@ void CWaterCannon::Render(void)
|
||||||
{
|
{
|
||||||
if ( m_abUsed[pointA] && m_abUsed[pointB] )
|
if ( m_abUsed[pointA] && m_abUsed[pointB] )
|
||||||
{
|
{
|
||||||
|
bool bFirst = false;
|
||||||
|
if ( i == 0 || m_abUsed[pointA] && !m_abUsed[pointC] )
|
||||||
|
bFirst = true;
|
||||||
|
|
||||||
if ( !bInit )
|
if ( !bInit )
|
||||||
{
|
{
|
||||||
CVector cp = CrossProduct(m_avecPos[pointB] - m_avecPos[pointA], TheCamera.GetForward());
|
CVector cp = CrossProduct(m_avecPos[pointB] - m_avecPos[pointA], TheCamera.GetForward());
|
||||||
|
@ -149,26 +173,25 @@ void CWaterCannon::Render(void)
|
||||||
bInit = true;
|
bInit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float dist = float(i*i*i) / 300.0f + 1.0f;
|
|
||||||
float brightness = float(i) / NUM_SEGMENTPOINTS;
|
float brightness = float(i) / NUM_SEGMENTPOINTS;
|
||||||
|
|
||||||
int32 color = (int32)((1.0f - brightness*brightness) * 255.0f);
|
int32 color = (int32)((1.0f - brightness*brightness) * 255.0f);
|
||||||
CVector offset = dist * norm;
|
|
||||||
|
|
||||||
RwIm3DVertexSetRGBA(&WaterCannonVertices[0], color, color, color, color);
|
CVector offset = (float(i)+1.0f) * norm;
|
||||||
|
|
||||||
|
RwIm3DVertexSetRGBA(&WaterCannonVertices[0], WATER_COLOR, WATER_COLOR, WATER_COLOR, bFirst ? 0 : color);
|
||||||
RwIm3DVertexSetPos (&WaterCannonVertices[0], m_avecPos[pointA].x - offset.x, m_avecPos[pointA].y - offset.y, m_avecPos[pointA].z - offset.z);
|
RwIm3DVertexSetPos (&WaterCannonVertices[0], m_avecPos[pointA].x - offset.x, m_avecPos[pointA].y - offset.y, m_avecPos[pointA].z - offset.z);
|
||||||
|
|
||||||
RwIm3DVertexSetRGBA(&WaterCannonVertices[1], color, color, color, color);
|
RwIm3DVertexSetRGBA(&WaterCannonVertices[1], WATER_COLOR, WATER_COLOR, WATER_COLOR, bFirst ? 0 : color);
|
||||||
RwIm3DVertexSetPos (&WaterCannonVertices[1], m_avecPos[pointA].x + offset.x, m_avecPos[pointA].y + offset.y, m_avecPos[pointA].z + offset.z);
|
RwIm3DVertexSetPos (&WaterCannonVertices[1], m_avecPos[pointA].x + offset.x, m_avecPos[pointA].y + offset.y, m_avecPos[pointA].z + offset.z);
|
||||||
|
|
||||||
RwIm3DVertexSetRGBA(&WaterCannonVertices[2], color, color, color, color);
|
offset = (float(i+1)+1.0f) * norm;
|
||||||
|
|
||||||
|
RwIm3DVertexSetRGBA(&WaterCannonVertices[2], WATER_COLOR, WATER_COLOR, WATER_COLOR, color);
|
||||||
RwIm3DVertexSetPos (&WaterCannonVertices[2], m_avecPos[pointB].x - offset.x, m_avecPos[pointB].y - offset.y, m_avecPos[pointB].z - offset.z);
|
RwIm3DVertexSetPos (&WaterCannonVertices[2], m_avecPos[pointB].x - offset.x, m_avecPos[pointB].y - offset.y, m_avecPos[pointB].z - offset.z);
|
||||||
|
|
||||||
RwIm3DVertexSetRGBA(&WaterCannonVertices[3], color, color, color, color);
|
RwIm3DVertexSetRGBA(&WaterCannonVertices[3], WATER_COLOR, WATER_COLOR, WATER_COLOR, color);
|
||||||
RwIm3DVertexSetPos (&WaterCannonVertices[3], m_avecPos[pointB].x + offset.x, m_avecPos[pointB].y + offset.y, m_avecPos[pointB].z + offset.z);
|
RwIm3DVertexSetPos (&WaterCannonVertices[3], m_avecPos[pointB].x + offset.x, m_avecPos[pointB].y + offset.y, m_avecPos[pointB].z + offset.z);
|
||||||
|
|
||||||
LittleTest();
|
|
||||||
|
|
||||||
if ( RwIm3DTransform(WaterCannonVertices, WATERCANNONVERTS, NULL, rwIM3D_VERTEXUV) )
|
if ( RwIm3DTransform(WaterCannonVertices, WATERCANNONVERTS, NULL, rwIM3D_VERTEXUV) )
|
||||||
{
|
{
|
||||||
RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, WaterCannonIndexList, WATERCANNONINDEXES);
|
RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, WaterCannonIndexList, WATERCANNONINDEXES);
|
||||||
|
@ -176,6 +199,7 @@ void CWaterCannon::Render(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pointC = pointA;
|
||||||
pointA = pointB--;
|
pointA = pointB--;
|
||||||
if ( pointB < 0 )
|
if ( pointB < 0 )
|
||||||
pointB += NUM_SEGMENTPOINTS;
|
pointB += NUM_SEGMENTPOINTS;
|
||||||
|
|
|
@ -14,6 +14,7 @@ public:
|
||||||
int32 m_nId;
|
int32 m_nId;
|
||||||
int16 m_nCur;
|
int16 m_nCur;
|
||||||
uint32 m_nTimeCreated;
|
uint32 m_nTimeCreated;
|
||||||
|
int32 field_C;
|
||||||
CVector m_avecPos[NUM_SEGMENTPOINTS];
|
CVector m_avecPos[NUM_SEGMENTPOINTS];
|
||||||
CVector m_avecVelocity[NUM_SEGMENTPOINTS];
|
CVector m_avecVelocity[NUM_SEGMENTPOINTS];
|
||||||
bool m_abUsed[NUM_SEGMENTPOINTS];
|
bool m_abUsed[NUM_SEGMENTPOINTS];
|
||||||
|
@ -25,7 +26,7 @@ public:
|
||||||
void PushPeds(void);
|
void PushPeds(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
VALIDATE_SIZE(CWaterCannon, 412);
|
VALIDATE_SIZE(CWaterCannon, 0x1A0);
|
||||||
|
|
||||||
class CWaterCannons
|
class CWaterCannons
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue