Gpt A developer implemented a brush settings form for Minecraft using the @minecraft/server and @minecraft/server-ui modules. The form allows players to configure brush shape, size, hollow mode, and material source, with dynamic dropdown options for palettes and presets. | 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: Loading 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 Loading "${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: Loading 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 Loading "${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: Loading 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 Loading "${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 ; } ; | | | } | | | } |