From 7a9dfd310b58b032463a100e5be238d9d0928cc2 Mon Sep 17 00:00:00 2001 From: aimozg <aimozg@ya.ru> Date: Mon, 24 May 2021 20:30:46 +0600 Subject: [PATCH] - Fixes "chest_bind" clothes affecting doll in character menu - "New" is default renderer - "Both" is always available - "Debug renderer" is always available (enabled in options) - "Colours" debug menu is always available, cheat mode enables character recolouring --- game/02-CSS/base.css | 9 +- game/02-CSS/canvasmodel.css | 8 + game/03-JavaScript/canvasmodel-editor.js | 907 +++++++++--------- game/04-Variables/colours.js | 1 - .../04-Variables/variables-versionUpdate.twee | 6 +- game/base-clothing/canvasmodel-img.twee | 12 +- game/base-system/caption.twee | 5 +- game/base-system/options.twee | 5 +- game/base-system/overlayReplace.twee | 12 +- 9 files changed, 510 insertions(+), 455 deletions(-) diff --git a/game/02-CSS/base.css b/game/02-CSS/base.css index ba66e9fc64..37b841b5d6 100644 --- a/game/02-CSS/base.css +++ b/game/02-CSS/base.css @@ -47,13 +47,20 @@ mouse.tooltip:hover span { transition:.2s ease-in; } -.closeButton, .macro-button { +.closeButton, .macro-button, .buttonlike { width: 100%; font-size: 110%; max-width: 240px; background-color: #222; border: 1px solid #444; } +.buttonlike { + display: inline-block; + text-align: center; +} +.buttonlike.-noborder { + border: 1px solid transparent; +} .widerButton button { max-width: 280px; diff --git a/game/02-CSS/canvasmodel.css b/game/02-CSS/canvasmodel.css index 25d61fb9dd..d969deda42 100644 --- a/game/02-CSS/canvasmodel.css +++ b/game/02-CSS/canvasmodel.css @@ -10,6 +10,14 @@ table.editorlayers { display: flex; flex-wrap: wrap; } +.editorcolours .export-block { + float: right; + padding-top: 1.33em; +} +.editorcolours table th { + font-size: small; + text-align: center; +} input.editlayer-z, input.editlayer-alpha { width: 3.5em; min-width: unset; diff --git a/game/03-JavaScript/canvasmodel-editor.js b/game/03-JavaScript/canvasmodel-editor.js index 623ecf81fa..686133c2f4 100644 --- a/game/03-JavaScript/canvasmodel-editor.js +++ b/game/03-JavaScript/canvasmodel-editor.js @@ -2,23 +2,23 @@ function eleprop(e, k, v) { if (k === '$oncreate') { v.call(e, e); } else if (k.indexOf('on') === 0) { - e.addEventListener(k.slice(2), v.bind(e)); - } else if (v !== undefined) { - if (k in e) { - e[k] = v; - } else { - e.setAttribute(k, v); - } - } - return e; + e.addEventListener(k.slice(2), v.bind(e)); + } else if (v !== undefined) { + if (k in e) { + e[k] = v; + } else { + e.setAttribute(k, v); + } + } + return e; } function eleprops(e, props) { - if (!props) return e; - for (let kv of Object.entries(props)) { - eleprop(e, kv[0], kv[1]); - } - return e; + if (!props) return e; + for (let kv of Object.entries(props)) { + eleprop(e, kv[0], kv[1]); + } + return e; } function elechild(e, c) { @@ -35,16 +35,16 @@ function elechild(e, c) { } function elechildren(e, children) { - if (arguments.length > 2) { - elechildren(e, Array.from(arguments).slice(1)); - } else if (typeof children === 'string' || children instanceof Node) { - elechild(e, children); - } else if (children) { - for (let c of children) { - elechild(e, c); - } - } - return e; + if (arguments.length > 2) { + elechildren(e, Array.from(arguments).slice(1)); + } else if (typeof children === 'string' || children instanceof Node) { + elechild(e, children); + } else if (children) { + for (let c of children) { + elechild(e, c); + } + } + return e; } /** @@ -65,31 +65,31 @@ function elechildren(e, children) { * ]) */ function element(tag, props, children) { - if (children === undefined && (typeof props === 'string' || Array.isArray(props) || props instanceof Node)) { - children = props; - props = null; - } - let e = document.createElement(tag); - elechildren(e, children); - eleprops(e, props); - return e; + if (children === undefined && (typeof props === 'string' || Array.isArray(props) || props instanceof Node)) { + children = props; + props = null; + } + let e = document.createElement(tag); + elechildren(e, children); + eleprops(e, props); + return e; } function elecustomprops(e, props, customProps) { - for (let kv of Object.entries(props)) { - let k = kv[0], v = kv[1]; - if (k in customProps) { - customProps[k](e, v) - } else { - eleprop(e, k, v) - } - } + for (let kv of Object.entries(props)) { + let k = kv[0], v = kv[1]; + if (k in customProps) { + customProps[k](e, v) + } else { + eleprop(e, k, v) + } + } } function customElement(tag, baseProps, props, children, customProps) { - let e = element(tag, baseProps, children); - elecustomprops(e, props, customProps); - return e; + let e = element(tag, baseProps, children); + elecustomprops(e, props, customProps); + return e; } /** @@ -97,19 +97,19 @@ function customElement(tag, baseProps, props, children, customProps) { * - set(newValue:(string|number)) - input listener */ function eInput(props) { - return customElement("input", {type: "text"}, props, null, { - set(e, set) { - set = set.bind(e); - e.addEventListener('input', () => { - let value = e.value; - if (e.type === 'number' || e.type === 'range') { - value = parseFloat(value); - if (!isFinite(value)) return; - } - set(value); - }) - } - }) + return customElement("input", {type: "text"}, props, null, { + set(e, set) { + set = set.bind(e); + e.addEventListener('input', () => { + let value = e.value; + if (e.type === 'number' || e.type === 'range') { + value = parseFloat(value); + if (!isFinite(value)) return; + } + set(value); + }) + } + }) } /** @@ -147,412 +147,444 @@ function eCheckbox(props) { * - set(newValue:string) - change listener */ function eSelect(props) { - return customElement("select", null, props, null, - { - items(e, items) { - for (let item of items) { - if (typeof item === 'string') item = {value: item, text: item}; - e.appendChild(element("option", { - value: item.value, - selected: item.value == e.value - }, item.text)); - } - }, - set(e, set) { - set = set.bind(e); - e.addEventListener("change", () => set(e.value)); - } - }) + return customElement("select", null, props, null, + { + items(e, items) { + for (let item of items) { + if (typeof item === 'string') item = {value: item, text: item}; + e.appendChild(element("option", { + value: item.value, + selected: item.value == e.value + }, item.text)); + } + }, + set(e, set) { + set = set.bind(e); + e.addEventListener("change", () => set(e.value)); + } + }) } function copyToClipboard(textarea, data) { textarea.value = data; - textarea.setAttribute("style",""); + textarea.setAttribute("style", ""); textarea.select(); document.execCommand("copy"); alert("Copied to clipboard!"); - textarea.setAttribute("style","display:none"); + textarea.setAttribute("style", "display:none"); } Macro.add('canvasColoursEditor', { - handler: function () { - if (!Renderer.lastCall) return; - function redrawImg() { - if (redrawImg.id) clearTimeout(redrawImg.id); - // throttle a little to avoid immediate redraw - redrawImg.id = setTimeout(()=>{ - Wikifier.wikifyEval(' <<updatesidebarimg>>'); - }, 50); - } - let V = State.variables; - - let groups = [ - { - name: 'Hair', - colours: setup.colours.hair, - default: setup.colours.hair_default, - setVars(variable) { - V.haircolour = variable; - redrawImg(); - }, - exportPrefix: 'setup.colours.hair = ', - exportSuffix: ';' - }, { - name: 'Eyes', - colours: setup.colours.eyes, - default: setup.colours.eyes_default, - setVars(variable) { - V.eyecolour = variable; - redrawImg(); - }, - exportPrefix: 'setup.colours.eyes = ', - exportSuffix: ';' - }, { - name: 'Clothes', - colours: setup.colours.clothes, - default: setup.colours.clothes_default, - setVars(variable) { - for (let item of Object.values(V.worn)) { - if (item.colour !== 0) item.colour = variable; - if (item.accessory_colour !== 0) item.accessory_colour = variable; - } - redrawImg(); - }, - exportPrefix: 'setup.colours.clothes = ', - exportSuffix: ';' - }, { - name: 'Lipstick', - colours: setup.colours.lipstick, - default: setup.colours.lipstick_default, - setVars(variable) { - V.makeup.lipstick = variable; - redrawImg(); - }, - exportPrefix: 'setup.colours.lipstick = ', - exportSuffix: ';' - }, { - name: 'Eyeshadow', - colours: setup.colours.eyeshadow, - default: setup.colours.eyeshadow_default, - setVars(variable) { - V.makeup.eyeshadow = variable; - redrawImg(); - }, - exportPrefix: 'setup.colours.eyeshadow = ', - exportSuffix: ';' - }, { - name: 'Mascara', - colours: setup.colours.mascara, - default: setup.colours.mascara_default, - setVars(variable) { - V.makeup.mascara = variable; - redrawImg(); - }, - exportPrefix: 'setup.colours.mascara = ', - exportSuffix: ';' - } - ] - elechildren(this.output, - "Links will re-colour your character. Won't check for clothes' list of valid colours, use for debugging purposes only! ", - element('div', - {class: 'editorcolours'}, - groups.map(group => - element('div', - [ - element('h4', - [ - group.name, - element('br'), - element('small', 'default brightness = '+(group.default.brightness||0.0)) - ] - ), - element('div', - [ - element('a', { - onclick() { - let textarea = this.parentElement.querySelector('textarea'); - let colours = group.colours.map(c=>{ - // Make deep copy and delete defaults - let c2 = clone(c); - for (let k in group.default) { - if (c2.canvasfilter[k] === group.default[k]) { - delete c2.canvasfilter[k]; - } else if (k === "contrast") { - if ('contrast' in c2.canvasfilter) { - c2.canvasfilter.contrast /= group.default.contrast; - } - } else if (k === 'brightness') { - if ('brightness' in c2.canvasfilter) { - c2.canvasfilter.brightness -= group.default.brightness; - } - } - } - return c2; - }); - copyToClipboard(textarea, group.exportPrefix + - JSON.stringify(colours) + - group.exportSuffix); - } - }, 'Export'), - element('textarea',{style:'display:none'}) - ] - ), - group.colours.map(colour => - element('div', {}, - [ - eInput({ - type: 'color', - value: colour.canvasfilter.blend, - set(value) { - colour.canvasfilter.blend = value; - redrawImg(); - } - }), - eInput({ - type: 'number', - class: 'editlayer-brightness', - value: colour.canvasfilter.brightness, - set(value) { - colour.canvasfilter.brightness = value; - redrawImg(); - }, - min: -1, - max: +1, - step: 0.01 - }), - eInput({ - type: 'number', - class: 'editlayer-contrast', - value: colour.canvasfilter.contrast, - set(value) { - colour.canvasfilter.contrast = value; - redrawImg(); - }, - min: 0, - max: +4, - step: 0.01 - }), - element('a', { - onclick() { - group.setVars(colour.variable); - redrawImg(); - } - }, - ' ' + colour.name_cap) - ] - ) // colour div - ) // colours - ] - ) // group div - ) // groups - ) // div flex - ) // this.output - } + handler: function () { + if (!Renderer.lastCall) return; + let cheat = !!this.args[0]; + + function redrawImg() { + if (redrawImg.id) clearTimeout(redrawImg.id); + // throttle a little to avoid immediate redraw + redrawImg.id = setTimeout(() => { + Wikifier.wikifyEval(' <<updatesidebarimg>>'); + }, 50); + } + + let V = State.variables; + + let groups = [ + { + name: 'Hair', + colours: setup.colours.hair, + default: setup.colours.hair_default, + setVars(variable) { + V.haircolour = variable; + redrawImg(); + }, + exportPrefix: 'setup.colours.hair = ', + exportSuffix: ';' + }, { + name: 'Eyes', + colours: setup.colours.eyes, + default: setup.colours.eyes_default, + setVars(variable) { + V.eyecolour = variable; + redrawImg(); + }, + exportPrefix: 'setup.colours.eyes = ', + exportSuffix: ';' + }, { + name: 'Clothes', + colours: setup.colours.clothes, + default: setup.colours.clothes_default, + setVars(variable) { + for (let item of Object.values(V.worn)) { + if (item.colour !== 0) item.colour = variable; + if (item.accessory_colour !== 0) item.accessory_colour = variable; + } + redrawImg(); + }, + exportPrefix: 'setup.colours.clothes = ', + exportSuffix: ';' + }, { + name: 'Lipstick', + colours: setup.colours.lipstick, + default: setup.colours.lipstick_default, + setVars(variable) { + V.makeup.lipstick = variable; + redrawImg(); + }, + exportPrefix: 'setup.colours.lipstick = ', + exportSuffix: ';' + }, { + name: 'Eyeshadow', + colours: setup.colours.eyeshadow, + default: setup.colours.eyeshadow_default, + setVars(variable) { + V.makeup.eyeshadow = variable; + redrawImg(); + }, + exportPrefix: 'setup.colours.eyeshadow = ', + exportSuffix: ';' + }, { + name: 'Mascara', + colours: setup.colours.mascara, + default: setup.colours.mascara_default, + setVars(variable) { + V.makeup.mascara = variable; + redrawImg(); + }, + exportPrefix: 'setup.colours.mascara = ', + exportSuffix: ';' + } + ] + elechildren(this.output, + cheat ? "Links will re-colour your character. Won't check for clothes' list of valid colours, use for debugging purposes only! " : "", + element('div', + {class: 'editorcolours'}, + groups.map(group => + element('div', + [ + element('div', + { class: 'export-block' }, + [ + element('a', { + onclick() { + let textarea = this.parentElement.querySelector('textarea'); + let colours = group.colours.map(c => { + // Make deep copy and delete defaults + let c2 = clone(c); + for (let k in group.default) { + if (c2.canvasfilter[k] === group.default[k]) { + delete c2.canvasfilter[k]; + } else if (k === "contrast") { + if ('contrast' in c2.canvasfilter) { + c2.canvasfilter.contrast /= group.default.contrast; + } + } else if (k === 'brightness') { + if ('brightness' in c2.canvasfilter) { + c2.canvasfilter.brightness -= group.default.brightness; + } + } + } + return c2; + }); + copyToClipboard(textarea, group.exportPrefix + + JSON.stringify(colours) + + group.exportSuffix); + } + }, 'Export'), + element('textarea', {style: 'display:none'}) + ] + ), // div.export-block + element('h4', + [ + group.name, + element('br') + ] + ), + element('table', + [ + element('thead', [ + element('th',''), + element('th','Brightness'), + element('th','Contrast'), + element('th',''), + ]), + element('tbody', + group.colours.map(colour => + element('tr', {}, + [ + element('td', [ + eInput({ + type: 'color', + value: colour.canvasfilter.blend, + set(value) { + colour.canvasfilter.blend = value; + redrawImg(); + } + }) + ]), + element('td', [ + eInput({ + type: 'number', + class: 'editlayer-brightness', + value: colour.canvasfilter.brightness, + set(value) { + colour.canvasfilter.brightness = value; + redrawImg(); + }, + min: -1, + max: +1, + step: 0.01 + }), + ]), + element('td', [ + eInput({ + type: 'number', + class: 'editlayer-contrast', + value: colour.canvasfilter.contrast, + set(value) { + colour.canvasfilter.contrast = value; + redrawImg(); + }, + min: 0, + max: +4, + step: 0.01 + }), + ]), + element('td', [ + cheat ? element('a', { + onclick() { + group.setVars(colour.variable); + redrawImg(); + } + }, + ' ' + colour.name_cap) + : (' ' + colour.name_cap) + ]) + ] + ) // colour div + ) // colours (tbody children) + ) // tbody + ] // table children + ), // table + ] // group div children + ) // group div + ) // groups + ) // div flex + ) // this.output + } }); Macro.add('canvasLayersEditor', { - handler: function () { - if (!Renderer.lastCall) return; - let layers = Renderer.lastCall[1]; - - function redraw() { - // TODO @aimozg make it work in static render mode too - Renderer.lastAnimation.invalidateCaches(); - Renderer.lastAnimation.redraw(); // it will queue redraw on next animation frame, so shouldn't lag much - } - - function redrawFull() { - // TODO @aimozg make it work in static render mode too - Renderer.lastAnimation.stop(); - Renderer.animateLayersAgain(); - } - - elechild(this.output, element('div', [ - element('button', { - type: 'button', - onclick() { - let layerProps = ["name", "show", "src", "z", "alpha", "desaturate", "brightness", "blendMode", "blend", "animation", "frames", "dx", "dy", "width", "height"]; - copyToClipboard(this.parentElement.querySelector("textarea"), JSON.stringify(layers.map(layer => { - let copy = {}; - for (let key of layerProps) { - if (key in layer && layer.show !== false) copy[key] = layer[key]; - } - return copy - }))) - } - }, 'Export'), - element('textarea', {style: 'display:none'}) - ])); - elechild(this.output, element('table', {class: 'editorlayers'}, [ - element('thead', [ - element('tr', - [ - element('th', 'name'), - element('th', 'show'), - element('th', 'src'), - element('th', 'z'), - element('th', 'alpha'), - element('th', 'desaturate'), - element('th', 'brightness'), - element('th', 'contrast'), - element('th', 'blendMode'), - element('th', 'blend'), - element('th', 'animation') - ])]), - element('tbody', - layers.map(layer => element('tr', [ - element('th', layer.name||''), - element('td', eCheckbox({ - class: 'editlayer-show', - value: !!layer.show, - set(value) { - layer.show = value; - redraw(); - } - })), - element('td', [ - element('a', { - onclick() { - delete Renderer.ImageCaches[layer.src]; - layer.src = layer.src.split('#')[0] + '#' + new Date().getTime() - redraw(); - } - }, '↺'), - eInput({ - class: 'editlayer-src', - value: layer.src.split('#')[0], - set(value) { - layer.src = value; - redraw(); - } - }) - ] - ), - element('td', eInput({ - class: 'editlayer-z', - type: 'number', - value: layer.z, - set(value) { - layer.z = value; - redraw(); - } - })), - element('td', eInput({ - class: 'editlayer-alpha', - type: 'number', - value: layer.alpha, - set(value) { - layer.alpha = value; - redraw(); - }, - min: 0, - max: 1, - step: 0.1 - })), - element('td', eCheckbox({ - value: layer.desaturate, - set(value) { - layer.desaturate = value; - redraw(); - }, - class: 'editlayer-desaturate' - })), - element('td', eInput({ - class: 'editlayer-brightness', - type: 'number', - value: layer.brightness, - set(value) { - layer.brightness = value; - redraw(); - }, - min: -1, - max: +1, - step: 0.01 - })), - element('td', eInput({ - class: 'editlayer-contrast', - type: 'number', - value: layer.contrast, - set(value) { - layer.contrast = value; - redraw(); - }, - min: 0, - max: +4, - step: 0.01 - })), - element('td', eSelect({ - class: 'editlayer-blendmode', - items: [{value: '', text: 'none'}, 'hard-light', 'multiply', 'screen', 'soft-light', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn'], - value: layer.blendMode, - set(value) { - layer.blendMode = value; - redraw(); - } - })), - element('td', eInput({ - type: 'color', - value: layer.blend, - set(value) { - layer.blend = value; - redraw(); - }, - class: 'editlayer-blend' - })), - element('td', eInput({ - class: 'editlayer-animation', - value: layer.animation, - onchange: function () { - layer.animation = this.value; - redrawFull(); - } - })) - ])) - ) - ])); - } + handler: function () { + if (!Renderer.lastCall) return; + let layers = Renderer.lastCall[1]; + + function redraw() { + // TODO @aimozg make it work in static render mode too + Renderer.lastAnimation.invalidateCaches(); + Renderer.lastAnimation.redraw(); // it will queue redraw on next animation frame, so shouldn't lag much + } + + function redrawFull() { + // TODO @aimozg make it work in static render mode too + Renderer.lastAnimation.stop(); + Renderer.animateLayersAgain(); + } + + elechild(this.output, element('div', [ + element('button', { + type: 'button', + onclick() { + let layerProps = ["name", "show", "src", "z", "alpha", "desaturate", "brightness", "blendMode", "blend", "animation", "frames", "dx", "dy", "width", "height"]; + copyToClipboard(this.parentElement.querySelector("textarea"), JSON.stringify(layers.map(layer => { + let copy = {}; + for (let key of layerProps) { + if (key in layer && layer.show !== false) copy[key] = layer[key]; + } + return copy + }))) + } + }, 'Export'), + element('textarea', {style: 'display:none'}) + ])); + elechild(this.output, element('table', {class: 'editorlayers'}, [ + element('thead', [ + element('tr', + [ + element('th', 'name'), + element('th', 'show'), + element('th', 'src'), + element('th', 'z'), + element('th', 'alpha'), + element('th', 'desaturate'), + element('th', 'brightness'), + element('th', 'contrast'), + element('th', 'blendMode'), + element('th', 'blend'), + element('th', 'animation') + ])]), + element('tbody', + layers.map(layer => element('tr', [ + element('th', layer.name || ''), + element('td', eCheckbox({ + class: 'editlayer-show', + value: !!layer.show, + set(value) { + layer.show = value; + redraw(); + } + })), + element('td', [ + element('a', { + onclick() { + delete Renderer.ImageCaches[layer.src]; + layer.src = layer.src.split('#')[0] + '#' + new Date().getTime() + redraw(); + } + }, '↺'), + eInput({ + class: 'editlayer-src', + value: layer.src.split('#')[0], + set(value) { + layer.src = value; + redraw(); + } + }) + ] + ), + element('td', eInput({ + class: 'editlayer-z', + type: 'number', + value: layer.z, + set(value) { + layer.z = value; + redraw(); + } + })), + element('td', eInput({ + class: 'editlayer-alpha', + type: 'number', + value: layer.alpha, + set(value) { + layer.alpha = value; + redraw(); + }, + min: 0, + max: 1, + step: 0.1 + })), + element('td', eCheckbox({ + value: layer.desaturate, + set(value) { + layer.desaturate = value; + redraw(); + }, + class: 'editlayer-desaturate' + })), + element('td', eInput({ + class: 'editlayer-brightness', + type: 'number', + value: layer.brightness, + set(value) { + layer.brightness = value; + redraw(); + }, + min: -1, + max: +1, + step: 0.01 + })), + element('td', eInput({ + class: 'editlayer-contrast', + type: 'number', + value: layer.contrast, + set(value) { + layer.contrast = value; + redraw(); + }, + min: 0, + max: +4, + step: 0.01 + })), + element('td', eSelect({ + class: 'editlayer-blendmode', + items: [{ + value: '', + text: 'none' + }, 'hard-light', 'multiply', 'screen', 'soft-light', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn'], + value: layer.blendMode, + set(value) { + layer.blendMode = value; + redraw(); + } + })), + element('td', eInput({ + type: 'color', + value: layer.blend, + set(value) { + layer.blend = value; + redraw(); + }, + class: 'editlayer-blend' + })), + element('td', eInput({ + class: 'editlayer-animation', + value: layer.animation, + onchange: function () { + layer.animation = this.value; + redrawFull(); + } + })) + ])) + ) + ])); + } }) Macro.add('canvasModelEditor', { handler: function () { let model = Renderer.lastModel; if (!model) return; let options = model.options; + function redraw() { model.redraw(); } + let optionListeners = []; // list of functions to call when model is imported function updateControls() { for (let control of optionListeners) control(); } function optionCategory(name) { - return element('div', {class:'optioncategory'}, name); + return element('div', {class: 'optioncategory'}, name); } + function optionContainer(name, editor) { return [ - element('label', { class:'optionlabel', 'for': 'modeloption-'+name}, name), - element('div', { class:'optioneditor' }, editor) + element('label', {class: 'optionlabel', 'for': 'modeloption-' + name}, name), + element('div', {class: 'optioneditor'}, editor) ] } + function booleanOption(name) { return optionContainer(name, eCheckbox({ - id: 'modeloption-'+name, + id: 'modeloption-' + name, value: options[name], set(value) { options[name] = value; redraw(); }, $oncreate(e) { - optionListeners.push(()=>{ + optionListeners.push(() => { e.value = options[name] }) } }) ); } + function stringOption(name) { return optionContainer(name, eInput({ - id: 'modeloption-'+name, + id: 'modeloption-' + name, value: options[name], type: 'text', set(value) { @@ -560,18 +592,19 @@ Macro.add('canvasModelEditor', { redraw() }, $oncreate(e) { - optionListeners.push(()=>{ + optionListeners.push(() => { e.value = options[name] }) } })) } + function numberOption(name, min, max, step, range) { let rangeLabel; if (range) { rangeLabel = element('label', - {'for':'modeloption-'+name}, - ''+options[name] + {'for': 'modeloption-' + name}, + '' + options[name] ) } else { rangeLabel = ''; @@ -590,7 +623,7 @@ Macro.add('canvasModelEditor', { redraw(); }, $oncreate(e) { - optionListeners.push(()=>{ + optionListeners.push(() => { e.value = options[name] }) } @@ -599,10 +632,11 @@ Macro.add('canvasModelEditor', { ] ); } + function selectOption(name, values, number) { return optionContainer(name, eSelect({ - id: 'modeloption-'+name, + id: 'modeloption-' + name, items: values, value: options[name], set(value) { @@ -611,13 +645,14 @@ Macro.add('canvasModelEditor', { redraw(); }, $oncreate(e) { - optionListeners.push(()=>{ + optionListeners.push(() => { e.value = options[name] }) } }) ); } + let generatedOptions = model.generatedOptions(); if (model.name !== "main") { elechild(this.output, element('div', [ @@ -625,8 +660,8 @@ Macro.add('canvasModelEditor', { element('div', {class: 'editormodelgroups'}, [ element('div', {class: 'editormodelgroup'}, Object.keys(model.options) - .filter(opt=>!generatedOptions.includes(options) && opt !== 'filters') - .map(opt=>{ + .filter(opt => !generatedOptions.includes(options) && opt !== 'filters') + .map(opt => { let value = model.options[opt]; switch (typeof value) { case 'number': @@ -645,7 +680,7 @@ Macro.add('canvasModelEditor', { ) return; } - let bodyWritings = ["",...Object.keys(setup.bodywriting)]; + let bodyWritings = ["", ...Object.keys(setup.bodywriting)]; let hairColourOptions = [...Object.keys(setup.colours.hair_map), "custom"]; let xhairColourOptions = ["", ...Object.keys(setup.colours.hair_map), "custom"]; @@ -658,7 +693,7 @@ Macro.add('canvasModelEditor', { element('button', { type: 'button', onclick() { - let ocopy ={}; + let ocopy = {}; let defaults = model.defaultOptions(); for (let key of Object.keys(options)) { if (generatedOptions.includes(key)) continue; @@ -673,8 +708,8 @@ Macro.add('canvasModelEditor', { type: 'button', onclick() { let textarea = this.parentElement.querySelector("textarea"); - if(textarea.getAttribute('style')) { - textarea.setAttribute('style',''); + if (textarea.getAttribute('style')) { + textarea.setAttribute('style', ''); textarea.value = ''; this.textContent = "Paste and click again to import"; } else { @@ -682,12 +717,12 @@ Macro.add('canvasModelEditor', { Object.assign(options, ioptions); model.redraw(); updateControls(); - textarea.setAttribute('style','display:none'); + textarea.setAttribute('style', 'display:none'); this.textContent = "Import"; } } }, 'Import'), - element('textarea', {rows:1, style: 'display:none'}) + element('textarea', {rows: 1, style: 'display:none'}) ])); elechild(this.output, element('div', [ element('h3', 'Model options'), @@ -808,14 +843,14 @@ Macro.add('canvasModelEditor', { selectOption("writing_right_thigh", bodyWritings), optionCategory("Dripping fluids"), - selectOption("drip_vaginal", ["","Start","VerySlow","Slow","Fast","VeryFast"]), - selectOption("drip_anal", ["","Start","VerySlow","Slow","Fast","VeryFast"]), - selectOption("drip_mouth", ["","Start","VerySlow","Slow","Fast","VeryFast"]), + selectOption("drip_vaginal", ["", "Start", "VerySlow", "Slow", "Fast", "VeryFast"]), + selectOption("drip_anal", ["", "Start", "VerySlow", "Slow", "Fast", "VeryFast"]), + selectOption("drip_mouth", ["", "Start", "VerySlow", "Slow", "Fast", "VeryFast"]), ]), element('div', {class: 'editormodelgroup'}, [ - setup.clothes_all_slots.map(slot=>[ - optionCategory("Clothes: "+slot), + setup.clothes_all_slots.map(slot => [ + optionCategory("Clothes: " + slot), selectOption("worn_" + slot, Object.values(setup.clothes[slot]).map(item => ({ value: item.index, @@ -823,10 +858,10 @@ Macro.add('canvasModelEditor', { })), true ), - numberOption("worn_"+slot+"_alpha", 0, 1, 0.1, true), - selectOption("worn_"+slot+"_integrity", ["tattered", "torn", "frayed", "full"]), - selectOption("worn_"+slot+"_colour", clothesColourOptions), - selectOption("worn_"+slot+"_acc_colour", clothesColourOptions) + numberOption("worn_" + slot + "_alpha", 0, 1, 0.1, true), + selectOption("worn_" + slot + "_integrity", ["tattered", "torn", "frayed", "full"]), + selectOption("worn_" + slot + "_colour", clothesColourOptions), + selectOption("worn_" + slot + "_acc_colour", clothesColourOptions) ]) ] )]) diff --git a/game/04-Variables/colours.js b/game/04-Variables/colours.js index fb1fb40526..29a06ea42f 100644 --- a/game/04-Variables/colours.js +++ b/game/04-Variables/colours.js @@ -114,7 +114,6 @@ setup.colours = { return Renderer.lintRgbStaged(tan, gradient).toHexString(); } }; -// TODO @aimozg Need some mechanism to apply different filters for "red" and "gray" sprites /** * Hair colour record: diff --git a/game/04-Variables/variables-versionUpdate.twee b/game/04-Variables/variables-versionUpdate.twee index f838fa76ef..3a5021d7aa 100644 --- a/game/04-Variables/variables-versionUpdate.twee +++ b/game/04-Variables/variables-versionUpdate.twee @@ -2166,8 +2166,10 @@ <</if>> <<if $sidebarRenderer is undefined>> - <!-- TODO @aimozg - Default both to encourage debugging, make default 'canvas' in production (after sneaky update?) --> - <<set $sidebarRenderer to 'both'>> + <<set $sidebarRenderer to 'canvas'>> +<</if>> +<<if $showDebugRenderer is undefined>> + <<set $showDebugRenderer to !!StartConfig.debug>> <</if>> <<if $makeup.pbcolour isnot 0 and !($makeup.pbcolour in setup.colours.hair_map)>> diff --git a/game/base-clothing/canvasmodel-img.twee b/game/base-clothing/canvasmodel-img.twee index e42d17c559..1304bd3ab6 100644 --- a/game/base-clothing/canvasmodel-img.twee +++ b/game/base-clothing/canvasmodel-img.twee @@ -77,10 +77,7 @@ if ($skinColor.tanningEnabled is true){ ██████ ██ ██ ███████ ███████ --> -<<if $worn.under_upper.type.includes("chest_bind")>> - <<set _modeloptions.breast_size to 1>> -<<else>> - <<switch $player.perceived_breastsize>> +<<switch $player.perceived_breastsize>> <<case 12>> <<set _modeloptions.breast_size to 6>> <<case 8 9 10 11>> @@ -93,8 +90,7 @@ if ($skinColor.tanningEnabled is true){ <<set _modeloptions.breast_size to 2>> <<case 0 1 2>> <<set _modeloptions.breast_size to 1>> - <</switch>> -<</if>> +<</switch>> <<set _modeloptions.breasts to "default">> <!-- @@ -402,6 +398,10 @@ Set model options & filters for player clothes <<set _modeloptions.breasts to "">> <</if>> +<<if $worn.under_upper.type.includes("chest_bind")>> + <<set _modeloptions.breast_size to 1>> +<</if>> + <<if $worn.lower.exposed gte 2 and $worn.under_lower.exposed gte 1>> <<set _modeloptions.crotch_visible to true>> <<set _modeloptions.crotch_exposed to true>> diff --git a/game/base-system/caption.twee b/game/base-system/caption.twee index ed5b69d862..beaedcd8fe 100644 --- a/game/base-system/caption.twee +++ b/game/base-system/caption.twee @@ -227,7 +227,7 @@ <</button>> <</if>> - <<if StartConfig.debug>> + <<if $showDebugRenderer>> <<button "DEBUG RENDERER">><<overlayReplace "canvasModel">><</button>> <</if>> <<if $debug is 1>> @@ -852,8 +852,7 @@ Your $worn.under_upper.name <<underupperhas>> been pulled to your $worn.under_up <<set _disabled to ["disabled","hidden"]>> <<if $sidebarRenderer is undefined>> - <!-- TODO @aimozg - Default both to encourage debugging, make default 'canvas' in production (after sneaky update?) --> - <<set $sidebarRenderer to 'both'>> + <<set $sidebarRenderer to 'canvas'>> <</if>> <<if $sidebarRenderer isnot 'img'>> <<selectmodel "main" "sidebar">> <!-- reuse sidebar cache slot --> diff --git a/game/base-system/options.twee b/game/base-system/options.twee index 2d8ad07687..6ba8dbfdb5 100644 --- a/game/base-system/options.twee +++ b/game/base-system/options.twee @@ -75,10 +75,9 @@ These require a passage change (move location, enter/exit a wardrobe, etc) to ap Sidebar character renderer: <<radiovar "$sidebarRenderer" "img" "Old">><<updatesidebarimg>><</radiovar>> | <<radiovar "$sidebarRenderer" "canvas" "New">><<updatesidebarimg>><</radiovar>> -<!-- TODO @aimozg - 'Both' is unlocked to encourage debugging, in stable release lock under debug mode/build --> -<!--<<if $debug or StartConfig.debug>>--> | <<radiovar "$sidebarRenderer" "both" "Both">><<updatesidebarimg>><</radiovar>> -<!--<</if>>--> +<br> +<label><<checkbox "$showDebugRenderer" false true autocheck>>Enable renderer debugger</label> <br> Lighten the background of the character: <label><<print '<<radiobutton "$imgLighten" "" ' + ($imgLighten is "" ? "checked" : "") + '>>'>>Disabled</label> | diff --git a/game/base-system/overlayReplace.twee b/game/base-system/overlayReplace.twee index a12a8e0589..c2d909d7dd 100644 --- a/game/base-system/overlayReplace.twee +++ b/game/base-system/overlayReplace.twee @@ -50,7 +50,7 @@ <<replace #customOverlayContent>><<canvasLayersEditor>><</replace>> <<case "canvasColours">> <<replace #customOverlayTitle>><<OverlayTitle "canvasColours">><</replace>> - <<replace #customOverlayContent>><<canvasColoursEditor>><</replace>> + <<replace #customOverlayContent>><<canvasColoursEditor `$cheatdisable is "f"`>><</replace>> <<case "canvasModel">> <<replace #customOverlayTitle>><<OverlayTitle "canvasModel">><</replace>> <<replace #customOverlayContent>><<canvasModelEditor>><</replace>> @@ -136,21 +136,27 @@ <<button "Layers">> <<overlayReplace "canvasLayers">> <</button>> + <<else>> + <div class="buttonlike -noborder">Layers</div> <</if>> - <<if $cheatdisable is "f" and $args[0] is not "canvasColours">> + <<if $args[0] is not "canvasColours">> <<button "Colours">> <<overlayReplace "canvasColours">> <</button>> + <<else>> + <div class="buttonlike -noborder">Colours</div> <</if>> <<if $args[0] is not "canvasModel">> <<button "Model">> <<overlayReplace "canvasModel">> <</button>> + <<else>> + <div class="buttonlike -noborder">Model</div> <</if>> <<if Renderer.lastAnimation>> <<button "Start/stop animation">> <<script>> - if (Renderer.lastAnimation.playing) Renderer.lastAnimation.stop(); + if (Renderer.lastAnimation.playing) Renderer.lastAnimation.stop(); else Renderer.lastAnimation.start(); <</script>> <</button>> -- GitLab