BrushConfigCommandExecutor.java
package com.hypixel.hytale.builtin.buildertools.scriptedbrushes;
import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin;
import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings;
import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.GlobalBrushOperation;
import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation;
import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.message.MessageFormat;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class BrushConfigCommandExecutor {
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
@Nonnull
private final Map<String, Integer> persistentStoredVariables;
private final BrushConfig brushConfig;
@Nonnull
private final Map<String, GlobalBrushOperation> globalOperations;
private int currentOperationIndex = 0;
@Nonnull
private final List<SequenceBrushOperation> sequentialOperations;
private boolean inDebugSteppingMode;
private boolean printOperations;
private boolean enableBreakpoints;
private DebugOutputTarget debugOutputTarget;
private boolean breakOnError;
@Nonnull
private final Map<String, BrushConfig> brushConfigStoredSnapshots;
private boolean allowOverwritingSavedSnapshots;
@Nonnull
private final Map<String, Integer> storedIndexes;
private boolean ignoreExistingBrushData;
private BrushConfigEditStore edit;
private long startTime;
public BrushConfigCommandExecutor(BrushConfig brushConfig) {
this.debugOutputTarget = BrushConfigCommandExecutor.DebugOutputTarget.Chat;
this.allowOverwritingSavedSnapshots = true;
this.persistentStoredVariables = new Object2IntOpenHashMap();
this.sequentialOperations = new ObjectArrayList<SequenceBrushOperation>();
this.globalOperations = new Object2ObjectOpenHashMap<String, GlobalBrushOperation>();
this.brushConfigStoredSnapshots = new Object2ObjectOpenHashMap<String, BrushConfig>();
this.storedIndexes = new Object2IntOpenHashMap();
this.brushConfig = brushConfig;
}
public void resetInternalState() {
this.currentOperationIndex = 0;
this.brushConfigStoredSnapshots.clear();
this.ignoreExistingBrushData = false;
this.printOperations = false;
this.inDebugSteppingMode = false;
this.enableBreakpoints = false;
this.debugOutputTarget = BrushConfigCommandExecutor.DebugOutputTarget.Chat;
this.breakOnError = false;
this.startTime = System.nanoTime();
this.storedIndexes.clear();
}
public void execute(@Nonnull Ref<EntityStore> ref, @Nonnull World world, @Nonnull Vector3i origin, boolean isHoldDownInteraction, @Nonnull InteractionType interactionType, @Nullable Consumer<BrushConfig> existingBrushDataLoadingConsumer, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
Player playerComponent = (Player)componentAccessor.getComponent(ref, Player.getComponentType());
assert playerComponent != null;
UUIDComponent uuidComponent = (UUIDComponent)componentAccessor.getComponent(ref, UUIDComponent.getComponentType());
assert uuidComponent != null;
this.resetInternalState();
this.brushConfig.resetToDefaultValues();
this.brushConfig.beginExecution(origin, isHoldDownInteraction, interactionType);
PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings = (PrototypePlayerBuilderToolSettings)ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(uuidComponent.getUuid());
if (!isHoldDownInteraction) {
prototypePlayerBuilderToolSettings.getIgnoredPaintOperations().clear();
}
this.edit = new BrushConfigEditStore(prototypePlayerBuilderToolSettings.addIgnoredPaintOperation(), this.brushConfig, world);
for(int i = 0; i < this.sequentialOperations.size(); ++i) {
((SequenceBrushOperation)this.sequentialOperations.get(i)).resetInternalState();
((SequenceBrushOperation)this.sequentialOperations.get(i)).preExecutionModifyBrushConfig(this, i);
}
for(GlobalBrushOperation globalOperation : this.globalOperations.values()) {
globalOperation.resetInternalState();
globalOperation.modifyBrushConfig(ref, this.brushConfig, this, componentAccessor);
}
if (!this.ignoreExistingBrushData && existingBrushDataLoadingConsumer != null) {
existingBrushDataLoadingConsumer.accept(this.brushConfig);
}
if (!this.inDebugSteppingMode) {
while(this.brushConfig.isCurrentlyExecuting() && !this.inDebugSteppingMode && this.step(ref, false, componentAccessor).equals(BrushConfig.BCExecutionStatus.Continue)) {
}
}
}
public void execute(@Nonnull Ref<EntityStore> ref, World world, Vector3i origin, boolean isHoldDownInteraction, InteractionType interactionType, ComponentAccessor<EntityStore> componentAccessor) {
this.execute(ref, world, origin, isHoldDownInteraction, interactionType, (Consumer)null, componentAccessor);
}
@Nonnull
public BrushConfig.BCExecutionStatus step(Ref<EntityStore> ref, boolean placePreviewAfterStep, ComponentAccessor<EntityStore> componentAccessor) {
if (!this.brushConfig.isCurrentlyExecuting()) {
return BrushConfig.BCExecutionStatus.Error;
} else if (this.sequentialOperations.isEmpty()) {
this.brushConfig.setErrorFlag("No operations to execute");
return this.completeStep(ref, placePreviewAfterStep, componentAccessor);
} else {
try {
SequenceBrushOperation brushOperation = (SequenceBrushOperation)this.sequentialOperations.get(this.currentOperationIndex);
if (this.printOperations) {
LOGGER.at(Level.INFO).log("[%d] %s", this.currentOperationIndex, brushOperation.getName());
PlayerRef playerRefComponent = (PlayerRef)componentAccessor.getComponent(ref, PlayerRef.getComponentType());
if (playerRefComponent != null) {
playerRefComponent.sendMessage(Message.translation("server.builderTools.brushConfig.debug.operationExecuting").param("index", this.currentOperationIndex).param("name", brushOperation.getName()));
}
}
brushOperation.modifyBrushConfig(ref, this.brushConfig, this, componentAccessor);
if (this.brushConfig.isHasExecutionContextEncounteredError() || !brushOperation.doesOperateOnBlocks()) {
return this.completeStep(ref, placePreviewAfterStep, componentAccessor);
}
int numModifyBlockIterations = brushOperation.getNumModifyBlockIterations();
for(int i = 0; i < numModifyBlockIterations; ++i) {
brushOperation.beginIterationIndex(i);
ToolOperation.executeShapeOperation(this.brushConfig.getOriginAfterOffset().x, this.brushConfig.getOriginAfterOffset().y, this.brushConfig.getOriginAfterOffset().z, (x, y, z, unused) -> brushOperation.modifyBlocks(ref, this.brushConfig, this, this.edit, x, y, z, componentAccessor), this.brushConfig.getShape(), this.brushConfig.getShapeWidth(), this.brushConfig.getShapeHeight(), this.brushConfig.getShapeThickness(), this.brushConfig.isCapped());
this.edit.flushCurrentEditsToPrevious();
}
} catch (Exception e) {
e.printStackTrace();
this.brushConfig.setErrorFlag(e.getMessage());
}
return this.completeStep(ref, placePreviewAfterStep, componentAccessor);
}
}
@Nonnull
private BrushConfig.BCExecutionStatus completeStep(Ref<EntityStore> ref, boolean placePreviewAfterStep, ComponentAccessor<EntityStore> componentAccessor) {
if (this.brushConfig.isHasExecutionContextEncounteredError()) {
if (this.breakOnError) {
PlayerRef playerRefComponent = (PlayerRef)componentAccessor.getComponent(ref, PlayerRef.getComponentType());
if (playerRefComponent != null) {
playerRefComponent.sendMessage(Message.translation("server.builderTools.brushConfig.debug.breakOnErrorTriggered").param("index", this.currentOperationIndex));
}
LOGGER.at(Level.INFO).log("[Breakpoint] Error at operation #%d - Entering step-through mode", this.currentOperationIndex);
this.inDebugSteppingMode = true;
this.brushConfig.clearError();
return BrushConfig.BCExecutionStatus.Continue;
} else {
this.exitExecution(ref, componentAccessor);
return BrushConfig.BCExecutionStatus.Error;
}
} else {
++this.currentOperationIndex;
if (this.currentOperationIndex >= this.sequentialOperations.size()) {
this.exitExecution(ref, componentAccessor);
return BrushConfig.BCExecutionStatus.Complete;
} else {
Player playerComponent = (Player)componentAccessor.getComponent(ref, Player.getComponentType());
assert playerComponent != null;
PlayerRef playerRefComponent = (PlayerRef)componentAccessor.getComponent(ref, PlayerRef.getComponentType());
assert playerRefComponent != null;
if (placePreviewAfterStep) {
BrushConfigEditStore returnEdit = this.edit;
BuilderToolsPlugin.getState(playerComponent, playerRefComponent).addToQueue((r, s, c) -> s.placeBrushConfig(r, this.startTime, returnEdit, c));
}
return BrushConfig.BCExecutionStatus.Continue;
}
}
}
public void exitExecution(Ref<EntityStore> ref, ComponentAccessor<EntityStore> componentAccessor) {
Player playerComponent = (Player)componentAccessor.getComponent(ref, Player.getComponentType());
assert playerComponent != null;
PlayerRef playerRefComponent = (PlayerRef)componentAccessor.getComponent(ref, PlayerRef.getComponentType());
assert playerRefComponent != null;
if (this.brushConfig.isHasExecutionContextEncounteredError() && this.currentOperationIndex < this.sequentialOperations.size()) {
this.sendExecutionErrorMessage(playerRefComponent, (SequenceBrushOperation)this.sequentialOperations.get(this.currentOperationIndex));
}
if (this.currentOperationIndex != 0) {
BrushConfigEditStore returnEdit = this.edit;
BuilderToolsPlugin.getState(playerComponent, playerRefComponent).addToQueue((r, s, compAccess) -> s.placeBrushConfig(r, this.startTime, returnEdit, compAccess));
}
this.brushConfig.endExecution();
}
private void sendExecutionErrorMessage(PlayerRef playerRef, @Nonnull SequenceBrushOperation brushOperation) {
playerRef.sendMessage(Message.translation("server.builderTools.brushConfig.executionError").param("index", this.currentOperationIndex).param("type", brushOperation.getName()).param("error", this.brushConfig.getExecutionErrorMessage()));
Message header = Message.translation("server.builderTools.brushConfig.settings.header");
Set<Message> items = (Set)brushOperation.getRegisteredOperationSettings().values().stream().map((value) -> Message.translation("server.builderTools.brushConfig.settings.item").param("name", value.getName()).param("value", value.getValueString())).collect(Collectors.toSet());
playerRef.sendMessage(MessageFormat.list(header, items));
}
public void storeOperatingIndex(String name, int index) {
name = name.toLowerCase();
if (this.storedIndexes.containsKey(name)) {
this.brushConfig.setErrorFlag("You already have a stored index with the name: '" + name + "'.");
} else if (index >= this.sequentialOperations.size()) {
this.brushConfig.setErrorFlag("Tried to store an index greater than the total size of sequential operations. Name: '" + name + "', index: '" + index + "'.");
} else {
this.storedIndexes.put(name, index);
}
}
public void loadOperatingIndex(String name) {
this.loadOperatingIndex(name, true);
}
public void loadOperatingIndex(String name, boolean allowFutureJump) {
name = name.toLowerCase();
if (!this.storedIndexes.containsKey(name)) {
this.brushConfig.setErrorFlag("Could not find a stored index with the name: '" + name + "'.");
} else {
int newIndex = (Integer)this.storedIndexes.get(name);
if (!allowFutureJump && newIndex > this.currentOperationIndex) {
this.brushConfig.setErrorFlag("This operation does not allow you to jump to an operation in the future, only the past. Index name: " + name + "'.");
} else {
this.currentOperationIndex = newIndex;
}
}
}
public void clearAllPersistentVariables() {
this.persistentStoredVariables.clear();
}
public void clearPersistentVariable(String variableName) {
variableName = variableName.toLowerCase();
if (!this.persistentStoredVariables.containsKey(variableName)) {
this.brushConfig.setErrorFlag("Could not find a stored persistent variable with the name: '" + variableName + "'.");
} else {
this.persistentStoredVariables.remove(variableName);
}
}
public void setPersistentVariable(String variableName, int value) {
variableName = variableName.toLowerCase();
this.persistentStoredVariables.put(variableName, value);
}
public int getPersistentVariableOrDefault(String variableName, int defaultValue) {
variableName = variableName.toLowerCase();
return !this.persistentStoredVariables.containsKey(variableName) ? defaultValue : (Integer)this.persistentStoredVariables.get(variableName);
}
public void storeBrushConfigSnapshot(@Nonnull String name) {
this.brushConfigStoredSnapshots.put(name.toLowerCase(), new BrushConfig(this.brushConfig));
}
public void loadBrushConfigSnapshot(String name, @Nonnull BrushConfig.DataSettingFlags... dataToLoad) {
name = name.toLowerCase();
if (!this.brushConfigStoredSnapshots.containsKey(name)) {
this.brushConfig.setErrorFlag("Could not find a stored brush config snapshot with the name: '" + name + "'.");
} else {
BrushConfig loadedBrushConfig = (BrushConfig)this.brushConfigStoredSnapshots.get(name);
for(BrushConfig.DataSettingFlags dataLoadFlag : dataToLoad) {
dataLoadFlag.loadData(this.brushConfig, loadedBrushConfig);
}
}
}
public void setAllowOverwritingSavedSnapshots(boolean allowOverwritingSavedSnapshots) {
this.allowOverwritingSavedSnapshots = allowOverwritingSavedSnapshots;
}
@Nonnull
public List<SequenceBrushOperation> getSequentialOperations() {
return this.sequentialOperations;
}
@Nonnull
public Map<String, GlobalBrushOperation> getGlobalOperations() {
return this.globalOperations;
}
public boolean isIgnoreExistingBrushData() {
return this.ignoreExistingBrushData;
}
public boolean isInDebugSteppingMode() {
return this.inDebugSteppingMode;
}
public BrushConfigEditStore getEdit() {
return this.edit;
}
public void setInDebugSteppingMode(boolean inDebugSteppingMode) {
this.inDebugSteppingMode = inDebugSteppingMode;
}
public void setPrintOperations(boolean printOperations) {
this.printOperations = printOperations;
}
public void setIgnoreExistingBrushData(boolean ignoreExistingBrushData) {
this.ignoreExistingBrushData = ignoreExistingBrushData;
}
public void setCurrentlyExecutingActionIndex(int newCurrentOperationIndex) {
if (newCurrentOperationIndex < 0) {
this.brushConfig.setErrorFlag("Cannot set a negative operation index: " + newCurrentOperationIndex);
} else if (newCurrentOperationIndex >= this.sequentialOperations.size()) {
this.brushConfig.setErrorFlag("Cannot set an operation index higher than the highest operation index: " + newCurrentOperationIndex + ". Highest operation index: " + (this.sequentialOperations.size() - 1));
} else {
this.currentOperationIndex = newCurrentOperationIndex - 1;
}
}
public int getCurrentOperationIndex() {
return this.currentOperationIndex;
}
public boolean isEnableBreakpoints() {
return this.enableBreakpoints;
}
public void setEnableBreakpoints(boolean enableBreakpoints) {
this.enableBreakpoints = enableBreakpoints;
}
public DebugOutputTarget getDebugOutputTarget() {
return this.debugOutputTarget;
}
public void setDebugOutputTarget(DebugOutputTarget debugOutputTarget) {
this.debugOutputTarget = debugOutputTarget;
}
public boolean isBreakOnError() {
return this.breakOnError;
}
public void setBreakOnError(boolean breakOnError) {
this.breakOnError = breakOnError;
}
public static enum DebugOutputTarget {
Chat,
Console,
Both;
private DebugOutputTarget() {
}
}
}