mirror of
https://github.com/songoda/SongodaCore.git
synced 2024-11-27 12:35:12 +01:00
Merge branch 'development' into 'master'
2.0.7 See merge request Songoda/songodaupdater!10
This commit is contained in:
commit
07b40f604c
@ -4,7 +4,7 @@ stages:
|
||||
variables:
|
||||
name: "SongodaCore"
|
||||
path: "/builds/$CI_PROJECT_PATH"
|
||||
version: "2.0.6"
|
||||
version: "2.0.7"
|
||||
|
||||
build:
|
||||
stage: build
|
||||
|
@ -46,7 +46,7 @@ public class SongodaCore {
|
||||
* Whenever we make a major change to the core GUI, updater,
|
||||
* or other function used by the core, increment this number
|
||||
*/
|
||||
private final static int coreRevision = 2;
|
||||
private final static int coreRevision = 3;
|
||||
private final static int updaterVersion = 1;
|
||||
|
||||
private final static Set<PluginInfo> registeredPlugins = new HashSet<>();
|
||||
|
@ -293,7 +293,7 @@ public enum CompatibleSounds {
|
||||
ENTITY_DROWNED_SHOOT,
|
||||
ENTITY_DROWNED_STEP,
|
||||
ENTITY_DROWNED_SWIM,
|
||||
ENTITY_EGG_THROW,
|
||||
ENTITY_EGG_THROW(ServerVersion.V1_9, v("SHOOT_ARROW", true)),
|
||||
ENTITY_ELDER_GUARDIAN_AMBIENT,
|
||||
ENTITY_ELDER_GUARDIAN_AMBIENT_LAND,
|
||||
ENTITY_ELDER_GUARDIAN_CURSE,
|
||||
@ -767,7 +767,7 @@ public enum CompatibleSounds {
|
||||
ITEM_CROSSBOW_QUICK_CHARGE_3,
|
||||
ITEM_CROSSBOW_SHOOT,
|
||||
ITEM_ELYTRA_FLYING,
|
||||
ITEM_FIRECHARGE_USE,
|
||||
ITEM_FIRECHARGE_USE(ServerVersion.V1_9, v("GHAST_FIREBALL", true)),
|
||||
ITEM_FLINTANDSTEEL_USE("FIRE_IGNITE"),
|
||||
ITEM_HOE_TILL,
|
||||
ITEM_NETHER_WART_PLANT,
|
||||
|
@ -6,6 +6,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
/**
|
||||
@ -932,7 +933,7 @@ public enum LegacyMaterials {
|
||||
TNT_MINECART("EXPLOSIVE_MINECART"),
|
||||
TORCH,
|
||||
TOTEM_OF_UNDYING("TOTEM"),
|
||||
TRADER_LLAMA_SPAWN_EGG(ServerVersion.V1_14, "LLAMA_SPAWN_EGG"),
|
||||
TRADER_LLAMA_SPAWN_EGG(),
|
||||
TRAPPED_CHEST,
|
||||
TRIDENT,
|
||||
TRIPWIRE,
|
||||
@ -1952,8 +1953,8 @@ public enum LegacyMaterials {
|
||||
case PURPLE_WALL_BANNER:
|
||||
case REDSTONE_WALL_TORCH:
|
||||
case REDSTONE_WIRE:
|
||||
case RED_TULIP:
|
||||
case SKELETON_SPAWN_EGG:
|
||||
case RED_WALL_BANNER:
|
||||
case SKELETON_WALL_SKULL:
|
||||
case SPRUCE_WALL_SIGN:
|
||||
case SWEET_BERRY_BUSH:
|
||||
case TALL_SEAGRASS:
|
||||
@ -1970,6 +1971,8 @@ public enum LegacyMaterials {
|
||||
switch (this) {
|
||||
case ACACIA_WOOD:
|
||||
case BIRCH_WOOD:
|
||||
case BREWING_STAND:
|
||||
case CAULDRON:
|
||||
case DARK_OAK_WOOD:
|
||||
case JUNGLE_WOOD:
|
||||
case OAK_WOOD:
|
||||
@ -2024,6 +2027,13 @@ public enum LegacyMaterials {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static LegacyMaterials getSpawnEgg(EntityType type) {
|
||||
if(type == EntityType.MUSHROOM_COW) {
|
||||
return MOOSHROOM_SPAWN_EGG;
|
||||
}
|
||||
return lookupMap.get(type.name() + "_SPAWN_EGG");
|
||||
}
|
||||
|
||||
public static LegacyMaterials getGlassPaneColor(int color) {
|
||||
switch (color) {
|
||||
case 0:
|
||||
|
@ -126,16 +126,18 @@ public class Config extends ConfigSection {
|
||||
fileName = file;
|
||||
}
|
||||
|
||||
public Config(@NotNull Plugin plugin, @NotNull String directory, @NotNull String file) {
|
||||
public Config(@NotNull Plugin plugin, @Nullable String directory, @NotNull String file) {
|
||||
this.plugin = plugin;
|
||||
dirName = directory;
|
||||
fileName = file;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigFileConfigurationAdapter getFileConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public File getFile() {
|
||||
if (file == null) {
|
||||
if (dirName != null) {
|
||||
@ -175,6 +177,7 @@ public class Config extends ConfigSection {
|
||||
* @param autosaveInterval time in seconds
|
||||
* @return this class
|
||||
*/
|
||||
@NotNull
|
||||
public Config setAutosaveInterval(int autosaveInterval) {
|
||||
this.autosaveInterval = autosaveInterval;
|
||||
return this;
|
||||
@ -202,6 +205,7 @@ public class Config extends ConfigSection {
|
||||
/**
|
||||
* Default comment applied to config nodes
|
||||
*/
|
||||
@Nullable
|
||||
public ConfigFormattingRules.CommentStyle getDefaultNodeCommentFormat() {
|
||||
return defaultNodeCommentFormat;
|
||||
}
|
||||
@ -211,7 +215,8 @@ public class Config extends ConfigSection {
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
public Config setDefaultNodeCommentFormat(ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
|
||||
@NotNull
|
||||
public Config setDefaultNodeCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
|
||||
this.defaultNodeCommentFormat = defaultNodeCommentFormat;
|
||||
return this;
|
||||
}
|
||||
@ -219,6 +224,7 @@ public class Config extends ConfigSection {
|
||||
/**
|
||||
* Default comment applied to section nodes
|
||||
*/
|
||||
@Nullable
|
||||
public ConfigFormattingRules.CommentStyle getDefaultSectionCommentFormat() {
|
||||
return defaultSectionCommentFormat;
|
||||
}
|
||||
@ -228,7 +234,8 @@ public class Config extends ConfigSection {
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
public Config setDefaultSectionCommentFormat(ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
|
||||
@NotNull
|
||||
public Config setDefaultSectionCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
|
||||
this.defaultSectionCommentFormat = defaultSectionCommentFormat;
|
||||
return this;
|
||||
}
|
||||
@ -245,6 +252,7 @@ public class Config extends ConfigSection {
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
@NotNull
|
||||
public Config setRootNodeSpacing(int rootNodeSpacing) {
|
||||
this.rootNodeSpacing = rootNodeSpacing;
|
||||
return this;
|
||||
@ -264,6 +272,7 @@ public class Config extends ConfigSection {
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
@NotNull
|
||||
public Config setCommentSpacing(int commentSpacing) {
|
||||
this.commentSpacing = commentSpacing;
|
||||
return this;
|
||||
|
@ -0,0 +1,35 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
public interface DataStoreObject<T> {
|
||||
|
||||
/**
|
||||
* @return a unique hashable instance of T to store this value under
|
||||
*/
|
||||
public abstract T getKey();
|
||||
|
||||
/**
|
||||
* @return a unique identifier for saving this value with
|
||||
*/
|
||||
public abstract String getConfigKey();
|
||||
|
||||
/**
|
||||
* Save this data to a ConfigurationSection
|
||||
*
|
||||
* @param sec
|
||||
*/
|
||||
public abstract void saveToSection(ConfigurationSection sec);
|
||||
|
||||
/**
|
||||
* @return true if this data has changed from the state saved to file
|
||||
*/
|
||||
public boolean hasChanged();
|
||||
|
||||
/**
|
||||
* Mark this data as needing a save or not
|
||||
*
|
||||
* @param isChanged
|
||||
*/
|
||||
public void setChanged(boolean isChanged);
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Used to easily store a set of one data value
|
||||
*
|
||||
* @param <T> DataObject class that is used to store the data
|
||||
* @since 2019-09-06
|
||||
* @author jascotty2
|
||||
*/
|
||||
public class SimpleDataStore<T extends DataStoreObject> {
|
||||
|
||||
protected final Plugin plugin;
|
||||
protected final String filename, dirName;
|
||||
private final Function<ConfigurationSection, T> getFromSection;
|
||||
protected final HashMap<Object, T> data = new HashMap();
|
||||
private File file;
|
||||
private final Object lock = new Object();
|
||||
SaveTask saveTask;
|
||||
Timer autosaveTimer;
|
||||
/**
|
||||
* time in seconds to start a save after a change is made
|
||||
*/
|
||||
int autosaveInterval = 60;
|
||||
|
||||
public SimpleDataStore(@NotNull Plugin plugin, @NotNull String filename, @NotNull Function<ConfigurationSection, T> loadFunction) {
|
||||
this.plugin = plugin;
|
||||
this.filename = filename;
|
||||
dirName = null;
|
||||
this.getFromSection = loadFunction;
|
||||
}
|
||||
|
||||
public SimpleDataStore(@NotNull Plugin plugin, @Nullable String directory, @NotNull String filename, @NotNull Function<ConfigurationSection, T> loadFunction) {
|
||||
this.plugin = plugin;
|
||||
this.filename = filename;
|
||||
this.dirName = directory;
|
||||
this.getFromSection = loadFunction;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public File getFile() {
|
||||
if (file == null) {
|
||||
if (dirName != null) {
|
||||
this.file = new File(plugin.getDataFolder() + dirName, filename != null ? filename : "data.yml");
|
||||
} else {
|
||||
this.file = new File(plugin.getDataFolder(), filename != null ? filename : "data.yml");
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a directly-modifiable instance of the data mapping for this
|
||||
* storage
|
||||
*/
|
||||
public Map<Object, T> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or {@code null}
|
||||
* if this map contains no mapping for the key.
|
||||
*
|
||||
* @param key key whose mapping is to be retrieved from this storage
|
||||
* @return the value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
*/
|
||||
@Nullable
|
||||
public T get(Object key) {
|
||||
return data.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for the specified key from this storage if present.
|
||||
*
|
||||
* @param key key whose mapping is to be removed from this storage
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
*/
|
||||
@Nullable
|
||||
public T remove(@NotNull Object key) {
|
||||
T temp;
|
||||
synchronized (lock) {
|
||||
temp = data.remove(key);
|
||||
}
|
||||
save();
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for the specified key from this storage if present.
|
||||
*
|
||||
* @param value value whose mapping is to be removed from this storage
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
*/
|
||||
@Nullable
|
||||
public T remove(@NotNull T value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
T temp;
|
||||
synchronized (lock) {
|
||||
temp = data.remove(value.getKey());
|
||||
}
|
||||
save();
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified value in this storage. If the map previously contained
|
||||
* a mapping for the key, the old value is replaced.
|
||||
*
|
||||
* @param value value to be added
|
||||
* @return the previous value associated with <tt>value.getKey()</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>value.getKey()</tt>.
|
||||
*/
|
||||
@Nullable
|
||||
public T add(@NotNull T value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
T temp;
|
||||
synchronized (lock) {
|
||||
temp = data.put(value.getKey(), value);
|
||||
}
|
||||
save();
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified value in this storage. If the map previously contained
|
||||
* a mapping for the key, the old value is replaced.
|
||||
*
|
||||
* @param value values to be added
|
||||
*/
|
||||
@Nullable
|
||||
public void addAll(@NotNull T[] value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (lock) {
|
||||
for (int i = 0; i < value.length; ++i) {
|
||||
if (value[i] != null) {
|
||||
data.put(value[i].getKey(), value[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified value in this storage. If the map previously contained
|
||||
* a mapping for the key, the old value is replaced.
|
||||
*
|
||||
* @param value values to be added
|
||||
*/
|
||||
@Nullable
|
||||
public void addAll(@NotNull Collection<T> value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (lock) {
|
||||
for (T v : value) {
|
||||
if (v != null) {
|
||||
data.put(v.getKey(), v);
|
||||
}
|
||||
}
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from the associated file
|
||||
*/
|
||||
public void load() {
|
||||
if (!getFile().exists()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
YamlConfiguration f = new YamlConfiguration();
|
||||
f.options().pathSeparator('\0');
|
||||
f.load(file);
|
||||
|
||||
synchronized (lock) {
|
||||
data.clear();
|
||||
f.getValues(false).entrySet().stream()
|
||||
.filter(d -> d.getValue() instanceof ConfigurationSection)
|
||||
.map(Map.Entry::getValue)
|
||||
.map(v -> getFromSection.apply((ConfigurationSection) v))
|
||||
.forEach(v -> data.put(v.getKey(), v));
|
||||
}
|
||||
} catch (IOException | InvalidConfigurationException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to load data from " + file.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally save this storage's data to file if there have been changes
|
||||
* made
|
||||
*/
|
||||
public void saveChanges() {
|
||||
if (saveTask != null || data.values().stream().anyMatch(v -> v.hasChanged())) {
|
||||
flushSave();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save this file data. This saves later asynchronously.
|
||||
*/
|
||||
public void save() {
|
||||
// save async even if no plugin or if plugin disabled
|
||||
if (saveTask == null) {
|
||||
autosaveTimer = new Timer((plugin != null ? plugin.getName() + "-DataStoreSave-" : "DataStoreSave-") + getFile().getName());
|
||||
autosaveTimer.schedule(saveTask = new SaveTask(), autosaveInterval * 1000L);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a new save of this storage's data
|
||||
*/
|
||||
public void flushSave() {
|
||||
if (saveTask != null) {
|
||||
//Close Threads
|
||||
saveTask.cancel();
|
||||
autosaveTimer.cancel();
|
||||
saveTask = null;
|
||||
autosaveTimer = null;
|
||||
}
|
||||
YamlConfiguration f = new YamlConfiguration();
|
||||
synchronized (lock) {
|
||||
data.values().stream().forEach(e -> e.saveToSection(f.createSection(e.getConfigKey())));
|
||||
}
|
||||
try {
|
||||
f.save(getFile());
|
||||
data.values().stream().forEach(e -> e.setChanged(false));
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to save data to " + file.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
class SaveTask extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
flushSave();
|
||||
}
|
||||
}
|
||||
}
|
335
src/main/java/com/songoda/core/gui/CustomizableGui.java
Normal file
335
src/main/java/com/songoda/core/gui/CustomizableGui.java
Normal file
@ -0,0 +1,335 @@
|
||||
package com.songoda.core.gui;
|
||||
|
||||
import com.songoda.core.compatibility.LegacyMaterials;
|
||||
import com.songoda.core.configuration.DataStoreObject;
|
||||
import com.songoda.core.configuration.SimpleDataStore;
|
||||
import com.songoda.core.gui.methods.Clickable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a GUI screen that can be user-configured
|
||||
*
|
||||
* @since 2019-09-06
|
||||
* @author jascotty2
|
||||
*/
|
||||
public class CustomizableGui extends Gui {
|
||||
|
||||
final Map<String, CustomButton> buttons;
|
||||
|
||||
public CustomizableGui(SimpleDataStore<CustomButton> buttons) {
|
||||
this((Map<String, CustomButton>) (Map) buttons.getData());
|
||||
}
|
||||
|
||||
public CustomizableGui(SimpleDataStore<CustomButton> buttons, Gui parent) {
|
||||
this((Map<String, CustomButton>) (Map) buttons.getData(), parent);
|
||||
}
|
||||
|
||||
public CustomizableGui(@NotNull Map<String, CustomButton> buttons) {
|
||||
this(buttons, null);
|
||||
}
|
||||
|
||||
public CustomizableGui(@NotNull Map<String, CustomButton> buttons, @Nullable Gui parent) {
|
||||
super(parent);
|
||||
this.buttons = buttons;
|
||||
if (buttons.containsKey("__DEFAULT__")) {
|
||||
blankItem = GuiUtils.getBorderItem(buttons.get("__DEFAULT__").icon);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomButton[] getButtons() {
|
||||
return buttons.values().toArray(new CustomButton[buttons.size()]);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CustomizableGui setDefaultItem(ItemStack item) {
|
||||
if ((blankItem = item) != null) {
|
||||
buttons.put("__DEFAULT__", (new CustomButton("__DEFAULT__")).setIcon(LegacyMaterials.getMaterial(item)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CustomButton getButton(@NotNull String key) {
|
||||
return key == null ? null : buttons.get(key.toLowerCase());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setItem(int defaultRow, int defaultCol, @NotNull String key, @NotNull ItemStack item) {
|
||||
final int cell = defaultCol + defaultRow * 9;
|
||||
return setItem(cell, key, item);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setItem(int defaultCell, @NotNull String key, @NotNull ItemStack item) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key = key.toLowerCase());
|
||||
if (btn == null) {
|
||||
buttons.put(key, btn = (new CustomButton(key, defaultCell)).setIcon(LegacyMaterials.getMaterial(item)));
|
||||
} else {
|
||||
ItemStack btnItem = btn.icon.getItem();
|
||||
ItemMeta itemMeta = item.getItemMeta();
|
||||
ItemMeta btnItemMeta = btnItem.getItemMeta();
|
||||
if (itemMeta != null && btnItemMeta != null) {
|
||||
btnItemMeta.setDisplayName(itemMeta.getDisplayName());
|
||||
btnItemMeta.setLore(itemMeta.getLore());
|
||||
btnItem.setItemMeta(itemMeta);
|
||||
}
|
||||
item = btnItem;
|
||||
}
|
||||
cellItems.put(btn.position, item);
|
||||
if (inventory != null && btn.position >= 0 && btn.position < inventory.getSize()) {
|
||||
inventory.setItem(btn.position, item);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setItem(int defaultRow, int defaultCol, @NotNull String key, @NotNull LegacyMaterials defaultItem, @NotNull String title, @NotNull String... lore) {
|
||||
final int cell = defaultCol + defaultRow * 9;
|
||||
return setItem(cell, key, defaultItem, title, lore);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setItem(int defaultCell, @NotNull String key, @NotNull LegacyMaterials defaultItem, @NotNull String title, @NotNull String... lore) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key = key.toLowerCase());
|
||||
if (btn == null) {
|
||||
buttons.put(key, btn = (new CustomButton(key, defaultCell)).setIcon(defaultItem));
|
||||
}
|
||||
ItemStack item = GuiUtils.createButtonItem(btn.icon, title, lore);
|
||||
cellItems.put(btn.position, item);
|
||||
if (inventory != null && btn.position >= 0 && btn.position < inventory.getSize()) {
|
||||
inventory.setItem(btn.position, item);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui highlightItem(@NotNull String key) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key.toLowerCase());
|
||||
if (btn != null) {
|
||||
this.highlightItem(btn.position);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui removeHighlight(@NotNull String key) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key.toLowerCase());
|
||||
if (btn != null) {
|
||||
this.removeHighlight(btn.position);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui updateItem(@NotNull String key, @Nullable String title, @NotNull String... lore) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key.toLowerCase());
|
||||
if (btn != null) {
|
||||
this.updateItem(btn.position, title, lore);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui updateItem(@NotNull String key, @Nullable String title, @Nullable List<String> lore) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key.toLowerCase());
|
||||
if (btn != null) {
|
||||
this.updateItem(btn.position, title, lore);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui updateItem(@NotNull String key, @NotNull LegacyMaterials itemTo, @NotNull String title, @NotNull String... lore) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key.toLowerCase());
|
||||
if (btn != null) {
|
||||
this.updateItem(btn.position, itemTo, title, lore);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui updateItem(@NotNull String key, @NotNull LegacyMaterials itemTo, @NotNull String title, @Nullable List<String> lore) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key.toLowerCase());
|
||||
if (btn != null) {
|
||||
this.updateItem(btn.position, itemTo, title, lore);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setAction(@NotNull String key, Clickable action) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key = key.toLowerCase());
|
||||
if (btn != null) {
|
||||
setConditional(btn.position, null, action);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setAction(@NotNull String key, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key = key.toLowerCase());
|
||||
if (btn != null) {
|
||||
setConditional(btn.position, type, action);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setButton(int defaultCell, @NotNull String key, ItemStack item, @Nullable Clickable action) {
|
||||
setItem(defaultCell, key, item);
|
||||
setAction(key, null, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setButton(int defaultCell, @NotNull String key, ItemStack item, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
setItem(defaultCell, key, item);
|
||||
setAction(key, type, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setButton(int defaultRow, int defaultCol, @NotNull String key, ItemStack item, @Nullable Clickable action) {
|
||||
final int defaultCell = defaultCol + defaultRow * 9;
|
||||
setItem(defaultCell, key, item);
|
||||
setAction(key, null, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CustomizableGui setButton(int defaultRow, int defaultCol, @NotNull String key, ItemStack item, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
final int defaultCell = defaultCol + defaultRow * 9;
|
||||
setItem(defaultCell, key, item);
|
||||
setAction(key, type, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CustomizableGui setNextPage(int row, int col, @NotNull ItemStack item) {
|
||||
return this.setNextPage(col + row * 9, item);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CustomizableGui setNextPage(int cell, @NotNull ItemStack item) {
|
||||
CustomButton btn = buttons.get("__NEXT__");
|
||||
if (btn == null) {
|
||||
buttons.put("__NEXT__", btn = (new CustomButton("__NEXT__", cell)).setIcon(LegacyMaterials.getMaterial(item)));
|
||||
} else {
|
||||
ItemStack btnItem = btn.icon.getItem();
|
||||
ItemMeta itemMeta = item.getItemMeta();
|
||||
ItemMeta btnItemMeta = btnItem.getItemMeta();
|
||||
if (itemMeta != null && btnItemMeta != null) {
|
||||
btnItemMeta.setDisplayName(itemMeta.getDisplayName());
|
||||
btnItemMeta.setLore(itemMeta.getLore());
|
||||
btnItem.setItemMeta(itemMeta);
|
||||
}
|
||||
item = btnItem;
|
||||
}
|
||||
return (CustomizableGui) super.setNextPage(btn.position, item);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CustomizableGui setPrevPage(int row, int col, @NotNull ItemStack item) {
|
||||
return this.setPrevPage(col + row * 9, item);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CustomizableGui setPrevPage(int cell, @NotNull ItemStack item) {
|
||||
CustomButton btn = buttons.get("__PREV__");
|
||||
if (btn == null) {
|
||||
buttons.put("__PREV__", btn = (new CustomButton("__PREV__", cell)).setIcon(LegacyMaterials.getMaterial(item)));
|
||||
} else {
|
||||
ItemStack btnItem = btn.icon.getItem();
|
||||
ItemMeta itemMeta = item.getItemMeta();
|
||||
ItemMeta btnItemMeta = btnItem.getItemMeta();
|
||||
if (itemMeta != null && btnItemMeta != null) {
|
||||
btnItemMeta.setDisplayName(itemMeta.getDisplayName());
|
||||
btnItemMeta.setLore(itemMeta.getLore());
|
||||
btnItem.setItemMeta(itemMeta);
|
||||
}
|
||||
item = btnItem;
|
||||
}
|
||||
return (CustomizableGui) super.setPrevPage(btn.position, item);
|
||||
}
|
||||
|
||||
public CustomizableGui clearActions(@NotNull String key) {
|
||||
CustomButton btn = key == null ? null : buttons.get(key = key.toLowerCase());
|
||||
if (btn != null) {
|
||||
this.clearActions(btn.position);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class CustomButton implements DataStoreObject<String> {
|
||||
|
||||
boolean _changed = false;
|
||||
final String key;
|
||||
int position = -1;
|
||||
LegacyMaterials icon = LegacyMaterials.STONE;
|
||||
|
||||
public CustomButton(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public CustomButton(String key, int position) {
|
||||
this.key = key;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public static CustomButton loadFromSection(ConfigurationSection sec) {
|
||||
CustomButton dat = new CustomButton(sec.getName());
|
||||
dat.icon = sec.contains("icon") ? LegacyMaterials.getMaterial(sec.getString("icon"), LegacyMaterials.STONE) : LegacyMaterials.STONE;
|
||||
dat.position = sec.getInt("position");
|
||||
return dat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToSection(ConfigurationSection sec) {
|
||||
sec.set("icon", icon.name());
|
||||
sec.set("position", position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasChanged() {
|
||||
return _changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChanged(boolean isChanged) {
|
||||
_changed = isChanged;
|
||||
}
|
||||
|
||||
public LegacyMaterials getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public CustomButton setIcon(LegacyMaterials icon) {
|
||||
this.icon = icon != null ? icon : LegacyMaterials.STONE;
|
||||
_changed = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* TODO: animated buttons
|
||||
@ -62,8 +64,8 @@ public class Gui {
|
||||
this.rows = 3;
|
||||
}
|
||||
|
||||
public Gui(GuiType type) {
|
||||
this.inventoryType = type;
|
||||
public Gui(@NotNull GuiType type) {
|
||||
this.inventoryType = type != null ? type : GuiType.STANDARD;
|
||||
switch (type) {
|
||||
case HOPPER:
|
||||
case DISPENSER:
|
||||
@ -74,7 +76,7 @@ public class Gui {
|
||||
}
|
||||
}
|
||||
|
||||
public Gui(Gui parent) {
|
||||
public Gui(@Nullable Gui parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@ -82,17 +84,18 @@ public class Gui {
|
||||
this.rows = Math.max(1, Math.min(6, rows));
|
||||
}
|
||||
|
||||
public Gui(int rows, Gui parent) {
|
||||
public Gui(int rows, @Nullable Gui parent) {
|
||||
this.parent = parent;
|
||||
this.rows = Math.max(1, Math.min(6, rows));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Player> getPlayers() {
|
||||
return inventory == null ? Collections.EMPTY_LIST
|
||||
: inventory.getViewers().stream()
|
||||
.filter(e -> e instanceof Player)
|
||||
.map(e -> (Player) e)
|
||||
.collect(Collectors.toList());
|
||||
.filter(e -> e instanceof Player)
|
||||
.map(e -> (Player) e)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
@ -139,7 +142,8 @@ public class Gui {
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the GUI without calling onClose() and without opening any parent GUIs
|
||||
* Close the GUI without calling onClose() and without opening any parent
|
||||
* GUIs
|
||||
*/
|
||||
public void exit() {
|
||||
allowClose = true;
|
||||
@ -151,21 +155,25 @@ public class Gui {
|
||||
.forEach(Player::closeInventory);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GuiType getType() {
|
||||
return inventoryType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setUnlocked(int cell) {
|
||||
unlockedCells.put(cell, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setUnlocked(int row, int col) {
|
||||
final int cell = col + row * 9;
|
||||
unlockedCells.put(cell, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setUnlockedRange(int cellFirst, int cellLast) {
|
||||
for (int cell = cellFirst; cell <= cellLast; ++cell) {
|
||||
unlockedCells.put(cell, true);
|
||||
@ -173,6 +181,7 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setUnlockedRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast) {
|
||||
final int last = cellColLast + cellRowLast * 9;
|
||||
for (int cell = cellColFirst + cellRowFirst * 9; cell <= last; ++cell) {
|
||||
@ -181,17 +190,20 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setUnlocked(int cell, boolean open) {
|
||||
unlockedCells.put(cell, open);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setUnlocked(int row, int col, boolean open) {
|
||||
final int cell = col + row * 9;
|
||||
unlockedCells.put(cell, open);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setTitle(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
@ -201,6 +213,7 @@ public class Gui {
|
||||
return rows;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setRows(int rows) {
|
||||
switch (inventoryType) {
|
||||
case HOPPER:
|
||||
@ -212,20 +225,24 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setDefaultAction(Clickable action) {
|
||||
@NotNull
|
||||
public Gui setDefaultAction(@Nullable Clickable action) {
|
||||
defaultClicker = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setDefaultItem(ItemStack item) {
|
||||
blankItem = item;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ItemStack getDefaultItem() {
|
||||
return blankItem;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ItemStack getItem(int cell) {
|
||||
if (inventory != null && unlockedCells.getOrDefault(cell, false)) {
|
||||
return inventory.getItem(cell);
|
||||
@ -233,6 +250,7 @@ public class Gui {
|
||||
return cellItems.get(cell);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ItemStack getItem(int row, int col) {
|
||||
final int cell = col + row * 9;
|
||||
if (inventory != null && unlockedCells.getOrDefault(cell, false)) {
|
||||
@ -241,7 +259,8 @@ public class Gui {
|
||||
return cellItems.get(cell);
|
||||
}
|
||||
|
||||
public Gui setItem(int cell, ItemStack item) {
|
||||
@NotNull
|
||||
public Gui setItem(int cell, @Nullable ItemStack item) {
|
||||
cellItems.put(cell, item);
|
||||
if (inventory != null && cell >= 0 && cell < inventory.getSize()) {
|
||||
inventory.setItem(cell, item);
|
||||
@ -249,7 +268,8 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setItem(int row, int col, ItemStack item) {
|
||||
@NotNull
|
||||
public Gui setItem(int row, int col, @Nullable ItemStack item) {
|
||||
final int cell = col + row * 9;
|
||||
cellItems.put(cell, item);
|
||||
if (inventory != null && cell >= 0 && cell < inventory.getSize()) {
|
||||
@ -258,6 +278,7 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui highlightItem(int cell) {
|
||||
ItemStack item = cellItems.get(cell);
|
||||
if (item != null && item.getType() != Material.AIR) {
|
||||
@ -266,6 +287,7 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui highlightItem(int row, int col) {
|
||||
final int cell = col + row * 9;
|
||||
ItemStack item = cellItems.get(cell);
|
||||
@ -275,6 +297,7 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui removeHighlight(int cell) {
|
||||
ItemStack item = cellItems.get(cell);
|
||||
if (item != null && item.getType() != Material.AIR) {
|
||||
@ -283,6 +306,7 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui removeHighlight(int row, int col) {
|
||||
final int cell = col + row * 9;
|
||||
ItemStack item = cellItems.get(cell);
|
||||
@ -292,11 +316,13 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui updateItem(int row, int col, String name, String... lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int row, int col, @Nullable String name, @NotNull String... lore) {
|
||||
return updateItem(col + row * 9, name, lore);
|
||||
}
|
||||
|
||||
public Gui updateItem(int cell, String name, String... lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int cell, @Nullable String name, @NotNull String... lore) {
|
||||
ItemStack item = cellItems.get(cell);
|
||||
if (item != null && item.getType() != Material.AIR) {
|
||||
setItem(cell, GuiUtils.updateItem(item, name, lore));
|
||||
@ -304,11 +330,13 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui updateItem(int row, int col, String name, List<String> lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int row, int col, @Nullable String name, @Nullable List<String> lore) {
|
||||
return updateItem(col + row * 9, name, lore);
|
||||
}
|
||||
|
||||
public Gui updateItem(int cell, String name, List<String> lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int cell, @NotNull String name, @Nullable List<String> lore) {
|
||||
ItemStack item = cellItems.get(cell);
|
||||
if (item != null && item.getType() != Material.AIR) {
|
||||
setItem(cell, GuiUtils.updateItem(item, title, lore));
|
||||
@ -316,11 +344,13 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui updateItem(int row, int col, ItemStack itemTo, String title, String... lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int row, int col, @NotNull ItemStack itemTo, @Nullable String title, @NotNull String... lore) {
|
||||
return updateItem(col + row * 9, itemTo, title, lore);
|
||||
}
|
||||
|
||||
public Gui updateItem(int cell, ItemStack itemTo, String title, String... lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int cell, @NotNull ItemStack itemTo, @Nullable String title, @NotNull String... lore) {
|
||||
ItemStack item = cellItems.get(cell);
|
||||
if (item != null && item.getType() != Material.AIR) {
|
||||
setItem(cell, GuiUtils.updateItem(item, itemTo, title, lore));
|
||||
@ -328,11 +358,13 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui updateItem(int row, int col, LegacyMaterials itemTo, String title, String... lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int row, int col, @NotNull LegacyMaterials itemTo, @Nullable String title, @NotNull String... lore) {
|
||||
return updateItem(col + row * 9, itemTo, title, lore);
|
||||
}
|
||||
|
||||
public Gui updateItem(int cell, LegacyMaterials itemTo, String title, String... lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int cell, @NotNull LegacyMaterials itemTo, @Nullable String title, @Nullable String... lore) {
|
||||
ItemStack item = cellItems.get(cell);
|
||||
if (item != null && item.getType() != Material.AIR) {
|
||||
setItem(cell, GuiUtils.updateItem(item, itemTo, title, lore));
|
||||
@ -340,11 +372,13 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui updateItem(int row, int col, ItemStack itemTo, String title, List<String> lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int row, int col, @NotNull ItemStack itemTo, @Nullable String title, @Nullable List<String> lore) {
|
||||
return updateItem(col + row * 9, itemTo, title, lore);
|
||||
}
|
||||
|
||||
public Gui updateItem(int cell, ItemStack itemTo, String title, List<String> lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int cell, @NotNull ItemStack itemTo, @Nullable String title, @Nullable List<String> lore) {
|
||||
ItemStack item = cellItems.get(cell);
|
||||
if (item != null && item.getType() != Material.AIR) {
|
||||
setItem(cell, GuiUtils.updateItem(item, itemTo, title, lore));
|
||||
@ -352,11 +386,13 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui updateItem(int row, int col, LegacyMaterials itemTo, String title, List<String> lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int row, int col, @NotNull LegacyMaterials itemTo, @Nullable String title, @Nullable List<String> lore) {
|
||||
return updateItem(col + row * 9, itemTo, title, lore);
|
||||
}
|
||||
|
||||
public Gui updateItem(int cell, LegacyMaterials itemTo, String title, List<String> lore) {
|
||||
@NotNull
|
||||
public Gui updateItem(int cell, @NotNull LegacyMaterials itemTo, @Nullable String title, @Nullable List<String> lore) {
|
||||
ItemStack item = cellItems.get(cell);
|
||||
if (item != null && item.getType() != Material.AIR) {
|
||||
setItem(cell, GuiUtils.updateItem(item, itemTo, title, lore));
|
||||
@ -364,34 +400,40 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setAction(int cell, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setAction(int cell, @Nullable Clickable action) {
|
||||
setConditional(cell, null, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setAction(int row, int col, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setAction(int row, int col, @Nullable Clickable action) {
|
||||
setConditional(col + row * 9, null, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setAction(int cell, ClickType type, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setAction(int cell, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
setConditional(cell, type, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setAction(int row, int col, ClickType type, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setAction(int row, int col, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
setConditional(col + row * 9, type, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setActionForRange(int cellFirst, int cellLast, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setActionForRange(int cellFirst, int cellLast, @Nullable Clickable action) {
|
||||
for (int cell = cellFirst; cell <= cellLast; ++cell) {
|
||||
setConditional(cell, null, action);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, @Nullable Clickable action) {
|
||||
final int last = cellColLast + cellRowLast * 9;
|
||||
for (int cell = cellColFirst + cellRowFirst * 9; cell <= last; ++cell) {
|
||||
setConditional(cell, null, action);
|
||||
@ -399,14 +441,16 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setActionForRange(int cellFirst, int cellLast, ClickType type, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setActionForRange(int cellFirst, int cellLast, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
for (int cell = cellFirst; cell <= cellLast; ++cell) {
|
||||
setConditional(cell, type, action);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, ClickType type, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
final int last = cellColLast + cellRowLast * 9;
|
||||
for (int cell = cellColFirst + cellRowFirst * 9; cell <= last; ++cell) {
|
||||
setConditional(cell, type, action);
|
||||
@ -414,90 +458,128 @@ public class Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui clearActions(int cell) {
|
||||
conditionalButtons.remove(cell);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui clearActions(int row, int col) {
|
||||
final int cell = col + row * 9;
|
||||
conditionalButtons.remove(cell);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setButton(int cell, ItemStack item, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setButton(int cell, ItemStack item, @Nullable Clickable action) {
|
||||
setItem(cell, item);
|
||||
setConditional(cell, null, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setButton(int row, int col, ItemStack item, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setButton(int row, int col, @Nullable ItemStack item, @Nullable Clickable action) {
|
||||
final int cell = col + row * 9;
|
||||
setItem(cell, item);
|
||||
setConditional(cell, null, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setButton(int cell, ItemStack item, ClickType type, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setButton(int cell, @Nullable ItemStack item, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
setItem(cell, item);
|
||||
setConditional(cell, type, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setButton(int row, int col, ItemStack item, ClickType type, Clickable action) {
|
||||
@NotNull
|
||||
public Gui setButton(int row, int col, @Nullable ItemStack item, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
final int cell = col + row * 9;
|
||||
setItem(cell, item);
|
||||
setConditional(cell, type, action);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void setConditional(int cell, ClickType type, Clickable action) {
|
||||
protected void setConditional(int cell, @Nullable ClickType type, @Nullable Clickable action) {
|
||||
Map<ClickType, Clickable> conditionals = conditionalButtons.get(cell);
|
||||
if (action != null) {
|
||||
if (conditionals == null) {
|
||||
conditionalButtons.put(cell, conditionals = new HashMap());
|
||||
}
|
||||
conditionals.put(type, action);
|
||||
if (conditionals == null) {
|
||||
conditionalButtons.put(cell, conditionals = new HashMap());
|
||||
}
|
||||
conditionals.put(type, action);
|
||||
}
|
||||
|
||||
public Gui setOnOpen(Openable action) {
|
||||
@NotNull
|
||||
public Gui setOnOpen(@Nullable Openable action) {
|
||||
opener = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setOnClose(Closable action) {
|
||||
@NotNull
|
||||
public Gui setOnClose(@Nullable Closable action) {
|
||||
closer = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setOnDrop(Droppable action) {
|
||||
@NotNull
|
||||
public Gui setOnDrop(@Nullable Droppable action) {
|
||||
dropper = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setOnPage(Pagable action) {
|
||||
@NotNull
|
||||
public Gui setOnPage(@Nullable Pagable action) {
|
||||
pager = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setNextPage(int row, int col, ItemStack item) {
|
||||
public Gui setNextPage(ItemStack item) {
|
||||
nextPage = item;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setPrevPage(ItemStack item) {
|
||||
prevPage = item;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setNextPage(int cell, @NotNull ItemStack item) {
|
||||
nextPageIndex = cell;
|
||||
if (page < pages) {
|
||||
setButton(nextPageIndex, nextPage = item, ClickType.LEFT, (event) -> this.nextPage(event.manager));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setNextPage(int row, int col, @NotNull ItemStack item) {
|
||||
nextPageIndex = col + row * 9;
|
||||
if (page < pages) {
|
||||
setButton(nextPageIndex, item, ClickType.LEFT, (event) -> this.nextPage(event.manager));
|
||||
setButton(nextPageIndex, nextPage = item, ClickType.LEFT, (event) -> this.nextPage(event.manager));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Gui setPrevPage(int row, int col, ItemStack item) {
|
||||
@NotNull
|
||||
public Gui setPrevPage(int cell, @NotNull ItemStack item) {
|
||||
prevPageIndex = cell;
|
||||
if (page > 1) {
|
||||
setButton(prevPageIndex, prevPage = item, ClickType.LEFT, (event) -> this.prevPage(event.manager));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Gui setPrevPage(int row, int col, @NotNull ItemStack item) {
|
||||
prevPageIndex = col + row * 9;
|
||||
if (page > 1) {
|
||||
setButton(prevPageIndex, item, ClickType.LEFT, (event) -> this.prevPage(event.manager));
|
||||
setButton(prevPageIndex, prevPage = item, ClickType.LEFT, (event) -> this.prevPage(event.manager));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void nextPage(GuiManager manager) {
|
||||
public void nextPage(@NotNull GuiManager manager) {
|
||||
if (page < pages) {
|
||||
int lastPage = page;
|
||||
++page;
|
||||
@ -515,7 +597,7 @@ public class Gui {
|
||||
}
|
||||
}
|
||||
|
||||
public void prevPage(GuiManager manager) {
|
||||
public void prevPage(@NotNull GuiManager manager) {
|
||||
if (page > 1) {
|
||||
int lastPage = page;
|
||||
--page;
|
||||
@ -547,11 +629,13 @@ public class Gui {
|
||||
}
|
||||
}
|
||||
|
||||
protected Inventory getOrCreateInventory(GuiManager manager) {
|
||||
@NotNull
|
||||
protected Inventory getOrCreateInventory(@NotNull GuiManager manager) {
|
||||
return inventory != null ? inventory : generateInventory(manager);
|
||||
}
|
||||
|
||||
protected Inventory generateInventory(GuiManager manager) {
|
||||
@NotNull
|
||||
protected Inventory generateInventory(@NotNull GuiManager manager) {
|
||||
final int cells = rows * 9;
|
||||
InventoryType t = inventoryType == null ? InventoryType.CHEST : inventoryType.type;
|
||||
switch (t) {
|
||||
@ -573,6 +657,7 @@ public class Gui {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Gui getParent() {
|
||||
return parent;
|
||||
}
|
||||
@ -595,11 +680,11 @@ public class Gui {
|
||||
return title;
|
||||
}
|
||||
|
||||
protected boolean onClickOutside(GuiManager manager, Player player, InventoryClickEvent event) {
|
||||
protected boolean onClickOutside(@NotNull GuiManager manager, @NotNull Player player, @NotNull InventoryClickEvent event) {
|
||||
return dropper != null ? dropper.onDrop(new GuiDropItemEvent(manager, this, player, event)) : true;
|
||||
}
|
||||
|
||||
protected boolean onClick(GuiManager manager, Player player, Inventory inventory, InventoryClickEvent event) {
|
||||
protected boolean onClick(@NotNull GuiManager manager, @NotNull Player player, @NotNull Inventory inventory, @NotNull InventoryClickEvent event) {
|
||||
final int cell = event.getSlot();
|
||||
Map<ClickType, Clickable> conditionals = conditionalButtons.get(cell);
|
||||
Clickable button;
|
||||
@ -608,7 +693,7 @@ public class Gui {
|
||||
button.onClick(new GuiClickEvent(manager, this, player, event, cell, true));
|
||||
} else {
|
||||
// no event for this button
|
||||
if(defaultClicker != null) {
|
||||
if (defaultClicker != null) {
|
||||
// this is a default action, not a triggered action
|
||||
defaultClicker.onClick(new GuiClickEvent(manager, this, player, event, cell, true));
|
||||
}
|
||||
@ -617,19 +702,19 @@ public class Gui {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean onClickPlayerInventory(GuiManager manager, Player player, Inventory openInv, InventoryClickEvent event) {
|
||||
protected boolean onClickPlayerInventory(@NotNull GuiManager manager, @NotNull Player player, @NotNull Inventory openInv, @NotNull InventoryClickEvent event) {
|
||||
// no events for this yet
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onOpen(GuiManager manager, Player player) {
|
||||
public void onOpen(@NotNull GuiManager manager, @NotNull Player player) {
|
||||
open = true;
|
||||
if (opener != null) {
|
||||
opener.onOpen(new GuiOpenEvent(manager, this, player));
|
||||
}
|
||||
}
|
||||
|
||||
public void onClose(GuiManager manager, Player player) {
|
||||
public void onClose(@NotNull GuiManager manager, @NotNull Player player) {
|
||||
if (!allowClose) {
|
||||
manager.showGUI(player, this);
|
||||
return;
|
||||
|
@ -26,7 +26,7 @@ public class SimplePagedGui extends Gui {
|
||||
private int rowsPerPage, maxCellSlot;
|
||||
protected ItemStack headerBackItem;
|
||||
protected ItemStack footerBackItem;
|
||||
final int nextPageIndex = -4, prevPageIndex = -6;
|
||||
final int nextPageIndex = 4, prevPageIndex = 6;
|
||||
|
||||
public SimplePagedGui() {
|
||||
this(null);
|
||||
@ -62,16 +62,6 @@ public class SimplePagedGui extends Gui {
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimplePagedGui setNextPage(ItemStack item) {
|
||||
nextPage = item;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimplePagedGui setPrevPage(ItemStack item) {
|
||||
prevPage = item;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimplePagedGui setItem(int row, int col, ItemStack item) {
|
||||
return setItem(col + row * 9, item);
|
||||
@ -80,7 +70,7 @@ public class SimplePagedGui extends Gui {
|
||||
@Override
|
||||
public SimplePagedGui setItem(int cell, ItemStack item) {
|
||||
// set the cell relative to the current page
|
||||
int cellIndex = page == 1 || (useHeader && cell < 9) ? cell : (cell + (page - 1) * (rowsPerPage * 9));
|
||||
int cellIndex = cell < 0 ? cell : (page == 1 || (useHeader && cell < 9) ? cell : (cell + (page - 1) * (rowsPerPage * 9)));
|
||||
|
||||
cellItems.put(cellIndex, item);
|
||||
if (open && cell >= 0 && cell < inventory.getSize()) {
|
||||
@ -120,20 +110,20 @@ public class SimplePagedGui extends Gui {
|
||||
@Override
|
||||
protected void updatePageNavigation() {
|
||||
if (page > 1) {
|
||||
inventory.setItem((rows * 9) - 6, prevPage);
|
||||
this.setButton(prevPageIndex, prevPage, ClickType.LEFT, (event) -> this.prevPage(event.manager));
|
||||
inventory.setItem(inventory.getSize() - prevPageIndex, prevPage);
|
||||
this.setButton(-prevPageIndex, prevPage, ClickType.LEFT, (event) -> this.prevPage(event.manager));
|
||||
} else {
|
||||
inventory.setItem((rows * 9) - 6, footerBackItem != null ? footerBackItem : blankItem);
|
||||
this.setItem(prevPageIndex, null);
|
||||
this.clearActions(prevPageIndex);
|
||||
inventory.setItem(inventory.getSize() - prevPageIndex, footerBackItem != null ? footerBackItem : blankItem);
|
||||
this.setItem(-prevPageIndex, null);
|
||||
this.clearActions(-prevPageIndex);
|
||||
}
|
||||
if (pages > 1 && page != pages) {
|
||||
inventory.setItem((rows * 9) - 4, nextPage);
|
||||
this.setButton(nextPageIndex, nextPage, ClickType.LEFT, (event) -> this.nextPage(event.manager));
|
||||
inventory.setItem(inventory.getSize() - nextPageIndex, nextPage);
|
||||
this.setButton(-nextPageIndex, nextPage, ClickType.LEFT, (event) -> this.nextPage(event.manager));
|
||||
} else {
|
||||
inventory.setItem((rows * 9) - 4, footerBackItem != null ? footerBackItem : blankItem);
|
||||
this.setItem(nextPageIndex, null);
|
||||
this.clearActions(nextPageIndex);
|
||||
inventory.setItem(inventory.getSize() - nextPageIndex, footerBackItem != null ? footerBackItem : blankItem);
|
||||
this.setItem(-nextPageIndex, null);
|
||||
this.clearActions(-nextPageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,16 @@ public class EntityStackerManager {
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if there is a default stacker hook loaded. <br />
|
||||
* NOTE: using a default stacker assumes that this library is shaded
|
||||
*
|
||||
* @return returns false if there are no supported plugins
|
||||
*/
|
||||
public static boolean isEnabled() {
|
||||
return manager.isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab the default hologram plugin. <br />
|
||||
* NOTE: using a default hologram assumes that this library is shaded
|
||||
|
@ -16,9 +16,11 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@ -28,11 +30,9 @@ import org.bukkit.inventory.meta.SkullMeta;
|
||||
|
||||
public class ItemUtils {
|
||||
|
||||
static boolean check_compatibility = false;
|
||||
static boolean can_getI18NDisplayName = true;
|
||||
|
||||
static void init() {
|
||||
check_compatibility = true;
|
||||
static {
|
||||
try {
|
||||
ItemStack.class.getMethod("getI18NDisplayName");
|
||||
} catch (NoSuchMethodException | SecurityException ex) {
|
||||
@ -40,6 +40,34 @@ public class ItemUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getItemName(ItemStack it) {
|
||||
if (it == null) {
|
||||
return null;
|
||||
} else if (can_getI18NDisplayName) {
|
||||
return it.getI18NDisplayName();
|
||||
} else {
|
||||
return itemName(it.getType());
|
||||
}
|
||||
}
|
||||
|
||||
static String itemName(Material mat) {
|
||||
String matName = mat.name().replace("_", " ");
|
||||
StringBuilder titleCase = new StringBuilder(matName.length());
|
||||
|
||||
Stream.of(matName.split(" ")).forEach(s -> {
|
||||
s = s.toLowerCase();
|
||||
if (s.equals("of")) {
|
||||
titleCase.append(s).append(" ");
|
||||
} else {
|
||||
char[] str = s.toCharArray();
|
||||
str[0] = Character.toUpperCase(str[0]);
|
||||
titleCase.append(new String(str)).append(" ");
|
||||
}
|
||||
});
|
||||
|
||||
return titleCase.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone of org.bukkit.inventory.ItemStack.asQuantity, since it is a paper-only function
|
||||
*
|
||||
@ -187,38 +215,34 @@ public class ItemUtils {
|
||||
return item;
|
||||
}
|
||||
|
||||
public static String getItemName(ItemStack it) {
|
||||
if (!check_compatibility) {
|
||||
init();
|
||||
}
|
||||
if (it == null) {
|
||||
return null;
|
||||
} else if (can_getI18NDisplayName) {
|
||||
return it.getI18NDisplayName();
|
||||
} else {
|
||||
return itemName(it.getType());
|
||||
}
|
||||
}
|
||||
private static Class mc_Item = NMSUtils.getNMSClass("Item");
|
||||
private static Method mc_Item_getItem;
|
||||
private static Field mc_Item_maxStackSize;
|
||||
|
||||
static String itemName(Material mat) {
|
||||
String matName = mat.name().replace("_", " ");
|
||||
StringBuilder titleCase = new StringBuilder(matName.length());
|
||||
static {
|
||||
if(mc_ItemStack != null) {
|
||||
try {
|
||||
mc_Item_getItem = mc_ItemStack.getDeclaredMethod("getItem");
|
||||
mc_Item_maxStackSize = mc_Item.getDeclaredField("maxStackSize");
|
||||
mc_Item_maxStackSize.setAccessible(true);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream.of(matName.split(" ")).forEach(s -> {
|
||||
s = s.toLowerCase();
|
||||
if (s.equals("of")) {
|
||||
titleCase.append(s).append(" ");
|
||||
} else {
|
||||
char[] str = s.toCharArray();
|
||||
str[0] = Character.toUpperCase(str[0]);
|
||||
titleCase.append(new String(str)).append(" ");
|
||||
}
|
||||
});
|
||||
public static ItemStack setMaxStack(ItemStack item, int max) {
|
||||
if (item != null && mc_Item_maxStackSize != null) {
|
||||
try {
|
||||
Object objItemStack = mc_Item_getItem.invoke(cb_CraftItemStack_asNMSCopy.invoke(null, item));
|
||||
mc_Item_maxStackSize.set(objItemStack, max);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Failed to set max stack size on item " + item, e);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
return titleCase.toString().trim();
|
||||
}
|
||||
|
||||
public static ItemStack getPlayerSkull(OfflinePlayer player) {
|
||||
public static ItemStack getPlayerSkull(OfflinePlayer player) {
|
||||
ItemStack head = LegacyMaterials.PLAYER_HEAD.getItem();
|
||||
if (ServerVersion.isServerVersionBelow(ServerVersion.V1_8)) {
|
||||
return head;
|
||||
@ -273,6 +297,40 @@ public class ItemUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use up whatever item the player is holding in their main hand
|
||||
*
|
||||
* @param player player to grab item from
|
||||
*/
|
||||
public static void takeActiveItem(Player player) {
|
||||
takeActiveItem(player, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use up whatever item the player is holding in their main hand
|
||||
*
|
||||
* @param player player to grab item from
|
||||
* @param amount number of items to use up
|
||||
*/
|
||||
public static void takeActiveItem(Player player, int amount) {
|
||||
if (player.getGameMode() == GameMode.CREATIVE) return;
|
||||
|
||||
ItemStack item = player.getInventory().getItemInHand();
|
||||
|
||||
int result = item.getAmount() - amount;
|
||||
item.setAmount(result);
|
||||
|
||||
player.setItemInHand(result > 0 ? item : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly check to see if the two items use the same material. <br />
|
||||
* NOTE: Does not check meta data; only checks the item material.
|
||||
*
|
||||
* @param is1 first item to compare
|
||||
* @param is2 item to compare against
|
||||
* @return true if both items are of the same material
|
||||
*/
|
||||
public static boolean isSimilarMaterial(ItemStack is1, ItemStack is2) {
|
||||
LegacyMaterials mat1 = LegacyMaterials.getMaterial(is1);
|
||||
return mat1 != null && mat1 == LegacyMaterials.getMaterial(is2);
|
||||
|
@ -26,14 +26,51 @@ public class TextUtils {
|
||||
return ChatColor.translateAlternateColorCodes('&', text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to an invisible colored string that's lore-safe <br />
|
||||
* (Safe to use as lore) <br />
|
||||
* Note: Do not use semi-colons in this string, or they will be lost when decoding!
|
||||
*
|
||||
* @param s string to convert
|
||||
* @return encoded string
|
||||
*/
|
||||
public static String convertToInvisibleLoreString(String s) {
|
||||
if (s == null || s.equals(""))
|
||||
return "";
|
||||
StringBuilder hidden = new StringBuilder();
|
||||
for (char c : s.toCharArray()) hidden.append(ChatColor.COLOR_CHAR).append(';').append(ChatColor.COLOR_CHAR).append(c);
|
||||
return hidden.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to an invisible colored string <br />
|
||||
* (Not safe to use as lore) <br />
|
||||
* Note: Do not use semi-colons in this string, or they will be lost when decoding!
|
||||
*
|
||||
* @param s string to convert
|
||||
* @return encoded string
|
||||
*/
|
||||
public static String convertToInvisibleString(String s) {
|
||||
if (s == null || s.equals(""))
|
||||
return "";
|
||||
StringBuilder hidden = new StringBuilder();
|
||||
for (char c : s.toCharArray()) hidden.append(ChatColor.COLOR_CHAR + "").append(c);
|
||||
for (char c : s.toCharArray()) hidden.append(ChatColor.COLOR_CHAR).append(c);
|
||||
return hidden.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes color markers used to encode strings as invisible text
|
||||
*
|
||||
* @param s encoded string
|
||||
* @return string with color markers removed
|
||||
*/
|
||||
public static String convertFromInvisibleString(String s) {
|
||||
if (s == null || s.equals("")) {
|
||||
return "";
|
||||
}
|
||||
return s.replaceAll(ChatColor.COLOR_CHAR + ";" + ChatColor.COLOR_CHAR + "|" + ChatColor.COLOR_CHAR, "");
|
||||
}
|
||||
|
||||
protected static final List<Charset> supportedCharsets = new ArrayList();
|
||||
|
||||
static {
|
||||
|
Loading…
Reference in New Issue
Block a user