| import { Player } from "@minecraft/server"; | |
| import { ModalFormData } from "@minecraft/server-ui"; | |
| import { | | | getPlayerSelection, | | | setPlayerSelection, | | | getPlayerPalettes, | | | getActiveSavedPaletteName | | | } from "../state/index.js"; | | | import { | | | MAX_BRUSH_SIZE, | | | DEFAULT_BRUSH_MATERIAL, | | | DEFAULT_PAINT_MASK_BLOCK, | | | MAX_HEIGHT_BRUSH_AMOUNT, | | | DEFAULT_HEIGHT_BRUSH_AMOUNT, | | | MAX_FOLIAGE_RADIUS, | | | DEFAULT_FOLIAGE_RADIUS, | | | DEFAULT_FOLIAGE_DENSITY, | | | DEFAULT_FOLIAGE_SOURCE, | | | DEFAULT_FOLIAGE_PRESET, | | | DEFAULT_FOLIAGE_CUSTOM, | | | FOLIAGE_PRESETS | | | } from "../constants/index.js"; | | | import { parseWeightedBlockInput, validateBlockIds } from "../utils/index.js"; | | | function getBrushShapeOptions() { | | | const shapeMap = { "Sphere": 'sphere', "Cube": 'cube', "Cylinder": 'cylinder', "Pyramid": 'pyramid', "Octahedron": 'octahedron', "Dome": 'dome', "Bowl": 'bowl', "Cone (Point Up)": 'cone_up', "Cone (Point Down)": 'cone_down', "Disk": 'disk', "Plate": 'plate', "Torus": 'torus' }; | |
| const reverseShapeMap = Object.fromEntries(Object.entries(shapeMap).map(([key, value]) => [value, key])); | |
| const shapeOptions = Object.keys(shapeMap); | |
| return { shapeOptions, shapeMap, reverseShapeMap }; | |
| } | | | export function buildSourceDropdownOptions(player, currentSourceValue, includePresets = false, includeCustom = true) { | |
| const selectionData = getPlayerSelection(player); | |
| const palettes = getPlayerPalettes(player); | |
| const savedPaletteNames = Object.keys(palettes).sort(); | |
| const sourceOptions = []; | |
| const sourceValueMap = {}; | |
| const reverseSourceMap = {}; | |
| if (includeCustom) { | |
| sourceOptions.push("Manual Input"); | |
| sourceValueMap["Manual Input"] = 'manual'; | |
| reverseSourceMap['manual'] = "Manual Input"; | |
| } | |
| if (includePresets) { | |
| sourceOptions.push("Preset"); | |
| sourceValueMap["Preset"] = 'preset'; | |
| reverseSourceMap['preset'] = "Preset"; | |
| } | | | const activePaletteData = selectionData.activePaletteData; | |
| const activeStatus = activePaletteData ? ` (${activePaletteData.length} items${selectionData.activePaletteIsDirty ? ' §c*' : ''})` : ' (Empty)'; | |
| const activeOptionLabel = `Active Palette${activeStatus}`; | |
| sourceOptions.push(activeOptionLabel); | |
| sourceValueMap[activeOptionLabel] = 'active'; | |
| reverseSourceMap['active'] = activeOptionLabel; | |
| savedPaletteNames.forEach(name => { | |
| const label = `Saved: ${name}`; | |
| sourceOptions.push(label); | |
| sourceValueMap[label] = name; | |
| reverseSourceMap[name] = label; | |
| }); | |
| let defaultStateValue = includeCustom ? 'manual' : (includePresets ? 'preset' : 'active'); | |
| let currentSourceIndex = sourceOptions.indexOf(reverseSourceMap[defaultStateValue]); | |
| if (currentSourceIndex < 0) currentSourceIndex = 0; | |
| const currentSourceLabel = reverseSourceMap[currentSourceValue]; | |
| if (currentSourceLabel && sourceOptions.includes(currentSourceLabel)) { | |
| currentSourceIndex = sourceOptions.indexOf(currentSourceLabel); | |
| } else if (currentSourceValue && !['manual', 'active', 'preset', 'custom'].includes(currentSourceValue)) { | |
| console.warn(`Source '${currentSourceValue}' not found in options, defaulting.`); | |
| } | |
| if (currentSourceValue === 'active') { | |
| currentSourceIndex = sourceOptions.findIndex(opt => opt.startsWith('Active Palette')); | |
| if (currentSourceIndex < 0) currentSourceIndex = 0; | |
| } | | | return { sourceOptions, sourceValueMap, currentSourceIndex }; | | | } | | | export async function showRegularBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentShape = selectionData.brushShape ?? 'sphere'; | | | const currentSize = selectionData.brushSize; | | | const currentHollow = selectionData.brushHollow; | | | const currentSourceValue = selectionData.brushMaterialSource ?? 'manual'; | | | const currentManualMaterial = selectionData.brushManualMaterial; | |
| const { shapeOptions, shapeMap, reverseShapeMap } = getBrushShapeOptions(); | |
| const currentShapeIndex = shapeOptions.indexOf(reverseShapeMap[currentShape] ?? shapeOptions[0]); | |
| const { sourceOptions, sourceValueMap, currentSourceIndex } = buildSourceDropdownOptions(player, currentSourceValue); | |
| const form = new ModalFormData().title("Regular Brush Settings") | |
| .dropdown("Shape", shapeOptions, { defaultValueIndex: currentShapeIndex }) | |
| .slider("Size (Radius/Half-Width)", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSize }) | |
| .toggle("Hollow Shape", { defaultValue: currentHollow }) | |
| .dropdown("Material Source", sourceOptions, { defaultValueIndex: currentSourceIndex }) | |
| .textField("Manual Blocks (id:wt,...)", "Used if Source is 'Manual Input'", { defaultValue: currentManualMaterial }) | |
| .submitButton("Update Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eRegular Brush settings unchanged."); | | | return; | | | } | | | let formIndex = 0; | | | const formVals = response.formValues; | |
| const newShapeName = shapeOptions[formVals[formIndex++]]; | |
| const newSize = formVals[formIndex++]; | |
| const newHollow = formVals[formIndex++]; | |
| const selectedOptionLabel = sourceOptions[formVals[formIndex++]]; | |
| const newManualMaterialInput = formVals[formIndex++].trim(); | |
| const newShape = shapeMap[newShapeName] ?? 'sphere'; | |
| const newSourceValue = sourceValueMap[selectedOptionLabel] ?? 'manual'; | |
| let manualInputIsValid = true; | |
| let finalManualMaterialString = currentManualMaterial; | |
| if (newSourceValue === 'manual') { | |
| if (!newManualMaterialInput) { manualInputIsValid = false; player.sendMessage("§cManual Blocks cannot be empty..."); } | |
| else { | |
| const parsedResult = parseWeightedBlockInput(newManualMaterialInput); | |
| if (parsedResult.errors.length > 0 || parsedResult.blocks.length === 0) { manualInputIsValid = false; player.sendMessage("§cError parsing manual blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedResult.blocks.map(b => b.id)); if (invalidIds.length > 0) { manualInputIsValid = false; player.sendMessage(§cInvalid IDs: ${invalidIds.join(',')}); } else { finalManualMaterialString = newManualMaterialInput; } } | |
| } | |
| if (!manualInputIsValid) { player.sendMessage("§eRegular Brush settings NOT updated due to invalid manual input."); return; } | |
| } | |
| if (newSourceValue === 'active' && (!selectionData.activePaletteData || selectionData.activePaletteData.length === 0)) { | |
| player.sendMessage("§cActive Palette is empty. Select 'Manual Input' or load/create a palette first. Settings not updated."); return; | |
| } | |
| if (!['manual', 'active'].includes(newSourceValue)) { | |
| const palettes = getPlayerPalettes(player); | |
| if (!palettes[newSourceValue]) { | |
| player.sendMessage(§cSelected saved palette "${newSourceValue}" no longer exists. Settings not updated.); return; | |
| } | |
| } | |
| selectionData.brushShape = newShape; | |
| selectionData.brushSize = newSize; | |
| selectionData.brushHollow = newHollow; | |
| selectionData.brushMaterialSource = newSourceValue; | |
| if (newSourceValue === 'manual') { | |
| selectionData.brushManualMaterial = finalManualMaterialString; | |
| } | |
| setPlayerSelection(player, selectionData); | |
| const sourceDesc = selectedOptionLabel; | |
| const hollowDesc = newHollow ? '(Hollow)' : ''; | |
| player.sendMessage(`§aRegular Brush updated: ${newShapeName}${hollowDesc}, Size ${newSize}, Src ${sourceDesc}`); | |
| } catch (e) { | |
| console.error(`[Regular Brush Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing regular brush settings form."); | | | } | | | } | | | export async function showPaintBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentShape = selectionData.brushShape ?? 'sphere'; | | | const currentSize = selectionData.brushSize; | | | const currentSourceValue = selectionData.brushMaterialSource ?? 'manual'; | | | const currentManualMaterial = selectionData.brushManualMaterial; | | | const currentMaskEnabled = selectionData.paintBrushMaskEnabled; | | | const currentMaskInput = selectionData.paintBrushMaskInput; | |
| const { shapeOptions, shapeMap, reverseShapeMap } = getBrushShapeOptions(); | |
| const currentShapeIndex = shapeOptions.indexOf(reverseShapeMap[currentShape] ?? shapeOptions[0]); | |
| const { sourceOptions, sourceValueMap, currentSourceIndex } = buildSourceDropdownOptions(player, currentSourceValue); | |
| const form = new ModalFormData().title("Paint Brush Settings") | |
| .dropdown("Shape", shapeOptions, { defaultValueIndex: currentShapeIndex }) | |
| .slider("Size (Radius/Half-Width)", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSize }) | |
| .dropdown("Paint Material Source", sourceOptions, { defaultValueIndex: currentSourceIndex }) | |
| .textField("Manual Paint Blocks (id:wt,...)", "Used if Source is 'Manual Input'", { defaultValue: currentManualMaterial }) | |
| .toggle("Enable Masking", { defaultValue: currentMaskEnabled }) | |
| .textField("Mask Blocks (IDs, comma-sep)", "e.g. stone,dirt,grass", { defaultValue: currentMaskInput }) | |
| .submitButton("Update Paint Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§ePaint Brush settings unchanged."); | | | return; | | | } | | | let formIndex = 0; | | | const formVals = response.formValues; | |
| const newShapeName = shapeOptions[formVals[formIndex++]]; | |
| const newSize = formVals[formIndex++]; | |
| const selectedOptionLabel = sourceOptions[formVals[formIndex++]]; | |
| const newManualMaterialInput = formVals[formIndex++].trim(); | |
| const newMaskEnabled = formVals[formIndex++]; | |
| const newMaskInput = formVals[formIndex++].trim(); | |
| const newShape = shapeMap[newShapeName] ?? 'sphere'; | |
| const newSourceValue = sourceValueMap[selectedOptionLabel] ?? 'manual'; | |
| let manualInputIsValid = true; | |
| let finalManualMaterialString = currentManualMaterial; | |
| if (newSourceValue === 'manual') { | |
| if (!newManualMaterialInput) { manualInputIsValid = false; player.sendMessage("§cManual Paint Blocks cannot be empty..."); } | |
| else { | |
| const parsedResult = parseWeightedBlockInput(newManualMaterialInput); | |
| if (parsedResult.errors.length > 0 || parsedResult.blocks.length === 0) { manualInputIsValid = false; player.sendMessage("§cError parsing manual blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedResult.blocks.map(b => b.id)); if (invalidIds.length > 0) { manualInputIsValid = false; player.sendMessage(§cInvalid IDs: ${invalidIds.join(',')}); } else { finalManualMaterialString = newManualMaterialInput; } } | |
| } | |
| if (!manualInputIsValid) { player.sendMessage("§ePaint Brush settings NOT updated due to invalid manual input."); return; } | |
| } | |
| if (newSourceValue === 'active' && (!selectionData.activePaletteData || selectionData.activePaletteData.length === 0)) { | |
| player.sendMessage("§cActive Palette is empty. Select 'Manual Input' or load/create a palette first. Settings not updated."); return; | |
| } | |
| if (!['manual', 'active'].includes(newSourceValue)) { | |
| const palettes = getPlayerPalettes(player); | |
| if (!palettes[newSourceValue]) { | |
| player.sendMessage(§cSelected saved palette "${newSourceValue}" no longer exists. Settings not updated.); return; | |
| } | |
| } | |
| let maskInputIsValid = true; | |
| let finalMaskInputString = currentMaskInput; | |
| if (newMaskEnabled) { | |
| if (!newMaskInput) { maskInputIsValid = false; player.sendMessage("§cMask Blocks cannot be empty..."); } | |
| else { | |
| const parsedMask = parseWeightedBlockInput(newMaskInput); | |
| if (parsedMask.blocks.length === 0) { maskInputIsValid = false; player.sendMessage("§cNo valid blocks found in Mask Blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedMask.blocks.map(b => b.id)); if (invalidIds.length > 0) { maskInputIsValid = false; player.sendMessage(§cInvalid Mask IDs: ${invalidIds.join(', ')}...); } else { finalMaskInputString = newMaskInput; } } | |
| } | |
| if (!maskInputIsValid) { player.sendMessage("§ePaint Brush settings NOT updated due to invalid mask input."); return; } | |
| } | |
| selectionData.brushShape = newShape; | |
| selectionData.brushSize = newSize; | |
| selectionData.brushMaterialSource = newSourceValue; | |
| if (newSourceValue === 'manual') { | |
| selectionData.brushManualMaterial = finalManualMaterialString; | |
| } | |
| selectionData.paintBrushMaskEnabled = newMaskEnabled; | |
| if (newMaskEnabled) { | |
| selectionData.paintBrushMaskInput = finalMaskInputString; | |
| } | |
| setPlayerSelection(player, selectionData); | |
| const paintSourceDesc = selectedOptionLabel; | |
| const maskDesc = newMaskEnabled ? `ON` : 'OFF'; | |
| player.sendMessage(`§aPaint Brush updated: ${newShapeName}, Size ${newSize}, Paint ${paintSourceDesc}, Mask ${maskDesc}`); | |
| } catch (e) { | |
| console.error(`[Paint Brush Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing paint brush settings form."); | | | } | | | } | | | export async function showHeightBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentShape = selectionData.brushShape ?? 'sphere'; | | | const currentSize = selectionData.brushSize; | | | const currentAmount = selectionData.heightBrushAmount ?? DEFAULT_HEIGHT_BRUSH_AMOUNT; | | | const currentMode = selectionData.heightBrushMode ?? "Raise"; | |
| const { shapeOptions, shapeMap, reverseShapeMap } = getBrushShapeOptions(); | |
| const currentShapeIndex = shapeOptions.indexOf(reverseShapeMap[currentShape] ?? shapeOptions[0]); | |
| const modeOptions = ["Raise", "Lower"]; | |
| const currentModeIndex = modeOptions.indexOf(currentMode); | |
| const form = new ModalFormData().title("Height Brush Settings") | |
| .dropdown("Shape", shapeOptions, { defaultValueIndex: currentShapeIndex }) | |
| .slider("Size (Radius/Half-Width)", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSize }) | |
| .slider("Amount", 1, MAX_HEIGHT_BRUSH_AMOUNT, { step: 1, defaultValue: currentAmount }) | |
| .dropdown("Mode", modeOptions, { defaultValueIndex: Math.max(0, currentModeIndex) }) | |
| .submitButton("Update Height Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { player.sendMessage("§eHeight Brush settings unchanged."); return; } | |
| let formIndex = 0; | |
| const formVals = response.formValues; | |
| const newShapeName = shapeOptions[formVals[formIndex++]]; | |
| const newSize = formVals[formIndex++]; | |
| const newAmount = formVals[formIndex++]; | |
| const newModeName = modeOptions[formVals[formIndex++]]; | |
| const newShape = shapeMap[newShapeName] ?? 'sphere'; | |
| selectionData.brushShape = newShape; | | | selectionData.brushSize = newSize; | | | selectionData.heightBrushAmount = newAmount; | | | selectionData.heightBrushMode = newModeName; | |
| setPlayerSelection(player, selectionData); | |
| player.sendMessage(`§aHeight Brush updated: ${newShapeName}, Size ${newSize}, Amt ${newAmount}, Mode ${newModeName}`); | |
| } catch (e) { console.error(`[Height Brush Settings Form] Error: ${e}\n${e.stack}`); player.sendMessage("§cError processing height brush settings form."); } | |
| } | | | export async function showFoliageBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentRadius = selectionData.foliageBrushRadius ?? DEFAULT_FOLIAGE_RADIUS; | | | const currentDensity = selectionData.foliageBrushDensity ?? DEFAULT_FOLIAGE_DENSITY; | | | const currentSourceValue = selectionData.foliageBrushPlantSource ?? DEFAULT_FOLIAGE_SOURCE; | | | const currentPreset = selectionData.foliageBrushPresetType ?? DEFAULT_FOLIAGE_PRESET; | | | const currentCustom = selectionData.foliageBrushCustomInput ?? DEFAULT_FOLIAGE_CUSTOM; | | | const { sourceOptions, sourceValueMap, currentSourceIndex } = buildSourceDropdownOptions(player, currentSourceValue, true, true); | |
| const presetOptions = Object.keys(FOLIAGE_PRESETS); | |
| let currentPresetIndex = presetOptions.indexOf(currentPreset); | |
| if (currentPresetIndex === -1 || presetOptions.length === 0) { currentPresetIndex = 0; } | |
| const form = new ModalFormData().title("Foliage Brush Settings") | |
| .slider("Scatter Radius", 1, MAX_FOLIAGE_RADIUS, { step: 1, defaultValue: currentRadius }) | |
| .slider("Density (%)", 0, 100, { step: 1, defaultValue: currentDensity }) | |
| .dropdown("Plant Source", sourceOptions, { defaultValueIndex: currentSourceIndex }) | |
| .dropdown("Preset Type", presetOptions.length > 0 ? presetOptions : ["No Presets Defined"], { defaultValueIndex: currentPresetIndex }) | |
| .textField("Custom Plants (id:wt,...)", "Used if Source is 'Custom Input'", { defaultValue: currentCustom }) | |
| .submitButton("Update Foliage Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eFoliage Brush settings unchanged."); | | | return; | | | } | | | const formVals = response.formValues; | |
| const newRadius = formVals[0]; | |
| const newDensity = formVals[1]; | |
| const selectedOptionLabel = sourceOptions[formVals[2]]; | |
| const newPresetName = presetOptions.length > 0 ? presetOptions[formVals[3]] : DEFAULT_FOLIAGE_PRESET; | |
| const newCustomInput = formVals[4].trim(); | |
| const newSourceValue = sourceValueMap[selectedOptionLabel] ?? DEFAULT_FOLIAGE_SOURCE; | | | let customInputIsValid = true; | | | let finalCustomInputString = currentCustom; | |
| if (newSourceValue === 'manual') { | |
| if (!newCustomInput) { customInputIsValid = false; player.sendMessage("§cCustom Plants cannot be empty..."); } | |
| else { | |
| const parsedResult = parseWeightedBlockInput(newCustomInput); | |
| if (parsedResult.errors.length > 0 || parsedResult.blocks.length === 0) { customInputIsValid = false; player.sendMessage("§cError parsing custom blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedResult.blocks.map(b => b.id)); if (invalidIds.length > 0) { customInputIsValid = false; player.sendMessage(§cInvalid IDs: ${invalidIds.join(',')}); } else { finalCustomInputString = newCustomInput; } } | |
| } | |
| if (!customInputIsValid) { player.sendMessage("§cFoliage Brush settings NOT updated due to invalid manual input."); return; } | |
| } | |
| if (newSourceValue === 'active' && (!selectionData.activePaletteData || selectionData.activePaletteData.length === 0)) { | |
| player.sendMessage("§cActive Palette is empty. Select another source or load/create a palette first. Settings not updated."); return; | |
| } | |
| if (!['preset', 'manual', 'active'].includes(newSourceValue)) { | |
| const palettes = getPlayerPalettes(player); | |
| if (!palettes[newSourceValue]) { | |
| player.sendMessage(§cSelected saved palette "${newSourceValue}" no longer exists. Settings not updated.); return; | |
| } | |
| } | |
| if (newSourceValue === 'preset' && presetOptions.length === 0) { | |
| player.sendMessage("§cCannot select 'Preset' source as no presets are defined. Settings not updated."); return; | |
| } | |
| selectionData.foliageBrushRadius = newRadius; | |
| selectionData.foliageBrushDensity = newDensity; | |
| selectionData.foliageBrushPlantSource = newSourceValue; | |
| selectionData.foliageBrushPresetType = newPresetName; | |
| if (newSourceValue === 'manual') { | |
| selectionData.foliageBrushCustomInput = finalCustomInputString; | |
| } | |
| setPlayerSelection(player, selectionData); | |
| const sourceDesc = selectedOptionLabel; | |
| player.sendMessage(`§aFoliage Brush updated: Radius=${newRadius}, Density=${newDensity}%, Source=${sourceDesc}`); | |
| } catch (e) { | |
| console.error(`[Foliage Brush Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing foliage brush settings form."); | | | } | | | }import { Player, system, GameMode, EquipmentSlot } from "@minecraft/server"; | | | import { ModalFormData, ActionFormData } from "@minecraft/server-ui"; | | | import { | | | getGlobalSettings, | | | saveGlobalSettings, | | | } from "../state/global_state.js"; | | | import { | | | getPlayerSelection, | | | setPlayerSelection | | | } from "../state/player_state.js"; | | | import { | | | MAX_UNDO_HISTORY, | | | MAX_BRUSH_SIZE, | | | DEFAULT_PLAYER_SPEED_MULTIPLIER, | | | MIN_PLAYER_SPEED_MULTIPLIER, | | | MAX_PLAYER_SPEED_MULTIPLIER, | | | DEFAULT_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | DEFAULT_AUTO_SPECTATOR_ENABLED, | | | DEFAULT_EXTENDED_REACH_ENABLED, | | | DEFAULT_SHOW_ACTION_FEEDBACK_MESSAGES | | | } from "../constants/settings.js"; | | | export async function showMainSettingsHubForm(player) { | |
| const form = new ActionFormData() | |
| .title("Addon Settings") | |
| .body("Choose the type of settings you want to configure.") | | | .button("World Settings", "textures/ui/world_glyph_color_2x") | | | .button("Player Settings", "textures/ui/icon_steve") | | | .button("Cancel", "textures/ui/cancel"); | | | try { | |
| const response = await form.show(player); | |
| if (response.canceled || response.selection === undefined) { | |
| return; | | | } | | | switch (response.selection) { | | | case 0: | | | await showWorldSettingsForm(player); | | | break; | | | case 1: | | | await showPlayerSettingsForm(player); | | | break; | | | case 2: | | | return; | | | } | |
| } catch (e) { | |
| console.error(`[Main Settings Hub Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError opening settings hub."); | |
| } | | | } | |
| async function showWorldSettingsForm(player) { | |
| const currentSettings = getGlobalSettings(); | |
| const form = new ModalFormData() | |
| .title("World Settings (Global)") | |
| .toggle("Show Action Bar Info", { defaultValue: currentSettings.showActionBarInfo }) | |
| .textField("Outline Particle ID", "e.g., minecraft:basic_flame_particle", { defaultValue: currentSettings.outlineParticleId }) | |
| .slider("Max Undo Steps (per player)", 0, 100, { step: 1, defaultValue: currentSettings.maxUndoHistory }) | |
| .slider("Default Brush Size", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSettings.defaultBrushSize }) | |
| .submitButton("Save World Settings"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eWorld settings unchanged."); | |
| system.run(() => showMainSettingsHubForm(player)); | |
| return; | | | } | | | const formVals = response.formValues; | |
| const newSettings = { ...currentSettings }; | |
| let validationErrors = []; | |
| newSettings.showActionBarInfo = formVals[0]; | |
| const newParticleId = formVals[1].trim(); | |
| if (!newParticleId) { | |
| validationErrors.push("Outline Particle ID cannot be empty."); | |
| } else { | |
| newSettings.outlineParticleId = newParticleId.includes(':') ? newParticleId : `minecraft:${newParticleId}`; | |
| } | |
| const newMaxUndo = formVals[2]; | |
| if (typeof newMaxUndo === 'number' && Number.isInteger(newMaxUndo)) { | |
| newSettings.maxUndoHistory = Math.max(0, Math.min(1000, newMaxUndo)); | |
| } else { | |
| validationErrors.push("Invalid value for Max Undo Steps."); | | | } | |
| const newDefaultBrushSize = formVals[3]; | |
| if (typeof newDefaultBrushSize === 'number' && Number.isInteger(newDefaultBrushSize)) { | |
| newSettings.defaultBrushSize = Math.max(1, Math.min(MAX_BRUSH_SIZE, newDefaultBrushSize)); | |
| } else { | |
| validationErrors.push("Invalid value for Default Brush Size."); | | | } | |
| if (validationErrors.length > 0) { | |
| player.sendMessage("§cInvalid input:\n" + validationErrors.join("\n") + "\n§eWorld settings not saved."); | |
| } else { | |
| if (saveGlobalSettings(newSettings)) { | |
| player.sendMessage("§aGlobal World settings saved successfully."); | | | } else { | | | player.sendMessage("§cFailed to save global World settings. Check server logs."); | | | } | | | } | |
| system.run(() => showMainSettingsHubForm(player)); | |
| } catch (e) { | |
| console.error(`[World Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError displaying or processing World settings form."); | | | system.run(() => showMainSettingsHubForm(player)); | | | } | | | } | |
| async function showPlayerSettingsForm(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const currentPlayerSpeedMultiplier = selectionData.playerSpeedMultiplier ?? DEFAULT_PLAYER_SPEED_MULTIPLIER; | | | const currentPlayerVerticalSpeedMultiplier = selectionData.playerVerticalSpeedMultiplier ?? DEFAULT_PLAYER_VERTICAL_SPEED_MULTIPLIER; | | | const currentAutoSpectatorEnabled = selectionData.autoSpectatorEnabled ?? DEFAULT_AUTO_SPECTATOR_ENABLED; | | | const currentExtendedReachEnabled = selectionData.extendedReachEnabled ?? DEFAULT_EXTENDED_REACH_ENABLED; | | | const currentShowActionFeedbackMessages = selectionData.showActionFeedbackMessages ?? DEFAULT_SHOW_ACTION_FEEDBACK_MESSAGES; | |
| const form = new ModalFormData() | |
| .title("Player Settings") | |
| .slider( | | | "Creative Horizontal Speed Multiplier", | | | MIN_PLAYER_SPEED_MULTIPLIER, | | | MAX_PLAYER_SPEED_MULTIPLIER, | | | { step: 0.1, defaultValue: currentPlayerSpeedMultiplier } | | | ) | | | .slider( | | | "Creative Vertical Speed Multiplier (Flying)", | | | MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | { step: 0.1, defaultValue: currentPlayerVerticalSpeedMultiplier } | | | ) | | | .toggle( | | | "Auto Spectator Near Walls (Creative Flying)", | | | { defaultValue: currentAutoSpectatorEnabled } | | | ) | | | .toggle( | | | "Enable Extended Reach Placement", | | | { defaultValue: currentExtendedReachEnabled } | | | ) | | | .toggle( | | | "Show Action Feedback Messages", | | | { defaultValue: currentShowActionFeedbackMessages } | | | ) | | | .submitButton("Save Player Settings"); | | | try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§ePlayer settings unchanged."); | |
| system.run(() => showMainSettingsHubForm(player)); | |
| return; | | | } | | | const formVals = response.formValues; | |
| const newSpeedMultiplier = formVals[0]; | |
| const newVerticalSpeedMultiplier = formVals[1]; | |
| const newAutoSpectatorEnabled = formVals[2]; | |
| const newExtendedReachEnabled = formVals[3]; | |
| const newShowActionFeedbackMessages = formVals[4]; | |
| let settingsChanged = false; | |
| let messages = []; | |
| if (typeof newSpeedMultiplier === 'number' && | |
| newSpeedMultiplier >= MIN_PLAYER_SPEED_MULTIPLIER && | |
| newSpeedMultiplier <= MAX_PLAYER_SPEED_MULTIPLIER) { | |
| if (selectionData.playerSpeedMultiplier !== parseFloat(newSpeedMultiplier.toFixed(2))) { | |
| selectionData.playerSpeedMultiplier = parseFloat(newSpeedMultiplier.toFixed(2)); | |
| settingsChanged = true; | |
| } | |
| } else { | |
| messages.push(§cInvalid horizontal speed multiplier. Must be between ${MIN_PLAYER_SPEED_MULTIPLIER} and ${MAX_PLAYER_SPEED_MULTIPLIER}. Horizontal speed not changed.); | |
| } | |
| if (typeof newVerticalSpeedMultiplier === 'number' && | |
| newVerticalSpeedMultiplier >= MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER && | |
| newVerticalSpeedMultiplier <= MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER) { | |
| if (selectionData.playerVerticalSpeedMultiplier !== parseFloat(newVerticalSpeedMultiplier.toFixed(2))) { | |
| selectionData.playerVerticalSpeedMultiplier = parseFloat(newVerticalSpeedMultiplier.toFixed(2)); | |
| settingsChanged = true; | |
| } | |
| } else { | |
| messages.push(§cInvalid vertical speed multiplier. Must be between ${MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER} and ${MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER}. Vertical speed not changed.); | |
| } | |
| if (typeof newAutoSpectatorEnabled === 'boolean') { | |
| if (selectionData.autoSpectatorEnabled !== newAutoSpectatorEnabled) { | |
| selectionData.autoSpectatorEnabled = newAutoSpectatorEnabled; | | | settingsChanged = true; | | | } | | | } else { | | | messages.push("§cInvalid value for Auto Spectator toggle. Setting not changed."); | | | } | |
| if (typeof newExtendedReachEnabled === 'boolean') { | |
| if (selectionData.extendedReachEnabled !== newExtendedReachEnabled) { | |
| selectionData.extendedReachEnabled = newExtendedReachEnabled; | | | settingsChanged = true; | | | } | | | } else { | | | messages.push("§cInvalid value for Extended Reach toggle. Setting not changed."); | | | } | |
| if (typeof newShowActionFeedbackMessages === 'boolean') { | |
| if (selectionData.showActionFeedbackMessages !== newShowActionFeedbackMessages) { | |
| selectionData.showActionFeedbackMessages = newShowActionFeedbackMessages; | | | settingsChanged = true; | | | } | | | } else { | | | messages.push("§cInvalid value for Show Action Feedback Messages toggle. Setting not changed."); | | | } | |
| if (settingsChanged) { | |
| setPlayerSelection(player, selectionData); | |
| messages.unshift(§aPlayer settings updated: HSpeed=${selectionData.playerSpeedMultiplier}x, VSpeed=${selectionData.playerVerticalSpeedMultiplier}x, AutoSpec=${selectionData.autoSpectatorEnabled ? 'ON' : 'OFF'}, ExtReach=${selectionData.extendedReachEnabled ? 'ON' : 'OFF'}, FeedbackMsgs=${selectionData.showActionFeedbackMessages ? 'ON' : 'OFF'}); | |
| } else if (!response.canceled && messages.length === 0) { | |
| messages.push("§eNo changes made to player settings."); | |
| } | |
| if (messages.length > 0) { | |
| player.sendMessage(messages.join('\n')); | |
| } | |
| system.run(() => showMainSettingsHubForm(player)); | |
| } catch (e) { | |
| console.error(`[Player Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError displaying or processing Player settings form."); | | | system.run(() => showMainSettingsHubForm(player)); | | | } | |
| }import { Player, system } from "@minecraft/server"; | |
| import { ActionFormData, ModalFormData } from "@minecraft/server-ui"; | |
| import { | | | getPlayerSelection, | | | getPlayerPalettes, | | | savePlayerPalettes, | | | addPlayerPalette, | | | deletePlayerPalette, | | | setActiveSavedPaletteName, | | | getActiveSavedPaletteName, | | | setPlayerSelection, | | | discardActivePaletteChanges | | | } from "../state/index.js"; | | | import { parseWeightedBlockInput, validateBlockIds } from "../utils/index.js"; | | | import { showEditMenu } from "./main_menu.js"; | | | import { analyzeSelectionForPalette } from "../core/analyze_selection.js"; | |
| export async function showPaletteMenu(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const activeSaved = getActiveSavedPaletteName(player); | |
| const activePaletteData = selectionData.activePaletteData; | | | const isDirty = !!selectionData.activePaletteIsDirty; | | | const activeItemsCount = activePaletteData?.length ?? 0; | |
| const activeStatus = activePaletteData ? ` (${activeItemsCount} items${isDirty ? ' §c*' : ''})` : ' (Empty/Inactive)'; | |
| const hasCompleteSelection = selectionData.selectionState === 2; | |
| const form = new ActionFormData() | |
| .title("Manage Palettes") | |
| .body(`§eActive Palette:§r ${activeStatus}\n§eLoaded Saved Palette:§r ${activeSaved ? `§b${activeSaved}` : "§7None"}`) | |
| .button("Load Saved Palette", "textures/ui/book_glyph_color") | |
| .button("Edit Saved Palette", "textures/ui/book_edit_default") | |
| .button("Save Options...", "textures/ui/icon_save") | |
| .button(Create from Selection... ${hasCompleteSelection ? '' : '§7(Needs Sel)'}, "textures/ui/icon_import") | |
| .button("Duplicate Loaded Saved Palette", "textures/ui/copy") | |
| .button("Delete Saved Palette", "textures/ui/trash") | |
| .button("Back", "textures/ui/cancel"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || response.selection === undefined) return; | |
| let refreshMenu = true; | |
| switch (response.selection) { | |
| case 0: await showLoadPaletteList(player); break; | |
| case 1: await showEditPaletteList(player); break; | |
| case 2: await showSaveOptionsMenu(player); refreshMenu = false; break; | |
| case 3: | |
| if (hasCompleteSelection) { | |
| const uniqueBlockIds = await analyzeSelectionForPalette(player); | |
| if (uniqueBlockIds) { | |
| await showCreatePaletteFromSelectionForm(player, uniqueBlockIds); | |
| refreshMenu = false; | |
| } else { | |
| refreshMenu = true; | |
| } | | | } else { | | | player.sendMessage("§cComplete selection needed to create palette."); | | | refreshMenu = true; | | | } | | | break; | |
| case 4: await duplicateActiveSavedPalette(player); break; | |
| case 5: await showDeletePaletteMenu(player); break; | |
| case 6: | |
| refreshMenu = false; | |
| await system.run(async () => { await showEditMenu(player); }); | |
| return; | | | } | |
| if (refreshMenu) { | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Palette Menu] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError displaying palette menu."); | | | } | | | } | |
| async function showLoadPaletteList(player) { | |
| const palettes = getPlayerPalettes(player); | |
| const paletteNames = Object.keys(palettes); | |
| const currentSelection = getPlayerSelection(player); | |
| const isDirty = !!currentSelection.activePaletteIsDirty; | |
| const form = new ActionFormData().title("Load Saved Palette"); | |
| let body = ""; | |
| if (isDirty) { | |
| body += "§cWarning: will discard unsaved Active Palette changes!§r\n"; | | | } | | | body += "Select saved palette to load into Active Palette:"; | |
| form.body(body); | |
| if (paletteNames.length === 0) { | |
| form.body(body + "\n§eNo saved palettes available.§r"); | |
| } else { | |
| paletteNames.forEach(name => form.button(name)); | |
| } | | | form.button("§c[Cancel]"); | | | try { | | | const response = await form.show(player); | | | const cancelIndex = paletteNames.length; | | | if (response.canceled || response.selection === undefined || response.selection === cancelIndex) { | | | return; | | | } | |
| if (response.selection < cancelIndex) { | |
| const selectedName = paletteNames[response.selection]; | |
| if (isDirty) { | |
| const confirmForm = new ModalFormData() | |
| .title("Discard Unsaved Changes?") | |
| .toggle(` "${selectedName}" will discard unsaved changes in the Active Palette. Continue?`, { defaultValue: false }) | |
| .submitButton("Confirm Load"); | |
| const confirmResponse = await confirmForm.show(player); | |
| if (confirmResponse.canceled || !confirmResponse.formValues || !confirmResponse.formValues[0]) { | | | player.sendMessage("§eLoad cancelled."); | | | return; | | | } | | | } | |
| const paletteDataToLoad = palettes[selectedName]; | |
| if (!paletteDataToLoad) { | |
| player.sendMessage(`§cError: Palette "${selectedName}" data not found.`); | |
| return; | |
| } | |
| currentSelection.activePaletteData = JSON.parse(JSON.stringify(paletteDataToLoad)); | |
| currentSelection.activeSavedPaletteName = selectedName; | |
| currentSelection.activePaletteIsDirty = false; | |
| setPlayerSelection(player, currentSelection); | |
| player.sendMessage(§aLoaded "${selectedName}" into Active Palette and set as loaded saved palette.); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Load Palette List] Error: ${e}\n${e.stack}`); | |
| player.sendMessage(§cError displaying load palette list.); | |
| } | |
| } | |
| async function showEditPaletteList(player) { | |
| const palettes = getPlayerPalettes(player); | |
| const paletteNames = Object.keys(palettes); | |
| const form = new ActionFormData().title("Edit Saved Palette"); | |
| form.body("Select saved palette to edit directly:"); | |
| if (paletteNames.length === 0) { | |
| form.body("§eNo saved palettes available to edit.§r"); | |
| } else { | |
| paletteNames.forEach(name => form.button(name)); | |
| } | | | form.button("§c[Cancel]"); | | | try { | | | const response = await form.show(player); | | | const cancelIndex = paletteNames.length; | | | if (response.canceled || response.selection === undefined || response.selection === cancelIndex) { | | | return; | | | } | |
| if (response.selection < cancelIndex) { | |
| const paletteNameToEdit = paletteNames[response.selection]; | |
| system.run(async () => { | |
| await editTextPalette(player, paletteNameToEdit); | |
| }); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Edit Palette List] Error: ${e}\n${e.stack}`); | |
| player.sendMessage(§cError selecting palette to edit.); | |
| } | |
| } | |
| async function editTextPalette(player, paletteName) { | |
| const palettes = getPlayerPalettes(player); | |
| const currentPaletteData = palettes[paletteName]; | |
| if (!currentPaletteData) { | |
| player.sendMessage(`§cPalette "${paletteName}" not found (maybe deleted?).`); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const currentBlocksString = currentPaletteData.map(b => `${b.id.replace('minecraft:', '')}:${b.weight}`).join(', '); | |
| const form = new ModalFormData() | |
| .title(`Edit Saved Palette: ${paletteName}`) | |
| .textField("Palette Name", "Edit name (must be unique)", { defaultValue: paletteName }) | |
| .textField("Blocks (format: id:weight, ...)", "Edit blocks and weights", { defaultValue: currentBlocksString }) | |
| .submitButton("Save Changes"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eEdit cancelled."); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const newPaletteName = response.formValues[0]?.trim(); | |
| const newBlockInput = response.formValues[1]?.trim(); | |
| if (!newPaletteName) { player.sendMessage("§cPalette name cannot be empty. Not saved."); } | |
| else if (!newBlockInput) { player.sendMessage("§cBlock input cannot be empty. Not saved."); } | |
| else { | |
| const { blocks: newBlocksWithWeights, errors } = parseWeightedBlockInput(newBlockInput); | |
| if (errors.length > 0) { player.sendMessage(`§cInput errors:\n${errors.join('\n')}`); } | |
| else if (newBlocksWithWeights.length === 0) { player.sendMessage("§cNo valid blocks parsed. Palette not saved."); } | |
| else { | |
| const { invalidIds } = validateBlockIds(newBlocksWithWeights.map(b => b.id)); | |
| if (invalidIds.length > 0) { player.sendMessage(`§cInvalid block ID(s) resolved: ${invalidIds.join(', ')}. Palette not saved.`); } | |
| else { | |
| const updatedPalettes = getPlayerPalettes(player); | |
| if (newPaletteName !== paletteName && updatedPalettes[newPaletteName]) { | |
| player.sendMessage(§cAnother palette named "${newPaletteName}" already exists. Cannot rename/save.); | |
| } else { | |
| if (newPaletteName !== paletteName) { | |
| delete updatedPalettes[paletteName]; | |
| } | |
| updatedPalettes[newPaletteName] = newBlocksWithWeights; | |
| if (savePlayerPalettes(player, updatedPalettes)) { | |
| player.sendMessage(`§aSaved palette "${newPaletteName}" updated successfully.`); | |
| const selectionData = getPlayerSelection(player); | |
| if (selectionData.activeSavedPaletteName === paletteName && newPaletteName !== paletteName) { | |
| setActiveSavedPaletteName(player, newPaletteName); | |
| player.sendMessage(`§eLoaded saved palette updated to "${newPaletteName}".`); | |
| } | | | } | | | } | | | } | | | } | | | } | |
| } catch (e) { | |
| console.error(`[WorldEdit Edit Palette Text] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError editing palette."); | |
| } | | | await system.run(async () => { await showPaletteMenu(player); }); | | | } | |
| async function showSaveOptionsMenu(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const activeSavedName = selectionData.activeSavedPaletteName; | | | const activePaletteData = selectionData.activePaletteData; | | | const isDirty = !!selectionData.activePaletteIsDirty; | | | const hasActiveData = !!activePaletteData && activePaletteData.length > 0; | |
| const form = new ActionFormData().title("Save Options"); | |
| let body = `§eActive Palette Status:§r ${hasActiveData ? `${activePaletteData.length} items` : 'Empty'}${isDirty ? ' §c(Unsaved Changes)' : ' (No Changes)'}\n`; | |
| body += `§eLoaded Saved Palette:§r ${activeSavedName ? `§b${activeSavedName}` : "§7None"}`; | |
| form.body(body); | |
| let buttonIndexMap = {}; | |
| let currentButtonIndex = 0; | |
| if (hasActiveData) { | |
| form.button("Save Active Palette as New..."); | |
| buttonIndexMap[currentButtonIndex++] = "save_as"; | |
| } else { | |
| form.button("Save Active Palette as New... §7(Empty)"); | |
| buttonIndexMap[currentButtonIndex++] = "save_as_disabled"; | |
| } | |
| if (activeSavedName && isDirty && hasActiveData) { | |
| form.button(`Update Saved '${activeSavedName}'`); | |
| buttonIndexMap[currentButtonIndex++] = "update_active"; | |
| } else { | |
| form.button(`Update Loaded Saved Palette §7(N/A)`); | |
| buttonIndexMap[currentButtonIndex++] = "update_disabled"; | |
| } | |
| if (hasActiveData || isDirty) { | |
| form.button("Discard Active Palette Changes"); | |
| buttonIndexMap[currentButtonIndex++] = "discard"; | |
| } else { | |
| form.button("Discard Active Palette Changes §7(N/A)"); | |
| buttonIndexMap[currentButtonIndex++] = "discard_disabled"; | |
| } | |
| form.button("Back"); | |
| buttonIndexMap[currentButtonIndex++] = "back"; | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || response.selection === undefined) { | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const action = buttonIndexMap[response.selection]; | |
| let returnToMainMenu = true; | |
| switch (action) { | |
| case "save_as": await saveActivePaletteAs(player); break; | |
| case "save_as_disabled": | | | player.sendMessage("§cActive Palette is empty, cannot 'Save As'."); | |
| returnToMainMenu = false; | |
| await system.run(async () => { await showSaveOptionsMenu(player); }); | |
| break; | | | case "update_active": await confirmOverwriteActiveSavedPalette(player); break; | | | case "update_disabled": | | | player.sendMessage("§cCannot update: No loaded saved palette, or no unsaved changes, or Active Palette is empty."); | |
| returnToMainMenu = false; | |
| await system.run(async () => { await showSaveOptionsMenu(player); }); | |
| break; | | | case "discard": await discardActivePaletteChanges(player); break; | | | case "discard_disabled": | | | player.sendMessage("§eNo Active Palette changes or data to discard."); | |
| returnToMainMenu = false; | |
| await system.run(async () => { await showSaveOptionsMenu(player); }); | |
| break; | |
| case "back": break; | |
| default: console.warn(`Invalid action selected in Save Options Menu: ${response.selection}`); break; | |
| } | |
| if (returnToMainMenu) { | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Save Options Menu] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError in save options menu."); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } | | | } | |
| async function saveActivePaletteAs(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const activePaletteData = selectionData.activePaletteData; | | | if (!activePaletteData || activePaletteData.length === 0) { | | | player.sendMessage("§cActive Palette is empty. Add blocks using Picker first."); | | | return; | | | } | | | const form = new ModalFormData() | | | .title("Save Active Palette As...") | |
| .textField("New Palette Name", "Enter a unique name", { defaultValue: "" }) | |
| .submitButton("Save Palette"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eSave cancelled."); | |
| return; | | | } | |
| const newPaletteName = response.formValues[0]?.trim(); | |
| if (!newPaletteName) { | |
| player.sendMessage("§cPalette name cannot be empty."); | | | return; | | | } | |
| const existingPalettes = getPlayerPalettes(player); | |
| if (existingPalettes[newPaletteName]) { | |
| player.sendMessage(`§cPalette named "${newPaletteName}" already exists.`); | |
| return; | | | } | |
| if (addPlayerPalette(player, newPaletteName, activePaletteData)) { | |
| player.sendMessage(`§aActive Palette saved as "${newPaletteName}".`); | |
| selectionData.activeSavedPaletteName = newPaletteName; | | | selectionData.activePaletteIsDirty = false; | | | setPlayerSelection(player, selectionData); | | | } | |
| } catch (e) { | |
| console.error(`[WorldEdit Save Active As] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError saving palette."); | |
| } | | | } | | | async function confirmOverwriteActiveSavedPalette(player) { | | | const selectionData = getPlayerSelection(player); | | | const activePaletteData = selectionData.activePaletteData; | | | const activeSavedName = selectionData.activeSavedPaletteName; | | | const isDirty = !!selectionData.activePaletteIsDirty; | |
| if (!activeSavedName) { player.sendMessage("§cNo loaded saved palette set to overwrite."); return; } | |
| if (!activePaletteData || activePaletteData.length === 0) { player.sendMessage("§cActive Palette is empty. Cannot overwrite."); return; } | |
| if (!isDirty) { player.sendMessage("§eNo unsaved changes in Active Palette to overwrite."); return; } | |
| const form = new ModalFormData() | |
| .title("Overwrite Saved Palette?") | |
| .toggle(Overwrite saved palette "${activeSavedName}" with current Active Palette changes? This cannot be undone., { defaultValue: false }) | |
| .submitButton("Overwrite Palette"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues || !response.formValues[0]) { | |
| player.sendMessage("§eOverwrite cancelled."); | |
| return; | |
| } | |
| if (addPlayerPalette(player, activeSavedName, activePaletteData)) { | |
| player.sendMessage(§aSaved palette "${activeSavedName}" updated with Active Palette changes.); | |
| selectionData.activePaletteIsDirty = false; | |
| setPlayerSelection(player, selectionData); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Overwrite Active Saved] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError overwriting palette."); | |
| } | | | } | |
| async function duplicateActiveSavedPalette(player) { | |
| const activeSavedName = getActiveSavedPaletteName(player); | |
| if (!activeSavedName) { | |
| player.sendMessage("§cNo loaded saved palette selected to duplicate."); | | | return; | | | } | |
| const palettes = getPlayerPalettes(player); | |
| const paletteToCopy = palettes[activeSavedName]; | |
| if (!paletteToCopy) { | |
| player.sendMessage(`§cCould not find data for loaded saved palette "${activeSavedName}".`); | |
| return; | | | } | |
| const form = new ModalFormData() | |
| .title(`Duplicate Saved Palette: ${activeSavedName}`) | |
| .textField("New Palette Name", `Enter a unique name for the copy`, { defaultValue: `${activeSavedName} Copy` }) | |
| .submitButton("Duplicate"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eDuplicate cancelled."); | |
| return; | | | } | |
| const newPaletteName = response.formValues[0]?.trim(); | |
| if (!newPaletteName) { | |
| player.sendMessage("§cNew palette name cannot be empty."); | | | return; | | | } | |
| if (palettes[newPaletteName]) { | |
| player.sendMessage(`§cPalette named "${newPaletteName}" already exists.`); | |
| return; | |
| } | |
| if (newPaletteName === activeSavedName) { | |
| player.sendMessage(§cNew name must be different from the original.); | |
| return; | |
| } | |
| const copiedData = JSON.parse(JSON.stringify(paletteToCopy)); | |
| if (addPlayerPalette(player, newPaletteName, copiedData)) { | |
| player.sendMessage(`§aPalette "${activeSavedName}" duplicated as "${newPaletteName}".`); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Duplicate Palette] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError duplicating palette."); | |
| } | | | } | |
| async function showDeletePaletteMenu(player) { | |
| const palettes = getPlayerPalettes(player); | |
| const paletteNames = Object.keys(palettes); | |
| const form = new ActionFormData().title("Delete Saved Palette"); | |
| if (paletteNames.length === 0) { | |
| form.body("No saved palettes to delete."); | |
| } else { | |
| form.body("Select a saved palette to delete:"); | |
| paletteNames.forEach(name => form.button(name)); | |
| } | | | form.button("§c[Back]"); | | | try { | | | const response = await form.show(player); | | | const backIndex = paletteNames.length; | | | if (response.canceled || response.selection === undefined || response.selection === backIndex) { | | | return; | | | } | |
| if (response.selection < backIndex) { | |
| const selectedName = paletteNames[response.selection]; | |
| const confirmForm = new ModalFormData() | |
| .title("Confirm Deletion") | |
| .toggle(`Delete your saved palette "${selectedName}"? This cannot be undone.`, { defaultValue: false }) | |
| .submitButton("Confirm Delete"); | |
| const confirmResponse = await confirmForm.show(player); | |
| if (!confirmResponse.canceled && confirmResponse.formValues?.[0] === true) { | |
| if (deletePlayerPalette(player, selectedName)) { | |
| player.sendMessage(`§aSaved palette "${selectedName}" deleted.`); | |
| } | |
| } else { | |
| player.sendMessage("§eDeletion cancelled."); | |
| } | | | } | |
| } catch (e) { | |
| console.error(`[WorldEdit Delete Palette] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError showing/processing delete palette menu."); | | | } | | | } | | | export async function showCreatePaletteFromSelectionForm(player, uniqueBlockIds) { | | | if (!uniqueBlockIds || uniqueBlockIds.length === 0) { | | | player.sendMessage("§cCannot create palette: No blocks provided for form."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | return; | | | } | |
| const form = new ModalFormData().title("Create Palette from Selection"); | |
| form.textField("New Palette Name", "Enter a unique name", { defaultValue: "" }); | |
| const formIndexToBlockId = {}; let currentIndex = 1; | |
| for (const blockId of uniqueBlockIds) { const simpleName = blockId.replace('minecraft:', ''); form.toggle(Include ${simpleName}?, { defaultValue: true }); formIndexToBlockId[currentIndex] = { type: 'toggle', blockId: blockId }; currentIndex++; form.slider(Weight (${simpleName}), 1, 100, { step: 1, defaultValue: 1 }); formIndexToBlockId[currentIndex] = { type: 'slider', blockId: blockId }; currentIndex++; } | |
| form.submitButton("Create Palette"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§ePalette creation cancelled."); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const formValues = response.formValues; const newPaletteName = formValues[0]?.trim(); | |
| if (!newPaletteName) { | |
| player.sendMessage("§cPalette name cannot be empty. Not saved."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | return; | | | } | |
| const existingPalettes = getPlayerPalettes(player); | |
| if (existingPalettes[newPaletteName]) { | |
| player.sendMessage(`§cPalette named "${newPaletteName}" already exists. Not saved.`); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const newPaletteData = []; | |
| for (let i = 1; i < formValues.length; i++) { | |
| const mapping = formIndexToBlockId[i]; | |
| if (mapping?.type === 'toggle') { | |
| const includeBlock = formValues[i]; | |
| const sliderIndex = i + 1; | |
| const weightMapping = formIndexToBlockId[sliderIndex]; | |
| if (includeBlock && weightMapping?.type === 'slider' && weightMapping?.blockId === mapping.blockId) { | |
| const weight = formValues[sliderIndex]; | |
| newPaletteData.push({ id: mapping.blockId, weight: weight }); | |
| } | | | i++; | | | } | | | } | | | if (newPaletteData.length === 0) { | | | player.sendMessage("§cNo blocks were included in the palette. Not saved."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | return; | | | } | |
| if (addPlayerPalette(player, newPaletteName, newPaletteData)) { | |
| player.sendMessage(`§aPalette "${newPaletteName}" created and saved successfully.`); | |
| } | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } catch (e) { | |
| console.error(`[Create Palette from Selection Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing create palette form."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | } | | | } |
| import { Player } from "@minecraft/server"; | |
| import { ModalFormData } from "@minecraft/server-ui"; | |
| import { | | | getPlayerSelection, | | | setPlayerSelection, | | | getPlayerPalettes, | | | getActiveSavedPaletteName | | | } from "../state/index.js"; | | | import { | | | MAX_BRUSH_SIZE, | | | DEFAULT_BRUSH_MATERIAL, | | | DEFAULT_PAINT_MASK_BLOCK, | | | MAX_HEIGHT_BRUSH_AMOUNT, | | | DEFAULT_HEIGHT_BRUSH_AMOUNT, | | | MAX_FOLIAGE_RADIUS, | | | DEFAULT_FOLIAGE_RADIUS, | | | DEFAULT_FOLIAGE_DENSITY, | | | DEFAULT_FOLIAGE_SOURCE, | | | DEFAULT_FOLIAGE_PRESET, | | | DEFAULT_FOLIAGE_CUSTOM, | | | FOLIAGE_PRESETS | | | } from "../constants/index.js"; | | | import { parseWeightedBlockInput, validateBlockIds } from "../utils/index.js"; | | | function getBrushShapeOptions() { | | | const shapeMap = { "Sphere": 'sphere', "Cube": 'cube', "Cylinder": 'cylinder', "Pyramid": 'pyramid', "Octahedron": 'octahedron', "Dome": 'dome', "Bowl": 'bowl', "Cone (Point Up)": 'cone_up', "Cone (Point Down)": 'cone_down', "Disk": 'disk', "Plate": 'plate', "Torus": 'torus' }; | |
| const reverseShapeMap = Object.fromEntries(Object.entries(shapeMap).map(([key, value]) => [value, key])); | |
| const shapeOptions = Object.keys(shapeMap); | |
| return { shapeOptions, shapeMap, reverseShapeMap }; | |
| } | | | export function buildSourceDropdownOptions(player, currentSourceValue, includePresets = false, includeCustom = true) { | |
| const selectionData = getPlayerSelection(player); | |
| const palettes = getPlayerPalettes(player); | |
| const savedPaletteNames = Object.keys(palettes).sort(); | |
| const sourceOptions = []; | |
| const sourceValueMap = {}; | |
| const reverseSourceMap = {}; | |
| if (includeCustom) { | |
| sourceOptions.push("Manual Input"); | |
| sourceValueMap["Manual Input"] = 'manual'; | |
| reverseSourceMap['manual'] = "Manual Input"; | |
| } | |
| if (includePresets) { | |
| sourceOptions.push("Preset"); | |
| sourceValueMap["Preset"] = 'preset'; | |
| reverseSourceMap['preset'] = "Preset"; | |
| } | | | const activePaletteData = selectionData.activePaletteData; | |
| const activeStatus = activePaletteData ? ` (${activePaletteData.length} items${selectionData.activePaletteIsDirty ? ' §c*' : ''})` : ' (Empty)'; | |
| const activeOptionLabel = `Active Palette${activeStatus}`; | |
| sourceOptions.push(activeOptionLabel); | |
| sourceValueMap[activeOptionLabel] = 'active'; | |
| reverseSourceMap['active'] = activeOptionLabel; | |
| savedPaletteNames.forEach(name => { | |
| const label = `Saved: ${name}`; | |
| sourceOptions.push(label); | |
| sourceValueMap[label] = name; | |
| reverseSourceMap[name] = label; | |
| }); | |
| let defaultStateValue = includeCustom ? 'manual' : (includePresets ? 'preset' : 'active'); | |
| let currentSourceIndex = sourceOptions.indexOf(reverseSourceMap[defaultStateValue]); | |
| if (currentSourceIndex < 0) currentSourceIndex = 0; | |
| const currentSourceLabel = reverseSourceMap[currentSourceValue]; | |
| if (currentSourceLabel && sourceOptions.includes(currentSourceLabel)) { | |
| currentSourceIndex = sourceOptions.indexOf(currentSourceLabel); | |
| } else if (currentSourceValue && !['manual', 'active', 'preset', 'custom'].includes(currentSourceValue)) { | |
| console.warn(`Source '${currentSourceValue}' not found in options, defaulting.`); | |
| } | |
| if (currentSourceValue === 'active') { | |
| currentSourceIndex = sourceOptions.findIndex(opt => opt.startsWith('Active Palette')); | |
| if (currentSourceIndex < 0) currentSourceIndex = 0; | |
| } | | | return { sourceOptions, sourceValueMap, currentSourceIndex }; | | | } | | | export async function showRegularBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentShape = selectionData.brushShape ?? 'sphere'; | | | const currentSize = selectionData.brushSize; | | | const currentHollow = selectionData.brushHollow; | | | const currentSourceValue = selectionData.brushMaterialSource ?? 'manual'; | | | const currentManualMaterial = selectionData.brushManualMaterial; | |
| const { shapeOptions, shapeMap, reverseShapeMap } = getBrushShapeOptions(); | |
| const currentShapeIndex = shapeOptions.indexOf(reverseShapeMap[currentShape] ?? shapeOptions[0]); | |
| const { sourceOptions, sourceValueMap, currentSourceIndex } = buildSourceDropdownOptions(player, currentSourceValue); | |
| const form = new ModalFormData().title("Regular Brush Settings") | |
| .dropdown("Shape", shapeOptions, { defaultValueIndex: currentShapeIndex }) | |
| .slider("Size (Radius/Half-Width)", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSize }) | |
| .toggle("Hollow Shape", { defaultValue: currentHollow }) | |
| .dropdown("Material Source", sourceOptions, { defaultValueIndex: currentSourceIndex }) | |
| .textField("Manual Blocks (id:wt,...)", "Used if Source is 'Manual Input'", { defaultValue: currentManualMaterial }) | |
| .submitButton("Update Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eRegular Brush settings unchanged."); | | | return; | | | } | | | let formIndex = 0; | | | const formVals = response.formValues; | |
| const newShapeName = shapeOptions[formVals[formIndex++]]; | |
| const newSize = formVals[formIndex++]; | |
| const newHollow = formVals[formIndex++]; | |
| const selectedOptionLabel = sourceOptions[formVals[formIndex++]]; | |
| const newManualMaterialInput = formVals[formIndex++].trim(); | |
| const newShape = shapeMap[newShapeName] ?? 'sphere'; | |
| const newSourceValue = sourceValueMap[selectedOptionLabel] ?? 'manual'; | |
| let manualInputIsValid = true; | |
| let finalManualMaterialString = currentManualMaterial; | |
| if (newSourceValue === 'manual') { | |
| if (!newManualMaterialInput) { manualInputIsValid = false; player.sendMessage("§cManual Blocks cannot be empty..."); } | |
| else { | |
| const parsedResult = parseWeightedBlockInput(newManualMaterialInput); | |
| if (parsedResult.errors.length > 0 || parsedResult.blocks.length === 0) { manualInputIsValid = false; player.sendMessage("§cError parsing manual blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedResult.blocks.map(b => b.id)); if (invalidIds.length > 0) { manualInputIsValid = false; player.sendMessage(§cInvalid IDs: ${invalidIds.join(',')}); } else { finalManualMaterialString = newManualMaterialInput; } } | |
| } | |
| if (!manualInputIsValid) { player.sendMessage("§eRegular Brush settings NOT updated due to invalid manual input."); return; } | |
| } | |
| if (newSourceValue === 'active' && (!selectionData.activePaletteData || selectionData.activePaletteData.length === 0)) { | |
| player.sendMessage("§cActive Palette is empty. Select 'Manual Input' or load/create a palette first. Settings not updated."); return; | |
| } | |
| if (!['manual', 'active'].includes(newSourceValue)) { | |
| const palettes = getPlayerPalettes(player); | |
| if (!palettes[newSourceValue]) { | |
| player.sendMessage(§cSelected saved palette "${newSourceValue}" no longer exists. Settings not updated.); return; | |
| } | |
| } | |
| selectionData.brushShape = newShape; | |
| selectionData.brushSize = newSize; | |
| selectionData.brushHollow = newHollow; | |
| selectionData.brushMaterialSource = newSourceValue; | |
| if (newSourceValue === 'manual') { | |
| selectionData.brushManualMaterial = finalManualMaterialString; | |
| } | |
| setPlayerSelection(player, selectionData); | |
| const sourceDesc = selectedOptionLabel; | |
| const hollowDesc = newHollow ? '(Hollow)' : ''; | |
| player.sendMessage(`§aRegular Brush updated: ${newShapeName}${hollowDesc}, Size ${newSize}, Src ${sourceDesc}`); | |
| } catch (e) { | |
| console.error(`[Regular Brush Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing regular brush settings form."); | | | } | | | } | | | export async function showPaintBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentShape = selectionData.brushShape ?? 'sphere'; | | | const currentSize = selectionData.brushSize; | | | const currentSourceValue = selectionData.brushMaterialSource ?? 'manual'; | | | const currentManualMaterial = selectionData.brushManualMaterial; | | | const currentMaskEnabled = selectionData.paintBrushMaskEnabled; | | | const currentMaskInput = selectionData.paintBrushMaskInput; | |
| const { shapeOptions, shapeMap, reverseShapeMap } = getBrushShapeOptions(); | |
| const currentShapeIndex = shapeOptions.indexOf(reverseShapeMap[currentShape] ?? shapeOptions[0]); | |
| const { sourceOptions, sourceValueMap, currentSourceIndex } = buildSourceDropdownOptions(player, currentSourceValue); | |
| const form = new ModalFormData().title("Paint Brush Settings") | |
| .dropdown("Shape", shapeOptions, { defaultValueIndex: currentShapeIndex }) | |
| .slider("Size (Radius/Half-Width)", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSize }) | |
| .dropdown("Paint Material Source", sourceOptions, { defaultValueIndex: currentSourceIndex }) | |
| .textField("Manual Paint Blocks (id:wt,...)", "Used if Source is 'Manual Input'", { defaultValue: currentManualMaterial }) | |
| .toggle("Enable Masking", { defaultValue: currentMaskEnabled }) | |
| .textField("Mask Blocks (IDs, comma-sep)", "e.g. stone,dirt,grass", { defaultValue: currentMaskInput }) | |
| .submitButton("Update Paint Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§ePaint Brush settings unchanged."); | | | return; | | | } | | | let formIndex = 0; | | | const formVals = response.formValues; | |
| const newShapeName = shapeOptions[formVals[formIndex++]]; | |
| const newSize = formVals[formIndex++]; | |
| const selectedOptionLabel = sourceOptions[formVals[formIndex++]]; | |
| const newManualMaterialInput = formVals[formIndex++].trim(); | |
| const newMaskEnabled = formVals[formIndex++]; | |
| const newMaskInput = formVals[formIndex++].trim(); | |
| const newShape = shapeMap[newShapeName] ?? 'sphere'; | |
| const newSourceValue = sourceValueMap[selectedOptionLabel] ?? 'manual'; | |
| let manualInputIsValid = true; | |
| let finalManualMaterialString = currentManualMaterial; | |
| if (newSourceValue === 'manual') { | |
| if (!newManualMaterialInput) { manualInputIsValid = false; player.sendMessage("§cManual Paint Blocks cannot be empty..."); } | |
| else { | |
| const parsedResult = parseWeightedBlockInput(newManualMaterialInput); | |
| if (parsedResult.errors.length > 0 || parsedResult.blocks.length === 0) { manualInputIsValid = false; player.sendMessage("§cError parsing manual blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedResult.blocks.map(b => b.id)); if (invalidIds.length > 0) { manualInputIsValid = false; player.sendMessage(§cInvalid IDs: ${invalidIds.join(',')}); } else { finalManualMaterialString = newManualMaterialInput; } } | |
| } | |
| if (!manualInputIsValid) { player.sendMessage("§ePaint Brush settings NOT updated due to invalid manual input."); return; } | |
| } | |
| if (newSourceValue === 'active' && (!selectionData.activePaletteData || selectionData.activePaletteData.length === 0)) { | |
| player.sendMessage("§cActive Palette is empty. Select 'Manual Input' or load/create a palette first. Settings not updated."); return; | |
| } | |
| if (!['manual', 'active'].includes(newSourceValue)) { | |
| const palettes = getPlayerPalettes(player); | |
| if (!palettes[newSourceValue]) { | |
| player.sendMessage(§cSelected saved palette "${newSourceValue}" no longer exists. Settings not updated.); return; | |
| } | |
| } | |
| let maskInputIsValid = true; | |
| let finalMaskInputString = currentMaskInput; | |
| if (newMaskEnabled) { | |
| if (!newMaskInput) { maskInputIsValid = false; player.sendMessage("§cMask Blocks cannot be empty..."); } | |
| else { | |
| const parsedMask = parseWeightedBlockInput(newMaskInput); | |
| if (parsedMask.blocks.length === 0) { maskInputIsValid = false; player.sendMessage("§cNo valid blocks found in Mask Blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedMask.blocks.map(b => b.id)); if (invalidIds.length > 0) { maskInputIsValid = false; player.sendMessage(§cInvalid Mask IDs: ${invalidIds.join(', ')}...); } else { finalMaskInputString = newMaskInput; } } | |
| } | |
| if (!maskInputIsValid) { player.sendMessage("§ePaint Brush settings NOT updated due to invalid mask input."); return; } | |
| } | |
| selectionData.brushShape = newShape; | |
| selectionData.brushSize = newSize; | |
| selectionData.brushMaterialSource = newSourceValue; | |
| if (newSourceValue === 'manual') { | |
| selectionData.brushManualMaterial = finalManualMaterialString; | |
| } | |
| selectionData.paintBrushMaskEnabled = newMaskEnabled; | |
| if (newMaskEnabled) { | |
| selectionData.paintBrushMaskInput = finalMaskInputString; | |
| } | |
| setPlayerSelection(player, selectionData); | |
| const paintSourceDesc = selectedOptionLabel; | |
| const maskDesc = newMaskEnabled ? `ON` : 'OFF'; | |
| player.sendMessage(`§aPaint Brush updated: ${newShapeName}, Size ${newSize}, Paint ${paintSourceDesc}, Mask ${maskDesc}`); | |
| } catch (e) { | |
| console.error(`[Paint Brush Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing paint brush settings form."); | | | } | | | } | | | export async function showHeightBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentShape = selectionData.brushShape ?? 'sphere'; | | | const currentSize = selectionData.brushSize; | | | const currentAmount = selectionData.heightBrushAmount ?? DEFAULT_HEIGHT_BRUSH_AMOUNT; | | | const currentMode = selectionData.heightBrushMode ?? "Raise"; | |
| const { shapeOptions, shapeMap, reverseShapeMap } = getBrushShapeOptions(); | |
| const currentShapeIndex = shapeOptions.indexOf(reverseShapeMap[currentShape] ?? shapeOptions[0]); | |
| const modeOptions = ["Raise", "Lower"]; | |
| const currentModeIndex = modeOptions.indexOf(currentMode); | |
| const form = new ModalFormData().title("Height Brush Settings") | |
| .dropdown("Shape", shapeOptions, { defaultValueIndex: currentShapeIndex }) | |
| .slider("Size (Radius/Half-Width)", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSize }) | |
| .slider("Amount", 1, MAX_HEIGHT_BRUSH_AMOUNT, { step: 1, defaultValue: currentAmount }) | |
| .dropdown("Mode", modeOptions, { defaultValueIndex: Math.max(0, currentModeIndex) }) | |
| .submitButton("Update Height Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { player.sendMessage("§eHeight Brush settings unchanged."); return; } | |
| let formIndex = 0; | |
| const formVals = response.formValues; | |
| const newShapeName = shapeOptions[formVals[formIndex++]]; | |
| const newSize = formVals[formIndex++]; | |
| const newAmount = formVals[formIndex++]; | |
| const newModeName = modeOptions[formVals[formIndex++]]; | |
| const newShape = shapeMap[newShapeName] ?? 'sphere'; | |
| selectionData.brushShape = newShape; | | | selectionData.brushSize = newSize; | | | selectionData.heightBrushAmount = newAmount; | | | selectionData.heightBrushMode = newModeName; | |
| setPlayerSelection(player, selectionData); | |
| player.sendMessage(`§aHeight Brush updated: ${newShapeName}, Size ${newSize}, Amt ${newAmount}, Mode ${newModeName}`); | |
| } catch (e) { console.error(`[Height Brush Settings Form] Error: ${e}\n${e.stack}`); player.sendMessage("§cError processing height brush settings form."); } | |
| } | | | export async function showFoliageBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentRadius = selectionData.foliageBrushRadius ?? DEFAULT_FOLIAGE_RADIUS; | | | const currentDensity = selectionData.foliageBrushDensity ?? DEFAULT_FOLIAGE_DENSITY; | | | const currentSourceValue = selectionData.foliageBrushPlantSource ?? DEFAULT_FOLIAGE_SOURCE; | | | const currentPreset = selectionData.foliageBrushPresetType ?? DEFAULT_FOLIAGE_PRESET; | | | const currentCustom = selectionData.foliageBrushCustomInput ?? DEFAULT_FOLIAGE_CUSTOM; | | | const { sourceOptions, sourceValueMap, currentSourceIndex } = buildSourceDropdownOptions(player, currentSourceValue, true, true); | |
| const presetOptions = Object.keys(FOLIAGE_PRESETS); | |
| let currentPresetIndex = presetOptions.indexOf(currentPreset); | |
| if (currentPresetIndex === -1 || presetOptions.length === 0) { currentPresetIndex = 0; } | |
| const form = new ModalFormData().title("Foliage Brush Settings") | |
| .slider("Scatter Radius", 1, MAX_FOLIAGE_RADIUS, { step: 1, defaultValue: currentRadius }) | |
| .slider("Density (%)", 0, 100, { step: 1, defaultValue: currentDensity }) | |
| .dropdown("Plant Source", sourceOptions, { defaultValueIndex: currentSourceIndex }) | |
| .dropdown("Preset Type", presetOptions.length > 0 ? presetOptions : ["No Presets Defined"], { defaultValueIndex: currentPresetIndex }) | |
| .textField("Custom Plants (id:wt,...)", "Used if Source is 'Custom Input'", { defaultValue: currentCustom }) | |
| .submitButton("Update Foliage Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eFoliage Brush settings unchanged."); | | | return; | | | } | | | const formVals = response.formValues; | |
| const newRadius = formVals[0]; | |
| const newDensity = formVals[1]; | |
| const selectedOptionLabel = sourceOptions[formVals[2]]; | |
| const newPresetName = presetOptions.length > 0 ? presetOptions[formVals[3]] : DEFAULT_FOLIAGE_PRESET; | |
| const newCustomInput = formVals[4].trim(); | |
| const newSourceValue = sourceValueMap[selectedOptionLabel] ?? DEFAULT_FOLIAGE_SOURCE; | | | let customInputIsValid = true; | | | let finalCustomInputString = currentCustom; | |
| if (newSourceValue === 'manual') { | |
| if (!newCustomInput) { customInputIsValid = false; player.sendMessage("§cCustom Plants cannot be empty..."); } | |
| else { | |
| const parsedResult = parseWeightedBlockInput(newCustomInput); | |
| if (parsedResult.errors.length > 0 || parsedResult.blocks.length === 0) { customInputIsValid = false; player.sendMessage("§cError parsing custom blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedResult.blocks.map(b => b.id)); if (invalidIds.length > 0) { customInputIsValid = false; player.sendMessage(§cInvalid IDs: ${invalidIds.join(',')}); } else { finalCustomInputString = newCustomInput; } } | |
| } | |
| if (!customInputIsValid) { player.sendMessage("§cFoliage Brush settings NOT updated due to invalid manual input."); return; } | |
| } | |
| if (newSourceValue === 'active' && (!selectionData.activePaletteData || selectionData.activePaletteData.length === 0)) { | |
| player.sendMessage("§cActive Palette is empty. Select another source or load/create a palette first. Settings not updated."); return; | |
| } | |
| if (!['preset', 'manual', 'active'].includes(newSourceValue)) { | |
| const palettes = getPlayerPalettes(player); | |
| if (!palettes[newSourceValue]) { | |
| player.sendMessage(§cSelected saved palette "${newSourceValue}" no longer exists. Settings not updated.); return; | |
| } | |
| } | |
| if (newSourceValue === 'preset' && presetOptions.length === 0) { | |
| player.sendMessage("§cCannot select 'Preset' source as no presets are defined. Settings not updated."); return; | |
| } | |
| selectionData.foliageBrushRadius = newRadius; | |
| selectionData.foliageBrushDensity = newDensity; | |
| selectionData.foliageBrushPlantSource = newSourceValue; | |
| selectionData.foliageBrushPresetType = newPresetName; | |
| if (newSourceValue === 'manual') { | |
| selectionData.foliageBrushCustomInput = finalCustomInputString; | |
| } | |
| setPlayerSelection(player, selectionData); | |
| const sourceDesc = selectedOptionLabel; | |
| player.sendMessage(`§aFoliage Brush updated: Radius=${newRadius}, Density=${newDensity}%, Source=${sourceDesc}`); | |
| } catch (e) { | |
| console.error(`[Foliage Brush Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing foliage brush settings form."); | | | } | | | }import { Player, system, GameMode, EquipmentSlot } from "@minecraft/server"; | | | import { ModalFormData, ActionFormData } from "@minecraft/server-ui"; | | | import { | | | getGlobalSettings, | | | saveGlobalSettings, | | | } from "../state/global_state.js"; | | | import { | | | getPlayerSelection, | | | setPlayerSelection | | | } from "../state/player_state.js"; | | | import { | | | MAX_UNDO_HISTORY, | | | MAX_BRUSH_SIZE, | | | DEFAULT_PLAYER_SPEED_MULTIPLIER, | | | MIN_PLAYER_SPEED_MULTIPLIER, | | | MAX_PLAYER_SPEED_MULTIPLIER, | | | DEFAULT_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | DEFAULT_AUTO_SPECTATOR_ENABLED, | | | DEFAULT_EXTENDED_REACH_ENABLED, | | | DEFAULT_SHOW_ACTION_FEEDBACK_MESSAGES | | | } from "../constants/settings.js"; | | | export async function showMainSettingsHubForm(player) { | |
| const form = new ActionFormData() | |
| .title("Addon Settings") | |
| .body("Choose the type of settings you want to configure.") | | | .button("World Settings", "textures/ui/world_glyph_color_2x") | | | .button("Player Settings", "textures/ui/icon_steve") | | | .button("Cancel", "textures/ui/cancel"); | | | try { | |
| const response = await form.show(player); | |
| if (response.canceled || response.selection === undefined) { | |
| return; | | | } | | | switch (response.selection) { | | | case 0: | | | await showWorldSettingsForm(player); | | | break; | | | case 1: | | | await showPlayerSettingsForm(player); | | | break; | | | case 2: | | | return; | | | } | |
| } catch (e) { | |
| console.error(`[Main Settings Hub Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError opening settings hub."); | |
| } | | | } | |
| async function showWorldSettingsForm(player) { | |
| const currentSettings = getGlobalSettings(); | |
| const form = new ModalFormData() | |
| .title("World Settings (Global)") | |
| .toggle("Show Action Bar Info", { defaultValue: currentSettings.showActionBarInfo }) | |
| .textField("Outline Particle ID", "e.g., minecraft:basic_flame_particle", { defaultValue: currentSettings.outlineParticleId }) | |
| .slider("Max Undo Steps (per player)", 0, 100, { step: 1, defaultValue: currentSettings.maxUndoHistory }) | |
| .slider("Default Brush Size", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSettings.defaultBrushSize }) | |
| .submitButton("Save World Settings"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eWorld settings unchanged."); | |
| system.run(() => showMainSettingsHubForm(player)); | |
| return; | | | } | | | const formVals = response.formValues; | |
| const newSettings = { ...currentSettings }; | |
| let validationErrors = []; | |
| newSettings.showActionBarInfo = formVals[0]; | |
| const newParticleId = formVals[1].trim(); | |
| if (!newParticleId) { | |
| validationErrors.push("Outline Particle ID cannot be empty."); | |
| } else { | |
| newSettings.outlineParticleId = newParticleId.includes(':') ? newParticleId : `minecraft:${newParticleId}`; | |
| } | |
| const newMaxUndo = formVals[2]; | |
| if (typeof newMaxUndo === 'number' && Number.isInteger(newMaxUndo)) { | |
| newSettings.maxUndoHistory = Math.max(0, Math.min(1000, newMaxUndo)); | |
| } else { | |
| validationErrors.push("Invalid value for Max Undo Steps."); | | | } | |
| const newDefaultBrushSize = formVals[3]; | |
| if (typeof newDefaultBrushSize === 'number' && Number.isInteger(newDefaultBrushSize)) { | |
| newSettings.defaultBrushSize = Math.max(1, Math.min(MAX_BRUSH_SIZE, newDefaultBrushSize)); | |
| } else { | |
| validationErrors.push("Invalid value for Default Brush Size."); | | | } | |
| if (validationErrors.length > 0) { | |
| player.sendMessage("§cInvalid input:\n" + validationErrors.join("\n") + "\n§eWorld settings not saved."); | |
| } else { | |
| if (saveGlobalSettings(newSettings)) { | |
| player.sendMessage("§aGlobal World settings saved successfully."); | | | } else { | | | player.sendMessage("§cFailed to save global World settings. Check server logs."); | | | } | | | } | |
| system.run(() => showMainSettingsHubForm(player)); | |
| } catch (e) { | |
| console.error(`[World Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError displaying or processing World settings form."); | | | system.run(() => showMainSettingsHubForm(player)); | | | } | | | } | |
| async function showPlayerSettingsForm(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const currentPlayerSpeedMultiplier = selectionData.playerSpeedMultiplier ?? DEFAULT_PLAYER_SPEED_MULTIPLIER; | | | const currentPlayerVerticalSpeedMultiplier = selectionData.playerVerticalSpeedMultiplier ?? DEFAULT_PLAYER_VERTICAL_SPEED_MULTIPLIER; | | | const currentAutoSpectatorEnabled = selectionData.autoSpectatorEnabled ?? DEFAULT_AUTO_SPECTATOR_ENABLED; | | | const currentExtendedReachEnabled = selectionData.extendedReachEnabled ?? DEFAULT_EXTENDED_REACH_ENABLED; | | | const currentShowActionFeedbackMessages = selectionData.showActionFeedbackMessages ?? DEFAULT_SHOW_ACTION_FEEDBACK_MESSAGES; | |
| const form = new ModalFormData() | |
| .title("Player Settings") | |
| .slider( | | | "Creative Horizontal Speed Multiplier", | | | MIN_PLAYER_SPEED_MULTIPLIER, | | | MAX_PLAYER_SPEED_MULTIPLIER, | | | { step: 0.1, defaultValue: currentPlayerSpeedMultiplier } | | | ) | | | .slider( | | | "Creative Vertical Speed Multiplier (Flying)", | | | MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | { step: 0.1, defaultValue: currentPlayerVerticalSpeedMultiplier } | | | ) | | | .toggle( | | | "Auto Spectator Near Walls (Creative Flying)", | | | { defaultValue: currentAutoSpectatorEnabled } | | | ) | | | .toggle( | | | "Enable Extended Reach Placement", | | | { defaultValue: currentExtendedReachEnabled } | | | ) | | | .toggle( | | | "Show Action Feedback Messages", | | | { defaultValue: currentShowActionFeedbackMessages } | | | ) | | | .submitButton("Save Player Settings"); | | | try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§ePlayer settings unchanged."); | |
| system.run(() => showMainSettingsHubForm(player)); | |
| return; | | | } | | | const formVals = response.formValues; | |
| const newSpeedMultiplier = formVals[0]; | |
| const newVerticalSpeedMultiplier = formVals[1]; | |
| const newAutoSpectatorEnabled = formVals[2]; | |
| const newExtendedReachEnabled = formVals[3]; | |
| const newShowActionFeedbackMessages = formVals[4]; | |
| let settingsChanged = false; | |
| let messages = []; | |
| if (typeof newSpeedMultiplier === 'number' && | |
| newSpeedMultiplier >= MIN_PLAYER_SPEED_MULTIPLIER && | |
| newSpeedMultiplier <= MAX_PLAYER_SPEED_MULTIPLIER) { | |
| if (selectionData.playerSpeedMultiplier !== parseFloat(newSpeedMultiplier.toFixed(2))) { | |
| selectionData.playerSpeedMultiplier = parseFloat(newSpeedMultiplier.toFixed(2)); | |
| settingsChanged = true; | |
| } | |
| } else { | |
| messages.push(§cInvalid horizontal speed multiplier. Must be between ${MIN_PLAYER_SPEED_MULTIPLIER} and ${MAX_PLAYER_SPEED_MULTIPLIER}. Horizontal speed not changed.); | |
| } | |
| if (typeof newVerticalSpeedMultiplier === 'number' && | |
| newVerticalSpeedMultiplier >= MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER && | |
| newVerticalSpeedMultiplier <= MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER) { | |
| if (selectionData.playerVerticalSpeedMultiplier !== parseFloat(newVerticalSpeedMultiplier.toFixed(2))) { | |
| selectionData.playerVerticalSpeedMultiplier = parseFloat(newVerticalSpeedMultiplier.toFixed(2)); | |
| settingsChanged = true; | |
| } | |
| } else { | |
| messages.push(§cInvalid vertical speed multiplier. Must be between ${MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER} and ${MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER}. Vertical speed not changed.); | |
| } | |
| if (typeof newAutoSpectatorEnabled === 'boolean') { | |
| if (selectionData.autoSpectatorEnabled !== newAutoSpectatorEnabled) { | |
| selectionData.autoSpectatorEnabled = newAutoSpectatorEnabled; | | | settingsChanged = true; | | | } | | | } else { | | | messages.push("§cInvalid value for Auto Spectator toggle. Setting not changed."); | | | } | |
| if (typeof newExtendedReachEnabled === 'boolean') { | |
| if (selectionData.extendedReachEnabled !== newExtendedReachEnabled) { | |
| selectionData.extendedReachEnabled = newExtendedReachEnabled; | | | settingsChanged = true; | | | } | | | } else { | | | messages.push("§cInvalid value for Extended Reach toggle. Setting not changed."); | | | } | |
| if (typeof newShowActionFeedbackMessages === 'boolean') { | |
| if (selectionData.showActionFeedbackMessages !== newShowActionFeedbackMessages) { | |
| selectionData.showActionFeedbackMessages = newShowActionFeedbackMessages; | | | settingsChanged = true; | | | } | | | } else { | | | messages.push("§cInvalid value for Show Action Feedback Messages toggle. Setting not changed."); | | | } | |
| if (settingsChanged) { | |
| setPlayerSelection(player, selectionData); | |
| messages.unshift(§aPlayer settings updated: HSpeed=${selectionData.playerSpeedMultiplier}x, VSpeed=${selectionData.playerVerticalSpeedMultiplier}x, AutoSpec=${selectionData.autoSpectatorEnabled ? 'ON' : 'OFF'}, ExtReach=${selectionData.extendedReachEnabled ? 'ON' : 'OFF'}, FeedbackMsgs=${selectionData.showActionFeedbackMessages ? 'ON' : 'OFF'}); | |
| } else if (!response.canceled && messages.length === 0) { | |
| messages.push("§eNo changes made to player settings."); | |
| } | |
| if (messages.length > 0) { | |
| player.sendMessage(messages.join('\n')); | |
| } | |
| system.run(() => showMainSettingsHubForm(player)); | |
| } catch (e) { | |
| console.error(`[Player Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError displaying or processing Player settings form."); | | | system.run(() => showMainSettingsHubForm(player)); | | | } | |
| }import { Player, system } from "@minecraft/server"; | |
| import { ActionFormData, ModalFormData } from "@minecraft/server-ui"; | |
| import { | | | getPlayerSelection, | | | getPlayerPalettes, | | | savePlayerPalettes, | | | addPlayerPalette, | | | deletePlayerPalette, | | | setActiveSavedPaletteName, | | | getActiveSavedPaletteName, | | | setPlayerSelection, | | | discardActivePaletteChanges | | | } from "../state/index.js"; | | | import { parseWeightedBlockInput, validateBlockIds } from "../utils/index.js"; | | | import { showEditMenu } from "./main_menu.js"; | | | import { analyzeSelectionForPalette } from "../core/analyze_selection.js"; | |
| export async function showPaletteMenu(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const activeSaved = getActiveSavedPaletteName(player); | |
| const activePaletteData = selectionData.activePaletteData; | | | const isDirty = !!selectionData.activePaletteIsDirty; | | | const activeItemsCount = activePaletteData?.length ?? 0; | |
| const activeStatus = activePaletteData ? ` (${activeItemsCount} items${isDirty ? ' §c*' : ''})` : ' (Empty/Inactive)'; | |
| const hasCompleteSelection = selectionData.selectionState === 2; | |
| const form = new ActionFormData() | |
| .title("Manage Palettes") | |
| .body(`§eActive Palette:§r ${activeStatus}\n§eLoaded Saved Palette:§r ${activeSaved ? `§b${activeSaved}` : "§7None"}`) | |
| .button("Load Saved Palette", "textures/ui/book_glyph_color") | |
| .button("Edit Saved Palette", "textures/ui/book_edit_default") | |
| .button("Save Options...", "textures/ui/icon_save") | |
| .button(Create from Selection... ${hasCompleteSelection ? '' : '§7(Needs Sel)'}, "textures/ui/icon_import") | |
| .button("Duplicate Loaded Saved Palette", "textures/ui/copy") | |
| .button("Delete Saved Palette", "textures/ui/trash") | |
| .button("Back", "textures/ui/cancel"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || response.selection === undefined) return; | |
| let refreshMenu = true; | |
| switch (response.selection) { | |
| case 0: await showLoadPaletteList(player); break; | |
| case 1: await showEditPaletteList(player); break; | |
| case 2: await showSaveOptionsMenu(player); refreshMenu = false; break; | |
| case 3: | |
| if (hasCompleteSelection) { | |
| const uniqueBlockIds = await analyzeSelectionForPalette(player); | |
| if (uniqueBlockIds) { | |
| await showCreatePaletteFromSelectionForm(player, uniqueBlockIds); | |
| refreshMenu = false; | |
| } else { | |
| refreshMenu = true; | |
| } | | | } else { | | | player.sendMessage("§cComplete selection needed to create palette."); | | | refreshMenu = true; | | | } | | | break; | |
| case 4: await duplicateActiveSavedPalette(player); break; | |
| case 5: await showDeletePaletteMenu(player); break; | |
| case 6: | |
| refreshMenu = false; | |
| await system.run(async () => { await showEditMenu(player); }); | |
| return; | | | } | |
| if (refreshMenu) { | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Palette Menu] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError displaying palette menu."); | | | } | | | } | |
| async function showLoadPaletteList(player) { | |
| const palettes = getPlayerPalettes(player); | |
| const paletteNames = Object.keys(palettes); | |
| const currentSelection = getPlayerSelection(player); | |
| const isDirty = !!currentSelection.activePaletteIsDirty; | |
| const form = new ActionFormData().title("Load Saved Palette"); | |
| let body = ""; | |
| if (isDirty) { | |
| body += "§cWarning: will discard unsaved Active Palette changes!§r\n"; | | | } | | | body += "Select saved palette to load into Active Palette:"; | |
| form.body(body); | |
| if (paletteNames.length === 0) { | |
| form.body(body + "\n§eNo saved palettes available.§r"); | |
| } else { | |
| paletteNames.forEach(name => form.button(name)); | |
| } | | | form.button("§c[Cancel]"); | | | try { | | | const response = await form.show(player); | | | const cancelIndex = paletteNames.length; | | | if (response.canceled || response.selection === undefined || response.selection === cancelIndex) { | | | return; | | | } | |
| if (response.selection < cancelIndex) { | |
| const selectedName = paletteNames[response.selection]; | |
| if (isDirty) { | |
| const confirmForm = new ModalFormData() | |
| .title("Discard Unsaved Changes?") | |
| .toggle(` "${selectedName}" will discard unsaved changes in the Active Palette. Continue?`, { defaultValue: false }) | |
| .submitButton("Confirm Load"); | |
| const confirmResponse = await confirmForm.show(player); | |
| if (confirmResponse.canceled || !confirmResponse.formValues || !confirmResponse.formValues[0]) { | | | player.sendMessage("§eLoad cancelled."); | | | return; | | | } | | | } | |
| const paletteDataToLoad = palettes[selectedName]; | |
| if (!paletteDataToLoad) { | |
| player.sendMessage(`§cError: Palette "${selectedName}" data not found.`); | |
| return; | |
| } | |
| currentSelection.activePaletteData = JSON.parse(JSON.stringify(paletteDataToLoad)); | |
| currentSelection.activeSavedPaletteName = selectedName; | |
| currentSelection.activePaletteIsDirty = false; | |
| setPlayerSelection(player, currentSelection); | |
| player.sendMessage(§aLoaded "${selectedName}" into Active Palette and set as loaded saved palette.); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Load Palette List] Error: ${e}\n${e.stack}`); | |
| player.sendMessage(§cError displaying load palette list.); | |
| } | |
| } | |
| async function showEditPaletteList(player) { | |
| const palettes = getPlayerPalettes(player); | |
| const paletteNames = Object.keys(palettes); | |
| const form = new ActionFormData().title("Edit Saved Palette"); | |
| form.body("Select saved palette to edit directly:"); | |
| if (paletteNames.length === 0) { | |
| form.body("§eNo saved palettes available to edit.§r"); | |
| } else { | |
| paletteNames.forEach(name => form.button(name)); | |
| } | | | form.button("§c[Cancel]"); | | | try { | | | const response = await form.show(player); | | | const cancelIndex = paletteNames.length; | | | if (response.canceled || response.selection === undefined || response.selection === cancelIndex) { | | | return; | | | } | |
| if (response.selection < cancelIndex) { | |
| const paletteNameToEdit = paletteNames[response.selection]; | |
| system.run(async () => { | |
| await editTextPalette(player, paletteNameToEdit); | |
| }); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Edit Palette List] Error: ${e}\n${e.stack}`); | |
| player.sendMessage(§cError selecting palette to edit.); | |
| } | |
| } | |
| async function editTextPalette(player, paletteName) { | |
| const palettes = getPlayerPalettes(player); | |
| const currentPaletteData = palettes[paletteName]; | |
| if (!currentPaletteData) { | |
| player.sendMessage(`§cPalette "${paletteName}" not found (maybe deleted?).`); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const currentBlocksString = currentPaletteData.map(b => `${b.id.replace('minecraft:', '')}:${b.weight}`).join(', '); | |
| const form = new ModalFormData() | |
| .title(`Edit Saved Palette: ${paletteName}`) | |
| .textField("Palette Name", "Edit name (must be unique)", { defaultValue: paletteName }) | |
| .textField("Blocks (format: id:weight, ...)", "Edit blocks and weights", { defaultValue: currentBlocksString }) | |
| .submitButton("Save Changes"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eEdit cancelled."); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const newPaletteName = response.formValues[0]?.trim(); | |
| const newBlockInput = response.formValues[1]?.trim(); | |
| if (!newPaletteName) { player.sendMessage("§cPalette name cannot be empty. Not saved."); } | |
| else if (!newBlockInput) { player.sendMessage("§cBlock input cannot be empty. Not saved."); } | |
| else { | |
| const { blocks: newBlocksWithWeights, errors } = parseWeightedBlockInput(newBlockInput); | |
| if (errors.length > 0) { player.sendMessage(`§cInput errors:\n${errors.join('\n')}`); } | |
| else if (newBlocksWithWeights.length === 0) { player.sendMessage("§cNo valid blocks parsed. Palette not saved."); } | |
| else { | |
| const { invalidIds } = validateBlockIds(newBlocksWithWeights.map(b => b.id)); | |
| if (invalidIds.length > 0) { player.sendMessage(`§cInvalid block ID(s) resolved: ${invalidIds.join(', ')}. Palette not saved.`); } | |
| else { | |
| const updatedPalettes = getPlayerPalettes(player); | |
| if (newPaletteName !== paletteName && updatedPalettes[newPaletteName]) { | |
| player.sendMessage(§cAnother palette named "${newPaletteName}" already exists. Cannot rename/save.); | |
| } else { | |
| if (newPaletteName !== paletteName) { | |
| delete updatedPalettes[paletteName]; | |
| } | |
| updatedPalettes[newPaletteName] = newBlocksWithWeights; | |
| if (savePlayerPalettes(player, updatedPalettes)) { | |
| player.sendMessage(`§aSaved palette "${newPaletteName}" updated successfully.`); | |
| const selectionData = getPlayerSelection(player); | |
| if (selectionData.activeSavedPaletteName === paletteName && newPaletteName !== paletteName) { | |
| setActiveSavedPaletteName(player, newPaletteName); | |
| player.sendMessage(`§eLoaded saved palette updated to "${newPaletteName}".`); | |
| } | | | } | | | } | | | } | | | } | | | } | |
| } catch (e) { | |
| console.error(`[WorldEdit Edit Palette Text] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError editing palette."); | |
| } | | | await system.run(async () => { await showPaletteMenu(player); }); | | | } | |
| async function showSaveOptionsMenu(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const activeSavedName = selectionData.activeSavedPaletteName; | | | const activePaletteData = selectionData.activePaletteData; | | | const isDirty = !!selectionData.activePaletteIsDirty; | | | const hasActiveData = !!activePaletteData && activePaletteData.length > 0; | |
| const form = new ActionFormData().title("Save Options"); | |
| let body = `§eActive Palette Status:§r ${hasActiveData ? `${activePaletteData.length} items` : 'Empty'}${isDirty ? ' §c(Unsaved Changes)' : ' (No Changes)'}\n`; | |
| body += `§eLoaded Saved Palette:§r ${activeSavedName ? `§b${activeSavedName}` : "§7None"}`; | |
| form.body(body); | |
| let buttonIndexMap = {}; | |
| let currentButtonIndex = 0; | |
| if (hasActiveData) { | |
| form.button("Save Active Palette as New..."); | |
| buttonIndexMap[currentButtonIndex++] = "save_as"; | |
| } else { | |
| form.button("Save Active Palette as New... §7(Empty)"); | |
| buttonIndexMap[currentButtonIndex++] = "save_as_disabled"; | |
| } | |
| if (activeSavedName && isDirty && hasActiveData) { | |
| form.button(`Update Saved '${activeSavedName}'`); | |
| buttonIndexMap[currentButtonIndex++] = "update_active"; | |
| } else { | |
| form.button(`Update Loaded Saved Palette §7(N/A)`); | |
| buttonIndexMap[currentButtonIndex++] = "update_disabled"; | |
| } | |
| if (hasActiveData || isDirty) { | |
| form.button("Discard Active Palette Changes"); | |
| buttonIndexMap[currentButtonIndex++] = "discard"; | |
| } else { | |
| form.button("Discard Active Palette Changes §7(N/A)"); | |
| buttonIndexMap[currentButtonIndex++] = "discard_disabled"; | |
| } | |
| form.button("Back"); | |
| buttonIndexMap[currentButtonIndex++] = "back"; | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || response.selection === undefined) { | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const action = buttonIndexMap[response.selection]; | |
| let returnToMainMenu = true; | |
| switch (action) { | |
| case "save_as": await saveActivePaletteAs(player); break; | |
| case "save_as_disabled": | | | player.sendMessage("§cActive Palette is empty, cannot 'Save As'."); | |
| returnToMainMenu = false; | |
| await system.run(async () => { await showSaveOptionsMenu(player); }); | |
| break; | | | case "update_active": await confirmOverwriteActiveSavedPalette(player); break; | | | case "update_disabled": | | | player.sendMessage("§cCannot update: No loaded saved palette, or no unsaved changes, or Active Palette is empty."); | |
| returnToMainMenu = false; | |
| await system.run(async () => { await showSaveOptionsMenu(player); }); | |
| break; | | | case "discard": await discardActivePaletteChanges(player); break; | | | case "discard_disabled": | | | player.sendMessage("§eNo Active Palette changes or data to discard."); | |
| returnToMainMenu = false; | |
| await system.run(async () => { await showSaveOptionsMenu(player); }); | |
| break; | |
| case "back": break; | |
| default: console.warn(`Invalid action selected in Save Options Menu: ${response.selection}`); break; | |
| } | |
| if (returnToMainMenu) { | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Save Options Menu] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError in save options menu."); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } | | | } | |
| async function saveActivePaletteAs(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const activePaletteData = selectionData.activePaletteData; | | | if (!activePaletteData || activePaletteData.length === 0) { | | | player.sendMessage("§cActive Palette is empty. Add blocks using Picker first."); | | | return; | | | } | | | const form = new ModalFormData() | | | .title("Save Active Palette As...") | |
| .textField("New Palette Name", "Enter a unique name", { defaultValue: "" }) | |
| .submitButton("Save Palette"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eSave cancelled."); | |
| return; | | | } | |
| const newPaletteName = response.formValues[0]?.trim(); | |
| if (!newPaletteName) { | |
| player.sendMessage("§cPalette name cannot be empty."); | | | return; | | | } | |
| const existingPalettes = getPlayerPalettes(player); | |
| if (existingPalettes[newPaletteName]) { | |
| player.sendMessage(`§cPalette named "${newPaletteName}" already exists.`); | |
| return; | | | } | |
| if (addPlayerPalette(player, newPaletteName, activePaletteData)) { | |
| player.sendMessage(`§aActive Palette saved as "${newPaletteName}".`); | |
| selectionData.activeSavedPaletteName = newPaletteName; | | | selectionData.activePaletteIsDirty = false; | | | setPlayerSelection(player, selectionData); | | | } | |
| } catch (e) { | |
| console.error(`[WorldEdit Save Active As] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError saving palette."); | |
| } | | | } | | | async function confirmOverwriteActiveSavedPalette(player) { | | | const selectionData = getPlayerSelection(player); | | | const activePaletteData = selectionData.activePaletteData; | | | const activeSavedName = selectionData.activeSavedPaletteName; | | | const isDirty = !!selectionData.activePaletteIsDirty; | |
| if (!activeSavedName) { player.sendMessage("§cNo loaded saved palette set to overwrite."); return; } | |
| if (!activePaletteData || activePaletteData.length === 0) { player.sendMessage("§cActive Palette is empty. Cannot overwrite."); return; } | |
| if (!isDirty) { player.sendMessage("§eNo unsaved changes in Active Palette to overwrite."); return; } | |
| const form = new ModalFormData() | |
| .title("Overwrite Saved Palette?") | |
| .toggle(Overwrite saved palette "${activeSavedName}" with current Active Palette changes? This cannot be undone., { defaultValue: false }) | |
| .submitButton("Overwrite Palette"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues || !response.formValues[0]) { | |
| player.sendMessage("§eOverwrite cancelled."); | |
| return; | |
| } | |
| if (addPlayerPalette(player, activeSavedName, activePaletteData)) { | |
| player.sendMessage(§aSaved palette "${activeSavedName}" updated with Active Palette changes.); | |
| selectionData.activePaletteIsDirty = false; | |
| setPlayerSelection(player, selectionData); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Overwrite Active Saved] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError overwriting palette."); | |
| } | | | } | |
| async function duplicateActiveSavedPalette(player) { | |
| const activeSavedName = getActiveSavedPaletteName(player); | |
| if (!activeSavedName) { | |
| player.sendMessage("§cNo loaded saved palette selected to duplicate."); | | | return; | | | } | |
| const palettes = getPlayerPalettes(player); | |
| const paletteToCopy = palettes[activeSavedName]; | |
| if (!paletteToCopy) { | |
| player.sendMessage(`§cCould not find data for loaded saved palette "${activeSavedName}".`); | |
| return; | | | } | |
| const form = new ModalFormData() | |
| .title(`Duplicate Saved Palette: ${activeSavedName}`) | |
| .textField("New Palette Name", `Enter a unique name for the copy`, { defaultValue: `${activeSavedName} Copy` }) | |
| .submitButton("Duplicate"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eDuplicate cancelled."); | |
| return; | | | } | |
| const newPaletteName = response.formValues[0]?.trim(); | |
| if (!newPaletteName) { | |
| player.sendMessage("§cNew palette name cannot be empty."); | | | return; | | | } | |
| if (palettes[newPaletteName]) { | |
| player.sendMessage(`§cPalette named "${newPaletteName}" already exists.`); | |
| return; | |
| } | |
| if (newPaletteName === activeSavedName) { | |
| player.sendMessage(§cNew name must be different from the original.); | |
| return; | |
| } | |
| const copiedData = JSON.parse(JSON.stringify(paletteToCopy)); | |
| if (addPlayerPalette(player, newPaletteName, copiedData)) { | |
| player.sendMessage(`§aPalette "${activeSavedName}" duplicated as "${newPaletteName}".`); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Duplicate Palette] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError duplicating palette."); | |
| } | | | } | |
| async function showDeletePaletteMenu(player) { | |
| const palettes = getPlayerPalettes(player); | |
| const paletteNames = Object.keys(palettes); | |
| const form = new ActionFormData().title("Delete Saved Palette"); | |
| if (paletteNames.length === 0) { | |
| form.body("No saved palettes to delete."); | |
| } else { | |
| form.body("Select a saved palette to delete:"); | |
| paletteNames.forEach(name => form.button(name)); | |
| } | | | form.button("§c[Back]"); | | | try { | | | const response = await form.show(player); | | | const backIndex = paletteNames.length; | | | if (response.canceled || response.selection === undefined || response.selection === backIndex) { | | | return; | | | } | |
| if (response.selection < backIndex) { | |
| const selectedName = paletteNames[response.selection]; | |
| const confirmForm = new ModalFormData() | |
| .title("Confirm Deletion") | |
| .toggle(`Delete your saved palette "${selectedName}"? This cannot be undone.`, { defaultValue: false }) | |
| .submitButton("Confirm Delete"); | |
| const confirmResponse = await confirmForm.show(player); | |
| if (!confirmResponse.canceled && confirmResponse.formValues?.[0] === true) { | |
| if (deletePlayerPalette(player, selectedName)) { | |
| player.sendMessage(`§aSaved palette "${selectedName}" deleted.`); | |
| } | |
| } else { | |
| player.sendMessage("§eDeletion cancelled."); | |
| } | | | } | |
| } catch (e) { | |
| console.error(`[WorldEdit Delete Palette] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError showing/processing delete palette menu."); | | | } | | | } | | | export async function showCreatePaletteFromSelectionForm(player, uniqueBlockIds) { | | | if (!uniqueBlockIds || uniqueBlockIds.length === 0) { | | | player.sendMessage("§cCannot create palette: No blocks provided for form."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | return; | | | } | |
| const form = new ModalFormData().title("Create Palette from Selection"); | |
| form.textField("New Palette Name", "Enter a unique name", { defaultValue: "" }); | |
| const formIndexToBlockId = {}; let currentIndex = 1; | |
| for (const blockId of uniqueBlockIds) { const simpleName = blockId.replace('minecraft:', ''); form.toggle(Include ${simpleName}?, { defaultValue: true }); formIndexToBlockId[currentIndex] = { type: 'toggle', blockId: blockId }; currentIndex++; form.slider(Weight (${simpleName}), 1, 100, { step: 1, defaultValue: 1 }); formIndexToBlockId[currentIndex] = { type: 'slider', blockId: blockId }; currentIndex++; } | |
| form.submitButton("Create Palette"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§ePalette creation cancelled."); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const formValues = response.formValues; const newPaletteName = formValues[0]?.trim(); | |
| if (!newPaletteName) { | |
| player.sendMessage("§cPalette name cannot be empty. Not saved."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | return; | | | } | |
| const existingPalettes = getPlayerPalettes(player); | |
| if (existingPalettes[newPaletteName]) { | |
| player.sendMessage(`§cPalette named "${newPaletteName}" already exists. Not saved.`); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const newPaletteData = []; | |
| for (let i = 1; i < formValues.length; i++) { | |
| const mapping = formIndexToBlockId[i]; | |
| if (mapping?.type === 'toggle') { | |
| const includeBlock = formValues[i]; | |
| const sliderIndex = i + 1; | |
| const weightMapping = formIndexToBlockId[sliderIndex]; | |
| if (includeBlock && weightMapping?.type === 'slider' && weightMapping?.blockId === mapping.blockId) { | |
| const weight = formValues[sliderIndex]; | |
| newPaletteData.push({ id: mapping.blockId, weight: weight }); | |
| } | | | i++; | | | } | | | } | | | if (newPaletteData.length === 0) { | | | player.sendMessage("§cNo blocks were included in the palette. Not saved."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | return; | | | } | |
| if (addPlayerPalette(player, newPaletteName, newPaletteData)) { | |
| player.sendMessage(`§aPalette "${newPaletteName}" created and saved successfully.`); | |
| } | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } catch (e) { | |
| console.error(`[Create Palette from Selection Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing create palette form."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | } | | | } |
| import { Player } from "@minecraft/server"; | |
| import { ModalFormData } from "@minecraft/server-ui"; | |
| import { | | | getPlayerSelection, | | | setPlayerSelection, | | | getPlayerPalettes, | | | getActiveSavedPaletteName | | | } from "../state/index.js"; | | | import { | | | MAX_BRUSH_SIZE, | | | DEFAULT_BRUSH_MATERIAL, | | | DEFAULT_PAINT_MASK_BLOCK, | | | MAX_HEIGHT_BRUSH_AMOUNT, | | | DEFAULT_HEIGHT_BRUSH_AMOUNT, | | | MAX_FOLIAGE_RADIUS, | | | DEFAULT_FOLIAGE_RADIUS, | | | DEFAULT_FOLIAGE_DENSITY, | | | DEFAULT_FOLIAGE_SOURCE, | | | DEFAULT_FOLIAGE_PRESET, | | | DEFAULT_FOLIAGE_CUSTOM, | | | FOLIAGE_PRESETS | | | } from "../constants/index.js"; | | | import { parseWeightedBlockInput, validateBlockIds } from "../utils/index.js"; | | | function getBrushShapeOptions() { | | | const shapeMap = { "Sphere": 'sphere', "Cube": 'cube', "Cylinder": 'cylinder', "Pyramid": 'pyramid', "Octahedron": 'octahedron', "Dome": 'dome', "Bowl": 'bowl', "Cone (Point Up)": 'cone_up', "Cone (Point Down)": 'cone_down', "Disk": 'disk', "Plate": 'plate', "Torus": 'torus' }; | |
| const reverseShapeMap = Object.fromEntries(Object.entries(shapeMap).map(([key, value]) => [value, key])); | |
| const shapeOptions = Object.keys(shapeMap); | |
| return { shapeOptions, shapeMap, reverseShapeMap }; | |
| } | | | export function buildSourceDropdownOptions(player, currentSourceValue, includePresets = false, includeCustom = true) { | |
| const selectionData = getPlayerSelection(player); | |
| const palettes = getPlayerPalettes(player); | |
| const savedPaletteNames = Object.keys(palettes).sort(); | |
| const sourceOptions = []; | |
| const sourceValueMap = {}; | |
| const reverseSourceMap = {}; | |
| if (includeCustom) { | |
| sourceOptions.push("Manual Input"); | |
| sourceValueMap["Manual Input"] = 'manual'; | |
| reverseSourceMap['manual'] = "Manual Input"; | |
| } | |
| if (includePresets) { | |
| sourceOptions.push("Preset"); | |
| sourceValueMap["Preset"] = 'preset'; | |
| reverseSourceMap['preset'] = "Preset"; | |
| } | | | const activePaletteData = selectionData.activePaletteData; | |
| const activeStatus = activePaletteData ? ` (${activePaletteData.length} items${selectionData.activePaletteIsDirty ? ' §c*' : ''})` : ' (Empty)'; | |
| const activeOptionLabel = `Active Palette${activeStatus}`; | |
| sourceOptions.push(activeOptionLabel); | |
| sourceValueMap[activeOptionLabel] = 'active'; | |
| reverseSourceMap['active'] = activeOptionLabel; | |
| savedPaletteNames.forEach(name => { | |
| const label = `Saved: ${name}`; | |
| sourceOptions.push(label); | |
| sourceValueMap[label] = name; | |
| reverseSourceMap[name] = label; | |
| }); | |
| let defaultStateValue = includeCustom ? 'manual' : (includePresets ? 'preset' : 'active'); | |
| let currentSourceIndex = sourceOptions.indexOf(reverseSourceMap[defaultStateValue]); | |
| if (currentSourceIndex < 0) currentSourceIndex = 0; | |
| const currentSourceLabel = reverseSourceMap[currentSourceValue]; | |
| if (currentSourceLabel && sourceOptions.includes(currentSourceLabel)) { | |
| currentSourceIndex = sourceOptions.indexOf(currentSourceLabel); | |
| } else if (currentSourceValue && !['manual', 'active', 'preset', 'custom'].includes(currentSourceValue)) { | |
| console.warn(`Source '${currentSourceValue}' not found in options, defaulting.`); | |
| } | |
| if (currentSourceValue === 'active') { | |
| currentSourceIndex = sourceOptions.findIndex(opt => opt.startsWith('Active Palette')); | |
| if (currentSourceIndex < 0) currentSourceIndex = 0; | |
| } | | | return { sourceOptions, sourceValueMap, currentSourceIndex }; | | | } | | | export async function showRegularBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentShape = selectionData.brushShape ?? 'sphere'; | | | const currentSize = selectionData.brushSize; | | | const currentHollow = selectionData.brushHollow; | | | const currentSourceValue = selectionData.brushMaterialSource ?? 'manual'; | | | const currentManualMaterial = selectionData.brushManualMaterial; | |
| const { shapeOptions, shapeMap, reverseShapeMap } = getBrushShapeOptions(); | |
| const currentShapeIndex = shapeOptions.indexOf(reverseShapeMap[currentShape] ?? shapeOptions[0]); | |
| const { sourceOptions, sourceValueMap, currentSourceIndex } = buildSourceDropdownOptions(player, currentSourceValue); | |
| const form = new ModalFormData().title("Regular Brush Settings") | |
| .dropdown("Shape", shapeOptions, { defaultValueIndex: currentShapeIndex }) | |
| .slider("Size (Radius/Half-Width)", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSize }) | |
| .toggle("Hollow Shape", { defaultValue: currentHollow }) | |
| .dropdown("Material Source", sourceOptions, { defaultValueIndex: currentSourceIndex }) | |
| .textField("Manual Blocks (id:wt,...)", "Used if Source is 'Manual Input'", { defaultValue: currentManualMaterial }) | |
| .submitButton("Update Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eRegular Brush settings unchanged."); | | | return; | | | } | | | let formIndex = 0; | | | const formVals = response.formValues; | |
| const newShapeName = shapeOptions[formVals[formIndex++]]; | |
| const newSize = formVals[formIndex++]; | |
| const newHollow = formVals[formIndex++]; | |
| const selectedOptionLabel = sourceOptions[formVals[formIndex++]]; | |
| const newManualMaterialInput = formVals[formIndex++].trim(); | |
| const newShape = shapeMap[newShapeName] ?? 'sphere'; | |
| const newSourceValue = sourceValueMap[selectedOptionLabel] ?? 'manual'; | |
| let manualInputIsValid = true; | |
| let finalManualMaterialString = currentManualMaterial; | |
| if (newSourceValue === 'manual') { | |
| if (!newManualMaterialInput) { manualInputIsValid = false; player.sendMessage("§cManual Blocks cannot be empty..."); } | |
| else { | |
| const parsedResult = parseWeightedBlockInput(newManualMaterialInput); | |
| if (parsedResult.errors.length > 0 || parsedResult.blocks.length === 0) { manualInputIsValid = false; player.sendMessage("§cError parsing manual blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedResult.blocks.map(b => b.id)); if (invalidIds.length > 0) { manualInputIsValid = false; player.sendMessage(§cInvalid IDs: ${invalidIds.join(',')}); } else { finalManualMaterialString = newManualMaterialInput; } } | |
| } | |
| if (!manualInputIsValid) { player.sendMessage("§eRegular Brush settings NOT updated due to invalid manual input."); return; } | |
| } | |
| if (newSourceValue === 'active' && (!selectionData.activePaletteData || selectionData.activePaletteData.length === 0)) { | |
| player.sendMessage("§cActive Palette is empty. Select 'Manual Input' or load/create a palette first. Settings not updated."); return; | |
| } | |
| if (!['manual', 'active'].includes(newSourceValue)) { | |
| const palettes = getPlayerPalettes(player); | |
| if (!palettes[newSourceValue]) { | |
| player.sendMessage(§cSelected saved palette "${newSourceValue}" no longer exists. Settings not updated.); return; | |
| } | |
| } | |
| selectionData.brushShape = newShape; | |
| selectionData.brushSize = newSize; | |
| selectionData.brushHollow = newHollow; | |
| selectionData.brushMaterialSource = newSourceValue; | |
| if (newSourceValue === 'manual') { | |
| selectionData.brushManualMaterial = finalManualMaterialString; | |
| } | |
| setPlayerSelection(player, selectionData); | |
| const sourceDesc = selectedOptionLabel; | |
| const hollowDesc = newHollow ? '(Hollow)' : ''; | |
| player.sendMessage(`§aRegular Brush updated: ${newShapeName}${hollowDesc}, Size ${newSize}, Src ${sourceDesc}`); | |
| } catch (e) { | |
| console.error(`[Regular Brush Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing regular brush settings form."); | | | } | | | } | | | export async function showPaintBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentShape = selectionData.brushShape ?? 'sphere'; | | | const currentSize = selectionData.brushSize; | | | const currentSourceValue = selectionData.brushMaterialSource ?? 'manual'; | | | const currentManualMaterial = selectionData.brushManualMaterial; | | | const currentMaskEnabled = selectionData.paintBrushMaskEnabled; | | | const currentMaskInput = selectionData.paintBrushMaskInput; | |
| const { shapeOptions, shapeMap, reverseShapeMap } = getBrushShapeOptions(); | |
| const currentShapeIndex = shapeOptions.indexOf(reverseShapeMap[currentShape] ?? shapeOptions[0]); | |
| const { sourceOptions, sourceValueMap, currentSourceIndex } = buildSourceDropdownOptions(player, currentSourceValue); | |
| const form = new ModalFormData().title("Paint Brush Settings") | |
| .dropdown("Shape", shapeOptions, { defaultValueIndex: currentShapeIndex }) | |
| .slider("Size (Radius/Half-Width)", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSize }) | |
| .dropdown("Paint Material Source", sourceOptions, { defaultValueIndex: currentSourceIndex }) | |
| .textField("Manual Paint Blocks (id:wt,...)", "Used if Source is 'Manual Input'", { defaultValue: currentManualMaterial }) | |
| .toggle("Enable Masking", { defaultValue: currentMaskEnabled }) | |
| .textField("Mask Blocks (IDs, comma-sep)", "e.g. stone,dirt,grass", { defaultValue: currentMaskInput }) | |
| .submitButton("Update Paint Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§ePaint Brush settings unchanged."); | | | return; | | | } | | | let formIndex = 0; | | | const formVals = response.formValues; | |
| const newShapeName = shapeOptions[formVals[formIndex++]]; | |
| const newSize = formVals[formIndex++]; | |
| const selectedOptionLabel = sourceOptions[formVals[formIndex++]]; | |
| const newManualMaterialInput = formVals[formIndex++].trim(); | |
| const newMaskEnabled = formVals[formIndex++]; | |
| const newMaskInput = formVals[formIndex++].trim(); | |
| const newShape = shapeMap[newShapeName] ?? 'sphere'; | |
| const newSourceValue = sourceValueMap[selectedOptionLabel] ?? 'manual'; | |
| let manualInputIsValid = true; | |
| let finalManualMaterialString = currentManualMaterial; | |
| if (newSourceValue === 'manual') { | |
| if (!newManualMaterialInput) { manualInputIsValid = false; player.sendMessage("§cManual Paint Blocks cannot be empty..."); } | |
| else { | |
| const parsedResult = parseWeightedBlockInput(newManualMaterialInput); | |
| if (parsedResult.errors.length > 0 || parsedResult.blocks.length === 0) { manualInputIsValid = false; player.sendMessage("§cError parsing manual blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedResult.blocks.map(b => b.id)); if (invalidIds.length > 0) { manualInputIsValid = false; player.sendMessage(§cInvalid IDs: ${invalidIds.join(',')}); } else { finalManualMaterialString = newManualMaterialInput; } } | |
| } | |
| if (!manualInputIsValid) { player.sendMessage("§ePaint Brush settings NOT updated due to invalid manual input."); return; } | |
| } | |
| if (newSourceValue === 'active' && (!selectionData.activePaletteData || selectionData.activePaletteData.length === 0)) { | |
| player.sendMessage("§cActive Palette is empty. Select 'Manual Input' or load/create a palette first. Settings not updated."); return; | |
| } | |
| if (!['manual', 'active'].includes(newSourceValue)) { | |
| const palettes = getPlayerPalettes(player); | |
| if (!palettes[newSourceValue]) { | |
| player.sendMessage(§cSelected saved palette "${newSourceValue}" no longer exists. Settings not updated.); return; | |
| } | |
| } | |
| let maskInputIsValid = true; | |
| let finalMaskInputString = currentMaskInput; | |
| if (newMaskEnabled) { | |
| if (!newMaskInput) { maskInputIsValid = false; player.sendMessage("§cMask Blocks cannot be empty..."); } | |
| else { | |
| const parsedMask = parseWeightedBlockInput(newMaskInput); | |
| if (parsedMask.blocks.length === 0) { maskInputIsValid = false; player.sendMessage("§cNo valid blocks found in Mask Blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedMask.blocks.map(b => b.id)); if (invalidIds.length > 0) { maskInputIsValid = false; player.sendMessage(§cInvalid Mask IDs: ${invalidIds.join(', ')}...); } else { finalMaskInputString = newMaskInput; } } | |
| } | |
| if (!maskInputIsValid) { player.sendMessage("§ePaint Brush settings NOT updated due to invalid mask input."); return; } | |
| } | |
| selectionData.brushShape = newShape; | |
| selectionData.brushSize = newSize; | |
| selectionData.brushMaterialSource = newSourceValue; | |
| if (newSourceValue === 'manual') { | |
| selectionData.brushManualMaterial = finalManualMaterialString; | |
| } | |
| selectionData.paintBrushMaskEnabled = newMaskEnabled; | |
| if (newMaskEnabled) { | |
| selectionData.paintBrushMaskInput = finalMaskInputString; | |
| } | |
| setPlayerSelection(player, selectionData); | |
| const paintSourceDesc = selectedOptionLabel; | |
| const maskDesc = newMaskEnabled ? `ON` : 'OFF'; | |
| player.sendMessage(`§aPaint Brush updated: ${newShapeName}, Size ${newSize}, Paint ${paintSourceDesc}, Mask ${maskDesc}`); | |
| } catch (e) { | |
| console.error(`[Paint Brush Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing paint brush settings form."); | | | } | | | } | | | export async function showHeightBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentShape = selectionData.brushShape ?? 'sphere'; | | | const currentSize = selectionData.brushSize; | | | const currentAmount = selectionData.heightBrushAmount ?? DEFAULT_HEIGHT_BRUSH_AMOUNT; | | | const currentMode = selectionData.heightBrushMode ?? "Raise"; | |
| const { shapeOptions, shapeMap, reverseShapeMap } = getBrushShapeOptions(); | |
| const currentShapeIndex = shapeOptions.indexOf(reverseShapeMap[currentShape] ?? shapeOptions[0]); | |
| const modeOptions = ["Raise", "Lower"]; | |
| const currentModeIndex = modeOptions.indexOf(currentMode); | |
| const form = new ModalFormData().title("Height Brush Settings") | |
| .dropdown("Shape", shapeOptions, { defaultValueIndex: currentShapeIndex }) | |
| .slider("Size (Radius/Half-Width)", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSize }) | |
| .slider("Amount", 1, MAX_HEIGHT_BRUSH_AMOUNT, { step: 1, defaultValue: currentAmount }) | |
| .dropdown("Mode", modeOptions, { defaultValueIndex: Math.max(0, currentModeIndex) }) | |
| .submitButton("Update Height Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { player.sendMessage("§eHeight Brush settings unchanged."); return; } | |
| let formIndex = 0; | |
| const formVals = response.formValues; | |
| const newShapeName = shapeOptions[formVals[formIndex++]]; | |
| const newSize = formVals[formIndex++]; | |
| const newAmount = formVals[formIndex++]; | |
| const newModeName = modeOptions[formVals[formIndex++]]; | |
| const newShape = shapeMap[newShapeName] ?? 'sphere'; | |
| selectionData.brushShape = newShape; | | | selectionData.brushSize = newSize; | | | selectionData.heightBrushAmount = newAmount; | | | selectionData.heightBrushMode = newModeName; | |
| setPlayerSelection(player, selectionData); | |
| player.sendMessage(`§aHeight Brush updated: ${newShapeName}, Size ${newSize}, Amt ${newAmount}, Mode ${newModeName}`); | |
| } catch (e) { console.error(`[Height Brush Settings Form] Error: ${e}\n${e.stack}`); player.sendMessage("§cError processing height brush settings form."); } | |
| } | | | export async function showFoliageBrushSettingsForm(player) { | | | const selectionData = getPlayerSelection(player); | | | const currentRadius = selectionData.foliageBrushRadius ?? DEFAULT_FOLIAGE_RADIUS; | | | const currentDensity = selectionData.foliageBrushDensity ?? DEFAULT_FOLIAGE_DENSITY; | | | const currentSourceValue = selectionData.foliageBrushPlantSource ?? DEFAULT_FOLIAGE_SOURCE; | | | const currentPreset = selectionData.foliageBrushPresetType ?? DEFAULT_FOLIAGE_PRESET; | | | const currentCustom = selectionData.foliageBrushCustomInput ?? DEFAULT_FOLIAGE_CUSTOM; | | | const { sourceOptions, sourceValueMap, currentSourceIndex } = buildSourceDropdownOptions(player, currentSourceValue, true, true); | |
| const presetOptions = Object.keys(FOLIAGE_PRESETS); | |
| let currentPresetIndex = presetOptions.indexOf(currentPreset); | |
| if (currentPresetIndex === -1 || presetOptions.length === 0) { currentPresetIndex = 0; } | |
| const form = new ModalFormData().title("Foliage Brush Settings") | |
| .slider("Scatter Radius", 1, MAX_FOLIAGE_RADIUS, { step: 1, defaultValue: currentRadius }) | |
| .slider("Density (%)", 0, 100, { step: 1, defaultValue: currentDensity }) | |
| .dropdown("Plant Source", sourceOptions, { defaultValueIndex: currentSourceIndex }) | |
| .dropdown("Preset Type", presetOptions.length > 0 ? presetOptions : ["No Presets Defined"], { defaultValueIndex: currentPresetIndex }) | |
| .textField("Custom Plants (id:wt,...)", "Used if Source is 'Custom Input'", { defaultValue: currentCustom }) | |
| .submitButton("Update Foliage Brush"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eFoliage Brush settings unchanged."); | | | return; | | | } | | | const formVals = response.formValues; | |
| const newRadius = formVals[0]; | |
| const newDensity = formVals[1]; | |
| const selectedOptionLabel = sourceOptions[formVals[2]]; | |
| const newPresetName = presetOptions.length > 0 ? presetOptions[formVals[3]] : DEFAULT_FOLIAGE_PRESET; | |
| const newCustomInput = formVals[4].trim(); | |
| const newSourceValue = sourceValueMap[selectedOptionLabel] ?? DEFAULT_FOLIAGE_SOURCE; | | | let customInputIsValid = true; | | | let finalCustomInputString = currentCustom; | |
| if (newSourceValue === 'manual') { | |
| if (!newCustomInput) { customInputIsValid = false; player.sendMessage("§cCustom Plants cannot be empty..."); } | |
| else { | |
| const parsedResult = parseWeightedBlockInput(newCustomInput); | |
| if (parsedResult.errors.length > 0 || parsedResult.blocks.length === 0) { customInputIsValid = false; player.sendMessage("§cError parsing custom blocks..."); } | |
| else { const { invalidIds } = validateBlockIds(parsedResult.blocks.map(b => b.id)); if (invalidIds.length > 0) { customInputIsValid = false; player.sendMessage(§cInvalid IDs: ${invalidIds.join(',')}); } else { finalCustomInputString = newCustomInput; } } | |
| } | |
| if (!customInputIsValid) { player.sendMessage("§cFoliage Brush settings NOT updated due to invalid manual input."); return; } | |
| } | |
| if (newSourceValue === 'active' && (!selectionData.activePaletteData || selectionData.activePaletteData.length === 0)) { | |
| player.sendMessage("§cActive Palette is empty. Select another source or load/create a palette first. Settings not updated."); return; | |
| } | |
| if (!['preset', 'manual', 'active'].includes(newSourceValue)) { | |
| const palettes = getPlayerPalettes(player); | |
| if (!palettes[newSourceValue]) { | |
| player.sendMessage(§cSelected saved palette "${newSourceValue}" no longer exists. Settings not updated.); return; | |
| } | |
| } | |
| if (newSourceValue === 'preset' && presetOptions.length === 0) { | |
| player.sendMessage("§cCannot select 'Preset' source as no presets are defined. Settings not updated."); return; | |
| } | |
| selectionData.foliageBrushRadius = newRadius; | |
| selectionData.foliageBrushDensity = newDensity; | |
| selectionData.foliageBrushPlantSource = newSourceValue; | |
| selectionData.foliageBrushPresetType = newPresetName; | |
| if (newSourceValue === 'manual') { | |
| selectionData.foliageBrushCustomInput = finalCustomInputString; | |
| } | |
| setPlayerSelection(player, selectionData); | |
| const sourceDesc = selectedOptionLabel; | |
| player.sendMessage(`§aFoliage Brush updated: Radius=${newRadius}, Density=${newDensity}%, Source=${sourceDesc}`); | |
| } catch (e) { | |
| console.error(`[Foliage Brush Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing foliage brush settings form."); | | | } | | | }import { Player, system, GameMode, EquipmentSlot } from "@minecraft/server"; | | | import { ModalFormData, ActionFormData } from "@minecraft/server-ui"; | | | import { | | | getGlobalSettings, | | | saveGlobalSettings, | | | } from "../state/global_state.js"; | | | import { | | | getPlayerSelection, | | | setPlayerSelection | | | } from "../state/player_state.js"; | | | import { | | | MAX_UNDO_HISTORY, | | | MAX_BRUSH_SIZE, | | | DEFAULT_PLAYER_SPEED_MULTIPLIER, | | | MIN_PLAYER_SPEED_MULTIPLIER, | | | MAX_PLAYER_SPEED_MULTIPLIER, | | | DEFAULT_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | DEFAULT_AUTO_SPECTATOR_ENABLED, | | | DEFAULT_EXTENDED_REACH_ENABLED, | | | DEFAULT_SHOW_ACTION_FEEDBACK_MESSAGES | | | } from "../constants/settings.js"; | | | export async function showMainSettingsHubForm(player) { | |
| const form = new ActionFormData() | |
| .title("Addon Settings") | |
| .body("Choose the type of settings you want to configure.") | | | .button("World Settings", "textures/ui/world_glyph_color_2x") | | | .button("Player Settings", "textures/ui/icon_steve") | | | .button("Cancel", "textures/ui/cancel"); | | | try { | |
| const response = await form.show(player); | |
| if (response.canceled || response.selection === undefined) { | |
| return; | | | } | | | switch (response.selection) { | | | case 0: | | | await showWorldSettingsForm(player); | | | break; | | | case 1: | | | await showPlayerSettingsForm(player); | | | break; | | | case 2: | | | return; | | | } | |
| } catch (e) { | |
| console.error(`[Main Settings Hub Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError opening settings hub."); | |
| } | | | } | |
| async function showWorldSettingsForm(player) { | |
| const currentSettings = getGlobalSettings(); | |
| const form = new ModalFormData() | |
| .title("World Settings (Global)") | |
| .toggle("Show Action Bar Info", { defaultValue: currentSettings.showActionBarInfo }) | |
| .textField("Outline Particle ID", "e.g., minecraft:basic_flame_particle", { defaultValue: currentSettings.outlineParticleId }) | |
| .slider("Max Undo Steps (per player)", 0, 100, { step: 1, defaultValue: currentSettings.maxUndoHistory }) | |
| .slider("Default Brush Size", 1, MAX_BRUSH_SIZE, { step: 1, defaultValue: currentSettings.defaultBrushSize }) | |
| .submitButton("Save World Settings"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eWorld settings unchanged."); | |
| system.run(() => showMainSettingsHubForm(player)); | |
| return; | | | } | | | const formVals = response.formValues; | |
| const newSettings = { ...currentSettings }; | |
| let validationErrors = []; | |
| newSettings.showActionBarInfo = formVals[0]; | |
| const newParticleId = formVals[1].trim(); | |
| if (!newParticleId) { | |
| validationErrors.push("Outline Particle ID cannot be empty."); | |
| } else { | |
| newSettings.outlineParticleId = newParticleId.includes(':') ? newParticleId : `minecraft:${newParticleId}`; | |
| } | |
| const newMaxUndo = formVals[2]; | |
| if (typeof newMaxUndo === 'number' && Number.isInteger(newMaxUndo)) { | |
| newSettings.maxUndoHistory = Math.max(0, Math.min(1000, newMaxUndo)); | |
| } else { | |
| validationErrors.push("Invalid value for Max Undo Steps."); | | | } | |
| const newDefaultBrushSize = formVals[3]; | |
| if (typeof newDefaultBrushSize === 'number' && Number.isInteger(newDefaultBrushSize)) { | |
| newSettings.defaultBrushSize = Math.max(1, Math.min(MAX_BRUSH_SIZE, newDefaultBrushSize)); | |
| } else { | |
| validationErrors.push("Invalid value for Default Brush Size."); | | | } | |
| if (validationErrors.length > 0) { | |
| player.sendMessage("§cInvalid input:\n" + validationErrors.join("\n") + "\n§eWorld settings not saved."); | |
| } else { | |
| if (saveGlobalSettings(newSettings)) { | |
| player.sendMessage("§aGlobal World settings saved successfully."); | | | } else { | | | player.sendMessage("§cFailed to save global World settings. Check server logs."); | | | } | | | } | |
| system.run(() => showMainSettingsHubForm(player)); | |
| } catch (e) { | |
| console.error(`[World Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError displaying or processing World settings form."); | | | system.run(() => showMainSettingsHubForm(player)); | | | } | | | } | |
| async function showPlayerSettingsForm(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const currentPlayerSpeedMultiplier = selectionData.playerSpeedMultiplier ?? DEFAULT_PLAYER_SPEED_MULTIPLIER; | | | const currentPlayerVerticalSpeedMultiplier = selectionData.playerVerticalSpeedMultiplier ?? DEFAULT_PLAYER_VERTICAL_SPEED_MULTIPLIER; | | | const currentAutoSpectatorEnabled = selectionData.autoSpectatorEnabled ?? DEFAULT_AUTO_SPECTATOR_ENABLED; | | | const currentExtendedReachEnabled = selectionData.extendedReachEnabled ?? DEFAULT_EXTENDED_REACH_ENABLED; | | | const currentShowActionFeedbackMessages = selectionData.showActionFeedbackMessages ?? DEFAULT_SHOW_ACTION_FEEDBACK_MESSAGES; | |
| const form = new ModalFormData() | |
| .title("Player Settings") | |
| .slider( | | | "Creative Horizontal Speed Multiplier", | | | MIN_PLAYER_SPEED_MULTIPLIER, | | | MAX_PLAYER_SPEED_MULTIPLIER, | | | { step: 0.1, defaultValue: currentPlayerSpeedMultiplier } | | | ) | | | .slider( | | | "Creative Vertical Speed Multiplier (Flying)", | | | MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER, | | | { step: 0.1, defaultValue: currentPlayerVerticalSpeedMultiplier } | | | ) | | | .toggle( | | | "Auto Spectator Near Walls (Creative Flying)", | | | { defaultValue: currentAutoSpectatorEnabled } | | | ) | | | .toggle( | | | "Enable Extended Reach Placement", | | | { defaultValue: currentExtendedReachEnabled } | | | ) | | | .toggle( | | | "Show Action Feedback Messages", | | | { defaultValue: currentShowActionFeedbackMessages } | | | ) | | | .submitButton("Save Player Settings"); | | | try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§ePlayer settings unchanged."); | |
| system.run(() => showMainSettingsHubForm(player)); | |
| return; | | | } | | | const formVals = response.formValues; | |
| const newSpeedMultiplier = formVals[0]; | |
| const newVerticalSpeedMultiplier = formVals[1]; | |
| const newAutoSpectatorEnabled = formVals[2]; | |
| const newExtendedReachEnabled = formVals[3]; | |
| const newShowActionFeedbackMessages = formVals[4]; | |
| let settingsChanged = false; | |
| let messages = []; | |
| if (typeof newSpeedMultiplier === 'number' && | |
| newSpeedMultiplier >= MIN_PLAYER_SPEED_MULTIPLIER && | |
| newSpeedMultiplier <= MAX_PLAYER_SPEED_MULTIPLIER) { | |
| if (selectionData.playerSpeedMultiplier !== parseFloat(newSpeedMultiplier.toFixed(2))) { | |
| selectionData.playerSpeedMultiplier = parseFloat(newSpeedMultiplier.toFixed(2)); | |
| settingsChanged = true; | |
| } | |
| } else { | |
| messages.push(§cInvalid horizontal speed multiplier. Must be between ${MIN_PLAYER_SPEED_MULTIPLIER} and ${MAX_PLAYER_SPEED_MULTIPLIER}. Horizontal speed not changed.); | |
| } | |
| if (typeof newVerticalSpeedMultiplier === 'number' && | |
| newVerticalSpeedMultiplier >= MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER && | |
| newVerticalSpeedMultiplier <= MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER) { | |
| if (selectionData.playerVerticalSpeedMultiplier !== parseFloat(newVerticalSpeedMultiplier.toFixed(2))) { | |
| selectionData.playerVerticalSpeedMultiplier = parseFloat(newVerticalSpeedMultiplier.toFixed(2)); | |
| settingsChanged = true; | |
| } | |
| } else { | |
| messages.push(§cInvalid vertical speed multiplier. Must be between ${MIN_PLAYER_VERTICAL_SPEED_MULTIPLIER} and ${MAX_PLAYER_VERTICAL_SPEED_MULTIPLIER}. Vertical speed not changed.); | |
| } | |
| if (typeof newAutoSpectatorEnabled === 'boolean') { | |
| if (selectionData.autoSpectatorEnabled !== newAutoSpectatorEnabled) { | |
| selectionData.autoSpectatorEnabled = newAutoSpectatorEnabled; | | | settingsChanged = true; | | | } | | | } else { | | | messages.push("§cInvalid value for Auto Spectator toggle. Setting not changed."); | | | } | |
| if (typeof newExtendedReachEnabled === 'boolean') { | |
| if (selectionData.extendedReachEnabled !== newExtendedReachEnabled) { | |
| selectionData.extendedReachEnabled = newExtendedReachEnabled; | | | settingsChanged = true; | | | } | | | } else { | | | messages.push("§cInvalid value for Extended Reach toggle. Setting not changed."); | | | } | |
| if (typeof newShowActionFeedbackMessages === 'boolean') { | |
| if (selectionData.showActionFeedbackMessages !== newShowActionFeedbackMessages) { | |
| selectionData.showActionFeedbackMessages = newShowActionFeedbackMessages; | | | settingsChanged = true; | | | } | | | } else { | | | messages.push("§cInvalid value for Show Action Feedback Messages toggle. Setting not changed."); | | | } | |
| if (settingsChanged) { | |
| setPlayerSelection(player, selectionData); | |
| messages.unshift(§aPlayer settings updated: HSpeed=${selectionData.playerSpeedMultiplier}x, VSpeed=${selectionData.playerVerticalSpeedMultiplier}x, AutoSpec=${selectionData.autoSpectatorEnabled ? 'ON' : 'OFF'}, ExtReach=${selectionData.extendedReachEnabled ? 'ON' : 'OFF'}, FeedbackMsgs=${selectionData.showActionFeedbackMessages ? 'ON' : 'OFF'}); | |
| } else if (!response.canceled && messages.length === 0) { | |
| messages.push("§eNo changes made to player settings."); | |
| } | |
| if (messages.length > 0) { | |
| player.sendMessage(messages.join('\n')); | |
| } | |
| system.run(() => showMainSettingsHubForm(player)); | |
| } catch (e) { | |
| console.error(`[Player Settings Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError displaying or processing Player settings form."); | | | system.run(() => showMainSettingsHubForm(player)); | | | } | |
| }import { Player, system } from "@minecraft/server"; | |
| import { ActionFormData, ModalFormData } from "@minecraft/server-ui"; | |
| import { | | | getPlayerSelection, | | | getPlayerPalettes, | | | savePlayerPalettes, | | | addPlayerPalette, | | | deletePlayerPalette, | | | setActiveSavedPaletteName, | | | getActiveSavedPaletteName, | | | setPlayerSelection, | | | discardActivePaletteChanges | | | } from "../state/index.js"; | | | import { parseWeightedBlockInput, validateBlockIds } from "../utils/index.js"; | | | import { showEditMenu } from "./main_menu.js"; | | | import { analyzeSelectionForPalette } from "../core/analyze_selection.js"; | |
| export async function showPaletteMenu(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const activeSaved = getActiveSavedPaletteName(player); | |
| const activePaletteData = selectionData.activePaletteData; | | | const isDirty = !!selectionData.activePaletteIsDirty; | | | const activeItemsCount = activePaletteData?.length ?? 0; | |
| const activeStatus = activePaletteData ? ` (${activeItemsCount} items${isDirty ? ' §c*' : ''})` : ' (Empty/Inactive)'; | |
| const hasCompleteSelection = selectionData.selectionState === 2; | |
| const form = new ActionFormData() | |
| .title("Manage Palettes") | |
| .body(`§eActive Palette:§r ${activeStatus}\n§eLoaded Saved Palette:§r ${activeSaved ? `§b${activeSaved}` : "§7None"}`) | |
| .button("Load Saved Palette", "textures/ui/book_glyph_color") | |
| .button("Edit Saved Palette", "textures/ui/book_edit_default") | |
| .button("Save Options...", "textures/ui/icon_save") | |
| .button(Create from Selection... ${hasCompleteSelection ? '' : '§7(Needs Sel)'}, "textures/ui/icon_import") | |
| .button("Duplicate Loaded Saved Palette", "textures/ui/copy") | |
| .button("Delete Saved Palette", "textures/ui/trash") | |
| .button("Back", "textures/ui/cancel"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || response.selection === undefined) return; | |
| let refreshMenu = true; | |
| switch (response.selection) { | |
| case 0: await showLoadPaletteList(player); break; | |
| case 1: await showEditPaletteList(player); break; | |
| case 2: await showSaveOptionsMenu(player); refreshMenu = false; break; | |
| case 3: | |
| if (hasCompleteSelection) { | |
| const uniqueBlockIds = await analyzeSelectionForPalette(player); | |
| if (uniqueBlockIds) { | |
| await showCreatePaletteFromSelectionForm(player, uniqueBlockIds); | |
| refreshMenu = false; | |
| } else { | |
| refreshMenu = true; | |
| } | | | } else { | | | player.sendMessage("§cComplete selection needed to create palette."); | | | refreshMenu = true; | | | } | | | break; | |
| case 4: await duplicateActiveSavedPalette(player); break; | |
| case 5: await showDeletePaletteMenu(player); break; | |
| case 6: | |
| refreshMenu = false; | |
| await system.run(async () => { await showEditMenu(player); }); | |
| return; | | | } | |
| if (refreshMenu) { | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Palette Menu] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError displaying palette menu."); | | | } | | | } | |
| async function showLoadPaletteList(player) { | |
| const palettes = getPlayerPalettes(player); | |
| const paletteNames = Object.keys(palettes); | |
| const currentSelection = getPlayerSelection(player); | |
| const isDirty = !!currentSelection.activePaletteIsDirty; | |
| const form = new ActionFormData().title("Load Saved Palette"); | |
| let body = ""; | |
| if (isDirty) { | |
| body += "§cWarning: will discard unsaved Active Palette changes!§r\n"; | | | } | | | body += "Select saved palette to load into Active Palette:"; | |
| form.body(body); | |
| if (paletteNames.length === 0) { | |
| form.body(body + "\n§eNo saved palettes available.§r"); | |
| } else { | |
| paletteNames.forEach(name => form.button(name)); | |
| } | | | form.button("§c[Cancel]"); | | | try { | | | const response = await form.show(player); | | | const cancelIndex = paletteNames.length; | | | if (response.canceled || response.selection === undefined || response.selection === cancelIndex) { | | | return; | | | } | |
| if (response.selection < cancelIndex) { | |
| const selectedName = paletteNames[response.selection]; | |
| if (isDirty) { | |
| const confirmForm = new ModalFormData() | |
| .title("Discard Unsaved Changes?") | |
| .toggle(` "${selectedName}" will discard unsaved changes in the Active Palette. Continue?`, { defaultValue: false }) | |
| .submitButton("Confirm Load"); | |
| const confirmResponse = await confirmForm.show(player); | |
| if (confirmResponse.canceled || !confirmResponse.formValues || !confirmResponse.formValues[0]) { | | | player.sendMessage("§eLoad cancelled."); | | | return; | | | } | | | } | |
| const paletteDataToLoad = palettes[selectedName]; | |
| if (!paletteDataToLoad) { | |
| player.sendMessage(`§cError: Palette "${selectedName}" data not found.`); | |
| return; | |
| } | |
| currentSelection.activePaletteData = JSON.parse(JSON.stringify(paletteDataToLoad)); | |
| currentSelection.activeSavedPaletteName = selectedName; | |
| currentSelection.activePaletteIsDirty = false; | |
| setPlayerSelection(player, currentSelection); | |
| player.sendMessage(§aLoaded "${selectedName}" into Active Palette and set as loaded saved palette.); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Load Palette List] Error: ${e}\n${e.stack}`); | |
| player.sendMessage(§cError displaying load palette list.); | |
| } | |
| } | |
| async function showEditPaletteList(player) { | |
| const palettes = getPlayerPalettes(player); | |
| const paletteNames = Object.keys(palettes); | |
| const form = new ActionFormData().title("Edit Saved Palette"); | |
| form.body("Select saved palette to edit directly:"); | |
| if (paletteNames.length === 0) { | |
| form.body("§eNo saved palettes available to edit.§r"); | |
| } else { | |
| paletteNames.forEach(name => form.button(name)); | |
| } | | | form.button("§c[Cancel]"); | | | try { | | | const response = await form.show(player); | | | const cancelIndex = paletteNames.length; | | | if (response.canceled || response.selection === undefined || response.selection === cancelIndex) { | | | return; | | | } | |
| if (response.selection < cancelIndex) { | |
| const paletteNameToEdit = paletteNames[response.selection]; | |
| system.run(async () => { | |
| await editTextPalette(player, paletteNameToEdit); | |
| }); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Edit Palette List] Error: ${e}\n${e.stack}`); | |
| player.sendMessage(§cError selecting palette to edit.); | |
| } | |
| } | |
| async function editTextPalette(player, paletteName) { | |
| const palettes = getPlayerPalettes(player); | |
| const currentPaletteData = palettes[paletteName]; | |
| if (!currentPaletteData) { | |
| player.sendMessage(`§cPalette "${paletteName}" not found (maybe deleted?).`); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const currentBlocksString = currentPaletteData.map(b => `${b.id.replace('minecraft:', '')}:${b.weight}`).join(', '); | |
| const form = new ModalFormData() | |
| .title(`Edit Saved Palette: ${paletteName}`) | |
| .textField("Palette Name", "Edit name (must be unique)", { defaultValue: paletteName }) | |
| .textField("Blocks (format: id:weight, ...)", "Edit blocks and weights", { defaultValue: currentBlocksString }) | |
| .submitButton("Save Changes"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eEdit cancelled."); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const newPaletteName = response.formValues[0]?.trim(); | |
| const newBlockInput = response.formValues[1]?.trim(); | |
| if (!newPaletteName) { player.sendMessage("§cPalette name cannot be empty. Not saved."); } | |
| else if (!newBlockInput) { player.sendMessage("§cBlock input cannot be empty. Not saved."); } | |
| else { | |
| const { blocks: newBlocksWithWeights, errors } = parseWeightedBlockInput(newBlockInput); | |
| if (errors.length > 0) { player.sendMessage(`§cInput errors:\n${errors.join('\n')}`); } | |
| else if (newBlocksWithWeights.length === 0) { player.sendMessage("§cNo valid blocks parsed. Palette not saved."); } | |
| else { | |
| const { invalidIds } = validateBlockIds(newBlocksWithWeights.map(b => b.id)); | |
| if (invalidIds.length > 0) { player.sendMessage(`§cInvalid block ID(s) resolved: ${invalidIds.join(', ')}. Palette not saved.`); } | |
| else { | |
| const updatedPalettes = getPlayerPalettes(player); | |
| if (newPaletteName !== paletteName && updatedPalettes[newPaletteName]) { | |
| player.sendMessage(§cAnother palette named "${newPaletteName}" already exists. Cannot rename/save.); | |
| } else { | |
| if (newPaletteName !== paletteName) { | |
| delete updatedPalettes[paletteName]; | |
| } | |
| updatedPalettes[newPaletteName] = newBlocksWithWeights; | |
| if (savePlayerPalettes(player, updatedPalettes)) { | |
| player.sendMessage(`§aSaved palette "${newPaletteName}" updated successfully.`); | |
| const selectionData = getPlayerSelection(player); | |
| if (selectionData.activeSavedPaletteName === paletteName && newPaletteName !== paletteName) { | |
| setActiveSavedPaletteName(player, newPaletteName); | |
| player.sendMessage(`§eLoaded saved palette updated to "${newPaletteName}".`); | |
| } | | | } | | | } | | | } | | | } | | | } | |
| } catch (e) { | |
| console.error(`[WorldEdit Edit Palette Text] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError editing palette."); | |
| } | | | await system.run(async () => { await showPaletteMenu(player); }); | | | } | |
| async function showSaveOptionsMenu(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const activeSavedName = selectionData.activeSavedPaletteName; | | | const activePaletteData = selectionData.activePaletteData; | | | const isDirty = !!selectionData.activePaletteIsDirty; | | | const hasActiveData = !!activePaletteData && activePaletteData.length > 0; | |
| const form = new ActionFormData().title("Save Options"); | |
| let body = `§eActive Palette Status:§r ${hasActiveData ? `${activePaletteData.length} items` : 'Empty'}${isDirty ? ' §c(Unsaved Changes)' : ' (No Changes)'}\n`; | |
| body += `§eLoaded Saved Palette:§r ${activeSavedName ? `§b${activeSavedName}` : "§7None"}`; | |
| form.body(body); | |
| let buttonIndexMap = {}; | |
| let currentButtonIndex = 0; | |
| if (hasActiveData) { | |
| form.button("Save Active Palette as New..."); | |
| buttonIndexMap[currentButtonIndex++] = "save_as"; | |
| } else { | |
| form.button("Save Active Palette as New... §7(Empty)"); | |
| buttonIndexMap[currentButtonIndex++] = "save_as_disabled"; | |
| } | |
| if (activeSavedName && isDirty && hasActiveData) { | |
| form.button(`Update Saved '${activeSavedName}'`); | |
| buttonIndexMap[currentButtonIndex++] = "update_active"; | |
| } else { | |
| form.button(`Update Loaded Saved Palette §7(N/A)`); | |
| buttonIndexMap[currentButtonIndex++] = "update_disabled"; | |
| } | |
| if (hasActiveData || isDirty) { | |
| form.button("Discard Active Palette Changes"); | |
| buttonIndexMap[currentButtonIndex++] = "discard"; | |
| } else { | |
| form.button("Discard Active Palette Changes §7(N/A)"); | |
| buttonIndexMap[currentButtonIndex++] = "discard_disabled"; | |
| } | |
| form.button("Back"); | |
| buttonIndexMap[currentButtonIndex++] = "back"; | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || response.selection === undefined) { | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const action = buttonIndexMap[response.selection]; | |
| let returnToMainMenu = true; | |
| switch (action) { | |
| case "save_as": await saveActivePaletteAs(player); break; | |
| case "save_as_disabled": | | | player.sendMessage("§cActive Palette is empty, cannot 'Save As'."); | |
| returnToMainMenu = false; | |
| await system.run(async () => { await showSaveOptionsMenu(player); }); | |
| break; | | | case "update_active": await confirmOverwriteActiveSavedPalette(player); break; | | | case "update_disabled": | | | player.sendMessage("§cCannot update: No loaded saved palette, or no unsaved changes, or Active Palette is empty."); | |
| returnToMainMenu = false; | |
| await system.run(async () => { await showSaveOptionsMenu(player); }); | |
| break; | | | case "discard": await discardActivePaletteChanges(player); break; | | | case "discard_disabled": | | | player.sendMessage("§eNo Active Palette changes or data to discard."); | |
| returnToMainMenu = false; | |
| await system.run(async () => { await showSaveOptionsMenu(player); }); | |
| break; | |
| case "back": break; | |
| default: console.warn(`Invalid action selected in Save Options Menu: ${response.selection}`); break; | |
| } | |
| if (returnToMainMenu) { | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Save Options Menu] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError in save options menu."); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } | | | } | |
| async function saveActivePaletteAs(player) { | |
| const selectionData = getPlayerSelection(player); | |
| const activePaletteData = selectionData.activePaletteData; | | | if (!activePaletteData || activePaletteData.length === 0) { | | | player.sendMessage("§cActive Palette is empty. Add blocks using Picker first."); | | | return; | | | } | | | const form = new ModalFormData() | | | .title("Save Active Palette As...") | |
| .textField("New Palette Name", "Enter a unique name", { defaultValue: "" }) | |
| .submitButton("Save Palette"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eSave cancelled."); | |
| return; | | | } | |
| const newPaletteName = response.formValues[0]?.trim(); | |
| if (!newPaletteName) { | |
| player.sendMessage("§cPalette name cannot be empty."); | | | return; | | | } | |
| const existingPalettes = getPlayerPalettes(player); | |
| if (existingPalettes[newPaletteName]) { | |
| player.sendMessage(`§cPalette named "${newPaletteName}" already exists.`); | |
| return; | | | } | |
| if (addPlayerPalette(player, newPaletteName, activePaletteData)) { | |
| player.sendMessage(`§aActive Palette saved as "${newPaletteName}".`); | |
| selectionData.activeSavedPaletteName = newPaletteName; | | | selectionData.activePaletteIsDirty = false; | | | setPlayerSelection(player, selectionData); | | | } | |
| } catch (e) { | |
| console.error(`[WorldEdit Save Active As] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError saving palette."); | |
| } | | | } | | | async function confirmOverwriteActiveSavedPalette(player) { | | | const selectionData = getPlayerSelection(player); | | | const activePaletteData = selectionData.activePaletteData; | | | const activeSavedName = selectionData.activeSavedPaletteName; | | | const isDirty = !!selectionData.activePaletteIsDirty; | |
| if (!activeSavedName) { player.sendMessage("§cNo loaded saved palette set to overwrite."); return; } | |
| if (!activePaletteData || activePaletteData.length === 0) { player.sendMessage("§cActive Palette is empty. Cannot overwrite."); return; } | |
| if (!isDirty) { player.sendMessage("§eNo unsaved changes in Active Palette to overwrite."); return; } | |
| const form = new ModalFormData() | |
| .title("Overwrite Saved Palette?") | |
| .toggle(Overwrite saved palette "${activeSavedName}" with current Active Palette changes? This cannot be undone., { defaultValue: false }) | |
| .submitButton("Overwrite Palette"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues || !response.formValues[0]) { | |
| player.sendMessage("§eOverwrite cancelled."); | |
| return; | |
| } | |
| if (addPlayerPalette(player, activeSavedName, activePaletteData)) { | |
| player.sendMessage(§aSaved palette "${activeSavedName}" updated with Active Palette changes.); | |
| selectionData.activePaletteIsDirty = false; | |
| setPlayerSelection(player, selectionData); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Overwrite Active Saved] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError overwriting palette."); | |
| } | | | } | |
| async function duplicateActiveSavedPalette(player) { | |
| const activeSavedName = getActiveSavedPaletteName(player); | |
| if (!activeSavedName) { | |
| player.sendMessage("§cNo loaded saved palette selected to duplicate."); | | | return; | | | } | |
| const palettes = getPlayerPalettes(player); | |
| const paletteToCopy = palettes[activeSavedName]; | |
| if (!paletteToCopy) { | |
| player.sendMessage(`§cCould not find data for loaded saved palette "${activeSavedName}".`); | |
| return; | | | } | |
| const form = new ModalFormData() | |
| .title(`Duplicate Saved Palette: ${activeSavedName}`) | |
| .textField("New Palette Name", `Enter a unique name for the copy`, { defaultValue: `${activeSavedName} Copy` }) | |
| .submitButton("Duplicate"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§eDuplicate cancelled."); | |
| return; | | | } | |
| const newPaletteName = response.formValues[0]?.trim(); | |
| if (!newPaletteName) { | |
| player.sendMessage("§cNew palette name cannot be empty."); | | | return; | | | } | |
| if (palettes[newPaletteName]) { | |
| player.sendMessage(`§cPalette named "${newPaletteName}" already exists.`); | |
| return; | |
| } | |
| if (newPaletteName === activeSavedName) { | |
| player.sendMessage(§cNew name must be different from the original.); | |
| return; | |
| } | |
| const copiedData = JSON.parse(JSON.stringify(paletteToCopy)); | |
| if (addPlayerPalette(player, newPaletteName, copiedData)) { | |
| player.sendMessage(`§aPalette "${activeSavedName}" duplicated as "${newPaletteName}".`); | |
| } | |
| } catch (e) { | |
| console.error(`[WorldEdit Duplicate Palette] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError duplicating palette."); | |
| } | | | } | |
| async function showDeletePaletteMenu(player) { | |
| const palettes = getPlayerPalettes(player); | |
| const paletteNames = Object.keys(palettes); | |
| const form = new ActionFormData().title("Delete Saved Palette"); | |
| if (paletteNames.length === 0) { | |
| form.body("No saved palettes to delete."); | |
| } else { | |
| form.body("Select a saved palette to delete:"); | |
| paletteNames.forEach(name => form.button(name)); | |
| } | | | form.button("§c[Back]"); | | | try { | | | const response = await form.show(player); | | | const backIndex = paletteNames.length; | | | if (response.canceled || response.selection === undefined || response.selection === backIndex) { | | | return; | | | } | |
| if (response.selection < backIndex) { | |
| const selectedName = paletteNames[response.selection]; | |
| const confirmForm = new ModalFormData() | |
| .title("Confirm Deletion") | |
| .toggle(`Delete your saved palette "${selectedName}"? This cannot be undone.`, { defaultValue: false }) | |
| .submitButton("Confirm Delete"); | |
| const confirmResponse = await confirmForm.show(player); | |
| if (!confirmResponse.canceled && confirmResponse.formValues?.[0] === true) { | |
| if (deletePlayerPalette(player, selectedName)) { | |
| player.sendMessage(`§aSaved palette "${selectedName}" deleted.`); | |
| } | |
| } else { | |
| player.sendMessage("§eDeletion cancelled."); | |
| } | | | } | |
| } catch (e) { | |
| console.error(`[WorldEdit Delete Palette] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError showing/processing delete palette menu."); | | | } | | | } | | | export async function showCreatePaletteFromSelectionForm(player, uniqueBlockIds) { | | | if (!uniqueBlockIds || uniqueBlockIds.length === 0) { | | | player.sendMessage("§cCannot create palette: No blocks provided for form."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | return; | | | } | |
| const form = new ModalFormData().title("Create Palette from Selection"); | |
| form.textField("New Palette Name", "Enter a unique name", { defaultValue: "" }); | |
| const formIndexToBlockId = {}; let currentIndex = 1; | |
| for (const blockId of uniqueBlockIds) { const simpleName = blockId.replace('minecraft:', ''); form.toggle(Include ${simpleName}?, { defaultValue: true }); formIndexToBlockId[currentIndex] = { type: 'toggle', blockId: blockId }; currentIndex++; form.slider(Weight (${simpleName}), 1, 100, { step: 1, defaultValue: 1 }); formIndexToBlockId[currentIndex] = { type: 'slider', blockId: blockId }; currentIndex++; } | |
| form.submitButton("Create Palette"); | |
| try { | |
| const response = await form.show(player); | |
| if (response.canceled || !response.formValues) { | |
| player.sendMessage("§ePalette creation cancelled."); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const formValues = response.formValues; const newPaletteName = formValues[0]?.trim(); | |
| if (!newPaletteName) { | |
| player.sendMessage("§cPalette name cannot be empty. Not saved."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | return; | | | } | |
| const existingPalettes = getPlayerPalettes(player); | |
| if (existingPalettes[newPaletteName]) { | |
| player.sendMessage(`§cPalette named "${newPaletteName}" already exists. Not saved.`); | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| return; | | | } | |
| const newPaletteData = []; | |
| for (let i = 1; i < formValues.length; i++) { | |
| const mapping = formIndexToBlockId[i]; | |
| if (mapping?.type === 'toggle') { | |
| const includeBlock = formValues[i]; | |
| const sliderIndex = i + 1; | |
| const weightMapping = formIndexToBlockId[sliderIndex]; | |
| if (includeBlock && weightMapping?.type === 'slider' && weightMapping?.blockId === mapping.blockId) { | |
| const weight = formValues[sliderIndex]; | |
| newPaletteData.push({ id: mapping.blockId, weight: weight }); | |
| } | | | i++; | | | } | | | } | | | if (newPaletteData.length === 0) { | | | player.sendMessage("§cNo blocks were included in the palette. Not saved."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | return; | | | } | |
| if (addPlayerPalette(player, newPaletteName, newPaletteData)) { | |
| player.sendMessage(`§aPalette "${newPaletteName}" created and saved successfully.`); | |
| } | |
| await system.run(async () => { await showPaletteMenu(player); }); | |
| } catch (e) { | |
| console.error(`[Create Palette from Selection Form] Error: ${e}\n${e.stack}`); | |
| player.sendMessage("§cError processing create palette form."); | | | await system.run(async () => { await showPaletteMenu(player); }); | | | } | | | } |