diff --git a/plugins.json b/plugins.json index 3d47923a..cc1c2982 100644 --- a/plugins.json +++ b/plugins.json @@ -1397,7 +1397,7 @@ "hytale_hitbox_helper": { "title": "Hytale Hitbox Helper", "author": "Marck.A.A", - "version": "1.0.0", + "version": "1.0.1", "description": "Dedicated tool to create Hytale hitboxes and JSON export.", "icon": "icon.png", "variant": "both", diff --git a/plugins/hytale_hitbox_helper/about.md b/plugins/hytale_hitbox_helper/about.md index 9082fdcc..d95cba6b 100644 --- a/plugins/hytale_hitbox_helper/about.md +++ b/plugins/hytale_hitbox_helper/about.md @@ -3,11 +3,17 @@ ## Features * **Dedicated Format:** Adds a new "Hytale Hitbox" project type. -* **Optimized Workspace:** * Automatic **Wireframe** view mode on activation. * **Quick Add:** One-click button to add a standard 32x32x32 hitbox. +* **Multiple Hitboxes:** It supports adding multiple Hitboxes. * **Smart Export:** * Exports strictly to Hytale's `.json` format. * Only exports elements named "hitbox" (case insensitive). * Automatic coordinate conversion (32 BB units = 1.0 Hytale unit). +* **References:** You can import other reference block mode models; they do not interfere with the hitbox. + +## References Models + +If you go to **File > Import** you can Import a model to use it as reference, it has to be (.blockymodel) +It wont be imported. ## How to Use diff --git a/plugins/hytale_hitbox_helper/hytale_hitbox_helper.js b/plugins/hytale_hitbox_helper/hytale_hitbox_helper.js index 7aac66e9..41027345 100644 --- a/plugins/hytale_hitbox_helper/hytale_hitbox_helper.js +++ b/plugins/hytale_hitbox_helper/hytale_hitbox_helper.js @@ -1,16 +1,46 @@ (function() { let export_action; let add_hitbox_action; + let import_reference_action; + let original_conditions = {}; const HITBOX_FORMAT_ID = 'hytale_hitbox'; + const actions_to_hide = [ + 'import_project', + 'import_bbmodel', + 'import_obj', + 'import_gltf', + 'import_image', + 'extrude_texture' + ]; + + function generateHitboxTexture() { + const canvas = document.createElement('canvas'); + const size = 128; + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext('2d'); + + ctx.clearRect(0, 0, size, size); + + ctx.strokeStyle = '#ff0000'; + const lineWidth = 2; + ctx.lineWidth = lineWidth; + + const offset = lineWidth / 2; + ctx.strokeRect(offset, offset, size - lineWidth, size - lineWidth); + + return canvas.toDataURL('image/png'); + } + BBPlugin.register('hytale_hitbox_helper', { title: 'Hytale Hitbox Helper', author: 'Marck.A.A', icon: 'icon.png', description: 'Tool to create easy Hytale hitboxes exportable to JSON.', - min_version: '4.8.0', - version: '1.0.0', + min_version: '4.8.0', + version: '1.0.1', variant: 'both', onload() { const format = new ModelFormat(HITBOX_FORMAT_ID, { @@ -21,23 +51,25 @@ target: 'Hytale', block_size: 32, centered_grid: true, + + optional_box_uv: true, box_uv: false, - optional_box_uv: false, single_texture: false, + uv_rotation: true, + per_texture_uv_size: true, + + bone_rig: true, rotate_cubes: false, - + onActivation() { document.body.classList.add('hytale_hitbox_mode'); - const viewMode = BarItems['view_mode']; - if (viewMode) { - viewMode.value = 'wireframe'; - viewMode.onChange(); + let existing_tex = Texture.all.find(t => t.id === 'hitbox_wireframe_tex'); + if (existing_tex) { + existing_tex.fromDataURL(generateHitboxTexture()); } Blockbench.showQuickMessage("Hytale Hitbox Mode Active"); }, onDeactivation() { - BarItems['view_mode'].value = 'textured'; - BarItems['view_mode'].onChange(); document.body.classList.remove('hytale_hitbox_mode'); } }); @@ -49,6 +81,107 @@ } `); + actions_to_hide.forEach(action_id => { + if (BarItems[action_id]) { + original_conditions[action_id] = BarItems[action_id].condition; + BarItems[action_id].condition = () => { + if (Format.id === HITBOX_FORMAT_ID) return false; + return Condition(original_conditions[action_id]); + }; + } + }); + + import_reference_action = new Action('import_hytale_reference', { + name: 'Import Reference (.blockymodel)', + icon: 'fa-file-import', + category: 'file', + condition: () => Format.id === HITBOX_FORMAT_ID, + click: function() { + Blockbench.import({ + extensions: ['blockymodel'], + type: 'Blockymodel Reference', + readtype: 'text', + multiple: true + }, function(files) { + if (!files || files.length === 0) return; + + if (!Codecs.blockymodel) { + Blockbench.showMessageBox({ + title: 'Missing Plugin', + message: 'The official Hytale plugin is required to read .blockymodel files.' + }); + return; + } + + let imported_root_groups = []; + let original_id = Format.id; + + Undo.initEdit({outliner: true}); + + Format.id = 'hytale_prop'; + + files.forEach(file => { + try { + let json = JSON.parse(file.content); + let content = Codecs.blockymodel.parse(json, file.path, { import_to_current_project: true }); + + if (content && content.new_groups) { + let new_groups = content.new_groups; + let imported_tex = content.new_textures && content.new_textures.length > 0 ? content.new_textures[0] : null; + + new_groups.forEach(g => { + if (!new_groups.includes(g.parent)) { + imported_root_groups.push(g); + } + + if (imported_tex) { + g.children.forEach(child => { + if (child instanceof Cube) { + for (const key in child.faces) { + child.faces[key].texture = imported_tex.uuid; + } + } + }); + } + }); + } + } catch (err) { + console.error("Error importing file:", file.name, err); + } + }); + + Format.id = original_id; + + if (imported_root_groups.length > 0) { + Undo.finishEdit('Import Reference (Base)'); + + setTimeout(() => { + Undo.initEdit({outliner: true, elements: [], groups: []}); + unselectAll(); + + let ref_group = Group.all.find(g => g.name.toLowerCase() === 'reference'); + if (!ref_group) { + ref_group = new Group({ + name: 'reference', + isOpen: true + }).init(); + } + + imported_root_groups.forEach(g => { + g.addTo(ref_group); + }); + + unselectAll(); + Undo.finishEdit('Format Reference'); + Canvas.updateAllFaces(); + Blockbench.showQuickMessage('Reference imported, textured and grouped!'); + }, 50); + } else { + Undo.cancelEdit(); + } + }); + } + }); add_hitbox_action = new Action('add_hytale_hitbox', { name: 'Add Hitbox', @@ -56,22 +189,41 @@ category: 'edit', condition: () => Format.id === HITBOX_FORMAT_ID, click: function() { + let hitbox_texture = Texture.all.find(t => t.id === 'hitbox_wireframe_tex'); + + if (!hitbox_texture) { + hitbox_texture = new Texture({ + id: 'hitbox_wireframe_tex', + name: 'Hitbox Wireframe' + }).add(); + + hitbox_texture.fromDataURL(generateHitboxTexture()); + } + const mesh = new Cube({ name: 'hitbox', - color: 2, - from: [-16, 0, -16], + color: 2, + from: [-16, 0, -16], to: [16, 32, 16], - }).init(); - - mesh.mesh.material.transparent = true; - mesh.mesh.material.opacity = 0.5; - - Undo.initEdit({elements: [mesh], outliner: true}); + autouv: 0 + }); + + for (const key in mesh.faces) { + mesh.faces[key].texture = hitbox_texture.uuid; + mesh.faces[key].uv = [0, 0, 16, 16]; + } + + mesh.init(); + + Undo.initEdit({elements: [mesh], textures: [hitbox_texture], outliner: true}); Undo.finishEdit('Add Hytale Hitbox'); + + Canvas.updateAllFaces(); } }); BarItems.add_element.side_menu.addAction(add_hitbox_action); + MenuBar.menus.file.addAction(import_reference_action, 'import'); export_action = new Action('export_hytale_hitbox', { name: 'Export Hytale Hitbox (.json)', @@ -86,13 +238,20 @@ MenuBar.menus.file.addAction(export_action, 'export'); }, onunload() { + actions_to_hide.forEach(action_id => { + if (BarItems[action_id] && original_conditions[action_id] !== undefined) { + BarItems[action_id].condition = original_conditions[action_id]; + } + }); + export_action.delete(); add_hitbox_action.delete(); + import_reference_action.delete(); } }); function exportHytaleHitbox() { - const hitboxes = Cube.all.filter(cube => + const hitboxes = Cube.all.filter(cube => cube.name.toLowerCase() === 'hitbox' && cube.export ); @@ -124,7 +283,7 @@ }); const content = JSON.stringify(json_output, null, 2); - + Blockbench.export({ type: 'JSON Model', extensions: ['json'], diff --git a/plugins/hytale_hitbox_helper/icon.png b/plugins/hytale_hitbox_helper/icon.png index 8533fb32..2b4e6a91 100644 Binary files a/plugins/hytale_hitbox_helper/icon.png and b/plugins/hytale_hitbox_helper/icon.png differ