package com.hypixel.hytale.builtin.buildertools.objimport;
import com.hypixel.hytale.builtin.buildertools.BlockColorIndex;
import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.common.util.StringUtil;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;
import com.hypixel.hytale.protocol.packets.interface_.Page;
import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage;
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule;
import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection;
import com.hypixel.hytale.server.core.ui.DropdownEntryInfo;
import com.hypixel.hytale.server.core.ui.LocalizableString;
import com.hypixel.hytale.server.core.ui.browser.FileBrowserConfig;
import com.hypixel.hytale.server.core.ui.browser.ServerFileBrowser;
import com.hypixel.hytale.server.core.ui.builder.EventData;
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class ObjImportPage extends InteractiveCustomUIPage<PageData> {
private static final String DEFAULT_BLOCK = "Rock_Stone";
private static final int DEFAULT_HEIGHT = 20;
private static final int MIN_HEIGHT = 1;
private static final int MAX_HEIGHT = 320;
private static final float DEFAULT_SCALE = 1.0F;
private static final float MIN_SCALE = 0.01F;
private static final float MAX_SCALE = 100.0F;
private static final String PASTE_TOOL_ID = "EditorTool_Paste";
private static final Path IMPORTS_DIR = Paths.get("imports", "models");
@Nonnull
private String objPath = "";
private int targetHeight = 20;
private boolean useScaleMode = false;
private float scale = 1.0F;
@Nonnull
private String blockPattern = "Rock_Stone";
private boolean fillSolid = true;
private boolean useMaterials = true;
private boolean autoDetectTextures = false;
@Nonnull
private String originStr = "bottom_center";
@Nonnull
private Origin origin;
@Nonnull
private String rotationStr;
@Nonnull
private MeshRotation rotation;
@Nullable
private String statusMessage;
private boolean isError;
private boolean isProcessing;
private boolean showBrowser;
@Nonnull
private final ServerFileBrowser browser;
private static final String[] AUTO_DETECT_SUFFIXES = new String[]{"", "_dif", "_diffuse"};
private static final String[] AUTO_DETECT_EXTENSIONS = new String[]{".png", ".jpg", ".jpeg"};
public ObjImportPage(@Nonnull PlayerRef playerRef) {
super(playerRef, CustomPageLifetime.CanDismiss, ObjImportPage.PageData.CODEC);
this.origin = ObjImportPage.Origin.BOTTOM_CENTER;
this.rotationStr = "y_up";
this.rotation = ObjImportPage.MeshRotation.NONE;
this.statusMessage = null;
this.isError = false;
this.isProcessing = false;
this.showBrowser = false;
FileBrowserConfig config = FileBrowserConfig.builder().listElementId("#BrowserPage #FileList").searchInputId("#BrowserPage #SearchInput").currentPathId("#BrowserPage #CurrentPath").roots(List.of(new FileBrowserConfig.RootEntry("Imports", IMPORTS_DIR))).allowedExtensions(".obj").enableRootSelector(false).enableSearch(true).enableDirectoryNav(true).maxResults(50).build();
try {
Files.createDirectories(IMPORTS_DIR);
} catch (IOException var4) {
}
this.browser = new ServerFileBrowser(config);
}
public void build(@Nonnull Ref<EntityStore> ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store<EntityStore> store) {
commandBuilder.append("Pages/ObjImportPage.ui");
commandBuilder.set("#ObjPath #Input.Value", this.objPath);
commandBuilder.set("#HeightInput #Input.Value", this.targetHeight);
commandBuilder.set("#ScaleInput #Input.Value", this.scale);
commandBuilder.set("#BlockPattern #Input.Value", this.blockPattern);
commandBuilder.set("#FillModeCheckbox #CheckBox.Value", this.fillSolid);
commandBuilder.set("#UseMaterialsCheckbox #CheckBox.Value", this.useMaterials);
commandBuilder.set("#AutoDetectTexturesCheckbox #CheckBox.Value", this.autoDetectTextures);
commandBuilder.set("#HeightInput.Visible", !this.useScaleMode);
commandBuilder.set("#ScaleInput.Visible", this.useScaleMode);
List<DropdownEntryInfo> sizeModeEntries = new ArrayList();
sizeModeEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.sizeMode.height"), "height"));
sizeModeEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.sizeMode.scale"), "scale"));
commandBuilder.set("#SizeModeInput #Input.Entries", sizeModeEntries);
commandBuilder.set("#SizeModeInput #Input.Value", this.useScaleMode ? "scale" : "height");
List<DropdownEntryInfo> originEntries = new ArrayList();
originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_front_left"), "bottom_front_left"));
originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_center"), "bottom_center"));
originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.center"), "center"));
originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.top_center"), "top_center"));
commandBuilder.set("#OriginInput #Input.Entries", originEntries);
commandBuilder.set("#OriginInput #Input.Value", this.originStr);
List<DropdownEntryInfo> axisEntries = new ArrayList();
axisEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.axis.yUp"), "y_up"));
axisEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.axis.zUp"), "z_up"));
axisEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.axis.xUp"), "x_up"));
commandBuilder.set("#RotationInput #Input.Entries", axisEntries);
commandBuilder.set("#RotationInput #Input.Value", this.rotationStr);
this.updateStatus(commandBuilder);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ObjPath #Input", EventData.of("@ObjPath", "#ObjPath #Input.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#HeightInput #Input", EventData.of("@Height", "#HeightInput #Input.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ScaleInput #Input", EventData.of("@Scale", "#ScaleInput #Input.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SizeModeInput #Input", EventData.of("SizeMode", "#SizeModeInput #Input.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#BlockPattern #Input", EventData.of("@BlockPattern", "#BlockPattern #Input.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#FillModeCheckbox #CheckBox", EventData.of("@FillSolid", "#FillModeCheckbox #CheckBox.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#UseMaterialsCheckbox #CheckBox", EventData.of("@UseMaterials", "#UseMaterialsCheckbox #CheckBox.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#AutoDetectTexturesCheckbox #CheckBox", EventData.of("@AutoDetectTextures", "#AutoDetectTexturesCheckbox #CheckBox.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#OriginInput #Input", EventData.of("@Origin", "#OriginInput #Input.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#RotationInput #Input", EventData.of("@Rotation", "#RotationInput #Input.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ImportButton", EventData.of("Import", "true"));
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ObjPath #BrowseButton", EventData.of("Browse", "true"));
commandBuilder.set("#FormContainer.Visible", !this.showBrowser);
commandBuilder.set("#BrowserPage.Visible", this.showBrowser);
if (this.showBrowser) {
this.buildBrowserPage(commandBuilder, eventBuilder);
}
}
private void buildBrowserPage(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) {
this.browser.buildSearchInput(commandBuilder, eventBuilder);
this.browser.buildCurrentPath(commandBuilder);
this.browser.buildFileList(commandBuilder, eventBuilder);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #SelectButton", EventData.of("BrowserSelect", "true"));
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #CancelButton", EventData.of("BrowserCancel", "true"));
}
private void updateStatus(@Nonnull UICommandBuilder commandBuilder) {
if (this.statusMessage != null) {
commandBuilder.set("#StatusText.Text", this.statusMessage);
commandBuilder.set("#StatusText.Visible", true);
commandBuilder.set("#StatusText.Style.TextColor", this.isError ? "#e74c3c" : "#cfd8e3");
} else {
commandBuilder.set("#StatusText.Visible", false);
}
}
private void setError(@Nonnull String message) {
this.statusMessage = message;
this.isError = true;
this.isProcessing = false;
this.rebuild();
}
private void setStatus(@Nonnull String message) {
this.statusMessage = message;
this.isError = false;
this.rebuild();
}
public void handleDataEvent(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull PageData data) {
boolean needsUpdate = false;
if (data.browse != null && data.browse) {
this.showBrowser = true;
this.rebuild();
} else if (data.browserCancel != null && data.browserCancel) {
this.showBrowser = false;
this.rebuild();
} else if (data.browserSelect != null && data.browserSelect) {
if (!this.browser.getSelectedItems().isEmpty()) {
String selectedPath = (String)this.browser.getSelectedItems().iterator().next();
this.objPath = this.browser.getRoot().resolve(selectedPath).toString();
}
this.showBrowser = false;
this.rebuild();
} else {
if (this.showBrowser && (data.file != null || data.searchQuery != null || data.searchResult != null)) {
boolean handled = false;
if (data.searchQuery != null) {
this.browser.setSearchQuery(data.searchQuery.trim().toLowerCase());
handled = true;
}
if (data.file != null) {
String fileName = data.file;
if ("..".equals(fileName)) {
this.browser.navigateUp();
handled = true;
} else {
Path targetPath = this.browser.resolveFromCurrent(fileName);
if (targetPath != null && Files.isDirectory(targetPath, new LinkOption[0])) {
this.browser.navigateTo(Paths.get(fileName));
handled = true;
} else if (targetPath != null && Files.isRegularFile(targetPath, new LinkOption[0])) {
this.objPath = targetPath.toString();
this.showBrowser = false;
this.rebuild();
return;
}
}
}
if (data.searchResult != null) {
Path resolvedPath = this.browser.resolveSecure(data.searchResult);
if (resolvedPath != null && Files.isRegularFile(resolvedPath, new LinkOption[0])) {
this.objPath = resolvedPath.toString();
this.showBrowser = false;
this.rebuild();
return;
}
}
if (handled) {
UICommandBuilder commandBuilder = new UICommandBuilder();
UIEventBuilder eventBuilder = new UIEventBuilder();
this.browser.buildFileList(commandBuilder, eventBuilder);
this.browser.buildCurrentPath(commandBuilder);
this.sendUpdate(commandBuilder, eventBuilder, false);
return;
}
}
if (data.objPath != null) {
this.objPath = StringUtil.stripQuotes(data.objPath.trim());
this.statusMessage = null;
needsUpdate = true;
}
if (data.height != null) {
this.targetHeight = Math.max(1, Math.min(320, data.height));
needsUpdate = true;
}
if (data.scale != null) {
this.scale = Math.max(0.01F, Math.min(100.0F, data.scale));
needsUpdate = true;
}
if (data.sizeMode != null) {
this.useScaleMode = "scale".equalsIgnoreCase(data.sizeMode);
this.rebuild();
} else {
if (data.blockPattern != null) {
this.blockPattern = data.blockPattern.trim();
needsUpdate = true;
}
if (data.fillSolid != null) {
this.fillSolid = data.fillSolid;
needsUpdate = true;
}
if (data.useMaterials != null) {
this.useMaterials = data.useMaterials;
needsUpdate = true;
}
if (data.autoDetectTextures != null) {
this.autoDetectTextures = data.autoDetectTextures;
needsUpdate = true;
}
if (data.origin != null) {
this.originStr = data.origin.trim().toLowerCase();
Origin var10001;
switch (this.originStr) {
case "bottom_front_left" -> var10001 = ObjImportPage.Origin.BOTTOM_FRONT_LEFT;
case "center" -> var10001 = ObjImportPage.Origin.CENTER;
case "top_center" -> var10001 = ObjImportPage.Origin.TOP_CENTER;
default -> var10001 = ObjImportPage.Origin.BOTTOM_CENTER;
}
this.origin = var10001;
needsUpdate = true;
}
if (data.rotation != null) {
this.rotationStr = data.rotation.trim().toLowerCase();
MeshRotation var16;
switch (this.rotationStr) {
case "z_up" -> var16 = ObjImportPage.MeshRotation.Z_UP_TO_Y_UP;
case "x_up" -> var16 = ObjImportPage.MeshRotation.X_UP_TO_Y_UP;
default -> var16 = ObjImportPage.MeshRotation.NONE;
}
this.rotation = var16;
needsUpdate = true;
}
if (data.doImport != null && data.doImport && !this.isProcessing) {
this.performImport(ref, store);
} else {
if (needsUpdate) {
this.sendUpdate();
}
}
}
}
}
@Nullable
private List<WeightedBlock> parseBlockPattern(@Nonnull String pattern) {
List<WeightedBlock> result = new ArrayList();
String[] parts = pattern.split(",");
for(String part : parts) {
part = part.trim();
if (!part.isEmpty()) {
int weight = 100;
String blockName = part;
int pctIdx = part.indexOf(37);
if (pctIdx > 0) {
try {
weight = Integer.parseInt(part.substring(0, pctIdx).trim());
blockName = part.substring(pctIdx + 1).trim();
} catch (NumberFormatException var12) {
return null;
}
}
int blockId = BlockType.getAssetMap().getIndex(blockName);
if (blockId == -2147483648) {
return null;
}
result.add(new WeightedBlock(blockId, weight));
}
}
return result.isEmpty() ? null : result;
}
private int selectRandomBlock(@Nonnull List<WeightedBlock> blocks, @Nonnull Random random) {
if (blocks.isEmpty()) {
throw new IllegalStateException("Cannot select from empty blocks list");
} else if (blocks.size() == 1) {
return ((WeightedBlock)blocks.get(0)).blockId;
} else {
int totalWeight = 0;
for(WeightedBlock wb : blocks) {
totalWeight += wb.weight;
}
if (totalWeight <= 0) {
return ((WeightedBlock)blocks.get(0)).blockId;
} else {
int roll = random.nextInt(totalWeight);
int cumulative = 0;
for(WeightedBlock wb : blocks) {
cumulative += wb.weight;
if (roll < cumulative) {
return wb.blockId;
}
}
return ((WeightedBlock)blocks.get(0)).blockId;
}
}
}
private void performImport(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
if (this.objPath.isEmpty()) {
this.setError("Please enter a path to an OBJ file");
} else {
Path path = Paths.get(this.objPath);
if (!SingleplayerModule.isOwner(this.playerRef)) {
Path normalizedPath = path.toAbsolutePath().normalize();
Path normalizedImports = IMPORTS_DIR.toAbsolutePath().normalize();
if (!normalizedPath.startsWith(normalizedImports)) {
this.setError("Files must be in the server's imports/models directory");
return;
}
}
if (!Files.exists(path, new LinkOption[0])) {
this.setError("File not found: " + this.objPath);
} else if (!this.objPath.toLowerCase().endsWith(".obj")) {
this.setError("File must be a .obj file");
} else {
List<WeightedBlock> blocks = this.parseBlockPattern(this.blockPattern);
if (blocks == null) {
this.setError("Invalid block pattern: " + this.blockPattern);
} else {
this.isProcessing = true;
this.setStatus("Processing...");
Player playerComponent = (Player)store.getComponent(ref, Player.getComponentType());
PlayerRef playerRefComponent = (PlayerRef)store.getComponent(ref, PlayerRef.getComponentType());
if (playerComponent != null && playerRefComponent != null) {
int finalHeight = this.targetHeight;
boolean finalUseScaleMode = this.useScaleMode;
float finalScale = this.scale;
String finalPath = this.objPath;
boolean finalFillSolid = this.fillSolid;
boolean finalUseMaterials = this.useMaterials;
boolean finalAutoDetectTextures = this.autoDetectTextures;
Origin finalOrigin = this.origin;
MeshRotation finalRotation = this.rotation;
BuilderToolsPlugin.addToQueue(playerComponent, playerRefComponent, (r, builderState, componentAccessor) -> {
try {
Path objFilePath = Paths.get(finalPath);
ObjParser.ObjMesh mesh = ObjParser.parse(objFilePath);
switch (finalRotation.ordinal()) {
case 1 -> mesh.transformZUpToYUp();
case 2 -> mesh.transformXUpToYUp();
}
int computedHeight;
if (finalUseScaleMode) {
float[] bounds = mesh.getBounds();
float meshHeight = bounds[4] - bounds[1];
computedHeight = Math.max(1, (int)Math.ceil((double)(meshHeight * finalScale)));
} else {
computedHeight = finalHeight;
}
if (blocks.isEmpty()) {
this.setError("No blocks available for import");
return;
}
BlockColorIndex colorIndex = BuilderToolsPlugin.get().getBlockColorIndex();
Map<String, BufferedImage> materialTextures = new HashMap();
Map<String, Integer> materialToBlockId = new HashMap();
int defaultBlockId = ((WeightedBlock)blocks.get(0)).blockId;
if (finalUseMaterials && mesh.mtlLib() != null) {
this.loadMaterialData(objFilePath, mesh, colorIndex, materialTextures, materialToBlockId, finalAutoDetectTextures);
if (!materialToBlockId.isEmpty()) {
defaultBlockId = (Integer)materialToBlockId.values().iterator().next();
}
}
boolean hasUvTextures = mesh.hasUvCoordinates() && !materialTextures.isEmpty();
boolean preserveOrigin = finalOrigin == ObjImportPage.Origin.BOTTOM_FRONT_LEFT;
MeshVoxelizer.VoxelResult result;
if (hasUvTextures) {
result = MeshVoxelizer.voxelize(mesh, computedHeight, finalFillSolid, materialTextures, materialToBlockId, colorIndex, defaultBlockId, preserveOrigin);
} else {
result = MeshVoxelizer.voxelize(mesh, computedHeight, finalFillSolid, (Map)null, materialToBlockId, colorIndex, defaultBlockId, preserveOrigin);
}
TextureSampler.clearCache();
int offsetX = 0;
int offsetY = 0;
int offsetZ = 0;
switch (finalOrigin.ordinal()) {
case 0:
default:
break;
case 1:
offsetX = -result.sizeX() / 2;
offsetZ = -result.sizeZ() / 2;
break;
case 2:
offsetX = -result.sizeX() / 2;
offsetY = -result.sizeY() / 2;
offsetZ = -result.sizeZ() / 2;
break;
case 3:
offsetX = -result.sizeX() / 2;
offsetY = -result.sizeY();
offsetZ = -result.sizeZ() / 2;
}
BlockSelection selection = new BlockSelection(result.countSolid(), 0);
selection.setPosition(0, 0, 0);
Random random = new Random();
boolean hasMaterialBlockIds = result.blockIds() != null;
for(int x = 0; x < result.sizeX(); ++x) {
for(int y = 0; y < result.sizeY(); ++y) {
for(int z = 0; z < result.sizeZ(); ++z) {
if (result.voxels()[x][y][z]) {
int blockId;
if (hasMaterialBlockIds) {
blockId = result.getBlockId(x, y, z);
if (blockId == 0) {
blockId = this.selectRandomBlock(blocks, random);
}
} else {
blockId = this.selectRandomBlock(blocks, random);
}
selection.addBlockAtLocalPos(x + offsetX, y + offsetY, z + offsetZ, blockId, 0, 0, 0);
}
}
}
}
selection.setSelectionArea(new Vector3i(offsetX, offsetY, offsetZ), new Vector3i(result.sizeX() - 1 + offsetX, result.sizeY() - 1 + offsetY, result.sizeZ() - 1 + offsetZ));
builderState.setSelection(selection);
builderState.sendSelectionToClient();
int blockCount = result.countSolid();
String textureInfo = hasUvTextures ? " (UV textured)" : "";
this.statusMessage = String.format("Success! %d blocks copied to clipboard (%dx%dx%d)%s", blockCount, result.sizeX(), result.sizeY(), result.sizeZ(), textureInfo);
this.isProcessing = false;
playerRefComponent.sendMessage(Message.translation("server.builderTools.objImport.success").param("count", blockCount).param("width", result.sizeX()).param("height", result.sizeY()).param("depth", result.sizeZ()));
playerComponent.getPageManager().setPage(r, store, Page.None);
this.switchToPasteTool(playerComponent, playerRefComponent);
} catch (ObjParser.ObjParseException e) {
BuilderToolsPlugin.get().getLogger().at(Level.WARNING).log("OBJ parse error: %s", e.getMessage());
this.setError("Parse error: " + e.getMessage());
} catch (IOException e) {
((HytaleLogger.Api)BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(e)).log("OBJ import IO error");
this.setError("IO error: " + e.getMessage());
} catch (Exception e) {
((HytaleLogger.Api)BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(e)).log("OBJ import error");
this.setError("Error: " + e.getMessage());
}
});
} else {
this.setError("Player not found");
}
}
}
}
}
private void switchToPasteTool(@Nonnull Player playerComponent, @Nonnull PlayerRef playerRef) {
Inventory inventory = playerComponent.getInventory();
ItemContainer hotbar = inventory.getHotbar();
ItemContainer storage = inventory.getStorage();
ItemContainer tools = inventory.getTools();
int hotbarSize = hotbar.getCapacity();
for(short slot = 0; slot < hotbarSize; ++slot) {
ItemStack itemStack = hotbar.getItemStack(slot);
if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) {
inventory.setActiveHotbarSlot((byte)slot);
playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)slot));
return;
}
}
short emptySlot = -1;
for(short slot = 0; slot < hotbarSize; ++slot) {
ItemStack itemStack = hotbar.getItemStack(slot);
if (itemStack == null || itemStack.isEmpty()) {
emptySlot = slot;
break;
}
}
if (emptySlot != -1) {
for(short slot = 0; slot < storage.getCapacity(); ++slot) {
ItemStack itemStack = storage.getItemStack(slot);
if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) {
storage.moveItemStackFromSlotToSlot(slot, 1, hotbar, emptySlot);
inventory.setActiveHotbarSlot((byte)emptySlot);
playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot));
return;
}
}
ItemStack pasteToolStack = null;
for(short slot = 0; slot < tools.getCapacity(); ++slot) {
ItemStack itemStack = tools.getItemStack(slot);
if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) {
pasteToolStack = itemStack;
break;
}
}
if (pasteToolStack != null) {
hotbar.setItemStackForSlot(emptySlot, new ItemStack(pasteToolStack.getItemId()));
inventory.setActiveHotbarSlot((byte)emptySlot);
playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot));
}
}
}
private void loadMaterialData(@Nonnull Path objPath, @Nonnull ObjParser.ObjMesh mesh, @Nonnull BlockColorIndex colorIndex, @Nonnull Map<String, BufferedImage> materialTextures, @Nonnull Map<String, Integer> materialToBlockId, boolean autoDetectTextures) throws IOException {
if (mesh.mtlLib() != null) {
Path mtlPath = objPath.getParent().resolve(mesh.mtlLib());
if (Files.exists(mtlPath, new LinkOption[0])) {
Map<String, MtlParser.MtlMaterial> materials = MtlParser.parse(mtlPath);
Path textureDir = mtlPath.getParent();
for(Map.Entry<String, MtlParser.MtlMaterial> entry : materials.entrySet()) {
String materialName = (String)entry.getKey();
MtlParser.MtlMaterial material = (MtlParser.MtlMaterial)entry.getValue();
String texturePath = material.diffuseTexturePath();
if (texturePath == null && autoDetectTextures) {
texturePath = findMatchingTexture(textureDir, materialName);
}
if (texturePath != null) {
Path resolvedPath = textureDir.resolve(texturePath);
BufferedImage texture = TextureSampler.loadTexture(resolvedPath);
if (texture != null) {
materialTextures.put(materialName, texture);
int[] avgColor = TextureSampler.getAverageColor(resolvedPath);
if (avgColor != null) {
int blockId = colorIndex.findClosestBlock(avgColor[0], avgColor[1], avgColor[2]);
if (blockId > 0) {
materialToBlockId.put(materialName, blockId);
}
}
continue;
}
}
int[] rgb = material.getDiffuseColorRGB();
if (rgb != null) {
int blockId = colorIndex.findClosestBlock(rgb[0], rgb[1], rgb[2]);
if (blockId > 0) {
materialToBlockId.put(materialName, blockId);
}
}
}
}
}
}
@Nullable
private static String findMatchingTexture(@Nonnull Path directory, @Nonnull String materialName) {
for(String suffix : AUTO_DETECT_SUFFIXES) {
for(String ext : AUTO_DETECT_EXTENSIONS) {
String filename = materialName + suffix + ext;
if (Files.exists(directory.resolve(filename), new LinkOption[0])) {
return filename;
}
}
}
return null;
}
private static record WeightedBlock(int blockId, int weight) {
}
public static enum Origin {
BOTTOM_FRONT_LEFT,
BOTTOM_CENTER,
CENTER,
TOP_CENTER;
private Origin() {
}
}
public static enum MeshRotation {
NONE,
Z_UP_TO_Y_UP,
X_UP_TO_Y_UP;
private MeshRotation() {
}
}
public static class PageData {
static final String KEY_OBJ_PATH = "@ObjPath";
static final String KEY_HEIGHT = "@Height";
static final String KEY_SCALE = "@Scale";
static final String KEY_SIZE_MODE = "SizeMode";
static final String KEY_BLOCK_PATTERN = "@BlockPattern";
static final String KEY_FILL_SOLID = "@FillSolid";
static final String KEY_USE_MATERIALS = "@UseMaterials";
static final String KEY_AUTO_DETECT_TEXTURES = "@AutoDetectTextures";
static final String KEY_ORIGIN = "@Origin";
static final String KEY_ROTATION = "@Rotation";
static final String KEY_IMPORT = "Import";
static final String KEY_BROWSE = "Browse";
static final String KEY_BROWSER_SELECT = "BrowserSelect";
static final String KEY_BROWSER_CANCEL = "BrowserCancel";
public static final BuilderCodec<PageData> CODEC;
@Nullable
private String objPath;
@Nullable
private Integer height;
@Nullable
private Float scale;
@Nullable
private String sizeMode;
@Nullable
private String blockPattern;
@Nullable
private Boolean fillSolid;
@Nullable
private Boolean useMaterials;
@Nullable
private Boolean autoDetectTextures;
@Nullable
private String origin;
@Nullable
private String rotation;
@Nullable
private Boolean doImport;
@Nullable
private Boolean browse;
@Nullable
private Boolean browserSelect;
@Nullable
private Boolean browserCancel;
@Nullable
private String file;
@Nullable
private String searchQuery;
@Nullable
private String searchResult;
public PageData() {
}
static {
CODEC = ((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)BuilderCodec.builder(PageData.class, PageData::new).addField(new KeyedCodec("@ObjPath", Codec.STRING), (entry, s) -> entry.objPath = s, (entry) -> entry.objPath)).addField(new KeyedCodec("@Height", Codec.INTEGER), (entry, i) -> entry.height = i, (entry) -> entry.height)).addField(new KeyedCodec("@Scale", Codec.FLOAT), (entry, f) -> entry.scale = f, (entry) -> entry.scale)).addField(new KeyedCodec("SizeMode", Codec.STRING), (entry, s) -> entry.sizeMode = s, (entry) -> entry.sizeMode)).addField(new KeyedCodec("@BlockPattern", Codec.STRING), (entry, s) -> entry.blockPattern = s, (entry) -> entry.blockPattern)).addField(new KeyedCodec("@FillSolid", Codec.BOOLEAN), (entry, b) -> entry.fillSolid = b, (entry) -> entry.fillSolid)).addField(new KeyedCodec("@UseMaterials", Codec.BOOLEAN), (entry, b) -> entry.useMaterials = b, (entry) -> entry.useMaterials)).addField(new KeyedCodec("@AutoDetectTextures", Codec.BOOLEAN), (entry, b) -> entry.autoDetectTextures = b, (entry) -> entry.autoDetectTextures)).addField(new KeyedCodec("@Origin", Codec.STRING), (entry, s) -> entry.origin = s, (entry) -> entry.origin)).addField(new KeyedCodec("@Rotation", Codec.STRING), (entry, s) -> entry.rotation = s, (entry) -> entry.rotation)).addField(new KeyedCodec("Import", Codec.STRING), (entry, s) -> entry.doImport = "true".equalsIgnoreCase(s), (entry) -> entry.doImport != null && entry.doImport ? "true" : null)).addField(new KeyedCodec("Browse", Codec.STRING), (entry, s) -> entry.browse = "true".equalsIgnoreCase(s), (entry) -> entry.browse != null && entry.browse ? "true" : null)).addField(new KeyedCodec("BrowserSelect", Codec.STRING), (entry, s) -> entry.browserSelect = "true".equalsIgnoreCase(s), (entry) -> entry.browserSelect != null && entry.browserSelect ? "true" : null)).addField(new KeyedCodec("BrowserCancel", Codec.STRING), (entry, s) -> entry.browserCancel = "true".equalsIgnoreCase(s), (entry) -> entry.browserCancel != null && entry.browserCancel ? "true" : null)).addField(new KeyedCodec("File", Codec.STRING), (entry, s) -> entry.file = s, (entry) -> entry.file)).addField(new KeyedCodec("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, (entry) -> entry.searchQuery)).addField(new KeyedCodec("SearchResult", Codec.STRING), (entry, s) -> entry.searchResult = s, (entry) -> entry.searchResult)).build();
}
}
}