diff --git a/plugins.json b/plugins.json index 6b412ba6..98c08261 100644 --- a/plugins.json +++ b/plugins.json @@ -208,7 +208,11 @@ "version": "7.8.0", "min_version": "4.8.0", "variant": "both", - "creation_date": "2020-02-02" + "creation_date": "2020-02-02", + "has_changelog": true, + "website": "https://ewanhowell.com/plugins/cem-template-loader/", + "repository": "https://github.com/ewanhowell5195/blockbenchPlugins/tree/main/cem_template_loader", + "bug_tracker": "https://github.com/ewanhowell5195/blockbenchPlugins/issues/new?title=[CEM Template Loader]" }, "optifine_player_models": { "title": "OptiFine Player Models", @@ -315,7 +319,9 @@ "formats": ["minectaft_title"] }, "has_changelog": true, - "website": "https://ewanhowell.com/plugins/minecraft-title-generator/" + "website": "https://ewanhowell.com/plugins/minecraft-title-generator/", + "repository": "https://github.com/ewanhowell5195/blockbenchPlugins/tree/main/minecraft-title-generator", + "bug_tracker": "https://github.com/ewanhowell5195/blockbenchPlugins/issues/new?title=[Minecraft Title Generator]" }, "workspaces": { "title": "Workspaces", diff --git a/plugins/cem_template_loader/cem_template_loader.js b/plugins/cem_template_loader/cem_template_loader.js index 525b8674..de5b2550 100644 --- a/plugins/cem_template_loader/cem_template_loader.js +++ b/plugins/cem_template_loader/cem_template_loader.js @@ -1,9 +1,17 @@ (() => { - let generatorActions, entitySelector, loaderShown, entityData, entityCategories, groupObserver, animationEditorPanel, animationControlPanel, context, boolMap, rangeMap, specialMap, styles, stopAnimations, updateSelection, docShown, documentation, editorKeybinds, tabChange, loader, editCheck, originalJEMFormat + let plugin + const connection = { + roots: [ + `https://wynem.com/assets`, + `https://raw.githubusercontent.com/ewanhowell5195/wynem/main/src/assets`, + `https://cdn.jsdelivr.net/gh/ewanhowell5195/wynem/src/assets` + ], + rootIndex: 0 + } + let root = connection.roots[0] const id = "cem_template_loader" const name = "CEM Template Loader" const icon = "keyboard_capslock" - const author = "Ewan Howell" const description = "Load template Java Edition entity models for use with OptiFine CEM." const links = { website: { @@ -25,502 +33,802 @@ colour: "#FF4444" } } - const E = s => $(document.createElement(s)) - const getBase64FromUrl = url => { - return new Promise(async (resolve) => { - const reader = new FileReader() - reader.readAsDataURL(await (await fetch(url)).blob()) - reader.onloadend = () => { - const base64data = reader.result - resolve(base64data) + + function loadPlugin() { + plugin = Plugin.register(id, { + title: name, + icon: "icon.png", + author: "Ewan Howell", + description: description + " Also includes an animation editor, so that you can create custom entity animations.", + tags: ["Minecraft: Java Edition", "OptiFine", "Templates"], + version: "8.0.0", + min_version: "4.10.0", + variant: "both", + creation_date: "2020-02-02", + has_changelog: true, + website: "https://ewanhowell.com/plugins/cem-template-loader/", + repository: "https://github.com/ewanhowell5195/blockbenchPlugins/tree/main/cem_template_loader", + bug_tracker: "https://github.com/ewanhowell5195/blockbenchPlugins/issues/new?title=[CEM Template Loader]", + onload() { + loadCEMTemplateLoader() + loadOptiFineEntityRestrictions() + loadOptiFineAnimationEditor() + }, + onunload() { + unloadCEMTemplateLoader() + unloadOptiFineEntityRestrictions() + unloadOptiFineAnimationEditor() } }) } - const imageObserver = new IntersectionObserver(function(entries, observer) { - entries.forEach(async function(entry) { - if ("src" in entry.target.dataset) { - if (entry.isIntersecting) { - imageObserver.unobserve(entry.target) - await fetch(entry.target.dataset.src, { method: "HEAD" }).then(r => { - if (r.status >= 400) throw new Error - entry.target.style.backgroundImage = `url("${entry.target.dataset.src}")` - }).catch(() => { - entry.target.style.backgroundImage = 'url("assets/logo_cutout.svg")' - entry.target.style.filter = "invert(1)" - entry.target.style.opacity = 0.35 - }) - delete entry.target.dataset.src + + async function fetchData(path, fallback) { + try { + const r = await fetch(`${root}/${path}`) + if (r.status !== 200 || r.headers.get("Content-Type")?.startsWith("text/html")) throw new Error + if (r.headers.get("Content-Type")?.startsWith("text/plain") || r.headers.get("Content-Type")?.startsWith("application/json")) return r.json() + return r + } catch { + for (let x = connection.rootIndex + 1; x < connection.roots.length; x++) { + connection.rootIndex = x + try { + const r = await fetch(`${connection.roots[x]}/${path}`) + if (r.status !== 200) throw new Error + root = connection.roots[x] + if (r.headers.get("Content-Type")?.startsWith("text/plain") || r.headers.get("Content-Type")?.startsWith("application/json")) return r.json() + return r + } catch {} + } + connection.failed = true + return fallback ? fallback() : {} + } + } + + // CEM TEMPLATE LOADER + + let styles, loader, loaderDialog, modelData, loadingPromise + + const loaderData = { + connection: null, + loading: true, + categories: {}, + category: null, + loadTexture: true, + entity: null, + search: "", + entities: [], + built: false + } + + async function loadCEMTemplateLoader() { + styles = Blockbench.addCSS(` + .spacer { + flex: 1; + } + .cem-template-loader-links { + display: flex; + justify-content: space-around; + margin: 20px 40px 0; + } + .cem-template-loader-links > a { + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + padding: 5px; + text-decoration: none; + flex-grow: 1; + flex-basis: 0; + color: var(--color-subtle_text); + text-align: center; + } + .cem-template-loader-links > a:hover { + background-color: var(--color-accent); + color: var(--color-light); + } + .cem-template-loader-links > a > i { + font-size: 32px; + width: 100%; + max-width: initial; + height: 32px; + text-align: center; + } + .cem-template-loader-links > a:hover > i { + color: var(--color-light) !important; + } + .cem-template-loader-links > a > p { + flex: 1; + display: flex; + align-items: center; + } + #format_page_cem_template_loader { + padding-bottom: 0; + } + #format_page_cem_template_loader .format_target { + margin-bottom: 6px; + } + #format_page_cem_template_loader div:nth-child(3), #format_page_cem_template_loader content { + overflow-y: auto; + } + #cem-container { + display: flex; + flex-direction: column; + height: 100%; + } + .cem-overlay { + position: absolute; + inset: 0; + z-index: 2; + background-color: var(--color-ui); + display: flex; + flex-direction: column; + padding: 40px; + text-align: center; + gap: 16px; + overflow-y: auto; + + > * { + max-width: 512px; + margin: 0 auto !important; + } + + > :first-child { + margin-top: auto !important; + } + + > :last-child { + margin-bottom: auto !important; + } + } + `) + loader = new ModelLoader(id, { + name, + description, + icon, + onStart: () => openLoader, + format_page: { + component: { + data: { + connection + }, + methods: { + openLoader + }, + template: ` +
+

${description}

+

Target : Minecraft: Java Edition with OptiFine Texturing Templates

+ +

How to use:

+

+

+

+

Do:

+

+

+

+

Do not:

+

+

+

+
+
+ +
+ +
+
+ ` } - } else imageObserver.unobserve(entry.target) + } }) - }) - const loadCEMTemplateModels = (entityData, data) => { - $("#cem_template_sidebar").append(entityData.categories.map(e => E("li").attr("id", `cem_template_tab_${e.name.toLowerCase()}`).append( - E("span").addClass("icon_wrapper f_left").append( - e.icon.match(/^icon-/) ? E("i").addClass(`${e.icon} icon`) : E("i").addClass("material-icons icon").text(e.icon) - ), - e.name - ).on("click", evt => { - $("#cem_template_sidebar>.selected").removeClass("selected") - $(evt.currentTarget).addClass("selected") - $("#cem_template_page>:not(:last-child)").css("display", "none") - const page = $(`#cem_template_page_${e.name.toLowerCase()}`).css("display", "block") - page.find(".search_bar>input").select() - }))) - $("#cem_template_sidebar>:first-child").addClass("selected") - $("#cem_template_buttons").before(entityData.categories.map((e, i) => E("div").css("display", i === 0 ? "block" : "none").attr("id", `cem_template_page_${e.name.toLowerCase()}`).append( - E("h2").text(e.name), - E("content").append( - E("section").append( - E("p").text(e.description) - ), - E("section").append( - E("label").text("Models"), - E("div").addClass("search_bar").attr("placeholder", "Search...").css("width", "220px").append( - E("input").addClass("dark_bordered").attr("type", "text").on("input", evt => { - const query = evt.currentTarget.value.toLowerCase() - $(evt.currentTarget).parent().next().find(".cem_template_model").each((i, e) => { - const label = $(e).children().eq(1) - if (~label.text().toLowerCase().indexOf(query)) { - $(e).css("display", "") - } else { - $(e).css("display", "none") - } - }) - }), - E("i").addClass("material-icons").text("search") - ), - E("ul").addClass("cem_template_list").append( - e.entities.map(f => { - const model = entityData.models[typeof f === "string" ? f : f.model || f.name] - const image = `https://wynem.com/assets/images/minecraft/renders/${f.name || f}.webp` + (data?.appendToURL ?? "") - const imageElement = E("div").addClass("cem_template_image").attr("data-src", image) - const element = E("li").addClass("cem_template_model").attr("data-modelid", f.name || f).attr("data-category", e.name).append( - imageElement, - E("label").text(typeof f === "string" ? f.replace(/_/g, " ") : f.display_name || f.name.replace(/_/g, " ")) - ).on({ - click: evt => { - $(".cem_template_model.selected").removeClass("selected") - element.addClass("selected") - }, - dblclick: evt => { - const selectedModel = $(".cem_template_model.selected") - loadModel(selectedModel.attr("data-modelid"), selectedModel.attr("data-category"), $("#cem_template_texture_check").is(":checked"), data) - } - }) - if (f.description) { - element.attr("title", f.description) - } - imageObserver.observe(imageElement[0]) - return element - }) - ) - ) - ) - ))) - } - const loadEntities = entityData => { - const entityCategories = {} - for (let category of entityData.categories) { - entityCategories[category.name] = { - description: category.description, - icon: category.icon, - entities: {} - } - for (let entity of category.entities) { - if (typeof entity === "string") entity = { - name: entity - } - const model = entityData.models[entity.model || entity.name] - entityCategories[category.name].entities[entity.name] = { - name: entity.display_name || entity.name.replace(/_/g, " ").replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1)), - file_name: entity.file_name || entity.name, - model: model.model, - texture_name: Array.isArray(entity.texture_name) ? entity.texture_name.map(e => e + ".png") : (entity.texture_name || entity.name) + ".png", - texture_data: model.texture_data && (typeof model.texture_data === "string" ? "data:image/png;base64," + model.texture_data : model.texture_data.map(e => "data:image/png;base64," + e)), - vanilla_textures: entity.vanilla_textures, - popup: entity.popup, - popup_width: entity.popup_width + loaderDialog = new Dialog({ + id, + title: name, + width: 980, + buttons: [], + sidebar: { + pages: {}, + onPageSwitch(page) { + loaderDialog.content_vue.category = page + loaderDialog.content_vue.$refs.entry.focus() + } + }, + lines: [``], + component: { + data: loaderData, + methods: { + async load() { + this.loading = true + await loadModel(this.entity, this.loadTexture) + loaderDialog.close() + }, + reload() { + window.cemTemplateLoaderReloaded = true + plugin.reload() + }, + close: () => loaderDialog.close() + }, + template: ` +
+
+

Loading...

+
+
+

Connection Failed

+

Failed to load CEM Template data.

+

Please make sure you are connected to the internet, and can access this cem_template_models.json file.

+

If you are unable to access the cem_template_models.json file, it may be blocked by your computer or your internet service provider. If it is not your computer blocking it, you may be able to use a VPN to bypass the block. One good example is Cloudflare WARP, which is a free program that commonly resolves this issue.

+ +
+
+

{{ category }}

+
+
{{ categories[category]?.description }}
+ +
+
+
+
+ +
{{ model.display_name ?? model.name.replace(/_/g, " ") }}
+
+
+
+

No results

+
+ +
+ + bug_report +
Report issues
+
+
+
+ ` + }, + async onBuild() { + loaderData.connection = connection + await loadCEMTemplateModels() + this.sidebar.build() + if (!modelData.categories) { + return this.content_vue.$forceUpdate() } + const categories = modelData.categories.map(e => [e.name, e]) + loaderData.categories = Object.fromEntries(categories) + loaderData.entities = categories.flatMap(c => c[1].entities.map(e => [c[0], e.name])) + loaderData.loading = false + loaderData.built = true + }, + onOpen() { + this.content_vue.$refs.entry.focus() + if (loaderData.built) { + loaderData.loading = false + this.content_vue.$forceUpdate() + } + } + }) + MenuBar.addAction(new Action(id, { + name, + description, + children: [ + new Action("cem_template_loader_placeholder", { + name: `All Entities`, + description: "All entities", + icon: "icon-player", + click: () => openLoader() + }) + ], + icon + }), "tools") + const script = document.createElement("script") + script.innerHTML = ` + if (!window.cemTemplateModelsLoaded) { + loadCEMTemplateModels() + } + ` + BarItems.cem_template_loader.menu_node.append(script) + if (window.cemTemplateLoaderReloaded) { + delete window.cemTemplateLoaderReloaded + openLoader() + } else if (Blockbench.isWeb) { + const params = (new URL(location.href)).searchParams + if (params.get("plugins")?.split(",").includes(id) && params.get("model") !== "") { + if (!await MinecraftEULA.promptUser(id)) return + await loadCEMTemplateModels() + loadModel(params.get("model"), params.has("texture")) } } - return entityCategories } - async function loadModel(modelID, categoryName, defaultTexture, data) { - if (!categoryName) { - const category = Object.entries(entityCategories).find(e => {return e[1].entities[modelID]}) - if (!category) return Blockbench.showQuickMessage("Unknown CEM template model", 2000) - categoryName = category[0] + + function unloadCEMTemplateLoader() { + styles.delete() + loader.delete() + loaderDialog.close() + for (const child of BarItems.cem_template_loader.children) { + child.delete?.() + } + BarItems.cem_template_loader.delete() + delete window.loadCEMTemplateModels + delete window.cemTemplateModelsLoaded + } + + window.loadCEMTemplateModels = async () => { + if (window.cemTemplateModelsLoaded) return loadingPromise + loadingPromise = fetchData("json/cem_template_models.json") + modelData = await loadingPromise + window.cemTemplateModelsLoaded = true + if (!modelData.categories) return + loaderDialog.sidebar.page = modelData.categories[0].name + loaderData.category = modelData.categories[0].name + for (const category of modelData.categories) { + for (let i = 0; i < category.entities.length; i++) { + if (typeof category.entities[i] === "string") { + category.entities[i] = { name: category.entities[i] } + } + } + } + modelData.entities = [] + for (const category of modelData.categories) { + loaderDialog.sidebar.pages[category.name] = { + label: category.name, + icon: category.icon + } + modelData.entities.push(...category.entities) + } + BarItems.cem_template_loader_placeholder.delete() + BarItems.cem_template_loader.children = modelData.categories.map(e => new Action(`cem_template_loader_${e.name.replace(/ /g, "_")}`, { + plugin: id, + name: `${e.name} Entities`, + description: e.description, + icon: e.icon, + click: () => openLoader(e.name) + })) + BarItems.cem_template_loader.children.push("_", { + name: `v${modelData.version}`, + id: "cem_template_loader_version", + icon: "info", + click: () => openLoader() + }) + if (MenuBar.menus.tools.label.classList.contains("opened")) { + MenuBar.menus.tools.show() } - const entity = entityCategories[categoryName].entities[modelID] - const model = JSON.parse(entity.model) - const createNewProject = !$("#cem_template_project_check").is(":checked") - if (!Project || createNewProject) { - newProject(Formats.optifine_entity) - Project.name = entity.file_name - Blockbench.setStatusBarText(entity.name) + } + + async function openLoader(category) { + if (!await MinecraftEULA.promptUser(id)) return + if (category) { + loaderData.category = category + loaderDialog.sidebar.page = category + if (loaderDialog.content_vue) loaderDialog.sidebar.build() } - Formats.optifine_entity.codec.parse(model, "") - if (defaultTexture) { + loaderDialog.show() + } + + const getBase64FromBlob = blob => new Promise(async fulfil => { + const reader = new FileReader() + reader.onloadend = () => fulfil(reader.result) + reader.readAsDataURL(blob) + }) + + async function loadModel(entity, loadTexture) { + const data = modelData.entities.find(e => e.name === entity) + if (!data) return Blockbench.showQuickMessage("Unknown CEM template model", 2000) + const model = modelData.models[data.model ?? data.name] + newProject(Formats.optifine_entity) + Formats.optifine_entity.codec.parse(JSON.parse(model.model), "") + let textureLoaded + if (loadTexture) { try { - for (const [idx, textureName] of (entity.vanilla_textures || [entity.texture_name]).entries()) { - const r = await fetch(`https://wynem.com/assets/images/minecraft/entities/${modelID}${idx || ""}.png` + (data?.appendToURL ?? ""), {method: "HEAD"}) - if (r.status < 400) new Texture({name: textureName}).fromDataURL(await getBase64FromUrl(`https://wynem.com/assets/images/minecraft/entities/${modelID}${idx || ""}.png` + (data?.appendToURL ?? ""))).add() - else throw new Exception + const textures = Array.isArray(data.texture_name) ? data.texture_name : [data.texture_name ?? data.name] + for (const [i, name] of textures.entries()) { + const texture = await fetchData(`images/minecraft/entities/${entity}${i || ""}.png`, () => null) + if (!texture) throw Error + const tex = new Texture({ name }).fromDataURL(await getBase64FromBlob(await texture.blob())).add() + if (!i && textures.length === 1) { + tex.use_as_default = true + } } + textureLoaded = true } catch { - if ((typeof entity.texture_data).match(/string|undefined/)) { - if (entity.texture_data) new Texture({name: entity.texture_name}).fromDataURL(entity.texture_data).add() - else TextureGenerator.addBitmap({ - name: entity.texture_name, + Blockbench.showQuickMessage("Unable to load vanilla texture", 2000) + loaderDialog.content_vue?.$forceUpdate() + } + } + if (!textureLoaded) { + const textureData = Array.isArray(model.texture_data) ? model.texture_data : [model.texture_data] + const textures = Array.isArray(data.texture_name) && Array.isArray(model.texture_data) ? data.texture_name : [data.texture_name ?? data.name] + for (const [i, name] of textures.entries()) { + let tex + if (textureData[i]) { + tex = new Texture({ name }).fromDataURL("data:image/png;base64," + textureData[i]).add() + } else { + tex = TextureGenerator.addBitmap({ + name, color: new tinycolor("#00000000"), type: "template", rearrange_uv: false, resolution: "16" - }) - } else for (let i = 0; i < entity.texture_data.length; i++) new Texture({name: entity.texture_name[i]}).fromDataURL(entity.texture_data[i]).add() - Blockbench.showQuickMessage("Unable to load vanilla texture", 2000) + }) + } + if (!i && textures.length === 1) { + tex.use_as_default = true + } } - } else { - if ((typeof entity.texture_data).match(/string|undefined/)) { - if (entity.texture_data) new Texture({name: entity.texture_name}).fromDataURL(entity.texture_data).add() - else TextureGenerator.addBitmap({ - name: entity.texture_name, - color: new tinycolor("#00000000"), - type: "template", - rearrange_uv: false, - resolution: "16" - }) - } else for (let i = 0; i < entity.texture_data.length; i++) new Texture({name: entity.texture_name[i]}).fromDataURL(entity.texture_data[i]).add() - } - Undo.history.length = 0 - Undo.index = 0 - entitySelector.hide() - if (entity.popup) { - const options = { - id: "cem_template_loader_popup", - title: name, - buttons: ["Okay"], - lines: [ - `
` + (entity.popup_width ? `` : "") + entity.popup - ] - } - if (entity.popup_width) options.width = parseInt(entity.popup_width) - new Dialog(options).show() - } - } - function loadInterface(categoryID, data) { - categoryID ??= "supported" - entitySelector.show() - if (!loaderShown) { - loaderShown = true - loadCEMTemplateModels(entityData, data) - $("#cem_template_load_button").on("click", async evt => { - const selectedModel = $(".cem_template_model.selected") - if (selectedModel.length === 0) return Blockbench.showQuickMessage("Please select a template model") - const modelID = selectedModel.attr("data-modelid") - await loadModel(modelID, selectedModel.attr("data-category"), $("#cem_template_texture_check").is(":checked"), data) - }) - $("#cem_template_load_button+button").on("click", evt => { - entitySelector.hide() - }) } - $("#cem_template_sidebar>.selected").removeClass("selected") - $(`#cem_template_tab_${categoryID.toLowerCase()}`).addClass("selected") - $("#cem_template_page>:not(:last-child)").css("display", "none") - $("#cem_template_wrapper .search_bar>input").val("") - $(".cem_template_model").removeClass("selected").css("display", "") - const page = $(`#cem_template_page_${categoryID.toLowerCase()}`).css("display", "block") - page.find(".search_bar>input").focus() } - async function setupPlugin(url, data) { - try { - entityData = await fetch(url + (data?.appendToURL ?? "")).then(e => e.json()) - // entityData = JSON.parse(fs.readFileSync("F:/Programming/GitHub/wynem/src/assets/json/cem_template_models.json", "UTF-8")) - entitySelector = new Dialog({ - id: "cem_template_selector", - title: name, - width: 980, - buttons: [], - lines: [` - `], + component: { + data: { + message: "", + removeRestrictions: false, + dontShowAgain: false + }, + methods: { + finish() { + if (this.removeRestrictions) { + settings.jem_restrictions.set(true) } - .cem_template_list > li label { - position: absolute; - bottom: 0; - text-align: center; - width: 100%; - pointer-events: none; - text-transform: capitalize; + if (this.dontShowAgain) { + settings.dialog_jem_restrictions.set(false) } - -
- -
-
-
- - - - -
-
- - -
+ jemRestrictionsDialog.close() + } + }, + template: ` +
+

{{ message }}

+

The OptiFine Entity format has limitations for the top level groups.
You cannot:

+

+

    +
  • Add new root groups
  • +
  • Remove root groups
  • +
  • Rename root groups
  • +
  • Move root groups
  • +
  • Rotate root groups
  • +
  • Move root group pivots
  • +
  • Have cubes or attachment points at root level
  • +
+

+

Doing any of the above can, and will, create broken/invalid models.

+

All elements added to OptiFine Entities must be placed within one of the existing groups. If you need to rotate something, create a subgroup and rotate that instead.

+
+ +

WARNING: It is highly recommended to leave restrictions enabled. This setting can be changed later in settings.

+
+ - `] - }) - loaderShown = false - entityCategories = loadEntities(entityData) - generatorActions = [] - for (const category of Object.values(entityData.categories)) { - generatorActions.push( - new Action(category.name.toLowerCase().replace(/\s+/g, "_"), { - name: `${category.name} Entities`, - description: category.description, - icon: category.icon, - click: () => loadInterface(category.name, data) - }) - ) + ` } - generatorActions.push("_", { - name: `v${entityData.version}`, - id: "cem_template_loader_version", - icon: "info" - }) - MenuBar.addAction({ - name: "Load CEM Template", - id, - description, - children: generatorActions, - icon, - }, "tools") - loader = new ModelLoader(id, { - name, - description, - icon, - onStart: () => loadInterface(entityData.categories[0].name, data), - format_page: { - component: { - methods: { - load: () => loadInterface(entityData.categories[0].name, data) - }, - template: ` -
-

${description}

-

Target : Minecraft: Java Edition with OptiFine Texturing Templates

- -

How to use:

-

-

    -
  • Press Load CEM Template and select a model.

  • -
  • Select your load settings, load the model, then edit the model.

  • -
  • Export your model as an OptiFine JEM to assets/minecraft/optifine/cem, using the provided name.

  • -
-

-

Do:

-

-

    -
  • Edit any of the cubes that were loaded with the template, add your own cubes, and create your own subgroups.

  • -
-

-

Do not:

-

-

    -
  • Edit any of the groups that were loaded with the template, add your own root groups, or remove any built in animations.

  • -
-

-
-
- -
- -
-
- ` + }) + editCheck = () => { + if (Format.id === "optifine_entity") { + if (!settings.jem_restrictions.value) { + const entry = Undo.history[Undo.history.length-1] + const check = editCheckProcess(entry) + if (check) { + if (settings.dialog_jem_restrictions.value) { + jemRestrictionsDialog.show() + jemRestrictionsDialog.content_vue.message = check + } else { + Blockbench.showQuickMessage(check, 1200) + } + Undo.loadSave(entry.before, entry.post) + Undo.history.pop() + Undo.index = Undo.history.length } } - }) - return true - } catch (err) { - console.error(err) - generatorActions = [] - generatorActions.push({ - name: "Connection failed", - description: "Could not connect to wynem.com", - id: "cem_template_loader_connection_failure", - icon: "wifi_off" - }, - "_", - new Action("cem_template_loader_retry_connection", { - name: `Retry connection`, - description: "Attempt to reconnect to wynem.com", - icon: "sync", - click: async () => { - for (let action of generatorActions) if (typeof action.delete === "function") action.delete() - MenuBar.removeAction("tools.cem_template_loader") - if (await setupPlugin("https://wynem.com/assets/json/cem_template_models.json?rnd=" + Math.random())) Blockbench.showQuickMessage("Reconnected sucessfully", 2000) - else{ - new Dialog({ - id: "cem_template_loader_connection_failure_dialog", - title: name, - lines: ['

Connection failed

Please check your internet connection and make sure that you can access wynem.com'], - "buttons": ["Okay"] - }).show() - } + } + } + Blockbench.on("finished_edit", editCheck) + originalJEMFormat = { + new: Formats.optifine_entity.new, + format_page: Formats.optifine_entity.format_page, + convertTo: Formats.optifine_entity.convertTo + } + Formats.optifine_entity.new = () => { + if (settings.jem_restrictions.value) originalJEMFormat.new.bind(Formats.optifine_entity)() + else openLoader() + } + Formats.optifine_entity.format_page = JSON.parse(JSON.stringify(Formats.optifine_entity.format_page)) + Formats.optifine_entity.format_page.content.push({ type: "h3", text: "CEM Template Loader" }, { text: "Creating an OptiFine entity will open CEM Template Loader. It is extremely rare to need a blank OptiFine entity model. You can disable this behavour with **File > Preferences > Settings > Edit > Remove OptiFine Entity Restrictions**, however, this is not recommended." }) + Formats.optifine_entity.convertTo = () => { + originalJEMFormat.convertTo.bind(Formats.optifine_entity)() + if (!settings.jem_restrictions.value) { + Blockbench.showMessageBox({ + title: "Conversion Warning", + message: "Models converted into OptiFine entities are not valid entity models. If you are converting a model into an OptiFine entity to use it in game, expect it to be broken. Instead load a new template entity model, and copy your elements across into that.", + buttons: ["Load Template", "Continue"], + icon: "warning" + }, button => { + if (button === 0) openLoader() + }) + } + } + } + + function unloadOptiFineEntityRestrictions() { + restrictionStyles.delete() + Blockbench.removeListener("finished_edit", editCheck) + jemRestrictionsDialog.close() + Formats.optifine_entity.new = originalJEMFormat.new + Formats.optifine_entity.format_page = originalJEMFormat.format_page + Formats.optifine_entity.convertTo = originalJEMFormat.convertTo + } + + function editCheckProcess(entry) { + if (entry.before.outliner) { + for (const group of entry.before.outliner) { + const postGroup = entry.post.outliner.find(e => e.uuid === group.uuid) + if (!postGroup) return "You cannot remove root cubes/groups!" + if (!group.origin.reduce((a, e, x) => a && e === postGroup.origin[x], true)) { + return "You cannot move root groups!" + } else if (group.name !== postGroup.name) { + return "You cannot rename root groups!" } - })) - MenuBar.addAction({ - name: "Load CEM Template", - id, - description, - children: generatorActions, - icon, - }, "tools") - return false + } + } + if (entry.post.outliner) { + for (const group of entry.post.outliner) { + const beforeGroup = entry.before.outliner.find(e => e.uuid === group.uuid) + if (!beforeGroup) return "You cannot add new root cubes/groups!" + } + } + if (entry.before.group && entry.post.group && Outliner.root.find(node => node instanceof Group && node.uuid == entry.before.group.uuid)) { + if (!entry.before.group.rotation.reduce((a, e, x) => a && e === entry.post.group.rotation[x], true)) { + return "You cannot rotate root groups!" + } else if (!entry.before.group.origin.reduce((a, e, x) => a && e === entry.post.group.origin[x], true)) { + return "You cannot move root group pivots!" + } } } + + // OPTIFINE ANIMATION EDITOR + + let animationStyles, groupObserver, animationEditorPanel, animationControlPanel, context, boolMap, rangeMap, specialMap, stopAnimations, updateSelection, docShown, documentation, editorKeybinds, tabChange + const E = s => $(document.createElement(s)) let frameCount const constants = { pi: Math.PI, @@ -543,7 +851,7 @@ frac: x => x - Math.floor(x), log: Math.log, pow: Math.pow, - random: (seed) => { + random: seed => { if (!seed) return Math.random() seed = constants.frac(seed * 123.34) seed += seed * (seed + 45.32) @@ -574,8 +882,8 @@ in: (x, ...vals) => vals.includes(x), print: (id, int, val) => { if (val === undefined) throw Error("Not enough arguments. print requires 3 arguments") - if (typeof val !== "number") throw Error("print can only print numbers, use printb instead") - if (frameCount % int === 0) console.log(`CEM print(${id}) = ${val}`) + if ((settings.ignore_unkown_optifine_animations.value && !(typeof val === "number" || typeof val === "function")) || (!settings.ignore_unkown_optifine_animations.value && typeof val !== "number")) throw Error("print can only print numbers, use printb instead") + if (frameCount % int === 0) console.log(`CEM print(${id}) = ${Number(val)}`) return val }, printb: (id, int, val) => { @@ -588,9 +896,7 @@ return val ?? id }, lerp: (a, b, c) => b * (1 - a) + c * a, - frame_time: 0, - move_forward: 0, - move_strafing: 0 + frame_time: 0 } const rangesObj = { pos_x: [-128, 0, 128, 0.1], @@ -645,6 +951,112 @@ const enabledBooleans = new Set([ "is_alive", "is_on_ground" ]) + const boneVars = ["tx", "ty", "tz", "rx", "ry", "rz", "sx", "sy", "sz", "visible", "visible_boxes"] + const renderVars = ["shadow_size", "shadow_opacity", "shadow_offset_x", "shadow_offset_z", "leash_offset_x", "leash_offset_y", "leash_offset_z"] + const vars = [ + "pi", + "true", + "false", + "time", + "limb_swing", + "limb_speed", + "age", + "head_pitch", + "head_yaw", + "player_pos_x", + "player_pos_y", + "player_pos_z", + "player_rot_x", + "player_rot_y", + "frame_time", + "dimension", + "rule_index", + "health", + "hurt_time", + "death_time", + "anger_time", + "max_health", + "pos_x", + "pos_y", + "pos_z", + "rot_x", + "rot_y", + "swing_progress", + "id", + "sin()", + "cos()", + "asin()", + "acos()", + "tan()", + "atan()", + "atan2()", + "torad()", + "todeg()", + "min()", + "max()", + "clamp()", + "abs()", + "floor()", + "ceil()", + "exp()", + "frac()", + "log()", + "pow()", + "random()", + "round()", + "signum()", + "sqrt()", + "fmod()", + "lerp()", + "if()", + "print()", + "printb()", + "between()", + "equals()", + "in()", + ...boolList + ] + const varLabels = { + "sin()": "sin(x)", + "cos()": "cos(x)", + "asin()": "asin(x)", + "acos()": "acos(x)", + "tan()": "tan(x)", + "atan()": "atan(x)", + "atan2()": "atan2(y, x)", + "torad()": "torad(deg)", + "todeg()": "todeg(rad)", + "min()": "min(x, y, …)", + "max()": "max(x, y, …)", + "clamp()": "clamp(x, min, max)", + "abs()": "abs(x)", + "floor()": "floor(x)", + "ceil()": "ceil(x)", + "exp()": "exp(x)", + "frac()": "frac(x)", + "log()": "log(x)", + "pow()": "pow(x, y)", + "random()": "random(seed)", + "round()": "round(x)", + "signum()": "signum(x)", + "sqrt()": "sqrt(x)", + "fmod()": "fmod(x, y)", + "lerp()": "lerp(t, x, y)", + "if()": "if(c, v, [c2, v2, …,] v_else)", + "print()": "print(id, n, x)", + "printb()": "printb(id, n, x)", + "between()": "between(x, min, max)", + "equals()": "equals(x, y, epsilon)", + "in()": "in(x, val1, val2, …)" + } + globalThis.optifineAnimationVariables = { + constants, + ranges: rangesObj, + booleans: boolList, + enabledBooleans: enabledBooleans, + autocomplete: vars, + autocompleteLabels: varLabels + } let bools = new Map() let ranges = new Map() let specials = new Map() @@ -670,9 +1082,16 @@ if (anim.length === 0) return "" const boolsMatch = anim.matchAll(/(?<=[\s\n(!&|=,]|^)is_[a-z_]+(?=[\s\n)!&|=,]|$)/g) for (const bool of boolsMatch) { - if (!boolList.has(bool[0])) throw [`Unknown boolean: "${bool[0]}" is not a supported boolean`] - boolMap.set(bool[0], bools.get(bool[0]) ?? enabledBooleans.has(bool[0]) ? true : false) - } + if (!boolList.has(bool[0])) { + if (settings.ignore_unkown_optifine_animations.value) { + boolMap.set(bool[0], false) + } else { + if (!boolList.has(bool[0])) throw [`Unknown boolean: "${bool[0]}" is not a supported boolean`] + } + } else { + boolMap.set(bool[0], bools.get(bool[0]) ?? enabledBooleans.has(bool[0]) ? true : false) + } + } const check = anim.match(/[^a-z0-9_,\+\-\*\/%!&\|>=<\(\)\[\]:\.\s]/gi) if (check) throw [`Unsupported character "${check[0]}" in animation "${anim.replace(/"`] const check2 = anim.match(/[\)\]]\s*\(|=>|(?<]|^)=|<<=|>>>=|>>=|[!=]==|\+\+|--|\.\.\.|(?>>?|\*\*/ui) @@ -696,8 +1115,259 @@ }) } let timescale = 1 - function setupAnimationPanel() { + function loadOptiFineAnimationEditor() { let steps, playing, paused, currentGroups + new Setting("ignore_unkown_optifine_animations", { + value: false, + category: "edit", + name: "Ignore Unknown OptiFine Animations", + description: "If an OptiFine animation contains unknown animations, they will be ignored, allowing the animation to play and the model to be exported. WARNING: Unknown animations will cause the model to fail to load when using OptiFine." + }) + animationStyles = Blockbench.addCSS(` + #panel_cem_animation .panel_vue_wrapper { + flex: 1; + padding: 8px; + overflow: auto !important; + display: flex; + flex-direction: column; + max-height: 100%; + } + #panel_cem_animation .prism-editor-wrapper { + background-color: var(--color-back); + padding-top: 0; + } + #panel_cem_animation .prism-editor-wrapper::-webkit-scrollbar-track, #panel_cem_animation .prism-editor-wrapper::-webkit-scrollbar-corner { + background-color: var(--color-back); + } + #panel_cem_animation .prism-editor__line-numbers { + overflow: visible; + min-height: 100% !important; + position: sticky; + left: 0; + } + #panel_cem_animation .prism-editor__line-number { + background-color: var(--color-back); + translate: -4px 0; + padding: 0 4px; + } + #panel_cem_animation .prism-editor__code { + overflow: visible !important; + } + #cem_animation_editor_container { + overflow: hidden; + flex: 1; + max-height: calc(100% - 40px); + min-height: 37px; + display: flex; + position: relative; + } + #cem_animation_controller_container { + flex: 1; + padding: 8px; + display: flex; + flex-direction: column; + max-height: 100%; + overflow-y: auto; + } + #cem_animation_editor { + cursor: text; + flex: 1; + } + #cem_animation_title { + margin-left: 0; + } + #cem_animation_content { + flex-direction: column; + flex: 1; + max-height: 100%; + } + .cem_animation_bar { + display: flex; + align-items: center; + gap: 8px; + } + .cem_animation_bar span { + display: flex; + gap: 8px; + align-items: center; + } + #cem_animation_part_name { + display: inline !important; + } + .cem_animation_bar i, #cem_doc > div.selected { + display: block; + } + .cem_animation_status { + min-width: 25px; + height: 25px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + } + #cem_animation_status_success { + background-color: var(--color-confirm); + color: var(--color-dark); + } + #cem_animation_status_error { + background-color: var(--color-close); + color: var(--color-light); + display: none; + } + #cem_animation_status_warning { + background-color: transparent; + fill: #FFA500; + display: none; + position: relative; + } + #cem_animation_status_warning:after { + content: "!"; + position: absolute; + font-weight: 600; + font-size: 20px; + color: var(--color-dark); + } + #cem_animation_error_message { + display: flex; + margin-left: 8px; + gap: 3px; + overflow-x: auto; + white-space: nowrap; + } + #cem_animation_error_message span { + font-family: var(--font-code); + color: #a6e22e; + } + .cem_animation_error_line { + background-color: var(--color-close) !important; + color: var(--color-light) !important; + position: relative; + padding-right: 4px; + margin-right: -4px; + } + .cem_animation_error_line::after { + content: ""; + position: absolute; + left: 100%; + border-top: 12px solid transparent; + border-bottom: 12px solid transparent; + border-left: 12px solid var(--color-close); + } + .spacer, .cem_animation_range input { + flex: 1; + } + .cem_animation_bool { + display: flex; + align-items: center; + gap: 8px; + flex: 1 1 50%; + padding-right: 8px; + } + .cem_animation_range { + display: flex; + align-items: center; + margin: 0 !important; + height: 30px; + box-sizing: content-box; + } + .cem_animation_range > :nth-child(2) { + flex: 1 1 0px; + min-width: 40px; + } + .cem_animation_range p { + margin: 0 8px 0 0; + } + .cem_animation_range_number { + width: 2em; + margin-left: 2px; + cursor: text; + } + .cem_animation_button { + height: 25px; + min-width: 30px; + cursor: pointer; + position: relative; + } + .cem_animation_button:hover, #cem_animation_doc_button:hover { + color: var(--color-light); + } + .cem_animation_button i { + position: absolute; + top: -3px; + font-size: 30px; + min-width: 30px; + } + .cem_animation_button_small { + height: 15px; + min-width: 20px; + } + .cem_animation_button_small i { + font-size: 20px; + min-width: 20px; + } + .cem_animation_button_disabled, .cem_animation_button_disabled div { + color: var(--color-subtle_text) !important; + cursor: default; + } + button.cem_animation_button_disabled { + text-decoration: none; + opacity: 0.5; + } + button.cem_animation_button_disabled:hover { + background-color: var(--color-button); + color: var(--color-subtle_text) !important; + } + #panel_cem_animation>h3>label, #panel_cem_animation_controller>h3>label { + white-space: nowrap; + text-overflow: ellipsis; + } + .cem_animation_range_number { + min-width: 60px; + } + #cem_animation_controller_variables { + position: relative; + margin-top: 24px; + display: flex; + flex-direction: column; + gap: 8px; + } + #cem_animation_controller_variables:not(:empty):before { + content: ""; + position: absolute; + bottom: calc(100% + 10px); + left: 8px; + right: 8px; + height: 1px; + background-color: var(--color-border); + } + #cem_animation_range_labels div { + height: 30px; + display: flex; + align-items: center; + } + #cem_animation_ranges>div{ + display: flex; + flex-direction: column; + gap: 2px; + } + #cem_animation_bools { + display: flex; + flex-wrap: wrap; + } + #cem_animation_buttons { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + #cem_animation_doc_button { + cursor: pointer; + } + #cem_animation_format_button { + position: absolute; + top: 12px; + right: 12px; + } + `) animationControlPanel = new Panel("cem_animation_controller", { name: "Animation Controller", growable: true, @@ -770,13 +1440,6 @@ this.text = JSON.stringify(JSON.parse(this.text), null, 2) }, play() { - if (playButton.hasClass("cem_animation_button_disabled")) return - let selected = Group.selected ?? Cube.selected[0] - if (!selected) return - while (selected.parent !== "root") selected = selected.parent - setupAnimations([selected]) - }, - playAll() { if (playButton.hasClass("cem_animation_button_disabled")) return currentGroups = Group.all.filter(e => e.parent === "root") setupAnimations(currentGroups) @@ -825,146 +1488,44 @@ } }, template: ` -
-
Select a group to start editing its animations
-
-
-

Editing animations for part:

- - -

Documentation

-
description
-
+
+
+

Editing animations for part:

+ + +

Documentation

+
description
+
+
+
+ +
code
+
+
+
+
-
- +
+ clear
-
-
- -
-
- clear -
-
- -
-
- - -
playlist_play
-
play_arrow
-
- -
code
+
+
+
+ + +
play_arrow
+
+
` } }) const reInValue = /:[\s\n]*"[^"]*$/ - const boneVars = ["tx", "ty", "tz", "rx", "ry", "rz", "sx", "sy", "sz", "visible", "visible_boxes"] - const renderVars = ["shadow_size", "shadow_opacity", "shadow_offset_x", "shadow_offset_z", "leash_offset_x", "leash_offset_y", "leash_offset_z"] - const vars = [ - "pi", - "true", - "false", - "time", - "limb_swing", - "limb_speed", - "age", - "head_pitch", - "head_yaw", - "player_pos_x", - "player_pos_y", - "player_pos_z", - "player_rot_x", - "player_rot_y", - "frame_time", - "dimension", - "rule_index", - "health", - "hurt_time", - "death_time", - "anger_time", - "max_health", - "pos_x", - "pos_y", - "pos_z", - "rot_x", - "rot_y", - "swing_progress", - "id", - "sin()", - "cos()", - "asin()", - "acos()", - "tan()", - "atan()", - "atan2()", - "torad()", - "todeg()", - "min()", - "max()", - "clamp()", - "abs()", - "floor()", - "ceil()", - "exp()", - "frac()", - "log()", - "pow()", - "random()", - "round()", - "signum()", - "sqrt()", - "fmod()", - "lerp()", - "if()", - "print()", - "printb()", - "between()", - "equals()", - "in()", - ...boolList - ] - const varLabels = { - "sin()": "sin(x)", - "cos()": "cos(x)", - "asin()": "asin(x)", - "acos()": "acos(x)", - "tan()": "tan(x)", - "atan()": "atan(x)", - "atan2()": "atan2(y, x)", - "torad()": "torad(deg)", - "todeg()": "todeg(rad)", - "min()": "min(x, y, …)", - "max()": "max(x, y, …)", - "clamp()": "clamp(x, min, max)", - "abs()": "abs(x)", - "floor()": "floor(x)", - "ceil()": "ceil(x)", - "exp()": "exp(x)", - "frac()": "frac(x)", - "log()": "log(x)", - "pow()": "pow(x, y)", - "random()": "random(seed)", - "round()": "round(x)", - "signum()": "signum(x)", - "sqrt()": "sqrt(x)", - "fmod()": "fmod(x, y)", - "lerp()": "lerp(t, x, y)", - "if()": "if(c, v, [c2, v2, …,] v_else)", - "print()": "print(id, n, x)", - "printb()": "printb(id, n, x)", - "between()": "between(x, min, max)", - "equals()": "equals(x, y, epsilon)", - "in()": "in(x, val1, val2, …)" - } function filterAndSortList(list, match, blacklist, labels) { const result = list.filter(f => f.startsWith(match) && f.length !== match.length) list.forEach(f => { @@ -974,8 +1535,6 @@ return result.map(text => ({text, label: labels && labels[text], overlap: match.length})) } const partName = $("#cem_animation_part_name") - const placeholder = $("#cem_animation_placeholder") - const content = $("#cem_animation_content") const statusSuccess = $("#cem_animation_status_success") const statusError = $("#cem_animation_status_error") const statusWarning = $("#cem_animation_status_warning") @@ -1198,7 +1757,7 @@ const button = E("div").addClass("cem_animation_button").append( E("button").attr({ id: "cem_animation_death_time_button", - title: 'Simulate the entity attacking. Runs "death_time"' + title: 'Simulate the entity getting killed. Runs "death_time"' }).text("Kill entity").on("click", evt => { if ($(evt.target).hasClass("cem_animation_button_disabled")) return specials.set("death_time", [0, true]) @@ -1355,6 +1914,23 @@ death_time: specials.get("death_time")?.[1] ? specials.get("death_time")[0] += difference : 0, swing_progress: specials.get("swing_progress")?.[1] ? specials.get("swing_progress")[0] += difference / 4 : 0 }, constants, Object.fromEntries(bools), Object.fromEntries(Array.from(ranges.entries()).map(e => [e[0], e[1][1]]))) + if (settings.ignore_unkown_optifine_animations.value) { + context = new Proxy(context, { + get(target, prop) { + if (prop in target) return target[prop] + const num = e => e ?? 0 + num.valueOf = () => 0 + return num + } + }) + } else { + context = new Proxy(context, { + get(target, prop) { + if (prop in target) return target[prop] + throw new Error(`Unknown variable / function
:
" ${prop} "`) + } + }) + } const parts = new Map() for (const part of Group.all) { const partObj = { @@ -1418,12 +1994,18 @@ playing = false paused = false } - function hide() { - placeholder.css("display", "block") - content.css("display", "none") - } let group - updateSelection = () => { + function selectGroup() { + partName.text(group.name) + const animation = JSON.stringify(group.cem_animations?.length === 0 ? [{}] : group.cem_animations, null, 2) + if (animation) { + parseAnimations(animation) + animationEditorPanel.vue.text = animation + editorWrapper[0].__vue__._data.undoStack = [{ plain: animation }] + editorWrapper[0].__vue__._data.undoOffset = 0 + } + } + updateSelection = () => { if (Project.format?.id === "optifine_entity") { resizeWindow() stopAnimations() @@ -1432,39 +2014,34 @@ while (selected.parent !== "root") selected = selected.parent if (group !== selected) { group = selected - partName.text(group.name) - content.css("display", "flex") - placeholder.css("display", "none") - const animation = JSON.stringify(group.cem_animations?.length === 0 ? [{}] : group.cem_animations, null, 2) - if (animation) { - parseAnimations(animation) - animationEditorPanel.vue.text = animation - editorWrapper[0].__vue__._data.undoStack = [{plain: animation}] - editorWrapper[0].__vue__._data.undoOffset = 0 - } + selectGroup() } } } } Blockbench.on("update_selection", updateSelection) - updateSelection() tabChange = () => { - group = null - content.css("display", "none") - placeholder.css("display", "block") + setTimeout(() => { + group = Group.all[0] + if (Format.id === "optifine_entity" && group && !Group.selected) { + selectGroup() + } + }, 0) } Blockbench.on("select_project", tabChange) + tabChange() + updateSelection() editorKeybinds = evt => { if (evt.key === "s" && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { BarItems.export_over.trigger() evt.stopImmediatePropagation() } } - $("#cem_animation_editor_container>div")[0].addEventListener("keydown", editorKeybinds, true) + $("#cem_animation_editor_container > div")[0].addEventListener("keydown", editorKeybinds, true) resizeWindow() function addAnimationToggles() { if (Project.format?.id === "optifine_entity") { - const toggle = $(".outliner_toggle") + const toggle = $("[toolbar_id='outliner'] > div > [toolbar_item='outliner_toggle']") if (toggle.hasClass("enabled")) { const toggles = $("#cubes_list .group [toggle='autouv']") if (toggles.length) { @@ -1478,7 +2055,7 @@ const rotateToggle = E("i").attr({ title: "Disable this group rotating while playing animations", toggle: "cem_animation_disable_rotations" - }).addClass("material-icons icon_off").text("sync_disabled").on("click", evt => { + }).addClass("outliner_toggle material-icons icon_off").text("sync_disabled").on("click", evt => { evt.stopPropagation() const partName = toggle.parent().find("input").val() const part = Project.groups.find(e => e.name === partName) @@ -1514,543 +2091,191 @@ subtree: true }) addAnimationToggles() + globalThis.optifineAnimationDocumentation = { + custom: [] + } async function showDocumentation() { - if (!docShown) { - let docData - try { - const r = await fetch("https://wynem.com/assets/json/cem_animation_doc.json") - if (r.status !== 200) throw Error - docData = await r.json() - } catch (err) { - console.error(err) - return new Dialog({ - id: "cem_template_loader_connection_failure_dialog", - title: "CEM Animation Documentation", - lines: ['

Connection failed

Please check your internet connection and make sure that you can access wynem.com'], - buttons: ["Okay"] - }).show() - } - docShown = true - documentation = new Dialog({ + if (optifineAnimationDocumentation.shown) { + optifineAnimationDocumentation.dialog.show() + } else { + if (!optifineAnimationDocumentation.data) { + optifineAnimationDocumentation.data = await fetchData("json/cem_animation_doc.json") + optifineAnimationDocumentation.data.tabs ??= [] + } + optifineAnimationDocumentation.shown = true + const tabs = [...optifineAnimationDocumentation.data.tabs, ...optifineAnimationDocumentation.custom] + optifineAnimationDocumentation.dialog = new Dialog({ id: "cem_animation_documentation", title: "CEM Animation Documentation", - width: 780, - lines: [` -
-
- `], - buttons: [] - }).show() - const doc = $("#cem_doc") - const tabs = $("#cem_doc_tabs") - for (const tab of docData.tabs) { - const name = tab.name.replace(/ /g, "_") - tabs.append(E("div").attr("id", `cem_doc_tab_${tab.name.replace(/ /g, "-")}`).html(tab.name).on("click", evt => { - $("#cem_doc_tabs>div").removeClass("selected") - $("#cem_doc>div").removeClass("selected") - $(evt.target).addClass("selected") - $(`#cem_doc_page_${name}`).addClass("selected") - $("#cem_animation_documentation .dialog_content")[0].scrollTo(0, 0) - })) - const page = E("div").attr("id", `cem_doc_page_${name}`).appendTo(doc) - for (const element of tab.elements) { - if (element.type === "heading") page.append(E("h2").html(element.text)) - else if (element.type === "text") page.append(E("p").html(element.text)) - else if (element.type === "code") page.append(E("pre").html(element.text)) - else if (element.type === "table") { - const table = E("table").appendTo(page) - if (element.tableType === "list") table.addClass("cem_doc_table_list") - for (const row of element.rows) { - const tr = E("tr").appendTo(table) - for (const [i, cell] of row.entries()) { - tr.append(E("td").html(cell)) + width: 980, + buttons: [], + sidebar: { + pages: Object.fromEntries(tabs.map(e => [e.name, { + label: e.name, + icon: e.icon + }])), + onPageSwitch(page) { + optifineAnimationDocumentation.dialog.content_vue.page = page + } + }, + lines: [``], + component: { + data: { + version: optifineAnimationDocumentation.data.version, + optifineVersion: optifineAnimationDocumentation.data.optifineVersion, + tabs, + connection, + page: tabs[0]?.name + }, + methods: { + reload() { + window.cemTemplateAnimationDocReloaded = true + plugin.reload() + } + }, + template: ` +
+
+

Connection Failed

+

Failed to load CEM Animation documentation.

+

Please make sure you are connected to the internet, and can access this cem_animation_doc.json file.

+

If you are unable to access the cem_animation_doc.json file, it may be blocked by your computer or your internet service provider. If it is not your computer blocking it, you may be able to use a VPN to bypass the block. One good example is Cloudflare WARP, which is a free program that commonly resolves this issue.

+ +
+
+ +
+

Documentation version: {{ version }}
Updated to: OptiFine {{ optifineVersion }}

+
+
+ ` + }, + onBuild() { + this.object.querySelector("#cem-container").addEventListener("click", e => { + if (e.target.classList.contains("cem-doc-tab-link")) { + this.content_vue.page = e.target.textContent + this.sidebar.page = e.target.textContent + this.sidebar.build() + } + }) } - } - $("#cem_doc_tabs>:first-child").addClass("selected") - $("#cem_doc>:first-child").addClass("selected") - $(".cem_doc_tab_link").on("click", evt => { - $("#cem_doc_tabs>div").removeClass("selected") - $("#cem_doc>div").removeClass("selected") - $(`#cem_doc_tab_${evt.target.textContent.replace(/ /g, "-")}`).addClass("selected") - $(`#cem_doc_page_${evt.target.textContent}`).addClass("selected") - $("#cem_animation_documentation .dialog_content")[0].scrollTo(0, 0) - }) - if (Blockbench.isWeb) $(".cem-doc-display-desktop").css("display", "none") - else $(".cem-doc-display-web").css("display", "none") - doc.append( - E("hr"), - E("p").html(`Documentation version: v${docData.version}\nUpdated to: OptiFine ${docData.optifineVersion}`) - ) - } else documentation.show() + }).show() + } + } + if (window.cemTemplateAnimationDocReloaded) { + delete window.cemTemplateAnimationDocReloaded + showDocumentation() } } - function addStyles() { - styles = Blockbench.addCSS(` - #panel_cem_animation .panel_vue_wrapper { - flex: 1; - padding: 8px; - overflow: auto !important; - display: flex; - flex-direction: column; - max-height: 100%; - } - #panel_cem_animation .prism-editor-wrapper { - background-color: var(--color-back); - } - #panel_cem_animation .prism-editor__line-numbers { - overflow: visible; - min-height: 100% !important; - position: sticky; - left: 0; - } - #panel_cem_animation .prism-editor__line-number { - background-color: var(--color-back); - } - #panel_cem_animation .prism-editor__code { - overflow: visible !important; - } - #cem_animation_editor_container { - overflow: auto; - flex: 1; - max-height: calc(100% - 40px); - min-height: 3.5em; - display: flex; - } - #cem_animation_controller_container { - flex: 1; - padding: 8px; - display: flex; - flex-direction: column; - max-height: 100%; - overflow: auto !important; - } - #cem_animation_editor { - cursor: text; - flex: 1; - } - #cem_animation_title { - margin-left: 0; - } - #cem_animation_content { - display: none; - flex-direction: column; - flex: 1; - max-height: 100%; - } - .cem_animation_bar { - display: flex; - align-items: center; - gap: 8px; - } - .cem_animation_bar span { - display: flex; - gap: 8px; - align-items: center; - } - #cem_animation_part_name { - display: inline !important; - } - .cem_animation_bar i, #cem_doc > div.selected { - display: block; - } - .cem_animation_status { - min-width: 25px; - height: 25px; - border-radius: 50%; - display: flex; - justify-content: center; - align-items: center; - } - #cem_animation_status_success { - background-color: var(--color-confirm); - color: var(--color-dark); - } - #cem_animation_status_error { - background-color: var(--color-close); - color: var(--color-light); - display: none; - } - #cem_animation_status_warning { - background-color: transparent; - fill: #FFA500; - display: none; - position: relative; - } - #cem_animation_status_warning:after { - content: "!"; - position: absolute; - font-weight: 600; - font-size: 20px; - color: var(--color-dark); - } - #cem_animation_error_message { - display: flex; - margin-left: 8px; - gap: 3px; - overflow-x: auto; - white-space: nowrap; - } - #cem_animation_error_message span { - font-family: var(--font-code); - color: #a6e22e; - } - .cem_animation_error_line { - background-color: var(--color-close) !important; - color: var(--color-light) !important; - position: relative; - padding-right: 4px; - margin-right: -4px; - } - .cem_animation_error_line::after { - content: ""; - position: absolute; - left: 100%; - border-top: 12px solid transparent; - border-bottom: 12px solid transparent; - border-left: 12px solid var(--color-close); - } - .spacer, .cem_animation_range input { - flex: 1; - } - .cem_animation_bool { - display: flex; - align-items: center; - gap: 8px; - flex: 1 1 50%; - padding-right: 8px; - } - .cem_animation_range { - display: flex; - align-items: center; - margin: 0 !important; - height: 30px; - box-sizing: content-box; - } - .cem_animation_range p { - margin: 0 8px 0 0; - } - .cem_animation_range_number { - width: 2em; - margin-left: 2px; - } - .cem_animation_button { - height: 25px; - min-width: 30px; - cursor: pointer; - position: relative; - } - .cem_animation_button:hover, #cem_animation_doc_button:hover { - color: var(--color-light); - } - .cem_animation_button i { - position: absolute; - top: -3px; - font-size: 30px; - min-width: 30px; - } - .cem_animation_button_small { - height: 15px; - min-width: 20px; - } - .cem_animation_button_small i { - font-size: 20px; - min-width: 20px; - } - .cem_animation_button_disabled, .cem_animation_button_disabled div { - color: var(--color-subtle_text) !important; - cursor: default; - } - button.cem_animation_button_disabled { - text-decoration: none; - opacity: 0.5; - } - button.cem_animation_button_disabled:hover { - background-color: var(--color-button); - color: var(--color-subtle_text) !important; - } - #panel_cem_animation>h3>label, #panel_cem_animation_controller>h3>label { - white-space: nowrap; - text-overflow: ellipsis; - } - .cem_animation_range_number { - min-width: 60px; - } - #cem_animation_controller_variables { - position: relative; - margin-top: 24px; - display: flex; - flex-direction: column; - gap: 8px; - } - #cem_animation_controller_variables:not(:empty):before { - content: ""; - position: absolute; - bottom: calc(100% + 10px); - left: 8px; - right: 8px; - height: 1px; - background-color: var(--color-border); - } - #cem_animation_range_labels div { - height: 30px; - display: flex; - align-items: center; - } - #cem_animation_ranges>div{ - display: flex; - flex-direction: column; - gap: 2px; - } - #cem_animation_bools { - display: flex; - flex-wrap: wrap; - } - #cem_animation_buttons { - display: flex; - flex-wrap: wrap; - gap: 8px; - } - #cem_animation_doc_button { - cursor: pointer; - } - #cem_animation_documentation .dialog_content { - margin: 0; - position: relative; - } - #cem_doc { - margin: 16px; - } - #cem_doc > div { - display: none; - } - #cem_doc * { - white-space: pre-wrap; - } - #cem_doc h2 { - font-size: 25px; - } - #cem_doc > div > :first-child { - margin-top: -8px; - } - #cem_doc h2:not(:first-child) { - padding-top: 16px; - } - #cem_doc td:not(:last-child) { - padding-right: 16px; - } - #cem_doc code, #cem_doc pre { - background-color: var(--color-back); - padding: 0 4px; - border: 1px solid var(--color-border); - user-select: text; - cursor: text; - font-family: var(--font-code) - } - #cem_doc pre { - margin-bottom: 16px; - } - #cem_doc img { - margin: 8px; - box-shadow: 0 3px 10px #0006; - } - #cem_doc_tabs { - background-color: var(--color-frame); - display: flex; - gap: 2px; - padding: 4px 4px 0; - position: sticky; - top: 0; - border-bottom: 4px solid var(--color-ui); - } - #cem_doc_tabs > div { - padding: 4px 12px; - cursor: pointer; - border-top: 2px solid transparent; - background-color: var(--color-back); - } - #cem_doc_tabs > div.selected { - background-color: var(--color-ui); - border-top-color: var(--color-accent); - cursor: default; - } - .cem_doc_table_list td:first-child { - font-weight: 600; - white-space: nowrap !important; - display: list-item; - list-style: inside; - font-family: var(--font-code); - } - .cem-doc-tab-link { - text-decoration: underline; - cursor: pointer; - color: var(--color-accent); - } - .cem-template-loader-links { - display: flex; - justify-content: space-around; - margin: 20px 40px 0; - } - .cem-template-loader-links > a { - display: flex; - flex-direction: column; - align-items: center; - gap: 5px; - padding: 5px; - text-decoration: none; - flex-grow: 1; - flex-basis: 0; - color: var(--color-subtle_text); - text-align: center; - } - .cem-template-loader-links > a:hover { - background-color: var(--color-accent); - color: var(--color-light); - } - .cem-template-loader-links > a > i { - font-size: 32px; - width: 100%; - max-width: initial; - height: 32px; - text-align: center; - } - .cem-template-loader-links > a:hover > i { - color: var(--color-light) !important; - } - .cem-template-loader-links > a > p { - flex: 1; - display: flex; - align-items: center; - } - #format_page_cem_template_loader { - padding-bottom: 0; - } - #format_page_cem_template_loader .format_target, #format_page_optifine_entity .format_target { - margin-bottom: 6px; - } - #format_page_cem_template_loader div:nth-child(3), #format_page_cem_template_loader content { - overflow-y: auto; - } - #format_page_optifine_entity h3.markdown { - margin-bottom: -10px; - } - `) + + function unloadOptiFineAnimationEditor() { + stopAnimations?.(true) + Blockbench.removeListener("update_selection", updateSelection) + Blockbench.removeListener("select_project", tabChange) + $("#cem_animation_editor_container > div")[0].removeEventListener("keydown", editorKeybinds) + groupObserver.disconnect() + $("[toggle='cem_animation_disable_rotations']").remove() + animationEditorPanel.delete() + animationControlPanel.delete() + animationStyles.delete() + optifineAnimationDocumentation.dialog?.close() + resizeWindow() + delete globalThis.optifineAnimationVariables + delete globalThis.optifineAnimationDocumentation } - Plugin.register(id, { - title: name, - icon: "icon.png", - author, - description: description + " Also includes an animation editor, so that you can create custom entity animations.", - tags: ["Minecraft: Java Edition", "OptiFine", "Templates"], - version: "7.8.0", - min_version: "4.8.0", - variant: "both", - creation_date: "2020-02-02", - async onload() { - addStyles() - await setupPlugin("https://wynem.com/assets/json/cem_template_models.json") - setupAnimationPanel() - new Setting("jem_restrictions", { - value: false, - category: "edit", - name: "Remove OptiFine Entity Restrictions", - description: "Remove the root group restrictions on the OptiFine Entity format. WARNING: You can easily break models with restrictions removed." - }) - function editCheckProcess(entry) { - if (entry.before.outliner) { - for (const group of entry.before.outliner) { - const postGroup = entry.post.outliner.find(e => e.uuid === group.uuid) - if (!postGroup) return "You cannot remove root cubes/groups!" - if (!group.origin.reduce((a, e, x) => a && e === postGroup.origin[x], true)) { - return "You cannot move root groups!" - } else if (group.name !== postGroup.name) { - return "You cannot rename root groups!" - } - } - } - if (entry.post.outliner) { - for (const group of entry.post.outliner) { - const beforeGroup = entry.before.outliner.find(e => e.uuid === group.uuid) - if (!beforeGroup) return "You cannot add new root cubes/groups!" - } - } - if (entry.before.group && entry.post.group && Outliner.root.find(node => node instanceof Group && node.uuid == entry.before.group.uuid)) { - if (!entry.before.group.rotation.reduce((a, e, x) => a && e === entry.post.group.rotation[x], true)) { - return "You cannot rotate root groups!" - } else if (!entry.before.group.origin.reduce((a, e, x) => a && e === entry.post.group.origin[x], true)) { - return "You cannot move root group pivots!" - } - } - } - editCheck = () => { - if (!settings.jem_restrictions.value && Format.id === "optifine_entity") { - const entry = Undo.history[Undo.history.length-1] - const check = editCheckProcess(entry) - if (check) { - Blockbench.showQuickMessage(check, 1200) - Undo.loadSave(entry.before, entry.post) - Undo.history.pop() - Undo.index = Undo.history.length - } - } - } - Blockbench.on("finished_edit", editCheck) - originalJEMFormat = { - new: Formats.optifine_entity.new, - format_page: Formats.optifine_entity.format_page, - convertTo: Formats.optifine_entity.convertTo - } - Formats.optifine_entity.new = () => { - if (settings.jem_restrictions.value) originalJEMFormat.new.bind(Formats.optifine_entity)() - else loadInterface() - } - Formats.optifine_entity.format_page = JSON.parse(JSON.stringify(Formats.optifine_entity.format_page)) - Formats.optifine_entity.format_page.content.push({ type: "h3", text: "CEM Template Loader" }, { text: "Creating an OptiFine entity will open CEM Template Loader. It is extremely rare to need a blank OptiFine entity model. You can disable this behavour with **File > Preferences > Settings > Edit > Remove OptiFine Entity Restrictions**, however, this is not recommended." }) - Formats.optifine_entity.convertTo = () => { - originalJEMFormat.convertTo.bind(Formats.optifine_entity)() - if (!settings.jem_restrictions.value) { - Blockbench.showMessageBox({ - title: "Conversion Warning", - message: "Models converted into OptiFine entities are not valid entity models. If you are converting a model into an OptiFine entity to use in game, expect it to be broken. Instead load a new template entity model, and copy your elements across into the template model.", - buttons: ["Load Template", "Continue"], - icon: "warning" - }, button => { - if (button === 0) loadInterface() - }) - } - } - if (Blockbench.isWeb) { - const params = (new URL(location.href)).searchParams - if (params.has("plugins") && params.get("plugins").split(",").includes("cem_template_loader") && params.has("model") && params.get("model") !== "") loadModel(params.get("model").toLowerCase(), null, params.has("texture")) - } - }, - onunload() { - stopAnimations?.(true) - Blockbench.removeListener("update_selection", updateSelection) - Blockbench.removeListener("select_project", tabChange) - Blockbench.removeListener("finished_edit", editCheck) - $("#cem_animation_editor_container>div")[0].removeEventListener("keydown", editorKeybinds) - groupObserver.disconnect() - loader.delete() - $("[toggle='cem_animation_disable_rotations']").remove() - for (const action of generatorActions) action.delete?.() - MenuBar.removeAction("tools.cem_template_loader") - animationEditorPanel.delete() - animationControlPanel.delete() - styles.delete() - Formats.optifine_entity.new = originalJEMFormat.new - Formats.optifine_entity.format_page = originalJEMFormat.format_page - Formats.optifine_entity.convertTo = originalJEMFormat.convertTo - entitySelector?.close() - documentation?.close() - resizeWindow() - } - }) + + // PLUGIN + + loadPlugin() })() \ No newline at end of file diff --git a/plugins/cem_template_loader/changelog.json b/plugins/cem_template_loader/changelog.json new file mode 100644 index 00000000..2d191c5b --- /dev/null +++ b/plugins/cem_template_loader/changelog.json @@ -0,0 +1,848 @@ +{ + "0.1.0": { + "title": "0.1.0", + "date": "2020-02-02", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Initial release" + ] + } + ] + }, + "0.2.0": { + "title": "0.2.0", + "date": "2020-02-17", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added 1.15 and 1.16 template models" + ] + } + ] + }, + "0.3.0": { + "title": "0.3.0", + "date": "2020-02-23", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed the \"minecart\" template model" + ] + } + ] + }, + "0.4.0": { + "title": "0.4.0", + "date": "2020-02-26", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Added missing \"hat\" bone to the \"illusioner\" template model" + ] + } + ] + }, + "0.5.0": { + "title": "0.5.0", + "date": "2020-03-05", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed the \"pufferfish_small\" template model" + ] + } + ] + }, + "0.6.0": { + "title": "0.6.0", + "date": "2020-03-19", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added new \"player\", \"player_slim\", \"elytra\", \"trident\", \"shield\", and \"arrow\" template models", + "Added new \"melon_golem\", \"furnace_golem\", and \"tropical_slime\" Minecraft Earth template models" + ] + }, + { + "title": "Changes", + "list": [ + "Modified the \"banner\" template model" + ] + } + ] + }, + "0.7.0": { + "title": "0.7.0", + "date": "2020-04-14", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added new Minecraft Earth template models" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed the \"wolf\" template model" + ] + } + ] + }, + "0.8.0": { + "title": "0.8.0", + "date": "2020-05-15", + "author": "Ewan Howell", + "categories": [ + { + "title": "Removed Features", + "list": [ + "Removed Minecraft Earth template models" + ] + }, + { + "title": "Changes", + "list": [ + "Added \"strider\", \"zoglin\", and \"zombified_piglin\" template models to the unsupported category" + ] + } + ] + }, + "0.9.0": { + "title": "0.9.0", + "date": "2020-05-20", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Added a section showing what template models are currently compatible with 1.15" + ] + } + ] + }, + "1.0.0": { + "title": "1.0.0", + "date": "2020-06-01", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added a new \"Legacy\" section which now contains the 1.14 chest template models", + "Replaced the chest template models with the 1.15 versions" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed the \"villager\" template model" + ] + } + ] + }, + "2.0.0": { + "title": "2.0.0", + "date": "2020-07-17", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Complete revamp of all template models" + ] + } + ] + }, + "2.1.0": { + "title": "2.1.0", + "date": "2020-07-17", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed \"player\" and \"player_slim\" template models" + ] + } + ] + }, + "2.2.0": { + "title": "2.2.0", + "date": "2020-07-22", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed the \"villager\" template model failing to load" + ] + } + ] + }, + "2.3.0": { + "title": "2.3.0", + "date": "2020-08-28", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added the \"piglin\", \"piglin_brute\", and \"zombified_piglin\" template models to the supported category" + ] + } + ] + }, + "2.4.0": { + "title": "2.4.0", + "date": "2020-10-03", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Added unofficial \"axolotl\", \"goat\", and \"warden\" template models" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Minor bug fixes to various template models" + ] + } + ] + }, + "2.5.0": { + "title": "2.5.0", + "date": "2020-10-04", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added an unofficial \"glow_squid\" template model" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed issues with the \"dolphin\", \"squid\", \"axolotl\", and \"warden\" template models" + ] + } + ] + }, + "2.6.0": { + "title": "2.6.0", + "date": "2020-10-17", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Added template textures to lots of template models", + "Improved the unofficial \"goat\" template model" + ] + } + ] + }, + "2.7.0": { + "title": "2.7.0", + "date": "2020-10-31", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed issues with the \"wither\" and \"parrot\" template models" + ] + } + ] + }, + "2.8.0": { + "title": "2.8.0", + "date": "2020-12-16", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Replaced the unofficial \"axolotl\", \"goat\", and \"glow_squid\" model templates with the official models" + ] + } + ] + }, + "2.9.0": { + "title": "2.9.0", + "date": "2021-01-25", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed a typo in the \"chest_large\" model" + ] + } + ] + }, + "2.10.0": { + "title": "2.10.0", + "date": "2021-02-03", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed issues with the \"wither\", \"wither skull\", and \"rabbit\" models" + ] + } + ] + }, + "3.0.0": { + "title": "3.0.0", + "date": "2021-03-25", + "author": "Ewan Howell", + "categories": [ + { + "title": "Technical Changes", + "list": [ + "Template models are now loaded from an external model JSON file" + ] + } + ] + }, + "3.1.0": { + "title": "3.1.0", + "date": "2021-03-27", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added the ability to reload the external external model JSON file", + "Added external model JSON version number" + ] + } + ] + }, + "4.0.0": { + "title": "4.0.0", + "date": "2021-05-25", + "author": "Ewan Howell", + "categories": [ + { + "title": "Technical Changes", + "list": [ + "Restructured the external model JSON file to reduce it's file size" + ] + } + ] + }, + "5.0.0": { + "title": "5.0.0", + "date": "2021-08-07", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added a brand new interface for selecting template models" + ] + }, + { + "title": "Technical Changes", + "list": [ + "Rewritten most of the plugin" + ] + } + ] + }, + "5.0.1": { + "title": "5.0.1", + "date": "2021-08-12", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Add plugin about details" + ] + } + ] + }, + "5.3.0": { + "title": "5.3.0", + "date": "2021-10-14", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Support for Blockbench v4.0.0" + ] + } + ] + }, + "5.4.1": { + "title": "5.4.1", + "date": "2021-12-15", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Updated unable to connect dialog" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed a bug with reloading template models" + ] + } + ] + }, + "5.10.2": { + "title": "5.10.2", + "date": "2022-10-17", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added info dialog that is shown when the plugin is first installed" + ] + }, + { + "title": "Changes", + "list": [ + "Added bug report button to the loader interface" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed \"CEM Template Loader\" not appeaing in the start screen" + ] + } + ] + }, + "5.11.0": { + "title": "5.11.0", + "date": "2022-03-24", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Models can now be automatically loaded from URL queries in the webapp" + ] + } + ] + }, + "6.0.0": { + "title": "6.0.0", + "date": "2022-05-05", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added an OptiFine animation editor with support for editing and previewing animations from OptiFine CEM" + ] + } + ] + }, + "6.0.1": { + "title": "6.0.1", + "date": "2022-05-05", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Only try to read OptiFine animations when the current format is an \"OptiFine Entity\"" + ] + } + ] + }, + "6.0.2": { + "title": "6.0.2", + "date": "2022-05-05", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed switching project tabs breaking the OptiFine animation editor" + ] + } + ] + }, + "6.0.3": { + "title": "6.0.3", + "date": "2022-05-09", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Only try to stop OptiFine animations when the current format is an \"OptiFine Entity\"" + ] + } + ] + }, + "6.0.4": { + "title": "6.0.4", + "date": "2022-05-10", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed selection update event listener when no format is loaded" + ] + } + ] + }, + "6.1.0": { + "title": "6.1.0", + "date": "2022-05-29", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Restructured the plugin's info dialog and renamed it to the about dialog" + ] + }, + { + "title": "Technical Changes", + "list": [ + "Changed all references to the \"Filter\" menu to the new \"Tools\" menu" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed \"CEM Template Loader\" not appeaing in the start screen" + ] + } + ] + }, + "6.2.0": { + "title": "6.2.0", + "date": "2022-06-4", + "author": "Ewan Howell", + "categories": [ + { + "title": "Technical Changes", + "list": [ + "Small edits to the plugin's structure" + ] + } + ] + }, + "6.2.1": { + "title": "6.2.1", + "date": "2022-07-11", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "Replaced tutorials playlist link with a single tutorial video" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed playing animations when a non root group is selected" + ] + } + ] + }, + "6.5.0": { + "title": "6.5.0", + "date": "2022-07-22", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added seeded randomness support to the \"random\" OptiFine animation function", + "Added new OptiFine animation variables: \"id\", \"rot_x/y\", \"player_pos_x/y/z\", \"player_rot_x/y\", \"death_time\", \"dimension\", \"frame_time\"", + "Added new OptiFine animation functions: \"printb\", \"lerp\"", + "Added support for OptiFine animation \"var.\" and \"varb.\" entity variables" + ] + }, + { + "title": "Changes", + "list": [ + "The \"print\" function has been updated to match the officially supported one" + ] + }, + { + "title": "Removed Features", + "list": [ + "Removed internal support for the \"idle_time\" and \"revenge_time\" OptiFine animation variables", + "Removed \"Invert this groups pivot point while playing animations\" group option" + ] + }, + { + "title": "Technical Changes", + "list": [ + "Switched to using the new ModelLoaders" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed the \"sqrt\" OptiFine animation function when used with numbers that are zero or below", + "Fixed the \"if\" OptiFine animation function allowing non boolean inputs", + "Fixed OptiFine animations crashing if a cube is selected", + "Fixed having groups named the same as functions causing issues" + ] + } + ] + }, + "6.5.1": { + "title": "6.5.1", + "date": "2022-07-25", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed a gap appearing between the OptiFine animation editor and the viewport" + ] + } + ] + }, + "6.6.0": { + "title": "6.6.0", + "date": "2022-10-02", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added \"rule_index\" OptiFine animation variable", + "Added \".visible\" OptiFine animation property" + ] + } + ] + }, + "6.6.1": { + "title": "6.6.1", + "date": "2022-10-11", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed issues with OptiFine animation validation", + "Fixed broken \".visible\" references" + ] + } + ] + }, + "6.6.2": { + "title": "6.6.2", + "date": "2022-10-24", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed \"print\" and \"lerp\" functions" + ] + } + ] + }, + "6.6.3": { + "title": "6.6.3", + "date": "2022-10-26", + "author": "Ewan Howell", + "categories": [ + { + "title": "Changes", + "list": [ + "\"rule_index\" animation variable is now defaulted to \"0\"", + "\"frame_time\" is now affected by the animation timescale" + ] + } + ] + }, + "6.6.4": { + "title": "6.6.4", + "date": "2022-11-07", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed issues with OptiFine animation validation" + ] + } + ] + }, + "6.6.5": { + "title": "6.6.5", + "date": "2022-11-25", + "author": "Ewan Howell", + "categories": [ + { + "title": "Technical Changes", + "list": [ + "Updated model source URL" + ] + } + ] + }, + "6.11.0": { + "title": "6.11.0", + "date": "2023-01-17", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added autocomplete support to the OptiFine animation editor" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed about action not being removed on unload" + ] + } + ] + }, + "6.11.1": { + "title": "6.11.1", + "date": "2023-01-18", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed autocomplete for bones with numbers in the name" + ] + } + ] + }, + "7.0.0": { + "title": "7.0.0", + "date": "2023-02-03", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added JEM format restrictions" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed disable rotation toggles appearing on cubes", + "Fixed broken styles" + ] + } + ] + }, + "7.1.0": { + "title": "7.1.0", + "date": "2023-9-18", + "author": "Ewan Howell", + "categories": [ + { + "title": "Bug Fixes", + "list": [ + "Fixed tab key in OptiFine animation editor", + "Fixed OptiFine animation editor autocomplete" + ] + } + ] + }, + "7.8.0": { + "title": "7.8.0", + "date": "2023-10-17", + "author": "Ewan Howell", + "categories": [ + { + "title": "Removed Features", + "list": [ + "Removed the ability to reload the template models" + ] + }, + { + "title": "Technical Changes", + "list": [ + "Update to new plugin repository format" + ] + } + ] + }, + "8.0.0": { + "title": "8.0.0", + "date": "2024-05-12", + "author": "Ewan Howell", + "categories": [ + { + "title": "New Features", + "list": [ + "Added a new dialog that shows when invalid operations are performed", + "Added an option to ignore unknown OptiFine animations", + "Loaded textures are now set as the default texture. This does not apply for models that load multiple textures" + ] + }, + { + "title": "Changes", + "list": [ + "\"Load vanilla texture\" will now be selected by default", + "Moved the pretty print button", + "The OptiFine animation editor will now always have a group selected", + "Changed the play animaions button's icon to be the regular play icon" + ] + }, + { + "title": "Removed Features", + "list": [ + "Removed \"Load into current project\"", + "Removed the ability to play only the current group's animations" + ] + }, + { + "title": "Technical Changes", + "list": [ + "Rewritten most of the plugin", + "The CEM Animation Editor can now be extended by other plugins", + "The CEM Animation Documentation can now be extended by other plugins", + "Added CDN url fallback system", + "The external model JSON file's version number will now be separated from the plugin's version number. I have reset that version number to 2.0.0 and it will increase separately from the plugin's version number from now on" + ] + }, + { + "title": "Bug Fixes", + "list": [ + "Fixed the page links in the CEM Animation Documentation" + ] + } + ] + } +} \ No newline at end of file diff --git a/plugins/minecraft_title_generator/changelog.json b/plugins/minecraft_title_generator/changelog.json index 57808173..a0b45aee 100644 --- a/plugins/minecraft_title_generator/changelog.json +++ b/plugins/minecraft_title_generator/changelog.json @@ -20,7 +20,7 @@ { "title": "Bug Fixes", "list": [ - "Fix resetting all tabs" + "Fixed resetting all tabs" ] } ] @@ -46,11 +46,11 @@ { "title": "Bug Fixes", "list": [ - "Fix missing pointer cursors", - "Fix debug mode duplicating textures", - "Fix plugin about action not getting removed on plugin unload", - "Fix anchor links in Blockbench webapp", - "Fix gradient overlay scaling" + "Fixed missing pointer cursors", + "Fixed debug mode duplicating textures", + "Fixed plugin about action not getting removed on plugin unload", + "Fixed anchor links in Blockbench webapp", + "Fixed gradient overlay scaling" ] } ] @@ -63,7 +63,7 @@ { "title": "Bug Fixes", "list": [ - "Fix for Safari versions older than version 16.4" + "Fixed for Safari versions older than version 16.4" ] } ] @@ -76,7 +76,7 @@ { "title": "Bug Fixes", "list": [ - "Fix the \"V\" character in the built-in font" + "Fixed the \"V\" character in the built-in font" ] } ] @@ -224,7 +224,7 @@ { "title": "Bug Fixes", "list": [ - "Fix fonts that are flat" + "Fixed fonts that are flat" ] } ] @@ -273,7 +273,7 @@ { "title": "Bug Fixes", "list": [ - "Fix debug mode thumbnails" + "Fixed debug mode thumbnails" ] } ] @@ -302,7 +302,7 @@ { "title": "Bug Fixes", "list": [ - "Fix backup CDN for if a CDN goes down after you have opened Blockbench and used the plugin" + "Fixed backup CDN for if a CDN goes down after you have opened Blockbench and used the plugin" ] } ] @@ -315,7 +315,7 @@ { "title": "Bug Fixes", "list": [ - "Fix ability to bypass EULA agreement" + "Fixed ability to bypass EULA agreement" ] } ] @@ -335,8 +335,8 @@ { "title": "Bug Fixes", "list": [ - "Fix render controls disappearing for some people", - "Fix render controls missing in Blockbench v4.10.0" + "Fixed render controls disappearing for some people", + "Fixed render controls missing in Blockbench v4.10.0" ] } ] diff --git a/plugins/minecraft_title_generator/minecraft_title_generator.js b/plugins/minecraft_title_generator/minecraft_title_generator.js index 45c8e62b..704e9eca 100644 --- a/plugins/minecraft_title_generator/minecraft_title_generator.js +++ b/plugins/minecraft_title_generator/minecraft_title_generator.js @@ -106,6 +106,8 @@ }, has_changelog: true, website: "https://ewanhowell.com/plugins/minecraft-title-generator/", + repository: "https://github.com/ewanhowell5195/blockbenchPlugins/tree/main/minecraft-title-generator", + bug_tracker: "https://github.com/ewanhowell5195/blockbenchPlugins/issues/new?title=[Minecraft Title Generator]", async onload() { styles = Blockbench.addCSS(` .minecraft-title-list {