mirror of
https://github.com/BentoBoxWorld/Level.git
synced 2024-11-23 18:45:17 +01:00
Added timings, fixed bugs.
This commit is contained in:
parent
7f75caab36
commit
85cd89bdf7
@ -21,8 +21,7 @@ import org.bukkit.World;
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Multiset;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.api.events.addon.AddonBaseEvent;
|
||||
import world.bentobox.bentobox.api.events.addon.AddonEvent;
|
||||
import world.bentobox.bentobox.api.panels.PanelItem;
|
||||
@ -128,7 +127,7 @@ public class LevelsManager {
|
||||
}
|
||||
// Save result
|
||||
addon.logWarning("Saving results");
|
||||
setIslandResults(island.getWorld(), island.getOwner(), r.getLevel(), r.getUwCount(), r.getMdCount());
|
||||
setIslandResults(island.getWorld(), island.getOwner(), r);
|
||||
// Save top ten
|
||||
addon.logWarning("Saving top ten");
|
||||
addon.getManager().saveTopTen(island.getWorld());
|
||||
@ -307,7 +306,7 @@ public class LevelsManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a level data for the island owner from the cache or database. Only island onwers are stored.
|
||||
* Load a level data for the island owner from the cache or database. Only island owners are stored.
|
||||
* @param islandOwner - UUID of island owner
|
||||
* @return LevelsData object or null if not found
|
||||
*/
|
||||
@ -399,6 +398,9 @@ public class LevelsManager {
|
||||
* @param uuid - the player's uuid
|
||||
*/
|
||||
public void removeEntry(World world, UUID uuid) {
|
||||
// Load the user if they haven't yet done anything to put them in the cache
|
||||
this.getLevelsData(uuid);
|
||||
// Remove them
|
||||
if (levelsCache.containsKey(uuid)) {
|
||||
levelsCache.get(uuid).remove(world);
|
||||
// Save
|
||||
@ -433,7 +435,9 @@ public class LevelsManager {
|
||||
* @param lv - initial island level
|
||||
*/
|
||||
public void setInitialIslandLevel(@NonNull Island island, long lv) {
|
||||
BentoBox.getInstance().logDebug("Setting initial island level " + island +" " + lv);
|
||||
if (island.getOwner() == null || island.getWorld() == null) return;
|
||||
BentoBox.getInstance().logDebug("saving");
|
||||
levelsCache.computeIfAbsent(island.getOwner(), LevelsData::new).setInitialLevel(island.getWorld(), lv);
|
||||
handler.saveObjectAsync(levelsCache.get(island.getOwner()));
|
||||
}
|
||||
@ -454,17 +458,15 @@ public class LevelsManager {
|
||||
/**
|
||||
* Set the island level for the owner of the island that targetPlayer is a member
|
||||
* @param world - world
|
||||
* @param owner
|
||||
* @param level
|
||||
* @param uwCount
|
||||
* @param mdCount
|
||||
* @param owner - owner of the island
|
||||
* @param r - results of the calculation
|
||||
*/
|
||||
private void setIslandResults(World world, @Nullable UUID owner, long level, Multiset<Material> uwCount,
|
||||
Multiset<Material> mdCount) {
|
||||
private void setIslandResults(World world, @Nullable UUID owner, Results r) {
|
||||
LevelsData ld = levelsCache.computeIfAbsent(owner, LevelsData::new);
|
||||
ld.setLevel(world, level);
|
||||
ld.setUwCount(world, uwCount);
|
||||
ld.setMdCount(world, mdCount);
|
||||
ld.setLevel(world, r.getLevel());
|
||||
ld.setUwCount(world, r.getUwCount());
|
||||
ld.setMdCount(world, r.getMdCount());
|
||||
ld.setPointsToNextLevel(world, r.getPointsToNextLevel());
|
||||
levelsCache.put(owner, ld);
|
||||
handler.saveObjectAsync(ld);
|
||||
// Update TopTen
|
||||
|
@ -146,18 +146,20 @@ public class IslandLevelCalculator {
|
||||
|
||||
|
||||
private final Results results;
|
||||
private long duration;
|
||||
|
||||
/**
|
||||
* Constructor to get the level for an island
|
||||
* @param addon - Level addon
|
||||
* @param island - the island to scan
|
||||
* @param r - completeable result that will be completed when the calculation is complete
|
||||
* @param r - completable result that will be completed when the calculation is complete
|
||||
*/
|
||||
public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Results> r) {
|
||||
this.addon = addon;
|
||||
this.island = island;
|
||||
this.r = r;
|
||||
results = new Results();
|
||||
duration = System.currentTimeMillis();
|
||||
chunksToCheck = getChunksToScan(island);
|
||||
this.limitCount = new HashMap<>(addon.getBlockConfig().getBlockLimits());
|
||||
}
|
||||
@ -310,7 +312,7 @@ public class IslandLevelCalculator {
|
||||
if (addon.getSettings().isNether()) {
|
||||
World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
|
||||
if (nether != null) {
|
||||
return Util.getChunkAtAsync(nether, x, z, false);
|
||||
return Util.getChunkAtAsync(nether, x, z, true);
|
||||
}
|
||||
}
|
||||
// There is no chunk to scan, so return a null chunk
|
||||
@ -319,13 +321,13 @@ public class IslandLevelCalculator {
|
||||
if (addon.getSettings().isEnd()) {
|
||||
World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld());
|
||||
if (end != null) {
|
||||
return Util.getChunkAtAsync(end, x, z, false);
|
||||
return Util.getChunkAtAsync(end, x, z, true);
|
||||
}
|
||||
}
|
||||
// There is no chunk to scan, so return a null chunk
|
||||
return CompletableFuture.completedFuture(null);
|
||||
default:
|
||||
return Util.getChunkAtAsync(world, x, z, false);
|
||||
return Util.getChunkAtAsync(world, x, z, true);
|
||||
|
||||
}
|
||||
}
|
||||
@ -530,6 +532,8 @@ public class IslandLevelCalculator {
|
||||
|
||||
// Report
|
||||
results.report = getReport();
|
||||
// Set the duration
|
||||
addon.getPipeliner().setTime(System.currentTimeMillis() - duration);
|
||||
// All done.
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,13 @@ import world.bentobox.level.Level;
|
||||
*/
|
||||
public class Pipeliner {
|
||||
|
||||
private static final int START_DURATION = 10; // 10 seconds
|
||||
private final Queue<IslandLevelCalculator> processQueue;
|
||||
private final BukkitTask task;
|
||||
private boolean inProcess;
|
||||
private final Level addon;
|
||||
private long time;
|
||||
private long count;
|
||||
|
||||
/**
|
||||
* Construct the pipeliner
|
||||
@ -53,8 +56,11 @@ public class Pipeliner {
|
||||
task.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of islands currently in the queue or in process
|
||||
*/
|
||||
public int getIslandsInQueue() {
|
||||
return processQueue.size();
|
||||
return inProcess ? processQueue.size() + 1 : processQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,8 +101,26 @@ public class Pipeliner {
|
||||
public CompletableFuture<Results> addIsland(Island island) {
|
||||
CompletableFuture<Results> r = new CompletableFuture<>();
|
||||
processQueue.add(new IslandLevelCalculator(addon, island, r));
|
||||
count++;
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the average time it takes to run a level check
|
||||
* @return the average time in seconds
|
||||
*/
|
||||
public int getTime() {
|
||||
return time == 0 || count == 0 ? START_DURATION : (int)((double)time/count/1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit how long a level check took
|
||||
* @param time the time to set
|
||||
*/
|
||||
public void setTime(long time) {
|
||||
// Running average
|
||||
this.time += time;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -78,8 +78,9 @@ public class IslandLevelCommand extends CompositeCommand {
|
||||
return false;
|
||||
|
||||
}
|
||||
user.sendMessage("island.level.calculating");
|
||||
int inQueue = addon.getPipeliner().getIslandsInQueue();
|
||||
user.sendMessage("island.level.calculating");
|
||||
user.sendMessage("island.level.estimated-wait", TextVariables.NUMBER, String.valueOf(addon.getPipeliner().getTime() * (inQueue + 1)));
|
||||
if (inQueue > 1) {
|
||||
user.sendMessage("island.level.in-queue", TextVariables.NUMBER, String.valueOf(inQueue + 1));
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandCreatedEvent;
|
||||
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandPreclearEvent;
|
||||
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandRegisteredEvent;
|
||||
@ -38,17 +39,20 @@ public class IslandActivitiesListeners implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onNewIsland(IslandCreatedEvent e) {
|
||||
BentoBox.getInstance().logDebug(e.getEventName());
|
||||
zeroIsland(e.getIsland());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onNewIsland(IslandResettedEvent e) {
|
||||
BentoBox.getInstance().logDebug(e.getEventName());
|
||||
zeroIsland(e.getIsland());
|
||||
}
|
||||
|
||||
private void zeroIsland(final Island island) {
|
||||
// Clear the island setting
|
||||
if (island.getOwner() != null && island.getWorld() != null) {
|
||||
BentoBox.getInstance().logDebug("Zeroing island");
|
||||
addon.getPipeliner().addIsland(island).thenAccept(results ->
|
||||
addon.getManager().setInitialIslandLevel(island, results.getLevel()));
|
||||
}
|
||||
@ -56,9 +60,11 @@ public class IslandActivitiesListeners implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onIslandDelete(IslandPreclearEvent e) {
|
||||
BentoBox.getInstance().logDebug(e.getEventName());
|
||||
// Remove player from the top ten and level
|
||||
UUID uuid = e.getIsland().getOwner();
|
||||
World world = e.getIsland().getWorld();
|
||||
BentoBox.getInstance().logDebug(uuid + " " + world);
|
||||
remove(world, uuid);
|
||||
}
|
||||
|
||||
@ -70,36 +76,42 @@ public class IslandActivitiesListeners implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onNewIslandOwner(TeamSetownerEvent e) {
|
||||
BentoBox.getInstance().logDebug(e.getEventName());
|
||||
// Remove player from the top ten and level
|
||||
remove(e.getIsland().getWorld(), e.getIsland().getOwner());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onIsland(TeamJoinedEvent e) {
|
||||
BentoBox.getInstance().logDebug(e.getEventName());
|
||||
// Remove player from the top ten and level
|
||||
remove(e.getIsland().getWorld(), e.getPlayerUUID());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onIsland(IslandUnregisteredEvent e) {
|
||||
BentoBox.getInstance().logDebug(e.getEventName());
|
||||
// Remove player from the top ten and level
|
||||
remove(e.getIsland().getWorld(), e.getPlayerUUID());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onIsland(IslandRegisteredEvent e) {
|
||||
BentoBox.getInstance().logDebug(e.getEventName());
|
||||
// Remove player from the top ten and level
|
||||
remove(e.getIsland().getWorld(), e.getPlayerUUID());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onIsland(TeamLeaveEvent e) {
|
||||
BentoBox.getInstance().logDebug(e.getEventName());
|
||||
// Remove player from the top ten and level
|
||||
remove(e.getIsland().getWorld(), e.getPlayerUUID());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onIsland(TeamKickEvent e) {
|
||||
BentoBox.getInstance().logDebug(e.getEventName());
|
||||
// Remove player from the top ten and level
|
||||
remove(e.getIsland().getWorld(), e.getPlayerUUID());
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import org.bukkit.World;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.database.objects.DataObject;
|
||||
import world.bentobox.bentobox.database.objects.Table;
|
||||
|
||||
@ -141,8 +142,12 @@ public class LevelsData implements DataObject {
|
||||
* @param world - world to remove
|
||||
*/
|
||||
public void remove(World world) {
|
||||
levels.remove(world.getName().toLowerCase(Locale.ENGLISH));
|
||||
initialLevel.remove(world.getName().toLowerCase(Locale.ENGLISH));
|
||||
BentoBox.getInstance().logDebug("Removing world");
|
||||
this.levels.remove(world.getName().toLowerCase(Locale.ENGLISH));
|
||||
this.initialLevel.remove(world.getName().toLowerCase(Locale.ENGLISH));
|
||||
this.pointsToNextLevel.remove(world.getName().toLowerCase(Locale.ENGLISH));
|
||||
this.mdCount.remove(world.getName().toLowerCase(Locale.ENGLISH));
|
||||
this.uwCount.remove(world.getName().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -211,6 +216,9 @@ public class LevelsData implements DataObject {
|
||||
* @return the uwCount
|
||||
*/
|
||||
public Map<Material, Integer> getUwCount(World world) {
|
||||
if (this.uwCount == null) {
|
||||
this.uwCount = new HashMap<>();
|
||||
}
|
||||
return uwCount.getOrDefault(world.getName(), Collections.emptyMap());
|
||||
}
|
||||
|
||||
@ -219,6 +227,9 @@ public class LevelsData implements DataObject {
|
||||
* @return the mdCount
|
||||
*/
|
||||
public Map<Material, Integer> getMdCount(World world) {
|
||||
if (this.mdCount == null) {
|
||||
this.mdCount = new HashMap<>();
|
||||
}
|
||||
return mdCount.getOrDefault(world.getName(), Collections.emptyMap());
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Enums;
|
||||
|
||||
import world.bentobox.bentobox.api.localization.TextVariables;
|
||||
import world.bentobox.bentobox.api.panels.Panel;
|
||||
import world.bentobox.bentobox.api.panels.PanelItem;
|
||||
import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler;
|
||||
@ -41,6 +42,9 @@ public class DetailsGUITab implements Tab, ClickHandler {
|
||||
UNDERWATER_BLOCKS
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts block materials to item materials
|
||||
*/
|
||||
private static final Map<Material, Material> M2I;
|
||||
static {
|
||||
Map<Material, Material> m2i = new HashMap<>();
|
||||
@ -72,6 +76,7 @@ public class DetailsGUITab implements Tab, ClickHandler {
|
||||
m2i.put(Material.BUBBLE_COLUMN, Material.WATER_BUCKET);
|
||||
m2i.put(Material.SWEET_BERRY_BUSH, Material.SWEET_BERRIES);
|
||||
m2i.put(Material.BAMBOO_SAPLING, Material.BAMBOO);
|
||||
m2i.put(Material.FIRE, Material.FLINT_AND_STEEL);
|
||||
// 1.16.1
|
||||
if (Enums.getIfPresent(Material.class, "WEEPING_VINES_PLANT").isPresent()) {
|
||||
m2i.put(Material.WEEPING_VINES_PLANT, Material.WEEPING_VINES);
|
||||
@ -104,19 +109,22 @@ public class DetailsGUITab implements Tab, ClickHandler {
|
||||
// Convert walls
|
||||
m = Enums.getIfPresent(Material.class, m.name().replace("WALL_", "")).or(m);
|
||||
// Tags
|
||||
if (Tag.FIRE.isTagged(m)) {
|
||||
items.add(new PanelItemBuilder()
|
||||
.icon(Material.CAMPFIRE)
|
||||
.name(Util.prettifyText(m.name()) + " x " + count)
|
||||
.build());
|
||||
return;
|
||||
if (Enums.getIfPresent(Material.class, "SOUL_CAMPFIRE").isPresent()) {
|
||||
if (Tag.FIRE.isTagged(m)) {
|
||||
items.add(new PanelItemBuilder()
|
||||
.icon(Material.CAMPFIRE)
|
||||
.name(Util.prettifyText(m.name()) + " x " + count)
|
||||
.build());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Tag.FLOWER_POTS.isTagged(m)) {
|
||||
m = Enums.getIfPresent(Material.class, m.name().replace("POTTED_", "")).or(m);
|
||||
}
|
||||
items.add(new PanelItemBuilder()
|
||||
.icon(M2I.getOrDefault(m, m))
|
||||
.name(Util.prettifyText(m.name()) + " x " + count)
|
||||
.name(user.getTranslation("island.level-details.syntax", TextVariables.NAME,
|
||||
Util.prettifyText(m.name()), TextVariables.NUMBER, String.valueOf(count)))
|
||||
.build());
|
||||
|
||||
}
|
||||
@ -141,30 +149,35 @@ public class DetailsGUITab implements Tab, ClickHandler {
|
||||
default:
|
||||
sumTotal.forEach(this::createItem);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (type.equals(DetailsType.ALL_BLOCKS) && items.isEmpty()) {
|
||||
// Nothing here - looks like they need to run level
|
||||
items.add(new PanelItemBuilder()
|
||||
.name(user.getTranslation("island.level-details.hint")).icon(Material.WRITTEN_BOOK)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PanelItem getIcon() {
|
||||
switch(type) {
|
||||
case ABOVE_SEA_LEVEL_BLOCKS:
|
||||
return new PanelItemBuilder().icon(Material.GRASS_BLOCK).name("Above Sea Level Blocks").build();
|
||||
return new PanelItemBuilder().icon(Material.GRASS_BLOCK).name(user.getTranslation("island.level-details.above-sea-level-blocks")).build();
|
||||
case SPAWNERS:
|
||||
return new PanelItemBuilder().icon(Material.SPAWNER).name("Spawners").build();
|
||||
return new PanelItemBuilder().icon(Material.SPAWNER).name(user.getTranslation("island.level-details.spawners")).build();
|
||||
case UNDERWATER_BLOCKS:
|
||||
return new PanelItemBuilder().icon(Material.WATER_BUCKET).name("Underwater Blocks").build();
|
||||
return new PanelItemBuilder().icon(Material.WATER_BUCKET).name(user.getTranslation("island.level-details.underwater-blocks")).build();
|
||||
default:
|
||||
return new PanelItemBuilder().icon(Material.GRASS_BLOCK).name("All Blocks").build();
|
||||
return new PanelItemBuilder().icon(Material.GRASS_BLOCK).name(user.getTranslation("island.level-details.all-blocks")).build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
String name = "&c No island!";
|
||||
String name = user.getTranslation("island.level-details.no-island");
|
||||
if (island.getOwner() != null) {
|
||||
name = island.getName() != null ? island.getName() : addon.getPlayers().getName(island.getOwner()) + "'s island";
|
||||
name = island.getName() != null ? island.getName() :
|
||||
user.getTranslation("island.level-details.names-island", TextVariables.NAME, addon.getPlayers().getName(island.getOwner()));
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@ -176,17 +189,16 @@ public class DetailsGUITab implements Tab, ClickHandler {
|
||||
|
||||
@Override
|
||||
public String getPermission() {
|
||||
String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world);
|
||||
switch(type) {
|
||||
case ABOVE_SEA_LEVEL_BLOCKS:
|
||||
return "";
|
||||
case ALL_BLOCKS:
|
||||
return "";
|
||||
return permPrefix + "island.level.details.above-sea-level";
|
||||
case SPAWNERS:
|
||||
return "";
|
||||
return permPrefix + "island.level.details.spawners";
|
||||
case UNDERWATER_BLOCKS:
|
||||
return "";
|
||||
return permPrefix + "island.level.details.underwater";
|
||||
default:
|
||||
return "";
|
||||
return permPrefix + "island.level.details.blocks";
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,18 @@ permissions:
|
||||
'[gamemode].island.value':
|
||||
description: Player can use value command
|
||||
default: true
|
||||
'[gamemode].island.level.details.blocks':
|
||||
description: Player see the level details
|
||||
default: true
|
||||
'[gamemode].island.level.details.spawners':
|
||||
description: Player can see the spawners tab in the level details
|
||||
default: false
|
||||
'[gamemode].island.level.details.underwater':
|
||||
description: Player can see the underwater tab in the level details
|
||||
default: false
|
||||
'[gamemode].island.level.details.above-sea-level':
|
||||
description: Player can see the above sea level tab in the level details
|
||||
default: false
|
||||
'[gamemode].admin.level':
|
||||
description: Player can use admin level command
|
||||
default: op
|
||||
|
@ -22,7 +22,8 @@ island:
|
||||
level:
|
||||
parameters: "[player]"
|
||||
description: "calculate your island level or show the level of [player]"
|
||||
calculating: "&a Calculating level..."
|
||||
calculating: "&a Calculating level..."
|
||||
estimated-wait: "&a Estimated wait: [number] seconds"
|
||||
in-queue: "&a You are number [number] in the queue"
|
||||
island-level-is: "&a Island level is &b[level]"
|
||||
required-points-to-next-level: "&a [points] points required until the next level"
|
||||
@ -35,6 +36,16 @@ island:
|
||||
gui-heading: "&6[name]: &B[rank]"
|
||||
island-level: "&b Level [level]"
|
||||
warp-to: "&A Warping to [name]'s island"
|
||||
|
||||
level-details:
|
||||
above-sea-level-blocks: "Above Sea Level Blocks"
|
||||
spawners: "Spawners"
|
||||
underwater-blocks: "Underwater Blocks"
|
||||
all-blocks: "All Blocks"
|
||||
no-island: "&c No island!"
|
||||
names-island: "[name]'s island"
|
||||
syntax: "[name] x [number]"
|
||||
hint: "&c Run level to see the block report"
|
||||
|
||||
value:
|
||||
description: "shows the value of any block"
|
||||
|
Loading…
Reference in New Issue
Block a user