From fb9443d79ca90f93818776a909313f3372eecb83 Mon Sep 17 00:00:00 2001 From: Maxime Reynier Date: Tue, 12 Aug 2025 14:31:45 +0200 Subject: [PATCH] Export as Datapack --- package-lock.json | 95 +++++ package.json | 3 + src/App.tsx | 33 +- src/components/ConfigVariablesEditor.tsx | 10 +- src/components/ImportExport.tsx | 459 +++++++++++------------ src/components/JSONPreview.tsx | 2 +- src/components/NPCInteractionEditor.tsx | 47 +-- src/types/npc.ts | 108 +++--- src/utils/validation.ts | 9 +- 9 files changed, 435 insertions(+), 331 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a9e61e..95f8322 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,15 @@ "version": "0.0.0", "dependencies": { "@tailwindcss/forms": "^0.5.10", + "file-saver": "^2.0.5", + "jszip": "^3.10.1", "lucide-react": "^0.539.0", "react": "^19.1.1", "react-dom": "^19.1.1" }, "devDependencies": { "@eslint/js": "^9.32.0", + "@types/file-saver": "^2.0.7", "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", "@vitejs/plugin-react": "^4.7.0", @@ -1448,6 +1451,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2112,6 +2121,11 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2527,6 +2541,11 @@ "node": ">=16.0.0" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2748,6 +2767,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -2775,6 +2799,11 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2841,6 +2870,11 @@ "node": ">=0.12.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2941,6 +2975,17 @@ "node": ">=6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2965,6 +3010,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", @@ -3472,6 +3525,11 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3721,6 +3779,11 @@ "node": ">= 0.8.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3791,6 +3854,20 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3906,6 +3983,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -3922,6 +4004,11 @@ "semver": "bin/semver.js" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3964,6 +4051,14 @@ "node": ">=0.10.0" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", diff --git a/package.json b/package.json index 2572868..1c12bc1 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,15 @@ }, "dependencies": { "@tailwindcss/forms": "^0.5.10", + "file-saver": "^2.0.5", + "jszip": "^3.10.1", "lucide-react": "^0.539.0", "react": "^19.1.1", "react-dom": "^19.1.1" }, "devDependencies": { "@eslint/js": "^9.32.0", + "@types/file-saver": "^2.0.7", "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", "@vitejs/plugin-react": "^4.7.0", diff --git a/src/App.tsx b/src/App.tsx index f5691c3..3e73d65 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,18 +16,20 @@ function App() { const [activeTab, setActiveTab] = useState('basic'); const [npcConfig, setNpcConfig] = useState({ + id: 'my_npc', + name: 'My NPC', hitbox: "player", - presets: [], - resourceIdentifier: "cobblemon:my_npc", config: [], names: ["My NPC"], - interaction: { type: "none" } + interactions: [], + interaction: { type: 'none' }, + resourceIdentifier: 'cobblemon:my_npc' }); const [dialogueConfig, setDialogueConfig] = useState(null); useEffect(() => { - if (npcConfig.interaction.type === 'dialogue' && !dialogueConfig) { + if (npcConfig.interactions && npcConfig.interactions.length > 0 && npcConfig.interactions[0].type === 'dialogue' && !dialogueConfig) { const newDialogue: DialogueConfiguration = { speakers: { npc: { @@ -48,7 +50,7 @@ function App() { }; setDialogueConfig(newDialogue); } - }, [npcConfig.interaction.type]); + }, [npcConfig.interactions, dialogueConfig]); const tabs = [ { id: 'basic', name: 'Basic Settings', icon: Settings }, @@ -80,10 +82,13 @@ function App() { case 'import': return ( { + if (configs.length > 0) { + setNpcConfig(configs[0]); + } + }} /> ); default: @@ -155,11 +160,15 @@ function App() {
Interaction
-
{npcConfig.interaction.type}
+
+ {npcConfig.interactions && npcConfig.interactions.length > 0 + ? npcConfig.interactions[0].type + : 'none'} +
Variables
-
{npcConfig.config.length}
+
{npcConfig.config?.length || 0}
@@ -188,4 +197,4 @@ function App() { ); } -export default App; \ No newline at end of file +export default App; diff --git a/src/components/ConfigVariablesEditor.tsx b/src/components/ConfigVariablesEditor.tsx index 36057cf..a41cce0 100644 --- a/src/components/ConfigVariablesEditor.tsx +++ b/src/components/ConfigVariablesEditor.tsx @@ -18,19 +18,19 @@ export function ConfigVariablesEditor({ config, onChange }: ConfigVariablesEdito onChange({ ...config, - config: [...config.config, newVariable] + config: [...(config.config || []), newVariable] }); }; const removeVariable = (index: number) => { onChange({ ...config, - config: config.config.filter((_, i) => i !== index) + config: (config.config || []).filter((_, i) => i !== index) }); }; const updateVariable = (index: number, field: keyof MoLangConfigVariable, value: any) => { - const newConfig = [...config.config]; + const newConfig = [...(config.config || [])]; newConfig[index] = { ...newConfig[index], [field]: value }; onChange({ ...config, @@ -57,13 +57,13 @@ export function ConfigVariablesEditor({ config, onChange }: ConfigVariablesEdito These variables can be referenced in dialogues, scripts, and other configurations.

- {config.config.length === 0 ? ( + {(config.config || []).length === 0 ? (
No configuration variables defined. Click "Add Variable" to create one.
) : (
- {config.config.map((variable, index) => ( + {(config.config || []).map((variable, index) => (

Variable {index + 1}

diff --git a/src/components/ImportExport.tsx b/src/components/ImportExport.tsx index 92ef69e..71f6f3c 100644 --- a/src/components/ImportExport.tsx +++ b/src/components/ImportExport.tsx @@ -1,172 +1,202 @@ -import { useRef, useState } from 'react'; -import { Upload, Download, FileText, AlertTriangle } from 'lucide-react'; -import type { NPCConfiguration, DialogueConfiguration } from '../types/npc'; +import React, { useRef, useState } from 'react'; +import { Download, FileText, Package } from 'lucide-react'; +import JSZip from 'jszip'; +import type {DialogueConfiguration, NPCConfig} from '../types/npc'; interface ImportExportProps { - npcConfig: NPCConfiguration; - dialogueConfig: DialogueConfiguration | null; - onNPCConfigLoad: (config: NPCConfiguration) => void; - onDialogueConfigLoad: (config: DialogueConfiguration) => void; + npcConfigs: NPCConfig[]; + dialogueConfiguration: DialogueConfiguration | null; + onImport: (configs: NPCConfig[]) => void; } -export function ImportExport({ - npcConfig, - dialogueConfig, - onNPCConfigLoad, - onDialogueConfigLoad -}: ImportExportProps) { +const MINECRAFT_VERSIONS = [ + { value: '1.20.1', label: 'Minecraft 1.20.1' }, + { value: '1.20.2', label: 'Minecraft 1.20.2' }, + { value: '1.20.3', label: 'Minecraft 1.20.3' }, + { value: '1.20.4', label: 'Minecraft 1.20.4' }, + { value: '1.20.5', label: 'Minecraft 1.20.5' }, + { value: '1.20.6', label: 'Minecraft 1.20.6' }, + { value: '1.21', label: 'Minecraft 1.21' }, + { value: '1.21.1', label: 'Minecraft 1.21.1' }, +]; + +const PACK_FORMAT: { [key: string]: number } = { + '1.20.1': 15, + '1.20.2': 18, + '1.20.3': 26, + '1.20.4': 26, + '1.20.5': 41, + '1.20.6': 48, + '1.21': 48, + '1.21.1': 48, +}; + +export function ImportExport({ npcConfigs, dialogueConfiguration, onImport }: ImportExportProps) { const npcFileInputRef = useRef(null); - const dialogueFileInputRef = useRef(null); + const [exportError, setExportError] = useState(null); const [importError, setImportError] = useState(null); const [importSuccess, setImportSuccess] = useState(null); + const [minecraftVersion, setMinecraftVersion] = useState('1.20.1'); + const [datapackName, setDatapackName] = useState('cobblemon_npcs'); + + const handleImportNPCs = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + setImportError(null); + setImportSuccess(null); - const handleFileRead = ( - file: File, - onLoad: (config: any) => void, - configType: string - ) => { const reader = new FileReader(); reader.onload = (e) => { try { const content = e.target?.result as string; - const config = JSON.parse(content); - - // Basic validation - if (configType === 'npc') { - if (!config.resourceIdentifier || !config.names) { - throw new Error('Invalid NPC configuration: missing required fields'); - } - } else if (configType === 'dialogue') { - if (!config.pages || !config.speakers) { - throw new Error('Invalid dialogue configuration: missing required fields'); + const configs = JSON.parse(content); + + if (!Array.isArray(configs)) { + setImportError('Le fichier doit contenir un tableau de configurations NPC'); + return; + } + + // Validation basique de la structure + for (let index = 0; index < configs.length; index++) { + const config = configs[index]; + if (!config.id || !config.name) { + setImportError(`Configuration NPC ${index + 1} invalide: id et name sont requis`); + return; } } - - onLoad(config); - setImportSuccess(`${configType} configuration loaded successfully!`); - setImportError(null); - setTimeout(() => setImportSuccess(null), 3000); + + onImport(configs); + setImportSuccess(`${configs.length} configuration(s) NPC importée(s) avec succès`); + + // Reset input + if (npcFileInputRef.current) { + npcFileInputRef.current.value = ''; + } } catch (error) { - setImportError(`Error loading ${configType}: ${error instanceof Error ? error.message : 'Invalid JSON'}`); - setImportSuccess(null); + setImportError(error instanceof Error ? error.message : 'Erreur lors de l\'importation'); } }; reader.readAsText(file); }; - const handleNPCFileSelect = (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (file) { - handleFileRead(file, onNPCConfigLoad, 'npc'); - } - }; + const generatePackMcmeta = (version: string, packName: string) => { + const packFormat = PACK_FORMAT[version] || 15; - const handleDialogueFileSelect = (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (file) { - handleFileRead(file, onDialogueConfigLoad, 'dialogue'); - } - }; - - const exportExample = () => { - const exampleNPC: NPCConfiguration = { - hitbox: "player", - presets: [], - resourceIdentifier: "mymod:example_npc", - config: [ - { - variableName: "greeting_message", - displayName: "Greeting Message", - description: "The message displayed when first talking to the NPC", - type: "TEXT", - defaultValue: "Hello, trainer!" - } - ], - isInvulnerable: true, - canDespawn: false, - names: ["Example NPC"], - interaction: { - type: "dialogue", - dialogue: "mymod:example_dialogue" - }, - battleConfiguration: { - canChallenge: true - }, - skill: 3, - party: { - type: "simple", - pokemon: [ - "pikachu level=25 moves=thunderbolt,quick-attack", - "charmander level=24 moves=ember,scratch" - ] + return { + pack: { + pack_format: packFormat, + description: `${packName} - Datapack pour Cobblemon avec NPCs personnalisés` } }; + }; - const exampleDialogue: DialogueConfiguration = { - speakers: { - npc: { - name: { type: "expression", expression: "q.npc.name" }, - face: "q.npc.face(false);" - }, - player: { - name: { type: "expression", expression: "q.player.username" }, - face: "q.player.face();" - } - }, - pages: [ - { - id: "greeting", - speaker: "npc", - lines: ["Hello there, trainer! Would you like to battle?"], - input: { - type: "option", - vertical: true, - options: [ - { - text: "Yes, let's battle!", - value: "accept", - action: ["q.npc.start_battle(q.player, 'single');"] - }, - { - text: "Maybe later.", - value: "decline", - action: ["q.dialogue.close();"] - } - ] - } - } - ] + const generateNPCFile = (npc: NPCConfig) => { + return { + aspects: npc.aspects || [], + model: npc.model || "cobblemon:generic_npc", + dialogue: npc.dialogue ? [`${npc.id}_dialogue`] : [], + party: npc.party || [], + battleTheme: npc.battleConfiguration?.battleTheme || "", + victoryTheme: npc.battleConfiguration?.victoryTheme || "", + defeatTheme: npc.battleConfiguration?.defeatTheme || "", + canBattle: npc.battleConfiguration?.canBattle || false, + ...npc.configVariables }; + }; - // Download both files - const downloadFile = (content: string, filename: string) => { - const blob = new Blob([content], { type: 'application/json' }); - const url = URL.createObjectURL(blob); + const exportAsDatapack = async () => { + try { + setExportError(null); + + if (npcConfigs.length === 0) { + setExportError('Aucune configuration NPC à exporter'); + return; + } + + if (!datapackName.trim()) { + setExportError('Le nom du datapack est requis'); + return; + } + + const zip = new JSZip(); + + // Générer pack.mcmeta + const packMcmeta = generatePackMcmeta(minecraftVersion, datapackName); + zip.file('pack.mcmeta', JSON.stringify(packMcmeta, null, 2)); + + // Créer les dossiers de structure + const dataFolder = zip.folder('data'); + const cobblemonFolder = dataFolder!.folder('cobblemon'); + const npcsFolder = cobblemonFolder!.folder('npc'); + const dialogueFolder = cobblemonFolder!.folder('dialogue'); + + // Générer les fichiers pour chaque NPC + for (const npc of npcConfigs) { + // Fichier NPC + const npcFile = generateNPCFile(npc); + npcsFolder!.file(`${npc.id}.json`, JSON.stringify(npcFile, null, 2)); + + console.log(dialogueConfiguration) + if (dialogueConfiguration) { + console.log(dialogueConfiguration) + // Utiliser la même approche que JSONPreview.tsx - sérialisation directe + const dialogueJson = JSON.stringify(dialogueConfiguration, null, 2); + dialogueFolder!.file(`${npc.id}_dialogue.json`, dialogueJson); + } + } + + // Générer le ZIP et déclencher le téléchargement + const content = await zip.generateAsync({ type: 'blob' }); + const url = URL.createObjectURL(content); const a = document.createElement('a'); a.href = url; - a.download = filename; - document.body.appendChild(a); + a.download = `${datapackName}.zip`; a.click(); - document.body.removeChild(a); URL.revokeObjectURL(url); - }; - downloadFile(JSON.stringify(exampleNPC, null, 2), 'example_npc.json'); - downloadFile(JSON.stringify(exampleDialogue, null, 2), 'example_dialogue.json'); + } catch (error) { + setExportError(error instanceof Error ? error.message : 'Erreur lors de l\'exportation'); + } + }; + + const exportAsJSON = () => { + try { + setExportError(null); + const dataStr = JSON.stringify(npcConfigs, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + link.download = 'npc_configurations.json'; + link.click(); + URL.revokeObjectURL(url); + } catch (error) { + setExportError(error instanceof Error ? error.message : 'Erreur lors de l\'exportation'); + } }; return (
-

Import & Export

+

Import / Export

+ + {exportError && ( +
+
+ +
+

{exportError}

+
+
+
+ )} - {/* Status Messages */} {importError && (
- +
-

Import Error

-

{importError}

+

{importError}

@@ -197,118 +227,81 @@ export function ImportExport({ ref={npcFileInputRef} type="file" accept=".json" - onChange={handleNPCFileSelect} - className="hidden" + onChange={handleImportNPCs} + className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100" /> - +

+ Fichier JSON contenant un tableau de configurations NPC +

- -
- - - -
-
- -
-

Import Tips

-
    -
  • • Files must be valid JSON format
  • -
  • • NPC configs require resourceIdentifier and names
  • -
  • • Dialogue configs require pages and speakers
  • -
  • • Import will override current configuration
  • -
- {/* Export/Examples Section */} + {/* Export Section */}
-

Examples & Templates

- -
- - -
-

Example Includes:

-
    -
  • • Complete NPC with battle configuration
  • -
  • • Simple dialogue with options
  • -
  • • Configuration variables example
  • -
  • • Battle party setup
  • -
  • • MoLang expressions
  • -
+

Export Configurations

+ +
+ {/* Configuration du Datapack */} +
+

Configuration du Datapack

+ +
+ + +
+ +
+ + setDatapackName(e.target.value)} + placeholder="nom_du_datapack" + className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" + /> +
+
+ + {/* Boutons d'export */} +
+ + + + +

+ {npcConfigs.length} configuration(s) prête(s) à l'export +

- -
-

Quick Start

-

- Download the example files to see a complete working NPC configuration. - You can then import and modify them to create your own NPCs. -

-
-
-
- - {/* Current Configuration Summary */} -
-

Current Configuration Summary

-
-
-
-
NPC Name
-
{npcConfig.names[0] || 'Unnamed NPC'}
-
-
-
Resource ID
-
{npcConfig.resourceIdentifier || 'Not set'}
-
-
-
Interaction
-
{npcConfig.interaction.type}
-
-
-
Can Battle
-
{npcConfig.battleConfiguration?.canChallenge ? 'Yes' : 'No'}
-
-
-
Config Variables
-
{npcConfig.config.length}
-
-
-
Has Dialogue
-
{dialogueConfig ? 'Yes' : 'No'}
-
-
diff --git a/src/components/JSONPreview.tsx b/src/components/JSONPreview.tsx index 47efa3a..90bcf7d 100644 --- a/src/components/JSONPreview.tsx +++ b/src/components/JSONPreview.tsx @@ -69,7 +69,7 @@ export function JSONPreview({ npcConfig, dialogueConfig }: JSONPreviewProps) { }; const generateFilenames = () => { - const baseName = npcConfig.resourceIdentifier + const baseName = (npcConfig.resourceIdentifier || 'cobblemon:npc') .split(':')[1] || 'npc'; return { diff --git a/src/components/NPCInteractionEditor.tsx b/src/components/NPCInteractionEditor.tsx index 09c2237..cc3860e 100644 --- a/src/components/NPCInteractionEditor.tsx +++ b/src/components/NPCInteractionEditor.tsx @@ -6,6 +6,8 @@ interface NPCInteractionEditorProps { } export function NPCInteractionEditor({ config, onChange }: NPCInteractionEditorProps) { + const currentInteraction = config.interaction || { type: 'none' }; + const handleInteractionChange = (interaction: NPCInteraction) => { onChange({ ...config, interaction }); }; @@ -39,7 +41,7 @@ export function NPCInteractionEditor({ config, onChange }: NPCInteractionEditorP handleTypeChange(e.target.value as NPCInteraction['type'])} className="form-radio" /> @@ -54,59 +56,48 @@ export function NPCInteractionEditor({ config, onChange }: NPCInteractionEditorP
- {config.interaction.type === 'dialogue' && ( + {currentInteraction.type === 'dialogue' && (
- + handleInteractionChange({ type: 'dialogue', dialogue: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" - placeholder="cobblemon:my_dialogue" + placeholder="dialogue_id" /> -

- Reference to a dialogue file (e.g., "cobblemon:my_dialogue" refers to data/cobblemon/dialogues/my_dialogue.json) -

)} - {config.interaction.type === 'script' && ( + {currentInteraction.type === 'script' && (
- + handleInteractionChange({ type: 'script', script: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" - placeholder="cobblemon:my_script" + placeholder="path/to/script.js" /> -

- Reference to a MoLang script file (e.g., "cobblemon:my_script" refers to data/cobblemon/molang/my_script.molang) -

)} - {config.interaction.type === 'custom_script' && ( + {currentInteraction.type === 'custom_script' && (
- +