PathFinder WORKS! 1.1
This commit is contained in:
parent
2bdc403f30
commit
87cd4f88c0
211
src/main/java/ruscz/game/gameOfProd/AStar/AStar.java
Normal file
211
src/main/java/ruscz/game/gameOfProd/AStar/AStar.java
Normal file
@ -0,0 +1,211 @@
|
||||
package ruscz.game.gameOfProd.AStar;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A Star Algorithm
|
||||
*
|
||||
* @author Marcelo Surriabre
|
||||
* @version 2.1, 2017-02-23
|
||||
*/
|
||||
public class AStar {
|
||||
private static int DEFAULT_HV_COST = 1;
|
||||
private static int DEFAULT_DIAGONAL_COST = 1000000000;
|
||||
private int hvCost;
|
||||
private int diagonalCost;
|
||||
private Node[][] searchArea;
|
||||
private PriorityQueue<Node> openList;
|
||||
private Set<Node> closedSet;
|
||||
private Node initialNode;
|
||||
private Node finalNode;
|
||||
|
||||
public AStar(int rows, int cols, Node initialNode, Node finalNode, int hvCost, int diagonalCost) {
|
||||
this.hvCost = hvCost;
|
||||
this.diagonalCost = diagonalCost;
|
||||
setInitialNode(initialNode);
|
||||
setFinalNode(finalNode);
|
||||
this.searchArea = new Node[rows][cols];
|
||||
this.openList = new PriorityQueue<>(Comparator.comparingInt(Node::getF));
|
||||
setNodes();
|
||||
this.closedSet = new HashSet<>();
|
||||
}
|
||||
|
||||
public AStar(int rows, int cols, Node initialNode, Node finalNode) {
|
||||
this(rows, cols, initialNode, finalNode, DEFAULT_HV_COST, DEFAULT_DIAGONAL_COST);
|
||||
}
|
||||
|
||||
private void setNodes() {
|
||||
for (int i = 0; i < searchArea.length; i++) {
|
||||
for (int j = 0; j < searchArea[0].length; j++) {
|
||||
Node node = new Node(i, j);
|
||||
node.calculateHeuristic(getFinalNode());
|
||||
this.searchArea[i][j] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlocks(int[][] blocksArray) {
|
||||
for (int i = 0; i < blocksArray.length; i++) {
|
||||
int row = blocksArray[i][0];
|
||||
int col = blocksArray[i][1];
|
||||
setBlock(row, col);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Node> findPath() {
|
||||
openList.add(initialNode);
|
||||
while (!isEmpty(openList)) {
|
||||
Node currentNode = openList.poll();
|
||||
closedSet.add(currentNode);
|
||||
assert currentNode != null;
|
||||
if (isFinalNode(currentNode)) {
|
||||
return getPath(currentNode);
|
||||
} else {
|
||||
addAdjacentNodes(currentNode);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private List<Node> getPath(Node currentNode) {
|
||||
List<Node> path = new ArrayList<>();
|
||||
path.add(currentNode);
|
||||
Node parent;
|
||||
while ((parent = currentNode.getParent()) != null) {
|
||||
path.add(0, parent);
|
||||
currentNode = parent;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private void addAdjacentNodes(Node currentNode) {
|
||||
addAdjacentUpperRow(currentNode);
|
||||
addAdjacentMiddleRow(currentNode);
|
||||
addAdjacentLowerRow(currentNode);
|
||||
}
|
||||
|
||||
private void addAdjacentLowerRow(Node currentNode) {
|
||||
int row = currentNode.getRow();
|
||||
int col = currentNode.getCol();
|
||||
int lowerRow = row + 1;
|
||||
if (lowerRow < getSearchArea().length) {
|
||||
if (col - 1 >= 0) {
|
||||
checkNode(currentNode, col - 1, lowerRow, getDiagonalCost()); // Comment this line if diagonal movements are not allowed
|
||||
}
|
||||
if (col + 1 < getSearchArea()[0].length) {
|
||||
checkNode(currentNode, col + 1, lowerRow, getDiagonalCost()); // Comment this line if diagonal movements are not allowed
|
||||
}
|
||||
checkNode(currentNode, col, lowerRow, getHvCost());
|
||||
}
|
||||
}
|
||||
|
||||
private void addAdjacentMiddleRow(Node currentNode) {
|
||||
int row = currentNode.getRow();
|
||||
int col = currentNode.getCol();
|
||||
if (col - 1 >= 0) {
|
||||
checkNode(currentNode, col - 1, row, getHvCost());
|
||||
}
|
||||
if (col + 1 < getSearchArea()[0].length) {
|
||||
checkNode(currentNode, col + 1, row, getHvCost());
|
||||
}
|
||||
}
|
||||
|
||||
private void addAdjacentUpperRow(Node currentNode) {
|
||||
int row = currentNode.getRow();
|
||||
int col = currentNode.getCol();
|
||||
int upperRow = row - 1;
|
||||
if (upperRow >= 0) {
|
||||
if (col - 1 >= 0) {
|
||||
checkNode(currentNode, col - 1, upperRow, getDiagonalCost()); // Comment this if diagonal movements are not allowed
|
||||
}
|
||||
if (col + 1 < getSearchArea()[0].length) {
|
||||
checkNode(currentNode, col + 1, upperRow, getDiagonalCost()); // Comment this if diagonal movements are not allowed
|
||||
}
|
||||
checkNode(currentNode, col, upperRow, getHvCost());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNode(Node currentNode, int col, int row, int cost) {
|
||||
Node adjacentNode = getSearchArea()[row][col];
|
||||
if (!adjacentNode.isBlock() && !getClosedSet().contains(adjacentNode)) {
|
||||
if (!getOpenList().contains(adjacentNode)) {
|
||||
adjacentNode.setNodeData(currentNode, cost);
|
||||
getOpenList().add(adjacentNode);
|
||||
} else {
|
||||
boolean changed = adjacentNode.checkBetterPath(currentNode, cost);
|
||||
if (changed) {
|
||||
getOpenList().remove(adjacentNode);
|
||||
getOpenList().add(adjacentNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFinalNode(Node currentNode) {
|
||||
return currentNode.equals(finalNode);
|
||||
}
|
||||
|
||||
private boolean isEmpty(PriorityQueue<Node> openList) {
|
||||
return openList.size() == 0;
|
||||
}
|
||||
|
||||
public void setBlock(int row, int col) {
|
||||
this.searchArea[row][col].setBlock(true);
|
||||
}
|
||||
|
||||
public Node getInitialNode() {
|
||||
return initialNode;
|
||||
}
|
||||
|
||||
public void setInitialNode(Node initialNode) {
|
||||
this.initialNode = initialNode;
|
||||
}
|
||||
|
||||
public Node getFinalNode() {
|
||||
return finalNode;
|
||||
}
|
||||
|
||||
public void setFinalNode(Node finalNode) {
|
||||
this.finalNode = finalNode;
|
||||
}
|
||||
|
||||
public Node[][] getSearchArea() {
|
||||
return searchArea;
|
||||
}
|
||||
|
||||
public void setSearchArea(Node[][] searchArea) {
|
||||
this.searchArea = searchArea;
|
||||
}
|
||||
|
||||
public PriorityQueue<Node> getOpenList() {
|
||||
return openList;
|
||||
}
|
||||
|
||||
public void setOpenList(PriorityQueue<Node> openList) {
|
||||
this.openList = openList;
|
||||
}
|
||||
|
||||
public Set<Node> getClosedSet() {
|
||||
return closedSet;
|
||||
}
|
||||
|
||||
public void setClosedSet(Set<Node> closedSet) {
|
||||
this.closedSet = closedSet;
|
||||
}
|
||||
|
||||
public int getHvCost() {
|
||||
return hvCost;
|
||||
}
|
||||
|
||||
public void setHvCost(int hvCost) {
|
||||
this.hvCost = hvCost;
|
||||
}
|
||||
|
||||
private int getDiagonalCost() {
|
||||
return diagonalCost;
|
||||
}
|
||||
|
||||
private void setDiagonalCost(int diagonalCost) {
|
||||
this.diagonalCost = diagonalCost;
|
||||
}
|
||||
}
|
||||
116
src/main/java/ruscz/game/gameOfProd/AStar/Node.java
Normal file
116
src/main/java/ruscz/game/gameOfProd/AStar/Node.java
Normal file
@ -0,0 +1,116 @@
|
||||
package ruscz.game.gameOfProd.AStar;
|
||||
|
||||
/**
|
||||
* Node Class
|
||||
*
|
||||
* @author Marcelo Surriabre
|
||||
* @version 2.0, 2017-02-23
|
||||
*/
|
||||
public class Node {
|
||||
|
||||
private int g;
|
||||
private int f;
|
||||
private int h;
|
||||
private int row;
|
||||
private int col;
|
||||
private boolean isBlock;
|
||||
private Node parent;
|
||||
|
||||
public Node(int row, int col) {
|
||||
super();
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
}
|
||||
|
||||
public void calculateHeuristic(Node finalNode) {
|
||||
this.h = Math.abs(finalNode.getRow() - getRow()) + Math.abs(finalNode.getCol() - getCol());
|
||||
}
|
||||
|
||||
public void setNodeData(Node currentNode, int cost) {
|
||||
int gCost = currentNode.getG() + cost;
|
||||
setParent(currentNode);
|
||||
setG(gCost);
|
||||
calculateFinalCost();
|
||||
}
|
||||
|
||||
public boolean checkBetterPath(Node currentNode, int cost) {
|
||||
int gCost = currentNode.getG() + cost;
|
||||
if (gCost < getG()) {
|
||||
setNodeData(currentNode, cost);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void calculateFinalCost() {
|
||||
int finalCost = getG() + getH();
|
||||
setF(finalCost);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object arg0) {
|
||||
Node other = (Node) arg0;
|
||||
return this.getRow() == other.getRow() && this.getCol() == other.getCol();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Node [row=" + row + ", col=" + col + "]";
|
||||
}
|
||||
|
||||
public int getH() {
|
||||
return h;
|
||||
}
|
||||
|
||||
public void setH(int h) {
|
||||
this.h = h;
|
||||
}
|
||||
|
||||
public int getG() {
|
||||
return g;
|
||||
}
|
||||
|
||||
public void setG(int g) {
|
||||
this.g = g;
|
||||
}
|
||||
|
||||
public int getF() {
|
||||
return f;
|
||||
}
|
||||
|
||||
public void setF(int f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
public Node getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(Node parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public boolean isBlock() {
|
||||
return isBlock;
|
||||
}
|
||||
|
||||
public void setBlock(boolean isBlock) {
|
||||
this.isBlock = isBlock;
|
||||
}
|
||||
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
public void setRow(int row) {
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
public int getCol() {
|
||||
return col;
|
||||
}
|
||||
|
||||
public void setCol(int col) {
|
||||
this.col = col;
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,6 @@ public class Map {
|
||||
private LinkedList<InteractionCell> interactionCells;
|
||||
private LinkedList<TriggerCell> triggerCells;
|
||||
private LinkedList<Npc> npcs;
|
||||
private boolean isBlocked;
|
||||
private int idExit;
|
||||
private ImageCell imageCell;
|
||||
|
||||
@ -31,12 +30,11 @@ public class Map {
|
||||
name = "Spawn";
|
||||
idExit = 0;
|
||||
mapArray = new TypeOfCell[height][width];
|
||||
moveToCells = new LinkedList<EventMoveToCell>();
|
||||
interactionCells = new LinkedList<InteractionCell>();
|
||||
triggerCells = new LinkedList<TriggerCell>();
|
||||
moveToCells = new LinkedList<>();
|
||||
interactionCells = new LinkedList<>();
|
||||
triggerCells = new LinkedList<>();
|
||||
npcs = new LinkedList<>();
|
||||
block = new LinkedList<Block>();
|
||||
isBlocked = false;
|
||||
block = new LinkedList<>();
|
||||
imageCell = new ImageCell();
|
||||
|
||||
TypeOfCell type;
|
||||
@ -57,13 +55,12 @@ public class Map {
|
||||
public Map(int height, int width, Boolean isBlocked) {
|
||||
name = "Spawn";
|
||||
idExit = 0;
|
||||
moveToCells = new LinkedList<EventMoveToCell>();
|
||||
block = new LinkedList<Block>();
|
||||
interactionCells = new LinkedList<InteractionCell>();
|
||||
triggerCells = new LinkedList<TriggerCell>();
|
||||
moveToCells = new LinkedList<>();
|
||||
block = new LinkedList<>();
|
||||
interactionCells = new LinkedList<>();
|
||||
triggerCells = new LinkedList<>();
|
||||
mapArray = new TypeOfCell[height][width];
|
||||
npcs = new LinkedList<>();
|
||||
this.isBlocked = isBlocked;
|
||||
imageCell = new ImageCell();
|
||||
|
||||
TypeOfCell type;
|
||||
@ -72,8 +69,8 @@ public class Map {
|
||||
else
|
||||
type = TypeOfCell.EMPTY;
|
||||
|
||||
for (int i = 0; i < mapArray.length; i++) {
|
||||
for (int j = 0; j < mapArray[0].length; j++) {
|
||||
for (int i = 0; i < mapArray.length / 2; i++) {
|
||||
for (int j = 0; j < mapArray[0].length / 2; j++) {
|
||||
mapArray[0][j] = type;
|
||||
mapArray[mapArray.length - 1][j] = type;
|
||||
}
|
||||
@ -86,11 +83,10 @@ public class Map {
|
||||
|
||||
public Map(TypeOfCell[][] mapArray) {
|
||||
idExit = 0;
|
||||
moveToCells = new LinkedList<EventMoveToCell>();
|
||||
interactionCells = new LinkedList<InteractionCell>();
|
||||
triggerCells = new LinkedList<TriggerCell>();
|
||||
block = new LinkedList<Block>();
|
||||
isBlocked = false;
|
||||
moveToCells = new LinkedList<>();
|
||||
interactionCells = new LinkedList<>();
|
||||
triggerCells = new LinkedList<>();
|
||||
block = new LinkedList<>();
|
||||
imageCell = new ImageCell();
|
||||
npcs = new LinkedList<>();
|
||||
this.mapArray = mapArray;
|
||||
|
||||
@ -1,33 +1,23 @@
|
||||
package ruscz.game.gameOfProd.Scripts;
|
||||
|
||||
import ruscz.game.gameOfProd.AStar.*;
|
||||
import ruscz.game.gameOfProd.Cells.TypeOfCell;
|
||||
import ruscz.game.gameOfProd.Map.Map;
|
||||
import ruscz.game.gameOfProd.Windows.WindowOfFreeMove;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class PathFinder {
|
||||
private static final int MAX_STEPS = 200;
|
||||
|
||||
private Map map;
|
||||
private boolean foundWay;
|
||||
private int startX;
|
||||
private int startY;
|
||||
|
||||
private static final int MAX_STEPS = 200;
|
||||
|
||||
public PathFinder(Map map) {
|
||||
this.map = map;
|
||||
foundWay = false;
|
||||
}
|
||||
|
||||
private LinkedList<Direction> prepareDirection(){
|
||||
LinkedList<Direction> directions = new LinkedList<>();
|
||||
directions.add(Direction.NONE);
|
||||
return directions;
|
||||
}
|
||||
|
||||
private Direction[] getAllWays(){
|
||||
return new Direction[]{Direction.DOWN, Direction.UP, Direction.RIGHT, Direction.LEFT};
|
||||
}
|
||||
|
||||
public LinkedList<Direction> getWayWithoutCollision(int startX, int startY, int endX, int endY) {
|
||||
int nowX = startX;
|
||||
int nowY = startY;
|
||||
@ -57,61 +47,49 @@ public class PathFinder {
|
||||
return directions;
|
||||
}
|
||||
|
||||
private LinkedList<Direction> getWayWithCollision(int startX, int startY, int endX, int endY, LinkedList<Direction> directions) {
|
||||
System.out.println(directions);
|
||||
if (startX == endX && startY == endY){
|
||||
foundWay = true;
|
||||
return directions;
|
||||
}
|
||||
|
||||
if (directions.toArray().length == MAX_STEPS){
|
||||
return prepareDirection();
|
||||
}
|
||||
public LinkedList<Direction> getWayWithCollision(int startX, int startY, int endX, int endY) {
|
||||
AStar aStar = new AStar(map.getMapArray().length, map.getMapArray()[0].length, new Node(startX, startY), new Node(endX, endY));
|
||||
LinkedList<Direction> directions = new LinkedList<>();
|
||||
|
||||
for (Direction direction: getAllWays()){
|
||||
switch (direction){
|
||||
case UP:
|
||||
if (!map.isBusyCell(startX, startY - 1) && startY - 1 != -1 && directions.getLast() != Direction.DOWN){
|
||||
directions.add(Direction.UP);
|
||||
startY--;
|
||||
return getWayWithCollision(startX, startY, endX, endY, directions);
|
||||
}
|
||||
break;
|
||||
case DOWN:
|
||||
if (!map.isBusyCell(startX, startY + 1) & startY + 1 != WindowOfFreeMove.CANVAS_HEIGHT + 1 && directions.getLast() != Direction.UP){
|
||||
directions.add(Direction.DOWN);
|
||||
startY++;
|
||||
return getWayWithCollision(startX, startY, endX, endY, directions);
|
||||
}
|
||||
break;
|
||||
case LEFT:
|
||||
if (!map.isBusyCell(startX - 1, startY) && startX - 1 != -1 && directions.getLast() != Direction.RIGHT){
|
||||
directions.add(Direction.LEFT);
|
||||
startX--;
|
||||
return getWayWithCollision(startX, startY, endX, endY, directions);
|
||||
}
|
||||
break;
|
||||
case RIGHT:
|
||||
if (!map.isBusyCell(startX + 1, startY) && startX - 1 != WindowOfFreeMove.CANVAS_WIDTH + 1 && directions.getLast() != Direction.LEFT){
|
||||
directions.add(Direction.RIGHT);
|
||||
startX++;
|
||||
return getWayWithCollision(startX, startY, endX, endY, directions);
|
||||
}
|
||||
break;
|
||||
for (int i = 0; i < map.getMapArray().length; i++) {
|
||||
for (int j = 0; j < map.getMapArray()[0].length; j++) {
|
||||
if (map.getMapArray()[i][j] == TypeOfCell.BLOCK || map.getMapArray()[i][j] == TypeOfCell.EVENT) {
|
||||
aStar.setBlock(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
directions.removeLast();
|
||||
return getWayWithCollision(startX, startY, endX, endY, directions);
|
||||
}
|
||||
|
||||
public LinkedList<Direction> getWayWithCollision(int startX, int startY, int endX, int endY){
|
||||
this.startX = startX;
|
||||
this.startY = startY;
|
||||
List<Node> nodeList = aStar.findPath();
|
||||
if (nodeList.size() == 0) {
|
||||
return directions;
|
||||
}
|
||||
Node nowNode = nodeList.get(0);
|
||||
if (nodeList.get(nodeList.size() - 1).getG() > MAX_STEPS)
|
||||
return new LinkedList<>();
|
||||
|
||||
LinkedList<Direction> directions = getWayWithCollision(startX, startY, endX, endY, prepareDirection());
|
||||
if (foundWay)
|
||||
return directions;
|
||||
else
|
||||
return prepareDirection();
|
||||
for (Node node : nodeList) {
|
||||
if (nowNode == node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nowNode.getG() > MAX_STEPS)
|
||||
return new LinkedList<>();
|
||||
|
||||
if (node.getRow() - nowNode.getRow() == 1) {
|
||||
directions.add(Direction.RIGHT);
|
||||
}
|
||||
if (node.getRow() - nowNode.getRow() == -1) {
|
||||
directions.add(Direction.LEFT);
|
||||
}
|
||||
if (node.getCol() - nowNode.getCol() == 1) {
|
||||
directions.add(Direction.DOWN);
|
||||
}
|
||||
if (node.getCol() - nowNode.getCol() == -1) {
|
||||
directions.add(Direction.UP);
|
||||
}
|
||||
nowNode = node;
|
||||
}
|
||||
return directions;
|
||||
}
|
||||
}
|
||||
@ -87,12 +87,9 @@ public class WindowOfFreeMove extends JFrame {
|
||||
dialogBar.setVisible(false);
|
||||
dialogBar.setLayout(null);
|
||||
|
||||
inputTimer = new Timer(10, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
if (!mapPainter.getActualMap().getPerson().getIsMoving() && canPersonMove && nextMove != KEY_EVENT) {
|
||||
mapPainter.getActualMap().getPerson().setDirectionMove(nextMove);
|
||||
}
|
||||
inputTimer = new Timer(10, actionEvent -> {
|
||||
if (!mapPainter.getActualMap().getPerson().getIsMoving() && canPersonMove && nextMove != KEY_EVENT) {
|
||||
mapPainter.getActualMap().getPerson().setDirectionMove(nextMove);
|
||||
}
|
||||
});
|
||||
|
||||
@ -190,8 +187,10 @@ public class WindowOfFreeMove extends JFrame {
|
||||
|
||||
mapPainter = new MapPainter(mapLinkedList, canvas);
|
||||
|
||||
Npc npc1 = new Npc(1,1, m5, mapPainter);
|
||||
m5.setNpcs(npc1);
|
||||
int startX = 1;
|
||||
int startY = 1;
|
||||
int endX = 8;
|
||||
int endY = 8;
|
||||
|
||||
setMapPainter(m5);
|
||||
|
||||
@ -200,22 +199,20 @@ public class WindowOfFreeMove extends JFrame {
|
||||
isReadyToPaint = true;
|
||||
canvas.repaint();
|
||||
|
||||
Npc npc2 = new Npc(1,1, m6, mapPainter);
|
||||
Npc npc2 = new Npc(startX, startY, m6, mapPainter);
|
||||
m6.setNpcs(npc2);
|
||||
|
||||
Timer timer = new Timer(10, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
mapPainter.getActualMap().getPerson().move();
|
||||
mapPainter.moveCamera();
|
||||
canvas.repaint();
|
||||
}
|
||||
Timer timer = new Timer(10, actionEvent -> {
|
||||
mapPainter.getActualMap().getPerson().move();
|
||||
mapPainter.moveCamera();
|
||||
canvas.repaint();
|
||||
});
|
||||
timer.start();
|
||||
while (true){
|
||||
npc2.move(9,9);
|
||||
npc2.move(1,1);
|
||||
while (true) {
|
||||
npc2.move(endX, endY);
|
||||
npc2.move(startX, startY);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Canvas extends JPanel {
|
||||
@ -241,7 +238,7 @@ public class WindowOfFreeMove extends JFrame {
|
||||
for (Cell cell : mapPainter.getActualMap().getInteractionEventCells()) {
|
||||
cell.paint(g2D);
|
||||
}
|
||||
for (Person person: mapPainter.getActualMap().getNpcs()){
|
||||
for (Person person : mapPainter.getActualMap().getNpcs()) {
|
||||
person.paint(g2D);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user