Added checks for broken gate frame blocks.
This commit is contained in:
parent
9e36cf189b
commit
99be2905be
@ -19,6 +19,7 @@ package de.craftinc.gates;
|
|||||||
import de.craftinc.gates.util.FloodUtil;
|
import de.craftinc.gates.util.FloodUtil;
|
||||||
import de.craftinc.gates.persistence.LocationUtil;
|
import de.craftinc.gates.persistence.LocationUtil;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ public class Gate implements ConfigurationSerializable
|
|||||||
{
|
{
|
||||||
protected Location location; /* saving both location and gateBlockLocations is redundant but makes it easy to allow players to reshape gates */
|
protected Location location; /* saving both location and gateBlockLocations is redundant but makes it easy to allow players to reshape gates */
|
||||||
protected Set<Location> gateBlockLocations = new HashSet<Location>(); /* Locations of the blocks inside the gate */
|
protected Set<Location> gateBlockLocations = new HashSet<Location>(); /* Locations of the blocks inside the gate */
|
||||||
|
protected Set<Block> gateFrameBlocks = new HashSet<Block>();
|
||||||
|
|
||||||
protected Location exit;
|
protected Location exit;
|
||||||
|
|
||||||
@ -79,7 +81,10 @@ public class Gate implements ConfigurationSerializable
|
|||||||
this.location = location;
|
this.location = location;
|
||||||
|
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
findPortalBlocks();
|
if (this.gateBlockLocations == null || this.gateBlockLocations.size() == 0 ) {
|
||||||
|
findPortalBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,16 +179,29 @@ public class Gate implements ConfigurationSerializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return Will never return 'null' but might return an empty Set.
|
||||||
|
*/
|
||||||
|
public Set<Block> getGateFrameBlocks()
|
||||||
|
{
|
||||||
|
return gateFrameBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected void findPortalBlocks()
|
protected void findPortalBlocks()
|
||||||
{
|
{
|
||||||
gateBlockLocations = new HashSet<Location>();
|
gateBlockLocations = new HashSet<Location>();
|
||||||
Set<Block> gateBlocks = FloodUtil.getGateFrameBlocks(location.getBlock());
|
Set<Block> gateBlocks = FloodUtil.getGatePortalBlocks(location.getBlock());
|
||||||
|
|
||||||
if (gateBlocks != null) {
|
if (gateBlocks != null) {
|
||||||
for (Block b : gateBlocks) {
|
for (Block b : gateBlocks) {
|
||||||
gateBlockLocations.add(b.getLocation());
|
gateBlockLocations.add(b.getLocation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gateFrameBlocks = FloodUtil.getFrame(gateBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -205,17 +223,22 @@ public class Gate implements ConfigurationSerializable
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
throw new Exception("Gate got closed. It has no exit.");
|
throw new Exception("Gate got closed. It has no exit.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isHidden) {
|
|
||||||
findPortalBlocks();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gateBlockLocations.size() == 0) {
|
if (gateBlockLocations.size() == 0) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
throw new Exception("Gate got closed. The frame is missing or broken.");
|
throw new Exception("Gate got closed. The frame is missing or broken. (no gate blocks)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isHidden() && Plugin.getPlugin().getConfig().getBoolean(Plugin.confCheckForBrokenGateFramesKey)) {
|
||||||
|
|
||||||
|
for (Block b : gateFrameBlocks) {
|
||||||
|
|
||||||
|
if (b.getType() == Material.AIR) {
|
||||||
|
setOpen(false);
|
||||||
|
throw new Exception("Gate got closed. The frame is missing or broken. (missing frame block(s))");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -266,6 +289,8 @@ public class Gate implements ConfigurationSerializable
|
|||||||
for (Map<String, Object> sgb : serializedGateBlocks) {
|
for (Map<String, Object> sgb : serializedGateBlocks) {
|
||||||
gateBlockLocations.add(LocationUtil.deserializeLocation(sgb));
|
gateBlockLocations.add(LocationUtil.deserializeLocation(sgb));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gateFrameBlocks = FloodUtil.getFrameWithLocations(gateBlockLocations);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Plugin.log("ERROR: Failed to load gate '" + id + "'! (" + e.getMessage() + ")");
|
Plugin.log("ERROR: Failed to load gate '" + id + "'! (" + e.getMessage() + ")");
|
||||||
|
@ -26,6 +26,7 @@ import java.util.logging.Level;
|
|||||||
import de.craftinc.gates.persistence.MigrationUtil;
|
import de.craftinc.gates.persistence.MigrationUtil;
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.Chunk;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
@ -46,8 +47,9 @@ public class GatesManager
|
|||||||
protected Map<String, Gate> gatesById;
|
protected Map<String, Gate> gatesById;
|
||||||
protected Map<SimpleChunk, Set<Gate>> gatesByChunk;
|
protected Map<SimpleChunk, Set<Gate>> gatesByChunk;
|
||||||
protected Map<SimpleLocation, Gate> gatesByLocation;
|
protected Map<SimpleLocation, Gate> gatesByLocation;
|
||||||
|
protected Map<SimpleLocation, Gate> gatesByFrameLocation;
|
||||||
private List<Gate> gates;
|
|
||||||
|
protected List<Gate> gates;
|
||||||
|
|
||||||
|
|
||||||
public Gate getGateWithId(String id)
|
public Gate getGateWithId(String id)
|
||||||
@ -68,7 +70,14 @@ public class GatesManager
|
|||||||
SimpleLocation simpleLocation = new SimpleLocation(location);
|
SimpleLocation simpleLocation = new SimpleLocation(location);
|
||||||
return gatesByLocation.get(simpleLocation);
|
return gatesByLocation.get(simpleLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Gate getGateAtFrameLocation(Location location)
|
||||||
|
{
|
||||||
|
SimpleLocation simpleLocation = new SimpleLocation(location);
|
||||||
|
return gatesByFrameLocation.get(simpleLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void saveGatesToDisk()
|
public void saveGatesToDisk()
|
||||||
{
|
{
|
||||||
@ -115,10 +124,25 @@ public class GatesManager
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Gate g : this.gates) {
|
||||||
|
try {
|
||||||
|
g.validate();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
try {
|
||||||
|
g.setOpen(false);
|
||||||
|
}
|
||||||
|
catch (Exception ignored) { }
|
||||||
|
|
||||||
|
Plugin.log(Level.FINER, "closed gate '" + g.getId() + "' reason: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fillGatesById();
|
fillGatesById();
|
||||||
fillGatesByChunk();
|
fillGatesByChunk();
|
||||||
fillGatesByLocation();
|
fillGatesByLocation();
|
||||||
|
fillGatesByFrameLocation();
|
||||||
|
|
||||||
Plugin.log("Loaded " + this.gates.size() + " gates.");
|
Plugin.log("Loaded " + this.gates.size() + " gates.");
|
||||||
|
|
||||||
@ -204,6 +228,22 @@ public class GatesManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void fillGatesByFrameLocation()
|
||||||
|
{
|
||||||
|
int numFrameBlocks = 0;
|
||||||
|
|
||||||
|
for (Gate g : gates) {
|
||||||
|
numFrameBlocks += g.gateFrameBlocks.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
gatesByFrameLocation = new HashMap<SimpleLocation, Gate>((int)(numFrameBlocks*1.25));
|
||||||
|
|
||||||
|
for (Gate g : gates) {
|
||||||
|
this.addGateByFrameLocations(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void removeGateById(String id)
|
protected void removeGateById(String id)
|
||||||
{
|
{
|
||||||
gatesById.remove(id);
|
gatesById.remove(id);
|
||||||
@ -216,7 +256,7 @@ public class GatesManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void removeGateFromLocations(Set<Location> gateBlocks)
|
protected void removeGateByLocation(Set<Location> gateBlocks)
|
||||||
{
|
{
|
||||||
for (Location l : gateBlocks) {
|
for (Location l : gateBlocks) {
|
||||||
SimpleLocation sl = new SimpleLocation(l);
|
SimpleLocation sl = new SimpleLocation(l);
|
||||||
@ -225,6 +265,15 @@ public class GatesManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void removeGateByFrameLocation(Set<Block> gateFrameBlocks)
|
||||||
|
{
|
||||||
|
for (Block block : gateFrameBlocks) {
|
||||||
|
SimpleLocation sl = new SimpleLocation(block.getLocation());
|
||||||
|
gatesByFrameLocation.remove(sl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void addGateByLocations(Gate g)
|
protected void addGateByLocations(Gate g)
|
||||||
{
|
{
|
||||||
for (Location l : g.getGateBlockLocations()) {
|
for (Location l : g.getGateBlockLocations()) {
|
||||||
@ -234,6 +283,15 @@ public class GatesManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void addGateByFrameLocations(Gate g)
|
||||||
|
{
|
||||||
|
for (Block block : g.getGateFrameBlocks()) {
|
||||||
|
SimpleLocation sl = new SimpleLocation(block.getLocation());
|
||||||
|
gatesByFrameLocation.put(sl, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void removeGateFromChunk(Gate g, Location l)
|
protected void removeGateFromChunk(Gate g, Location l)
|
||||||
{
|
{
|
||||||
if (l != null) {
|
if (l != null) {
|
||||||
@ -350,13 +408,16 @@ public class GatesManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void handleGateLocationChange(Gate g, Location oldLocation, Set<Location> oldGateBlockLocations)
|
public void handleGateLocationChange(Gate g, Location oldLocation, Set<Location> oldGateBlockLocations, Set<Block> oldGateFrameBlocks)
|
||||||
{
|
{
|
||||||
this.removeGateFromChunk(g, oldLocation);
|
this.removeGateFromChunk(g, oldLocation);
|
||||||
this.addGateByChunk(g);
|
this.addGateByChunk(g);
|
||||||
|
|
||||||
this.removeGateFromLocations(oldGateBlockLocations);
|
this.removeGateByLocation(oldGateBlockLocations);
|
||||||
this.addGateByLocations(g);
|
this.addGateByLocations(g);
|
||||||
|
|
||||||
|
this.removeGateByFrameLocation(oldGateFrameBlocks);
|
||||||
|
this.addGateByFrameLocations(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -365,6 +426,7 @@ public class GatesManager
|
|||||||
this.addGateByChunk(g);
|
this.addGateByChunk(g);
|
||||||
this.addGateByLocations(g);
|
this.addGateByLocations(g);
|
||||||
this.addGateWithId(g);
|
this.addGateWithId(g);
|
||||||
|
this.addGateByFrameLocations(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -372,7 +434,8 @@ public class GatesManager
|
|||||||
{
|
{
|
||||||
this.removeGateById(g.getId());
|
this.removeGateById(g.getId());
|
||||||
this.removeGateFromChunk(g, g.getLocation());
|
this.removeGateFromChunk(g, g.getLocation());
|
||||||
this.removeGateFromLocations(g.getGateBlockLocations());
|
this.removeGateByLocation(g.getGateBlockLocations());
|
||||||
|
this.removeGateByFrameLocation(g.getGateFrameBlocks());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ public class Plugin extends JavaPlugin
|
|||||||
protected PlayerRespawnListener respawnListener = new PlayerRespawnListener();
|
protected PlayerRespawnListener respawnListener = new PlayerRespawnListener();
|
||||||
protected PlayerChangedWorldListener worldChangeListener = new PlayerChangedWorldListener();
|
protected PlayerChangedWorldListener worldChangeListener = new PlayerChangedWorldListener();
|
||||||
protected PlayerJoinListener joinListener = new PlayerJoinListener();
|
protected PlayerJoinListener joinListener = new PlayerJoinListener();
|
||||||
|
protected BlockBreakListener blockBreakListener = new BlockBreakListener();
|
||||||
|
|
||||||
|
|
||||||
public Plugin()
|
public Plugin()
|
||||||
@ -162,7 +163,9 @@ public class Plugin extends JavaPlugin
|
|||||||
pm.registerEvents(this.worldChangeListener, this);
|
pm.registerEvents(this.worldChangeListener, this);
|
||||||
pm.registerEvents(this.joinListener, this);
|
pm.registerEvents(this.joinListener, this);
|
||||||
|
|
||||||
|
if (getConfig().getBoolean(confCheckForBrokenGateFramesKey)) {
|
||||||
|
pm.registerEvents(this.blockBreakListener, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,6 +75,9 @@ public class CommandInfo extends BaseCommand
|
|||||||
gate.getExit().getWorld().getName());
|
gate.getExit().getWorld().getName());
|
||||||
else
|
else
|
||||||
sendMessage(ChatColor.DARK_AQUA + "NOTE: this gate has no exit");
|
sendMessage(ChatColor.DARK_AQUA + "NOTE: this gate has no exit");
|
||||||
|
|
||||||
|
|
||||||
|
Plugin.log("frame blocks: " + gate.getGateFrameBlocks());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
48
src/de/craftinc/gates/listeners/BlockBreakListener.java
Normal file
48
src/de/craftinc/gates/listeners/BlockBreakListener.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/* Craft Inc. Gates
|
||||||
|
Copyright (C) 2011-2013 Craft Inc. Gates Team (see AUTHORS.txt)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with this program (LGPLv3). If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package de.craftinc.gates.listeners;
|
||||||
|
|
||||||
|
|
||||||
|
import de.craftinc.gates.Gate;
|
||||||
|
import de.craftinc.gates.Plugin;
|
||||||
|
import de.craftinc.gates.util.GateBlockChangeSender;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
|
|
||||||
|
public class BlockBreakListener implements Listener
|
||||||
|
{
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL)
|
||||||
|
public void onBlockBreak(BlockBreakEvent event)
|
||||||
|
{
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gate gate = Plugin.getPlugin().getGatesManager().getGateAtFrameLocation(event.getBlock().getLocation());
|
||||||
|
|
||||||
|
if (gate != null && !gate.isHidden()) {
|
||||||
|
try {
|
||||||
|
gate.setOpen(false);
|
||||||
|
}
|
||||||
|
catch (Exception ignored) { }
|
||||||
|
|
||||||
|
GateBlockChangeSender.updateGateBlocks(gate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.BlockFace;
|
import org.bukkit.block.BlockFace;
|
||||||
@ -44,10 +45,100 @@ public class FloodUtil
|
|||||||
exp2.add(BlockFace.NORTH);
|
exp2.add(BlockFace.NORTH);
|
||||||
exp2.add(BlockFace.SOUTH);
|
exp2.add(BlockFace.SOUTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the all frame blocks of an gate.
|
||||||
|
* @param blocks All blocks inside the gate.
|
||||||
|
* @return A Set containing all frame block. Will never return 'null'.
|
||||||
|
*/
|
||||||
|
public static Set<Block> getFrame(Set<Block> blocks)
|
||||||
|
{
|
||||||
|
if (blocks == null || blocks.isEmpty()) {
|
||||||
|
return new HashSet<Block>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to find gate's direction (north-south or east-west)
|
||||||
|
Set<BlockFace> gateFrameSearchFaces = null;
|
||||||
|
|
||||||
|
for (Block b : blocks) {
|
||||||
|
|
||||||
|
if (blocks.contains(b.getRelative(BlockFace.EAST)) ||
|
||||||
|
blocks.contains(b.getRelative(BlockFace.WEST))) {
|
||||||
|
|
||||||
|
gateFrameSearchFaces = exp1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocks.contains(b.getRelative(BlockFace.NORTH)) ||
|
||||||
|
blocks.contains(b.getRelative(BlockFace.SOUTH))) {
|
||||||
|
|
||||||
|
gateFrameSearchFaces = exp2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gateFrameSearchFaces != null) {
|
||||||
|
return _getFrame(blocks, gateFrameSearchFaces);
|
||||||
|
}
|
||||||
|
else { // no direction found (the gate might only consist of blocks one over another)
|
||||||
|
|
||||||
|
// Try one direction and check if the found blocks are not air.
|
||||||
|
// If air is found (frame broken or wrong direction) return the other direction
|
||||||
|
Set<Block> frameBlocks = _getFrame(blocks, exp1);
|
||||||
|
|
||||||
|
for (Block b : frameBlocks) {
|
||||||
|
|
||||||
|
if (b.getType() == Material.AIR) {
|
||||||
|
return _getFrame(blocks, exp2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return frameBlocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected static Set<Block> _getFrame(Set<Block> blocks, Set<BlockFace> searchDirections)
|
||||||
|
{
|
||||||
|
Set<Block> frameBlocks = new HashSet<Block>();
|
||||||
|
|
||||||
|
for (Block b : blocks) {
|
||||||
|
|
||||||
|
for (BlockFace bf : searchDirections) {
|
||||||
|
Block bb = b.getRelative(bf);
|
||||||
|
|
||||||
|
if (!blocks.contains(bb)) {
|
||||||
|
frameBlocks.add(bb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return frameBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the all frame blocks of an gate.
|
||||||
|
* @param locations All locations inside the gate.
|
||||||
|
* @return A Set containing all frame block. Will never return 'null'.
|
||||||
|
*/
|
||||||
|
public static Set<Block> getFrameWithLocations(Set<Location> locations)
|
||||||
|
{
|
||||||
|
Set<Block> blocks = new HashSet<Block>();
|
||||||
|
|
||||||
|
for (Location l : locations) {
|
||||||
|
blocks.add(l.getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFrame(blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// For the same frame and location this set of blocks is deterministic
|
// For the same frame and location this set of blocks is deterministic
|
||||||
public static Set<Block> getGateFrameBlocks(Block block)
|
public static Set<Block> getGatePortalBlocks(Block block)
|
||||||
{
|
{
|
||||||
int frameBlockSearchLimit = Plugin.getPlugin().getConfig().getInt(Plugin.confMaxGateBlocksKey);
|
int frameBlockSearchLimit = Plugin.getPlugin().getConfig().getInt(Plugin.confMaxGateBlocksKey);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user