Player movement #38
@ -1,26 +1,35 @@
|
||||
const Player = require('./Player');
|
||||
const Hunter = require("./Hunter");
|
||||
|
||||
class Game {
|
||||
constructor() {
|
||||
this.players = [];
|
||||
this.whosNext = 0;
|
||||
this.currentPlayerIndex = 0;
|
||||
this.started = false;
|
||||
this.round = 0;
|
||||
this.hunter = new Hunter();
|
||||
}
|
||||
|
||||
finish_turn() {
|
||||
let move_to_next_round = false;
|
||||
// move on to next player; skip dead players
|
||||
do {
|
||||
this.whosNext++;
|
||||
if (this.whosNext === this.players.length) {
|
||||
this.whosNext = 0;
|
||||
this.round++;
|
||||
if (this.players.length === 0) break;
|
||||
|
||||
this.currentPlayerIndex++;
|
||||
if (this.currentPlayerIndex >= this.players.length) {
|
||||
this.currentPlayerIndex = 0;
|
||||
move_to_next_round = true;
|
||||
}
|
||||
} while (!this.players[this.whosNext].isAlive);
|
||||
} while (!this.players[this.currentPlayerIndex].isAlive); // skip dead players
|
||||
this.finish_round();
|
||||
}
|
||||
|
||||
finish_round() {
|
||||
this.round++;
|
||||
// kill players with hunter
|
||||
if (this.round >= 5) {
|
||||
this.hunter.move(1);
|
||||
this.hunter.move_by(1);
|
||||
this.hunter.hunt(this.players);
|
||||
}
|
||||
// check if all players are dead
|
||||
@ -28,6 +37,32 @@ class Game {
|
||||
// todo: end game (all players are dead)
|
||||
}
|
||||
}
|
||||
|
||||
add_player(name) {
|
||||
this.players.push(new Player(name));
|
||||
}
|
||||
|
||||
remove_player(name) {
|
||||
let index = this.get_player_index(name);
|
||||
if (index !== -1) {
|
||||
this.players.splice(index, 1);
|
||||
if (this.currentPlayerIndex >= index) this.currentPlayerIndex--;
|
||||
}
|
||||
if (this.currentPlayerIndex === index) this.finish_turn(); // if current player leaves: move on to next
|
||||
}
|
||||
|
||||
current_player_is(name) {
|
||||
return this.players[this.currentPlayerIndex].name === name;
|
||||
}
|
||||
|
||||
get_player_index(name) {
|
||||
return this.players.findIndex(player => player.name === name);
|
||||
}
|
||||
|
||||
move_player(name, amount) {
|
||||
let index = this.get_player_index(name);
|
||||
this.players[index].move_by(amount);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Game;
|
@ -3,7 +3,7 @@ class Hunter {
|
||||
this.position = 0;
|
||||
}
|
||||
|
||||
move(amount) {
|
||||
move_by(amount) {
|
||||
this.position += amount;
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
class Player {
|
||||
constructor(socketUsername) {
|
||||
this.socketUsername = socketUsername;
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.position = 0;
|
||||
this.isAlive = true;
|
||||
}
|
||||
|
||||
move(amount) {
|
||||
move_by(amount) {
|
||||
this.position += amount;
|
||||
if (this.position === 15) {
|
||||
//todo: move by 1 only on the last 3 fields
|
||||
if (this.position >= 16) {
|
||||
// todo: win
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
function isMobile() {
|
||||
let mobileDeviceIndicator = 0;
|
||||
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||
mobileDeviceIndicator = 1;
|
||||
}
|
||||
/*
|
||||
let hasTouchscreen = 'ontouchstart' in window;
|
||||
alert(hasTouchscreen ? 'has touchscreen' : 'doesn\'t have touchscreen');
|
||||
*/
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
const Player = require('./Player');
|
||||
const Game = require('./Game');
|
||||
|
||||
const express = require('express');
|
||||
@ -50,7 +49,7 @@ io.on('connection', socket => {
|
||||
}
|
||||
|
||||
if (gameState[socket.room].players.length < 4 && !gameState[socket.room].started) {
|
||||
gameState[socket.room].players.push(new Player(socket.username));
|
||||
gameState[socket.room].add_player(socket.username);
|
||||
addedUser = true;
|
||||
|
||||
socket.emit('login');
|
||||
@ -60,7 +59,7 @@ io.on('connection', socket => {
|
||||
|
||||
generate_log_message(socket.room, socket.username, "JOINED", "");
|
||||
} else {
|
||||
// TODO
|
||||
io.to(socket.id).emit('error', 'Game started already or room has two many members');
|
||||
}
|
||||
});
|
||||
|
||||
@ -78,23 +77,13 @@ io.on('connection', socket => {
|
||||
socket.on('disconnect', function () {
|
||||
if (gameState[socket.room] !== undefined && addedUser) {
|
||||
socket.broadcast.to(socket.room).emit('user left', socket.username);
|
||||
let index = -1;
|
||||
for (let i = 0; i < gameState[socket.room].players.length; i++) {
|
||||
if (gameState[socket.room].players[i].socketUsername === socket.username) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
gameState[socket.room].remove_player(socket.username);
|
||||
|
||||
if (index > -1) {
|
||||
gameState[socket.room].players.splice(index, 1);
|
||||
}
|
||||
// TODO Close card if card is opened and active player left
|
||||
|
||||
socket.leave(socket.room);
|
||||
|
||||
if (gameState[socket.room].players.length === 0) {
|
||||
delete gameState[socket.room];
|
||||
}
|
||||
if (gameState[socket.room].players.length === 0) delete gameState[socket.room];
|
||||
}
|
||||
|
||||
generate_log_message(socket.room, socket.username, "LEFT", "");
|
||||
@ -103,12 +92,7 @@ io.on('connection', socket => {
|
||||
// Game
|
||||
socket.on('roll dice', function () {
|
||||
if (gameState[socket.room] !== undefined && addedUser) {
|
||||
if(gameState[socket.room].players[gameState[socket.room].whosNext] === undefined) {
|
||||
console.log(gameState[socket.room].players)
|
||||
console.log(gameState[socket.room].whosNext)
|
||||
}
|
||||
|
||||
if (gameState[socket.room].players[gameState[socket.room].whosNext].socketUsername === socket.username) {
|
||||
if (gameState[socket.room].current_player_is(socket.username)) {
|
||||
gameState[socket.room].started = true;
|
||||
let sides = 3;
|
||||
let randomNumber = Math.floor(Math.random() * sides) + 1;
|
||||
@ -117,28 +101,40 @@ io.on('connection', socket => {
|
||||
|
||||
generate_log_message(socket.room, socket.username, "DICE", randomNumber);
|
||||
} else {
|
||||
// TODO
|
||||
io.to(socket.id).emit('error', 'It\'s not your turn');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('get card', function (difficulty) {
|
||||
if (gameState[socket.room] !== undefined && addedUser) {
|
||||
if (gameState[socket.room].players[gameState[socket.room].whosNext].socketUsername === socket.username) {
|
||||
if (gameState[socket.room].current_player_is(socket.username)) {
|
||||
io.in(socket.room).emit('card', {'username': socket.username, 'card': getRandomCard(difficulty)});
|
||||
|
||||
generate_log_message(socket.room, socket.username, "CARD", difficulty);
|
||||
} else {
|
||||
// TODO
|
||||
io.to(socket.id).emit('error', 'It\'s not your turn');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('card finished', function (difficulty, answerIsCorrect) {
|
||||
if (gameState[socket.room] !== undefined && addedUser) {
|
||||
if (answerIsCorrect) gameState[socket.room].players[gameState[socket.room].whosNext].move(difficulty);
|
||||
if (answerIsCorrect) {
|
||||
gameState[socket.room].move_player(socket.username, difficulty);
|
||||
generate_log_message(socket.room, socket.username, "MOVE", difficulty);
|
||||
}
|
||||
io.in(socket.room).emit('card destroyed');
|
||||
gameState[socket.room].finish_turn();
|
||||
|
||||
let index = gameState[socket.room].get_player_index(socket.username);
|
||||
let next_player = gameState[socket.room].players[gameState[socket.room].currentPlayerIndex].name;
|
||||
|
||||
io.in(socket.room).emit('player moved', {
|
||||
"next_player": next_player,
|
||||
"player": index,
|
||||
"position": gameState[socket.room].players[index].position
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -161,6 +157,9 @@ function generate_log_message(room, user, type, message) {
|
||||
case 'DICE':
|
||||
color = '\x1b[34m';
|
||||
break;
|
||||
case 'MOVE':
|
||||
color = '\x1b[30m';
|
||||
break;
|
||||
default:
|
||||
color = '\x1b[0m';
|
||||
}
|
||||
@ -191,4 +190,4 @@ function shuffleAnswers(card) {
|
||||
function pad(width, string, padding) {
|
||||
if (string === undefined || string === null) return pad(width, " ", " ");
|
||||
return (width <= string.length) ? string : pad(width, string + padding, padding);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
#error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#login {
|
||||
background: #212121;
|
||||
height: 100%;
|
||||
|
@ -1,35 +0,0 @@
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: Arial, sans-serif
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-rows: 80% 20%;
|
||||
height: calc(100% - 3em - 10px);
|
||||
}
|
||||
|
||||
.material-icon {
|
||||
font-family: Material Icons, sans-serif !important;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: "liga";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
<main>
|
||||
<div id="login">
|
||||
<form>
|
||||
<h2 id="error"></h2>
|
||||
<label for="username">Benutzername: </label>
|
||||
<input id="username" name="username" placeholder="Benutzername" type="text">
|
||||
|
||||
|
@ -2,15 +2,29 @@ let socket;
|
||||
let connected = false;
|
||||
|
||||
function start_chat() {
|
||||
socket = io("/", {
|
||||
closeOnBeforeunload: false
|
||||
});
|
||||
|
||||
socket.on('login', function () {
|
||||
connected = true;
|
||||
|
||||
document.getElementById('login').style.display = 'none';
|
||||
document.getElementById('game').style.display = 'flex';
|
||||
document.getElementById('chat').style.display = 'flex';
|
||||
start_chat();
|
||||
start_game();
|
||||
resize();
|
||||
|
||||
addLogMessage("Welcome " + username + "!");
|
||||
});
|
||||
|
||||
socket.on('error', function (data) {
|
||||
if (data === 'Game started already or room has two many members') {
|
||||
document.getElementById('login').style.display = 'flex';
|
||||
document.getElementById('game').style.display = 'none';
|
||||
document.getElementById('chat').style.display = 'none';
|
||||
document.getElementById('error').innerText = data;
|
||||
}
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
socket.on('new message', function (data) {
|
||||
addChatMessage(data);
|
||||
});
|
||||
@ -22,9 +36,6 @@ function start_chat() {
|
||||
socket.on('user left', function (data) {
|
||||
addLogMessage(data + ' left');
|
||||
});
|
||||
|
||||
// Login
|
||||
socket.emit('add user', {'username': username, 'room_name': room_name});
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
|
@ -14,38 +14,30 @@ let rolled_number = null;
|
||||
let game = document.getElementById('game');
|
||||
let app;
|
||||
let border_card_stack = new PIXI.Graphics();
|
||||
let my_turn;
|
||||
|
||||
let game_board_size = 2000;
|
||||
let max_size = calculate_size();
|
||||
let sprite_size = Math.floor(game_board_size / 11);
|
||||
|
||||
const rolled_number_style = new PIXI.TextStyle({
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 140,
|
||||
fontWeight: 'bold',
|
||||
wordWrap: true,
|
||||
wordWrapWidth: game_board_size * 0.5 - 20,
|
||||
});
|
||||
let rolled_number_text = new PIXI.Text("", rolled_number_style);
|
||||
|
||||
// fields
|
||||
let sprites = [
|
||||
new Sprite(9, 9),
|
||||
new Sprite(9, 7),
|
||||
new Sprite(9, 5),
|
||||
new Sprite(9, 3),
|
||||
new Sprite(9, 1),
|
||||
new Sprite(7, 1),
|
||||
new Sprite(5, 1),
|
||||
new Sprite(3, 1),
|
||||
new Sprite(1, 1),
|
||||
new Sprite(1, 3),
|
||||
new Sprite(1, 5),
|
||||
new Sprite(1, 7),
|
||||
new Sprite(1, 9),
|
||||
new Sprite(3, 9),
|
||||
new Sprite(9, 9), // lower right
|
||||
new Sprite(7, 9),
|
||||
new Sprite(5, 9),
|
||||
new Sprite(7, 9)
|
||||
new Sprite(3, 9),
|
||||
new Sprite(1, 9), // upper right
|
||||
new Sprite(1, 7),
|
||||
new Sprite(1, 5),
|
||||
new Sprite(1, 3),
|
||||
new Sprite(1, 1), // upper left
|
||||
new Sprite(3, 1),
|
||||
new Sprite(5, 1),
|
||||
new Sprite(7, 1),
|
||||
new Sprite(9, 1), // lower left
|
||||
new Sprite(9, 3),
|
||||
new Sprite(9, 5),
|
||||
new Sprite(9, 7)
|
||||
];
|
||||
|
||||
function start_game() {
|
||||
@ -66,19 +58,22 @@ function start_game() {
|
||||
|
||||
|
||||
// White circles
|
||||
let first_circle = generate_circle(new PIXI.Graphics(), 3, 9);
|
||||
app.stage.addChild(first_circle);
|
||||
let player_a = generate_circle(new PIXI.Graphics(), 9, 9, 'yellow', 1);
|
||||
app.stage.addChild(player_a);
|
||||
|
||||
let second_circle = generate_circle(new PIXI.Graphics(), 5, 9);
|
||||
app.stage.addChild(second_circle);
|
||||
let player_b = generate_circle(new PIXI.Graphics(), 9, 9, 'blue', 2);
|
||||
app.stage.addChild(player_b);
|
||||
|
||||
let third_circle = generate_circle(new PIXI.Graphics(), 7, 9);
|
||||
app.stage.addChild(third_circle);
|
||||
let player_c = generate_circle(new PIXI.Graphics(), 9, 9, 'green', 3);
|
||||
app.stage.addChild(player_c);
|
||||
|
||||
let player_d = generate_circle(new PIXI.Graphics(), 9, 9, 'red', 4);
|
||||
app.stage.addChild(player_d);
|
||||
|
||||
|
||||
// Card stacks
|
||||
let cards_1 = generate_card_stack(PIXI.Sprite.from('/img/card_stack.png'), 3, 3, function () {
|
||||
if (!show_card && rolled_number === 1) {
|
||||
if (diced && !show_card && rolled_number === 1) {
|
||||
console.log("1");
|
||||
socket.emit('get card', 1);
|
||||
}
|
||||
@ -86,7 +81,7 @@ function start_game() {
|
||||
app.stage.addChild(cards_1);
|
||||
|
||||
let cards_2 = generate_card_stack(PIXI.Sprite.from('/img/card_stack.png'), 5, 3, function () {
|
||||
if (!show_card && rolled_number === 2) {
|
||||
if (diced && !show_card && rolled_number === 2) {
|
||||
console.log("2");
|
||||
socket.emit('get card', 2);
|
||||
}
|
||||
@ -94,7 +89,7 @@ function start_game() {
|
||||
app.stage.addChild(cards_2);
|
||||
|
||||
let cards_3 = generate_card_stack(PIXI.Sprite.from('/img/card_stack.png'), 7, 3, function () {
|
||||
if (!show_card && rolled_number === 3) {
|
||||
if (diced && !show_card && rolled_number === 3) {
|
||||
console.log("3");
|
||||
socket.emit('get card', 3);
|
||||
}
|
||||
@ -131,6 +126,29 @@ function start_game() {
|
||||
// logo.rotation -= Math.PI / 8;
|
||||
app.stage.addChild(logo);
|
||||
|
||||
|
||||
my_turn = new PIXI.Text("", new PIXI.TextStyle({
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 70,
|
||||
fontWeight: 'bold',
|
||||
}));
|
||||
my_turn.x = sprite_size * 3;
|
||||
my_turn.y = sprite_size * 8;
|
||||
app.stage.addChild(my_turn);
|
||||
|
||||
|
||||
let rolled_number_text = new PIXI.Text("", new PIXI.TextStyle({
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 140,
|
||||
fontWeight: 'bold',
|
||||
wordWrap: true,
|
||||
wordWrapWidth: game_board_size * 0.5 - 20,
|
||||
}));
|
||||
rolled_number_text.x = sprite_size * 7 - sprite_size * 0.2 + dice.width / 2 - rolled_number_text.width / 2;
|
||||
rolled_number_text.y = sprite_size * 6 - sprite_size * 0.2;
|
||||
app.stage.addChild(rolled_number_text);
|
||||
|
||||
|
||||
socket.on('dice', function (randomInt) {
|
||||
rolled_number = randomInt;
|
||||
diced = true;
|
||||
@ -138,10 +156,7 @@ function start_game() {
|
||||
border_card_stack.lineStyle(15, 0x862323, 1);
|
||||
border_card_stack.drawRoundedRect(sprite_size * (1 + 2 * rolled_number) - sprite_size * 0.2, sprite_size * 3 - sprite_size * 0.2, sprite_size * 1.5, sprite_size * 3 * 0.72, 10);
|
||||
|
||||
rolled_number_text = new PIXI.Text(rolled_number, rolled_number_style);
|
||||
rolled_number_text.x = sprite_size * 7 - sprite_size * 0.2 + dice.width / 2 - rolled_number_text.width / 2;
|
||||
rolled_number_text.y = sprite_size * 6 - sprite_size * 0.2;
|
||||
app.stage.addChild(rolled_number_text);
|
||||
rolled_number_text.text = rolled_number;
|
||||
});
|
||||
|
||||
socket.on('card', function (data) {
|
||||
@ -158,10 +173,46 @@ function start_game() {
|
||||
diced = false;
|
||||
show_card = false;
|
||||
card.destroyCard();
|
||||
rolled_number_text.destroy();
|
||||
rolled_number_text.text = "";
|
||||
border_card_stack.clear();
|
||||
});
|
||||
|
||||
socket.on('player moved', function (data) {
|
||||
my_turn.text = "";
|
||||
|
||||
let player = data.player;
|
||||
let position = data.position;
|
||||
let next_player = data.next_player;
|
||||
|
||||
let x = sprites[position].coord_x;
|
||||
let y = sprites[position].coord_y;
|
||||
|
||||
switch (player) {
|
||||
case 0:
|
||||
player_a.clear();
|
||||
player_a = generate_circle(new PIXI.Graphics(), y, x, 'yellow', 1);
|
||||
app.stage.addChild(player_a);
|
||||
break;
|
||||
case 1:
|
||||
player_b.clear();
|
||||
player_b = generate_circle(new PIXI.Graphics(), y, x, 'blue', 2);
|
||||
app.stage.addChild(player_b);
|
||||
break;
|
||||
case 2:
|
||||
player_c.clear();
|
||||
player_c = generate_circle(new PIXI.Graphics(), y, x, 'green', 3);
|
||||
app.stage.addChild(player_c);
|
||||
break;
|
||||
case 3:
|
||||
player_d.clear();
|
||||
player_d = generate_circle(new PIXI.Graphics(), y, x, 'red', 4);
|
||||
app.stage.addChild(player_d);
|
||||
break;
|
||||
}
|
||||
|
||||
if (next_player === username) my_turn.text = "Your Turn";
|
||||
});
|
||||
|
||||
resize();
|
||||
}
|
||||
|
||||
@ -183,10 +234,36 @@ function generate_red_border(graphics) {
|
||||
return graphics;
|
||||
}
|
||||
|
||||
function generate_circle(graphics, x, y) {
|
||||
function generate_circle(graphics, x, y, color, offset) {
|
||||
graphics.lineStyle(0);
|
||||
graphics.beginFill(0xffffff, 1);
|
||||
graphics.drawCircle(sprite_size * x - sprite_size * 0.2 + sprite_size * 0.75, sprite_size * y - sprite_size * 0.2 + sprite_size * 0.75, sprite_size / 4);
|
||||
switch (color) {
|
||||
case 'yellow':
|
||||
graphics.beginFill(0xFFDDA1, 1);
|
||||
break;
|
||||
case 'red':
|
||||
graphics.beginFill(0xF47A93, 1);
|
||||
break;
|
||||
case 'green':
|
||||
graphics.beginFill(0x6C9A8B, 1);
|
||||
break;
|
||||
case 'blue':
|
||||
graphics.beginFill(0x4169E1, 1);
|
||||
break;
|
||||
}
|
||||
switch (offset) {
|
||||
case 1:
|
||||
graphics.drawCircle(sprite_size * x - 65 - sprite_size * 0.2 + sprite_size * 0.75, sprite_size * y + 65 - sprite_size * 0.2 + sprite_size * 0.75, sprite_size / 4);
|
||||
break; // upper left
|
||||
case 2:
|
||||
graphics.drawCircle(sprite_size * x + 65 - sprite_size * 0.2 + sprite_size * 0.75, sprite_size * y + 65 - sprite_size * 0.2 + sprite_size * 0.75, sprite_size / 4);
|
||||
break; // upper right
|
||||
case 3:
|
||||
graphics.drawCircle(sprite_size * x - 65 - sprite_size * 0.2 + sprite_size * 0.75, sprite_size * y - 65 - sprite_size * 0.2 + sprite_size * 0.75, sprite_size / 4);
|
||||
break; // lower left
|
||||
case 4:
|
||||
graphics.drawCircle(sprite_size * x + 65 - sprite_size * 0.2 + sprite_size * 0.75, sprite_size * y - 65 - sprite_size * 0.2 + sprite_size * 0.75, sprite_size / 4);
|
||||
break; // lower right
|
||||
}
|
||||
graphics.endFill();
|
||||
return graphics;
|
||||
}
|
||||
@ -221,4 +298,4 @@ function resize() {
|
||||
app.stage.scale.set(size / game_board_size, size / game_board_size);
|
||||
|
||||
app.renderer.resize(size, size);
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,12 @@ document.getElementById('ok').addEventListener('click', function () {
|
||||
username = document.getElementById('username').value;
|
||||
room_name = document.getElementById('room').value;
|
||||
|
||||
document.getElementById('login').style.display = 'none';
|
||||
document.getElementById('game').style.display = 'flex';
|
||||
document.getElementById('chat').style.display = 'flex';
|
||||
socket = io("/", {
|
||||
closeOnBeforeunload: false
|
||||
});
|
||||
|
||||
start_chat();
|
||||
start_game();
|
||||
resize();
|
||||
|
||||
// Login
|
||||
socket.emit('add user', {'username': username, 'room_name': room_name});
|
||||
});
|
Loading…
Reference in New Issue
Block a user