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