mirror of
https://github.com/CitizensDev/Citizens2.git
synced 2024-11-26 12:46:04 +01:00
Improve text
This commit is contained in:
parent
e9128acb47
commit
bcd3975f1c
@ -72,9 +72,14 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
|
||||
Iterator<NPC> itr = npcRegistry.iterator();
|
||||
while (itr.hasNext()) {
|
||||
NPC npc = itr.next();
|
||||
npc.despawn();
|
||||
for (Trait t : npc.getTraits())
|
||||
t.onRemove();
|
||||
try {
|
||||
npc.despawn();
|
||||
for (Trait t : npc.getTraits())
|
||||
t.onRemove();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// ensure that all entities are despawned
|
||||
}
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ public class NPCSelector implements Listener {
|
||||
NPC npc = event.getNPC();
|
||||
List<MetadataValue> selected = player.getMetadata("selected");
|
||||
if (selected == null || selected.size() == 0 || selected.get(0).asInt() != npc.getId()) {
|
||||
if (Util.isSettingFulfilled(player, Setting.SELECTION_ITEM)
|
||||
if (Util.matchesItemInHand(player, Setting.SELECTION_ITEM.asString())
|
||||
&& npc.getTrait(Owner.class).isOwnedBy(player)) {
|
||||
player.removeMetadata("selected", plugin);
|
||||
select(player, npc);
|
||||
|
@ -15,20 +15,18 @@ import net.citizensnpcs.api.exception.NPCLoadException;
|
||||
import net.citizensnpcs.api.trait.Trait;
|
||||
import net.citizensnpcs.api.util.DataKey;
|
||||
import net.citizensnpcs.editor.Editor;
|
||||
import net.citizensnpcs.npc.CitizensNPC;
|
||||
import net.citizensnpcs.trait.Toggleable;
|
||||
import net.citizensnpcs.util.Messages;
|
||||
import net.citizensnpcs.util.Messaging;
|
||||
import net.citizensnpcs.util.Paginator;
|
||||
import net.citizensnpcs.util.Util;
|
||||
import net.minecraft.server.EntityHuman;
|
||||
import net.minecraft.server.EntityLiving;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.conversations.Conversation;
|
||||
import org.bukkit.conversations.ConversationAbandonedEvent;
|
||||
import org.bukkit.conversations.ConversationAbandonedListener;
|
||||
import org.bukkit.conversations.ConversationFactory;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@ -37,6 +35,7 @@ import org.bukkit.plugin.Plugin;
|
||||
public class Text extends Trait implements Runnable, Toggleable, Listener, ConversationAbandonedListener {
|
||||
private final Map<String, Date> cooldowns = new HashMap<String, Date>();
|
||||
private int currentIndex;
|
||||
private String itemInHandPattern = Setting.TALK_ITEM.asString();
|
||||
private final Plugin plugin;
|
||||
private boolean randomTalker = Setting.DEFAULT_RANDOM_TALKER.asBoolean();
|
||||
private double range = Setting.DEFAULT_TALK_CLOSE_RANGE.asDouble();
|
||||
@ -49,7 +48,7 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
this.plugin = CitizensAPI.getPlugin();
|
||||
}
|
||||
|
||||
public void add(String string) {
|
||||
void add(String string) {
|
||||
text.add(string);
|
||||
}
|
||||
|
||||
@ -58,7 +57,7 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
Bukkit.dispatchCommand((Player) event.getContext().getForWhom(), "npc text");
|
||||
}
|
||||
|
||||
public void edit(int index, String newText) {
|
||||
void edit(int index, String newText) {
|
||||
text.set(index, newText);
|
||||
}
|
||||
|
||||
@ -78,18 +77,19 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
@Override
|
||||
public void end() {
|
||||
Messaging.sendTr(player, Messages.TEXT_EDITOR_END);
|
||||
conversation.abandon();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean hasIndex(int index) {
|
||||
boolean hasIndex(int index) {
|
||||
return index >= 0 && text.size() > index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(DataKey key) throws NPCLoadException {
|
||||
text.clear();
|
||||
// TODO: backwards compat, remove later
|
||||
// TODO: legacy, remove later
|
||||
for (DataKey sub : key.getIntegerSubKeys())
|
||||
text.add(sub.getString(""));
|
||||
for (DataKey sub : key.getRelative("text").getIntegerSubKeys())
|
||||
@ -101,13 +101,14 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
realisticLooker = key.getBoolean("realistic-looking", realisticLooker);
|
||||
randomTalker = key.getBoolean("random-talker", randomTalker);
|
||||
range = key.getDouble("range", range);
|
||||
itemInHandPattern = key.getString("talkitem", itemInHandPattern);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onRightClick(NPCRightClickEvent event) {
|
||||
if (!event.getNPC().equals(npc))
|
||||
return;
|
||||
if (Util.isSettingFulfilled(event.getClicker(), Setting.TALK_ITEM) && !shouldTalkClose())
|
||||
if (Util.matchesItemInHand(event.getClicker(), itemInHandPattern) && !shouldTalkClose())
|
||||
sendText(event.getClicker());
|
||||
}
|
||||
|
||||
@ -115,33 +116,37 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
text.addAll(Setting.DEFAULT_TEXT.asList());
|
||||
}
|
||||
|
||||
public void remove(int index) {
|
||||
void remove(int index) {
|
||||
text.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!npc.isSpawned())
|
||||
if (!npc.isSpawned() || !talkClose)
|
||||
return;
|
||||
EntityHuman search = null;
|
||||
EntityLiving handle = ((CitizensNPC) npc).getHandle();
|
||||
if ((search = handle.world.findNearbyPlayer(handle, range)) != null && talkClose) {
|
||||
Player player = (Player) search.getBukkitEntity();
|
||||
List<Entity> nearby = npc.getBukkitEntity().getNearbyEntities(range, range, range);
|
||||
for (Entity search : nearby) {
|
||||
if (!(search instanceof Player))
|
||||
continue;
|
||||
Player player = (Player) search;
|
||||
// If the cooldown is not expired, do not send text
|
||||
if (cooldowns.get(player.getName()) != null) {
|
||||
if (!new Date().after(cooldowns.get(player.getName())))
|
||||
Date cooldown = cooldowns.get(player.getName());
|
||||
if (cooldown != null) {
|
||||
if (!new Date().after(cooldown))
|
||||
return;
|
||||
cooldowns.remove(player.getName());
|
||||
}
|
||||
if (sendText(player)) {
|
||||
// Add a cooldown if the text was successfully sent
|
||||
Date wait = new Date();
|
||||
int secondsDelta = new Random().nextInt(Setting.TALK_CLOSE_MAXIMUM_COOLDOWN.asInt())
|
||||
+ Setting.TALK_CLOSE_MINIMUM_COOLDOWN.asInt();
|
||||
long millisecondsDelta = TimeUnit.MILLISECONDS.convert(secondsDelta, TimeUnit.SECONDS);
|
||||
wait.setTime(wait.getTime() + millisecondsDelta);
|
||||
cooldowns.put(player.getName(), wait);
|
||||
}
|
||||
if (!sendText(player))
|
||||
return;
|
||||
// Add a cooldown if the text was successfully sent
|
||||
Date wait = new Date();
|
||||
int secondsDelta = new Random().nextInt(Setting.TALK_CLOSE_MAXIMUM_COOLDOWN.asInt())
|
||||
+ Setting.TALK_CLOSE_MINIMUM_COOLDOWN.asInt();
|
||||
if (secondsDelta <= 0)
|
||||
return;
|
||||
long millisecondsDelta = TimeUnit.MILLISECONDS.convert(secondsDelta, TimeUnit.SECONDS);
|
||||
wait.setTime(wait.getTime() + millisecondsDelta);
|
||||
cooldowns.put(player.getName(), wait);
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +156,8 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
key.setBoolean("random-talker", randomTalker);
|
||||
key.setBoolean("realistic-looking", realisticLooker);
|
||||
key.setDouble("range", range);
|
||||
// TODO: for backwards compat purposes, remove later
|
||||
key.setString("talkitem", itemInHandPattern);
|
||||
// TODO: legacy, remove later
|
||||
for (int i = 0; i < 100; i++)
|
||||
key.removeKey(String.valueOf(i));
|
||||
key.removeKey("text");
|
||||
@ -159,7 +165,7 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
key.setString("text." + String.valueOf(i), text.get(i));
|
||||
}
|
||||
|
||||
public boolean sendPage(Player player, int page) {
|
||||
boolean sendPage(Player player, int page) {
|
||||
Paginator paginator = new Paginator().header(npc.getName() + "'s Text Entries");
|
||||
for (int i = 0; i < text.size(); i++)
|
||||
paginator.addLine("<a>" + i + " <7>- <e>" + text.get(i));
|
||||
@ -167,7 +173,7 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
return paginator.sendPage(player, page);
|
||||
}
|
||||
|
||||
public boolean sendText(Player player) {
|
||||
private boolean sendText(Player player) {
|
||||
if (!player.hasPermission("citizens.admin") && !player.hasPermission("citizens.npc.talk"))
|
||||
return false;
|
||||
if (text.size() == 0)
|
||||
@ -185,7 +191,15 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean shouldTalkClose() {
|
||||
void setItemInHandPattern(String pattern) {
|
||||
itemInHandPattern = pattern;
|
||||
}
|
||||
|
||||
void setRange(double range) {
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
boolean shouldTalkClose() {
|
||||
return talkClose;
|
||||
}
|
||||
|
||||
@ -194,11 +208,11 @@ public class Text extends Trait implements Runnable, Toggleable, Listener, Conve
|
||||
return (talkClose = !talkClose);
|
||||
}
|
||||
|
||||
public boolean toggleRandomTalker() {
|
||||
boolean toggleRandomTalker() {
|
||||
return (randomTalker = !randomTalker);
|
||||
}
|
||||
|
||||
public boolean toggleRealisticLooking() {
|
||||
boolean toggleRealisticLooking() {
|
||||
return (realisticLooker = !realisticLooker);
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,9 @@ public class TextStartPrompt extends StringPrompt {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Prompt acceptInput(ConversationContext context, String input) {
|
||||
input = ChatColor.stripColor(input.trim().split(" ")[0]);
|
||||
public Prompt acceptInput(ConversationContext context, String original) {
|
||||
String[] parts = ChatColor.stripColor(original.trim()).split(" ");
|
||||
String input = parts[0];
|
||||
CommandSender sender = (CommandSender) context.getForWhom();
|
||||
if (input.equalsIgnoreCase("add"))
|
||||
return new TextAddPrompt(text);
|
||||
@ -31,9 +32,24 @@ public class TextStartPrompt extends StringPrompt {
|
||||
else if (input.equalsIgnoreCase("realistic looking"))
|
||||
Messaging.sendTr(sender, Messages.TEXT_EDITOR_REALISTIC_LOOKING_SET,
|
||||
text.toggleRealisticLooking());
|
||||
else if (input.equalsIgnoreCase("close"))
|
||||
else if (input.equalsIgnoreCase("close") || input.equalsIgnoreCase("talk-close"))
|
||||
Messaging.sendTr(sender, Messages.TEXT_EDITOR_CLOSE_TALKER_SET, text.toggle());
|
||||
else if (input.equalsIgnoreCase("help")) {
|
||||
else if (input.equalsIgnoreCase("range")) {
|
||||
try {
|
||||
double range = Math.max(0, Double.parseDouble(parts[1]));
|
||||
text.setRange(range);
|
||||
Messaging.sendTr(sender, Messages.TEXT_EDITOR_RANGE_SET, range);
|
||||
} catch (NumberFormatException e) {
|
||||
Messaging.sendErrorTr(sender, Messages.TEXT_EDITOR_INVALID_RANGE);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
Messaging.sendErrorTr(sender, Messages.TEXT_EDITOR_INVALID_RANGE);
|
||||
}
|
||||
} else if (input.equalsIgnoreCase("item")) {
|
||||
if (parts.length > 1) {
|
||||
text.setItemInHandPattern(parts[1]);
|
||||
Messaging.sendTr(sender, Messages.TEXT_EDITOR_SET_ITEM, parts[1]);
|
||||
}
|
||||
} else if (input.equalsIgnoreCase("help")) {
|
||||
context.setSessionData("said-text", false);
|
||||
Messaging.send(sender, getPromptText(context));
|
||||
} else
|
||||
|
@ -104,6 +104,13 @@ public class LinearWaypointProvider implements WaypointProvider {
|
||||
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_BEGIN);
|
||||
}
|
||||
|
||||
private void clearWaypoints() {
|
||||
editingSlot = 0;
|
||||
waypoints.clear();
|
||||
destroyWaypointMarkers();
|
||||
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_WAYPOINTS_CLEARED);
|
||||
}
|
||||
|
||||
private void createWaypointMarker(int index, Waypoint waypoint) {
|
||||
Entity entity = spawnMarker(player.getWorld(), waypoint.getLocation().add(0, 1, 0));
|
||||
if (entity == null)
|
||||
@ -208,13 +215,6 @@ public class LinearWaypointProvider implements WaypointProvider {
|
||||
});
|
||||
}
|
||||
|
||||
private void clearWaypoints() {
|
||||
editingSlot = 0;
|
||||
waypoints.clear();
|
||||
destroyWaypointMarkers();
|
||||
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_WAYPOINTS_CLEARED);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (!event.getPlayer().equals(player) || event.getAction() == Action.PHYSICAL)
|
||||
@ -328,6 +328,10 @@ public class LinearWaypointProvider implements WaypointProvider {
|
||||
itr = getNewIterator();
|
||||
}
|
||||
|
||||
private Navigator getNavigator() {
|
||||
return npc.getNavigator();
|
||||
}
|
||||
|
||||
private Iterator<Waypoint> getNewIterator() {
|
||||
LinearWaypointsCompleteEvent event = new LinearWaypointsCompleteEvent(
|
||||
LinearWaypointProvider.this, waypoints.iterator());
|
||||
@ -336,10 +340,6 @@ public class LinearWaypointProvider implements WaypointProvider {
|
||||
return next;
|
||||
}
|
||||
|
||||
private Navigator getNavigator() {
|
||||
return npc.getNavigator();
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return paused;
|
||||
}
|
||||
|
@ -15,26 +15,26 @@ public class LinearWaypointsCompleteEvent extends CitizensEvent {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public WaypointProvider getWaypointProvider() {
|
||||
return provider;
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public Iterator<Waypoint> getNextWaypoints() {
|
||||
return next;
|
||||
}
|
||||
|
||||
public WaypointProvider getWaypointProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public void setNextWaypoints(Iterator<Waypoint> waypoints) {
|
||||
this.next = waypoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
}
|
||||
|
@ -174,11 +174,14 @@ public class Messages {
|
||||
public static final String TEXT_EDITOR_INVALID_INDEX = "citizens.editors.text.invalid-index";
|
||||
public static final String TEXT_EDITOR_INVALID_INPUT = "citizens.editors.text.invalid-input";
|
||||
public static final String TEXT_EDITOR_INVALID_PAGE = "citizens.editors.text.invalid-page";
|
||||
public static final String TEXT_EDITOR_INVALID_RANGE = "citizens.editors.text.invalid-range";
|
||||
public static final String TEXT_EDITOR_PAGE_PROMPT = "citizens.editors.text.change-page-prompt";
|
||||
public static final String TEXT_EDITOR_RANDOM_TALKER_SET = "citizens.editors.text.random-talker-set";
|
||||
public static final String TEXT_EDITOR_RANGE_SET = "citizens.editors.text.range-set";
|
||||
public static final String TEXT_EDITOR_REALISTIC_LOOKING_SET = "citizens.editors.text.realistic-looking-set";
|
||||
public static final String TEXT_EDITOR_REMOVE_PROMPT = "citizens.editors.text.remove-prompt";
|
||||
public static final String TEXT_EDITOR_REMOVED_ENTRY = "citizens.editors.text.removed-entry";
|
||||
public static final String TEXT_EDITOR_SET_ITEM = "citizens.editors.text.talk-item-set";
|
||||
public static final String TEXT_EDITOR_START_PROMPT = "citizens.editors.text.start-prompt";
|
||||
public static final String TRAIT_LOAD_FAILED = "citizens.notifications.trait-load-failed";
|
||||
public static final String TRAIT_NOT_CONFIGURABLE = "citizens.commands.traitc.not-configurable";
|
||||
|
@ -280,6 +280,10 @@ public class NMS {
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateSenses(EntityLiving entity) {
|
||||
entity.az().a();
|
||||
}
|
||||
|
||||
static {
|
||||
// true field above false and three synchronised lists
|
||||
THREAD_STOPPER = getField(NetworkManager.class, "m");
|
||||
@ -322,8 +326,4 @@ public class NMS {
|
||||
STAIR_MATERIALS.add(material.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateSenses(EntityLiving entity) {
|
||||
entity.az().a();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.citizensnpcs.util;
|
||||
|
||||
import net.citizensnpcs.Settings.Setting;
|
||||
import net.citizensnpcs.api.event.NPCCollisionEvent;
|
||||
import net.citizensnpcs.api.event.NPCPushEvent;
|
||||
import net.citizensnpcs.api.npc.NPC;
|
||||
@ -104,16 +103,12 @@ public class Util {
|
||||
return ((0 <= degrees) && (degrees < 45 + leeway)) || ((315 - leeway <= degrees) && (degrees <= 360));
|
||||
}
|
||||
|
||||
public static boolean isSettingFulfilled(Player player, Setting setting) {
|
||||
String parts = setting.asString();
|
||||
if (parts.contains("*"))
|
||||
return true;
|
||||
for (String part : Splitter.on(',').split(parts)) {
|
||||
if (Material.matchMaterial(part) == player.getItemInHand().getType()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public static boolean isLoaded(Location location) {
|
||||
if (location.getWorld() == null)
|
||||
return false;
|
||||
int chunkX = location.getBlockX() >> 4;
|
||||
int chunkZ = location.getBlockZ() >> 4;
|
||||
return location.getWorld().isChunkLoaded(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
public static EntityType matchEntityType(String toMatch) {
|
||||
@ -138,6 +133,18 @@ public class Util {
|
||||
return type;
|
||||
}
|
||||
|
||||
public static boolean matchesItemInHand(Player player, String setting) {
|
||||
String parts = setting;
|
||||
if (parts.contains("*"))
|
||||
return true;
|
||||
for (String part : Splitter.on(',').split(parts)) {
|
||||
if (Material.matchMaterial(part) == player.getItemInHand().getType()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void sendPacketNearby(Location location, Packet packet) {
|
||||
sendPacketNearby(location, packet, 64);
|
||||
}
|
||||
@ -166,12 +173,4 @@ public class Util {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLoaded(Location location) {
|
||||
if (location.getWorld() == null)
|
||||
return false;
|
||||
int chunkX = location.getBlockX() >> 4;
|
||||
int chunkZ = location.getBlockZ() >> 4;
|
||||
return location.getWorld().isChunkLoaded(chunkX, chunkZ);
|
||||
}
|
||||
}
|
||||
|
@ -131,11 +131,14 @@ citizens.editors.text.invalid-edit-type=Invalid edit type.
|
||||
citizens.editors.text.invalid-index={0} is not a valid index!
|
||||
citizens.editors.text.invalid-input=Invalid input.
|
||||
citizens.editors.text.invalid-page=Invalid page number.
|
||||
citizens.editors.text.invalid-range=Invalid range.
|
||||
citizens.editors.text.random-talker-set=[[Random talking]] set to [[{0}]].
|
||||
citizens.editors.text.range-set=[[Range]] set to [[{0}]].
|
||||
citizens.editors.text.realistic-looking-set=[[Realistic looking]] set to [[{0}]].
|
||||
citizens.editors.text.remove-prompt=Enter the index of the entry you wish to remove or [[page]] to view more pages.
|
||||
citizens.editors.text.removed-entry=[[Removed]] entry at index [[{0}]].
|
||||
citizens.editors.text.start-prompt=Type [[add]] to add an entry, [[edit]] to edit entries, [[remove]] to remove entries, [[close]] to toggle the NPC as a close talker, and [[random]] to toggle the NPC as a random talker. Type [[help]] to show this again.
|
||||
citizens.editors.text.start-prompt=Type [[add]] to add an entry, [[edit]] to edit entries, [[remove]] to remove entries, [[close]] to toggle the NPC as a close talker, [[item]] to set the item in hand pattern, [[range]] to set the talking range, and [[random]] to toggle the NPC as a random talker. Type [[help]] to show this again.
|
||||
citizens.editors.text.talk-item-set=[[Talk item pattern]] set to [[{0}]].
|
||||
citizens.editors.waypoints.linear.added-waypoint=[[Added]] a waypoint at ({0}) ([[{1}]], [[{2}]])
|
||||
citizens.editors.waypoints.linear.begin=<b>Entered the linear waypoint editor!<br> [[Left click]] to add a waypoint, [[right click]] to remove.<br> Type [[toggle path]] to toggle showing entities at waypoints, [[triggers]] to enter the trigger editor and [[clear]] to clear all waypoints.
|
||||
citizens.editors.waypoints.linear.edit-slot-set=Editing slot set to [[{0}]] ({1}).
|
||||
|
Loading…
Reference in New Issue
Block a user