Tile par rapport a la base de donnée + fix rotation tuile
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
package controller;
|
||||
|
||||
import model.Tile;
|
||||
import model.TileDatabaseManager;
|
||||
import view.HexagonTile;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.Point;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -15,19 +17,31 @@ public class GameController implements TilePlacer {
|
||||
private Tile nextTile;
|
||||
private HexagonTile nextTilePreview;
|
||||
private GameContext gameContext;
|
||||
private TileDatabaseManager dbManager;
|
||||
private List<Tile> currentTiles;
|
||||
private int tileIndex;
|
||||
|
||||
public GameController(GameContext gameContext, JPanel gridPanel, Tile nextTile, HexagonTile nextTilePreview) {
|
||||
public GameController(GameContext gameContext, JPanel gridPanel, HexagonTile nextTilePreview) {
|
||||
this.gameContext = gameContext;
|
||||
this.gridPanel = gridPanel;
|
||||
this.hexagonMap = gameContext.getHexagonMap();
|
||||
this.availablePositions = gameContext.getAvailablePositions();
|
||||
this.nextTile = nextTile;
|
||||
this.nextTilePreview = nextTilePreview;
|
||||
|
||||
// Mettre à jour la preview initiale
|
||||
this.dbManager = new TileDatabaseManager();
|
||||
this.tileIndex = 0;
|
||||
|
||||
loadSeries(1); // Charger la série par défaut (ex. série 1)
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
public void loadSeries(int idSeries) {
|
||||
currentTiles = dbManager.getTilesBySeries(idSeries);
|
||||
tileIndex = 0;
|
||||
System.out.println("Série " + idSeries + " chargée avec " + currentTiles.size() + " tuiles.");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void placeTile(Point position) {
|
||||
if (availablePositions.contains(position)) {
|
||||
@@ -37,7 +51,9 @@ public class GameController implements TilePlacer {
|
||||
return;
|
||||
}
|
||||
|
||||
hexTile.setTile(nextTile);
|
||||
System.out.println("Placement de la tuile avec ID : " + (nextTile != null ? nextTile.getId() : "null") + " à la position : " + position);
|
||||
|
||||
hexTile.setTile(nextTile); // Place la tuile actuelle
|
||||
gridPanel.revalidate();
|
||||
gridPanel.repaint();
|
||||
|
||||
@@ -52,32 +68,52 @@ public class GameController implements TilePlacer {
|
||||
}
|
||||
|
||||
gameContext.repaintGrid(gridPanel);
|
||||
generateNextTile();
|
||||
generateNextTile(); // Génère la tuile suivante
|
||||
}
|
||||
}
|
||||
|
||||
public void initializeGame(CameraController cameraController) {
|
||||
Tile initialTile = generateRandomTile();
|
||||
|
||||
|
||||
public void initializeGame(CameraController cameraController) {
|
||||
generateNextTile(); // Génère la première tuile et assigne une tuile valide à `nextTile`
|
||||
|
||||
Tile initialTile = getNextTile(); // Récupère la tuile générée
|
||||
if (initialTile == null) {
|
||||
System.out.println("Erreur : aucune tuile initiale générée.");
|
||||
return; // Arrête l'initialisation si aucune tuile n'a été générée
|
||||
}
|
||||
|
||||
System.out.println("ID de la tuile initiale générée : " + initialTile.getId()); // Affiche l'ID de la tuile initiale
|
||||
|
||||
int centerX = gridPanel.getPreferredSize().width / 2;
|
||||
int centerY = gridPanel.getPreferredSize().height / 2;
|
||||
|
||||
|
||||
Point initialPosition = new Point(0, 0);
|
||||
initialPosition.setLocation(centerX / 50, centerY / 50);
|
||||
|
||||
placeInitialTile(initialPosition, cameraController, initialTile);
|
||||
initialPosition.setLocation(centerX / 50, centerY / 50); // Calcule la position centrale
|
||||
|
||||
placeInitialTile(initialPosition, cameraController, initialTile); // Place la première tuile
|
||||
generateNextTile();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void placeInitialTile(Point position, CameraController cameraController, Tile tile) {
|
||||
addHexagonTile(position, gridPanel, 50, cameraController, tile);
|
||||
availablePositions.remove(position);
|
||||
|
||||
if (tile == null) {
|
||||
System.out.println("Erreur : tuile initiale non définie.");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("Placement de la tuile initiale avec ID : " + tile.getId() + " à la position : " + position);
|
||||
|
||||
addHexagonTile(position, gridPanel, 50, cameraController, tile); // Place la première tuile
|
||||
availablePositions.remove(position); // Marque la position comme occupée
|
||||
|
||||
Point[] adjacentPositions = getAdjacentPositions(position);
|
||||
for (Point adj : adjacentPositions) {
|
||||
if (!hexagonMap.containsKey(adj)) {
|
||||
availablePositions.add(adj);
|
||||
addHexagonTile(adj, gridPanel, 50, cameraController, null);
|
||||
addHexagonTile(adj, gridPanel, 50, cameraController, null); // Placeholder vide pour les positions adjacentes
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,44 +123,68 @@ public class GameController implements TilePlacer {
|
||||
System.out.println("Erreur : position ou panel est null");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int xOffset = position.x * (int) (hexSize * 3 / 2);
|
||||
int yOffset = position.y * (int) (Math.sqrt(3) * hexSize);
|
||||
|
||||
|
||||
if (cameraController != null) {
|
||||
Point viewOffset = cameraController.getViewOffset();
|
||||
xOffset += viewOffset.x;
|
||||
yOffset += viewOffset.y;
|
||||
}
|
||||
|
||||
|
||||
if (position.x % 2 != 0) {
|
||||
yOffset += (int) (Math.sqrt(3) * hexSize / 2);
|
||||
}
|
||||
|
||||
HexagonTile hexTile = new HexagonTile(position);
|
||||
|
||||
boolean isPlaceholder = (tile == null); // Si tile est null, c'est un placeholder
|
||||
HexagonTile hexTile = new HexagonTile(position, isPlaceholder);
|
||||
|
||||
if (tile != null) {
|
||||
hexTile.setTile(tile);
|
||||
} else {
|
||||
System.out.println("Aucun tile n'a été fourni pour cette position : " + position);
|
||||
}
|
||||
|
||||
|
||||
hexTile.setBounds(xOffset, yOffset, hexSize, hexSize);
|
||||
hexTile.addMouseListener(new HexagonMouseListener(hexTile, this, availablePositions));
|
||||
|
||||
|
||||
hexagonMap.put(position, hexTile);
|
||||
panel.add(hexTile);
|
||||
panel.revalidate();
|
||||
panel.repaint();
|
||||
}
|
||||
|
||||
|
||||
public void generateNextTile() {
|
||||
nextTile = new Tile();
|
||||
updatePreview();
|
||||
if (tileIndex < currentTiles.size()) {
|
||||
nextTile = currentTiles.get(tileIndex++);
|
||||
System.out.println("Génération de la prochaine tuile avec ID : " + nextTile.getId() + " (index " + tileIndex + ")");
|
||||
updatePreview(); // Met à jour l'aperçu de la tuile suivante
|
||||
} else {
|
||||
nextTile = null; // Fin de la série, plus de tuiles à placer
|
||||
updatePreview(); // Met à jour l'aperçu pour refléter l'absence de prochaine tuile
|
||||
System.out.println("Fin de la série. Plus de tuiles à placer.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void updatePreview() {
|
||||
nextTilePreview.setTile(nextTile);
|
||||
nextTilePreview.repaint();
|
||||
if (nextTilePreview != null) {
|
||||
if (nextTile != null) {
|
||||
nextTilePreview.setTile(nextTile); // Met à jour avec une tuile valide
|
||||
} else {
|
||||
nextTilePreview.setTile(null); // Affiche un placeholder ou un message si `nextTile` est null
|
||||
}
|
||||
nextTilePreview.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Tile getNextTile() {
|
||||
return nextTile;
|
||||
}
|
||||
|
||||
private Point[] getAdjacentPositions(Point position) {
|
||||
@@ -148,12 +208,4 @@ public class GameController implements TilePlacer {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private Tile generateRandomTile() {
|
||||
return new Tile();
|
||||
}
|
||||
|
||||
public Tile getNextTile() {
|
||||
return nextTile;
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,8 @@ public class MouseWheelController implements MouseWheelListener {
|
||||
|
||||
private HexagonTile previewTile;
|
||||
private GameController gameController;
|
||||
private long lastRotationTime = 0; // Stocke le temps de la dernière rotation
|
||||
private static final int ROTATION_DELAY = 100; // Délai minimum en millisecondes entre chaque rotation
|
||||
|
||||
public MouseWheelController(HexagonTile previewTile, GameController gameController) {
|
||||
this.previewTile = previewTile;
|
||||
@@ -18,14 +20,21 @@ public class MouseWheelController implements MouseWheelListener {
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||
Tile nextTile = gameController.getNextTile();
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
if (e.getWheelRotation() < 0) {
|
||||
nextTile.rotateClockwise();
|
||||
} else if (e.getWheelRotation() > 0) {
|
||||
nextTile.rotateClockwise();
|
||||
// Si le délai entre les rotations est respecté, on procède à la rotation
|
||||
if (currentTime - lastRotationTime >= ROTATION_DELAY) {
|
||||
Tile nextTile = gameController.getNextTile();
|
||||
if (nextTile != null) {
|
||||
if (e.getWheelRotation() < 0) {
|
||||
nextTile.rotateClockwise();
|
||||
} else if (e.getWheelRotation() > 0) {
|
||||
nextTile.rotateCounterClockwise();
|
||||
}
|
||||
|
||||
previewTile.repaint(); // Mettre à jour l'aperçu avec la nouvelle rotation
|
||||
lastRotationTime = currentTime; // Mise à jour du temps de la dernière rotation
|
||||
}
|
||||
}
|
||||
|
||||
previewTile.repaint(); // Mettre à jour l'aperçu avec la nouvelle rotation
|
||||
}
|
||||
}
|
||||
|
@@ -1,76 +1,41 @@
|
||||
package model;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class Tile {
|
||||
private int id; // Ajoute l'attribut id
|
||||
private TerrainType[] terrains; // 2 terrains maximum par tuile
|
||||
private int segmentsForTerrain1; // Nombre de segments pour le premier terrain
|
||||
private static final Random random = new Random();
|
||||
private int segmentsForTerrain1;
|
||||
private int rotation;
|
||||
|
||||
public Tile() {
|
||||
this.terrains = new TerrainType[2]; // Seulement deux terrains
|
||||
generateTerrains();
|
||||
assignSegments();
|
||||
this.rotation = 0; // Rotation initiale à 0
|
||||
// Constructeur modifié pour inclure l'ID
|
||||
public Tile(int id, TerrainType terrain1, TerrainType terrain2, int segmentsForTerrain1) {
|
||||
this.id = id;
|
||||
this.terrains = new TerrainType[]{terrain1, terrain2};
|
||||
this.segmentsForTerrain1 = segmentsForTerrain1;
|
||||
this.rotation = 0;
|
||||
}
|
||||
|
||||
// Méthode pour tourner la tuile dans le sens des aiguilles d'une montre
|
||||
public void rotateClockwise() {
|
||||
rotation = (rotation + 1) % 6; // Modulo 6 pour garder une rotation entre 0 et 5
|
||||
rotation = (rotation + 1) % 6;
|
||||
}
|
||||
|
||||
// Méthode pour obtenir la rotation actuelle
|
||||
public void rotateCounterClockwise() {
|
||||
rotation = (rotation + 5) % 6; // Tourner dans le sens inverse, équivalent à -1 dans un modulo 6
|
||||
}
|
||||
|
||||
|
||||
public int getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
// Génère deux terrains aléatoires pour la tuile
|
||||
private void generateTerrains() {
|
||||
terrains[0] = generateRandomTerrain();
|
||||
terrains[1] = generateRandomTerrain();
|
||||
|
||||
// Assure que les deux terrains sont différents
|
||||
while (terrains[0] == terrains[1]) {
|
||||
terrains[1] = generateRandomTerrain();
|
||||
}
|
||||
}
|
||||
|
||||
// Assigner le nombre de segments pour chaque terrain avec plus de diversité
|
||||
private void assignSegments() {
|
||||
// Terrain 1 occupe entre 1 et 5 segments, le reste pour le terrain 2
|
||||
this.segmentsForTerrain1 = random.nextInt(5) + 1;
|
||||
}
|
||||
|
||||
// Génère un terrain aléatoire avec plus de variété dans les probabilités
|
||||
private TerrainType generateRandomTerrain() {
|
||||
int rand = random.nextInt(100);
|
||||
|
||||
if (rand < 15) {
|
||||
return TerrainType.MER; // 15% MER
|
||||
} else if (rand < 30) {
|
||||
return TerrainType.CHAMP; // 15% CHAMP
|
||||
} else if (rand < 50) {
|
||||
return TerrainType.PRE; // 20% PRE
|
||||
} else if (rand < 75) {
|
||||
return TerrainType.FORET; // 25% FORET
|
||||
} else {
|
||||
return TerrainType.MONTAGNE; // 25% MONTAGNE
|
||||
}
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public TerrainType getTerrain(int index) {
|
||||
if (index >= 0 && index < 2) {
|
||||
return terrains[index];
|
||||
}
|
||||
return null;
|
||||
return index >= 0 && index < 2 ? terrains[index] : null;
|
||||
}
|
||||
|
||||
public int getSegmentsForTerrain(int index) {
|
||||
if (index == 0) {
|
||||
return segmentsForTerrain1; // Nombre de segments pour le premier terrain
|
||||
} else {
|
||||
return 6 - segmentsForTerrain1; // Le reste pour le second terrain
|
||||
}
|
||||
return index == 0 ? segmentsForTerrain1 : 6 - segmentsForTerrain1;
|
||||
}
|
||||
}
|
||||
|
44
src/main/java/model/TileDatabaseManager.java
Normal file
44
src/main/java/model/TileDatabaseManager.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package model;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TileDatabaseManager {
|
||||
private static final String DB_URL = "jdbc:mariadb://dwarves.iut-fbleau.fr/akagundu";
|
||||
private static final String USER = "akagundu";
|
||||
private static final String PASSWORD = "dersim62Lodek";
|
||||
|
||||
public List<Tile> getTilesBySeries(int idSeries) {
|
||||
List<Tile> tiles = new ArrayList<>();
|
||||
String query = "SELECT id, couleur1, couleur2, chiffre FROM Tuile WHERE id_serie = ? ORDER BY id ASC";
|
||||
|
||||
try (Connection cnx = DriverManager.getConnection(DB_URL, USER, PASSWORD);
|
||||
PreparedStatement pst = cnx.prepareStatement(query)) {
|
||||
pst.setInt(1, idSeries);
|
||||
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
int id = rs.getInt("id"); // Récupère l'ID de la tuile
|
||||
String couleur1 = rs.getString("couleur1");
|
||||
String couleur2 = rs.getString("couleur2");
|
||||
int segmentsForTerrain1 = rs.getInt("chiffre");
|
||||
|
||||
System.out.println("Récupération de la tuile avec ID : " + id); // Message de débogage
|
||||
|
||||
// Crée la tuile avec l'ID et les autres paramètres
|
||||
Tile tile = new Tile(id, TerrainType.valueOf(couleur1),
|
||||
couleur2 != null ? TerrainType.valueOf(couleur2) : null,
|
||||
segmentsForTerrain1);
|
||||
tiles.add(tile);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
System.err.println("Database error: " + e.getMessage());
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -4,15 +4,13 @@ import controller.GameController;
|
||||
import controller.CameraController;
|
||||
import controller.GameContext;
|
||||
import controller.MouseWheelController;
|
||||
import model.Tile;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class GameView extends JFrame {
|
||||
private JPanel gridPanel;
|
||||
private Tile nextTile; // Tuile en attente
|
||||
private HexagonTile nextTilePreview; // Composant pour afficher la tuile en attente
|
||||
private HexagonTile nextTilePreview;
|
||||
private GameController gameController;
|
||||
private CameraController cameraController;
|
||||
private GameContext gameContext;
|
||||
@@ -35,16 +33,15 @@ public class GameView extends JFrame {
|
||||
|
||||
add(gridPanel, BorderLayout.CENTER);
|
||||
|
||||
// Initialiser la tuile en attente et la preview
|
||||
nextTile = new Tile();
|
||||
nextTilePreview = new HexagonTile(null);
|
||||
nextTilePreview.setTile(nextTile); // Lier nextTile à la preview
|
||||
// Initialiser la preview pour la prochaine tuile
|
||||
nextTilePreview = new HexagonTile(null, false);
|
||||
|
||||
JPanel controlPanel = createControlPanel();
|
||||
controlPanel.setPreferredSize(new Dimension(200, 600));
|
||||
add(controlPanel, BorderLayout.EAST);
|
||||
|
||||
// Initialiser les contrôleurs avec le contexte de jeu
|
||||
gameController = new GameController(gameContext, gridPanel, nextTile, nextTilePreview); // Passer nextTile et nextTilePreview
|
||||
gameController = new GameController(gameContext, gridPanel, nextTilePreview);
|
||||
cameraController = new CameraController(gridPanel, gameContext);
|
||||
|
||||
// Ajouter un écouteur pour la molette de la souris
|
||||
|
@@ -8,14 +8,15 @@ import java.awt.*;
|
||||
import java.awt.geom.Path2D;
|
||||
|
||||
public class HexagonTile extends JPanel {
|
||||
|
||||
private Tile tile;
|
||||
private Point position;
|
||||
private boolean isPlaceholder; // Nouveau champ pour indiquer si l'hexagone est un placeholder
|
||||
|
||||
public HexagonTile(Point position) {
|
||||
public HexagonTile(Point position, boolean isPlaceholder) {
|
||||
this.position = position;
|
||||
this.isPlaceholder = isPlaceholder;
|
||||
this.tile = null;
|
||||
setPreferredSize(new Dimension(100, 100)); // Ajuste selon la taille de la tuile
|
||||
setPreferredSize(new Dimension(100, 100));
|
||||
}
|
||||
|
||||
public Point getPosition() {
|
||||
@@ -24,6 +25,7 @@ public class HexagonTile extends JPanel {
|
||||
|
||||
public void setTile(Tile tile) {
|
||||
this.tile = tile;
|
||||
this.isPlaceholder = false; // Une fois la tuile posée, ce n'est plus un placeholder
|
||||
repaint();
|
||||
}
|
||||
|
||||
@@ -32,7 +34,7 @@ public class HexagonTile extends JPanel {
|
||||
}
|
||||
|
||||
public boolean isFilled() {
|
||||
return this.tile != null; // Vérifie si la tuile a déjà été placée
|
||||
return this.tile != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,42 +44,35 @@ public class HexagonTile extends JPanel {
|
||||
|
||||
int centerX = getWidth() / 2;
|
||||
int centerY = getHeight() / 2;
|
||||
int largeRadius = 50;
|
||||
int largeRadius = isPlaceholder ? 40 : 50; // Réduction de taille pour les placeholders
|
||||
|
||||
// Créer la zone de découpe pour le grand hexagone
|
||||
Shape largeHexagon = createHexagon(centerX, centerY, largeRadius);
|
||||
g2d.setClip(largeHexagon);
|
||||
|
||||
if (tile != null) {
|
||||
// Dessiner les 6 segments de terrain en fonction des proportions et de la rotation
|
||||
drawTerrainSegments(g2d, centerX, centerY, largeRadius, tile.getRotation());
|
||||
} else {
|
||||
g2d.setColor(Color.LIGHT_GRAY); // Couleur par défaut pour une case vide
|
||||
g2d.setColor(Color.LIGHT_GRAY);
|
||||
g2d.fill(largeHexagon);
|
||||
}
|
||||
|
||||
// Dessiner la bordure de l'hexagone
|
||||
g2d.setClip(null);
|
||||
g2d.setColor(Color.BLACK);
|
||||
g2d.setStroke(new BasicStroke(3)); // Bordure épaisse
|
||||
g2d.setStroke(new BasicStroke(3));
|
||||
g2d.draw(largeHexagon);
|
||||
}
|
||||
|
||||
// Dessiner les 6 segments de terrain avec la rotation
|
||||
private void drawTerrainSegments(Graphics2D g2d, int centerX, int centerY, int radius, int rotation) {
|
||||
int segmentsTerrain1 = tile.getSegmentsForTerrain(0);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int segmentIndex = (i + rotation) % 6; // Appliquer la rotation aux segments
|
||||
if (segmentIndex < segmentsTerrain1) {
|
||||
g2d.setColor(getTerrainColor(tile.getTerrain(0))); // Premier terrain
|
||||
} else {
|
||||
g2d.setColor(getTerrainColor(tile.getTerrain(1))); // Deuxième terrain
|
||||
}
|
||||
int segmentIndex = (i + rotation) % 6;
|
||||
g2d.setColor(segmentIndex < segmentsTerrain1 ?
|
||||
getTerrainColor(tile.getTerrain(0)) :
|
||||
getTerrainColor(tile.getTerrain(1)));
|
||||
g2d.fillArc(centerX - radius, centerY - radius, 2 * radius, 2 * radius, 60 * i, 60);
|
||||
}
|
||||
}
|
||||
|
||||
// Créer la forme hexagonale
|
||||
private Shape createHexagon(int centerX, int centerY, int radius) {
|
||||
Path2D hexagon = new Path2D.Double();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
@@ -94,25 +89,17 @@ public class HexagonTile extends JPanel {
|
||||
return hexagon;
|
||||
}
|
||||
|
||||
// Obtenir la couleur en fonction du type de terrain
|
||||
private Color getTerrainColor(TerrainType terrain) {
|
||||
if (terrain == null) {
|
||||
return Color.WHITE; // Par défaut si le terrain est nul
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
switch (terrain) {
|
||||
case MER:
|
||||
return Color.BLUE;
|
||||
case CHAMP:
|
||||
return Color.YELLOW;
|
||||
case PRE:
|
||||
return Color.GREEN;
|
||||
case FORET:
|
||||
return new Color(34, 139, 34); // Vert foncé
|
||||
case MONTAGNE:
|
||||
return Color.GRAY;
|
||||
default:
|
||||
return Color.WHITE;
|
||||
case MER: return Color.BLUE;
|
||||
case CHAMP: return Color.YELLOW;
|
||||
case PRE: return Color.GREEN;
|
||||
case FORET: return new Color(34, 139, 34);
|
||||
case MONTAGNE: return Color.GRAY;
|
||||
default: return Color.WHITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user