diff --git a/.gitignore b/.gitignore
index 3c3629e..25c8fdb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
node_modules
+package-lock.json
\ No newline at end of file
diff --git a/dist/hytale_plugin.js b/dist/hytale_plugin.js
index ed08fb3..35c9e81 100644
--- a/dist/hytale_plugin.js
+++ b/dist/hytale_plugin.js
@@ -1,4 +1,10 @@
(() => {
+ var __defProp = Object.defineProperty;
+ var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+ };
+
// src/cleanup.ts
var list = [];
function track(...items) {
@@ -23,6 +29,218 @@
}
};
+ // src/translations/en.json
+ var en_exports = {};
+ __export(en_exports, {
+ actions: () => actions,
+ default: () => en_default,
+ directory: () => directory,
+ error: () => error,
+ formats: () => formats,
+ menu: () => menu,
+ plugin: () => plugin,
+ settings: () => settings2,
+ textures: () => textures,
+ toggle: () => toggle
+ });
+ var plugin = {
+ title: "Hytale Models",
+ description: "Create models and animations for Hytale"
+ };
+ var menu = {
+ import_attachment: "Import Attachment",
+ reload_all: "Reload All Attachments"
+ };
+ var error = {
+ no_textures: "No textures found for this model."
+ };
+ var actions = {
+ reload_attachments: "Reload Attachments",
+ reload_all_attachments: "Reload All Attachments",
+ hytale_add_quad: "Add Quad",
+ cut_selection: "Cut texture selection",
+ paste_texture: "Paste into texture",
+ pasted_layer_name: "pasted"
+ };
+ var textures = {
+ import: "Import Textures",
+ message: "No textures were found for this model. How would you like to import textures?",
+ select_files: "Select Files",
+ select_folder: "Select Folder",
+ skip: "Skip"
+ };
+ var directory = {
+ select_texture_folder: "Select Texture Folder",
+ file_not_found: "File not found",
+ export_blockymodel: "Export Hytale BlockyModel",
+ export_blockymodel_desc: "Export a BlockyModel file"
+ };
+ var formats = {
+ character: {
+ name: "Hytale Character",
+ description: "Create character and attachment models using Hytale's blockymodel format"
+ },
+ prop: {
+ name: "Hytale Prop",
+ description: "Create prop models using Hytale's blockymodel format"
+ },
+ page: {
+ informations: "Information",
+ resources: "Resources",
+ info_list: "* One texture can be applied to a model at a time\n* UV sizes are linked to the size of each cube and cannot be modified, except by stretching the cube\n* Models can have a maximum of 255 nodes",
+ resource_list: "* [Modeling Overview and Style Guide](https://hytale.com/news/2025/12/an-introduction-to-making-models-for-hytale)\n* [Modeling Tutorial](https://youtu.be/Q07i3wmGy0Y)"
+ },
+ category: "Hytale"
+ };
+ var settings2 = {
+ duplicate_bones: {
+ name: "Duplicate Bone Names",
+ description: "Allow creating duplicate groups names in Hytale formats. Multiple groups with the same name can be used to apply animations to multiple nodes at once."
+ },
+ photoshop_copy: {
+ name: "Copy-Paste with Magenta Alpha",
+ description: "Copy image selections with magenta background and remove magenta when pasting to help transfer transparency to Photoshop"
+ },
+ group_pivot: {
+ name: "Show Group Pivot Indicator",
+ description: "Show a dot in Edit mode indicating the rotation pivot point for animations"
+ },
+ preview_texture: {
+ name: "Preview Selected Texture",
+ description: "When selecting a texture in a Hytale format, preview the texture on the model instantly"
+ }
+ };
+ var toggle = {
+ toggle_attachment_editing: "Toggle Attachment Editing"
+ };
+ var en_default = {
+ plugin,
+ menu,
+ error,
+ actions,
+ textures,
+ directory,
+ formats,
+ settings: settings2,
+ toggle
+ };
+
+ // src/translations/pt-BR.json
+ var pt_BR_exports = {};
+ __export(pt_BR_exports, {
+ actions: () => actions2,
+ default: () => pt_BR_default,
+ directory: () => directory2,
+ error: () => error2,
+ formats: () => formats2,
+ menu: () => menu2,
+ plugin: () => plugin2,
+ settings: () => settings3,
+ textures: () => textures2,
+ toggle: () => toggle2
+ });
+ var plugin2 = {
+ title: "Modelos Hytale",
+ description: "Crie modelos e anima\xE7\xF5es para o Hytale"
+ };
+ var menu2 = {
+ import_attachment: "Importar Anexo",
+ reload_all: "Recarregar Todos os Anexos"
+ };
+ var error2 = {
+ no_textures: "Nenhuma textura encontrada para este modelo."
+ };
+ var actions2 = {
+ reload_attachments: "Recarregar Anexos",
+ reload_all_attachments: "Recarregar Todos os Anexos",
+ hytale_add_quad: "Adicionar Quad",
+ cut_selection: "Recortar sele\xE7\xE3o da textura",
+ paste_texture: "Colar na textura",
+ pasted_layer_name: "colado"
+ };
+ var textures2 = {
+ import: "Importar Texturas",
+ message: "Nenhuma textura foi encontrada para este modelo. Como voc\xEA deseja importar as texturas?",
+ select_files: "Selecionar Arquivos",
+ select_folder: "Selecionar Pasta",
+ skip: "Pular"
+ };
+ var directory2 = {
+ select_texture_folder: "Selecionar Pasta de Texturas",
+ file_not_found: "Arquivo n\xE3o encontrado",
+ export_blockymodel: "Exportar BlockyModel do Hytale",
+ export_blockymodel_desc: "Exportar um arquivo BlockyModel"
+ };
+ var formats2 = {
+ character: {
+ name: "Personagem Hytale",
+ description: "Crie modelos de personagens e anexos usando o formato blockymodel do Hytale"
+ },
+ prop: {
+ name: "Objeto Hytale",
+ description: "Crie modelos de objetos usando o formato blockymodel do Hytale"
+ },
+ page: {
+ informations: "Informa\xE7\xF5es",
+ resources: "Recursos",
+ info_list: "* Apenas uma textura pode ser aplicada a um modelo por vez\n* Os tamanhos de UV s\xE3o vinculados ao tamanho de cada cubo e n\xE3o podem ser modificados, exceto esticando o cubo\n* Os modelos podem ter no m\xE1ximo 255 n\xF3s",
+ resource_list: "* [Guia de Modelagem e Estilo](https://hytale.com/news/2025/12/an-introduction-to-making-models-for-hytale)\n* [Tutorial de Modelagem](https://youtu.be/Q07i3wmGy0Y)"
+ },
+ category: "Hytale"
+ };
+ var settings3 = {
+ duplicate_bones: {
+ name: "Duplicar Nomes de Ossos",
+ description: "Permite criar grupos com nomes duplicados nos formatos do Hytale. V\xE1rios grupos com o mesmo nome podem ser usados para aplicar anima\xE7\xF5es a v\xE1rios n\xF3s ao mesmo tempo."
+ },
+ photoshop_copy: {
+ name: "Copiar e Colar com Alpha Magenta",
+ description: "Copia sele\xE7\xF5es de imagem com fundo magenta e remove o magenta ao colar, ajudando a transferir transpar\xEAncia para o Photoshop"
+ },
+ group_pivot: {
+ name: "Mostrar Indicador de Piv\xF4 do Grupo",
+ description: "Mostra um ponto no modo de Edi\xE7\xE3o indicando o ponto de piv\xF4 de rota\xE7\xE3o para anima\xE7\xF5es"
+ },
+ preview_texture: {
+ name: "Pr\xE9-visualizar Textura Selecionada",
+ description: "Ao selecionar uma textura em um formato Hytale, a textura \xE9 exibida instantaneamente no modelo"
+ }
+ };
+ var toggle2 = {
+ toggle_attachment_editing: "Alternar Edi\xE7\xE3o de Anexos"
+ };
+ var pt_BR_default = {
+ plugin: plugin2,
+ menu: menu2,
+ error: error2,
+ actions: actions2,
+ textures: textures2,
+ directory: directory2,
+ formats: formats2,
+ settings: settings3,
+ toggle: toggle2
+ };
+
+ // src/i18n.ts
+ var translations = {
+ en: en_exports,
+ pt: pt_BR_exports
+ };
+ function t(path) {
+ const langCode = Language.code.split("_")[0];
+ const currentLangObj = translations[langCode] || translations["en"];
+ const resolvePath = (obj, path2) => {
+ return path2.split(".").reduce((prev, curr) => {
+ return prev ? prev[curr] : null;
+ }, obj);
+ };
+ let result = resolvePath(currentLangObj, path);
+ if (!result && langCode !== "en") {
+ result = resolvePath(translations["en"], path);
+ }
+ return result || path;
+ }
+
// src/util.ts
function qualifiesAsMainShape(object) {
return object instanceof Cube && object.rotation.allEqual(0);
@@ -59,27 +277,27 @@
return [...new Set(paths)];
}
function loadTexturesFromPaths(paths, preferredName) {
- const textures = [];
+ const textures3 = [];
for (let texturePath of paths) {
- let texture = Texture.all.find((t) => t.path == texturePath);
+ let texture = Texture.all.find((t2) => t2.path == texturePath);
if (!texture) {
texture = new Texture().fromPath(texturePath).add(false, true);
}
- textures.push(texture);
+ textures3.push(texture);
}
- if (textures.length > 0) {
- let primary = preferredName && textures.find((t) => t.name.startsWith(preferredName)) || textures[0];
- if (!Texture.all.find((t) => t.use_as_default)) {
+ if (textures3.length > 0) {
+ let primary = preferredName && textures3.find((t2) => t2.name.startsWith(preferredName)) || textures3[0];
+ if (!Texture.all.find((t2) => t2.use_as_default)) {
primary.use_as_default = true;
}
}
- return textures;
+ return textures3;
}
function promptForTextures(dirname) {
Blockbench.showMessageBox({
- title: "Import Textures",
- message: "No textures were found for this model. How would you like to import textures?",
- buttons: ["Select Files", "Select Folder", "Skip"]
+ title: t("textures.import"),
+ message: t("textures.message"),
+ buttons: [t("textures.select_files"), t("textures.select_folder"), t("textures.skip")]
}, (choice) => {
let project = Project;
if (choice === 2 || !project) return;
@@ -98,7 +316,7 @@
});
} else if (choice === 1) {
let folderPath = Blockbench.pickDirectory({
- title: "Select Texture Folder",
+ title: t("directory.select_textures_folder"),
startpath: dirname,
resource_id: "texture"
});
@@ -107,7 +325,7 @@
let files = fs.readdirSync(folderPath);
let pngFiles = files.filter((f) => f.match(/\.png$/i));
if (pngFiles.length === 0) {
- Blockbench.showQuickMessage("No PNG files found in selected folder");
+ Blockbench.showQuickMessage(t("textures.no_textures_found"), 3e3);
return;
}
let paths = pngFiles.map((f) => PathModule.join(folderPath, f));
@@ -722,8 +940,8 @@
}
});
let export_action = new Action("export_blockymodel", {
- name: "Export Hytale Blockymodel",
- description: "Export a blockymodel file",
+ name: t("action.export_blockymodel"),
+ description: t("action.export_blockymodel_desc"),
icon: "icon-format_hytale",
category: "file",
condition: { formats: FORMAT_IDS },
@@ -782,42 +1000,33 @@
};
let format_page = {
content: [
- { type: "h3", text: tl("mode.start.format.informations") },
- {
- text: `* One texture can be applied to a model at a time
- * UV sizes are linked to the size of each cube and cannot be modified, except by stretching the cube
- * Models can have a maximum of 255 nodes`.replace(/(\t| {4,4})+/g, "")
- },
- { type: "h3", text: tl("mode.start.format.resources") },
- {
- text: [
- "* [Modeling Overview and Style Guide](https://hytale.com/news/2025/12/an-introduction-to-making-models-for-hytale)",
- "* [Modeling Tutorial](https://youtu.be/Q07i3wmGy0Y)"
- ].join("\n")
- }
+ { type: "h3", text: t("formats.page.informations") },
+ { text: t("formats.page.info_list") },
+ { type: "h3", text: t("formats.page.resources") },
+ { text: t("formats.page.resource_list") }
]
};
let format_character = new ModelFormat("hytale_character", {
- name: "Hytale Character",
- description: "Create character and attachment models using Hytale's blockymodel format",
+ name: t("formats.character.name"),
+ description: t("formats.character.description"),
icon: "icon-format_hytale",
format_page,
block_size: 64,
...common
// TODO: Auto-reload attachments on tab switch. Needs dirty tracking and setting toggle to avoid losing unsaved changes
/*
- onActivation() {
- common.onActivation?.();
- setTimeout(() => reload_all_attachments?.click(), 0);
- }
- */
+ onActivation() {
+ common.onActivation?.();
+ setTimeout(() => reload_all_attachments?.click(), 0);
+ }
+ */
});
let format_prop = new ModelFormat("hytale_prop", {
- name: "Hytale Prop",
- description: "Create prop models using Hytale's blockymodel format",
+ name: t("formats.prop.name"),
+ description: t("formats.prop.description"),
icon: "icon-format_hytale",
format_page,
- block_size: 32,
+ block_size: 64,
...common
});
let int_setting = new Setting("hytale_integer_size", {
@@ -838,7 +1047,7 @@
track(format_character);
track(format_prop);
Language.addTranslations("en", {
- "format_category.hytale": "Hytale"
+ "format_category.hytale": t("formats.category")
});
}
function isHytaleFormat() {
@@ -921,9 +1130,9 @@
}
});
let setting = new Setting("hytale_duplicate_bone_names", {
- name: "Duplicate Bone Names",
+ name: t("settings.duplicate_bones.name"),
category: "edit",
- description: "Allow creating duplicate groups names in Hytale formats. Multiple groups with the same name can be used to apply animations to multiple nodes at once.",
+ description: t("settings.duplicate_bones.description"),
type: "toggle",
value: false
});
@@ -1184,8 +1393,8 @@
}
function setupTextureHandling() {
let setting = new Setting("preview_selected_texture", {
- name: "Preview Selected Texture",
- description: "When selecting a texture in a Hytale format, preview the texture on the model instantly",
+ name: t("settings.preview_texture.name"),
+ description: t("settings.preview_texture.description"),
category: "preview",
type: "toggle",
value: true
@@ -1222,7 +1431,7 @@
tex.group = textureGroup.uuid;
updateUVSize(tex);
}
- let texture = newTextures.find((t) => t.name.startsWith(attachmentName)) ?? newTextures[0];
+ let texture = newTextures.find((t2) => t2.name.startsWith(attachmentName)) ?? newTextures[0];
return texture.uuid;
}
function setupAttachmentTextures() {
@@ -1237,7 +1446,7 @@
let collection = getCollection(this.cube);
if (collection && "texture" in collection) {
if (collection.texture) {
- let texture = Texture.all.find((t) => t.uuid == collection.texture);
+ let texture = Texture.all.find((t2) => t2.uuid == collection.texture);
if (texture) return texture;
}
return null;
@@ -1274,14 +1483,14 @@
}
}
];
- Texture.all.forEach((t) => {
+ Texture.all.forEach((t2) => {
arr.push({
- name: t.name,
+ name: t2.name,
// @ts-expect-error
- icon: t.img,
- marked: t.uuid == context.texture,
+ icon: t2.img,
+ marked: t2.uuid == context.texture,
click() {
- applyTexture(t.uuid, "Apply texture to collection");
+ applyTexture(t2.uuid, "Apply texture to collection");
}
});
});
@@ -1308,7 +1517,7 @@
let collections = Collection.selected.slice();
let remove_elements = [];
let remove_groups = [];
- let textures = [];
+ let textures3 = [];
let texture_groups = [];
for (let collection of collections) {
if (collection.export_codec === "blockymodel") {
@@ -1318,8 +1527,8 @@
}
let texture_group = TextureGroup.all.find((tg) => tg.name === collection.name);
if (texture_group) {
- let textures2 = Texture.all.filter((t) => t.group === texture_group.uuid);
- textures.safePush(...textures2);
+ let textures22 = Texture.all.filter((t2) => t2.group === texture_group.uuid);
+ textures3.safePush(...textures22);
texture_groups.push(texture_group);
}
}
@@ -1331,13 +1540,13 @@
outliner: true,
// @ts-expect-error
texture_groups,
- textures
+ textures: textures3
});
collections.forEach((c) => Collection.all.remove(c));
collections.empty();
- textures.forEach((t) => t.remove(true));
- textures.empty();
- texture_groups.forEach((t) => t.remove());
+ textures3.forEach((t2) => t2.remove(true));
+ textures3.empty();
+ texture_groups.forEach((t2) => t2.remove());
texture_groups.empty();
remove_groups.forEach((group) => group.remove());
remove_groups.empty();
@@ -1349,46 +1558,56 @@
});
track(shared_delete);
let import_as_attachment = new Action("import_as_hytale_attachment", {
- name: "Import Attachment",
+ name: t("menu.import_attachment"),
icon: "fa-hat-cowboy",
condition: { formats: FORMAT_IDS },
click() {
- Filesystem.importFile({
- extensions: ["blockymodel"],
- type: "Blockymodel",
- multiple: true,
- startpath: Project.export_path.replace(/[\\\/]\w+.\w+$/, "") + osfs + "Attachments"
- }, (files) => {
- for (let file of files) {
- let json = autoParseJSON(file.content);
- let attachment_name = file.name.replace(/\.\w+$/, "");
- let content = Codecs.blockymodel.parse(json, file.path, { attachment: attachment_name });
- let name = file.name.split(".")[0];
- let new_groups = content.new_groups;
- let root_groups = new_groups.filter((group) => !new_groups.includes(group.parent));
- let collection = new Collection({
- name,
- children: root_groups.map((g) => g.uuid),
- export_codec: "blockymodel",
- visibility: true
- }).add();
- collection.export_path = file.path;
- let texturesToProcess = content.new_textures;
- if (texturesToProcess.length === 0) {
- let dirname = PathModule.dirname(file.path);
- let texturePaths = discoverTexturePaths(dirname, attachment_name);
- for (let texPath of texturePaths) {
- let tex = new Texture().fromPath(texPath).add(false);
- texturesToProcess.push(tex);
+ Filesystem.importFile(
+ {
+ extensions: ["blockymodel"],
+ type: "Blockymodel",
+ multiple: true,
+ startpath: Project.export_path.replace(/[\\\/]\w+.\w+$/, "") + osfs + "Attachments"
+ },
+ (files) => {
+ for (let file of files) {
+ let json = autoParseJSON(file.content);
+ let attachment_name = file.name.replace(/\.\w+$/, "");
+ let content = Codecs.blockymodel.parse(json, file.path, {
+ attachment: attachment_name
+ });
+ let name = file.name.split(".")[0];
+ let new_groups = content.new_groups;
+ let root_groups = new_groups.filter(
+ (group) => !new_groups.includes(group.parent)
+ );
+ let collection = new Collection({
+ name,
+ children: root_groups.map((g) => g.uuid),
+ export_codec: "blockymodel",
+ visibility: true
+ }).add();
+ collection.export_path = file.path;
+ let texturesToProcess = content.new_textures;
+ if (texturesToProcess.length === 0) {
+ let dirname = PathModule.dirname(file.path);
+ let texturePaths = discoverTexturePaths(dirname, attachment_name);
+ for (let texPath of texturePaths) {
+ let tex = new Texture().fromPath(texPath).add(false);
+ texturesToProcess.push(tex);
+ }
}
+ let textureUuid = processAttachmentTextures(
+ attachment_name,
+ texturesToProcess
+ );
+ if (textureUuid) {
+ collection.texture = textureUuid;
+ }
+ Canvas.updateAllFaces();
}
- let textureUuid = processAttachmentTextures(attachment_name, texturesToProcess);
- if (textureUuid) {
- collection.texture = textureUuid;
- }
- Canvas.updateAllFaces();
}
- });
+ );
}
});
track(import_as_attachment);
@@ -1410,7 +1629,7 @@
});
}
let reload_attachment_action = new Action("reload_hytale_attachment", {
- name: "Reload Attachment",
+ name: t("actions.reload_attachments"),
icon: "refresh",
condition: () => Collection.selected.length && Modes.edit,
click() {
@@ -1422,7 +1641,7 @@
Collection.menu.addAction(reload_attachment_action, 10);
track(reload_attachment_action);
reload_all_attachments = new Action("reload_all_hytale_attachments", {
- name: "Reload All Attachments",
+ name: t("actions.reload_all_attachments"),
icon: "sync",
condition: { formats: FORMAT_IDS },
click() {
@@ -1474,13 +1693,13 @@
default: true
});
track(property);
- function weightedCubicBezier(t) {
+ function weightedCubicBezier(t2) {
let P0 = 0, P1 = 0.05, P2 = 0.95, P3 = 1;
let W0 = 2, W1 = 1, W2 = 2, W3 = 1;
- let b0 = (1 - t) ** 3;
- let b1 = 3 * (1 - t) ** 2 * t;
- let b2 = 3 * (1 - t) * t ** 2;
- let b3 = t ** 3;
+ let b0 = (1 - t2) ** 3;
+ let b1 = 3 * (1 - t2) ** 2 * t2;
+ let b2 = 3 * (1 - t2) * t2 ** 2;
+ let b3 = t2 ** 3;
let w0 = b0 * W0;
let w1 = b1 * W1;
let w2 = b2 * W2;
@@ -1597,7 +1816,7 @@
});
track(is_piece_property);
let add_quad_action = new Action("hytale_add_quad", {
- name: "Add Quad",
+ name: t("action.hytale_add_quad.name"),
icon: "highlighter_size_5",
category: "edit",
condition: { formats: FORMAT_IDS, modes: ["edit"] },
@@ -1653,7 +1872,7 @@
UVEditor.getSelectedFaces(base_quad, true).replace([fkey]);
base_quad.select();
Canvas.updateView({ elements: [base_quad], element_aspects: { transform: true, geometry: true, faces: true } });
- Undo.finishEdit("Add quad", { outliner: true, elements: selected, selection: true });
+ Undo.finishEdit(t("action.hytale_add_quad.name"), { outliner: true, elements: selected, selection: true });
Vue.nextTick(function() {
if (settings.create_rename.value) {
base_quad.rename();
@@ -1935,8 +2154,9 @@
// src/photoshop_copy_paste.ts
function setupPhotoshopTools() {
let setting = new Setting("copy_paste_magenta_alpha", {
- name: "Copy-Paste with Magenta Alpha",
- description: "Copy image selections with magenta background and remove magenta when pasting to help transfer transparency to Photoshop",
+ name: t("settings.photoshop_copy.name"),
+ description: t("settings.photoshop_copy.description"),
+ category: "paint",
type: "toggle",
category: "paint",
value: false
@@ -1964,7 +2184,11 @@
copy_canvas.width = rect.width;
copy_canvas.height = rect.height;
selection.maskCanvas(copy_ctx, [rect.start_x, rect.start_y]);
- copy_ctx.drawImage(canvas, -rect.start_x + offset[0], -rect.start_y + offset[1]);
+ copy_ctx.drawImage(
+ canvas,
+ -rect.start_x + offset[0],
+ -rect.start_y + offset[1]
+ );
Clipbench.image = {
x: rect.start_x,
y: rect.start_y,
@@ -1996,7 +2220,9 @@
});
}
if (cut) {
- SharedActions.runSpecific("delete", "image_content", event, { message: "Cut texture selection" });
+ SharedActions.runSpecific("delete", "image_content", event, {
+ message: t("actions.cut_selection")
+ });
}
}
});
@@ -2017,7 +2243,10 @@
}
let offset;
if (Clipbench.image) {
- offset = [Math.clamp(Clipbench.image.x, 0, texture.width), Math.clamp(Clipbench.image.y, 0, texture.height)];
+ offset = [
+ Math.clamp(Clipbench.image.x, 0, texture.width),
+ Math.clamp(Clipbench.image.y, 0, texture.height)
+ ];
offset[0] = Math.clamp(offset[0], 0, texture.width - frame.width);
offset[1] = Math.clamp(offset[1], 0, texture.height - frame.height);
}
@@ -2025,8 +2254,16 @@
if (old_frame || texture.currentFrame) {
offset[1] += texture.display_height * ((texture.currentFrame || 0) - old_frame);
}
- let layer = new TextureLayer({ name: "pasted", offset }, texture);
- let image_data = frame.ctx.getImageData(0, 0, frame.width, frame.height);
+ let layer = new TextureLayer(
+ { name: t("actions.pasted_layer_name"), offset },
+ texture
+ );
+ let image_data = frame.ctx.getImageData(
+ 0,
+ 0,
+ frame.width,
+ frame.height
+ );
for (let i = 0; i < image_data.data.length; i += 4) {
if (image_data.data[i] == 255 && image_data.data[i + 1] == 0 && image_data.data[i + 2] == 255) {
image_data.data[i + 0] = 0;
@@ -2041,8 +2278,9 @@
layer.addForEditing();
layer.setLimbo();
texture.updateChangesAfterEdit();
- Undo.finishEdit("Paste into texture");
- if (Toolbox.selected.id != "selection_tool") BarItems.move_layer_tool.select();
+ Undo.finishEdit(t("actions.paste_texture"));
+ if (Toolbox.selected.id != "selection_tool")
+ BarItems.move_layer_tool.select();
updateInterfacePanels();
BARS.updateConditions();
}
@@ -2127,7 +2365,10 @@
size
];
let geometry = new THREE.BufferGeometry();
- geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3));
+ geometry.setAttribute(
+ "position",
+ new THREE.Float32BufferAttribute(vertices, 3)
+ );
let material = new THREE.LineBasicMaterial({ vertexColors: true });
super(geometry, material);
this.updateColors();
@@ -2156,7 +2397,10 @@
...gizmo_colors.b.toArray(),
...gizmo_colors.b.toArray()
];
- this.geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
+ this.geometry.setAttribute(
+ "color",
+ new THREE.Float32BufferAttribute(colors, 3)
+ );
}
};
ThickLineAxisHelper.prototype.constructor = ThickLineAxisHelper;
@@ -2184,8 +2428,8 @@
setting;
constructor() {
this.setting = new Setting("show_group_pivot_indicator", {
- name: "Show Group Pivot Indicator",
- description: "Show a dot in Edit mode indicating the rotation pivot point for animations",
+ name: t("settings.group_pivot.name"),
+ description: t("settings.group_pivot.description"),
category: "preview",
type: "toggle",
value: true
@@ -2202,7 +2446,10 @@
this.dot.visible = false;
Canvas.scene.add(this.dot);
this.listener = Blockbench.on("update_selection", () => this.update());
- this.cameraListener = Blockbench.on("update_camera_position", () => this.updateScale());
+ this.cameraListener = Blockbench.on(
+ "update_camera_position",
+ () => this.updateScale()
+ );
this.update();
}
updateScale() {
@@ -2228,7 +2475,9 @@
this.dot.visible = false;
return;
}
- this.dot.material.color.copy(this.getAccentColor());
+ this.dot.material.color.copy(
+ this.getAccentColor()
+ );
let mesh = group.mesh;
if (mesh) {
let worldPos = new THREE.Vector3();
@@ -2362,7 +2611,7 @@
`);
StateMemory.init("hytale_attachments_hidden", "boolean");
attachmentsHidden = StateMemory.get("hytale_attachments_hidden") ?? false;
- let toggle = new Toggle("toggle_attachment_editing", {
+ let toggle3 = new Toggle("toggle_attachment_editing", {
name: "Toggle Attachment Editing",
description: "Lock or unlock attachment elements for editing",
icon: "fa-paperclip",
@@ -2377,7 +2626,7 @@
});
let outlinerPanel = Panels.outliner;
if (outlinerPanel && outlinerPanel.toolbars.length > 0) {
- outlinerPanel.toolbars[0].add(toggle, -1);
+ outlinerPanel.toolbars[0].add(toggle3, -1);
}
let hookFinishedEdit = Blockbench.on("finished_edit", scheduleVisibilityUpdate);
let hookSelectMode = Blockbench.on("select_mode", scheduleVisibilityUpdate);
@@ -2385,7 +2634,7 @@
if (attachmentsHidden) {
setTimeout(applyOutlinerVisibility, 100);
}
- track(toggle, hookFinishedEdit, hookSelectMode, hookSelection, style, {
+ track(toggle3, hookFinishedEdit, hookSelectMode, hookSelection, style, {
delete() {
Panels.outliner?.node?.querySelectorAll(`.${HIDDEN_CLASS}`).forEach((el) => {
el.classList.remove(HIDDEN_CLASS);
@@ -3170,7 +3419,7 @@ body.hytale-uv-outline-only #uv_frame .selection_rectangle {
author: "JannisX11, Kanno",
icon: "icon.png",
version: package_default.version,
- description: "Create models and animations for Hytale",
+ description: t("plugin.description"),
tags: ["Hytale"],
variant: "both",
min_version: "5.0.5",
diff --git a/src/attachments.ts b/src/attachments.ts
index 0c77446..d93e586 100644
--- a/src/attachments.ts
+++ b/src/attachments.ts
@@ -9,6 +9,7 @@ import {
setupAttachmentTextures,
processAttachmentTextures,
} from "./attachment_texture";
+import { t } from "./i18n";
export { AttachmentCollection } from "./attachment_texture";
export let reload_all_attachments: Action;
@@ -74,55 +75,69 @@ export function setupAttachments() {
});
track(shared_delete);
- let import_as_attachment = new Action('import_as_hytale_attachment', {
- name: 'Import Attachment',
- icon: 'fa-hat-cowboy',
- condition: {formats: FORMAT_IDS},
- click() {
- Filesystem.importFile({
- extensions: ['blockymodel'],
- type: 'Blockymodel',
- multiple: true,
- startpath: Project.export_path.replace(/[\\\/]\w+.\w+$/, '') + osfs + 'Attachments'
- }, (files) => {
- for (let file of files) {
- let json = autoParseJSON(file.content as string);
- let attachment_name = file.name.replace(/\.\w+$/, '');
- let content: any = Codecs.blockymodel.parse(json, file.path, {attachment: attachment_name});
- let name = file.name.split('.')[0]
-
- let new_groups = content.new_groups as Group[];
- let root_groups = new_groups.filter(group => !new_groups.includes(group.parent as Group));
-
- let collection = new Collection({
- name,
- children: root_groups.map(g => g.uuid),
- export_codec: 'blockymodel',
- visibility: true,
- }).add() as AttachmentCollection;
- collection.export_path = file.path;
-
- let texturesToProcess: Texture[] = content.new_textures as Texture[];
-
- if (texturesToProcess.length === 0) {
- let dirname = PathModule.dirname(file.path);
- let texturePaths = discoverTexturePaths(dirname, attachment_name);
- for (let texPath of texturePaths) {
- let tex = new Texture().fromPath(texPath).add(false);
- texturesToProcess.push(tex);
- }
- }
-
- let textureUuid = processAttachmentTextures(attachment_name, texturesToProcess);
- if (textureUuid) {
- collection.texture = textureUuid;
- }
-
- Canvas.updateAllFaces();
- }
- })
- }
- });
+ let import_as_attachment = new Action("import_as_hytale_attachment", {
+ name: t("menu.import_attachment"),
+ icon: "fa-hat-cowboy",
+ condition: { formats: FORMAT_IDS },
+ click() {
+ Filesystem.importFile(
+ {
+ extensions: ["blockymodel"],
+ type: "Blockymodel",
+ multiple: true,
+ startpath:
+ Project.export_path.replace(/[\\\/]\w+.\w+$/, "") +
+ osfs +
+ "Attachments",
+ },
+ (files) => {
+ for (let file of files) {
+ let json = autoParseJSON(file.content as string);
+ let attachment_name = file.name.replace(/\.\w+$/, "");
+ let content: any = Codecs.blockymodel.parse(json, file.path, {
+ attachment: attachment_name,
+ });
+ let name = file.name.split(".")[0];
+
+ let new_groups = content.new_groups as Group[];
+ let root_groups = new_groups.filter(
+ (group) => !new_groups.includes(group.parent as Group)
+ );
+
+ let collection = new Collection({
+ name,
+ children: root_groups.map((g) => g.uuid),
+ export_codec: "blockymodel",
+ visibility: true,
+ }).add() as AttachmentCollection;
+ collection.export_path = file.path;
+
+ let texturesToProcess: Texture[] =
+ content.new_textures as Texture[];
+
+ if (texturesToProcess.length === 0) {
+ let dirname = PathModule.dirname(file.path);
+ let texturePaths = discoverTexturePaths(dirname, attachment_name);
+ for (let texPath of texturePaths) {
+ let tex = new Texture().fromPath(texPath).add(false);
+ texturesToProcess.push(tex);
+ }
+ }
+
+ let textureUuid = processAttachmentTextures(
+ attachment_name,
+ texturesToProcess
+ );
+ if (textureUuid) {
+ collection.texture = textureUuid;
+ }
+
+ Canvas.updateAllFaces();
+ }
+ }
+ );
+ },
+ });
track(import_as_attachment);
let toolbar = Panels.collections.toolbars[0];
toolbar.add(import_as_attachment);
@@ -148,7 +163,7 @@ export function setupAttachments() {
}
let reload_attachment_action = new Action('reload_hytale_attachment', {
- name: 'Reload Attachment',
+ name: t('actions.reload_attachments'),
icon: 'refresh',
condition: () => Collection.selected.length && Modes.edit,
click() {
@@ -161,7 +176,7 @@ export function setupAttachments() {
track(reload_attachment_action);
reload_all_attachments = new Action('reload_all_hytale_attachments', {
- name: 'Reload All Attachments',
+ name: t('actions.reload_all_attachments'),
icon: 'sync',
condition: {formats: FORMAT_IDS},
click() {
diff --git a/src/blockymodel.ts b/src/blockymodel.ts
index 7693c48..f7d5974 100644
--- a/src/blockymodel.ts
+++ b/src/blockymodel.ts
@@ -5,6 +5,7 @@ import { parseAnimationFile } from "./blockyanim"
import { track } from "./cleanup"
import { Config } from "./config"
import { FORMAT_IDS } from "./formats"
+import { t } from "./i18n"
import { cubeIsQuad, getMainShape, qualifiesAsMainShape } from "./util"
type BlockymodelJSON = {
@@ -131,9 +132,9 @@ function loadTexturesFromPaths(paths: string[], preferredName?: string): Texture
*/
function promptForTextures(dirname: string) {
Blockbench.showMessageBox({
- title: 'Import Textures',
- message: 'No textures were found for this model. How would you like to import textures?',
- buttons: ['Select Files', 'Select Folder', 'Skip'],
+ title: t('textures.import'),
+ message: t('textures.message'),
+ buttons: [t('textures.select_files'), t('textures.select_folder'), t('textures.skip')],
}, (choice) => {
let project = Project;
if (choice === 2 || !project) return;
@@ -155,17 +156,17 @@ function promptForTextures(dirname: string) {
} else if (choice === 1) {
// Select folder containing textures
let folderPath = Blockbench.pickDirectory({
- title: 'Select Texture Folder',
- startpath: dirname,
- resource_id: 'texture'
- });
+ title: t("directory.select_textures_folder"),
+ startpath: dirname,
+ resource_id: "texture",
+ });
if (folderPath && Project === project) {
let fs = requireNativeModule('fs');
let files = fs.readdirSync(folderPath);
let pngFiles = files.filter((f: string) => f.match(/\.png$/i));
if (pngFiles.length === 0) {
- Blockbench.showQuickMessage('No PNG files found in selected folder');
+ Blockbench.showQuickMessage(t('textures.no_textures_found'), 3000);
return;
}
@@ -827,8 +828,8 @@ export function setupBlockymodelCodec(): Codec {
},
})
let export_action = new Action('export_blockymodel', {
- name: 'Export Hytale Blockymodel',
- description: 'Export a blockymodel file',
+ name: t('action.export_blockymodel'),
+ description: t('action.export_blockymodel_desc'),
icon: 'icon-format_hytale',
category: 'file',
condition: {formats: FORMAT_IDS},
diff --git a/src/element.ts b/src/element.ts
index 0f0b586..e535a8c 100644
--- a/src/element.ts
+++ b/src/element.ts
@@ -2,6 +2,8 @@
//! Licensed under the GNU General Public License, see LICENSE.MD
import { track } from "./cleanup";
+
+import { t } from "./i18n";
import { FORMAT_IDS, isHytaleFormat } from "./formats";
import { cubeIsQuad } from "./util";
@@ -115,7 +117,7 @@ export function setupElements() {
track(is_piece_property);
let add_quad_action = new Action('hytale_add_quad', {
- name: 'Add Quad',
+ name: t('action.hytale_add_quad.name'),
icon: 'highlighter_size_5',
category: 'edit',
condition: {formats: FORMAT_IDS, modes: ['edit']},
@@ -178,7 +180,7 @@ export function setupElements() {
base_quad.select();
Canvas.updateView({elements: [base_quad], element_aspects: {transform: true, geometry: true, faces: true}})
- Undo.finishEdit('Add quad', {outliner: true, elements: selected, selection: true});
+ Undo.finishEdit(t('action.hytale_add_quad.name'), {outliner: true, elements: selected, selection: true});
Vue.nextTick(function() {
if (settings.create_rename.value) {
diff --git a/src/formats.ts b/src/formats.ts
index 42342a8..9dd2424 100644
--- a/src/formats.ts
+++ b/src/formats.ts
@@ -3,6 +3,7 @@
import { setupBlockymodelCodec } from "./blockymodel";
import { track } from "./cleanup";
+import { t } from "./i18n";
export const FORMAT_IDS = [
'hytale_character',
@@ -16,7 +17,6 @@ export function setupFormats() {
category: 'hytale',
target: 'Hytale',
codec,
-
forward_direction: '+z',
single_texture_default: true,
animation_files: true,
@@ -42,41 +42,36 @@ export function setupFormats() {
}
}
let format_page: FormatPage = {
- content: [
- {type: 'h3', text: tl('mode.start.format.informations')},
- {text: `* One texture can be applied to a model at a time
- * UV sizes are linked to the size of each cube and cannot be modified, except by stretching the cube
- * Models can have a maximum of 255 nodes`.replace(/(\t| {4,4})+/g, '')
- },
- {type: 'h3', text: tl('mode.start.format.resources')},
- {text: ['* [Modeling Overview and Style Guide](https://hytale.com/news/2025/12/an-introduction-to-making-models-for-hytale)',
- '* [Modeling Tutorial](https://youtu.be/Q07i3wmGy0Y)'].join('\n')
- }
- ]
+ content: [
+ { type: "h3", text: t("formats.page.informations") },
+ { text: t("formats.page.info_list") },
+ { type: "h3", text: t("formats.page.resources") },
+ { text: t("formats.page.resource_list") },
+ ],
};
- let format_character = new ModelFormat('hytale_character', {
- name: 'Hytale Character',
- description: 'Create character and attachment models using Hytale\'s blockymodel format',
- icon: 'icon-format_hytale',
- format_page,
- block_size: 64,
- ...common
- // TODO: Auto-reload attachments on tab switch. Needs dirty tracking and setting toggle to avoid losing unsaved changes
- /*
+ let format_character = new ModelFormat("hytale_character", {
+ name: t("formats.character.name"),
+ description: t("formats.character.description"),
+ icon: "icon-format_hytale",
+ format_page,
+ block_size: 64,
+ ...common,
+ // TODO: Auto-reload attachments on tab switch. Needs dirty tracking and setting toggle to avoid losing unsaved changes
+ /*
onActivation() {
common.onActivation?.();
setTimeout(() => reload_all_attachments?.click(), 0);
}
*/
});
- let format_prop = new ModelFormat('hytale_prop', {
- name: 'Hytale Prop',
- description: 'Create prop models using Hytale\'s blockymodel format',
- icon: 'icon-format_hytale',
- format_page,
- block_size: 32,
- ...common
+ let format_prop = new ModelFormat("hytale_prop", {
+ name: t("formats.prop.name"),
+ description: t("formats.prop.description"),
+ icon: "icon-format_hytale",
+ format_page,
+ block_size: 64,
+ ...common,
});
// Integer size
@@ -100,9 +95,9 @@ export function setupFormats() {
track(format_character);
track(format_prop);
- Language.addTranslations('en', {
- 'format_category.hytale': 'Hytale'
- })
+ Language.addTranslations("en", {
+ "format_category.hytale": t("formats.category"),
+ });
}
export function isHytaleFormat() {
diff --git a/src/i18n.ts b/src/i18n.ts
new file mode 100644
index 0000000..eb5e74d
--- /dev/null
+++ b/src/i18n.ts
@@ -0,0 +1,31 @@
+///
+
+
+import * as en from "./translations/en.json";
+import * as pt from "./translations/pt-BR.json";
+
+type TranslationSchema = typeof en;
+
+
+const translations: Record = {
+ en,
+ pt,
+};
+
+export function t(path: string): string {
+ const langCode = Language.code.split("_")[0];
+
+ const currentLangObj = translations[langCode] || translations["en"];
+ const resolvePath = (obj: any, path: string) => {
+ return path.split(".").reduce((prev, curr) => {
+ return prev ? prev[curr] : null;
+ }, obj);
+ };
+
+ let result = resolvePath(currentLangObj, path);
+ if (!result && langCode !== "en") {
+ result = resolvePath(translations["en"], path);
+ }
+
+ return result || path;
+}
diff --git a/src/name_overlap.ts b/src/name_overlap.ts
index a5360b0..4b9845a 100644
--- a/src/name_overlap.ts
+++ b/src/name_overlap.ts
@@ -3,6 +3,7 @@
import { track } from "./cleanup";
import { isHytaleFormat } from "./formats";
+import { t } from "./i18n";
// @ts-expect-error
const Animation = window.Animation as typeof _Animation;
@@ -89,13 +90,13 @@ export function setupNameOverlap() {
}
})
- let setting = new Setting('hytale_duplicate_bone_names', {
- name: 'Duplicate Bone Names',
- category: 'edit',
- description: 'Allow creating duplicate groups names in Hytale formats. Multiple groups with the same name can be used to apply animations to multiple nodes at once.',
- type: 'toggle',
- value: false
- })
+ let setting = new Setting("hytale_duplicate_bone_names", {
+ name: t("settings.duplicate_bones.name"),
+ category: "edit",
+ description: t("settings.duplicate_bones.description"),
+ type: "toggle",
+ value: false,
+ });
let override = Group.addBehaviorOverride({
condition: () => isHytaleFormat() && setting.value == true,
// @ts-ignore
diff --git a/src/photoshop_copy_paste.ts b/src/photoshop_copy_paste.ts
index d68f47d..c78577f 100644
--- a/src/photoshop_copy_paste.ts
+++ b/src/photoshop_copy_paste.ts
@@ -3,162 +3,199 @@
import { track } from "./cleanup";
import { FORMAT_IDS } from "./formats";
+import { t } from "./i18n";
declare global {
const Clipbench: any
}
export function setupPhotoshopTools() {
-
- let setting = new Setting('copy_paste_magenta_alpha', {
- name: 'Copy-Paste with Magenta Alpha',
- description: 'Copy image selections with magenta background and remove magenta when pasting to help transfer transparency to Photoshop',
- type: 'toggle',
- category: 'paint',
- value: false
- })
- track(setting);
-
- let shared_copy = SharedActions.add('copy', {
- subject: 'image_content_photoshop',
- condition: () => Prop.active_panel == 'uv' && Modes.paint && Texture.getDefault() && FORMAT_IDS.includes(Format.id) && setting.value == true,
- priority: 2,
- run(event, cut) {
- let texture = Texture.getDefault();
- let selection = texture.selection;
-
- let {canvas, ctx, offset} = texture.getActiveCanvas();
-
- if (selection.override != null) {
- Clipbench.image = {
- x: offset[0], y: offset[1],
- frame: texture.currentFrame,
- data: ''
- }
- } else {
- let rect = selection.getBoundingRect();
- let copy_canvas = document.createElement('canvas');
- let copy_ctx = copy_canvas.getContext('2d');
- copy_canvas.width = rect.width;
- copy_canvas.height = rect.height;
-
- selection.maskCanvas(copy_ctx, [rect.start_x, rect.start_y]);
- copy_ctx.drawImage(canvas, -rect.start_x + offset[0], -rect.start_y + offset[1]);
-
- Clipbench.image = {
- x: rect.start_x,
- y: rect.start_y,
- frame: texture.currentFrame,
- data: ''
- }
- canvas = copy_canvas;
- }
-
- // Add pink background
- let canvas_copy_magenta = document.createElement('canvas');
- let copy_ctx_magenta = canvas_copy_magenta.getContext('2d');
- canvas_copy_magenta.width = canvas.width;
- canvas_copy_magenta.height = canvas.height;
- copy_ctx_magenta.fillStyle = '#ff00ff';
- copy_ctx_magenta.fillRect(0, 0, canvas.width, canvas.height);
- copy_ctx_magenta.drawImage(canvas, 0, 0);
- canvas = canvas_copy_magenta;
-
- Clipbench.image.data = canvas.toDataURL('image/png', 1);
-
- if (isApp) {
- let clipboard = requireNativeModule('clipboard');
- // @ts-expect-error
- let img = nativeImage.createFromDataURL(Clipbench.image.data);
- clipboard.writeImage(img);
- } else {
- canvas.toBlob(blob => {
- navigator.clipboard.write([
- new ClipboardItem({
- [blob.type]: blob,
- }),
- ]);
- });
- }
-
- if (cut) {
- SharedActions.runSpecific('delete', 'image_content', event, {message: 'Cut texture selection'});
- }
+ let setting = new Setting("copy_paste_magenta_alpha", {
+ name: t("settings.photoshop_copy.name"),
+ description: t("settings.photoshop_copy.description"),
+ category: "paint",
+ type: "toggle",
+ value: false,
+ });
+ track(setting);
+
+ let shared_copy = SharedActions.add("copy", {
+ subject: "image_content_photoshop",
+ condition: () =>
+ Prop.active_panel == "uv" &&
+ Modes.paint &&
+ Texture.getDefault() &&
+ FORMAT_IDS.includes(Format.id) &&
+ setting.value == true,
+ priority: 2,
+ run(event, cut) {
+ let texture = Texture.getDefault();
+ let selection = texture.selection;
+
+ let { canvas, ctx, offset } = texture.getActiveCanvas();
+
+ if (selection.override != null) {
+ Clipbench.image = {
+ x: offset[0],
+ y: offset[1],
+ frame: texture.currentFrame,
+ data: "",
+ };
+ } else {
+ let rect = selection.getBoundingRect();
+ let copy_canvas = document.createElement("canvas");
+ let copy_ctx = copy_canvas.getContext("2d");
+ copy_canvas.width = rect.width;
+ copy_canvas.height = rect.height;
+
+ selection.maskCanvas(copy_ctx, [rect.start_x, rect.start_y]);
+ copy_ctx.drawImage(
+ canvas,
+ -rect.start_x + offset[0],
+ -rect.start_y + offset[1]
+ );
+
+ Clipbench.image = {
+ x: rect.start_x,
+ y: rect.start_y,
+ frame: texture.currentFrame,
+ data: "",
+ };
+ canvas = copy_canvas;
+ }
+
+ // Add pink background
+ let canvas_copy_magenta = document.createElement("canvas");
+ let copy_ctx_magenta = canvas_copy_magenta.getContext("2d");
+ canvas_copy_magenta.width = canvas.width;
+ canvas_copy_magenta.height = canvas.height;
+ copy_ctx_magenta.fillStyle = "#ff00ff";
+ copy_ctx_magenta.fillRect(0, 0, canvas.width, canvas.height);
+ copy_ctx_magenta.drawImage(canvas, 0, 0);
+ canvas = canvas_copy_magenta;
+
+ Clipbench.image.data = canvas.toDataURL("image/png", 1);
+
+ if (isApp) {
+ let clipboard = requireNativeModule("clipboard");
+ // @ts-expect-error
+ let img = nativeImage.createFromDataURL(Clipbench.image.data);
+ clipboard.writeImage(img);
+ } else {
+ canvas.toBlob((blob) => {
+ navigator.clipboard.write([
+ new ClipboardItem({
+ [blob.type]: blob,
+ }),
+ ]);
+ });
+ }
+
+ if (cut) {
+ SharedActions.runSpecific("delete", "image_content", event, {
+ message: t("actions.cut_selection"),
+ });
+ }
+ },
+ });
+ track(shared_copy);
+ let shared_paste = SharedActions.add("paste", {
+ subject: "image_content_photoshop",
+ condition: () =>
+ Prop.active_panel == "uv" &&
+ Modes.paint &&
+ Texture.getDefault() &&
+ FORMAT_IDS.includes(Format.id) &&
+ setting.value == true,
+ priority: 2,
+ run(event) {
+ let texture = Texture.getDefault();
+
+ async function loadFromDataUrl(data_url: string) {
+ let frame = new CanvasFrame();
+ await frame.loadFromURL(data_url);
+
+ Undo.initEdit({ textures: [texture], bitmap: true });
+ if (!texture.layers_enabled) {
+ texture.flags.add("temporary_layers");
+ texture.activateLayers(false);
}
- })
- track(shared_copy);
- let shared_paste = SharedActions.add('paste', {
- subject: 'image_content_photoshop',
- condition: () => Prop.active_panel == 'uv' && Modes.paint && Texture.getDefault() && FORMAT_IDS.includes(Format.id) && setting.value == true,
- priority: 2,
- run(event) {
-
- let texture = Texture.getDefault();
-
- async function loadFromDataUrl(data_url: string) {
- let frame = new CanvasFrame();
- await frame.loadFromURL(data_url);
-
- Undo.initEdit({textures: [texture], bitmap: true});
- if (!texture.layers_enabled) {
- texture.flags.add('temporary_layers');
- texture.activateLayers(false);
- }
- let offset;
- if (Clipbench.image) {
- offset = [Math.clamp(Clipbench.image.x, 0, texture.width), Math.clamp(Clipbench.image.y, 0, texture.height)];
- offset[0] = Math.clamp(offset[0], 0, texture.width-frame.width);
- offset[1] = Math.clamp(offset[1], 0, texture.height-frame.height);
- }
- let old_frame = Clipbench.image?.frame || 0;
- if (old_frame || texture.currentFrame) {
- offset[1] += texture.display_height * ((texture.currentFrame||0) - old_frame);
- }
- let layer = new TextureLayer({name: 'pasted', offset}, texture);
- let image_data = frame.ctx.getImageData(0, 0, frame.width, frame.height);
-
- // Filter magenta
- for (let i = 0; i < image_data.data.length; i += 4) {
- if (image_data.data[i] == 255 && image_data.data[i+1] == 0 && image_data.data[i+2] == 255) {
- image_data.data[i+0] = 0;
- image_data.data[i+1] = 0;
- image_data.data[i+2] = 0;
- image_data.data[i+3] = 0;
- }
- }
-
-
- layer.setSize(frame.width, frame.height);
- layer.ctx.putImageData(image_data, 0, 0);
- if (!offset) layer.center();
-
- layer.addForEditing();
- layer.setLimbo();
- texture.updateChangesAfterEdit();
-
- Undo.finishEdit('Paste into texture');
- if (Toolbox.selected.id != 'selection_tool') (BarItems.move_layer_tool as Tool).select();
- updateInterfacePanels();
- BARS.updateConditions();
- }
-
-
- if (isApp) {
- let clipboard = requireNativeModule('clipboard');
- var image = clipboard.readImage().toDataURL();
- loadFromDataUrl(image);
- } else {
- navigator.clipboard.read().then(content => {
- if (content && content[0] && content[0].types.includes('image/png')) {
- content[0].getType('image/png').then(blob => {
- let url = URL.createObjectURL(blob);
- loadFromDataUrl(url);
- })
- }
- }).catch(() => {})
- }
-
+ let offset;
+ if (Clipbench.image) {
+ offset = [
+ Math.clamp(Clipbench.image.x, 0, texture.width),
+ Math.clamp(Clipbench.image.y, 0, texture.height),
+ ];
+ offset[0] = Math.clamp(offset[0], 0, texture.width - frame.width);
+ offset[1] = Math.clamp(offset[1], 0, texture.height - frame.height);
+ }
+ let old_frame = Clipbench.image?.frame || 0;
+ if (old_frame || texture.currentFrame) {
+ offset[1] +=
+ texture.display_height * ((texture.currentFrame || 0) - old_frame);
}
- });
- track(shared_paste);
+ let layer = new TextureLayer(
+ { name: t("actions.pasted_layer_name"), offset },
+ texture
+ );
+ let image_data = frame.ctx.getImageData(
+ 0,
+ 0,
+ frame.width,
+ frame.height
+ );
+
+ // Filter magenta
+ for (let i = 0; i < image_data.data.length; i += 4) {
+ if (
+ image_data.data[i] == 255 &&
+ image_data.data[i + 1] == 0 &&
+ image_data.data[i + 2] == 255
+ ) {
+ image_data.data[i + 0] = 0;
+ image_data.data[i + 1] = 0;
+ image_data.data[i + 2] = 0;
+ image_data.data[i + 3] = 0;
+ }
+ }
+
+ layer.setSize(frame.width, frame.height);
+ layer.ctx.putImageData(image_data, 0, 0);
+ if (!offset) layer.center();
+
+ layer.addForEditing();
+ layer.setLimbo();
+ texture.updateChangesAfterEdit();
+
+ Undo.finishEdit(t("actions.paste_texture"));
+ if (Toolbox.selected.id != "selection_tool")
+ (BarItems.move_layer_tool as Tool).select();
+ updateInterfacePanels();
+ BARS.updateConditions();
+ }
+
+ if (isApp) {
+ let clipboard = requireNativeModule("clipboard");
+ var image = clipboard.readImage().toDataURL();
+ loadFromDataUrl(image);
+ } else {
+ navigator.clipboard
+ .read()
+ .then((content) => {
+ if (
+ content &&
+ content[0] &&
+ content[0].types.includes("image/png")
+ ) {
+ content[0].getType("image/png").then((blob) => {
+ let url = URL.createObjectURL(blob);
+ loadFromDataUrl(url);
+ });
+ }
+ })
+ .catch(() => {});
+ }
+ },
+ });
+ track(shared_paste);
}
diff --git a/src/pivot_marker.ts b/src/pivot_marker.ts
index 822110a..6e64ba1 100644
--- a/src/pivot_marker.ts
+++ b/src/pivot_marker.ts
@@ -1,79 +1,141 @@
//! Copyright (C) 2025 Hypixel Studios Canada inc.
//! Licensed under the GNU General Public License, see LICENSE.MD
+import { t } from "./i18n";
+
declare global {
- const gizmo_colors: Record
+ const gizmo_colors: Record;
}
export const ThickLineAxisHelper = class ThickLineAxisHelper extends THREE.LineSegments {
- constructor( size: number = 1 ) {
-
- let a = 0.04, b = 0.025;
-
- let vertices = [
- 0, a, 0, size, a, 0,
- 0, 0, b, size, 0, b,
- 0, 0, -b, size, 0, -b,
-
- 0, 0, a, 0, size, a,
- b, 0, 0, b, size, 0,
- -b, 0, 0, -b, size, 0,
-
- a, 0, 0, a, 0, size,
- 0, b, 0, 0, b, size,
- 0, -b, 0, 0, -b, size,
- ];
-
- let geometry = new THREE.BufferGeometry();
- geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
- let material = new THREE.LineBasicMaterial( { vertexColors: true } );
-
- super(geometry, material);
- this.updateColors();
-
- material.transparent = true;
- material.depthTest = false;
- this.renderOrder = 800;
- }
- updateColors() {
- let colors = [
- ...gizmo_colors.r.toArray(), ...gizmo_colors.r.toArray(),
- ...gizmo_colors.r.toArray(), ...gizmo_colors.r.toArray(),
- ...gizmo_colors.r.toArray(), ...gizmo_colors.r.toArray(),
-
- ...gizmo_colors.g.toArray(), ...gizmo_colors.g.toArray(),
- ...gizmo_colors.g.toArray(), ...gizmo_colors.g.toArray(),
- ...gizmo_colors.g.toArray(), ...gizmo_colors.g.toArray(),
-
- ...gizmo_colors.b.toArray(), ...gizmo_colors.b.toArray(),
- ...gizmo_colors.b.toArray(), ...gizmo_colors.b.toArray(),
- ...gizmo_colors.b.toArray(), ...gizmo_colors.b.toArray(),
- ]
- this.geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
- }
-}
+ constructor(size: number = 1) {
+ let a = 0.04,
+ b = 0.025;
+
+ let vertices = [
+ 0,
+ a,
+ 0,
+ size,
+ a,
+ 0,
+ 0,
+ 0,
+ b,
+ size,
+ 0,
+ b,
+ 0,
+ 0,
+ -b,
+ size,
+ 0,
+ -b,
+
+ 0,
+ 0,
+ a,
+ 0,
+ size,
+ a,
+ b,
+ 0,
+ 0,
+ b,
+ size,
+ 0,
+ -b,
+ 0,
+ 0,
+ -b,
+ size,
+ 0,
+
+ a,
+ 0,
+ 0,
+ a,
+ 0,
+ size,
+ 0,
+ b,
+ 0,
+ 0,
+ b,
+ size,
+ 0,
+ -b,
+ 0,
+ 0,
+ -b,
+ size,
+ ];
+
+ let geometry = new THREE.BufferGeometry();
+ geometry.setAttribute(
+ "position",
+ new THREE.Float32BufferAttribute(vertices, 3)
+ );
+ let material = new THREE.LineBasicMaterial({ vertexColors: true });
+
+ super(geometry, material);
+ this.updateColors();
+
+ material.transparent = true;
+ material.depthTest = false;
+ this.renderOrder = 800;
+ }
+ updateColors() {
+ let colors = [
+ ...gizmo_colors.r.toArray(),
+ ...gizmo_colors.r.toArray(),
+ ...gizmo_colors.r.toArray(),
+ ...gizmo_colors.r.toArray(),
+ ...gizmo_colors.r.toArray(),
+ ...gizmo_colors.r.toArray(),
+
+ ...gizmo_colors.g.toArray(),
+ ...gizmo_colors.g.toArray(),
+ ...gizmo_colors.g.toArray(),
+ ...gizmo_colors.g.toArray(),
+ ...gizmo_colors.g.toArray(),
+ ...gizmo_colors.g.toArray(),
+
+ ...gizmo_colors.b.toArray(),
+ ...gizmo_colors.b.toArray(),
+ ...gizmo_colors.b.toArray(),
+ ...gizmo_colors.b.toArray(),
+ ...gizmo_colors.b.toArray(),
+ ...gizmo_colors.b.toArray(),
+ ];
+ this.geometry.setAttribute(
+ "color",
+ new THREE.Float32BufferAttribute(colors, 3)
+ );
+ }
+};
ThickLineAxisHelper.prototype.constructor = ThickLineAxisHelper;
-
export class CustomPivotMarker {
- original_helpers: THREE.LineSegments[];
-
- constructor() {
- this.original_helpers = Canvas.pivot_marker.children.slice() as THREE.LineSegments[];
- let [helper1, helper2] = this.original_helpers;
- let helper1_new = new ThickLineAxisHelper(1);
- let helper2_new = new ThickLineAxisHelper(1);
- helper1_new.rotation.copy(helper1.rotation);
- helper2_new.rotation.copy(helper2.rotation);
-
- Canvas.pivot_marker.children.empty();
- Canvas.pivot_marker.add(helper1_new, helper2_new);
- }
-
- delete() {
- Canvas.pivot_marker.children.empty();
- Canvas.pivot_marker.add(...this.original_helpers);
- }
+ original_helpers: THREE.LineSegments[];
+
+ constructor() {
+ this.original_helpers =
+ Canvas.pivot_marker.children.slice() as THREE.LineSegments[];
+ let [helper1, helper2] = this.original_helpers;
+ let helper1_new = new ThickLineAxisHelper(1);
+ let helper2_new = new ThickLineAxisHelper(1);
+ helper1_new.rotation.copy(helper1.rotation);
+ helper2_new.rotation.copy(helper2.rotation);
+
+ Canvas.pivot_marker.children.empty();
+ Canvas.pivot_marker.add(helper1_new, helper2_new);
+ }
+
+ delete() {
+ Canvas.pivot_marker.children.empty();
+ Canvas.pivot_marker.add(...this.original_helpers);
+ }
}
/**
@@ -82,101 +144,108 @@ export class CustomPivotMarker {
* shows at the parent group's pivot.
*/
export class GroupPivotIndicator {
- dot: THREE.Mesh;
- listener: Deletable;
- cameraListener: Deletable;
- setting: Setting;
-
- constructor() {
- this.setting = new Setting('show_group_pivot_indicator', {
- name: 'Show Group Pivot Indicator',
- description: 'Show a dot in Edit mode indicating the rotation pivot point for animations',
- category: 'preview',
- type: 'toggle',
- value: true
- });
-
- let geometry = new THREE.SphereGeometry(0.65, 12, 12);
- let material = new THREE.MeshBasicMaterial({
- color: this.getAccentColor(),
- transparent: true,
- opacity: 0.9,
- depthTest: false
- });
- this.dot = new THREE.Mesh(geometry, material);
- this.dot.renderOrder = 900;
- this.dot.visible = false;
-
- Canvas.scene.add(this.dot);
-
- this.listener = Blockbench.on('update_selection', () => this.update());
- this.cameraListener = Blockbench.on('update_camera_position', () => this.updateScale());
- this.update();
- }
-
- updateScale() {
- if (!this.dot.visible) return;
- // @ts-expect-error
- let scale = Preview.selected.calculateControlScale(this.dot.position) || 0.8;
- this.dot.scale.setScalar(scale * 0.7);
- }
-
- getAccentColor(): THREE.Color {
- let cssColor = getComputedStyle(document.body).getPropertyValue('--color-accent').trim();
- return new THREE.Color(cssColor || '#3e90ff');
- }
-
- update() {
- if (!this.setting.value) {
- this.dot.visible = false;
- return;
- }
- if (Modes.paint) {
- this.dot.visible = false;
- return;
- }
-
- let group = this.getRelevantGroup();
- if (!group) {
- this.dot.visible = false;
- return;
- }
-
- // Update color in case accent changed
- (this.dot.material as THREE.MeshBasicMaterial).color.copy(this.getAccentColor());
-
- // Get world position of group pivot
- let mesh = group.mesh;
- if (mesh) {
- let worldPos = new THREE.Vector3();
- mesh.getWorldPosition(worldPos);
- this.dot.position.copy(worldPos);
- this.dot.visible = true;
- this.updateScale();
- } else {
- this.dot.visible = false;
- }
- }
-
- getRelevantGroup(): Group | null {
- let sel = Outliner.selected[0];
- if (!sel) return null;
-
- if (sel instanceof Group) {
- return sel;
- }
- if (sel.parent instanceof Group) {
- return sel.parent;
- }
- return null;
- }
-
- delete() {
- Canvas.scene.remove(this.dot);
- this.dot.geometry.dispose();
- (this.dot.material as THREE.MeshBasicMaterial).dispose();
- this.listener.delete();
- this.cameraListener.delete();
- this.setting.delete();
- }
+ dot: THREE.Mesh;
+ listener: Deletable;
+ cameraListener: Deletable;
+ setting: Setting;
+
+ constructor() {
+ this.setting = new Setting("show_group_pivot_indicator", {
+ name: t("settings.group_pivot.name"),
+ description: t("settings.group_pivot.description"),
+ category: "preview",
+ type: "toggle",
+ value: true,
+ });
+
+ let geometry = new THREE.SphereGeometry(0.65, 12, 12);
+ let material = new THREE.MeshBasicMaterial({
+ color: this.getAccentColor(),
+ transparent: true,
+ opacity: 0.9,
+ depthTest: false,
+ });
+ this.dot = new THREE.Mesh(geometry, material);
+ this.dot.renderOrder = 900;
+ this.dot.visible = false;
+
+ Canvas.scene.add(this.dot);
+
+ this.listener = Blockbench.on("update_selection", () => this.update());
+ this.cameraListener = Blockbench.on("update_camera_position", () =>
+ this.updateScale()
+ );
+ this.update();
+ }
+
+ updateScale() {
+ if (!this.dot.visible) return;
+ // @ts-expect-error
+ let scale =
+ Preview.selected.calculateControlScale(this.dot.position) || 0.8;
+ this.dot.scale.setScalar(scale * 0.7);
+ }
+
+ getAccentColor(): THREE.Color {
+ let cssColor = getComputedStyle(document.body)
+ .getPropertyValue("--color-accent")
+ .trim();
+ return new THREE.Color(cssColor || "#3e90ff");
+ }
+
+ update() {
+ if (!this.setting.value) {
+ this.dot.visible = false;
+ return;
+ }
+ if (Modes.paint) {
+ this.dot.visible = false;
+ return;
+ }
+
+ let group = this.getRelevantGroup();
+ if (!group) {
+ this.dot.visible = false;
+ return;
+ }
+
+ // Update color in case accent changed
+ (this.dot.material as THREE.MeshBasicMaterial).color.copy(
+ this.getAccentColor()
+ );
+
+ // Get world position of group pivot
+ let mesh = group.mesh;
+ if (mesh) {
+ let worldPos = new THREE.Vector3();
+ mesh.getWorldPosition(worldPos);
+ this.dot.position.copy(worldPos);
+ this.dot.visible = true;
+ this.updateScale();
+ } else {
+ this.dot.visible = false;
+ }
+ }
+
+ getRelevantGroup(): Group | null {
+ let sel = Outliner.selected[0];
+ if (!sel) return null;
+
+ if (sel instanceof Group) {
+ return sel;
+ }
+ if (sel.parent instanceof Group) {
+ return sel.parent;
+ }
+ return null;
+ }
+
+ delete() {
+ Canvas.scene.remove(this.dot);
+ this.dot.geometry.dispose();
+ (this.dot.material as THREE.MeshBasicMaterial).dispose();
+ this.listener.delete();
+ this.cameraListener.delete();
+ this.setting.delete();
+ }
}
diff --git a/src/plugin.ts b/src/plugin.ts
index c09fcf0..e3743ee 100644
--- a/src/plugin.ts
+++ b/src/plugin.ts
@@ -17,6 +17,7 @@ import { setupTextureHandling, updateUVSize } from "./texture";
import { setupNameOverlap } from "./name_overlap";
import { setupUVOutline } from "./uv_outline";
import { setupTempFixes } from './temp_fixes'
+import { t } from "./i18n";
import { setupPreviewScenes } from "./preview_scenes";
BBPlugin.register('hytale_plugin', {
@@ -24,7 +25,7 @@ BBPlugin.register('hytale_plugin', {
author: 'JannisX11, Kanno',
icon: 'icon.png',
version: Package.version,
- description: 'Create models and animations for Hytale',
+ description: t('plugin.description'),
tags: ['Hytale'],
variant: 'both',
min_version: '5.0.5',
diff --git a/src/texture.ts b/src/texture.ts
index 4ae4e7b..9730921 100644
--- a/src/texture.ts
+++ b/src/texture.ts
@@ -4,6 +4,7 @@
import { AttachmentCollection } from "./attachments";
import { track } from "./cleanup";
import { isHytaleFormat } from "./formats";
+import { t } from "./i18n";
export function updateUVSize(texture: Texture) {
let size = [texture.width, texture.display_height];
@@ -17,13 +18,13 @@ export function updateUVSize(texture: Texture) {
export function setupTextureHandling() {
- let setting = new Setting('preview_selected_texture', {
- name: 'Preview Selected Texture',
- description: 'When selecting a texture in a Hytale format, preview the texture on the model instantly',
- category: 'preview',
- type: 'toggle',
- value: true
- })
+ let setting = new Setting("preview_selected_texture", {
+ name: t("settings.preview_texture.name"),
+ description: t("settings.preview_texture.description"),
+ category: "preview",
+ type: "toggle",
+ value: true,
+ });
track(setting);
// Auto-set selected texture: for grouped textures update the collection, otherwise set as default
diff --git a/src/translations/en.json b/src/translations/en.json
new file mode 100644
index 0000000..1fb67c4
--- /dev/null
+++ b/src/translations/en.json
@@ -0,0 +1,72 @@
+{
+ "plugin": {
+ "title": "Hytale Models",
+ "description": "Create models and animations for Hytale"
+ },
+ "menu": {
+ "import_attachment": "Import Attachment",
+ "reload_all": "Reload All Attachments"
+ },
+ "error": {
+ "no_textures": "No textures found for this model."
+ },
+ "actions": {
+ "reload_attachments": "Reload Attachments",
+ "reload_all_attachments": "Reload All Attachments",
+ "hytale_add_quad": "Add Quad",
+ "cut_selection": "Cut texture selection",
+ "paste_texture": "Paste into texture",
+ "pasted_layer_name": "pasted"
+ },
+ "textures": {
+ "import": "Import Textures",
+ "message": "No textures were found for this model. How would you like to import textures?",
+ "select_files": "Select Files",
+ "select_folder": "Select Folder",
+ "skip": "Skip"
+ },
+ "directory": {
+ "select_texture_folder": "Select Texture Folder",
+ "file_not_found": "File not found",
+ "export_blockymodel": "Export Hytale BlockyModel",
+ "export_blockymodel_desc": "Export a BlockyModel file"
+ },
+ "formats": {
+ "character": {
+ "name": "Hytale Character",
+ "description": "Create character and attachment models using Hytale's blockymodel format"
+ },
+ "prop": {
+ "name": "Hytale Prop",
+ "description": "Create prop models using Hytale's blockymodel format"
+ },
+ "page": {
+ "informations": "Information",
+ "resources": "Resources",
+ "info_list": "* One texture can be applied to a model at a time\n* UV sizes are linked to the size of each cube and cannot be modified, except by stretching the cube\n* Models can have a maximum of 255 nodes",
+ "resource_list": "* [Modeling Overview and Style Guide](https://hytale.com/news/2025/12/an-introduction-to-making-models-for-hytale)\n* [Modeling Tutorial](https://youtu.be/Q07i3wmGy0Y)"
+ },
+ "category": "Hytale"
+ },
+ "settings": {
+ "duplicate_bones": {
+ "name": "Duplicate Bone Names",
+ "description": "Allow creating duplicate groups names in Hytale formats. Multiple groups with the same name can be used to apply animations to multiple nodes at once."
+ },
+ "photoshop_copy": {
+ "name": "Copy-Paste with Magenta Alpha",
+ "description": "Copy image selections with magenta background and remove magenta when pasting to help transfer transparency to Photoshop"
+ },
+ "group_pivot": {
+ "name": "Show Group Pivot Indicator",
+ "description": "Show a dot in Edit mode indicating the rotation pivot point for animations"
+ },
+ "preview_texture": {
+ "name": "Preview Selected Texture",
+ "description": "When selecting a texture in a Hytale format, preview the texture on the model instantly"
+ }
+ },
+ "toggle": {
+ "toggle_attachment_editing": "Toggle Attachment Editing"
+ }
+}
diff --git a/src/translations/pt-BR.json b/src/translations/pt-BR.json
new file mode 100644
index 0000000..e7a5c3d
--- /dev/null
+++ b/src/translations/pt-BR.json
@@ -0,0 +1,73 @@
+{
+ "plugin": {
+ "title": "Modelos Hytale",
+ "description": "Crie modelos e animações para o Hytale"
+ },
+ "menu": {
+ "import_attachment": "Importar Anexo",
+ "reload_all": "Recarregar Todos os Anexos"
+ },
+ "error": {
+ "no_textures": "Nenhuma textura encontrada para este modelo."
+ },
+ "actions": {
+ "reload_attachments": "Recarregar Anexos",
+ "reload_all_attachments": "Recarregar Todos os Anexos",
+ "hytale_add_quad": "Adicionar Quad",
+ "cut_selection": "Recortar seleção da textura",
+ "paste_texture": "Colar na textura",
+ "pasted_layer_name": "colado"
+ },
+ "textures": {
+ "import": "Importar Texturas",
+ "message": "Nenhuma textura foi encontrada para este modelo. Como você deseja importar as texturas?",
+ "select_files": "Selecionar Arquivos",
+ "select_folder": "Selecionar Pasta",
+ "skip": "Pular"
+ },
+ "directory": {
+ "select_texture_folder": "Selecionar Pasta de Texturas",
+ "file_not_found": "Arquivo não encontrado",
+ "export_blockymodel": "Exportar BlockyModel do Hytale",
+ "export_blockymodel_desc": "Exportar um arquivo BlockyModel"
+ },
+ "formats": {
+ "character": {
+ "name": "Personagem Hytale",
+ "description": "Crie modelos de personagens e anexos usando o formato blockymodel do Hytale"
+ },
+
+ "prop": {
+ "name": "Objeto Hytale",
+ "description": "Crie modelos de objetos usando o formato blockymodel do Hytale"
+ },
+ "page": {
+ "informations": "Informações",
+ "resources": "Recursos",
+ "info_list": "* Apenas uma textura pode ser aplicada a um modelo por vez\n* Os tamanhos de UV são vinculados ao tamanho de cada cubo e não podem ser modificados, exceto esticando o cubo\n* Os modelos podem ter no máximo 255 nós",
+ "resource_list": "* [Guia de Modelagem e Estilo](https://hytale.com/news/2025/12/an-introduction-to-making-models-for-hytale)\n* [Tutorial de Modelagem](https://youtu.be/Q07i3wmGy0Y)"
+ },
+ "category": "Hytale"
+ },
+ "settings": {
+ "duplicate_bones": {
+ "name": "Duplicar Nomes de Ossos",
+ "description": "Permite criar grupos com nomes duplicados nos formatos do Hytale. Vários grupos com o mesmo nome podem ser usados para aplicar animações a vários nós ao mesmo tempo."
+ },
+ "photoshop_copy": {
+ "name": "Copiar e Colar com Alpha Magenta",
+ "description": "Copia seleções de imagem com fundo magenta e remove o magenta ao colar, ajudando a transferir transparência para o Photoshop"
+ },
+ "group_pivot": {
+ "name": "Mostrar Indicador de Pivô do Grupo",
+ "description": "Mostra um ponto no modo de Edição indicando o ponto de pivô de rotação para animações"
+ },
+ "preview_texture": {
+ "name": "Pré-visualizar Textura Selecionada",
+ "description": "Ao selecionar uma textura em um formato Hytale, a textura é exibida instantaneamente no modelo"
+ }
+ },
+ "toggle": {
+ "toggle_attachment_editing": "Alternar Edição de Anexos"
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
index 6d839e9..6b42552 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,13 +1,15 @@
{
"compilerOptions": {
- "target": "es2023",
- "module": "es2022",
- "moduleResolution": "node",
- "allowUmdGlobalAccess": true,
- "lib": ["ES2023", "DOM", "DOM.Iterable"],
- "types": ["blockbench-types"],
- "rootDir": "src",
- "strictNullChecks": false
+ "target": "es2023",
+ "module": "es2022",
+ "moduleResolution": "node",
+ "allowUmdGlobalAccess": true,
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
+ "types": ["blockbench-types"],
+ "rootDir": "src",
+ "strictNullChecks": false,
+ "resolveJsonModule": true,
+ // "esModuleInterop": true
},
"exclude": [
"node_modules"