mirror of
https://github.com/zoriya/Bomberman.git
synced 2026-06-09 20:25:31 +00:00
Merge branch 'develop' of github.com:AnonymusRaccoon/Bomberman into win_size
This commit is contained in:
@@ -57,6 +57,9 @@ set(HEADERS
|
||||
sources/Vector/Vector2.hpp
|
||||
sources/Vector/Vector3.hpp
|
||||
sources/Utils/Cache.hpp
|
||||
sources/Meshes/AMesh.hpp
|
||||
sources/Meshes/MeshSphere.hpp
|
||||
sources/Shaders/Shaders.hpp
|
||||
)
|
||||
|
||||
set(SRC
|
||||
@@ -97,7 +100,10 @@ set(SRC
|
||||
sources/Model/ModelAnimations.cpp
|
||||
sources/Vector/Vector2.cpp
|
||||
sources/Vector/Vector3.cpp
|
||||
sources/Shaders/Shaders.cpp sources/Shaders/Shaders.hpp)
|
||||
sources/Shaders/Shaders.cpp
|
||||
sources/Meshes/MeshSphere.cpp
|
||||
sources/Meshes/AMesh.cpp
|
||||
)
|
||||
|
||||
find_package(raylib QUIET)
|
||||
if (NOT raylib_FOUND)
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
namespace RAY::Drawables::Drawables2D
|
||||
{
|
||||
|
||||
Rectangle::Rectangle(const Vector2 &position, const Vector2 &dimensions, const Color &color) :
|
||||
ADrawable2D(position, color), _dimensions(dimensions)
|
||||
Rectangle::Rectangle(const Vector2 &position, const Vector2 &dimensions, const Color &color, float scale, float rotation) :
|
||||
ADrawable2D(position, color, scale, rotation), _dimensions(dimensions)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -11,76 +11,83 @@
|
||||
#include <raylib.h>
|
||||
#include "Drawables/ADrawable2D.hpp"
|
||||
|
||||
namespace RAY::Drawables::Drawables2D {
|
||||
namespace RAY::Drawables::Drawables2D
|
||||
{
|
||||
//! @brief Rectangle in a two-dimensional space
|
||||
class Rectangle: public ADrawable2D
|
||||
class Rectangle : public ADrawable2D
|
||||
{
|
||||
public:
|
||||
//! @brief Rectangle constructor
|
||||
//! @param position position of top-left point
|
||||
//! @param dimensions dimensions of the rectangle
|
||||
//! @param Color Color of the rectangle
|
||||
Rectangle(const Vector2 &position, const Vector2 &dimensions, const Color &color = WHITE);
|
||||
public:
|
||||
//! @brief Rectangle constructor
|
||||
//! @param position position of top-left point
|
||||
//! @param dimensions dimensions of the rectangle
|
||||
//! @param Color Color of the rectangle
|
||||
Rectangle(const Vector2 &position,
|
||||
const Vector2 &dimensions,
|
||||
const Color &color = WHITE,
|
||||
float scale = 1,
|
||||
float rotation = 0);
|
||||
|
||||
//! @brief Rectangle constructor
|
||||
//! @param x x-position of top-left point
|
||||
//! @param y y-position of top-left point
|
||||
//! @param width width of the rectangle
|
||||
//! @param length length of the rectangle
|
||||
//! @param Color Color of the rectangle
|
||||
Rectangle(int x, int y, int width, int height, const Color &color = WHITE);
|
||||
//! @brief Rectangle constructor
|
||||
//! @param x x-position of top-left point
|
||||
//! @param y y-position of top-left point
|
||||
//! @param width width of the rectangle
|
||||
//! @param length length of the rectangle
|
||||
//! @param Color Color of the rectangle
|
||||
Rectangle(int x, int y, int width, int height, const Color &color = WHITE);
|
||||
|
||||
//! @brief A default copy constructor
|
||||
Rectangle(const Rectangle &) = default;
|
||||
//! @brief A default copy constructor
|
||||
Rectangle(const Rectangle &) = default;
|
||||
|
||||
//! @brief A rectangle is assignable
|
||||
Rectangle &operator=(const Rectangle &) = default;
|
||||
//! @brief A rectangle is assignable
|
||||
Rectangle &operator=(const Rectangle &) = default;
|
||||
|
||||
//! @brief A default destructor
|
||||
~Rectangle() override = default;
|
||||
//! @brief A default destructor
|
||||
~Rectangle() override = default;
|
||||
|
||||
//! @return the dimensions of the rectangle
|
||||
const Vector2 &getDimensions(void);
|
||||
//! @return the dimensions of the rectangle
|
||||
const Vector2 &getDimensions(void);
|
||||
|
||||
//! @return the width of the rectangle
|
||||
float getWidth(void) const;
|
||||
//! @return the width of the rectangle
|
||||
float getWidth(void) const;
|
||||
|
||||
//! @return the height of the rectangle
|
||||
float getHeight(void) const;
|
||||
//! @return the height of the rectangle
|
||||
float getHeight(void) const;
|
||||
|
||||
//! @brief set dimensions
|
||||
Rectangle &setDimensions(const Vector2 &dimensions);
|
||||
//! @brief set dimensions
|
||||
Rectangle &setDimensions(const Vector2 &dimensions);
|
||||
|
||||
//! @brief increment width of the rectangle
|
||||
//! @param width incrementer
|
||||
Rectangle &incrementWidth(float width);
|
||||
//! @brief increment width of the rectangle
|
||||
//! @param width incrementer
|
||||
Rectangle &incrementWidth(float width);
|
||||
|
||||
//! @brief increment height of the rectangle
|
||||
//! @param height incrementer
|
||||
Rectangle &incrementHeight(float height);
|
||||
//! @brief increment height of the rectangle
|
||||
//! @param height incrementer
|
||||
Rectangle &incrementHeight(float height);
|
||||
|
||||
//! @brief set rectangle's height
|
||||
//! @param height height of the rectangle
|
||||
Rectangle &setHeight(float height);
|
||||
//! @brief set rectangle's height
|
||||
//! @param height height of the rectangle
|
||||
Rectangle &setHeight(float height);
|
||||
|
||||
//! @brief set rectangle's width
|
||||
//! @param width width of the rectangle
|
||||
Rectangle &setWidth(float width);
|
||||
//! @brief set rectangle's width
|
||||
//! @param width width of the rectangle
|
||||
Rectangle &setWidth(float width);
|
||||
|
||||
//! @brief set dimensions
|
||||
Rectangle &setDimensions(float x, float y);
|
||||
//! @brief set dimensions
|
||||
Rectangle &setDimensions(float x, float y);
|
||||
|
||||
//! @brief Draw point on window
|
||||
virtual void drawOn(RAY::Window &) override;
|
||||
//! @brief Draw point on image
|
||||
virtual void drawOn(RAY::Image &image) override;
|
||||
//! @brief Draw point on window
|
||||
virtual void drawOn(RAY::Window &) override;
|
||||
|
||||
protected:
|
||||
//! @brief Diemnsions of the rectangle
|
||||
Vector2 _dimensions;
|
||||
|
||||
INTERNAL:
|
||||
operator ::Rectangle() const;
|
||||
//! @brief Draw point on image
|
||||
virtual void drawOn(RAY::Image &image) override;
|
||||
|
||||
protected:
|
||||
//! @brief Diemnsions of the rectangle
|
||||
Vector2 _dimensions;
|
||||
|
||||
INTERNAL:
|
||||
|
||||
operator ::Rectangle() const;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -11,14 +11,20 @@
|
||||
namespace RAY::Drawables
|
||||
{
|
||||
|
||||
ADrawable2D::ADrawable2D(const Vector2 &position, const RAY::Color &color) :
|
||||
_position(position), _color(color)
|
||||
ADrawable2D::ADrawable2D(const Vector2 &position, const RAY::Color &color, float scale, float rotation) :
|
||||
_rotation(rotation),
|
||||
_scale(scale),
|
||||
_position(position),
|
||||
_color(color)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ADrawable2D::ADrawable2D(int x, int y, const RAY::Color &color) :
|
||||
_position(static_cast<float>(x), static_cast<float>(y)), _color(color)
|
||||
ADrawable2D::ADrawable2D(int x, int y, const RAY::Color &color, float scale, float rotation) :
|
||||
_rotation(rotation),
|
||||
_scale(scale),
|
||||
_position(static_cast<float>(x), static_cast<float>(y)),
|
||||
_color(color)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -50,4 +56,14 @@ namespace RAY::Drawables
|
||||
this->_color = color;
|
||||
return *this;
|
||||
}
|
||||
|
||||
float ADrawable2D::getScale() const
|
||||
{
|
||||
return this->_scale;
|
||||
}
|
||||
|
||||
void ADrawable2D::setScale(float scale)
|
||||
{
|
||||
this->_scale = scale;
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,12 @@ namespace RAY::Drawables {
|
||||
//! @brief ADrawable constructor
|
||||
//! @param poition position of top-left point
|
||||
//! @param Color Color of the color
|
||||
ADrawable2D(const Vector2 &position, const RAY::Color &color);
|
||||
ADrawable2D(const Vector2 &position, const RAY::Color &color, float scale = 1, float rotation = 0);
|
||||
//! @brief ADrawable constructor
|
||||
//! @param x x-position of top-left point
|
||||
//! @param y y-position of top-left point
|
||||
//! @param Color Color of the color
|
||||
ADrawable2D(int x, int y, const RAY::Color &color);
|
||||
ADrawable2D(int x, int y, const RAY::Color &color, float scale = 1, float rotation = 0);
|
||||
|
||||
//! @brief A default copy constructor
|
||||
ADrawable2D(const ADrawable2D &) = default;
|
||||
@@ -58,7 +58,16 @@ namespace RAY::Drawables {
|
||||
//! @brief Draw drawble on image
|
||||
virtual void drawOn(RAY::Image &image) = 0;
|
||||
|
||||
//! @brief scale getter
|
||||
float getScale() const;
|
||||
//! @brief scale setters
|
||||
void setScale(float scale);
|
||||
|
||||
protected:
|
||||
//! @brief rotation
|
||||
float _rotation = 0;
|
||||
//! @brief scale
|
||||
float _scale = 1;
|
||||
//! @brief Top-left position
|
||||
Vector2 _position;
|
||||
//! @brief Color of the ADrawable
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace RAY
|
||||
public:
|
||||
//! @brief Create an image, loading a file
|
||||
//! @param filename: path to file to load
|
||||
//! @param lonely: should be set to true if the entity's loaded data must be independant from others
|
||||
//! @param lonely: should be set to true if the entity's loaded data must be independent from others
|
||||
Image(const std::string &filename, bool lonely = false);
|
||||
|
||||
//! @brief A default copy constructor
|
||||
|
||||
@@ -14,11 +14,11 @@ namespace RAY {
|
||||
Cache<::Texture> Texture::_texturesCache(LoadTexture, UnloadTexture);
|
||||
|
||||
Texture::Texture()
|
||||
: Rectangle(Vector2(0, 0), Vector2(0, 0), WHITE)
|
||||
: Rectangle(Vector2(0, 0), Vector2(0, 0), WHITE, 0, 0)
|
||||
{}
|
||||
|
||||
Texture::Texture(const std::string &filename, bool lonely):
|
||||
Rectangle(Vector2(0, 0), Vector2(0, 0), WHITE),
|
||||
Texture::Texture(const std::string &filename, bool lonely, float scale, float rotation):
|
||||
Rectangle(Vector2(0, 0), Vector2(0, 0), WHITE, scale, rotation),
|
||||
_texture(_texturesCache.fetch(filename, lonely)),
|
||||
_resourcePath(filename)
|
||||
{
|
||||
@@ -56,9 +56,12 @@ namespace RAY {
|
||||
{
|
||||
if (!this->_texture)
|
||||
return;
|
||||
DrawTextureEx(*this, this->_position, this->_rotation, this->_scale, this->_color);
|
||||
}
|
||||
|
||||
float scale = this->_dimensions.x / this->_texture->width;
|
||||
|
||||
DrawTextureEx(*this, this->_position, 0, scale, this->_color);
|
||||
void Texture::unload()
|
||||
{
|
||||
this->_texture = nullptr;
|
||||
this->_resourcePath = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace RAY
|
||||
//! @brief Create an texture, loading a file
|
||||
//! @param filename: path to file to load
|
||||
//! @param lonely: should be set to true if the entity's loaded data must be independant from others
|
||||
explicit Texture(const std::string &filename, bool lonely = false);
|
||||
explicit Texture(const std::string &filename, bool lonely = false, float scale = 1, float rotation = 0);
|
||||
|
||||
//! @brief Create an empty texture
|
||||
Texture();
|
||||
@@ -44,6 +44,9 @@ namespace RAY
|
||||
//! @brief Load texture from file, lets one use one entity for multiple files
|
||||
Texture &use(const std::string &filename);
|
||||
|
||||
//! @brief Unload the current texture (calls to drawOn will no-op).
|
||||
void unload();
|
||||
|
||||
//! @return path of loaded texture
|
||||
const std::string &getResourcePath() const;
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Created by cbihan on 16/06/2021.
|
||||
//
|
||||
|
||||
#include "AMesh.hpp"
|
||||
|
||||
namespace RAY::Mesh
|
||||
{
|
||||
std::shared_ptr<::Mesh> RAY::Mesh::AMesh::getRaylibMesh() const
|
||||
{
|
||||
return this->_raylibMesh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by cbihan on 16/06/2021.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <raylib.h>
|
||||
#include <memory>
|
||||
|
||||
namespace RAY::Mesh
|
||||
{
|
||||
class AMesh
|
||||
{
|
||||
protected:
|
||||
std::shared_ptr<::Mesh> _raylibMesh;
|
||||
|
||||
INTERNAL:
|
||||
//! @brief getter for _raylibMesh
|
||||
std::shared_ptr<::Mesh> getRaylibMesh() const;
|
||||
|
||||
public:
|
||||
|
||||
//! @brief dtor
|
||||
virtual ~AMesh() = default;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by cbihan on 16/06/2021.
|
||||
//
|
||||
|
||||
#include "MeshSphere.hpp"
|
||||
|
||||
namespace RAY::Mesh
|
||||
{
|
||||
MeshSphere::MeshSphere(float radius_arg, int rings_arg, int slices_arg) :
|
||||
radius(radius_arg),
|
||||
rings(rings_arg),
|
||||
slices(slices_arg)
|
||||
{
|
||||
this->_raylibMesh = std::make_shared<::Mesh>(GenMeshSphere(radius_arg, rings_arg, slices_arg));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Created by cbihan on 16/06/2021.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <raylib.h>
|
||||
#include "AMesh.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace RAY::Mesh
|
||||
{
|
||||
class MeshSphere : public AMesh
|
||||
{
|
||||
public:
|
||||
//! @brief radius
|
||||
float radius;
|
||||
//! @brief rings
|
||||
int rings;
|
||||
//! @brief slices
|
||||
int slices;
|
||||
|
||||
|
||||
//! @brief ctor
|
||||
MeshSphere(float radius_arg, int rings_arg, int slices_arg);
|
||||
//! @brief copy ctor
|
||||
MeshSphere(const MeshSphere &) = default;
|
||||
//! @brief dtor
|
||||
~MeshSphere() = default;
|
||||
//! @brief assignment operator
|
||||
MeshSphere &operator=(const MeshSphere &) = default;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -32,10 +32,20 @@ namespace RAY::Drawables::Drawables3D
|
||||
this->setTextureToMaterial(texture->first, texture->second);
|
||||
}
|
||||
|
||||
Model::Model(const Mesh &mesh)
|
||||
: ADrawable3D({0, 0, 0}, WHITE),
|
||||
_model(std::make_shared<::Model>(LoadModelFromMesh(mesh)))
|
||||
Model::Model(const Mesh::AMesh &mesh,
|
||||
std::optional<std::pair<MaterialType, std::string>> texture,
|
||||
const RAY::Vector3 &scale,
|
||||
const RAY::Vector3 &position,
|
||||
const RAY::Vector3 &rotationAxis,
|
||||
float rotationAngle)
|
||||
: ADrawable3D(position, WHITE),
|
||||
_model(std::make_shared<::Model>(LoadModelFromMesh(*mesh.getRaylibMesh()))),
|
||||
_rotationAxis(rotationAxis),
|
||||
_rotationAngle(rotationAngle),
|
||||
_scale(scale)
|
||||
{
|
||||
if (texture.has_value())
|
||||
this->setTextureToMaterial(texture->first, texture->second);
|
||||
}
|
||||
|
||||
bool Model::unloadKeepMeshes()
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "Model/ModelAnimation.hpp"
|
||||
#include "Shaders/Shaders.hpp"
|
||||
#include <raylib.h>
|
||||
#include "Meshes/AMesh.hpp"
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include "Utils/Cache.hpp"
|
||||
@@ -36,7 +37,12 @@ namespace RAY::Drawables::Drawables3D {
|
||||
|
||||
//! @brief Create an model, loading a file
|
||||
//! @param mesh: mesh to load
|
||||
Model(const Mesh &mesh);
|
||||
Model(const Mesh::AMesh &mesh,
|
||||
std::optional<std::pair<MaterialType, std::string>> texture = std::nullopt,
|
||||
const RAY::Vector3 &scale = RAY::Vector3(1, 1, 1),
|
||||
const RAY::Vector3 &position = {0, 0, 0},
|
||||
const RAY::Vector3 &rotationAxis = RAY::Vector3(0, 1, 0),
|
||||
float rotationAngle = 0);
|
||||
|
||||
//! @brief A copy constructor
|
||||
Model(const Model &model) = default;
|
||||
|
||||
@@ -12,11 +12,12 @@ namespace RAY
|
||||
Cache<::Shader> Shader::_shadersCache(LoadShader, UnloadShader);
|
||||
|
||||
|
||||
Shader::Shader(const std::string &vertexFile, const std::string &fragmentFile)
|
||||
Shader::Shader(const std::string &vertexFile, const std::string &fragmentFile, bool lonely)
|
||||
: _vertexFile(vertexFile),
|
||||
_fragmentFile(fragmentFile),
|
||||
_rayLibShader(_shadersCache.fetch(vertexFile, fragmentFile))
|
||||
_rayLibShader(_shadersCache.fetch(vertexFile, fragmentFile, lonely))
|
||||
{
|
||||
this->_rayLibShader->locs[SHADER_LOC_MAP_EMISSION] = GetShaderLocation(*this->_rayLibShader, "mask");
|
||||
}
|
||||
|
||||
const std::shared_ptr<::Shader> &Shader::getShaderPtr() const
|
||||
@@ -37,6 +38,19 @@ namespace RAY
|
||||
SetShaderValue(*this->_rayLibShader, this->_shaderIndexVars[varName], &value, SHADER_UNIFORM_FLOAT);
|
||||
}
|
||||
|
||||
void Shader::setShaderUniformVar(const std::string &varName, int value)
|
||||
{
|
||||
if (this->_shaderIndexVars.find(varName) == this->_shaderIndexVars.end()) {
|
||||
int varShaderIndex = GetShaderLocation(*this->_rayLibShader, varName.c_str());
|
||||
|
||||
if (varShaderIndex < 0) {
|
||||
throw Exception::WrongInputError("The loaded shader doesn't have a variable called: " + varName);
|
||||
}
|
||||
this->_shaderIndexVars[varName] = varShaderIndex;
|
||||
}
|
||||
SetShaderValue(*this->_rayLibShader, this->_shaderIndexVars[varName], &value, SHADER_UNIFORM_INT);
|
||||
}
|
||||
|
||||
void Shader::BeginUsingCustomShader(Shader &shader)
|
||||
{
|
||||
BeginShaderMode(*shader.getShaderPtr());
|
||||
|
||||
@@ -42,8 +42,14 @@ namespace RAY
|
||||
//! @note Throw if the var is not found
|
||||
void setShaderUniformVar(const std::string &varName, float value);
|
||||
|
||||
//! @brief The set var for float values
|
||||
//! @note Throw if the var is not found
|
||||
void setShaderUniformVar(const std::string &varName, int value);
|
||||
|
||||
void setLocation(::ShaderLocationIndex, const std::string &name);
|
||||
|
||||
//! @brief ctor if no vertexfile in needed set it to nullptr
|
||||
Shader(const std::string &vertexFile, const std::string &fragmentFile);
|
||||
Shader(const std::string &vertexFile, const std::string &fragmentFile, bool lonely = false);
|
||||
//! @brief Default copy ctor
|
||||
Shader(const Shader &) = default;
|
||||
//! @brief dtor
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace RAY {
|
||||
this->_cache.emplace(path, std::vector<std::shared_ptr<T>>());
|
||||
std::vector<std::shared_ptr<T>> &matchingDataVector = this->_cache.at(path);
|
||||
|
||||
if (matchingDataVector.size()) {
|
||||
if (!matchingDataVector.empty()) {
|
||||
for (std::shared_ptr<T> &i: matchingDataVector) {
|
||||
if (!lonely)
|
||||
return i;
|
||||
@@ -117,23 +117,34 @@ namespace RAY {
|
||||
_dataLoader(std::move(dataLoader)), _dataUnloader(std::move(dataUnloader))
|
||||
{};
|
||||
|
||||
std::shared_ptr<::Shader> fetch(const std::string &vertexFile, const std::string &fragmentFile)
|
||||
std::shared_ptr<::Shader> fetch(const std::string &vertexFile, const std::string &fragmentFile, bool lonely = false)
|
||||
{
|
||||
const std::string index = vertexFile + fragmentFile;
|
||||
|
||||
if (vertexFile.empty() && fragmentFile.empty()) {
|
||||
throw RAY::Exception::WrongInputError();
|
||||
}
|
||||
if (this->_cache.find(index) != this->_cache.end())
|
||||
return this->_cache[index];
|
||||
if (!this->_cache.contains(index)) {
|
||||
this->_cache.emplace(index, std::vector<std::shared_ptr<::Shader>>());
|
||||
}
|
||||
std::vector<std::shared_ptr<::Shader>> &matchingDataVector = this->_cache.at(index);
|
||||
|
||||
this->_cache.emplace(index, std::shared_ptr<::Shader>(
|
||||
if (!matchingDataVector.empty()) {
|
||||
for (std::shared_ptr<::Shader> &i: matchingDataVector) {
|
||||
if (!lonely)
|
||||
return i;
|
||||
if (lonely && i.use_count() == 1)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
matchingDataVector.emplace_back(std::shared_ptr<::Shader>(
|
||||
new ::Shader(
|
||||
this->_dataLoader(vertexFile.empty() ? nullptr : vertexFile.c_str(), fragmentFile.c_str())),
|
||||
[this](::Shader *p) {
|
||||
this->_dataUnloader(*p);
|
||||
}));
|
||||
return this->_cache[index];
|
||||
return matchingDataVector.back();
|
||||
};
|
||||
private:
|
||||
//! @brief function to call to load data
|
||||
@@ -143,6 +154,6 @@ namespace RAY {
|
||||
std::function<void(::Shader)> _dataUnloader;
|
||||
|
||||
//! @brief map storing shared ptr of caches
|
||||
std::unordered_map<std::string, std::shared_ptr<::Shader>> _cache;
|
||||
std::unordered_map<std::string, std::vector<std::shared_ptr<::Shader>>> _cache;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user