mirror of
https://github.com/Xiao-MoMi/Custom-Fishing.git
synced 2025-12-26 10:29:16 +00:00
add conditional actions
This commit is contained in:
@@ -18,9 +18,7 @@
|
||||
package net.momirealms.customfishing.command.sub;
|
||||
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import dev.jorel.commandapi.arguments.OfflinePlayerArgument;
|
||||
import dev.jorel.commandapi.arguments.PlayerArgument;
|
||||
import dev.jorel.commandapi.arguments.StringArgument;
|
||||
import dev.jorel.commandapi.arguments.UUIDArgument;
|
||||
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
@@ -28,11 +26,9 @@ import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
import net.momirealms.customfishing.setting.Locale;
|
||||
import net.momirealms.customfishing.storage.user.OfflineUserImpl;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FishingBagCommand {
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
package net.momirealms.customfishing.command.sub;
|
||||
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import dev.jorel.commandapi.arguments.*;
|
||||
import dev.jorel.commandapi.arguments.ArgumentSuggestions;
|
||||
import dev.jorel.commandapi.arguments.EntitySelectorArgument;
|
||||
import dev.jorel.commandapi.arguments.IntegerArgument;
|
||||
import dev.jorel.commandapi.arguments.StringArgument;
|
||||
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.common.Key;
|
||||
|
||||
@@ -21,10 +21,12 @@ import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.common.Pair;
|
||||
import net.momirealms.customfishing.api.manager.ActionManager;
|
||||
import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionExpansion;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionFactory;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl;
|
||||
import net.momirealms.customfishing.util.ClassUtils;
|
||||
@@ -69,6 +71,8 @@ public class ActionManagerImpl implements ActionManager {
|
||||
this.registerActionBarAction();
|
||||
this.registerCloseInvAction();
|
||||
this.registerDelayedAction();
|
||||
this.registerConditionalAction();
|
||||
this.registerPriorityAction();
|
||||
}
|
||||
|
||||
public void load() {
|
||||
@@ -373,6 +377,64 @@ public class ActionManagerImpl implements ActionManager {
|
||||
});
|
||||
}
|
||||
|
||||
private void registerConditionalAction() {
|
||||
registerAction("conditional", (args, chance) -> {
|
||||
if (args instanceof ConfigurationSection section) {
|
||||
Action[] actions = getActions(section.getConfigurationSection("actions"));
|
||||
Requirement[] requirements = plugin.getRequirementManager().getRequirements(section.getConfigurationSection("conditions"), false);
|
||||
return condition -> {
|
||||
if (Math.random() > chance) return;
|
||||
if (requirements != null)
|
||||
for (Requirement requirement : requirements) {
|
||||
if (!requirement.isConditionMet(condition)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (actions != null)
|
||||
for (Action action : actions) {
|
||||
action.trigger(condition);
|
||||
}
|
||||
};
|
||||
}
|
||||
LogUtils.warn("Illegal value format found at action: conditional");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void registerPriorityAction() {
|
||||
registerAction("priority", (args, chance) -> {
|
||||
if (args instanceof ConfigurationSection section) {
|
||||
List<Pair<Requirement[], Action[]>> conditionActionPairList = new ArrayList<>();
|
||||
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection inner) {
|
||||
Action[] actions = getActions(inner.getConfigurationSection("actions"));
|
||||
Requirement[] requirements = plugin.getRequirementManager().getRequirements(inner.getConfigurationSection("conditions"), false);
|
||||
conditionActionPairList.add(Pair.of(requirements, actions));
|
||||
}
|
||||
}
|
||||
return condition -> {
|
||||
if (Math.random() > chance) return;
|
||||
outer:
|
||||
for (Pair<Requirement[], Action[]> pair : conditionActionPairList) {
|
||||
if (pair.left() != null)
|
||||
for (Requirement requirement : pair.left()) {
|
||||
if (!requirement.isConditionMet(condition)) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
if (pair.right() != null)
|
||||
for (Action action : pair.right()) {
|
||||
action.trigger(condition);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
LogUtils.warn("Illegal value format found at action: conditional");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void registerPluginExpAction() {
|
||||
registerAction("plugin-exp", (args, chance) -> {
|
||||
if (args instanceof ConfigurationSection section) {
|
||||
|
||||
@@ -19,10 +19,13 @@ package net.momirealms.customfishing.mechanic.effect;
|
||||
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.common.Key;
|
||||
import net.momirealms.customfishing.api.common.Pair;
|
||||
import net.momirealms.customfishing.api.manager.EffectManager;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.Effect;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.FishingEffect;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.Modifier;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.util.ConfigUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
@@ -111,7 +114,8 @@ public class EffectManagerImpl implements EffectManager {
|
||||
public Effect getEffectFromSection(ConfigurationSection section) {
|
||||
if (section == null) return getInitialEffect();
|
||||
return new FishingEffect.Builder()
|
||||
.lootWeightModifier(ConfigUtils.getModifiers(section.getStringList("weight")))
|
||||
.lootWeightModifier(ConfigUtils.getModifiers(section.getStringList("weight-single")))
|
||||
.lootWeightModifier(getGroupModifiers(section.getStringList("weight-group")))
|
||||
.timeModifier(section.getDouble("hook-time", 1))
|
||||
.difficultyModifier(section.getDouble("difficulty", 0))
|
||||
.multipleLootChance(section.getDouble("multiple-loot", 0))
|
||||
@@ -140,4 +144,21 @@ public class EffectManagerImpl implements EffectManager {
|
||||
public void disable() {
|
||||
this.effectMap.clear();
|
||||
}
|
||||
|
||||
private List<Pair<String, Modifier>> getGroupModifiers(List<String> modList) {
|
||||
List<Pair<String, Modifier>> result = new ArrayList<>();
|
||||
for (String group : modList) {
|
||||
String[] split = group.split(":",2);
|
||||
String key = split[0];
|
||||
List<String> members = plugin.getLootManager().getLootGroup(key);
|
||||
if (members == null) {
|
||||
LogUtils.warn("Group " + key + " doesn't include any loot. The effect would not take effect.");
|
||||
return result;
|
||||
}
|
||||
for (String loot : members) {
|
||||
result.add(Pair.of(loot, ConfigUtils.getModifier(split[1])));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
return;
|
||||
}
|
||||
|
||||
Action[] globalActions = LootManagerImpl.globalLootProperties.getActions(ActionTrigger.FAILURE);
|
||||
Action[] globalActions = LootManagerImpl.GlobalSetting.globalLootProperties.getActions(ActionTrigger.FAILURE);
|
||||
if (globalActions != null)
|
||||
for (Action action : globalActions)
|
||||
action.trigger(fishingPreparation);
|
||||
@@ -579,7 +579,7 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
fishingPreparation.insertArg("{score}","-1");
|
||||
}
|
||||
|
||||
Action[] globalActions = LootManagerImpl.globalLootProperties.getActions(ActionTrigger.SUCCESS);
|
||||
Action[] globalActions = LootManagerImpl.GlobalSetting.globalLootProperties.getActions(ActionTrigger.SUCCESS);
|
||||
if (globalActions != null)
|
||||
for (Action action : globalActions)
|
||||
action.trigger(fishingPreparation);
|
||||
|
||||
@@ -21,14 +21,13 @@ import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.manager.LootManager;
|
||||
import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionTrigger;
|
||||
import net.momirealms.customfishing.api.mechanic.game.GameConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.CFLoot;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.Loot;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.LootType;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
@@ -38,16 +37,22 @@ public class LootManagerImpl implements LootManager {
|
||||
|
||||
private final CustomFishingPlugin plugin;
|
||||
private final HashMap<String, Loot> lootMap;
|
||||
public static CFLoot globalLootProperties;
|
||||
private boolean disableStats;
|
||||
private boolean disableGames;
|
||||
private boolean instantGame;
|
||||
private boolean showInFinder;
|
||||
private String gameGroup;
|
||||
private final HashMap<String, List<String>> lootGroupMap;
|
||||
|
||||
public static class GlobalSetting {
|
||||
public static CFLoot globalLootProperties;
|
||||
public static boolean disableStats;
|
||||
public static boolean disableGames;
|
||||
public static boolean instantGame;
|
||||
public static boolean showInFinder;
|
||||
public static String gameGroup;
|
||||
public static String lootGroup;
|
||||
}
|
||||
|
||||
public LootManagerImpl(CustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.lootMap = new HashMap<>();
|
||||
this.lootGroupMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public void load() {
|
||||
@@ -57,6 +62,7 @@ public class LootManagerImpl implements LootManager {
|
||||
|
||||
public void unload() {
|
||||
this.lootMap.clear();
|
||||
this.lootGroupMap.clear();
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
@@ -88,6 +94,17 @@ public class LootManagerImpl implements LootManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loot getGlobalLootProperties() {
|
||||
return GlobalSetting.globalLootProperties;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getLootGroup(String key) {
|
||||
return lootGroupMap.get(key);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Loot getLoot(String key) {
|
||||
@@ -96,16 +113,17 @@ public class LootManagerImpl implements LootManager {
|
||||
|
||||
private void loadGlobalLootProperties() {
|
||||
YamlConfiguration config = plugin.getConfig("config.yml");
|
||||
globalLootProperties = getSingleSectionItem(
|
||||
GlobalSetting.globalLootProperties = getSingleSectionItem(
|
||||
Objects.requireNonNull(config.getConfigurationSection("mechanics.global-loot-properties")),
|
||||
"GLOBAL",
|
||||
"global"
|
||||
);
|
||||
disableStats = globalLootProperties.disableStats();
|
||||
disableGames = globalLootProperties.disableGame();
|
||||
instantGame = globalLootProperties.instanceGame();
|
||||
showInFinder = globalLootProperties.showInFinder();
|
||||
gameGroup = globalLootProperties.gameConfig;
|
||||
GlobalSetting.disableStats = GlobalSetting.globalLootProperties.disableStats();
|
||||
GlobalSetting.disableGames = GlobalSetting.globalLootProperties.disableGame();
|
||||
GlobalSetting.instantGame = GlobalSetting.globalLootProperties.instanceGame();
|
||||
GlobalSetting.showInFinder = GlobalSetting.globalLootProperties.showInFinder();
|
||||
GlobalSetting.lootGroup = GlobalSetting.globalLootProperties.getLootGroup();
|
||||
GlobalSetting.gameGroup = GlobalSetting.globalLootProperties.getGameConfigKey();
|
||||
}
|
||||
|
||||
private void loadSingleFile(File file, String namespace) {
|
||||
@@ -122,17 +140,23 @@ public class LootManagerImpl implements LootManager {
|
||||
} else {
|
||||
lootMap.put(entry.getKey(), loot);
|
||||
}
|
||||
String group = loot.getLootGroup();
|
||||
if (group != null) {
|
||||
List<String> groupMembers = lootGroupMap.computeIfAbsent(group, k -> new ArrayList<>());
|
||||
groupMembers.add(loot.getID());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CFLoot getSingleSectionItem(ConfigurationSection section, String namespace, String key) {
|
||||
return new CFLoot.Builder(key, LootType.valueOf(namespace.toUpperCase(Locale.ENGLISH)))
|
||||
.disableStats(section.getBoolean("disable-stat", disableStats))
|
||||
.disableGames(section.getBoolean("disable-game", disableGames))
|
||||
.instantGame(section.getBoolean("instant-game", instantGame))
|
||||
.showInFinder(section.getBoolean("show-in-fishfinder", showInFinder))
|
||||
.gameConfig(section.getString("game-group", gameGroup))
|
||||
.disableStats(section.getBoolean("disable-stat", GlobalSetting.disableStats))
|
||||
.disableGames(section.getBoolean("disable-game", GlobalSetting.disableGames))
|
||||
.instantGame(section.getBoolean("instant-game", GlobalSetting.instantGame))
|
||||
.showInFinder(section.getBoolean("show-in-fishfinder", GlobalSetting.showInFinder))
|
||||
.gameConfig(section.getString("game-group", GlobalSetting.gameGroup))
|
||||
.lootGroup(section.getString("loot-group", GlobalSetting.lootGroup))
|
||||
.nick(section.getString("nick", section.getString("display.name", key)))
|
||||
.addActions(getActionMap(section.getConfigurationSection("events")))
|
||||
.addTimesActions(getTimesActionMap(section.getConfigurationSection("events.success-times")))
|
||||
@@ -163,159 +187,4 @@ public class LootManagerImpl implements LootManager {
|
||||
}
|
||||
return actionMap;
|
||||
}
|
||||
|
||||
public static class CFLoot implements Loot {
|
||||
|
||||
private final String id;
|
||||
private final LootType type;
|
||||
private String gameConfig;
|
||||
private final HashMap<ActionTrigger, Action[]> actionMap;
|
||||
private final HashMap<Integer, Action[]> successTimesActionMap;
|
||||
private String nick;
|
||||
private boolean showInFinder;
|
||||
private boolean disableGame;
|
||||
private boolean disableStats;
|
||||
private boolean instanceGame;
|
||||
private double score;
|
||||
|
||||
public CFLoot(String id, LootType type) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.actionMap = new HashMap<>();
|
||||
this.successTimesActionMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public static CFLoot of(String id, LootType type) {
|
||||
return new CFLoot(id, type);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final CFLoot loot;
|
||||
|
||||
public Builder(String id, LootType type) {
|
||||
this.loot = new CFLoot(id, type);
|
||||
}
|
||||
|
||||
public Builder nick(String nick) {
|
||||
this.loot.nick = nick;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder showInFinder(boolean show) {
|
||||
this.loot.showInFinder = show;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder instantGame(boolean instant) {
|
||||
this.loot.instanceGame = instant;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder gameConfig(String gameConfig) {
|
||||
this.loot.gameConfig = gameConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder disableGames(boolean disable) {
|
||||
this.loot.disableGame = disable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder disableStats(boolean disable) {
|
||||
this.loot.disableStats = disable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder score(double score) {
|
||||
this.loot.score = score;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addActions(ActionTrigger trigger, Action[] actions) {
|
||||
this.loot.actionMap.put(trigger, actions);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addActions(HashMap<ActionTrigger, Action[]> actionMap) {
|
||||
this.loot.actionMap.putAll(actionMap);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addTimesActions(int times, Action[] actions) {
|
||||
this.loot.successTimesActionMap.put(times, actions);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addTimesActions(HashMap<Integer, Action[]> actionMap) {
|
||||
this.loot.successTimesActionMap.putAll(actionMap);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CFLoot build() {
|
||||
return loot;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean instanceGame() {
|
||||
return this.instanceGame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LootType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getNick() {
|
||||
return this.nick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showInFinder() {
|
||||
return this.showInFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getScore() {
|
||||
return this.score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableGame() {
|
||||
return this.disableGame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableStats() {
|
||||
return this.disableStats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameConfig getGameConfig() {
|
||||
return CustomFishingPlugin.get().getGameManager().getGameConfig(this.gameConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action[] getActions(ActionTrigger actionTrigger) {
|
||||
return actionMap.get(actionTrigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action[] getSuccessTimesActions(int times) {
|
||||
return successTimesActionMap.get(times);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, Action[]> getSuccessTimesActionMap() {
|
||||
return successTimesActionMap;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import net.momirealms.customfishing.api.integration.SeasonInterface;
|
||||
import net.momirealms.customfishing.api.manager.RequirementManager;
|
||||
import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.condition.Condition;
|
||||
import net.momirealms.customfishing.api.mechanic.game.GameExpansion;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.RequirementExpansion;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.RequirementFactory;
|
||||
|
||||
@@ -20,7 +20,6 @@ package net.momirealms.customfishing.storage.method;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.data.DataStorageInterface;
|
||||
import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
import net.momirealms.customfishing.api.data.user.OnlineUser;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -28,7 +28,6 @@ import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.data.StorageType;
|
||||
import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
import net.momirealms.customfishing.api.data.user.OnlineUser;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.storage.method.AbstractStorage;
|
||||
import org.bson.Document;
|
||||
|
||||
@@ -20,7 +20,6 @@ package net.momirealms.customfishing.storage.method.database.sql;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
import net.momirealms.customfishing.api.data.user.OnlineUser;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.setting.Config;
|
||||
import net.momirealms.customfishing.storage.method.AbstractStorage;
|
||||
|
||||
@@ -21,7 +21,6 @@ import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.data.StorageType;
|
||||
import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
import net.momirealms.customfishing.api.data.user.OnlineUser;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.setting.Config;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
Reference in New Issue
Block a user