afegit efecte de "colp" quan el jugador es "alcanssat" per un globo
This commit is contained in:
@@ -9,7 +9,7 @@ game.play_area.rect.h 200 # Alto de la zona jugable
|
|||||||
game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
|
game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
|
||||||
game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
|
game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
|
||||||
game.hit_stop true # Indica si debe haber un paro cuando el jugador es golpeado por un globo
|
game.hit_stop true # Indica si debe haber un paro cuando el jugador es golpeado por un globo
|
||||||
game.hit_stop_ms 300 # Cantidad de milisegundos que dura el hit_stop
|
game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop
|
||||||
|
|
||||||
## --- FADE ---
|
## --- FADE ---
|
||||||
fade.color 1F2B30 # Color hexadecimal para el efecto de fundido
|
fade.color 1F2B30 # Color hexadecimal para el efecto de fundido
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ game.play_area.rect.h 216 # Alto de la zona jugable
|
|||||||
game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
|
game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
|
||||||
game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
|
game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
|
||||||
game.hit_stop true # Indica si debe haber un paro cuando el jugador es golpeado por un globo
|
game.hit_stop true # Indica si debe haber un paro cuando el jugador es golpeado por un globo
|
||||||
game.hit_stop_ms 300 # Cantidad de milisegundos que dura el hit_stop
|
game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop
|
||||||
|
|
||||||
## --- FADE ---
|
## --- FADE ---
|
||||||
fade.color 1F2B30 # Color hexadecimal para el efecto de fundido
|
fade.color 1F2B30 # Color hexadecimal para el efecto de fundido
|
||||||
|
|||||||
BIN
data/gfx/player/hit.png
Normal file
BIN
data/gfx/player/hit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 312 B |
@@ -60,6 +60,16 @@ void Audio::pauseMusic() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Continua la música pausada
|
||||||
|
void Audio::resumeMusic() {
|
||||||
|
if (music_enabled_ && music_.state == MusicState::PAUSED) {
|
||||||
|
#ifndef NO_AUDIO
|
||||||
|
JA_ResumeMusic();
|
||||||
|
#endif
|
||||||
|
music_.state = MusicState::PLAYING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Detiene la música
|
// Detiene la música
|
||||||
void Audio::stopMusic() {
|
void Audio::stopMusic() {
|
||||||
if (music_enabled_) {
|
if (music_enabled_) {
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ class Audio {
|
|||||||
auto operator=(const Audio &) -> Audio & = delete; // Evitar asignación
|
auto operator=(const Audio &) -> Audio & = delete; // Evitar asignación
|
||||||
|
|
||||||
// --- Método principal ---
|
// --- Método principal ---
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
// --- Control de Música ---
|
// --- Control de Música ---
|
||||||
void playMusic(const std::string &name, int loop = -1); // Reproducir música en bucle
|
void playMusic(const std::string &name, int loop = -1); // Reproducir música en bucle
|
||||||
void pauseMusic(); // Pausar reproducción de música
|
void pauseMusic(); // Pausar reproducción de música
|
||||||
|
void resumeMusic(); // Continua la música pausada
|
||||||
void stopMusic(); // Detener completamente la música
|
void stopMusic(); // Detener completamente la música
|
||||||
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
|
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
|
||||||
|
|
||||||
|
|||||||
@@ -402,10 +402,13 @@ void Director::setFileList() {
|
|||||||
Asset::get()->add(PREFIX + "/data/gfx/player/player2_coffee2.pal", AssetType::PALETTE);
|
Asset::get()->add(PREFIX + "/data/gfx/player/player2_coffee2.pal", AssetType::PALETTE);
|
||||||
Asset::get()->add(PREFIX + "/data/gfx/player/player2_invencible.pal", AssetType::PALETTE);
|
Asset::get()->add(PREFIX + "/data/gfx/player/player2_invencible.pal", AssetType::PALETTE);
|
||||||
Asset::get()->add(PREFIX + "/data/gfx/player/player2_power.png", AssetType::BITMAP);
|
Asset::get()->add(PREFIX + "/data/gfx/player/player2_power.png", AssetType::BITMAP);
|
||||||
|
|
||||||
// Animaciones del jugador
|
// Animaciones del jugador
|
||||||
Asset::get()->add(PREFIX + "/data/gfx/player/player.ani", AssetType::ANIMATION);
|
Asset::get()->add(PREFIX + "/data/gfx/player/player.ani", AssetType::ANIMATION);
|
||||||
Asset::get()->add(PREFIX + "/data/gfx/player/player_power.ani", AssetType::ANIMATION);
|
Asset::get()->add(PREFIX + "/data/gfx/player/player_power.ani", AssetType::ANIMATION);
|
||||||
|
|
||||||
|
// Texturas - Golpe del jugador
|
||||||
|
Asset::get()->add(PREFIX + "/data/gfx/player/hit.png", AssetType::BITMAP);
|
||||||
|
|
||||||
// Fuentes de texto
|
// Fuentes de texto
|
||||||
Asset::get()->add(PREFIX + "/data/font/8bithud.png", AssetType::BITMAP);
|
Asset::get()->add(PREFIX + "/data/font/8bithud.png", AssetType::BITMAP);
|
||||||
|
|||||||
61
source/hit.h
Normal file
61
source/hit.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||||
|
|
||||||
|
#include <memory> // Para std::unique_ptr y std::shared_ptr
|
||||||
|
|
||||||
|
#include "sprite.h" // Para Sprite
|
||||||
|
#include "texture.h" // Para Texture
|
||||||
|
|
||||||
|
// Estructura que representa una colisión o impacto visual
|
||||||
|
struct Hit {
|
||||||
|
private:
|
||||||
|
// Indica si el Hit está activo o no
|
||||||
|
bool enabled{false};
|
||||||
|
|
||||||
|
// Sprite asociado al Hit, gestionado con un puntero único
|
||||||
|
std::unique_ptr<Sprite> sprite;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Elimina el constructor por defecto para obligar a pasar una textura
|
||||||
|
Hit() = delete;
|
||||||
|
|
||||||
|
// Constructor que obliga a pasar una textura compartida para crear el Sprite
|
||||||
|
// Esto evita que se pueda crear un Hit sin recursos gráficos válidos
|
||||||
|
explicit Hit(std::shared_ptr<Texture> texture)
|
||||||
|
: sprite(std::make_unique<Sprite>(texture)) {}
|
||||||
|
|
||||||
|
// Establece la posición del Sprite en el espacio
|
||||||
|
void setPos(SDL_FPoint position) {
|
||||||
|
SDL_FPoint centered_position = {position.x - (sprite->getWidth() / 2), position.y - (sprite->getHeight() / 2)};
|
||||||
|
sprite->setPosition(centered_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activa o desactiva el Hit
|
||||||
|
void enable(bool value) {
|
||||||
|
enabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consulta si el Hit está activo
|
||||||
|
bool isEnabled() const {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea un "Hit" en la posición especificada
|
||||||
|
void create(SDL_FPoint position) {
|
||||||
|
setPos(position);
|
||||||
|
enable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dibuja el hit
|
||||||
|
void render() {
|
||||||
|
if (enabled) {
|
||||||
|
sprite->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deshabilita el hit
|
||||||
|
void disable() {
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <functional> // Para function
|
#include <functional> // Para function
|
||||||
#include <iostream> // Para std::cout, std::endl
|
#include <iostream> // Para std::cout, std::endl
|
||||||
#include <iterator> // Para distance, size
|
#include <iterator> // Para distance, size
|
||||||
|
#include <memory> // Para std::make_unique
|
||||||
|
|
||||||
#include "asset.h" // Para Asset
|
#include "asset.h" // Para Asset
|
||||||
#include "audio.h" // Para Audio
|
#include "audio.h" // Para Audio
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
#include "fade.h" // Para Fade, FadeType, FadeMode
|
#include "fade.h" // Para Fade, FadeType, FadeMode
|
||||||
#include "global_events.h" // Para check
|
#include "global_events.h" // Para check
|
||||||
#include "global_inputs.h" // Para check
|
#include "global_inputs.h" // Para check
|
||||||
|
#include "hit.h" // Para Hit
|
||||||
#include "input.h" // Para InputAction, Input, INPUT_DO_NOT_ALLOW_REPEAT, INPUT_ALLOW_REPEAT, InputDevice
|
#include "input.h" // Para InputAction, Input, INPUT_DO_NOT_ALLOW_REPEAT, INPUT_ALLOW_REPEAT, InputDevice
|
||||||
#include "item.h" // Para Item, ItemType
|
#include "item.h" // Para Item, ItemType
|
||||||
#include "lang.h" // Para getText
|
#include "lang.h" // Para getText
|
||||||
@@ -49,7 +51,8 @@ Game::Game(int player_id, int current_stage, bool demo)
|
|||||||
fade_in_(std::make_unique<Fade>()),
|
fade_in_(std::make_unique<Fade>()),
|
||||||
fade_out_(std::make_unique<Fade>()),
|
fade_out_(std::make_unique<Fade>()),
|
||||||
balloon_manager_(std::make_unique<BalloonManager>()),
|
balloon_manager_(std::make_unique<BalloonManager>()),
|
||||||
tabe_(std::make_unique<Tabe>()) {
|
tabe_(std::make_unique<Tabe>()),
|
||||||
|
hit_(Hit(Resource::get()->getTexture("hit.png"))) {
|
||||||
// Pasa variables
|
// Pasa variables
|
||||||
demo_.enabled = demo;
|
demo_.enabled = demo;
|
||||||
|
|
||||||
@@ -213,7 +216,7 @@ void Game::updatePlayers() {
|
|||||||
}
|
}
|
||||||
// En caso contrario, el jugador ha sido golpeado por un globo activo
|
// En caso contrario, el jugador ha sido golpeado por un globo activo
|
||||||
else {
|
else {
|
||||||
handlePlayerCollision(player);
|
handlePlayerCollision(player, balloon);
|
||||||
|
|
||||||
if (demo_.enabled && allPlayersAreNotPlaying()) {
|
if (demo_.enabled && allPlayersAreNotPlaying()) {
|
||||||
fade_out_->setType(FadeType::RANDOM_SQUARE);
|
fade_out_->setType(FadeType::RANDOM_SQUARE);
|
||||||
@@ -830,7 +833,7 @@ void Game::renderPathSprites() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Acciones a realizar cuando el jugador colisiona con un globo
|
// Acciones a realizar cuando el jugador colisiona con un globo
|
||||||
void Game::handlePlayerCollision(std::shared_ptr<Player> &player) {
|
void Game::handlePlayerCollision(std::shared_ptr<Player> &player, std::shared_ptr<Balloon> &balloon) {
|
||||||
if (!player->isPlaying() || player->isInvulnerable()) {
|
if (!player->isPlaying() || player->isInvulnerable()) {
|
||||||
// Si no está jugando o tiene inmunidad, no hace nada
|
// Si no está jugando o tiene inmunidad, no hace nada
|
||||||
return;
|
return;
|
||||||
@@ -847,15 +850,19 @@ void Game::handlePlayerCollision(std::shared_ptr<Player> &player) {
|
|||||||
// Si no tiene cafes, muere
|
// Si no tiene cafes, muere
|
||||||
playSound("player_collision.wav");
|
playSound("player_collision.wav");
|
||||||
if (param.game.hit_stop) {
|
if (param.game.hit_stop) {
|
||||||
|
pauseMusic();
|
||||||
|
auto position = getCollisionPoint(player->getCollider(), balloon->getCollider());
|
||||||
|
putHitOnScreen(position);
|
||||||
SDL_Delay(param.game.hit_stop_ms);
|
SDL_Delay(param.game.hit_stop_ms);
|
||||||
|
hit_.disable();
|
||||||
|
resumeMusic();
|
||||||
}
|
}
|
||||||
screen_->shake();
|
screen_->shake();
|
||||||
playSound("voice_no.wav");
|
playSound("voice_no.wav");
|
||||||
player->setPlayingState(PlayerState::ROLLING);
|
player->setPlayingState(PlayerState::ROLLING);
|
||||||
players_to_reorder_.push_back(player);
|
players_to_reorder_.push_back(player);
|
||||||
if (allPlayersAreNotPlaying()) {
|
if (allPlayersAreNotPlaying()) {
|
||||||
// No se puede subir poder de fase si no hay nadie jugando
|
Stage::power_can_be_added = false; // No se puede subir poder de fase si no hay nadie jugando
|
||||||
Stage::power_can_be_added = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -979,6 +986,7 @@ void Game::fillCanvas() {
|
|||||||
// Dibuja los objetos
|
// Dibuja los objetos
|
||||||
background_->render();
|
background_->render();
|
||||||
renderPlayers();
|
renderPlayers();
|
||||||
|
renderHit();
|
||||||
renderSmartSprites();
|
renderSmartSprites();
|
||||||
renderItems();
|
renderItems();
|
||||||
balloon_manager_->render();
|
balloon_manager_->render();
|
||||||
@@ -1598,6 +1606,16 @@ void Game::playMusic() {
|
|||||||
Audio::get()->playMusic("playing.ogg");
|
Audio::get()->playMusic("playing.ogg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pausa la música
|
||||||
|
void Game::pauseMusic() {
|
||||||
|
Audio::get()->pauseMusic();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retoma la música que eestaba pausada
|
||||||
|
void Game::resumeMusic() {
|
||||||
|
Audio::get()->resumeMusic();
|
||||||
|
}
|
||||||
|
|
||||||
// Detiene la música
|
// Detiene la música
|
||||||
void Game::stopMusic() const {
|
void Game::stopMusic() const {
|
||||||
if (!demo_.enabled) {
|
if (!demo_.enabled) {
|
||||||
@@ -1823,6 +1841,19 @@ void Game::checkServiceMenu() {
|
|||||||
service_menu_was_active_ = service_menu_is_active;
|
service_menu_was_active_ = service_menu_is_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dibuja el golpe que recibe el jugador al impactarle un globo
|
||||||
|
void Game::renderHit() {
|
||||||
|
hit_.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coloca el Hit en pantalla obligando a hacer un renderizado
|
||||||
|
void Game::putHitOnScreen(SDL_FPoint position) {
|
||||||
|
hit_.create(position);
|
||||||
|
fillCanvas();
|
||||||
|
render();
|
||||||
|
hit_.disable();
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
// Comprueba los eventos en el modo DEBUG
|
// Comprueba los eventos en el modo DEBUG
|
||||||
void Game::checkDebugEvents(const SDL_Event &event) {
|
void Game::checkDebugEvents(const SDL_Event &event) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <string> // Para string
|
#include <string> // Para string
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
|
#include "hit.h" // Para Hit
|
||||||
#include "item.h" // Para Item, ItemType
|
#include "item.h" // Para Item, ItemType
|
||||||
#include "manage_hiscore_table.h" // Para HiScoreEntry
|
#include "manage_hiscore_table.h" // Para HiScoreEntry
|
||||||
#include "options.h" // Para SettingsOptions, settings
|
#include "options.h" // Para SettingsOptions, settings
|
||||||
@@ -147,6 +148,7 @@ class Game {
|
|||||||
int menace_threshold_ = 0; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos
|
int menace_threshold_ = 0; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos
|
||||||
GameState state_ = GameState::FADE_IN; // Estado
|
GameState state_ = GameState::FADE_IN; // Estado
|
||||||
std::vector<std::shared_ptr<Player>> players_to_reorder_;
|
std::vector<std::shared_ptr<Player>> players_to_reorder_;
|
||||||
|
Hit hit_; // Para representar colisiones en pantalla
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
bool auto_pop_balloons_ = false; // Si es true, incrementa automaticamente los globos explotados
|
bool auto_pop_balloons_ = false; // Si es true, incrementa automaticamente los globos explotados
|
||||||
@@ -186,9 +188,9 @@ class Game {
|
|||||||
auto allPlayersAreNotPlaying() -> bool; // Verifica si ningún jugador está activo
|
auto allPlayersAreNotPlaying() -> bool; // Verifica si ningún jugador está activo
|
||||||
|
|
||||||
// --- Colisiones de jugadores ---
|
// --- Colisiones de jugadores ---
|
||||||
void handlePlayerCollision(std::shared_ptr<Player> &player); // Procesa colisión de jugador con globo
|
void handlePlayerCollision(std::shared_ptr<Player> &player, std::shared_ptr<Balloon> &balloon); // Procesa colisión de jugador con globo
|
||||||
auto checkPlayerBalloonCollision(std::shared_ptr<Player> &player) -> std::shared_ptr<Balloon>; // Detecta colisión jugador-globo
|
auto checkPlayerBalloonCollision(std::shared_ptr<Player> &player) -> std::shared_ptr<Balloon>; // Detecta colisión jugador-globo
|
||||||
void checkPlayerItemCollision(std::shared_ptr<Player> &player); // Detecta colisión jugador-ítem
|
void checkPlayerItemCollision(std::shared_ptr<Player> &player); // Detecta colisión jugador-ítem
|
||||||
|
|
||||||
// --- Sistema de entrada (input) ---
|
// --- Sistema de entrada (input) ---
|
||||||
void checkInput(); // Gestiona toda la entrada durante el juego
|
void checkInput(); // Gestiona toda la entrada durante el juego
|
||||||
@@ -284,11 +286,16 @@ class Game {
|
|||||||
// --- Sistema de audio ---
|
// --- Sistema de audio ---
|
||||||
static void playMusic(); // Reproduce la música de fondo
|
static void playMusic(); // Reproduce la música de fondo
|
||||||
void stopMusic() const; // Detiene la reproducción de música
|
void stopMusic() const; // Detiene la reproducción de música
|
||||||
|
void pauseMusic(); // Pausa la música
|
||||||
|
void resumeMusic(); // Retoma la música que eestaba pausada
|
||||||
void playSound(const std::string &name) const; // Reproduce un efecto de sonido específico
|
void playSound(const std::string &name) const; // Reproduce un efecto de sonido específico
|
||||||
|
|
||||||
// --- Utilidades y servicios ---
|
// --- Utilidades y servicios ---
|
||||||
void checkServiceMenu(); // Verifica si el menú de servicio está activo
|
void checkServiceMenu(); // Verifica si el menú de servicio está activo
|
||||||
|
|
||||||
|
void renderHit(); // Dibuja el golpe que recibe el jugador al impactarle un globo
|
||||||
|
void putHitOnScreen(SDL_FPoint position); // Coloca el Hit en pantalla obligando a hacer un renderizado
|
||||||
|
|
||||||
// SISTEMA DE GRABACIÓN (CONDICIONAL)
|
// SISTEMA DE GRABACIÓN (CONDICIONAL)
|
||||||
#ifdef RECORDING
|
#ifdef RECORDING
|
||||||
void updateRecording(); // Actualiza variables durante modo de grabación
|
void updateRecording(); // Actualiza variables durante modo de grabación
|
||||||
|
|||||||
@@ -24,6 +24,25 @@ auto distanceSquared(int x1, int y1, int x2, int y2) -> double {
|
|||||||
return DELTA_X * DELTA_X + DELTA_Y * DELTA_Y;
|
return DELTA_X * DELTA_X + DELTA_Y * DELTA_Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Obtiene el punto de colisión entre dos circulos
|
||||||
|
auto getCollisionPoint(const Circle &a, const Circle &b) -> SDL_FPoint {
|
||||||
|
float dx = b.x - a.x;
|
||||||
|
float dy = b.y - a.y;
|
||||||
|
float dist = std::sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
// Normaliza el vector
|
||||||
|
float nx = dx / dist;
|
||||||
|
float ny = dy / dist;
|
||||||
|
|
||||||
|
// Punto en el borde del círculo A hacia B
|
||||||
|
SDL_FPoint contact;
|
||||||
|
contact.x = a.x + nx * a.r;
|
||||||
|
contact.y = a.y + ny * a.r;
|
||||||
|
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Detector de colisiones entre dos circulos
|
// Detector de colisiones entre dos circulos
|
||||||
auto checkCollision(const Circle &a, const Circle &b) -> bool {
|
auto checkCollision(const Circle &a, const Circle &b) -> bool {
|
||||||
// Calcula el radio total al cuadrado
|
// Calcula el radio total al cuadrado
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ struct Zone {
|
|||||||
|
|
||||||
// Colisiones y geometría
|
// Colisiones y geometría
|
||||||
auto distanceSquared(int x1, int y1, int x2, int y2) -> double;
|
auto distanceSquared(int x1, int y1, int x2, int y2) -> double;
|
||||||
|
auto getCollisionPoint(const Circle &a, const Circle &b) -> SDL_FPoint;
|
||||||
auto checkCollision(const Circle &a, const Circle &b) -> bool;
|
auto checkCollision(const Circle &a, const Circle &b) -> bool;
|
||||||
auto checkCollision(const Circle &a, const SDL_FRect &b) -> bool;
|
auto checkCollision(const Circle &a, const SDL_FRect &b) -> bool;
|
||||||
auto checkCollision(const SDL_FRect &a, const SDL_FRect &b) -> bool;
|
auto checkCollision(const SDL_FRect &a, const SDL_FRect &b) -> bool;
|
||||||
|
|||||||
Reference in New Issue
Block a user