mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 06:18:44 +00:00
Created Boat bumper logic. Refactored logic for powering up / dpwn
YachtEventType: Added some new events, a generic power down event and a bumper_crash event for an affected boat GameState: Implemented boat bumper logic MessageFactory: Made new messages for powerdown and status effect ClientYacht: Had to create another powerDown functional interface to inform the race view controller when to turn off the icon RaceViewController/GameClient: Now waits for a message about powering down before turning off rather than waiting time client side #story[1293]
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package seng302.gameServer;
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import com.sun.corba.se.spi.activation.Server;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -59,6 +60,7 @@ public class GameState implements Runnable {
|
|||||||
//Powerup Constants
|
//Powerup Constants
|
||||||
private static final Integer VELOCITY_BOOST_MULTIPLIER = 2;
|
private static final Integer VELOCITY_BOOST_MULTIPLIER = 2;
|
||||||
private static final Integer HANDLING_BOOST_MULTIPLIER = 2;
|
private static final Integer HANDLING_BOOST_MULTIPLIER = 2;
|
||||||
|
private static final Long BUMPER_DISABLE_TIME = 5_000L;
|
||||||
|
|
||||||
private static Long previousUpdateTime;
|
private static Long previousUpdateTime;
|
||||||
public static Double windDirection;
|
public static Double windDirection;
|
||||||
@@ -279,7 +281,7 @@ public class GameState implements Runnable {
|
|||||||
spawnNewToken();
|
spawnNewToken();
|
||||||
notifyMessageListeners(MessageFactory.getRaceXML());
|
notifyMessageListeners(MessageFactory.getRaceXML());
|
||||||
}
|
}
|
||||||
}, 0, 60000);
|
}, 0, 15_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 29/08/17 wmu16 - This sort of update should be in game state
|
// TODO: 29/08/17 wmu16 - This sort of update should be in game state
|
||||||
@@ -351,13 +353,14 @@ public class GameState implements Runnable {
|
|||||||
* Randomly select a subset of tokensInPlay from a pre defined superset
|
* Randomly select a subset of tokensInPlay from a pre defined superset
|
||||||
* Broadasts a new race status message to show this update
|
* Broadasts a new race status message to show this update
|
||||||
*/
|
*/
|
||||||
public static void spawnNewToken() {
|
private void spawnNewToken() {
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
tokensInPlay.clear();
|
tokensInPlay.clear();
|
||||||
|
|
||||||
//Get a random token location with random type
|
//Get a random token location with random type
|
||||||
Token token = allTokens.get(random.nextInt(allTokens.size()));
|
Token token = allTokens.get(random.nextInt(allTokens.size()));
|
||||||
token.assignRandomType();
|
// token.assignRandomType();
|
||||||
|
token.assignType(TokenType.BUMPER);
|
||||||
|
|
||||||
logger.debug("Spawned token of type " + token.getTokenType());
|
logger.debug("Spawned token of type " + token.getTokenType());
|
||||||
|
|
||||||
@@ -383,8 +386,9 @@ public class GameState implements Runnable {
|
|||||||
updateVelocity(yacht);
|
updateVelocity(yacht);
|
||||||
yacht.runAutoPilot();
|
yacht.runAutoPilot();
|
||||||
yacht.updateLocation(timeInterval);
|
yacht.updateLocation(timeInterval);
|
||||||
|
preformTokenUpdates(
|
||||||
|
yacht); //This update must be done before collision. Sorry sorta hacky rn.
|
||||||
checkCollision(yacht);
|
checkCollision(yacht);
|
||||||
preformTokenUpdates(yacht); //This update must always be done lsat
|
|
||||||
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
|
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
|
||||||
checkForLegProgression(yacht);
|
checkForLegProgression(yacht);
|
||||||
raceFinished = false;
|
raceFinished = false;
|
||||||
@@ -405,38 +409,66 @@ public class GameState implements Runnable {
|
|||||||
*/
|
*/
|
||||||
private void preformTokenUpdates(ServerYacht yacht) {
|
private void preformTokenUpdates(ServerYacht yacht) {
|
||||||
checkTokenPickUp(yacht);
|
checkTokenPickUp(yacht);
|
||||||
|
checkPowerUpTimeout(yacht);
|
||||||
|
TokenType powerUp = yacht.getPowerUp();
|
||||||
|
|
||||||
if (yacht.getPowerUp() != null) {
|
if (powerUp != null) {
|
||||||
switch (yacht.getPowerUp()) {
|
switch (powerUp) {
|
||||||
case WIND_WALKER:
|
case WIND_WALKER:
|
||||||
windWalk(yacht);
|
windWalk(yacht);
|
||||||
break;
|
break;
|
||||||
case BUMPER:
|
case BUMPER:
|
||||||
|
ServerYacht collidedYacht = checkYachtCollision(yacht);
|
||||||
|
if (collidedYacht != null) {
|
||||||
|
yacht.powerDown();
|
||||||
|
boatTempShutDown(collidedYacht);
|
||||||
|
notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht));
|
||||||
|
notifyMessageListeners(
|
||||||
|
MessageFactory.makeStatusEffectMessage(yacht, powerUp));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPowerUpTimeout(yacht);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 23/09/17 wmu16 - This is a hacky way to have the boat power down. Need some sort of separation between token and status effect :/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the given boat for BUMPER_DISABLE_TIME ms.
|
||||||
|
*
|
||||||
|
* @param yacht The yacht to disable
|
||||||
|
*/
|
||||||
|
private void boatTempShutDown(ServerYacht yacht) {
|
||||||
|
yacht.setSpeedMultiplier(0);
|
||||||
|
Timer shutDownTimer = new Timer("Shutdown Timer");
|
||||||
|
shutDownTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
yacht.powerDown(); //Note this actually resets the boat to normal.
|
||||||
|
}
|
||||||
|
}, BUMPER_DISABLE_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks how long a powerup has been active for. If it has exceeded its timeout, it powers the
|
* Checks how long a powerup has been active for. If it has exceeded its timeout, it powers the
|
||||||
* yacht down. WARNING. Do not call if the yacht does not have an active power up. Check with
|
* yacht down.
|
||||||
* yacht.getPowerup != null first
|
|
||||||
*
|
*
|
||||||
* @param yacht The yacht to check to power down
|
* @param yacht The yacht to check to power down
|
||||||
*/
|
*/
|
||||||
private void checkPowerUpTimeout(ServerYacht yacht) {
|
private void checkPowerUpTimeout(ServerYacht yacht) {
|
||||||
if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp()
|
if (yacht.getPowerUp() != null) {
|
||||||
.getTimeout()) {
|
if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp()
|
||||||
yacht.powerDown();
|
.getTimeout()) {
|
||||||
String logMessage = yacht.getBoatName() + "'s power-up token expired";
|
String logMessage =
|
||||||
notifyMessageListeners(
|
yacht.getBoatName() + "'s " + yacht.getPowerUp().getName() + " expired";
|
||||||
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
|
notifyMessageListeners(
|
||||||
logger.debug("Yacht: " + yacht.getShortName() + " powered down!");
|
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
|
||||||
|
notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht));
|
||||||
|
logger.debug("Yacht: " + yacht.getShortName() + " powered down!");
|
||||||
|
|
||||||
|
yacht.powerDown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,14 @@ public class MessageFactory {
|
|||||||
return new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION);
|
return new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a message to be sent out whenever a yacht picks up a boost
|
||||||
|
*
|
||||||
|
* @param serverYacht The yacht that has picked up a power up
|
||||||
|
* @param token The token which they picked up
|
||||||
|
* @return The corresponding YachtEventCodeMessage
|
||||||
|
*/
|
||||||
public static YachtEventCodeMessage makePickupMessage(ServerYacht serverYacht, Token token) {
|
public static YachtEventCodeMessage makePickupMessage(ServerYacht serverYacht, Token token) {
|
||||||
YachtEventType yachtEventType = null;
|
YachtEventType yachtEventType = null;
|
||||||
switch (token.getTokenType()) {
|
switch (token.getTokenType()) {
|
||||||
@@ -159,6 +167,37 @@ public class MessageFactory {
|
|||||||
return new YachtEventCodeMessage(serverYacht.getSourceId(), yachtEventType);
|
return new YachtEventCodeMessage(serverYacht.getSourceId(), yachtEventType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a message representing a certain buff / debuff for a given yacht. For now this is
|
||||||
|
* just for the bumper debuff so the affected boat is aware that it has been crashed. This could
|
||||||
|
* however be extended to render affects for all boats given a certain debuff.
|
||||||
|
*
|
||||||
|
* @param yacht The yacht affected by some status
|
||||||
|
* @param token The token indicating what status they have
|
||||||
|
* @return A YachtEventCodeMessage
|
||||||
|
*/
|
||||||
|
public static YachtEventCodeMessage makeStatusEffectMessage(ServerYacht yacht,
|
||||||
|
TokenType token) {
|
||||||
|
YachtEventType yachtEventType = null;
|
||||||
|
switch (token) {
|
||||||
|
case BUMPER:
|
||||||
|
yachtEventType = YachtEventType.BUMPER_CRASH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new YachtEventCodeMessage(yacht.getSourceId(), yachtEventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a message to be sent out when a given yacht powers down (From a boost of any type)
|
||||||
|
*
|
||||||
|
* @param yacht The yacht that is powering down
|
||||||
|
* @return A YachtEventCodeMessage representing this action
|
||||||
|
*/
|
||||||
|
public static YachtEventCodeMessage makePowerDownMessage(ServerYacht yacht) {
|
||||||
|
return new YachtEventCodeMessage(yacht.getSourceId(), YachtEventType.POWER_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
public static ChatterMessage makeChatterMessage(Integer messageType, String message) {
|
public static ChatterMessage makeChatterMessage(Integer messageType, String message) {
|
||||||
return new ChatterMessage(messageType, "SERVER: " + message);
|
return new ChatterMessage(messageType, "SERVER: " + message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ public enum YachtEventType {
|
|||||||
TOKEN_BUMPER(35),
|
TOKEN_BUMPER(35),
|
||||||
TOKEN_HANDLING(36),
|
TOKEN_HANDLING(36),
|
||||||
TOKEN_WIND_WALKER(37),
|
TOKEN_WIND_WALKER(37),
|
||||||
TOKEN_RANDOM(38);
|
TOKEN_RANDOM(38),
|
||||||
|
POWER_DOWN(39),
|
||||||
|
BUMPER_CRASH(40);
|
||||||
|
|
||||||
|
|
||||||
private int code;
|
private int code;
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,22 @@ public class ClientYacht extends Observable {
|
|||||||
void notifyRounding(ClientYacht yacht, int legNumber);
|
void notifyRounding(ClientYacht yacht, int legNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//This notifies RaceViewController so it can display icon - wmu16
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface PowerUpListener {
|
public interface PowerUpListener {
|
||||||
|
|
||||||
void notifyPowerUp(ClientYacht yacht, TokenType tokenType);
|
void notifyPowerUp(ClientYacht yacht, TokenType tokenType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//This notifies RaceViewController so it can remove token icon - wmu16
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface PowerDownListener {
|
||||||
|
|
||||||
|
void notifyPowerDown(ClientYacht yacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||||
|
|
||||||
|
|
||||||
@@ -70,6 +81,8 @@ public class ClientYacht extends Observable {
|
|||||||
private List<YachtLocationListener> locationListeners = new ArrayList<>();
|
private List<YachtLocationListener> locationListeners = new ArrayList<>();
|
||||||
private List<MarkRoundingListener> markRoundingListeners = new ArrayList<>();
|
private List<MarkRoundingListener> markRoundingListeners = new ArrayList<>();
|
||||||
private List<PowerUpListener> powerUpListeners = new ArrayList<>();
|
private List<PowerUpListener> powerUpListeners = new ArrayList<>();
|
||||||
|
private List<PowerDownListener> powerDownListeners = new ArrayList<>();
|
||||||
|
|
||||||
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
|
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
|
||||||
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
|
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
|
||||||
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
|
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
|
||||||
@@ -211,6 +224,13 @@ public class ClientYacht extends Observable {
|
|||||||
this.position = position;
|
this.position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void powerDown() {
|
||||||
|
this.powerUp = null;
|
||||||
|
for (PowerDownListener listener : powerDownListeners) {
|
||||||
|
listener.notifyPowerDown(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setPowerUp(TokenType tokenType) {
|
public void setPowerUp(TokenType tokenType) {
|
||||||
this.powerUp = tokenType;
|
this.powerUp = tokenType;
|
||||||
for (PowerUpListener listener : powerUpListeners) {
|
for (PowerUpListener listener : powerUpListeners) {
|
||||||
@@ -295,6 +315,10 @@ public class ClientYacht extends Observable {
|
|||||||
powerUpListeners.add(listener);
|
powerUpListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addPowerDownListener(PowerDownListener listener) {
|
||||||
|
powerDownListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public void removeMarkRoundingListener(MarkRoundingListener listener) {
|
public void removeMarkRoundingListener(MarkRoundingListener listener) {
|
||||||
markRoundingListeners.remove(listener);
|
markRoundingListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,4 +39,11 @@ public class Token extends GeoPoint {
|
|||||||
tokenTypeList.remove(TokenType.RANDOM);
|
tokenTypeList.remove(TokenType.RANDOM);
|
||||||
tokenType = tokenTypeList.get(random.nextInt(tokenTypeList.size()));
|
tokenType = tokenTypeList.get(random.nextInt(tokenTypeList.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exists for testing purposes only
|
||||||
|
*/
|
||||||
|
public void assignType(TokenType tokenType) {
|
||||||
|
this.tokenType = tokenType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -410,8 +410,15 @@ public class GameClient {
|
|||||||
* @param yachtEventData The YachtEvent data packet
|
* @param yachtEventData The YachtEvent data packet
|
||||||
*/
|
*/
|
||||||
private void processYachtEvent(YachtEventData yachtEventData) {
|
private void processYachtEvent(YachtEventData yachtEventData) {
|
||||||
|
ClientYacht thisYacht = allBoatsMap.get(yachtEventData.getSubjectId().intValue());
|
||||||
|
|
||||||
if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) {
|
if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) {
|
||||||
showCollisionAlert(yachtEventData);
|
showCollisionAlert(yachtEventData);
|
||||||
|
} else if (yachtEventData.getEventId() == YachtEventType.POWER_DOWN.getCode()) {
|
||||||
|
thisYacht.powerDown();
|
||||||
|
Sounds.playTokenPickupSound(); // TODO: 23/09/17 This should be power down sound
|
||||||
|
} else if (yachtEventData.getEventId() == YachtEventType.BUMPER_CRASH.getCode()) {
|
||||||
|
// TODO: 23/09/17 notify the client that the yacht has been disabled
|
||||||
} else {
|
} else {
|
||||||
TokenType tokenType = null;
|
TokenType tokenType = null;
|
||||||
if (yachtEventData.getEventId() == YachtEventType.TOKEN_VELOCITY.getCode()) {
|
if (yachtEventData.getEventId() == YachtEventType.TOKEN_VELOCITY.getCode()) {
|
||||||
@@ -427,7 +434,7 @@ public class GameClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showTokenPickUp(tokenType);
|
showTokenPickUp(tokenType);
|
||||||
allBoatsMap.get(yachtEventData.getSubjectId().intValue()).setPowerUp(tokenType);
|
thisYacht.setPowerUp(tokenType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import javafx.scene.shape.Polyline;
|
|||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.StageStyle;
|
import javafx.stage.StageStyle;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
import seng302.model.ClientYacht;
|
import seng302.model.ClientYacht;
|
||||||
import seng302.model.ClientYacht.PowerUpListener;
|
import seng302.model.ClientYacht.PowerUpListener;
|
||||||
import seng302.model.RaceState;
|
import seng302.model.RaceState;
|
||||||
@@ -140,6 +141,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
private JFXDialog finishScreenDialog;
|
private JFXDialog finishScreenDialog;
|
||||||
private FinishDialogController finishDialogController;
|
private FinishDialogController finishDialogController;
|
||||||
|
|
||||||
|
//Icon stuff
|
||||||
|
private Timer blinkingTimer = new Timer();
|
||||||
|
private ImageView iconToDisplay;
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
Sounds.stopMusic();
|
Sounds.stopMusic();
|
||||||
Sounds.playRaceMusic();
|
Sounds.playRaceMusic();
|
||||||
@@ -253,6 +258,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
});
|
});
|
||||||
|
|
||||||
player.addPowerUpListener(this::displayPowerUpIcon);
|
player.addPowerUpListener(this::displayPowerUpIcon);
|
||||||
|
player.addPowerDownListener(this::removeIcon);
|
||||||
|
|
||||||
updateOrder(raceState.getPlayerPositions());
|
updateOrder(raceState.getPlayerPositions());
|
||||||
gameView = new GameView3D();
|
gameView = new GameView3D();
|
||||||
@@ -301,7 +307,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
*/
|
*/
|
||||||
private void displayPowerUpIcon(ClientYacht yacht, TokenType tokenType) {
|
private void displayPowerUpIcon(ClientYacht yacht, TokenType tokenType) {
|
||||||
if (yacht == player) {
|
if (yacht == player) {
|
||||||
final ImageView iconToDisplay;
|
|
||||||
|
|
||||||
switch (tokenType) {
|
switch (tokenType) {
|
||||||
case BOOST:
|
case BOOST:
|
||||||
@@ -324,7 +329,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
iconToDisplay.setVisible(true);
|
iconToDisplay.setVisible(true);
|
||||||
|
|
||||||
//Start blinking icon towards end
|
//Start blinking icon towards end
|
||||||
Timer blinkingTimer = new Timer();
|
if (blinkingTimer != null) {
|
||||||
|
blinkingTimer.cancel();
|
||||||
|
}
|
||||||
|
blinkingTimer = new Timer("Blinking Timer");
|
||||||
blinkingTimer.schedule(new TimerTask() {
|
blinkingTimer.schedule(new TimerTask() {
|
||||||
Boolean isVisible = true;
|
Boolean isVisible = true;
|
||||||
|
|
||||||
@@ -334,16 +342,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
iconToDisplay.setVisible(isVisible);
|
iconToDisplay.setVisible(isVisible);
|
||||||
}
|
}
|
||||||
}, (int) (tokenType.getTimeout() * ICON_BLINK_TIMEOUT_RATIO), ICON_BLINK_PERIOD);
|
}, (int) (tokenType.getTimeout() * ICON_BLINK_TIMEOUT_RATIO), ICON_BLINK_PERIOD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Turn icon off after the time out
|
public void removeIcon(ClientYacht yacht) {
|
||||||
Timer switchOffTimer = new Timer();
|
if (yacht == player) {
|
||||||
switchOffTimer.schedule(new TimerTask() {
|
blinkingTimer.cancel();
|
||||||
@Override
|
iconToDisplay.setVisible(false);
|
||||||
public void run() {
|
|
||||||
blinkingTimer.cancel();
|
|
||||||
iconToDisplay.setVisible(false);
|
|
||||||
}
|
|
||||||
}, tokenType.getTimeout());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user