diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java index 46a73b131..124b8661a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -47,16 +47,20 @@ import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.regions.selector.RegionSelectorType; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.session.request.Request; +import com.sk89q.worldedit.util.Countable; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.snapshot.Snapshot; import javax.annotation.Nullable; import java.time.ZoneId; import java.util.Calendar; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.atomic.AtomicBoolean; @@ -95,6 +99,7 @@ public class LocalSession { private transient ZoneId timezone = ZoneId.systemDefault(); private transient BlockVector3 cuiTemporaryBlock; private transient EditSession.ReorderMode reorderMode = EditSession.ReorderMode.MULTI_STAGE; + private transient List> lastDistribution; // Saved properties private String lastScript; @@ -973,4 +978,20 @@ public class LocalSession { public String getNavWandItem() { return navWandItem; } + + /** + * Get the last block distribution stored in this session. + * + * @return block distribution or {@code null} + */ + public List> getLastDistribution() { + return lastDistribution == null ? null : Collections.unmodifiableList(lastDistribution); + } + + /** + * Store a block distribution in this session. + */ + public void setLastDistribution(List> dist) { + lastDistribution = dist; + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java index b9a064726..d0dd6ef5a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java @@ -30,8 +30,8 @@ import com.sk89q.worldedit.command.tool.SelectionWand; import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.Logging; +import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder; import com.sk89q.worldedit.entity.Player; -import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.extension.platform.permission.ActorSelectorLimits; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.block.BlockDistributionCounter; @@ -56,14 +56,15 @@ import com.sk89q.worldedit.regions.selector.SphereRegionSelector; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.Countable; import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.formatting.component.BlockDistributionResult; import com.sk89q.worldedit.util.formatting.component.CommandListBox; +import com.sk89q.worldedit.util.formatting.component.PaginationBox; import com.sk89q.worldedit.util.formatting.component.SubtleFormat; import com.sk89q.worldedit.util.formatting.component.TextComponentProducer; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; import com.sk89q.worldedit.world.World; -import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.item.ItemTypes; @@ -71,11 +72,11 @@ import com.sk89q.worldedit.world.storage.ChunkStore; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; +import org.enginehub.piston.annotation.param.ArgFlag; import org.enginehub.piston.annotation.param.Switch; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.stream.Stream; import static com.sk89q.worldedit.command.util.Logging.LogMode.POSITION; @@ -468,21 +469,35 @@ public class SelectionCommands { desc = "Get the distribution of blocks in the selection" ) @CommandPermissions("worldedit.analysis.distr") - public void distr(Player player, LocalSession session, EditSession editSession, + public void distr(Player player, LocalSession session, @Switch(name = 'c', desc = "Get the distribution of the clipboard instead") boolean clipboardDistr, @Switch(name = 'd', desc = "Separate blocks by state") - boolean separateStates) throws WorldEditException { + boolean separateStates, + @ArgFlag(name = 'p', desc = "Gets page from a previous distribution.", def = "") + Integer page) throws WorldEditException { List> distribution; - if (clipboardDistr) { - Clipboard clipboard = session.getClipboard().getClipboard(); // throws if missing - BlockDistributionCounter count = new BlockDistributionCounter(clipboard, separateStates); - RegionVisitor visitor = new RegionVisitor(clipboard.getRegion(), count); - Operations.completeBlindly(visitor); - distribution = count.getDistribution(); + if (page == null) { + if (clipboardDistr) { + Clipboard clipboard = session.getClipboard().getClipboard(); // throws if missing + BlockDistributionCounter count = new BlockDistributionCounter(clipboard, separateStates); + RegionVisitor visitor = new RegionVisitor(clipboard.getRegion(), count); + Operations.completeBlindly(visitor); + distribution = count.getDistribution(); + } else { + try (EditSession editSession = session.createEditSession(player)) { + distribution = editSession.getBlockDistribution(session.getSelection(player.getWorld()), separateStates); + } + } + session.setLastDistribution(distribution); + page = 1; } else { - distribution = editSession.getBlockDistribution(session.getSelection(player.getWorld()), separateStates); + distribution = session.getLastDistribution(); + if (distribution == null) { + player.printError("No previous distribution."); + return; + } } if (distribution.isEmpty()) { // *Should* always be false @@ -490,28 +505,9 @@ public class SelectionCommands { return; } - // note: doing things like region.getArea is inaccurate for non-cuboids. - int size = distribution.stream().mapToInt(Countable::getAmount).sum(); - player.print("# total blocks: " + size); - - for (Countable c : distribution) { - String name = c.getID().getBlockType().getName(); - String str; - if (separateStates) { - str = String.format("%-7s (%.3f%%) %s (%s)", - String.valueOf(c.getAmount()), - c.getAmount() / (double) size * 100, - name, - c.getID().getAsString()); - } else { - str = String.format("%-7s (%.3f%%) %s (%s)", - String.valueOf(c.getAmount()), - c.getAmount() / (double) size * 100, - name, - c.getID().getBlockType().getId()); - } - player.print(str); - } + final int finalPage = page; + WorldEditAsyncCommandBuilder.createAndSendMessage(player, + () -> new BlockDistributionResult(distribution, separateStates).create(finalPage), null); } @Command( diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/BlockDistributionResult.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/BlockDistributionResult.java new file mode 100644 index 000000000..797557a52 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/BlockDistributionResult.java @@ -0,0 +1,90 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.util.formatting.component; + +import com.google.common.base.Strings; +import com.sk89q.worldedit.util.Countable; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockType; + +import java.util.List; + +public class BlockDistributionResult extends PaginationBox { + + private final List> distribution; + private final int totalBlocks; + private final boolean separateStates; + + public BlockDistributionResult(List> distribution, boolean separateStates) { + super("Block Distribution", "//distr -p %page%" + (separateStates ? " -d" : "")); + this.distribution = distribution; + // note: doing things like region.getArea is inaccurate for non-cuboids. + this.totalBlocks = distribution.stream().mapToInt(Countable::getAmount).sum(); + this.separateStates = separateStates; + setComponentsPerPage(7); + } + + @Override + public Component getComponent(int number) { + Countable c = distribution.get(number); + TextComponent.Builder line = TextComponent.builder(); + + final int count = c.getAmount(); + + final double perc = count / (double) totalBlocks * 100; + final int maxDigits = (int) (Math.log10(totalBlocks) + 1); + final int curDigits = (int) (Math.log10(count) + 1); + line.append(String.format("%s%.3f%% ", perc < 10 ? " " : "", perc), TextColor.GOLD); + final int space = maxDigits - curDigits; + String pad = Strings.repeat(" ", space == 0 ? 2 : 2 * space + 1); + line.append(String.format("%s%s", count, pad), TextColor.YELLOW); + + final BlockState state = c.getID(); + final BlockType blockType = state.getBlockType(); + TextComponent blockName = TextComponent.of(blockType.getName(), TextColor.LIGHT_PURPLE); + TextComponent toolTip; + if (separateStates && state != blockType.getDefaultState()) { + toolTip = TextComponent.of(state.getAsString(), TextColor.GRAY); + blockName = blockName.append(TextComponent.of("*", TextColor.LIGHT_PURPLE)); + } else { + toolTip = TextComponent.of(blockType.getId(), TextColor.GRAY); + } + blockName = blockName.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, toolTip)); + line.append(blockName); + + return line.build(); + } + + @Override + public int getComponentsSize() { + return distribution.size(); + } + + @Override + public Component create(int page) throws InvalidComponentException { + super.getContents().append(TextComponent.of("Total Block Count: " + totalBlocks, TextColor.GRAY)) + .append(TextComponent.newline()); + return super.create(page); + } +}