mirror of
https://github.com/BentoBoxWorld/BentoBox.git
synced 2024-10-01 16:17:30 +02:00
Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop
This commit is contained in:
commit
44003773c7
@ -28,7 +28,6 @@ import world.bentobox.bentobox.listeners.BannedVisitorCommands;
|
||||
import world.bentobox.bentobox.listeners.BlockEndDragon;
|
||||
import world.bentobox.bentobox.listeners.DeathListener;
|
||||
import world.bentobox.bentobox.listeners.JoinLeaveListener;
|
||||
import world.bentobox.bentobox.listeners.NetherTreesListener;
|
||||
import world.bentobox.bentobox.listeners.PanelListenerManager;
|
||||
import world.bentobox.bentobox.listeners.PortalTeleportationListener;
|
||||
import world.bentobox.bentobox.listeners.StandardSpawnProtectionListener;
|
||||
@ -232,8 +231,6 @@ public class BentoBox extends JavaPlugin {
|
||||
manager.registerEvents(new StandardSpawnProtectionListener(this), this);
|
||||
// Nether portals
|
||||
manager.registerEvents(new PortalTeleportationListener(this), this);
|
||||
// Nether trees conversion
|
||||
manager.registerEvents(new NetherTreesListener(this), this);
|
||||
// End dragon blocking
|
||||
manager.registerEvents(new BlockEndDragon(this), this);
|
||||
// Banned visitor commands
|
||||
|
@ -171,11 +171,6 @@ public interface WorldSettings extends ConfigObject {
|
||||
*/
|
||||
boolean isNetherIslands();
|
||||
|
||||
/**
|
||||
* @return the netherTrees
|
||||
*/
|
||||
boolean isNetherTrees();
|
||||
|
||||
/**
|
||||
* @return the onJoinResetEnderChest
|
||||
*/
|
||||
@ -251,6 +246,12 @@ public interface WorldSettings extends ConfigObject {
|
||||
*/
|
||||
boolean isDeathsCounted();
|
||||
|
||||
/**
|
||||
* @return true if deaths in the world are reset when the player has a new island
|
||||
* @since 1.6.0
|
||||
*/
|
||||
boolean isDeathsResetOnNewIsland();
|
||||
|
||||
/**
|
||||
* @return whether a player can set their home in the Nether or not.
|
||||
*/
|
||||
|
@ -77,6 +77,7 @@ public class Flag implements Comparable<Flag> {
|
||||
private final boolean subPanel;
|
||||
private Set<GameModeAddon> gameModes = new HashSet<>();
|
||||
private final Addon addon;
|
||||
private final int cooldown;
|
||||
|
||||
private Flag(Builder builder) {
|
||||
this.id = builder.id;
|
||||
@ -90,6 +91,7 @@ public class Flag implements Comparable<Flag> {
|
||||
if (builder.gameModeAddon != null) {
|
||||
this.gameModes.add(builder.gameModeAddon);
|
||||
}
|
||||
this.cooldown = builder.cooldown;
|
||||
this.addon = builder.addon;
|
||||
}
|
||||
|
||||
@ -105,6 +107,13 @@ public class Flag implements Comparable<Flag> {
|
||||
return Optional.ofNullable(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cooldown
|
||||
*/
|
||||
public int getCooldown() {
|
||||
return cooldown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a setting is set in this world
|
||||
* @param world - world
|
||||
@ -322,6 +331,9 @@ public class Flag implements Comparable<Flag> {
|
||||
: user.getTranslation("protection.panel.flag-item.setting-disabled");
|
||||
pib.description(user.getTranslation("protection.panel.flag-item.setting-layout", TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())
|
||||
, "[setting]", islandSetting));
|
||||
if (this.cooldown > 0 && island.isCooldown(this)) {
|
||||
pib.description(user.getTranslation("protection.panel.flag-item.setting-cooldown"));
|
||||
}
|
||||
}
|
||||
return pib;
|
||||
}
|
||||
@ -384,6 +396,9 @@ public class Flag implements Comparable<Flag> {
|
||||
private GameModeAddon gameModeAddon;
|
||||
private Addon addon;
|
||||
|
||||
// Cooldown
|
||||
private int cooldown;
|
||||
|
||||
/**
|
||||
* Builder for making flags
|
||||
* @param id - a unique id that MUST be the same as the enum of the flag
|
||||
@ -476,6 +491,17 @@ public class Flag implements Comparable<Flag> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cooldown for {@link Type#SETTING} flag. Only applicable for settings.
|
||||
* @param cooldown in seconds
|
||||
* @return Builder
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public Builder cooldown(int cooldown) {
|
||||
this.cooldown = cooldown;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the flag
|
||||
* @return Flag
|
||||
|
@ -61,9 +61,17 @@ public class IslandToggleClick implements ClickHandler {
|
||||
// Save changes
|
||||
plugin.getIWM().getAddon(user.getWorld()).ifPresent(GameModeAddon::saveWorldSettings);
|
||||
} else {
|
||||
// Check cooldown
|
||||
if (!user.isOp() && island.isCooldown(flag)) {
|
||||
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 1F, 1F);
|
||||
user.notify("protection.panel.flag-item.setting-cooldown");
|
||||
return;
|
||||
}
|
||||
// Toggle flag
|
||||
island.toggleFlag(flag);
|
||||
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
|
||||
// Set cooldown
|
||||
island.setCooldown(flag);
|
||||
}
|
||||
// Apply change to panel
|
||||
panel.getInventory().setItem(slot, flag.toPanelItem(plugin, user, invisible).getItem());
|
||||
|
@ -35,6 +35,7 @@ import world.bentobox.bentobox.api.logs.LogEntry;
|
||||
import world.bentobox.bentobox.api.user.User;
|
||||
import world.bentobox.bentobox.database.objects.adapters.Adapter;
|
||||
import world.bentobox.bentobox.database.objects.adapters.FlagSerializer;
|
||||
import world.bentobox.bentobox.database.objects.adapters.FlagSerializer3;
|
||||
import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter;
|
||||
import world.bentobox.bentobox.lists.Flags;
|
||||
import world.bentobox.bentobox.managers.IslandWorldManager;
|
||||
@ -146,6 +147,13 @@ public class Island implements DataObject {
|
||||
@Expose
|
||||
private boolean doNotLoad;
|
||||
|
||||
/**
|
||||
* Used to store flag cooldowns for this island
|
||||
*/
|
||||
@Adapter(FlagSerializer3.class)
|
||||
@Expose
|
||||
private Map<Flag, Long> cooldowns = new HashMap<>();
|
||||
|
||||
public Island() {}
|
||||
|
||||
public Island(@NonNull Location location, UUID owner, int protectionRange) {
|
||||
@ -1049,6 +1057,43 @@ public class Island implements DataObject {
|
||||
!getCenter().toVector().toLocation(iwm.getEndWorld(getWorld())).getBlock().getType().equals(Material.AIR);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a flag is on cooldown. Only stored in memory so a server restart will reset the cooldown.
|
||||
* @param flag - flag
|
||||
* @return true if on cooldown, false if not
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public boolean isCooldown(Flag flag) {
|
||||
if (cooldowns.containsKey(flag) && cooldowns.get(flag) > System.currentTimeMillis()) {
|
||||
return true;
|
||||
}
|
||||
cooldowns.remove(flag);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a cooldown for this flag on this island.
|
||||
* @param flag - Flag to cooldown
|
||||
*/
|
||||
public void setCooldown(Flag flag) {
|
||||
cooldowns.put(flag, flag.getCooldown() * 1000 + System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cooldowns
|
||||
*/
|
||||
public Map<Flag, Long> getCooldowns() {
|
||||
return cooldowns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cooldowns the cooldowns to set
|
||||
*/
|
||||
public void setCooldowns(Map<Flag, Long> cooldowns) {
|
||||
this.cooldowns = cooldowns;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@ -1061,4 +1106,6 @@ public class Island implements DataObject {
|
||||
+ ", purgeProtected=" + purgeProtected + ", flags=" + flags + ", history=" + history
|
||||
+ ", levelHandicap=" + levelHandicap + ", spawnPoint=" + spawnPoint + ", doNotLoad=" + doNotLoad + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,57 @@
|
||||
package world.bentobox.bentobox.database.objects.adapters;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.configuration.MemorySection;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.api.flags.Flag;
|
||||
|
||||
/**
|
||||
* Serializes the {@link world.bentobox.bentobox.database.objects.Island#getFlags() getFlags()} and
|
||||
* {@link world.bentobox.bentobox.database.objects.Island#setFlags(Map)} () setFlags()}
|
||||
* in {@link world.bentobox.bentobox.database.objects.Island}
|
||||
* @author tastybento
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public class FlagSerializer3 implements AdapterInterface<Map<Flag, Long>, Map<String, Long>> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<Flag, Long> deserialize(Object object) {
|
||||
Map<Flag, Long> result = new HashMap<>();
|
||||
if (object == null) {
|
||||
return result;
|
||||
}
|
||||
// For YAML
|
||||
if (object instanceof MemorySection) {
|
||||
MemorySection section = (MemorySection) object;
|
||||
for (String key : section.getKeys(false)) {
|
||||
BentoBox.getInstance().getFlagsManager().getFlag(key).ifPresent(flag -> result.put(flag, section.getLong(key)));
|
||||
}
|
||||
} else {
|
||||
for (Entry<String, Long> en : ((Map<String, Long>)object).entrySet()) {
|
||||
BentoBox.getInstance().getFlagsManager().getFlag(en.getKey()).ifPresent(flag -> result.put(flag, en.getValue()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<String, Long> serialize(Object object) {
|
||||
Map<String, Long> result = new HashMap<>();
|
||||
if (object == null) {
|
||||
return result;
|
||||
}
|
||||
Map<Flag, Long> flags = (Map<Flag, Long>)object;
|
||||
for (Entry<Flag, Long> en: flags.entrySet()) {
|
||||
if (en != null && en.getKey() != null) {
|
||||
result.put(en.getKey().getID(), en.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package world.bentobox.bentobox.listeners;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Tag;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.StructureGrowEvent;
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.api.configuration.WorldSettings;
|
||||
|
||||
/**
|
||||
* Handles conversion of trees in the Nether if {@link WorldSettings#isNetherTrees()} is {@code true}.
|
||||
*
|
||||
* @author tastybento
|
||||
*/
|
||||
public class NetherTreesListener implements Listener {
|
||||
|
||||
private BentoBox plugin;
|
||||
|
||||
public NetherTreesListener(@NonNull BentoBox plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts trees to gravel and glowstone.
|
||||
*
|
||||
* @param e event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void onTreeGrow(StructureGrowEvent e) {
|
||||
// Don't do anything if we're not in the right place.
|
||||
if (!plugin.getIWM().isNetherTrees(e.getWorld()) || !e.getWorld().getEnvironment().equals(World.Environment.NETHER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Modify everything!
|
||||
for (BlockState b : e.getBlocks()) {
|
||||
if (Tag.LOGS.isTagged(b.getType())) {
|
||||
b.setType(Material.GRAVEL);
|
||||
} else if (Tag.LEAVES.isTagged(b.getType())) {
|
||||
b.setType(Material.GLOWSTONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -256,11 +256,11 @@ public final class Flags {
|
||||
*/
|
||||
// PVP
|
||||
public static final Flag PVP_OVERWORLD = new Flag.Builder("PVP_OVERWORLD", Material.ARROW).type(Type.SETTING)
|
||||
.defaultRank(DISABLED).listener(new PVPListener()).build();
|
||||
.defaultRank(DISABLED).listener(new PVPListener()).cooldown(60).build();
|
||||
public static final Flag PVP_NETHER = new Flag.Builder("PVP_NETHER", Material.IRON_AXE).type(Type.SETTING)
|
||||
.defaultRank(DISABLED).build();
|
||||
.defaultRank(DISABLED).cooldown(60).build();
|
||||
public static final Flag PVP_END = new Flag.Builder("PVP_END", Material.END_CRYSTAL).type(Type.SETTING)
|
||||
.defaultRank(DISABLED).build();
|
||||
.defaultRank(DISABLED).cooldown(60).build();
|
||||
|
||||
// Fire
|
||||
/**
|
||||
|
@ -416,16 +416,6 @@ public class IslandWorldManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if nether trees should be created in the nether or not
|
||||
*
|
||||
* @param world - world
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isNetherTrees(@Nullable World world) {
|
||||
return world != null && (gameModes.containsKey(world) && gameModes.get(world).getWorldSettings().isNetherTrees());
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the End Dragon can spawn or not in this world
|
||||
*
|
||||
@ -700,10 +690,26 @@ public class IslandWorldManager {
|
||||
gameModes.get(world).getWorldSettings().setResetEpoch(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the death count should be reset when joining a team in this world
|
||||
* @param world - world
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isTeamJoinDeathReset(@NonNull World world) {
|
||||
return gameModes.get(world).getWorldSettings().isTeamJoinDeathReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if deaths in the world are reset when the player starts a new island.
|
||||
* This includes a totally new island and also a new island from a reset.
|
||||
* @param world - world
|
||||
* @return true or false
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public boolean isDeathsResetOnNewIsland(@NonNull World world) {
|
||||
return gameModes.get(world).getWorldSettings().isDeathsResetOnNewIsland();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum number of deaths allowed in this world
|
||||
* @param world - world
|
||||
|
@ -70,11 +70,18 @@ public class WebManager {
|
||||
topicsContent = repo.getContent("catalog/topics.json").getContent().replaceAll("\\n", "");
|
||||
catalogContent = repo.getContent("catalog/catalog.json").getContent().replaceAll("\\n", "");
|
||||
} catch (IllegalAccessException e) {
|
||||
plugin.log("Could not connect to GitHub.");
|
||||
if (plugin.getSettings().isLogGithubDownloadData()) {
|
||||
plugin.log("Could not connect to GitHub.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.logError("An error occurred when downloading from GitHub...");
|
||||
plugin.logError("An error occurred when downloading data from GitHub...");
|
||||
plugin.logStacktrace(e);
|
||||
}
|
||||
|
||||
// People were concerned that the download took ages, so we need to tell them it's over now.
|
||||
if (plugin.getSettings().isLogGithubDownloadData()) {
|
||||
plugin.log("Successfully downloaded data from GitHub.");
|
||||
}
|
||||
|
||||
// Decoding the Base64 encoded contents
|
||||
tagsContent = new String(Base64.getDecoder().decode(tagsContent), StandardCharsets.UTF_8);
|
||||
|
@ -176,6 +176,11 @@ public class NewIsland {
|
||||
// Set home location
|
||||
plugin.getPlayers().setHomeLocation(user, new Location(next.getWorld(), next.getX() + 0.5D, next.getY(), next.getZ() + 0.5D), 1);
|
||||
|
||||
// Reset deaths
|
||||
if (plugin.getIWM().isDeathsResetOnNewIsland(world)) {
|
||||
plugin.getPlayers().setDeaths(world, user.getUniqueId(), 0);
|
||||
}
|
||||
|
||||
// Save the player so that if the server crashes weird things won't happen
|
||||
plugin.getPlayers().save(user.getUniqueId());
|
||||
|
||||
|
@ -1053,6 +1053,7 @@ protection:
|
||||
blocked-rank: "&3- &c"
|
||||
minimal-rank: "&3- &2"
|
||||
menu-layout: "&a[description]"
|
||||
setting-cooldown: "&cSetting is on cooldown"
|
||||
setting-layout: |
|
||||
&a[description]
|
||||
|
||||
|
@ -59,7 +59,8 @@ public class MariaDBDatabaseHandlerTest {
|
||||
" \"history\": [],\n" +
|
||||
" \"levelHandicap\": 0,\n" +
|
||||
" \"spawnPoint\": {},\n" +
|
||||
" \"doNotLoad\": false\n" +
|
||||
" \"doNotLoad\": false,\n" +
|
||||
" \"cooldowns\": {}\n" +
|
||||
"}";
|
||||
private MariaDBDatabaseHandler<Island> handler;
|
||||
private Island instance;
|
||||
@ -278,7 +279,8 @@ public class MariaDBDatabaseHandlerTest {
|
||||
" \"history\": [],\n" +
|
||||
" \"levelHandicap\": 0,\n" +
|
||||
" \"spawnPoint\": {},\n" +
|
||||
" \"doNotLoad\": false\n" +
|
||||
" \"doNotLoad\": false,\n" +
|
||||
" \"cooldowns\": {}\n" +
|
||||
"}");
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,8 @@ public class MySQLDatabaseHandlerTest {
|
||||
" \"history\": [],\n" +
|
||||
" \"levelHandicap\": 0,\n" +
|
||||
" \"spawnPoint\": {},\n" +
|
||||
" \"doNotLoad\": false\n" +
|
||||
" \"doNotLoad\": false,\n" +
|
||||
" \"cooldowns\": {}\n" +
|
||||
"}";
|
||||
private MySQLDatabaseHandler<Island> handler;
|
||||
private Island instance;
|
||||
@ -277,7 +278,8 @@ public class MySQLDatabaseHandlerTest {
|
||||
" \"history\": [],\n" +
|
||||
" \"levelHandicap\": 0,\n" +
|
||||
" \"spawnPoint\": {},\n" +
|
||||
" \"doNotLoad\": false\n" +
|
||||
" \"doNotLoad\": false,\n" +
|
||||
" \"cooldowns\": {}\n" +
|
||||
"}");
|
||||
}
|
||||
|
||||
|
@ -1,49 +0,0 @@
|
||||
package world.bentobox.bentobox.listeners;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
import org.powermock.reflect.Whitebox;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.managers.IslandWorldManager;
|
||||
|
||||
/**
|
||||
* Tests {@link NetherTreesListener}.
|
||||
*
|
||||
* @author Poslovitch
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Ignore
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({BentoBox.class})
|
||||
public class NetherTreesListenerTest {
|
||||
|
||||
/* Plugin */
|
||||
private BentoBox plugin;
|
||||
|
||||
/* Island World Manager */
|
||||
private IslandWorldManager iwm;
|
||||
|
||||
/* Listener */
|
||||
private NetherTreesListener listener;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
/* Plugin */
|
||||
plugin = mock(BentoBox.class);
|
||||
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
|
||||
|
||||
/* Island World Manager */
|
||||
iwm = mock(IslandWorldManager.class);
|
||||
when(plugin.getIWM()).thenReturn(iwm);
|
||||
|
||||
/* Listener */
|
||||
listener = new NetherTreesListener(plugin);
|
||||
}
|
||||
}
|
@ -402,22 +402,6 @@ public class IslandWorldManagerTest {
|
||||
assertNull(iwm.getEndWorld(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link world.bentobox.bentobox.managers.IslandWorldManager#isNetherTrees(org.bukkit.World)}.
|
||||
*/
|
||||
@Test
|
||||
public void testIsNetherTrees() {
|
||||
assertFalse(iwm.isNetherTrees(netherWorld));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link world.bentobox.bentobox.managers.IslandWorldManager#isNetherTrees(org.bukkit.World)}.
|
||||
*/
|
||||
@Test
|
||||
public void testIsNetherTreesNull() {
|
||||
assertFalse(iwm.isNetherTrees(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link world.bentobox.bentobox.managers.IslandWorldManager#isDragonSpawn(org.bukkit.World)}.
|
||||
*/
|
||||
|
@ -1,6 +1,3 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package world.bentobox.bentobox.managers;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@ -32,7 +29,6 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
|
Loading…
Reference in New Issue
Block a user