Plex-FAWE/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java

951 lines
41 KiB
Java

/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.extension.platform;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extension.platform.binding.Bindings;
import com.fastasyncworldedit.core.extension.platform.binding.ConsumeBindings;
import com.fastasyncworldedit.core.extension.platform.binding.PrimitiveBindings;
import com.fastasyncworldedit.core.extension.platform.binding.ProvideBindings;
import com.fastasyncworldedit.core.internal.command.MethodInjector;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.util.StringMan;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.task.ThrowableSupplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.MissingWorldException;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.ApplyBrushCommands;
import com.sk89q.worldedit.command.BiomeCommands;
import com.sk89q.worldedit.command.BiomeCommandsRegistration;
import com.sk89q.worldedit.command.BrushCommands;
import com.sk89q.worldedit.command.BrushCommandsRegistration;
import com.sk89q.worldedit.command.ChunkCommands;
import com.sk89q.worldedit.command.ChunkCommandsRegistration;
import com.sk89q.worldedit.command.ClipboardCommands;
import com.sk89q.worldedit.command.ClipboardCommandsRegistration;
import com.sk89q.worldedit.command.ExpandCommands;
import com.sk89q.worldedit.command.GeneralCommands;
import com.sk89q.worldedit.command.GenerationCommands;
import com.sk89q.worldedit.command.GenerationCommandsRegistration;
import com.sk89q.worldedit.command.HistoryCommands;
import com.sk89q.worldedit.command.HistoryCommandsRegistration;
import com.sk89q.worldedit.command.HistorySubCommands;
import com.sk89q.worldedit.command.HistorySubCommandsRegistration;
import com.sk89q.worldedit.command.NavigationCommands;
import com.sk89q.worldedit.command.NavigationCommandsRegistration;
import com.sk89q.worldedit.command.PaintBrushCommands;
import com.sk89q.worldedit.command.RegionCommands;
import com.sk89q.worldedit.command.RegionCommandsRegistration;
import com.sk89q.worldedit.command.SchematicCommands;
import com.sk89q.worldedit.command.SchematicCommandsRegistration;
import com.sk89q.worldedit.command.SelectionCommands;
import com.sk89q.worldedit.command.SelectionCommandsRegistration;
import com.sk89q.worldedit.command.SnapshotCommands;
import com.sk89q.worldedit.command.SnapshotCommandsRegistration;
import com.sk89q.worldedit.command.SnapshotUtilCommands;
import com.sk89q.worldedit.command.SnapshotUtilCommandsRegistration;
import com.sk89q.worldedit.command.SuperPickaxeCommands;
import com.sk89q.worldedit.command.SuperPickaxeCommandsRegistration;
import com.sk89q.worldedit.command.ToolCommands;
import com.sk89q.worldedit.command.ToolCommandsRegistration;
import com.sk89q.worldedit.command.ToolUtilCommands;
import com.sk89q.worldedit.command.ToolUtilCommandsRegistration;
import com.sk89q.worldedit.command.UtilityCommands;
import com.sk89q.worldedit.command.UtilityCommandsRegistration;
import com.sk89q.worldedit.command.WorldEditCommands;
import com.sk89q.worldedit.command.WorldEditCommandsRegistration;
import com.sk89q.worldedit.command.argument.Arguments;
import com.sk89q.worldedit.command.argument.BooleanConverter;
import com.sk89q.worldedit.command.argument.Chunk3dVectorConverter;
import com.sk89q.worldedit.command.argument.CommaSeparatedValuesConverter;
import com.sk89q.worldedit.command.argument.DirectionConverter;
import com.sk89q.worldedit.command.argument.DirectionVectorConverter;
import com.sk89q.worldedit.command.argument.EntityRemoverConverter;
import com.sk89q.worldedit.command.argument.EnumConverter;
import com.sk89q.worldedit.command.argument.ExpressionConverter;
import com.sk89q.worldedit.command.argument.FactoryConverter;
import com.sk89q.worldedit.command.argument.HeightConverter;
import com.sk89q.worldedit.command.argument.LocationConverter;
import com.sk89q.worldedit.command.argument.OffsetConverter;
import com.sk89q.worldedit.command.argument.RegionFactoryConverter;
import com.sk89q.worldedit.command.argument.RegistryConverter;
import com.sk89q.worldedit.command.argument.SideEffectConverter;
import com.sk89q.worldedit.command.argument.VectorConverter;
import com.sk89q.worldedit.command.argument.WorldConverter;
import com.sk89q.worldedit.command.argument.ZonedDateTimeConverter;
import com.sk89q.worldedit.command.util.PermissionCondition;
import com.sk89q.worldedit.command.util.PrintCommandHelp;
import com.sk89q.worldedit.command.util.SubCommandPermissionCondition;
import com.sk89q.worldedit.command.util.annotation.ConfirmHandler;
import com.sk89q.worldedit.command.util.annotation.PreloadHandler;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.Event;
import com.sk89q.worldedit.event.platform.CommandEvent;
import com.sk89q.worldedit.event.platform.CommandSuggestionEvent;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.annotation.Selection;
import com.sk89q.worldedit.internal.command.CommandArgParser;
import com.sk89q.worldedit.internal.command.CommandLoggingHandler;
import com.sk89q.worldedit.internal.command.CommandRegistrationHandler;
import com.sk89q.worldedit.internal.command.exception.ExceptionConverter;
import com.sk89q.worldedit.internal.command.exception.WorldEditExceptionConverter;
import com.sk89q.worldedit.internal.util.ErrorReporting;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.internal.util.Substring;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.SessionKey;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.logging.DynamicStreamHandler;
import com.sk89q.worldedit.util.logging.LogFormat;
import com.sk89q.worldedit.world.World;
import org.apache.logging.log4j.Logger;
import org.enginehub.piston.Command;
import org.enginehub.piston.CommandManager;
import org.enginehub.piston.converter.ArgumentConverter;
import org.enginehub.piston.converter.ArgumentConverters;
import org.enginehub.piston.converter.ConversionResult;
import org.enginehub.piston.exception.CommandException;
import org.enginehub.piston.exception.CommandExecutionException;
import org.enginehub.piston.exception.ConditionFailedException;
import org.enginehub.piston.exception.UsageException;
import org.enginehub.piston.gen.CommandRegistration;
import org.enginehub.piston.impl.CommandManagerServiceImpl;
import org.enginehub.piston.inject.InjectedValueAccess;
import org.enginehub.piston.inject.InjectedValueStore;
import org.enginehub.piston.inject.Key;
import org.enginehub.piston.inject.MapBackedValueStore;
import org.enginehub.piston.inject.MemoizingValueAccess;
import org.enginehub.piston.inject.MergedValueAccess;
import org.enginehub.piston.part.SubCommandPart;
import org.enginehub.piston.suggestion.Suggestion;
import org.enginehub.piston.util.HelpGenerator;
import org.enginehub.piston.util.ValueProvider;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Handles the registration and invocation of commands.
*
* <p>This class is primarily for internal usage.</p>
*/
public final class PlatformCommandManager {
public static final Pattern COMMAND_CLEAN_PATTERN = Pattern.compile("^[/]+");
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final java.util.logging.Logger COMMAND_LOG =
java.util.logging.Logger.getLogger("com.sk89q.worldedit.CommandLog");
private final WorldEdit worldEdit;
private final PlatformManager platformManager;
private final CommandManagerServiceImpl commandManagerService;
private final CommandManager commandManager;
private final InjectedValueStore globalInjectedValues;
private final DynamicStreamHandler dynamicHandler = new DynamicStreamHandler();
private final WorldEditExceptionConverter exceptionConverter;
public final CommandRegistrationHandler registration;
//FAWE start
private static PlatformCommandManager INSTANCE;
//FAWE end
/**
* Create a new instance.
*
* @param worldEdit the WorldEdit instance
*/
public PlatformCommandManager(final WorldEdit worldEdit, PlatformManager platformManager) {
checkNotNull(worldEdit);
checkNotNull(platformManager);
//FAWE start
INSTANCE = this;
//FAWE end
this.worldEdit = worldEdit;
this.platformManager = platformManager;
this.exceptionConverter = new WorldEditExceptionConverter(worldEdit);
this.commandManagerService = new CommandManagerServiceImpl();
this.commandManager = commandManagerService.newCommandManager();
this.globalInjectedValues = MapBackedValueStore.create();
this.registration = new CommandRegistrationHandler(
ImmutableList.of(
new CommandLoggingHandler(worldEdit, COMMAND_LOG),
new MethodInjector(),
//FAWE start
new ConfirmHandler(),
new PreloadHandler()
//FAWE end
));
// setup separate from main constructor
// ensures that everything is definitely assigned
initialize();
}
private void initialize() {
// Register this instance for command events
worldEdit.getEventBus().register(this);
// Setup the logger
COMMAND_LOG.addHandler(dynamicHandler);
// Set up the commands manager
registerAlwaysInjectedValues();
registerArgumentConverters();
registerAllCommands();
}
private void registerArgumentConverters() {
DirectionVectorConverter.register(worldEdit, commandManager);
DirectionConverter.register(worldEdit, commandManager);
FactoryConverter.register(worldEdit, commandManager);
for (int count = 2; count <= 3; count++) {
commandManager.registerConverter(
Key.of(double.class, Annotations.radii(count)),
CommaSeparatedValuesConverter.wrapAndLimit(ArgumentConverters.get(
TypeToken.of(double.class)
), count)
);
}
VectorConverter.register(commandManager);
Chunk3dVectorConverter.register(commandManager);
EnumConverter.register(commandManager);
RegistryConverter.register(commandManager);
ZonedDateTimeConverter.register(commandManager);
BooleanConverter.register(commandManager);
EntityRemoverConverter.register(commandManager);
RegionFactoryConverter.register(commandManager);
WorldConverter.register(commandManager);
LocationConverter.register(commandManager);
ExpressionConverter.register(commandManager);
SideEffectConverter.register(commandManager);
HeightConverter.register(commandManager);
OffsetConverter.register(worldEdit, commandManager);
//FAWE start
commandManager.registerConverter(
Key.of(com.sk89q.worldedit.function.pattern.Pattern.class, Annotations.patternList()),
CommaSeparatedValuesConverter.wrap(commandManager.getConverter(Key.of(
com.sk89q.worldedit.function.pattern.Pattern.class)).get())
);
registerBindings(new ConsumeBindings(worldEdit, this));
registerBindings(new PrimitiveBindings(worldEdit));
registerBindings(new ProvideBindings(worldEdit));
//FAWE end
}
//FAWE start
private void registerBindings(Bindings bindings) {
bindings.register(globalInjectedValues, commandManager);
}
//FAWE end
private void registerAlwaysInjectedValues() {
globalInjectedValues.injectValue(
Key.of(Region.class, Selection.class),
context -> {
LocalSession localSession = context.injectedValue(Key.of(LocalSession.class))
.orElseThrow(() -> new IllegalStateException("No LocalSession"));
return context.injectedValue(Key.of(World.class))
.map(world -> {
try {
return localSession.getSelection(world);
} catch (IncompleteRegionException e) {
exceptionConverter.convert(e);
throw new AssertionError("Should have thrown a new exception.", e);
}
});
}
);
//FAWE start
/*
globalInjectedValues.injectValue(Key.of(EditSession.class),
context -> {
LocalSession localSession = context.injectedValue(Key.of(LocalSession.class))
.orElseThrow(() -> new IllegalStateException("No LocalSession"));
return context.injectedValue(Key.of(Actor.class))
.map(actor -> {
EditSession editSession = localSession.createEditSession(actor);
editSession.enableStandardMode();
Request.request().setEditSession(editSession);
return editSession;
});
});
*/
// TODO: Ping @MattBDev to reimplement 2020-02-04
// globalInjectedValues.injectValue(Key.of(CFICommands.CFISettings.class),
// context -> context.injectedValue(Key.of(Actor.class))
// .orElseThrow(() -> new IllegalStateException("No CFI Settings")).getMeta("CFISettings"));
//FAWE end
globalInjectedValues.injectValue(
Key.of(World.class),
context -> {
LocalSession localSession = context.injectedValue(Key.of(LocalSession.class))
.orElseThrow(() -> new IllegalStateException("No LocalSession"));
return context.injectedValue(Key.of(Actor.class))
.map(actor -> {
try {
if (localSession.hasWorldOverride()) {
return localSession.getWorldOverride();
} else if (actor instanceof Locatable && ((Locatable) actor).getExtent() instanceof World) {
return (World) ((Locatable) actor).getExtent();
} else {
throw new MissingWorldException();
}
} catch (MissingWorldException e) {
exceptionConverter.convert(e);
throw new AssertionError("Should have thrown a new exception.", e);
}
});
}
);
//FAWE start
globalInjectedValues.injectValue(Key.of(InjectedValueAccess.class), Optional::of);
//FAWE end
}
/**
* Internal use only.
*/
public <CI> void registerSubCommands(
String name, List<String> aliases, String desc,
CommandRegistration<CI> registration, CI instance
) {
registerSubCommands(name, aliases, desc, registration, instance, m -> {
});
}
private <CI> void registerSubCommands(
String name, List<String> aliases, String desc,
CommandRegistration<CI> registration, CI instance,
Consumer<CommandManager> additionalConfig
) {
commandManager.register(name, cmd -> {
cmd.aliases(aliases);
cmd.description(TextComponent.of(desc));
cmd.action(Command.Action.NULL_ACTION);
CommandManager manager = commandManagerService.newCommandManager();
this.registration.register(
manager,
registration,
instance
);
additionalConfig.accept(manager);
final List<Command> subCommands = manager.getAllCommands().collect(Collectors.toList());
cmd.addPart(SubCommandPart.builder(
Caption.of("worldedit.argument.action"),
TextComponent.of("Sub-command to run.")
)
.withCommands(subCommands)
.required()
.build());
cmd.condition(new SubCommandPermissionCondition.Generator(subCommands).build());
});
}
private <CI> void registerSubCommands(
String name, List<String> aliases, String desc,
Consumer<BiConsumer<CommandRegistration, CI>> handlerInstance,
@Nonnull Consumer<CommandManager> additionalConfig
) {
commandManager.register(name, cmd -> {
cmd.aliases(aliases);
cmd.description(TextComponent.of(desc));
cmd.action(Command.Action.NULL_ACTION);
CommandManager manager = commandManagerService.newCommandManager();
//FAWE start
handlerInstance.accept((handler, instance) ->
this.registration.register(
manager,
handler,
instance
));
//FAWE end
additionalConfig.accept(manager);
final List<Command> subCommands = manager.getAllCommands().collect(Collectors.toList());
cmd.addPart(SubCommandPart.builder(
Caption.of("worldedit.argument.action"),
TextComponent.of("Sub-command to run.")
)
.withCommands(subCommands)
.required()
.build());
cmd.condition(new SubCommandPermissionCondition.Generator(subCommands).build());
});
}
public void registerAllCommands() {
//FAWE start
if (Settings.settings().ENABLED_COMPONENTS.COMMANDS) {
// TODO: Ping @MattBDev to reimplement (or remove) 2020-02-04
// registerSubCommands(
// "patterns",
// ImmutableList.of(),
// "Patterns determine what blocks are placed",
// PatternCommandsRegistration.builder(),
// new PatternCommands()
// );
// registerSubCommands(
// "masks",
// ImmutableList.of(),
// "Masks determine which blocks are placed",
// MaskCommandsRegistration.builder(),
// new MaskCommands(worldEdit)
// );
// registerSubCommands(
// "transforms",
// ImmutableList.of(),
// "Transforms modify how a block is placed",
// TransformCommandsRegistration.builder(),
// new TransformCommands()
// );
//FAWE end
registerSubCommands(
"schematic",
ImmutableList.of("schem", "/schematic", "/schem"),
"Schematic commands for saving/loading areas",
SchematicCommandsRegistration.builder(),
new SchematicCommands(worldEdit)
);
registerSubCommands(
"snapshot",
//FAWE start - add "/" aliases as well
ImmutableList.of("snap", "/snapshot", "/snap"),
//FAWE end
"Snapshot commands for restoring backups",
SnapshotCommandsRegistration.builder(),
new SnapshotCommands(worldEdit)
);
registerSubCommands(
"superpickaxe",
//FAWE start - register /<command> commands
ImmutableList.of("pickaxe", "/pickaxe", "sp", "/sp"),
//FAWE end
"Super-pickaxe commands",
SuperPickaxeCommandsRegistration.builder(),
new SuperPickaxeCommands(worldEdit)
);
registerSubCommands(
"brush",
//FAWE start - register tools as brushes (?)
Lists.newArrayList("br", "/brush", "/br", "/tool", "tool"),
"Brushing commands",
c -> {
c.accept(BrushCommandsRegistration.builder(), new BrushCommands(worldEdit));
c.accept(ToolCommandsRegistration.builder(), new ToolCommands(worldEdit));
c.accept(ToolUtilCommandsRegistration.builder(), new ToolUtilCommands(worldEdit));
},
//FAWE end
manager -> {
PaintBrushCommands.register(commandManagerService, manager, registration);
ApplyBrushCommands.register(commandManagerService, manager, registration);
}
);
registerSubCommands(
"worldedit",
//FAWE start - register fawe
ImmutableList.of("we", "fawe", "fastasyncworldedit"),
//FAWE end
"WorldEdit commands",
WorldEditCommandsRegistration.builder(),
new WorldEditCommands(worldEdit)
);
this.registration.register(
commandManager,
BiomeCommandsRegistration.builder(),
new BiomeCommands()
);
this.registration.register(
commandManager,
ChunkCommandsRegistration.builder(),
new ChunkCommands(worldEdit)
);
this.registration.register(
commandManager,
ClipboardCommandsRegistration.builder(),
new ClipboardCommands()
);
GeneralCommands.register(
registration,
commandManager,
commandManagerService,
worldEdit
);
this.registration.register(
commandManager,
GenerationCommandsRegistration.builder(),
new GenerationCommands(worldEdit)
);
//FAWE start
HistoryCommands history = new HistoryCommands(worldEdit);
this.registration.register(
commandManager,
HistoryCommandsRegistration.builder(),
history
);
registerSubCommands(
"/history",
ImmutableList.of("/frb"),
"Manage your history",
HistorySubCommandsRegistration.builder(),
new HistorySubCommands(history)
);
//FAWE end
this.registration.register(
commandManager,
NavigationCommandsRegistration.builder(),
new NavigationCommands(worldEdit)
);
this.registration.register(
commandManager,
RegionCommandsRegistration.builder(),
new RegionCommands()
);
this.registration.register(
commandManager,
SelectionCommandsRegistration.builder(),
new SelectionCommands(worldEdit)
);
ExpandCommands.register(registration, commandManager, commandManagerService);
this.registration.register(
commandManager,
SnapshotUtilCommandsRegistration.builder(),
new SnapshotUtilCommands(worldEdit)
);
this.registration.register(
commandManager,
ToolCommandsRegistration.builder(),
new ToolCommands(worldEdit)
);
this.registration.register(
commandManager,
ToolUtilCommandsRegistration.builder(),
new ToolUtilCommands(worldEdit)
);
this.registration.register(
commandManager,
UtilityCommandsRegistration.builder(),
new UtilityCommands(worldEdit)
);
}
}
public static PlatformCommandManager getInstance() {
return INSTANCE;
}
public ExceptionConverter getExceptionConverter() {
return exceptionConverter;
}
void registerCommandsWith(Platform platform) {
LOGGER.info("Registering commands with " + platform.getClass().getCanonicalName());
LocalConfiguration config = platform.getConfiguration();
boolean logging = config.logCommands;
String path = config.logFile;
// Register log
if (!logging || path.isEmpty()) {
dynamicHandler.setHandler(null);
COMMAND_LOG.setLevel(Level.OFF);
} else {
File file = new File(config.getWorkingDirectoryPath().toFile(), path);
COMMAND_LOG.setLevel(Level.ALL);
LOGGER.info("Logging WorldEdit commands to " + file.getAbsolutePath());
try {
dynamicHandler.setHandler(new FileHandler(file.getAbsolutePath(), true));
} catch (IOException e) {
LOGGER.warn("Could not use command log file " + path + ": " + e.getMessage());
}
dynamicHandler.setFormatter(new LogFormat(config.logFormat));
}
platform.registerCommands(commandManager);
}
void removeCommands() {
dynamicHandler.setHandler(null);
}
private Stream<Substring> parseArgs(String input) {
return CommandArgParser.forArgString(input).parseArgs();
}
//FAWE start
public int parseCommand(String args, Actor actor) {
InjectedValueAccess context;
if (actor == null) {
context = globalInjectedValues;
} else {
context = initializeInjectedValues(args::toString, actor, null, false);
}
return parseCommand(args, context);
}
public <T> T parseConverter(String args, InjectedValueAccess access, Class<T> clazz) {
ArgumentConverter<T> converter = commandManager.getConverter(Key.of(clazz)).orElse(null);
if (converter != null) {
ConversionResult<T> result = converter.convert(args, access);
Collection<T> values = result.orElse(Collections.emptyList());
if (!values.isEmpty()) {
return values.iterator().next();
}
}
return null;
}
public int parseCommand(String args, InjectedValueAccess access) {
if (args.isEmpty()) {
return 0;
}
String[] split = parseArgs(args)
.map(Substring::getSubstring)
.toArray(String[]::new);
return commandManager.execute(access, ImmutableList.copyOf(split));
}
@Subscribe
public void handleCommand(CommandEvent event) {
Request.reset();
Actor actor = event.getActor();
String args = event.getArguments();
TaskManager.taskManager().taskNow(() -> {
if (!Fawe.isMainThread()) {
Thread.currentThread().setName("FAWE Thread for player: " + actor.getName());
}
int space0 = args.indexOf(' ');
String arg0 = space0 == -1 ? args : args.substring(0, space0);
Optional<Command> optional = commandManager.getCommand(arg0);
if (!optional.isPresent()) {
return;
}
Command cmd = optional.get();
PermissionCondition queued = cmd.getCondition().as(PermissionCondition.class).orElse(null);
if (queued != null && !queued.isQueued()) {
handleCommandOnCurrentThread(event);
return;
} else {
actor.decline();
}
actor.runAction(() -> {
SessionKey key = actor.getSessionKey();
if (key.isActive()) {
PlatformCommandManager.this.handleCommandOnCurrentThread(event);
}
}, false, true);
}, Fawe.isMainThread());
}
public void handleCommandOnCurrentThread(CommandEvent event) {
Actor actor = platformManager.createProxyActor(event.getActor());
String[] split = parseArgs(event.getArguments())
.map(Substring::getSubstring)
.toArray(String[]::new);
// No command found!
if (!commandManager.containsCommand(split[0])) {
return;
}
LocalSession session = worldEdit.getSessionManager().get(actor);
Request.request().setSession(session);
if (actor instanceof Entity) {
Extent extent = ((Entity) actor).getExtent();
if (extent instanceof World) {
Request.request().setWorld(((World) extent));
}
}
MemoizingValueAccess context = initializeInjectedValues(event::getArguments, actor, event, false);
ThrowableSupplier<Throwable> task = () -> commandManager.execute(context, ImmutableList.copyOf(split));
handleCommandTask(task, context, session, event);
}
public void handleCommandTask(
ThrowableSupplier<Throwable> task,
InjectedValueAccess context,
@Nullable LocalSession session,
CommandEvent event
) {
Actor actor = context.injectedValue(Key.of(Actor.class)).orElseThrow(() -> new IllegalStateException("No player"));
long start = System.currentTimeMillis();
try {
// This is a bit of a hack, since the call method can only throw CommandExceptions
// everything needs to be wrapped at least once. Which means to handle all WorldEdit
// exceptions without writing a hook into every dispatcher, we need to unwrap these
// exceptions and rethrow their converted form, if their is one.
try {
Object result = task.get();
} catch (Throwable t) {
// Use the exception converter to convert the exception if any of its causes
// can be converted, otherwise throw the original exception
Throwable next = t;
do {
exceptionConverter.convert(next);
next = next.getCause();
} while (next != null);
throw t;
}
} catch (ConditionFailedException e) {
if (e.getCondition() instanceof PermissionCondition) {
actor.print(Caption.of(
"fawe.error.no-perm",
StringMan.getString(((PermissionCondition) e.getCondition()).getPermissions())
));
} else {
actor.print(e.getRichMessage());
}
} catch (FaweException e) {
actor.print(Caption.of("fawe.cancel.reason", e.getComponent()));
} catch (UsageException e) {
actor.printError(e.getRichMessage());
} catch (CommandExecutionException e) {
handleUnknownException(actor, e.getCause());
} catch (CommandException e) {
if (e.getCause() instanceof FaweException) {
actor.print(Caption.of("fawe.cancel.reason", ((FaweException) e.getCause()).getComponent()));
} else {
Component msg = e.getRichMessage();
if (msg == TextComponent.empty()) {
List<String> argList = parseArgs(event.getArguments())
.map(Substring::getSubstring)
.collect(Collectors.toList());
printUsage(actor, argList);
} else {
actor.printError(msg);
}
}
} catch (Throwable t) {
handleUnknownException(actor, t);
} finally {
if (context instanceof MemoizingValueAccess) {
context = ((MemoizingValueAccess) context).snapshotMemory();
}
Optional<EditSession> editSessionOpt = context.injectedValue(Key.of(EditSession.class));
// Require null CommandEvent#getSession as it means the editsession is being handled somewhere else.
if (editSessionOpt.isPresent() && event.getSession() == null) {
EditSession editSession = editSessionOpt.get();
editSession.close();
session.remember(editSession);
long time = System.currentTimeMillis() - start;
double timeS = (time / 1000.0);
int changed = editSession.getBlockChangeCount();
double throughput = timeS == 0 ? changed : changed / timeS;
if (time > 1000) {
actor.print(Caption.of(
"worldedit.command.time-elapsed",
TextComponent.of(timeS),
TextComponent.of(changed),
TextComponent.of(Math.round(throughput))
));
}
worldEdit.flushBlockBag(actor, editSession);
}
// TODO: Ping @MattBDev to reimplement 2020-02-04
// CFICommands.CFISettings cfi = actor.getMeta("CFISettings");
// if (cfi != null) {
// HeightMapMCAGenerator gen = cfi.getGenerator();
// if (gen != null && gen.isModified()) {
// try {
// gen.update();
// CFIChangeSet set = new CFIChangeSet(gen, actor.getUniqueId());
// session.remember(actor, gen, set, actor.getLimit());
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }
Request.reset();
}
event.setCancelled(true);
}
private void printUsage(Actor actor, List<String> arguments) {
PrintCommandHelp.help(arguments, 0, false,
getCommandManager(), actor, "//help"
);
}
//FAWE end
//FAWE start - Event & suggestions, make method public
public MemoizingValueAccess initializeInjectedValues(Arguments arguments, Actor actor, Event event, boolean isSuggestions) {
//FAWE end
InjectedValueStore store = MapBackedValueStore.create();
store.injectValue(Key.of(Actor.class), ValueProvider.constant(actor));
if (actor instanceof Player) {
store.injectValue(Key.of(Player.class), ValueProvider.constant((Player) actor));
} else {
store.injectValue(Key.of(Player.class), context -> {
throw new CommandException(Caption.of("worldedit.command.player-only"), ImmutableList.of());
});
}
store.injectValue(Key.of(Arguments.class), ValueProvider.constant(arguments));
store.injectValue(
Key.of(LocalSession.class),
context -> {
LocalSession localSession = worldEdit.getSessionManager().get(actor);
localSession.tellVersion(actor);
return Optional.of(localSession);
}
);
store.injectValue(Key.of(boolean.class), context -> Optional.of(isSuggestions));
store.injectValue(Key.of(InjectedValueStore.class), ValueProvider.constant(store));
store.injectValue(Key.of(Event.class), ValueProvider.constant(event));
//FAWE start - allow giving editsessions
if (event instanceof CommandEvent) {
EditSession session = ((CommandEvent) event).getSession();
if (session != null) {
store.injectValue(Key.of(EditSession.class), context -> Optional.of(session));
}
}
//FAWE end
return MemoizingValueAccess.wrap(
MergedValueAccess.of(store, globalInjectedValues)
);
}
private void handleUnknownException(Actor actor, Throwable t) {
//FAWE start - Exchange name
LOGGER.error("An unexpected error while handling a FastAsyncWorldEdit command", t);
ErrorReporting.trigger(actor, t);
//FAWE end
}
@Subscribe
public void handleCommandSuggestion(CommandSuggestionEvent event) {
try {
String rawArgs = event.getArguments();
//FAWE start
// Increase the resulting positions by 1 if we remove a leading `/`
final int posOffset = rawArgs.startsWith("/") ? 1 : 0;
String arguments = rawArgs.startsWith("/") ? rawArgs.substring(1) : rawArgs;
//FAWE end
List<Substring> split = parseArgs(arguments).collect(Collectors.toList());
List<String> argStrings = split.stream()
.map(Substring::getSubstring)
.collect(Collectors.toList());
//FAWE start - event & suggestion
MemoizingValueAccess access = initializeInjectedValues(() -> arguments, event.getActor(), event, true);
//FAWE end
ImmutableSet<Suggestion> suggestions;
try {
suggestions = commandManager.getSuggestions(access, argStrings);
} catch (Throwable t) { // catch errors which are *not* command exceptions generated by parsers/suggesters
if (!(t instanceof InputParseException) && !(t instanceof CommandException)) {
Throwable cause = t;
// These exceptions can be very nested, and should not be printed as they are not an actual error.
boolean printError = true;
while ((cause = cause.getCause()) != null) {
if (cause instanceof InputParseException || cause instanceof CommandException) {
printError = false;
break;
}
}
if (printError) {
LOGGER.error("Unexpected error occurred while generating suggestions for input: {}", arguments, t);
return;
}
}
return;
}
event.setSuggestions(suggestions.stream()
.map(suggestion -> {
int noSlashLength = arguments.length();
Substring original = suggestion.getReplacedArgument() == split.size()
? Substring.from(arguments, noSlashLength, noSlashLength)
: split.get(suggestion.getReplacedArgument());
return Substring.wrap(
suggestion.getSuggestion(),
original.getStart() + posOffset,
original.getEnd() + posOffset
);
}).collect(Collectors.toList()));
} catch (ConditionFailedException e) {
if (e.getCondition() instanceof PermissionCondition) {
event.setSuggestions(new ArrayList<>());
}
}
}
/**
* Get the command manager instance.
*
* @return the command manager
*/
public CommandManager getCommandManager() {
return commandManager;
}
}