diff --git a/.eslintrc.json b/.eslintrc.json index a3c6afbb4..21de30d0c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,247 +1,248 @@ -{ - "root": true, - "env": { - "browser": true, - "commonjs": true, - "es6": true, - "node": true - }, - "plugins": ["pug", "@typescript-eslint"], - "globals": { - "alertify": true, - "dragonBones": true, - "monaco": true, - "nw": true, - "PIXI": true, - "QRCode": true, - "riot": true, - "soundbox": true - }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2018 - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ], - "rules": { - "@typescript-eslint/no-var-requires": "off", - "accessor-pairs": "error", - "array-bracket-spacing": ["error", "never"], - "array-callback-return": "error", - "array-element-newline": ["error", "consistent"], - "arrow-body-style": "error", - "arrow-parens": "off", - "arrow-spacing": ["error", {"after": true, "before": true}], - "block-scoped-var": "error", - "block-spacing": ["error", "never"], - "brace-style": ["error", "1tbs", {"allowSingleLine": false}], - "callback-return": "off", - "camelcase": "error", - "capitalized-comments": ["off", "always"], - "class-methods-use-this": "error", - "comma-dangle": ["warn", "never"], - "comma-spacing": ["error", {"before": false, "after": true}], - "comma-style": ["error", "last"], - "complexity": "error", - "computed-property-spacing": ["error","never"], - "consistent-return": "error", - "consistent-this": "error", - "curly": "error", - "default-case": "error", - "dot-location": "off", - "dot-notation": "error", - "eol-last": ["error", "always"], - "eqeqeq": "error", - "func-call-spacing": ["error", "never"], - "func-name-matching": "error", - "func-names": ["error", "as-needed"], - "func-style": ["error", "expression"], - "function-paren-newline": ["error", "multiline"], - "generator-star-spacing": "error", - "global-require": "off", - "guard-for-in": "off", - "handle-callback-err": "error", - "id-blacklist": ["error", "data", "tag", "element"], - "id-length": ["error", {"min": 1, "max": 24}], - "id-match": "error", - "indent": ["error", 4, {"VariableDeclarator": "first", "MemberExpression": "off", "ignoreComments": true}], - "init-declarations": "off", - "jsx-quotes": "error", - "key-spacing": ["error", {"beforeColon": false, "afterColon": true, "mode": "strict"}], - "keyword-spacing": ["error", {"after": true, "before": true}], - "line-comment-position": "off", - "linebreak-style": ["error", "unix"], - "lines-around-comment": "off", - "max-depth": ["error", 4], - "max-len": ["error", {"code": 100, "ignoreComments": false, "ignoreUrls": true, "ignoreStrings": true, "ignoreTemplateLiterals": true, "ignoreRegExpLiterals": true}], - "max-lines": "off", - "max-lines-per-function": ["error", {"max": 80, "skipBlankLines": true, "skipComments": true}], - "max-nested-callbacks": ["error", 10], - "max-params": ["error", 5], - "max-statements": "off", - "max-statements-per-line": ["error", {"max": 1}], - "multiline-ternary": ["error", "always-multiline"], - "new-cap": "warn", - "new-parens": "error", - "newline-per-chained-call": ["error", {"ignoreChainWithDepth": 2}], - "no-alert": "error", - "no-array-constructor": "error", - "no-await-in-loop": "error", - "no-bitwise": "warn", - "no-caller": "error", - "no-confusing-arrow": "error", - "no-console": ["warn", { - "allow": ["error"] - }], - "no-continue": "off", - "no-div-regex": "error", - "no-duplicate-imports": "error", - "no-else-return": "error", - "no-empty-function": "error", - "no-eq-null": "error", - "no-eval": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-label": "error", - "no-extra-parens": "off", - "no-floating-decimal": "error", - "no-implicit-coercion": "error", - "no-implicit-globals": "error", - "no-implied-eval": "error", - "no-inline-comments": "off", - "no-inner-declarations": [ - "error", - "functions" - ], - "no-invalid-this": "off", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": "error", - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-loop-func": "error", - "no-magic-numbers": "off", - "no-mixed-operators": [ - "error", - { - "groups": [ - ["&", "|", "^", "~", "<<", ">>", ">>>"], - ["==", "!=", "===", "!==", ">", ">=", "<", "<="], - ["&&", "||"], - ["in", "instanceof"] - ], - "allowSamePrecedence": true - } - ], - "no-mixed-requires": "error", - "no-mixed-spaces-and-tabs": "error", - "no-multi-spaces": "error", - "no-multi-str": "error", - "no-multiple-empty-lines": ["error", {"max": 2, "maxEOF": 1}], - "no-negated-condition": "off", - "no-nested-ternary": "error", - "no-new": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-require": "error", - "no-new-wrappers": "error", - "no-octal-escape": "error", - "no-param-reassign": "off", - "no-path-concat": "error", - "no-plusplus": "off", - "no-process-env": "warn", - "no-process-exit": "error", - "no-proto": "error", - "no-prototype-builtins": "error", - "no-restricted-globals": "error", - "no-restricted-imports": "error", - "no-restricted-modules": "error", - "no-restricted-properties": "error", - "no-restricted-syntax": ["error", "WithStatement"], - "no-return-assign": "error", - "no-return-await": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-shadow": "off", - "no-shadow-restricted-names": "error", - "no-sync": "off", - "no-tabs": "error", - "no-template-curly-in-string": "error", - "no-ternary": "off", - "no-throw-literal": "error", - "no-trailing-spaces": "error", - "no-undef-init": "error", - "no-undefined": "error", - "no-underscore-dangle": ["error", {"allow": ["__dirname"]}], - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": "error", - "no-unsafe-negation": "error", - "no-unused-expressions": "error", - "no-use-before-define": "error", - "no-useless-call": "error", - "no-useless-computed-key": "error", - "no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-escape": "error", - "no-useless-rename": "error", - "no-useless-return": "error", - "no-var": "off", - "no-void": "off", - "no-warning-comments": "warn", - "no-whitespace-before-property": "error", - "no-with": "error", - "object-curly-newline": ["error", { - "ObjectExpression": {"minProperties": 1}, - "ObjectPattern": "never", - "ImportDeclaration": "never", - "ExportDeclaration": {"multiline": true, "minProperties": 2} - }], - "object-curly-spacing": ["error", "never"], - "object-property-newline": ["error", {"allowAllPropertiesOnSameLine": true}], - "object-shorthand": "error", - "one-var": "off", - "one-var-declaration-per-line": ["error", "initializations"], - "operator-assignment": "error", - "operator-linebreak": ["error", "after"], - "padded-blocks": ["error", "never"], - "prefer-arrow-callback": "off", - "prefer-const": "error", - "prefer-destructuring": "error", - "prefer-exponentiation-operator": "warn", - "prefer-numeric-literals": "error", - "prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "off", - "quote-props": ["error", "as-needed"], - "quotes": ["error", "single"], - "radix": "error", - "require-atomic-updates": "error", - "require-await": "error", - "rest-spread-spacing": "error", - "semi": ["error", "always"], - "semi-spacing": "error", - "semi-style": ["error", "last"], - "sort-imports": "off", - "sort-keys": "off", - "sort-vars": "off", - "space-before-blocks": ["error", "always"], - "space-before-function-paren": ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}], - "space-in-parens": ["error", "never"], - "space-infix-ops": "error", - "space-unary-ops": "error", - "spaced-comment": "error", - "strict": "off", - "symbol-description": "error", - "template-curly-spacing": ["error", "never"], - "template-tag-spacing": ["error", "never"], - "unicode-bom": ["error", "never"], - "vars-on-top": "off", - "wrap-regex": "off", - "yield-star-spacing": "error", - "yoda": ["error", "never"] - } -} +{ + "root": true, + "env": { + "browser": true, + "commonjs": true, + "es6": true, + "node": true + }, + "plugins": ["pug", "@typescript-eslint"], + "globals": { + "alertify": true, + "dragonBones": true, + "monaco": true, + "nw": true, + "PIXI": true, + "QRCode": true, + "riot": true, + "soundbox": true + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2018 + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@typescript-eslint/no-var-requires": "off", + "accessor-pairs": "error", + "array-bracket-spacing": ["error", "never"], + "array-callback-return": "error", + "array-element-newline": ["error", "consistent"], + "arrow-body-style": "error", + "arrow-parens": "off", + "arrow-spacing": ["error", {"after": true, "before": true}], + "block-scoped-var": "error", + "block-spacing": ["error", "never"], + "brace-style": ["error", "1tbs", {"allowSingleLine": false}], + "callback-return": "off", + "camelcase": "error", + "capitalized-comments": ["off", "always"], + "class-methods-use-this": "error", + "comma-dangle": ["warn", "never"], + "comma-spacing": ["error", {"before": false, "after": true}], + "comma-style": ["error", "last"], + "complexity": "error", + "computed-property-spacing": ["error","never"], + "consistent-return": "error", + "consistent-this": "error", + "curly": "error", + "default-case": "error", + "dot-location": "off", + "dot-notation": "error", + "eol-last": ["error", "always"], + "eqeqeq": "error", + "func-call-spacing": ["error", "never"], + "func-name-matching": "error", + "func-names": ["error", "as-needed"], + "func-style": ["error", "expression"], + "function-paren-newline": ["error", "multiline"], + "generator-star-spacing": "error", + "global-require": "off", + "guard-for-in": "off", + "handle-callback-err": "error", + "id-blacklist": ["error", "data", "tag", "element"], + "id-length": ["error", {"min": 1, "max": 24}], + "id-match": "error", + "indent": ["error", 4, {"VariableDeclarator": "first", "MemberExpression": "off", "ignoreComments": true}], + "init-declarations": "off", + "jsx-quotes": "error", + "key-spacing": ["error", {"beforeColon": false, "afterColon": true, "mode": "strict"}], + "keyword-spacing": ["error", {"after": true, "before": true}], + "line-comment-position": "off", + "linebreak-style": ["error", "unix"], + "lines-around-comment": "off", + "max-depth": ["error", 4], + "max-len": ["error", {"code": 100, "ignoreComments": false, "ignoreUrls": true, "ignoreStrings": true, "ignoreTemplateLiterals": true, "ignoreRegExpLiterals": true}], + "max-lines": "off", + "max-lines-per-function": ["error", {"max": 80, "skipBlankLines": true, "skipComments": true}], + "max-nested-callbacks": ["error", 10], + "max-params": ["error", 5], + "max-statements": "off", + "max-statements-per-line": ["error", {"max": 1}], + "multiline-ternary": ["error", "always-multiline"], + "new-cap": "warn", + "new-parens": "error", + "newline-per-chained-call": ["error", {"ignoreChainWithDepth": 2}], + "no-alert": "error", + "no-array-constructor": "error", + "no-await-in-loop": "error", + "no-bitwise": "warn", + "no-caller": "error", + "no-confusing-arrow": "error", + "no-console": ["warn", { + "allow": ["error"] + }], + "no-continue": "off", + "no-div-regex": "error", + "no-duplicate-imports": "error", + "no-else-return": "error", + "no-empty-function": "error", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "off", + "no-floating-decimal": "error", + "no-implicit-coercion": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-inner-declarations": [ + "error", + "functions" + ], + "no-invalid-this": "off", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-operators": [ + "error", + { + "groups": [ + ["&", "|", "^", "~", "<<", ">>", ">>>"], + ["==", "!=", "===", "!==", ">", ">=", "<", "<="], + ["&&", "||"], + ["in", "instanceof"] + ], + "allowSamePrecedence": true + } + ], + "no-mixed-requires": "error", + "no-mixed-spaces-and-tabs": "error", + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": ["error", {"max": 2, "maxEOF": 1}], + "no-negated-condition": "off", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-path-concat": "error", + "no-plusplus": "off", + "no-process-env": "warn", + "no-process-exit": "error", + "no-proto": "error", + "no-prototype-builtins": "error", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-modules": "error", + "no-restricted-properties": "error", + "no-restricted-syntax": ["error", "WithStatement"], + "no-return-assign": "error", + "no-return-await": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "off", + "no-shadow-restricted-names": "error", + "no-sync": "off", + "no-tabs": "error", + "no-template-curly-in-string": "error", + "no-ternary": "off", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef-init": "error", + "no-undefined": "error", + "no-underscore-dangle": ["error", {"allow": ["__dirname"]}], + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unsafe-negation": "error", + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": "error", + "no-use-before-define": "error", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-escape": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "off", + "no-void": "off", + "no-warning-comments": "warn", + "no-whitespace-before-property": "error", + "no-with": "error", + "object-curly-newline": ["error", { + "ObjectExpression": {"minProperties": 1}, + "ObjectPattern": "never", + "ImportDeclaration": "never", + "ExportDeclaration": {"multiline": true, "minProperties": 2} + }], + "object-curly-spacing": ["error", "never"], + "object-property-newline": ["error", {"allowAllPropertiesOnSameLine": true}], + "object-shorthand": "error", + "one-var": "off", + "one-var-declaration-per-line": ["error", "initializations"], + "operator-assignment": "error", + "operator-linebreak": ["error", "after"], + "padded-blocks": ["error", "never"], + "prefer-arrow-callback": "off", + "prefer-const": "error", + "prefer-destructuring": "error", + "prefer-exponentiation-operator": "warn", + "prefer-numeric-literals": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "off", + "quote-props": ["error", "as-needed"], + "quotes": ["error", "single"], + "radix": "error", + "require-atomic-updates": "error", + "require-await": "error", + "rest-spread-spacing": "error", + "semi": ["error", "always"], + "semi-spacing": "error", + "semi-style": ["error", "last"], + "sort-imports": "off", + "sort-keys": "off", + "sort-vars": "off", + "space-before-blocks": ["error", "always"], + "space-before-function-paren": ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}], + "space-in-parens": ["error", "never"], + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": "error", + "strict": "off", + "symbol-description": "error", + "template-curly-spacing": ["error", "never"], + "template-tag-spacing": ["error", "never"], + "unicode-bom": ["error", "never"], + "vars-on-top": "off", + "wrap-regex": "off", + "yield-star-spacing": "error", + "yoda": ["error", "never"] + } +} diff --git a/app/data/Wood_End.wav b/app/data/Wood_End.wav new file mode 100644 index 000000000..2e0b68c85 Binary files /dev/null and b/app/data/Wood_End.wav differ diff --git a/app/data/Wood_Start.wav b/app/data/Wood_Start.wav new file mode 100644 index 000000000..522753469 Binary files /dev/null and b/app/data/Wood_Start.wav differ diff --git a/app/data/ct.libs/place/index.js b/app/data/ct.libs/place/index.js index 0c8830fed..d627888cf 100644 --- a/app/data/ct.libs/place/index.js +++ b/app/data/ct.libs/place/index.js @@ -286,6 +286,9 @@ drawDebugGraphic(absolute) { const shape = this._shape || getSSCDShape(this); const g = this.$cDebugCollision; + const inverse = this.transform.localTransform.clone().invert(); + this.$cDebugCollision.transform.setFromMatrix(inverse); + this.$cDebugCollision.position.set(0, 0); let color = 0x00ffff; if (this instanceof Copy) { color = 0x0066ff; diff --git a/app/data/ct.libs/place/injections/beforedraw.js b/app/data/ct.libs/place/injections/beforedraw.js index 4a73bb439..b50d023df 100644 --- a/app/data/ct.libs/place/injections/beforedraw.js +++ b/app/data/ct.libs/place/injections/beforedraw.js @@ -1,7 +1,9 @@ if ([/*%debugMode%*/][0] && this instanceof ct.templates.Copy) { - this.$cDebugText.scale.x = this.$cDebugCollision.scale.x = 1 / this.scale.x; - this.$cDebugText.scale.y = this.$cDebugCollision.scale.y = 1 / this.scale.y; - this.$cDebugText.angle = this.$cDebugCollision.angle = -this.angle; + const inverse = this.transform.localTransform.clone().invert(); + this.$cDebugCollision.transform.setFromMatrix(inverse); + this.$cDebugCollision.position.set(0, 0); + this.$cDebugText.transform.setFromMatrix(inverse); + this.$cDebugText.position.set(0, 0); const newtext = `Partitions: ${this.$chashes.join(', ')} CGroup: ${this.cgroup || 'unset'} diff --git a/app/data/ct.release/rooms.js b/app/data/ct.release/rooms.js index 4190dec59..ae8ccbb98 100644 --- a/app/data/ct.release/rooms.js +++ b/app/data/ct.release/rooms.js @@ -22,26 +22,28 @@ class Room extends PIXI.Container { ct.room = ct.rooms.current = this; } if (template) { - if (template.extends) { - ct.u.ext(this, template.extends); - } this.onCreate = template.onCreate; this.onStep = template.onStep; this.onDraw = template.onDraw; this.onLeave = template.onLeave; this.template = template; this.name = template.name; + this.isUi = template.isUi; + if (template.extends) { + ct.u.ext(this, template.extends); + } if (this === ct.room) { ct.pixiApp.renderer.backgroundColor = ct.u.hexToPixi(this.template.backgroundColor); } /*%beforeroomoncreate%*/ for (let i = 0, li = template.bgs.length; i < li; i++) { - // Need to put extensions here, so we don't use ct.backgrounds.add + // Need to put additional properties like parallax here, + // so we don't use ct.backgrounds.add const bg = new ct.templates.Background( template.bgs[i].texture, null, template.bgs[i].depth, - template.bgs[i].extends + template.bgs[i] ); this.addChild(bg); } @@ -52,17 +54,22 @@ class Room extends PIXI.Container { this.addChild(tl); } for (let i = 0, li = template.objects.length; i < li; i++) { - const exts = template.objects[i].exts || {}; + const copy = template.objects[i]; + const exts = copy.exts || {}; + const customProperties = copy.customProperties || {}; ct.templates.copyIntoRoom( - template.objects[i].template, - template.objects[i].x, - template.objects[i].y, + copy.template, + copy.x, + copy.y, this, { - tx: template.objects[i].tx, - ty: template.objects[i].ty, - tr: template.objects[i].tr, - ...exts + ...exts, + ...customProperties, + scaleX: copy.scale.x, + scaleY: copy.scale.y, + rotation: copy.rotation, + alpha: copy.opacity, + tint: copy.tint } ); } diff --git a/app/data/ct.release/templates.js b/app/data/ct.release/templates.js index 6961e1c8d..24019b8c4 100644 --- a/app/data/ct.release/templates.js +++ b/app/data/ct.release/templates.js @@ -65,6 +65,7 @@ const Copy = (function Copy() { this.parent = container; this.blendMode = t.blendMode || PIXI.BLEND_MODES.NORMAL; this.loop = t.loopAnimation; + this.animationSpeed = t.animationFPS / 60; if (t.playAnimationOnStart) { this.play(); } @@ -89,14 +90,11 @@ const Copy = (function Copy() { this.timer1 = this.timer2 = this.timer3 = this.timer4 = this.timer5 = this.timer6 = 0; if (exts) { ct.u.ext(this, exts); - if (exts.tx) { - this.scale.x = exts.tx; + if (exts.scaleX) { + this.scale.x = exts.scaleX; } - if (exts.ty) { - this.scale.y = exts.ty; - } - if (exts.tr) { - this.angle = exts.tr; + if (exts.scaleY) { + this.scale.y = exts.scaleY; } } this.uid = ++uid; diff --git a/app/data/ct.release/tilemaps.js b/app/data/ct.release/tilemaps.js index 9928663e0..9ffb427c7 100644 --- a/app/data/ct.release/tilemaps.js +++ b/app/data/ct.release/tilemaps.js @@ -19,15 +19,21 @@ class Tilemap extends PIXI.Container { Object.assign(this, template.extends); } for (let i = 0, l = template.tiles.length; i < l; i++) { - const textures = ct.res.getTexture(template.tiles[i].texture); - const sprite = new PIXI.Sprite(textures[template.tiles[i].frame]); - sprite.anchor.x = sprite.anchor.y = 0; + const tile = template.tiles[i]; + const textures = ct.res.getTexture(tile.texture); + const sprite = new PIXI.Sprite(textures[tile.frame]); + sprite.anchor.x = textures[0].defaultAnchor.x; + sprite.anchor.y = textures[0].defaultAnchor.y; sprite.shape = textures.shape; + sprite.scale.set(tile.scale.x, tile.scale.y); + sprite.rotation = tile.rotation; + sprite.alpha = tile.opacity; + sprite.tint = tile.tint; + sprite.x = tile.x; + sprite.y = tile.y; this.addChild(sprite); this.pixiTiles.push(sprite); this.tiles[i].sprite = sprite; - sprite.x = template.tiles[i].x; - sprite.y = template.tiles[i].y; } } else { this.tiles = []; diff --git a/app/data/cursorErase.svg b/app/data/cursorErase.svg new file mode 100644 index 000000000..c96e113da --- /dev/null +++ b/app/data/cursorErase.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/app/data/cursorRotate.svg b/app/data/cursorRotate.svg new file mode 100644 index 000000000..d2f8adb9d --- /dev/null +++ b/app/data/cursorRotate.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/data/i18n/Brazilian Portuguese.json b/app/data/i18n/Brazilian Portuguese.json index b22be4a88..e56fc4e58 100644 --- a/app/data/i18n/Brazilian Portuguese.json +++ b/app/data/i18n/Brazilian Portuguese.json @@ -67,7 +67,8 @@ "template": "", "room": "", "sound": "", - "tandem": "" + "tandem": "", + "type": "" }, "newName": "Novo nome:", "saveProject": "Salvar projeto", @@ -125,7 +126,9 @@ "", "" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "current": "Novo", @@ -587,7 +590,10 @@ "SpringStream": "Spring Stream", "LucasDracula": "Lucas Dracula", "Horizon": "", - "HCBlack": "" + "HCBlack": "", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "codeFontDefault": "Padrão (Iosevka Light)", "codeFontOldSchool": "Old school", @@ -602,7 +608,10 @@ "translateToYourLanguage": "Traduza o ct.js!", "disableSounds": "Desabilitar sons de UI", "changeDataFolder": "", - "forceProductionForDebug": "" + "forceProductionForDebug": "", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "save": "Salvar projeto", @@ -638,14 +647,36 @@ "sounds": "Sons", "ui": "UI", "fx": "FX", - "templates": "Tipos" - }, - "copyCustomProperties": { - "customProperties": "", - "addProperty": "", - "property": "", - "value": "", - "delete": "" + "templates": "Tipos", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "", @@ -716,7 +747,13 @@ "copyProperties": { "position": "", "rotation": "", - "scale": "" + "scale": "", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "", + "property": "", + "value": "" }, "customProperties": "", "restrictCamera": "", @@ -736,7 +773,19 @@ "deleteTiles": "Remover tiles", "moveTilesToLayer": "Mover para camada", "shiftTiles": "Trocar tiles", - "changeCopyRotation": "Rotacionar" + "changeCopyRotation": "Rotacionar", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Ativo", @@ -869,7 +918,9 @@ "add": "", "multiply": "", "screen": "" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "assetInput": { "changeAsset": "", @@ -889,5 +940,71 @@ "visitAuthorsItch": "", "visitAuthorsTwitter": "", "tipAuthor": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } } \ No newline at end of file diff --git a/app/data/i18n/Chinese Simplified.json b/app/data/i18n/Chinese Simplified.json index e9f0b1729..f501867a3 100644 --- a/app/data/i18n/Chinese Simplified.json +++ b/app/data/i18n/Chinese Simplified.json @@ -67,7 +67,8 @@ "template": "模板", "room": "房间", "sound": "声音", - "tandem": "粒子发射器" + "tandem": "粒子发射器", + "type": "" }, "newName": "新名称", "saveProject": "保存项目", @@ -125,7 +126,9 @@ "骨骼精灵图形", "骨骼精灵图形" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "current": "新建", @@ -542,7 +545,10 @@ "SpringStream": "Spring Stream", "LucasDracula": "Lucas Dracula", "Horizon": "Horizon", - "HCBlack": "High-contrast Black" + "HCBlack": "High-contrast Black", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "newFont": "新字体:", "language": "语言", @@ -552,7 +558,10 @@ "codeDense": "密集布局", "disableSounds": "关闭 UI 音效", "changeDataFolder": "设置数据文件夹位置", - "forceProductionForDebug": "强制生产任务用于调试导出" + "forceProductionForDebug": "强制生产任务用于调试导出", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "heading": "项目", @@ -588,7 +597,36 @@ "restart": "重新启动", "launch": "编译并运行", "launchHotkeys": "(F5; Alt+F5 在默认浏览器中运行)", - "templates": "类型" + "templates": "类型", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "docsPanel": { "documentation": "文档", @@ -640,13 +678,6 @@ "folderNotWritable": "您没有权限写入这个文件夹. 选择其他试试", "complete": "文件夹已设置, 一切正常✅" }, - "copyCustomProperties": { - "customProperties": "自定义属性", - "addProperty": "添加属性", - "property": "属性", - "value": "值", - "delete": "删除" - }, "assetViewer": { "addNewGroup": "新分组", "ungrouped": "显示未分组", @@ -716,7 +747,13 @@ "copyProperties": { "position": "位置", "rotation": "旋转", - "scale": "缩放" + "scale": "缩放", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "添加属性", + "property": "属性", + "value": "值" }, "customProperties": "自定义属性", "restrictCamera": "保持相机在一个矩形中", @@ -736,7 +773,19 @@ "deleteTiles": "删除图块", "moveTilesToLayer": "移至图层", "shiftTiles": "移动瓷砖", - "changeCopyRotation": "旋转" + "changeCopyRotation": "旋转", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "激活", @@ -869,7 +918,9 @@ "add": "掺入 (溶解)", "multiply": "倍增 (变暗)", "screen": "遮蔽 (变亮)" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "assetInput": { "changeAsset": "切换资产", @@ -889,5 +940,71 @@ "visitAuthorsItch": "访问作者的 itch.io 页面", "visitAuthorsTwitter": "访问作者的 Twitter 页面", "tipAuthor": "奖励作者的辛勤工作 :D" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } -} +} \ No newline at end of file diff --git a/app/data/i18n/Comments.json b/app/data/i18n/Comments.json index b3308135e..a610995ab 100644 --- a/app/data/i18n/Comments.json +++ b/app/data/i18n/Comments.json @@ -67,7 +67,8 @@ "template": "", "room": "", "sound": "", - "tandem": "" + "tandem": "", + "type": "" }, "newName": "", "saveProject": "", @@ -125,7 +126,9 @@ "", "" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "current": "", @@ -587,7 +590,10 @@ "SpringStream": "", "LucasDracula": "", "Horizon": "", - "HCBlack": "" + "HCBlack": "", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "codeFontDefault": "", "codeFontOldSchool": "", @@ -602,7 +608,10 @@ "forceProductionForDebug": "", "heading": "", "language": "", - "translateToYourLanguage": "" + "translateToYourLanguage": "", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "save": "", @@ -638,14 +647,36 @@ "fx": "", "restart": "", "project": "", - "templates": "" - }, - "copyCustomProperties": { - "customProperties": "", - "addProperty": "", - "property": "", - "value": "", - "delete": "" + "templates": "", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "", @@ -716,7 +747,13 @@ "copyProperties": { "position": "", "rotation": "", - "scale": "" + "scale": "", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "", + "property": "", + "value": "" }, "customProperties": "", "restrictCamera": "", @@ -736,7 +773,19 @@ "deleteTiles": "", "moveTilesToLayer": "", "shiftTiles": "", - "changeCopyRotation": "" + "changeCopyRotation": "", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "", @@ -869,7 +918,9 @@ "add": "", "multiply": "", "screen": "" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "assetInput": { "changeAsset": "", @@ -889,5 +940,71 @@ "visitAuthorsItch": "", "visitAuthorsTwitter": "", "tipAuthor": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } } \ No newline at end of file diff --git a/app/data/i18n/Debug.json b/app/data/i18n/Debug.json index 1ad1f5a2f..5f0ea7c12 100644 --- a/app/data/i18n/Debug.json +++ b/app/data/i18n/Debug.json @@ -64,7 +64,11 @@ "text": "common.fieldTypes.text", "textfield": "common.fieldTypes.textfield", "texture": "common.fieldTypes.texture", - "type": "common.fieldTypes.type" + "type": "common.fieldTypes.type", + "template": "", + "room": "", + "sound": "", + "tandem": "" }, "newName": "common.newName", "saveProject": "common.saveProject", @@ -77,7 +81,54 @@ "addToNotes": "common.addToNotes", "noRooms": "common.noRooms", "tileLayer": "common.tileLayer", - "ctSite": "common.ctSite" + "ctSite": "common.ctSite", + "docsShort": "", + "docsLong": "", + "openAssetGallery": "", + "assetTypes": { + "textures": [ + "", + "", + "" + ], + "templates": [ + "", + "", + "" + ], + "emitterTandems": [ + "", + "", + "" + ], + "rooms": [ + "", + "", + "" + ], + "fonts": [ + "", + "", + "" + ], + "styles": [ + "", + "", + "" + ], + "sounds": [ + "", + "", + "" + ], + "skeletons": [ + "", + "", + "" + ] + }, + "next": "", + "previous": "" }, "colorPicker": { "current": "colorPicker.current", @@ -97,7 +148,15 @@ "log": "exportPanel.log", "windowsCrossBuildWarning": "exportPanel.windowsCrossBuildWarning", "cannotBuildForMacOnWin": "exportPanel.cannotBuildForMacOnWin", - "firstRunNotice": "exportPanel.firstRunNotice" + "firstRunNotice": "exportPanel.firstRunNotice", + "projectTitleRequired": "", + "appIdRequired": "", + "noAndroidSdkFound": "", + "envVarNotice": "", + "downloadAndroidStudio": "", + "requiresInternetNotice": "", + "noJdkFound": "", + "downloadJDK": "" }, "intro": { "loading": "intro.loading", @@ -132,7 +191,9 @@ "github": "intro.github", "itch": "intro.itch", "vkontakte": "intro.vkontakte", - "patreon": "intro.patreon" + "patreon": "intro.patreon", + "templates": "", + "templatesInfo": "" }, "modules": { "author": "modules.author", @@ -174,7 +235,8 @@ "skeletons": "texture.skeletons", "createTemplate": "texture.createTemplate", "importFromClipboard": "texture.importFromClipboard", - "generatePlaceholder": "texture.generatePlaceholder" + "generatePlaceholder": "texture.generatePlaceholder", + "textures": "" }, "sounds": { "create": "sounds.create", @@ -365,7 +427,9 @@ "site": "settings.authoring.site", "title": "settings.authoring.title", "version": "settings.authoring.version", - "versionPostfix": "settings.authoring.versionPostfix" + "versionPostfix": "settings.authoring.versionPostfix", + "appId": "", + "appIdExplanation": "" }, "branding": { "heading": "settings.branding.heading", @@ -374,7 +438,11 @@ "icon": "settings.branding.icon", "iconNotice": "settings.branding.iconNotice", "invertPreloaderScheme": "settings.branding.invertPreloaderScheme", - "hideLoadingLogo": "settings.branding.hideLoadingLogo" + "hideLoadingLogo": "settings.branding.hideLoadingLogo", + "splashScreen": "", + "splashScreenNotice": "", + "forceSmoothIcons": "", + "forceSmoothSplashScreen": "" }, "modules": { "heading": "settings.modules.heading" @@ -392,7 +460,15 @@ "windowed": "settings.rendering.launchModes.windowed" }, "hideCursor": "settings.rendering.hideCursor", - "pixelatedRender": "settings.rendering.pixelatedRender" + "pixelatedRender": "settings.rendering.pixelatedRender", + "usePixiLegacy": "", + "mobileBuilds": "", + "screenOrientation": "", + "screenOrientations": { + "unspecified": "", + "landscape": "", + "portrait": "" + } }, "scripts": { "heading": "settings.scripts.heading", @@ -434,13 +510,19 @@ "deleteContentType": "settings.content.deleteContentType", "confirmDeletionMessage": "settings.content.confirmDeletionMessage", "gotoEntries": "settings.content.gotoEntries", - "entries": "settings.content.entries" + "entries": "settings.content.entries", + "array": "" }, "contentTypes": "settings.contentTypes" }, "extensionsEditor": { "noEntries": "extensionsEditor.noEntries", - "addRow": "extensionsEditor.addRow" + "addRow": "extensionsEditor.addRow", + "actions": "", + "values": "", + "moveDown": "", + "moveUp": "", + "deleteRow": "" }, "textureGenerator": { "name": "textureGenerator.name", @@ -494,7 +576,8 @@ "exportDesktop": "mainMenu.deploy.exportDesktop", "successZipExport": "mainMenu.deploy.successZipExport", "zipExport": "mainMenu.deploy.zipExport", - "heading": "mainMenu.deploy.heading" + "heading": "mainMenu.deploy.heading", + "exportAndroid": "" }, "latestProjects": { "recentProjects": "mainMenu.latestProjects.recentProjects" @@ -507,7 +590,10 @@ "SpringStream": "mainMenu.settings.themes.SpringStream", "LucasDracula": "mainMenu.settings.themes.LucasDracula", "Horizon": "mainMenu.settings.themes.Horizon", - "HCBlack": "mainMenu.settings.themes.HCBlack" + "HCBlack": "mainMenu.settings.themes.HCBlack", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "codeFontDefault": "mainMenu.settings.codeFontDefault", "codeFontOldSchool": "mainMenu.settings.codeFontOldSchool", @@ -523,7 +609,9 @@ "forceProductionForDebug": "mainMenu.settings.forceProductionForDebug", "heading": "mainMenu.settings.heading", "language": "mainMenu.settings.language", - "translateToYourLanguage": "mainMenu.settings.translateToYourLanguage" + "translateToYourLanguage": "mainMenu.settings.translateToYourLanguage", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "save": "mainMenu.project.save", @@ -542,7 +630,8 @@ "heading": "mainMenu.meta.heading", "twitter": "mainMenu.meta.twitter", "vkontakte": "mainMenu.meta.vkontakte", - "ctjsForum": "mainMenu.meta.ctjsForum" + "ctjsForum": "mainMenu.meta.ctjsForum", + "openStylebook": "" } }, "appView": { @@ -558,14 +647,36 @@ "fx": "appView.fx", "restart": "appView.restart", "project": "appView.project", - "templates": "appView.templates" - }, - "copyCustomProperties": { - "customProperties": "copyCustomProperties.customProperties", - "addProperty": "copyCustomProperties.addProperty", - "property": "copyCustomProperties.property", - "value": "copyCustomProperties.value", - "delete": "copyCustomProperties.delete" + "templates": "appView.templates", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "assetViewer.addNewGroup", @@ -636,7 +747,13 @@ "copyProperties": { "position": "roomView.copyProperties.position", "rotation": "roomView.copyProperties.rotation", - "scale": "roomView.copyProperties.scale" + "scale": "roomView.copyProperties.scale", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "copyCustomProperties.addProperty", + "property": "copyCustomProperties.property", + "value": "copyCustomProperties.value" }, "customProperties": "roomView.customProperties", "restrictCamera": "roomView.restrictCamera", @@ -656,7 +773,19 @@ "deleteTiles": "roomView.deleteTiles", "moveTilesToLayer": "roomView.moveTilesToLayer", "shiftTiles": "roomView.shiftTiles", - "changeCopyRotation": "roomView.changeCopyRotation" + "changeCopyRotation": "roomView.changeCopyRotation", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "styleView.active", @@ -779,6 +908,103 @@ "learnAboutTypes": "templateView.learnAboutTypes", "name": "templateView.name", "step": "templateView.step", - "visible": "templateView.visible" + "visible": "templateView.visible", + "appearance": "", + "opacity": "", + "blendMode": "", + "playAnimationOnStart": "", + "blendModes": { + "normal": "", + "add": "", + "multiply": "", + "screen": "" + }, + "animationFPS": "", + "loopAnimation": "" + }, + "assetInput": { + "changeAsset": "", + "jumpToAsset": "", + "selectAssetHeader": "" + }, + "builtinAssetGallery": { + "galleryTip": "", + "assetGalleryHeader": "", + "importIntoProject": "", + "importAll": "", + "byAuthorPrefix": "", + "cannotImportExplanation": "", + "nameOccupied": "", + "cannotImportNameOccupied": "", + "visitSource": "", + "visitAuthorsItch": "", + "visitAuthorsTwitter": "", + "tipAuthor": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } -} +} \ No newline at end of file diff --git a/app/data/i18n/Dutch.json b/app/data/i18n/Dutch.json index 38bc739b2..27f6095c6 100644 --- a/app/data/i18n/Dutch.json +++ b/app/data/i18n/Dutch.json @@ -67,7 +67,8 @@ "template": "Sjabloon", "room": "Kamer", "sound": "Geluid", - "tandem": "Deeltjesverspreider" + "tandem": "Deeltjesverspreider", + "type": "" }, "newName": "Nieuwe naam:", "saveProject": "Project opslaan", @@ -80,7 +81,7 @@ "addToNotes": "Notitie toevoegen", "noRooms": "Je hebt ten minste één kamer nodig om de app te compileren.", "tileLayer": "tegellaag", - "ctSite": "ct.js thuispagina", + "ctSite": "ct.js thuispagina", "docsShort": "Docs", "docsLong": "Documentatie", "openAssetGallery": "Galerij", @@ -125,7 +126,9 @@ "skeletten", "skeletten" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "current": "New", @@ -589,7 +592,10 @@ "SpringStream": "Lente Stroom", "LucasDracula": "Lucas Dracula", "Horizon": "Horizon", - "HCBlack": "Hoog-contrast Zwart" + "HCBlack": "Hoog-contrast Zwart", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "codeFontDefault": "Standaard (Iosevka Licht)", "codeFontOldSchool": "Ouderwets", @@ -602,7 +608,10 @@ "heading": "Instellingen", "disableSounds": "Schakel UI geluiden uit", "changeDataFolder": "Stel maplocatie voor data in", - "forceProductionForDebug": "Forceer productietaken voor debug exports" + "forceProductionForDebug": "Forceer productietaken voor debug exports", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "save": "Sla project op", @@ -638,14 +647,36 @@ "fx": "FX", "restart": "Herstart", "project": "Project", - "templates": "Sjablonen" - }, - "copyCustomProperties": { - "customProperties": "Aangepaste eigenschappen", - "addProperty": "Voeg eigenschap toe", - "property": "Eigenschap", - "value": "Waarde", - "delete": "Verwijder" + "templates": "Sjablonen", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "Nieuwe groep", @@ -716,7 +747,13 @@ "copyProperties": { "position": "Positie", "rotation": "Rotatie", - "scale": "Schaal" + "scale": "Schaal", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "Voeg eigenschap toe", + "property": "Eigenschap", + "value": "Waarde" }, "customProperties": "Aangepaste eigenschappen", "restrictCamera": "Houd camera in een rechthoek", @@ -736,7 +773,19 @@ "deleteTiles": "Verwijder tegels", "moveTilesToLayer": "Beweeg naar laag", "shiftTiles": "Verschuif tegels", - "changeCopyRotation": "Draai" + "changeCopyRotation": "Draai", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Activeer", @@ -869,7 +918,9 @@ "add": "Voeg toe (brand)", "multiply": "Vermenigvuldig (donkerder maken)", "screen": "Scherm (lichter maken)" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "assetInput": { "changeAsset": "Druk om het onderdeel te veranderen", @@ -889,5 +940,71 @@ "visitAuthorsItch": "De auteurs itch.io pagina bezoeken", "visitAuthorsTwitter": "De Twitterpagina van de auteur bezoeken", "tipAuthor": "De auteur een fooi geven voor hun harde werk :D" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } -} +} \ No newline at end of file diff --git a/app/data/i18n/English.json b/app/data/i18n/English.json index e662c6a48..b3c43288e 100644 --- a/app/data/i18n/English.json +++ b/app/data/i18n/English.json @@ -50,6 +50,8 @@ "zoom": "Zoom", "zoomIn": "Zoom in", "zoomOut": "Zoom out", + "undo": "Undo", + "redo": "Redo", "fieldTypes": { "checkbox": "Checkbox", "code": "Codebox", @@ -71,7 +73,8 @@ "template": "Template", "room": "Room", "sound": "Sound", - "tandem": "Particle emitter" + "tandem": "Particle emitter", + "type": "" }, "newName": "New name:", "saveProject": "Save project", @@ -160,13 +163,6 @@ "old": "Old", "projectPalette": "Project's Palette" }, - "copyCustomProperties": { - "customProperties": "Custom Properties", - "addProperty": "Add Property", - "property": "Property", - "value": "Value", - "delete": "Delete" - }, "curveEditor": { "curveLineHint": "Click the curve to add a point", "dragPointHint": "Drag to move the point, right-click to delete it", @@ -655,7 +651,8 @@ "altTemplateLayout": "Alternative layout for template editor", "disableSounds": "Disable UI sounds", "changeDataFolder": "Set data folder location", - "forceProductionForDebug": "Force production tasks for debug exports" + "forceProductionForDebug": "Force production tasks for debug exports", + "settings": "" }, "project": { "heading": "Project", @@ -725,11 +722,11 @@ "roomBackgrounds": { "add": "Add a Background", "depth": "Depth:", - "movement": "Movement speed (X, Y):", - "parallax": "Parallax (X, Y):", + "movement": "Movement speed:", + "parallax": "Parallax:", "repeat": "Repeat:", - "scale": "Scaling (X, Y):", - "shift": "Shift (X, Y):", + "scale": "Scaling:", + "shift": "Shift:", "notBackgroundTextureWarning": "This texture is not marked as a background. It will have gaps when exported.", "fixBackground": "Fix it.", "dismissWarning": "Dismiss." @@ -745,7 +742,14 @@ "width": "View width:", "height": "View height:", "isUi": "Is a UI layer?", - "events": "Room events", + "simulate": "Simulate", + "grid": "Grid", + "toggleDiagonalGrid": "Diagonal grid", + "changeGridSize": "Change cell size", + "events": "Events", + "gridOff": "Disable grid", + "xrayMode": "X-ray mode", + "colorizeTileLayers": "Colorize tile layers", "copies": "Copies", "backgrounds": "Backgrounds", "backgroundColor": "Background color:", @@ -754,7 +758,6 @@ "add": "Add", "none": "Nothing", "done": "Done", - "grid": "Set grid", "hotkeysNotice": "Ctrl = Delete, Alt = No grid, Shift = Multiple", "hotkeysNoticeMovement": "Ctrl = Delete, Shift = Select", "shift": "Shift everything", @@ -769,17 +772,26 @@ "selectAndMove": "Select and Move", "customProperties": "Custom Properties", "findTileset": "Find a tileset", + "resetView": "Reset view", "copyProperties": { "position": "Position", "rotation": "Rotation", - "scale": "Scale" + "scale": "Scale", + "opacity": "Opacity", + "tint": "Tint", + "multipleValues": "(Multiple)" + }, + "copyCustomProperties": { + "addProperty": "Add Property", + "property": "Property", + "value": "Value", + "nameOccupied": "This property is used by ct.js! You should come up with a different name." }, "restrictCamera": "Keep camera in a rectangle", "minimumX": "Min X", "minimumY": "Min Y", "maximumX": "Max X", "maximumY": "Max Y", - "gridOff": "Disable grid", "gridSize": "Grid size:", "toCenter": "To center", "shiftLabel": "Shift by:", @@ -791,7 +803,14 @@ "deleteTiles": "Delete tiles", "moveTilesToLayer": "Move to layer", "shiftTiles": "Shift tiles", - "changeCopyRotation": "Rotate" + "changeCopyRotation": "Rotate", + "tools": { + "select": "Select tool", + "addCopies": "Add copies", + "addTiles": "Add tiles", + "manageBackgrounds": "Manage backgrounds", + "roomProperties": "Room properties" + } }, "styleView": { "active": "Active", @@ -984,6 +1003,7 @@ "depth": "Depth:", "opacity": "Opacity:", "blendMode": "Blend mode:", + "animationFPS": "Animation FPS:", "playAnimationOnStart": "Play animation on start", "loopAnimation": "Loop animation", "blendModes": { diff --git a/app/data/i18n/French.json b/app/data/i18n/French.json index 095ed0bb4..108ea31b9 100644 --- a/app/data/i18n/French.json +++ b/app/data/i18n/French.json @@ -93,7 +93,8 @@ "template": "", "room": "", "sound": "", - "tandem": "" + "tandem": "", + "type": "" }, "newName": "Nouveau nom:", "saveProject": "Sauvegarder le projet", @@ -151,7 +152,9 @@ "", "" ] - } + }, + "next": "", + "previous": "" }, "exportPanel": { "hide": "Cacher", @@ -589,7 +592,10 @@ "SpringStream": "", "LucasDracula": "", "Horizon": "", - "HCBlack": "" + "HCBlack": "", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "codeFontDefault": "Font par défault (Iosevka Light)", "codeFontOldSchool": "Old school", @@ -602,7 +608,10 @@ "heading": "Paramètres", "disableSounds": "", "changeDataFolder": "", - "forceProductionForDebug": "" + "forceProductionForDebug": "", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "save": "Sauvegarder le projet", @@ -638,14 +647,36 @@ "fx": "FX", "restart": "", "project": "", - "templates": "Types" - }, - "copyCustomProperties": { - "customProperties": "", - "addProperty": "", - "property": "", - "value": "", - "delete": "" + "templates": "Types", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "", @@ -716,7 +747,13 @@ "copyProperties": { "position": "", "rotation": "", - "scale": "" + "scale": "", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "", + "property": "", + "value": "" }, "customProperties": "", "restrictCamera": "", @@ -736,7 +773,19 @@ "deleteTiles": "Supprimer les Tiles", "moveTilesToLayer": "Déplacer le calque", "shiftTiles": "Déplacer les Tiles", - "changeCopyRotation": "" + "changeCopyRotation": "", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Activer", @@ -869,7 +918,9 @@ "add": "", "multiply": "", "screen": "" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "assetInput": { "changeAsset": "", @@ -889,5 +940,71 @@ "visitAuthorsItch": "", "visitAuthorsTwitter": "", "tipAuthor": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } } \ No newline at end of file diff --git a/app/data/i18n/German.json b/app/data/i18n/German.json index 67b9c85b0..d43d8e4fb 100644 --- a/app/data/i18n/German.json +++ b/app/data/i18n/German.json @@ -67,7 +67,8 @@ "template": "", "room": "", "sound": "", - "tandem": "" + "tandem": "", + "type": "" }, "newName": "Neuer Name:", "saveProject": "Project speichern", @@ -125,7 +126,9 @@ "", "" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "current": "Neu", @@ -589,7 +592,10 @@ "SpringStream": "", "LucasDracula": "", "Horizon": "", - "HCBlack": "" + "HCBlack": "", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "codeFontDefault": "Standard (Iosevka Light)", "codeFontOldSchool": "Old School", @@ -602,7 +608,10 @@ "heading": "Einstellungen", "disableSounds": "", "changeDataFolder": "", - "forceProductionForDebug": "" + "forceProductionForDebug": "", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "save": "Projekt speichern", @@ -638,14 +647,36 @@ "fx": "FX", "restart": "", "project": "", - "templates": "Types" - }, - "copyCustomProperties": { - "customProperties": "", - "addProperty": "", - "property": "", - "value": "", - "delete": "" + "templates": "Types", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "", @@ -716,7 +747,13 @@ "copyProperties": { "position": "", "rotation": "", - "scale": "" + "scale": "", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "", + "property": "", + "value": "" }, "customProperties": "", "restrictCamera": "", @@ -736,7 +773,19 @@ "deleteTiles": "Tiles löschen ", "moveTilesToLayer": "Auf Ebene verschieben", "shiftTiles": "Tiles verschieben", - "changeCopyRotation": "" + "changeCopyRotation": "", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Aktiv", @@ -869,7 +918,9 @@ "add": "", "multiply": "", "screen": "" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "assetInput": { "changeAsset": "", @@ -889,5 +940,71 @@ "visitAuthorsItch": "", "visitAuthorsTwitter": "", "tipAuthor": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } } \ No newline at end of file diff --git a/app/data/i18n/Japanese.json b/app/data/i18n/Japanese.json index f86390759..6e73f73f0 100644 --- a/app/data/i18n/Japanese.json +++ b/app/data/i18n/Japanese.json @@ -69,7 +69,8 @@ "template": "", "room": "", "sound": "", - "tandem": "" + "tandem": "", + "type": "" }, "newName": "新しい名前:", "saveProject": "プロジェクトを保存", @@ -125,7 +126,9 @@ "", "" ] - } + }, + "next": "", + "previous": "" }, "assetInput": { "changeAsset": "アセットを変更する", @@ -144,13 +147,6 @@ "old": "古い", "projectPalette": "プロジェクトのパレット" }, - "copyCustomProperties": { - "customProperties": "カスタムプロパティ", - "addProperty": "プロパティを追加", - "property": "プロパティ", - "value": "数字", - "delete": "削除" - }, "curveEditor": { "curveLineHint": "カーブをクリックしてポイントを追加", "dragPointHint": "ドラッグでポイントを移動、右クリックで削除", @@ -620,7 +616,10 @@ "SpringStream": "春の小川", "LucasDracula": "ルーカス・ドラキュラ", "Horizon": "地平線", - "HCBlack": "高コントラストの黒" + "HCBlack": "高コントラストの黒", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "language": "言語", "translateToYourLanguage": "ct.jsを翻訳する!", @@ -634,7 +633,10 @@ "codeDense": "高密度なレイアウト", "disableSounds": "UIサウンドを無効化", "changeDataFolder": "データフォルダの場所を設定", - "forceProductionForDebug": "デバッグエクスポートのためのプロダクションタスクの強制" + "forceProductionForDebug": "デバッグエクスポートのためのプロダクションタスクの強制", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "heading": "プロジェクト", @@ -670,7 +672,36 @@ "sounds": "音", "ui": "UI", "fx": "FX", - "templates": "テンプレート" + "templates": "テンプレート", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "roomBackgrounds": { "add": "背景を追加", @@ -722,7 +753,13 @@ "copyProperties": { "position": "位置", "rotation": "回転", - "scale": "大きさ" + "scale": "大きさ", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "プロパティを追加", + "property": "プロパティ", + "value": "数字" }, "restrictCamera": "カメラを四角に収める", "minimumX": "最小 X", @@ -741,7 +778,19 @@ "deleteTiles": "複数のタイルを削除", "moveTilesToLayer": "レイヤーに移動", "shiftTiles": "タイルに移動", - "changeCopyRotation": "回転" + "changeCopyRotation": "回転", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "有効", @@ -874,7 +923,9 @@ "add": "", "multiply": "", "screen": "" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "builtinAssetGallery": { "galleryTip": "", @@ -889,5 +940,71 @@ "visitAuthorsItch": "", "visitAuthorsTwitter": "", "tipAuthor": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } } \ No newline at end of file diff --git a/app/data/i18n/Polish.json b/app/data/i18n/Polish.json index f79bb9f29..537c723fe 100644 --- a/app/data/i18n/Polish.json +++ b/app/data/i18n/Polish.json @@ -67,7 +67,8 @@ "template": "", "room": "", "sound": "", - "tandem": "" + "tandem": "", + "type": "" }, "newName": "Nowa nazwa:", "saveProject": "Zapisz projekt", @@ -125,7 +126,9 @@ "", "" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "current": "Nowy", @@ -589,7 +592,10 @@ "SpringStream": "", "LucasDracula": "", "Horizon": "", - "HCBlack": "" + "HCBlack": "", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "codeFontDefault": "Domyślny (Iosevka Light)", "codeFontOldSchool": "Old school", @@ -602,7 +608,10 @@ "heading": "Ustawienia", "disableSounds": "", "changeDataFolder": "", - "forceProductionForDebug": "" + "forceProductionForDebug": "", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "save": "Zapisz projekt", @@ -638,14 +647,36 @@ "fx": "", "restart": "", "project": "", - "templates": "Typy" - }, - "copyCustomProperties": { - "customProperties": "", - "addProperty": "", - "property": "", - "value": "", - "delete": "" + "templates": "Typy", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "", @@ -716,7 +747,13 @@ "copyProperties": { "position": "", "rotation": "", - "scale": "" + "scale": "", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "", + "property": "", + "value": "" }, "customProperties": "", "restrictCamera": "", @@ -736,7 +773,19 @@ "deleteTiles": "Usuń kafelki", "moveTilesToLayer": "Przenieś do warstwy", "shiftTiles": "Przesuń kafelki", - "changeCopyRotation": "" + "changeCopyRotation": "", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Aktywny", @@ -869,7 +918,9 @@ "add": "", "multiply": "", "screen": "" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "assetInput": { "changeAsset": "", @@ -889,5 +940,71 @@ "visitAuthorsItch": "", "visitAuthorsTwitter": "", "tipAuthor": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } } \ No newline at end of file diff --git a/app/data/i18n/Romanian.json b/app/data/i18n/Romanian.json index 8807389aa..09d1e5995 100644 --- a/app/data/i18n/Romanian.json +++ b/app/data/i18n/Romanian.json @@ -67,7 +67,8 @@ "template": "", "room": "", "sound": "", - "tandem": "" + "tandem": "", + "type": "" }, "newName": "Nume nou:", "saveProject": "Salvează proiectul", @@ -125,7 +126,9 @@ "", "" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "current": "Nou", @@ -589,7 +592,10 @@ "SpringStream": "", "LucasDracula": "", "Horizon": "", - "HCBlack": "" + "HCBlack": "", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "codeFontDefault": "", "codeFontOldSchool": "", @@ -602,7 +608,10 @@ "heading": "Setări", "disableSounds": "", "changeDataFolder": "", - "forceProductionForDebug": "" + "forceProductionForDebug": "", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "save": "Salvează proiectul", @@ -638,14 +647,36 @@ "fx": "", "restart": "", "project": "", - "templates": "Tipuri" - }, - "copyCustomProperties": { - "customProperties": "", - "addProperty": "", - "property": "", - "value": "", - "delete": "" + "templates": "Tipuri", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "", @@ -716,7 +747,13 @@ "copyProperties": { "position": "", "rotation": "", - "scale": "" + "scale": "", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "", + "property": "", + "value": "" }, "customProperties": "", "restrictCamera": "", @@ -736,7 +773,19 @@ "deleteTiles": "Șterge tile-urile", "moveTilesToLayer": "Mută în layer", "shiftTiles": "Deplasează tile-urile", - "changeCopyRotation": "" + "changeCopyRotation": "", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Activ", @@ -869,7 +918,9 @@ "add": "", "multiply": "", "screen": "" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "assetInput": { "changeAsset": "", @@ -889,5 +940,71 @@ "visitAuthorsItch": "", "visitAuthorsTwitter": "", "tipAuthor": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } } \ No newline at end of file diff --git a/app/data/i18n/Russian.json b/app/data/i18n/Russian.json index 9afbd2a4f..b854d70c8 100644 --- a/app/data/i18n/Russian.json +++ b/app/data/i18n/Russian.json @@ -67,7 +67,8 @@ "template": "Шаблон", "room": "Комната", "sound": "Звук", - "tandem": "Система частиц" + "tandem": "Система частиц", + "type": "" }, "newName": "Новое имя:", "saveProject": "Сохранить проект", @@ -125,7 +126,9 @@ "скелетных спрайта", "скелетных спрайтов" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "old": "Старый", @@ -607,7 +610,8 @@ "disableSounds": "Отключить звуки интерфейса", "changeDataFolder": "Изменить папку данных ct.js", "forceProductionForDebug": " Включить релизные функции для дебаг-экспортов", - "altTemplateLayout": "Альт. вёрстка редактора шаблонов" + "altTemplateLayout": "Альт. вёрстка редактора шаблонов", + "settings": "" }, "project": { "save": "Сохранить проект", @@ -643,14 +647,36 @@ "ui": "Интерфейс", "fx": "Эффекты", "restart": "Перезапустить", - "templates": "Шаблоны" - }, - "copyCustomProperties": { - "customProperties": "Пользовательские поля", - "addProperty": "Добавить поле", - "property": "Имя поля", - "value": "Значение", - "delete": "Удалить" + "templates": "Шаблоны", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "Новая группа", @@ -721,7 +747,13 @@ "copyProperties": { "position": "Позиция", "rotation": "Поворот", - "scale": "Размер" + "scale": "Размер", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "Добавить поле", + "property": "Имя поля", + "value": "Значение" }, "customProperties": "Пользовательские поля", "restrictCamera": "Ограничить камеру в прямоугольнике", @@ -741,7 +773,19 @@ "deleteTiles": "Удалить плитки", "moveTilesToLayer": "Переместить в другой слой", "shiftTiles": "Сместить плитки", - "changeCopyRotation": "Повернуть" + "changeCopyRotation": "Повернуть", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Активно?", @@ -875,7 +919,8 @@ "multiply": "Умножить (затемнение)", "screen": "Экран (осветление)" }, - "loopAnimation": "Цикличная анимация" + "loopAnimation": "Цикличная анимация", + "animationFPS": "" }, "assetInput": { "changeAsset": "Нажмите, чтобы заменить ассет", @@ -909,7 +954,8 @@ "actions": "Действия", "pointer": "События указателя", "misc": "Разное", - "animation": "Анимация" + "animation": "Анимация", + "timers": "" }, "coreEvents": { "OnCreate": "Создание", @@ -931,7 +977,8 @@ "OnActionDown": "Действие нажато", "OnFrameChange": "Смена кадра", "OnAnimationLoop": "Перезапуск анимации", - "OnAnimationComplete": "Конец анимации" + "OnAnimationComplete": "Конец анимации", + "Timer": "" }, "coreParameterizedNames": { "OnActionPress": "При нажатии %%action%%", @@ -942,7 +989,8 @@ "action": "Действие" }, "coreEventsLocals": { - "OnActionDown_value": "Текущее значение действия" + "OnActionDown_value": "Текущее значение действия", + "OnActionPress_value": "" }, "coreEventsDescriptions": { "OnCreate": "Срабатывает при создании копии.", @@ -955,7 +1003,8 @@ "OnActionRelease": "Срабатывает, когда действие перестаёт быть активно — когда отпускают клавиши, джойстики и т.п.", "OnActionDown": "Срабатывает каждый кадр, пока методы ввода этого действия активны.", "OnAnimationLoop": "Срабатывает каждый раз, когда цикличная анимация подходит к концу.", - "OnAnimationComplete": "Срабатывает один раз, когда нецикличная анимация заканчивается." + "OnAnimationComplete": "Срабатывает один раз, когда нецикличная анимация заканчивается.", + "Timer": "" } } -} +} \ No newline at end of file diff --git a/app/data/i18n/Spanish.json b/app/data/i18n/Spanish.json index eff068789..e264772d7 100644 --- a/app/data/i18n/Spanish.json +++ b/app/data/i18n/Spanish.json @@ -67,7 +67,8 @@ "template": "", "room": "", "sound": "", - "tandem": "" + "tandem": "", + "type": "" }, "newName": "Nombre Nuevo:", "saveProject": "Guardar proyecto", @@ -125,7 +126,9 @@ "", "" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "current": "Nuevo", @@ -590,7 +593,10 @@ "SpringStream": "Corriente de Primavera", "LucasDracula": "Lucas Dracula", "Horizon": "", - "HCBlack": "" + "HCBlack": "", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "codeFontDefault": "Predeterminado (Iosevka Light)", "codeFontOldSchool": "Vieja Escuela", @@ -602,7 +608,10 @@ "codeDense": "Diseño Denso", "disableSounds": "", "changeDataFolder": "", - "forceProductionForDebug": "" + "forceProductionForDebug": "", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "save": "Guardar proyecto", @@ -638,14 +647,36 @@ "restart": "Reiniciar", "project": "Proyecto", "fx": "Effectos", - "templates": "Tipos" - }, - "copyCustomProperties": { - "customProperties": "", - "addProperty": "", - "property": "", - "value": "", - "delete": "" + "templates": "Tipos", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "", @@ -716,7 +747,13 @@ "copyProperties": { "position": "", "rotation": "", - "scale": "" + "scale": "", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "", + "property": "", + "value": "" }, "customProperties": "", "restrictCamera": "", @@ -736,7 +773,19 @@ "deleteTiles": "Eliminar tiles", "moveTilesToLayer": "Mover a capa", "shiftTiles": "Cambiar tiles", - "changeCopyRotation": "Rotar" + "changeCopyRotation": "Rotar", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Activar", @@ -869,7 +918,9 @@ "add": "", "multiply": "", "screen": "" - } + }, + "animationFPS": "", + "loopAnimation": "" }, "assetInput": { "changeAsset": "", @@ -889,5 +940,71 @@ "visitAuthorsItch": "", "visitAuthorsTwitter": "", "tipAuthor": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + } } } \ No newline at end of file diff --git a/app/data/i18n/Turkish.json b/app/data/i18n/Turkish.json index ac128cd89..275b8e8b2 100644 --- a/app/data/i18n/Turkish.json +++ b/app/data/i18n/Turkish.json @@ -76,7 +76,8 @@ "template": "Şablon", "room": "Oda", "sound": "Ses", - "tandem": "Partikül dağıtıcısı" + "tandem": "Partikül dağıtıcısı", + "type": "" }, "assetTypes": { "textures": [ @@ -125,7 +126,9 @@ "edit": "Değiştir", "nothingToShowFiller": "Burada gösterilecek hiçbir şey yok!", "required": "Gerekli", - "settings": "Ayarlar" + "settings": "Ayarlar", + "next": "", + "previous": "" }, "assetInput": { "changeAsset": "Görseli değiştirmek için tıkla", @@ -158,13 +161,6 @@ "old": "Eski", "projectPalette": "Proje'nin paleti" }, - "copyCustomProperties": { - "customProperties": "Özel Özellikler", - "addProperty": "Özellik Ekle", - "property": "Özellik", - "value": "Değer", - "delete": "Sil" - }, "curveEditor": { "curveLineHint": "Nokta eklemek için kavise tıkla", "dragPointHint": "Noktayı hareket ettirmek için sürükle, silmek için sağ tıkla.", @@ -621,7 +617,10 @@ "SpringStream": "İlkbahar Yayını", "LucasDracula": "Lucas Drakula", "Horizon": "Ufuk", - "HCBlack": "Yüksek-kontrast siyah" + "HCBlack": "Yüksek-kontrast siyah", + "RosePine": "", + "RosePineMoon": "", + "RosePineDawn": "" }, "language": "Dil", "translateToYourLanguage": "ct.js'i çevir!", @@ -635,7 +634,10 @@ "codeDense": "Düzeni yoğunlaştır", "disableSounds": "Arayüz seslerini kapat", "changeDataFolder": "Veri klasörü yolunu ayarla", - "forceProductionForDebug": "Hata ayıklamaları dışarı aktarmak için üretim işlemlerini zorla" + "forceProductionForDebug": "Hata ayıklamaları dışarı aktarmak için üretim işlemlerini zorla", + "settings": "", + "prideMode": "", + "altTemplateLayout": "" }, "project": { "heading": "Proje", @@ -671,7 +673,36 @@ "sounds": "Sesler", "ui": "Arayüz", "fx": "Efektler", - "templates": "Şablonlar" + "templates": "Şablonlar", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "roomBackgrounds": { "add": "Arkaplan ekle", @@ -736,13 +767,31 @@ "copyProperties": { "position": "Pozisyon", "rotation": "Rotasyon", - "scale": "Boyut" + "scale": "Boyut", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "Özellik Ekle", + "property": "Özellik", + "value": "Değer" }, "restrictCamera": "Kamerayı dörtgende tut", "minimumX": "Minimum X", "minimumY": "Minimum Y", "maximumX": "Maksimum X", - "maximumY": "Maksimum Y" + "maximumY": "Maksimum Y", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Aktif", @@ -888,6 +937,74 @@ "add": "Ekle (yanık)", "multiply": "Çoğalt (karanlık)", "screen": "Ekran (aydınlık)" + }, + "animationFPS": "", + "loopAnimation": "" + }, + "scriptables": { + "addEvent": "", + "removeEvent": "", + "removeEventConfirm": "", + "changeArguments": "", + "eventAlreadyExists": "", + "localEventVars": "", + "createEventHint": "", + "coreEventsCategories": { + "lifecycle": "", + "actions": "", + "pointer": "", + "misc": "", + "animation": "", + "timers": "" + }, + "coreEvents": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnPointerClick": "", + "OnPointerSecondaryClick": "", + "OnPointerEnter": "", + "OnPointerLeave": "", + "OnPointerDown": "", + "OnPointerUp": "", + "OnPointerUpOutside": "", + "OnPointerWheel": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnFrameChange": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" + }, + "coreParameterizedNames": { + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "" + }, + "coreEventsArguments": { + "action": "" + }, + "coreEventsLocals": { + "OnActionDown_value": "", + "OnActionPress_value": "" + }, + "coreEventsDescriptions": { + "OnCreate": "", + "OnRoomStart": "", + "OnStep": "", + "OnDraw": "", + "OnDestroy": "", + "OnRoomEnd": "", + "OnActionPress": "", + "OnActionRelease": "", + "OnActionDown": "", + "OnAnimationLoop": "", + "OnAnimationComplete": "", + "Timer": "" } } -} +} \ No newline at end of file diff --git a/app/data/i18n/Ukranian.json b/app/data/i18n/Ukranian.json index d842a68b8..923a7e622 100644 --- a/app/data/i18n/Ukranian.json +++ b/app/data/i18n/Ukranian.json @@ -67,7 +67,8 @@ "template": "Шаблон", "room": "Кімната", "sound": "Звук", - "tandem": "Система частинок" + "tandem": "Система частинок", + "type": "" }, "newName": "Нове ім'я:", "saveProject": "Зберегти проект", @@ -125,7 +126,9 @@ "скелетного спрайту", "скелетних спрайтів" ] - } + }, + "next": "", + "previous": "" }, "colorPicker": { "old": "Старий", @@ -607,7 +610,8 @@ "disableSounds": "Вимкнути звуки інтерфейсу", "changeDataFolder": "Змінити папку даних ct.js", "forceProductionForDebug": " Включити релізні функції для дебаг-експортів", - "altTemplateLayout": "Альт. верстка редактора шаблонів" + "altTemplateLayout": "Альт. верстка редактора шаблонів", + "settings": "" }, "project": { "save": "Зберегти проект", @@ -643,14 +647,36 @@ "ui": "Інтерфейс", "fx": "Ефекти", "restart": "Перезапустити", - "templates": "Шаблони" - }, - "copyCustomProperties": { - "customProperties": "Користувацькі поля", - "addProperty": "Додати поле", - "property": "Ім'я поля", - "value": "Значення", - "delete": "Видалити" + "templates": "Шаблони", + "tour": { + "header": "", + "aboutTour": "", + "helpPanel": "", + "helpPanelTabs": "", + "projectResources": "", + "tabTextures": "", + "tabTexturesImport": "", + "tabTexturesGallery": "", + "tabTexturesClipboard": "", + "tabTexturesPlaceholders": "", + "tabTemplates": "", + "tabRooms": "", + "tabSounds": "", + "tabSoundsImport": "", + "tabSoundsGallery": "", + "tabSoundsRecord": "", + "tabInterlude": "", + "tabUI": "", + "tabFX": "", + "tabProject": "", + "tabProjectModules": "", + "tabProjectModuleDocs": "", + "tabMainMenu": "", + "tabMainMenuSettings": "", + "tabMainMenuMeta": "", + "helpPanelReminder": "", + "buttonStartTutorial": "" + } }, "assetViewer": { "addNewGroup": "Нова група", @@ -721,7 +747,13 @@ "copyProperties": { "position": "Позиція", "rotation": "Поворот", - "scale": "Розмір" + "scale": "Розмір", + "multipleValues": "" + }, + "copyCustomProperties": { + "addProperty": "Додати поле", + "property": "Ім'я поля", + "value": "Значення" }, "customProperties": "Користувацькі поля", "restrictCamera": "Обмежити камеру в прямокутнику", @@ -741,7 +773,19 @@ "deleteTiles": "Видалити плитки", "moveTilesToLayer": "Перемістити до іншого шару", "shiftTiles": "Змістити плитки", - "changeCopyRotation": "Повернути" + "changeCopyRotation": "Повернути", + "simulate": "", + "toggleDiagonalGrid": "", + "changeGridSize": "", + "xrayMode": "", + "colorizeTileLayers": "", + "tools": { + "select": "", + "addCopies": "", + "addTiles": "", + "manageBackgrounds": "", + "roomProperties": "" + } }, "styleView": { "active": "Активно?", @@ -875,7 +919,8 @@ "multiply": "Помножити (затемнення)", "screen": "Екран (освітлення)" }, - "loopAnimation": "Циклічна анімація" + "loopAnimation": "Циклічна анімація", + "animationFPS": "" }, "assetInput": { "changeAsset": "Натисніть, щоб замінити ассет", @@ -883,7 +928,7 @@ "selectAssetHeader": "Вибрати асет" }, "builtinAssetGallery": { - "galleryTip": "Це вбудована галерея різних безкоштовних текстур і звуків. Вони всі під ліцензією CCO, WTFPL, або на спец. правах для движка ct.js, і ти можеш використовувати їх як завгодно, в комерційних проектах і не тільки." , + "galleryTip": "Це вбудована галерея різних безкоштовних текстур і звуків. Вони всі під ліцензією CCO, WTFPL, або на спец. правах для движка ct.js, і ти можеш використовувати їх як завгодно, в комерційних проектах і не тільки.", "assetGalleryHeader": "Асети", "importIntoProject": "Вставити в проект", "importAll": "Імпортувати все", @@ -909,7 +954,8 @@ "actions": "Дії", "pointer": "Події покажчика", "misc": "Різне", - "animation": "Анімація" + "animation": "Анімація", + "timers": "" }, "coreEvents": { "OnCreate": "Створення", @@ -931,7 +977,8 @@ "OnActionDown": "Дія натиснута", "OnFrameChange": "Зміна кадру", "OnAnimationLoop": "Перепуск анімації", - "OnAnimationComplete": "Кінець анімації" + "OnAnimationComplete": "Кінець анімації", + "Timer": "" }, "coreParameterizedNames": { "OnActionPress": "При натисканні %%action%%", @@ -942,7 +989,8 @@ "action": "Дія" }, "coreEventsLocals": { - "OnActionDown_value": "Поточне значення дії" + "OnActionDown_value": "Поточне значення дії", + "OnActionPress_value": "" }, "coreEventsDescriptions": { "OnCreate": "Спрацьовує під час створення копії.", @@ -955,7 +1003,8 @@ "OnActionRelease": "Спрацьовує, коли дія перестає бути активною - коли відпускають клавіші, джойстики і т.п.", "OnActionDown": "Спрацьовує кожен кадр, доки методи введення цієї дії активні.", "OnAnimationLoop": "Спрацьовує щоразу, коли циклічна анімація добігає кінця.", - "OnAnimationComplete": "Спрацьовує один раз, коли нециклічна анімація закінчується." + "OnAnimationComplete": "Спрацьовує один раз, коли нециклічна анімація закінчується.", + "Timer": "" } } -} +} \ No newline at end of file diff --git a/app/package-lock.json b/app/package-lock.json index 1d68db617..c7973a8be 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "ctjs", - "version": "2.0.2", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ctjs", - "version": "2.0.2", + "version": "2.1.0", "license": "MIT", "dependencies": { "@capacitor/cli": "^3.4.0", @@ -34,6 +34,7 @@ "nanoid": "^3.1.31", "npm": "^8.11.0", "opentype.js": "^1.3.3", + "pixi-ease": "^3.0.7", "pixi-particles": "4.3.1", "pixi.js": "5.3.11", "pixi.js-legacy": "5.3.11", @@ -8071,6 +8072,11 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, + "node_modules/penner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/penner/-/penner-0.1.3.tgz", + "integrity": "sha512-UzkaC2L6d9J1VzJAFH0TQwuKE/rerpTZkgW6aPLVeu/LdjWn6rnuY9lXcVN1AE9tZVfHrsJ2gZOBsRjpQECNHA==" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -8090,6 +8096,23 @@ "node": ">=4" } }, + "node_modules/pixi-ease": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/pixi-ease/-/pixi-ease-3.0.7.tgz", + "integrity": "sha512-ew1IzAi2layygHrk8+tk6XTuue3WM6Po5LsA46v+9BtQNMa2sX0PMLzQ4nq1UpTbLg4Dwqep9RTzDRsFhCYJJw==", + "dependencies": { + "eventemitter3": "^4.0.0", + "penner": "^0.1.3" + }, + "peerDependencies": { + "pixi.js": ">=4.6.0" + } + }, + "node_modules/pixi-ease/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/pixi-particles": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/pixi-particles/-/pixi-particles-4.3.1.tgz", @@ -8802,6 +8825,21 @@ "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "optional": true }, + "node_modules/rollup": { + "version": "2.76.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.76.0.tgz", + "integrity": "sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==", + "peer": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/rsvp": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", @@ -9634,6 +9672,19 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -16226,6 +16277,11 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, + "penner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/penner/-/penner-0.1.3.tgz", + "integrity": "sha512-UzkaC2L6d9J1VzJAFH0TQwuKE/rerpTZkgW6aPLVeu/LdjWn6rnuY9lXcVN1AE9tZVfHrsJ2gZOBsRjpQECNHA==" + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -16236,6 +16292,22 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, + "pixi-ease": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/pixi-ease/-/pixi-ease-3.0.7.tgz", + "integrity": "sha512-ew1IzAi2layygHrk8+tk6XTuue3WM6Po5LsA46v+9BtQNMa2sX0PMLzQ4nq1UpTbLg4Dwqep9RTzDRsFhCYJJw==", + "requires": { + "eventemitter3": "^4.0.0", + "penner": "^0.1.3" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + } + } + }, "pixi-particles": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/pixi-particles/-/pixi-particles-4.3.1.tgz", @@ -16776,6 +16848,15 @@ } } }, + "rollup": { + "version": "2.76.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.76.0.tgz", + "integrity": "sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==", + "peer": true, + "requires": { + "fsevents": "~2.3.2" + } + }, "rsvp": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", @@ -17411,6 +17492,12 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "peer": true + }, "uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", diff --git a/app/package.json b/app/package.json index 4c8ff0c01..4f0f4db12 100644 --- a/app/package.json +++ b/app/package.json @@ -2,7 +2,7 @@ "main": "index.html", "name": "ctjs", "description": "ct.js — a free 2D game engine", - "version": "2.0.2", + "version": "2.2.0", "homepage": "https://ctjs.rocks/", "author": { "name": "Cosmo Myzrail Gorynych", @@ -78,6 +78,7 @@ "nanoid": "^3.1.31", "npm": "^8.11.0", "opentype.js": "^1.3.3", + "pixi-ease": "^3.0.7", "pixi-particles": "4.3.1", "pixi.js": "5.3.11", "pixi.js-legacy": "5.3.11", diff --git a/gulpfile.js b/gulpfile.js index 5f8b05fc7..ac881aa2f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -253,7 +253,6 @@ const watchRequires = () => { .on('change', fileChangeNotifier) .on('error', err => { notifier.notify(makeErrorObj('Failure of node_requires', err)); - console.error('[node_requires error]', err); }); }; const watchIcons = () => { diff --git a/package-lock.json b/package-lock.json index 87dfd7d30..69791099d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ctjsbuildenvironment", - "version": "2.0.2", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ctjsbuildenvironment", - "version": "2.0.2", + "version": "2.1.0", "license": "MIT", "dependencies": { "@ct.js/gulp-typescript": "^6.0.0", diff --git a/package.json b/package.json index f94bda427..f8c49215d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ctjsbuildenvironment", - "version": "2.0.2", + "version": "2.2.0", "description": "", "directories": { "doc": "docs" diff --git a/src/examples/DungeonCrawler_tutorial.ict b/src/examples/DungeonCrawler_tutorial.ict index a405ee2cb..156dd055a 100644 --- a/src/examples/DungeonCrawler_tutorial.ict +++ b/src/examples/DungeonCrawler_tutorial.ict @@ -1,4 +1,4 @@ -ctjsVersion: 2.0.2 +ctjsVersion: 2.2.0 notes: /* empty */ libs: fittoscreen: @@ -453,7 +453,7 @@ textures: height: 16 offx: 0 offy: 0 - origname: 'i14d3f53f-a868-4820-84cd-63234ab6e6b9.png}' + origname: i14d3f53f-a868-4820-84cd-63234ab6e6b9.png shape: rect left: 0 right: 16 @@ -478,7 +478,7 @@ textures: height: 32 offx: 0 offy: 0 - origname: 'ia42e0dff-69c9-46c3-8996-745aaa769698.png}' + origname: ia42e0dff-69c9-46c3-8996-745aaa769698.png shape: rect left: 16 right: 16 @@ -729,7 +729,7 @@ templates: extends: cgroup: '' uid: c40336ea-2846-45d5-97ba-d51852d3f6c5 - lastmod: 1656663050756 + lastmod: 1659098686679 events: - lib: core arguments: {} @@ -738,7 +738,6 @@ templates: ct.camera.shiftY = -16; this.maxSpeed = 2; - this.animationSpeed = 5 / ct.speed; this.invincibleTimer = 0; eventKey: OnCreate - lib: core @@ -818,6 +817,7 @@ templates: type: template loopAnimation: true playAnimationOnStart: true + animationFPS: 5 - name: Crate depth: 0 texture: 31948760-8965-4768-ab1d-2b87d0b7fc3a @@ -1271,917 +1271,1193 @@ rooms: 'y': 64 uid: 7fea051e-83e5-4e54-848b-ab3ca861e5cb exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 152 'y': 192 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 160 'y': 208 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 168 'y': 192 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 280 'y': 192 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 280 'y': 64 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 96 'y': 88 uid: c40336ea-2846-45d5-97ba-d51852d3f6c5 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 96 'y': 160 uid: c701a029-af09-4fcf-9712-0fcdaa3581be exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 tiles: - depth: -6000 tiles: - x: 48 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 96 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 176 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 192 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 208 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 272 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 272 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 272 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 272 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 272 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 240 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 224 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 208 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 192 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 176 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 96 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 208 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 208 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 208 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 192 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 192 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 192 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 192 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 192 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 192 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 208 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 240 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 272 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 208 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 240 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 256 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 272 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 64 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 80 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 96 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 112 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 128 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 192 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 112 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 144 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 176 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 208 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 64 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 80 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 96 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 112 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 128 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 144 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 160 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 176 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 192 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 208 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 224 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 240 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 256 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 272 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 288 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 288 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 48 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 224 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 12 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 1 - - 1 - - 1 - x: 224 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 128 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 9 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 1 - - 1 - - 1 - x: 192 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 9 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 1 - - 1 - - 1 - x: 160 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 9 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 1 - - 1 - - 1 - x: 272 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 10 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 1 - - 1 - - 1 extends: cgroup: Solid hidden: false @@ -2189,764 +2465,954 @@ rooms: tiles: - x: 64 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 224 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 256 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 272 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 272 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 256 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 240 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 208 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 208 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 224 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 256 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 272 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 272 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 272 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 240 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 224 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 224 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 224 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 240 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 256 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 256 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 256 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 192 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 288 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 48 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 32 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 26 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 3 - - 1 - - 1 - x: 112 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 240 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 96 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 240 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 224 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 256 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 224 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 240 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 256 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 6 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 0 - - 1 - - 1 - x: 144 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 6 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 0 - - 1 - - 1 - x: 160 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 6 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 0 - - 1 - - 1 - x: 160 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 6 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 0 - - 1 - - 1 - x: 192 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 96 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 240 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 208 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 208 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 208 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 224 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 192 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 176 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 176 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 224 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 240 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 256 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 240 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 15 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 7 - - 1 - - 1 - - 1 - x: 240 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 15 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 7 - - 1 - - 1 - - 1 - x: 64 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 80 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 96 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 112 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 128 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 144 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 160 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 176 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 192 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 208 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 224 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 240 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 256 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 272 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 288 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - - x: 80 - 'y': 144 + - x: 96 + 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: ae954867-1039-43f6-bf6d-d8684cb58612 - grid: - - 0 - - 0 - - 1 - - 1 - x: 224 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 256 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 extends: cgroup: Decor hidden: false @@ -2977,6 +3443,8 @@ rooms: }); eventKey: OnRoomStart type: room + simulate: true + isUi: ! '' - name: Level_02 onstep: '' ondraw: '' @@ -2989,1752 +3457,2424 @@ rooms: 'y': 144 uid: c701a029-af09-4fcf-9712-0fcdaa3581be exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 106 'y': 238 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 359 'y': 239 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 136 'y': 61 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 111 'y': 79 uid: c40336ea-2846-45d5-97ba-d51852d3f6c5 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 124 'y': 233 uid: 9fc1fec8-50bb-49e6-8fb4-c10ad589bf0b exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 112 'y': 220 uid: 9fc1fec8-50bb-49e6-8fb4-c10ad589bf0b exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 255 'y': 232 uid: 5e40076f-8a61-4dfd-a517-a6c943ab9991 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 568 'y': 16 uid: 7fea051e-83e5-4e54-848b-ab3ca861e5cb exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 458 'y': 41 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 451 'y': 47 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 459 'y': 51 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 279 'y': 41 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 387 'y': 58 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 323 'y': 90 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 373 'y': 99 uid: 5e40076f-8a61-4dfd-a517-a6c943ab9991 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 447 'y': 68 uid: 5e40076f-8a61-4dfd-a517-a6c943ab9991 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 317 'y': 55 uid: 5e40076f-8a61-4dfd-a517-a6c943ab9991 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 432 'y': 80 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 352 'y': 112 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 368 'y': 48 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 568 'y': 80 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 334 'y': 230 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 224 'y': 184 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 240 'y': 184 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 256 'y': 184 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 272 'y': 184 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 224 'y': 248 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 240 'y': 248 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 256 'y': 248 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 272 'y': 248 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 280 'y': 56 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 280 'y': 72 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 408 'y': 104 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 424 'y': 104 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 440 'y': 104 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 160 'y': 224 uid: 469b9eef-6117-43a5-87d3-7151da7eba06 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 352 'y': 96 uid: 469b9eef-6117-43a5-87d3-7151da7eba06 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 352 'y': 208 uid: b336d91c-2876-4686-b8b4-901045a86ac7 + exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 tiles: - depth: -6000 tiles: - x: 64 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 96 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 176 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 304 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 320 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 96 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 176 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 192 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 304 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 320 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 368 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 352 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 336 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 368 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 368 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 368 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 368 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 368 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 368 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 320 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 320 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 320 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 368 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 320 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 13 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 1 - - 1 - - 1 - x: 304 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 272 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 256 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 272 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 288 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 304 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 320 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 336 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 352 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 368 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 448 - 'y': -0.0 + 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 384 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 400 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 416 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 432 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 448 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 464 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 464 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 464 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 464 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 480 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 496 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 512 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 528 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 544 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 480 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 496 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 512 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 528 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 544 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 560 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 576 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 576 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 576 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 576 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 576 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 576 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 544 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 544 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 544 'y': -16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 544 'y': -32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 544 'y': -32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 576 'y': -32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 560 'y': -32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 576 'y': -16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 480 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 10 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 1 - - 1 - - 1 - x: 528 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 10 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 1 - - 1 - - 1 - x: 544 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 12 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 1 - - 1 - - 1 - x: 432 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 416 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 400 - 'y': -0.0 + 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 384 - 'y': -0.0 + 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 416 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 13 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 1 - - 1 - - 1 - x: 128 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 144 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 160 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 208 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 64 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 144 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 80 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 96 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 112 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 128 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 144 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 160 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 64 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 48 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 288 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 304 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 368 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 352 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 336 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 320 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 288 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 192 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 176 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 160 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 144 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 128 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 112 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 96 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 80 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 464 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 448 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 432 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 384 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 272 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 288 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 320 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 336 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 352 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 368 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 384 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 400 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 432 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 448 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 496 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 512 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 464 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 560 'y': -16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 576 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 560 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 544 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 528 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 512 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 496 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 480 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 256 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 272 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 304 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 176 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 14 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 1 - - 1 - - 1 - x: 304 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 14 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 1 - - 1 - - 1 - x: 208 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 14 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 1 - - 1 - - 1 - x: 192 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 3 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 0 - - 1 - - 1 - x: 64 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 144 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 144 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 64 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 208 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 288 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 208 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 288 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 192 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 11 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 1 - - 1 - - 1 - x: 464 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 464 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 320 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 2 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 0 - - 1 - - 1 - x: 368 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 2 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 0 - - 1 - - 1 - x: 304 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 13 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 1 - - 1 - - 1 - x: 288 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 13 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 1 - - 1 - - 1 - x: 400 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 13 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 1 - - 1 - - 1 - x: 416 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 14 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 1 - - 1 - - 1 extends: cgroup: Solid hidden: false @@ -4742,1596 +5882,1994 @@ rooms: tiles: - x: 96 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 160 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 176 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 320 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 336 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 352 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 176 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 320 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 336 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 352 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 192 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 192 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 304 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 112 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 96 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 80 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 336 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 352 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 336 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 352 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 336 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 352 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 176 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 26 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 3 - - 1 - - 1 - x: 224 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 26 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 3 - - 1 - - 1 - x: 16 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 26 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 3 - - 1 - - 1 - x: 48 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 26 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 3 - - 1 - - 1 - x: 240 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 26 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 3 - - 1 - - 1 - x: 400 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 26 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 3 - - 1 - - 1 - x: 512 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 26 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 3 - - 1 - - 1 - x: 496 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 26 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 3 - - 1 - - 1 - x: 272 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 288 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 304 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 336 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 352 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 368 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 384 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 400 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 416 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 432 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 448 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 304 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 352 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 368 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 416 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 448 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 304 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 400 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 416 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 464 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 480 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 496 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 512 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 528 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 544 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 560 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 560 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 448 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 432 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 416 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 400 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 384 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 368 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 352 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 320 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 304 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 272 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 288 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 304 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 320 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 336 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 352 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 368 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 384 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 336 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 448 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 432 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 384 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 400 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 336 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 320 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 384 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 432 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 320 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 336 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 320 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 368 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 336 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 352 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 272 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 272 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 224 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 304 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 208 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 5 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 0 - - 1 - - 1 - x: 208 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 5 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 0 - - 1 - - 1 - x: 288 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 5 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 0 - - 1 - - 1 - x: 288 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 5 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 0 - - 1 - - 1 - x: 336 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 352 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 352 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 336 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 560 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 560 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 240 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 30 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 3 - - 1 - - 1 - x: 256 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 224 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 256 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 240 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 96 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 112 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 128 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 144 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 160 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 176 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 192 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 208 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 64 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 48 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 160 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 144 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 304 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 288 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 272 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 256 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 288 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 304 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 320 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 336 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 352 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 368 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 384 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 400 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 416 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 432 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 448 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 464 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 480 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 496 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 512 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 528 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 544 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 560 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 576 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 160 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 7 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 7 - - 0 - - 1 - - 1 - x: 560 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 15 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 7 - - 1 - - 1 - - 1 - x: 352 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 7 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 7 - - 0 - - 1 - - 1 - x: 224 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 240 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 256 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 272 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 272 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 20 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 2 - - 1 - - 1 - x: 288 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 20 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 2 - - 1 - - 1 - x: 272 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 28 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 3 - - 1 - - 1 - x: 288 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 28 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 3 - - 1 - - 1 - x: 272 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 28 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 3 - - 1 - - 1 - x: 288 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 29 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 5 - - 3 - - 1 - - 1 - x: 448 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 20 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 2 - - 1 - - 1 - x: 432 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 20 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 2 - - 1 - - 1 - x: 416 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 20 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 2 - - 1 - - 1 - x: 400 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 20 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 2 - - 1 - - 1 - x: 416 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 20 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 2 - - 1 - - 1 - - x: 336 - 'y': 128 + - x: 352 + 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: ae954867-1039-43f6-bf6d-d8684cb58612 - grid: - - 0 - - 0 - - 1 - - 1 extends: cgroup: Decor hidden: false @@ -6353,6 +7891,8 @@ rooms: }); eventKey: OnRoomStart type: room + simulate: true + isUi: ! '' - name: MainMenu oncreate: '' onstep: '' @@ -6364,41 +7904,62 @@ rooms: copies: - x: 120 'y': 116 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + rotation: 0 uid: 38f86691-8fcc-44e4-a201-8479ca64935f exts: {} + customProperties: {} - x: 32 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + rotation: 0 uid: ff0f561c-a96c-4205-a083-01d2a60ebb67 exts: {} + customProperties: {} tiles: - depth: -10 tiles: - - x: 16 - 'y': 16 + - x: 121 + 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: dc7b9662-c746-403c-9f3e-67425fd480a3 - grid: - - 0 - - 0 - - 1 - - 1 - - x: 62 - 'y': 151 + - x: 120 + 'y': 153 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 9a1f5e9c-16a8-4900-8991-fab3f42fa1fd - grid: - - 0 - - 0 - - 1 - - 1 extends: {} + hidden: false uid: eed8a427-1c70-425d-b633-7b6ea00c7b7c thumbnail: 7b6ea00c7b7c extends: {} gridX: 16 gridY: 16 backgroundColor: '#000337' - lastmod: 1656663135237 + lastmod: 1661000148459 events: [] type: room + simulate: true + isUi: ! '' - name: Level_03 onstep: '' ondraw: '' @@ -6410,2340 +7971,3338 @@ rooms: 'y': 64 uid: c40336ea-2846-45d5-97ba-d51852d3f6c5 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 112 'y': 64 uid: 469b9eef-6117-43a5-87d3-7151da7eba06 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 112 'y': 128 uid: c701a029-af09-4fcf-9712-0fcdaa3581be exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 104 'y': 192 uid: 36c82b3e-f5c1-4fff-b24a-417fa9509c96 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 120 'y': 192 uid: 36c82b3e-f5c1-4fff-b24a-417fa9509c96 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 112 'y': 224 uid: 469b9eef-6117-43a5-87d3-7151da7eba06 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 248 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 264 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 104 'y': 272 uid: 36c82b3e-f5c1-4fff-b24a-417fa9509c96 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 120 'y': 272 uid: 36c82b3e-f5c1-4fff-b24a-417fa9509c96 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 280 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 296 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 312 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 104 'y': 320 uid: 36c82b3e-f5c1-4fff-b24a-417fa9509c96 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 120 'y': 320 uid: 36c82b3e-f5c1-4fff-b24a-417fa9509c96 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 152 'y': 328 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 168 'y': 328 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 184 'y': 328 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 200 'y': 328 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 216 'y': 328 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 112 'y': 336 uid: 469b9eef-6117-43a5-87d3-7151da7eba06 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 136 'y': 344 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 232 'y': 344 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 136 'y': 360 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 232 'y': 360 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 181 'y': 362 uid: 3b6057ca-f175-4449-ac24-1c5630e0af7c exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 205 'y': 376 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 152 'y': 376 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 136 'y': 376 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 232 'y': 376 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 216 'y': 392 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 168 'y': 392 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 184 'y': 392 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 200 'y': 392 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 392 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 136 'y': 392 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 112 'y': 393 uid: 9fc1fec8-50bb-49e6-8fb4-c10ad589bf0b exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 408 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 104 'y': 416 uid: 36c82b3e-f5c1-4fff-b24a-417fa9509c96 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 120 'y': 416 uid: 36c82b3e-f5c1-4fff-b24a-417fa9509c96 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 424 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 88 'y': 440 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 440 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 456 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 120 'y': 464 uid: 36c82b3e-f5c1-4fff-b24a-417fa9509c96 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 472 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 136 'y': 472 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 488 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 120 'y': 488 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 72 'y': 504 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 136 'y': 504 uid: 0bc3c56b-3bf9-4912-9e3f-c27654c1b910 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 112 'y': 528 uid: 469b9eef-6117-43a5-87d3-7151da7eba06 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 115 'y': 574 uid: 9fc1fec8-50bb-49e6-8fb4-c10ad589bf0b exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 104 'y': 588 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 118 'y': 592 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 110 'y': 597 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 105 'y': 601 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 118 'y': 604 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 104 'y': 614 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 120 'y': 614 uid: 4e473462-8009-438e-9f53-1032e3a335cf exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 112 'y': 624 uid: c701a029-af09-4fcf-9712-0fcdaa3581be exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 113 'y': 636 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 58 'y': 643 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 122 'y': 648 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 90 'y': 659 uid: 5e40076f-8a61-4dfd-a517-a6c943ab9991 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 112 'y': 672 uid: 7fea051e-83e5-4e54-848b-ab3ca861e5cb exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 74 'y': 680 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 154 'y': 682 uid: 173dfb94-5133-4bbc-9e7d-6ed8c9fef330 exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 - x: 96 'y': 416 uid: b336d91c-2876-4686-b8b4-901045a86ac7 + exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 tiles: - depth: -6000 tiles: - x: 48 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 96 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 0 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 288 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 320 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 320 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 352 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 368 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 400 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 416 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 448 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 512 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 528 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 544 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 560 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 576 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 528 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 544 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 560 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 576 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 512 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 176 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 176 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 176 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 176 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 160 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 144 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 128 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 32 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 32 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 32 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 32 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 32 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 64 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 96 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 112 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 48 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 176 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 0 - - 1 - - 1 - x: 80 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 14 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 1 - - 1 - - 1 - x: 128 'y': 464 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 14 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 1 - - 1 - - 1 - x: 80 'y': 384 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 14 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 1 - - 1 - - 1 - x: 128 'y': 336 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 144 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 160 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 48 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 64 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 80 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 10 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 1 - - 1 - - 1 - x: 128 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 10 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 1 - - 1 - - 1 - x: 96 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 112 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 64 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 144 'y': 16 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 48 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 3 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 0 - - 1 - - 1 - x: 144 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 12 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 1 - - 1 - - 1 - x: 64 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 12 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 4 - - 1 - - 1 - - 1 - x: 48 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 64 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 144 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 160 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 19 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 2 - - 1 - - 1 - x: 48 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 64 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 80 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 112 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 128 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 144 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 160 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 96 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 14 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 1 - - 1 - - 1 - x: 80 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 11 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 1 - - 1 - - 1 - x: 128 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 160 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 48 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 - x: 80 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 2 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 0 - - 1 - - 1 - x: 128 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 2 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 0 - - 1 - - 1 - x: 128 'y': 432 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 3 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 0 - - 1 - - 1 - x: 80 'y': 336 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 3 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 3 - - 0 - - 1 - - 1 - x: 128 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 128 'y': 304 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 80 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 32 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 32 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 176 'y': 688 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 176 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 1 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 0 - - 1 - - 1 - x: 32 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 14 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 1 - - 1 - - 1 - x: 176 'y': 704 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 8 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 1 - - 1 - - 1 extends: cgroup: Solid - depth: -7000 tiles: - x: 208 'y': 352 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 192 'y': 368 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 192 'y': 352 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 208 'y': 368 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 176 'y': 368 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 160 'y': 352 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 176 'y': 352 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 96 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 112 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 112 'y': 432 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 96 'y': 480 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 112 'y': 560 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 96 'y': 544 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 24 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 3 - - 1 - - 1 - x: 96 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 112 'y': 592 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 96 'y': 576 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 112 'y': 576 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 96 'y': 560 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 112 'y': 544 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 112 'y': 528 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 496 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 464 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 448 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 464 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 416 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 400 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 416 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 96 'y': 400 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 96 'y': 512 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 96 'y': 528 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 512 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 160 'y': 368 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 176 'y': 384 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 192 'y': 384 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 208 'y': 384 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 112 'y': 480 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 96 'y': 432 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 18 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 2 - - 2 - - 1 - - 1 - x: 96 'y': 288 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 304 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 320 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 336 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 352 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 368 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 384 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 272 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 288 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 336 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 352 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 368 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 384 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 192 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 240 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 256 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 208 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 112 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 96 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 32 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 112 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 112 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 96 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 16 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 0 - - 2 - - 1 - - 1 - x: 112 'y': 224 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 96 'y': 176 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 144 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 80 'y': 80 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 80 'y': 48 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 64 'y': 64 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 112 'y': 304 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 112 'y': 320 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 25 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 3 - - 1 - - 1 - x: 96 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 608 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 160 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 160 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 160 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 160 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 48 'y': 672 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 48 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 48 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 48 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 144 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 128 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 112 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 80 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 48 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 656 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 64 'y': 640 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 17 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 1 - - 2 - - 1 - - 1 - x: 96 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 112 'y': 144 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 96 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - x: 112 'y': 160 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 22 + rotation: 0 texture: 4f8a6124-427b-410b-a467-ac42afec3644 - grid: - - 6 - - 2 - - 1 - - 1 - - x: 96 - 'y': 112 + - x: 112 + 'y': 128 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: ae954867-1039-43f6-bf6d-d8684cb58612 - grid: - - 0 - - 0 - - 1 - - 1 - - x: 96 - 'y': 608 + - x: 112 + 'y': 624 + opacity: 1 + tint: 16777215 + scale: + x: 1 + 'y': 1 + frame: 0 + rotation: 0 texture: ae954867-1039-43f6-bf6d-d8684cb58612 - grid: - - 0 - - 0 - - 1 - - 1 extends: cgroup: Decor uid: ae6bb30c-9ad9-4390-b632-127493eb58cf @@ -8774,6 +11333,8 @@ rooms: } eventKey: OnRoomEnd type: room + simulate: true + isUi: ! '' - name: UI_InGame onstep: '' onleave: '' @@ -8784,14 +11345,21 @@ rooms: - x: 96 'y': 88 uid: ff0f561c-a96c-4205-a083-01d2a60ebb67 + exts: {} + customProperties: {} + opacity: 1 + tint: 16777215 + rotation: 0 + scale: + x: 1 + 'y': 1 tiles: - depth: -10 tiles: [] extends: {} uid: ee8b4583-614a-44d6-aacc-6569f49f6b80 thumbnail: 6569f49f6b80 - extends: - isUi: true + extends: {} gridX: 8 gridY: 8 backgroundColor: '#000337' @@ -8819,6 +11387,8 @@ rooms: code: this.hearts.width = 16 * ct.room.lives; eventKey: OnDraw type: room + simulate: true + isUi: true actions: - name: MoveX methods: diff --git a/src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png} b/src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png similarity index 100% rename from src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png} rename to src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png diff --git a/src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png}_prev.png b/src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png_prev.png similarity index 100% rename from src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png}_prev.png rename to src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png_prev.png diff --git a/src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png}_prev@2.png b/src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png_prev@2.png similarity index 100% rename from src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png}_prev@2.png rename to src/examples/DungeonCrawler_tutorial/img/i14d3f53f-a868-4820-84cd-63234ab6e6b9.png_prev@2.png diff --git a/src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png} b/src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png similarity index 100% rename from src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png} rename to src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png diff --git a/src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png}_prev.png b/src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png_prev.png similarity index 100% rename from src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png}_prev.png rename to src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png_prev.png diff --git a/src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png}_prev@2.png b/src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png_prev@2.png similarity index 100% rename from src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png}_prev@2.png rename to src/examples/DungeonCrawler_tutorial/img/ia42e0dff-69c9-46c3-8996-745aaa769698.png_prev@2.png diff --git a/src/examples/DungeonCrawler_tutorial/img/r127493eb58cf.png b/src/examples/DungeonCrawler_tutorial/img/r127493eb58cf.png deleted file mode 100644 index 8749b9745..000000000 Binary files a/src/examples/DungeonCrawler_tutorial/img/r127493eb58cf.png and /dev/null differ diff --git a/src/examples/DungeonCrawler_tutorial/img/r1cd7a8ef-febb-4174-ad90-42846504b06f.png b/src/examples/DungeonCrawler_tutorial/img/r1cd7a8ef-febb-4174-ad90-42846504b06f.png new file mode 100644 index 000000000..f6cd9432a Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/r1cd7a8ef-febb-4174-ad90-42846504b06f.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/r1cd7a8ef-febb-4174-ad90-42846504b06f@r.png b/src/examples/DungeonCrawler_tutorial/img/r1cd7a8ef-febb-4174-ad90-42846504b06f@r.png new file mode 100644 index 000000000..f6cce5a41 Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/r1cd7a8ef-febb-4174-ad90-42846504b06f@r.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/r326893eb-b9c2-4616-aaee-bb4641e0423e.png b/src/examples/DungeonCrawler_tutorial/img/r326893eb-b9c2-4616-aaee-bb4641e0423e.png new file mode 100644 index 000000000..ed46f03b9 Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/r326893eb-b9c2-4616-aaee-bb4641e0423e.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/r326893eb-b9c2-4616-aaee-bb4641e0423e@r.png b/src/examples/DungeonCrawler_tutorial/img/r326893eb-b9c2-4616-aaee-bb4641e0423e@r.png new file mode 100644 index 000000000..0c795276f Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/r326893eb-b9c2-4616-aaee-bb4641e0423e@r.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/r342f55fa6324.png b/src/examples/DungeonCrawler_tutorial/img/r342f55fa6324.png deleted file mode 100644 index 8749b9745..000000000 Binary files a/src/examples/DungeonCrawler_tutorial/img/r342f55fa6324.png and /dev/null differ diff --git a/src/examples/DungeonCrawler_tutorial/img/r3786dc4c7d4c.png b/src/examples/DungeonCrawler_tutorial/img/r3786dc4c7d4c.png deleted file mode 100644 index 8749b9745..000000000 Binary files a/src/examples/DungeonCrawler_tutorial/img/r3786dc4c7d4c.png and /dev/null differ diff --git a/src/examples/DungeonCrawler_tutorial/img/r42846504b06f.png b/src/examples/DungeonCrawler_tutorial/img/r42846504b06f.png deleted file mode 100644 index 39a24d49b..000000000 Binary files a/src/examples/DungeonCrawler_tutorial/img/r42846504b06f.png and /dev/null differ diff --git a/src/examples/DungeonCrawler_tutorial/img/r6569f49f6b80.png b/src/examples/DungeonCrawler_tutorial/img/r6569f49f6b80.png deleted file mode 100644 index d09582a51..000000000 Binary files a/src/examples/DungeonCrawler_tutorial/img/r6569f49f6b80.png and /dev/null differ diff --git a/src/examples/DungeonCrawler_tutorial/img/r7b6ea00c7b7c.png b/src/examples/DungeonCrawler_tutorial/img/r7b6ea00c7b7c.png deleted file mode 100644 index 97d86c767..000000000 Binary files a/src/examples/DungeonCrawler_tutorial/img/r7b6ea00c7b7c.png and /dev/null differ diff --git a/src/examples/DungeonCrawler_tutorial/img/rae6bb30c-9ad9-4390-b632-127493eb58cf.png b/src/examples/DungeonCrawler_tutorial/img/rae6bb30c-9ad9-4390-b632-127493eb58cf.png new file mode 100644 index 000000000..0a55fb81c Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/rae6bb30c-9ad9-4390-b632-127493eb58cf.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/rae6bb30c-9ad9-4390-b632-127493eb58cf@r.png b/src/examples/DungeonCrawler_tutorial/img/rae6bb30c-9ad9-4390-b632-127493eb58cf@r.png new file mode 100644 index 000000000..d6730ecc9 Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/rae6bb30c-9ad9-4390-b632-127493eb58cf@r.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/rbb4641e0423e.png b/src/examples/DungeonCrawler_tutorial/img/rbb4641e0423e.png deleted file mode 100644 index 29bb94e95..000000000 Binary files a/src/examples/DungeonCrawler_tutorial/img/rbb4641e0423e.png and /dev/null differ diff --git a/src/examples/DungeonCrawler_tutorial/img/ree8b4583-614a-44d6-aacc-6569f49f6b80.png b/src/examples/DungeonCrawler_tutorial/img/ree8b4583-614a-44d6-aacc-6569f49f6b80.png new file mode 100644 index 000000000..71018e25a Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/ree8b4583-614a-44d6-aacc-6569f49f6b80.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/ree8b4583-614a-44d6-aacc-6569f49f6b80@r.png b/src/examples/DungeonCrawler_tutorial/img/ree8b4583-614a-44d6-aacc-6569f49f6b80@r.png new file mode 100644 index 000000000..0a886df3c Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/ree8b4583-614a-44d6-aacc-6569f49f6b80@r.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/reed8a427-1c70-425d-b633-7b6ea00c7b7c.png b/src/examples/DungeonCrawler_tutorial/img/reed8a427-1c70-425d-b633-7b6ea00c7b7c.png new file mode 100644 index 000000000..492948a81 Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/reed8a427-1c70-425d-b633-7b6ea00c7b7c.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/reed8a427-1c70-425d-b633-7b6ea00c7b7c@r.png b/src/examples/DungeonCrawler_tutorial/img/reed8a427-1c70-425d-b633-7b6ea00c7b7c@r.png new file mode 100644 index 000000000..6615020cd Binary files /dev/null and b/src/examples/DungeonCrawler_tutorial/img/reed8a427-1c70-425d-b633-7b6ea00c7b7c@r.png differ diff --git a/src/examples/DungeonCrawler_tutorial/img/splash.png b/src/examples/DungeonCrawler_tutorial/img/splash.png index ce0daeb29..6615020cd 100644 Binary files a/src/examples/DungeonCrawler_tutorial/img/splash.png and b/src/examples/DungeonCrawler_tutorial/img/splash.png differ diff --git a/src/icons/cursor.svg b/src/icons/cursor.svg new file mode 100644 index 000000000..56102beca --- /dev/null +++ b/src/icons/cursor.svg @@ -0,0 +1,2 @@ + + diff --git a/src/icons/lock.svg b/src/icons/lock.svg new file mode 100644 index 000000000..de09d9db3 --- /dev/null +++ b/src/icons/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/redo.svg b/src/icons/redo.svg new file mode 100644 index 000000000..996380f66 --- /dev/null +++ b/src/icons/redo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/icons/undo.svg b/src/icons/undo.svg new file mode 100644 index 000000000..4a23bbdef --- /dev/null +++ b/src/icons/undo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/icons/unlock.svg b/src/icons/unlock.svg new file mode 100644 index 000000000..01dc35973 --- /dev/null +++ b/src/icons/unlock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/js/3rdparty/alertify.js b/src/js/3rdparty/alertify.js index c5329270e..d2d0d0222 100644 --- a/src/js/3rdparty/alertify.js +++ b/src/js/3rdparty/alertify.js @@ -395,6 +395,7 @@ _alertify.injectCSS(); + const {soundbox} = require('./data/node_requires/3rdparty/soundbox'); return { _$$alertify: _alertify, parent(elem) { diff --git a/src/js/3rdparty/soundbox.js b/src/js/3rdparty/soundbox.js deleted file mode 100644 index 6148b18e4..000000000 --- a/src/js/3rdparty/soundbox.js +++ /dev/null @@ -1,11 +0,0 @@ -(function () { - 'use strict';class SoundBox{constructor(){this.sounds={};this.instances=[];this.default_volume=1}load(a,b,d){this.sounds[a]=new Audio(b);if("function"==typeof d)this.sounds[a].addEventListener("canplaythrough",d);else return new Promise((c,b)=>{this.sounds[a].addEventListener("canplaythrough",c);this.sounds[a].addEventListener("error",b)})}remove(a){"undefined"!=typeof this.sounds&&delete this.sounds[a]}play(a,b,d=null){if("undefined"==typeof this.sounds[a])return console.error("Can't find sound called '"+ -a+"'."),!1;var c=this.sounds[a].cloneNode(!0);c.volume=d||this.default_volume;c.play();this.instances.push(c);c.addEventListener("ended",()=>{let a=this.instances.indexOf(c);-1!=a&&this.instances.splice(a,1)});return"function"==typeof b?(c.addEventListener("ended",b),!0):new Promise((a,b)=>c.addEventListener("ended",a))}stop_all(){let a=this.instances.slice();for(let b of a)b.pause(),b.dispatchEvent(new Event("ended"))}}SoundBox.version="0.3.4"; - - const soundbox = new SoundBox(); - - soundbox.load('Success', 'data/Success.wav'); - soundbox.load('Failure', 'data/Failure.wav'); - - window.soundbox = soundbox; -})(); diff --git a/src/js/addMigrationMethod.js b/src/js/addMigrationMethod.js new file mode 100644 index 000000000..134239a42 --- /dev/null +++ b/src/js/addMigrationMethod.js @@ -0,0 +1,11 @@ +(function addMigrationMethod(window) { + window.migrationProcess = window.migrationProcess || []; + window.applyMigrationCode = function applyMigrationCode(version) { + const process = window.migrationProcess.find(process => process.version === version); + if (!process) { + throw new Error(`Cannot find migration code for version ${version}`); + } + process.process(global.currentProject) + .then(() => window.alertify.success(`Applied migration code for version ${version}`, 'success', 3000)); + }; +})(this); diff --git a/src/js/loadProject.js b/src/js/loadProject.js deleted file mode 100644 index e012ae300..000000000 --- a/src/js/loadProject.js +++ /dev/null @@ -1,239 +0,0 @@ -(function addLoadProjectMethod(window) { - window.migrationProcess = window.migrationProcess || []; - window.applyMigrationCode = function applyMigrationCode(version) { - const process = window.migrationProcess.find(process => process.version === version); - if (!process) { - throw new Error(`Cannot find migration code for version ${version}`); - } - process.process(global.currentProject) - .then(() => window.alertify.success(`Applied migration code for version ${version}`, 'success', 3000)); - }; - - const fs = require('fs-extra'), - path = require('path'); - // @see https://semver.org/ - const semverRegex = /(\d+)\.(\d+)\.(\d+)(-[A-Za-z.-]*(\d+)?[A-Za-z.-]*)?/; - const semverToArray = string => { - const raw = semverRegex.exec(string); - return [ - raw[1], - raw[2], - raw[3], - // -next- versions and other postfixes will count as a fourth component. - // They all will apply before regular versions - raw[4] ? raw[5] || 1 : null - ]; - }; - - /** - * Applies migration scripts to older projects. - */ - var adapter = async project => { - var version = semverToArray(project.ctjsVersion || '0.2.0'); - - const migrationToExecute = window.migrationProcess - // Sort all the patches chronologically - .sort((m1, m2) => { - const m1Version = semverToArray(m1.version); - const m2Version = semverToArray(m2.version); - - for (let i = 0; i < 4; i++) { - if (m1Version[i] < m2Version[i] || m1Version[i] === null) { - return -1; - } else if (m1Version[i] > m2Version[i]) { - return 1; - } - } - // eslint-disable-next-line no-console - console.warn(`Two equivalent versions found for migration, ${m1.version} and ${m2.version}.`); - return 0; - }) - // Throw out patches for current and previous versions - .filter((migration) => { - const migrationVersion = semverToArray(migration.version); - for (let i = 0; i < 3; i++) { - // if any of the first three version numbers is lower than project's, - // skip the patch - if (migrationVersion[i] < version[i]) { - return false; - } - if (migrationVersion[i] > version[i]) { - return true; - } - } - // a lazy check for equal base versions - if (migrationVersion.slice(0, 3).join('.') === version.slice(0, 3).join('.')) { - // handle the case with two postfixed versions - if (migrationVersion[3] !== null && version[3] !== null) { - return migrationVersion[3] > version[3]; - } - // postfixed source, unpostfixed patch - if (migrationVersion[3] === null && version[3] !== null) { - return true; - } - return false; - } - return true; - }); - - if (migrationToExecute.length) { - // eslint-disable-next-line no-console - console.debug(`Applying migration sequence: patches ${migrationToExecute.map(m => m.version).join(', ')}.`); - } - for (const migration of migrationToExecute) { - // eslint-disable-next-line no-console - console.debug(`Migrating project from version ${project.ctjsVersion || '0.2.0'} to ${migration.version}`); - // We do need to apply updates in a sequence - // eslint-disable-next-line no-await-in-loop - await migration.process(project); - } - - // Unfortunately, recent versions of eslint give false positives on this line - // @see https://github.com/eslint/eslint/issues/11900 - // @see https://github.com/eslint/eslint/issues/11899 - // eslint-disable-next-line require-atomic-updates - project.ctjsVersion = process.versions.ctjs; - }; - - /** - * Opens the project and refreshes the whole app. - * - * @param {Object} projectData Loaded JSON file, in js object form - * @returns {void} - */ - var loadProject = async projectData => { - const glob = require('./data/node_requires/glob'); - global.currentProject = projectData; - window.alertify.log(window.languageJSON.intro.loadingProject); - glob.modified = false; - - try { - await adapter(projectData); - fs.ensureDir(global.projdir); - fs.ensureDir(global.projdir + '/img'); - fs.ensureDir(global.projdir + '/snd'); - - const lastProjects = localStorage.lastProjects ? localStorage.lastProjects.split(';') : []; - if (lastProjects.indexOf(path.normalize(global.projdir + '.ict')) !== -1) { - lastProjects.splice(lastProjects.indexOf(path.normalize(global.projdir + '.ict')), 1); - } - lastProjects.unshift(path.normalize(global.projdir + '.ict')); - if (lastProjects.length > 15) { - lastProjects.pop(); - } - localStorage.lastProjects = lastProjects.join(';'); - - if (global.currentProject.settings.title) { - document.title = global.currentProject.settings.title + ' — ct.js'; - } - - glob.scriptTypings = {}; - for (const script of global.currentProject.scripts) { - glob.scriptTypings[script.name] = [ - monaco.languages.typescript.javascriptDefaults.addExtraLib(script.code), - monaco.languages.typescript.typescriptDefaults.addExtraLib(script.code) - ]; - } - - const {loadAllTypedefs, resetTypedefs} = require('./data/node_requires/resources/modules/typedefs'); - resetTypedefs(); - loadAllTypedefs(); - - const {unloadAllEvents, loadAllModulesEvents} = require('./data/node_requires/events'); - unloadAllEvents(); - await loadAllModulesEvents(); - - window.signals.trigger('projectLoaded'); - setTimeout(() => { - window.riot.update(); - }, 0); - } catch (err) { - window.alertify.alert(window.languageJSON.intro.loadingProjectError + err); - } - }; - - /** - * Checks file format and loads it - * - * @param {String} proj The path to the file. - * @returns {void} - */ - var loadProjectFile = async proj => { - const textProjData = await fs.readFile(proj, 'utf8'); - let projectData; - // Before v1.3, projects were stored in JSON format - try { - if (textProjData.indexOf('{') === 0) { // First, make a silly check for JSON files - projectData = JSON.parse(textProjData); - } else { - try { - const YAML = require('js-yaml'); - projectData = YAML.load(textProjData); - } catch (e) { - // whoopsie, wrong window - // eslint-disable-next-line no-console - console.warn(`Tried to load a file ${proj} as a YAML, but got an error (see below). Falling back to JSON.`); - console.error(e); - projectData = JSON.parse(textProjData); - } - } - } catch (e) { - window.alertify.error(e); - throw e; - } - if (!projectData) { - window.alertify.error(window.languageJSON.common.wrongFormat); - return; - } - try { - loadProject(projectData); - } catch (e) { - window.alertify.error(e); - throw e; - } - }; - - window.loadProject = async proj => { - if (!proj) { - const baseMessage = 'An attempt to open a project with an empty path.'; - alertify.error(baseMessage + ' See the console for the call stack.'); - const err = new Error(baseMessage); - throw err; - } - sessionStorage.projname = path.basename(proj); - global.projdir = path.dirname(proj) + path.sep + path.basename(proj, '.ict'); - - let recoveryStat; - try { - recoveryStat = await fs.stat(proj + '.recovery'); - } catch (err) { - // no recovery file found - void 0; - } - if (recoveryStat && recoveryStat.isFile()) { - const targetStat = await fs.stat(proj); - const voc = window.languageJSON.intro.recovery; - const userResponse = await window.alertify - .okBtn(voc.loadRecovery) - .cancelBtn(voc.loadTarget) - /* {0} — target file date - {1} — target file state (newer/older) - {2} — recovery file date - {3} — recovery file state (newer/older) - */ - .confirm(voc.message - .replace('{0}', targetStat.mtime.toLocaleString()) - .replace('{1}', targetStat.mtime < recoveryStat.mtime ? voc.older : voc.newer) - .replace('{2}', recoveryStat.mtime.toLocaleString()) - .replace('{3}', recoveryStat.mtime < targetStat.mtime ? voc.older : voc.newer)); - window.alertify - .okBtn(window.languageJSON.common.ok) - .cancelBtn(window.languageJSON.common.cancel); - if (userResponse.buttonClicked === 'ok') { - return loadProjectFile(proj + '.recovery'); - } - return loadProjectFile(proj); - } - return loadProjectFile(proj); - }; -})(this); diff --git a/src/js/projectMigrationScripts/2.1.0.js b/src/js/projectMigrationScripts/2.1.0.js index 6f2e915f2..d6ed06abf 100644 --- a/src/js/projectMigrationScripts/2.1.0.js +++ b/src/js/projectMigrationScripts/2.1.0.js @@ -2,6 +2,7 @@ window.migrationProcess = window.migrationProcess || []; window.migrationProcess.push({ version: '2.1.0', + // eslint-disable-next-line max-lines-per-function process: project => new Promise(resolve => { const templateEventMap = { oncreate: 'OnCreate', @@ -36,6 +37,7 @@ window.migrationProcess.push({ } } for (const room of project.rooms) { + room.simulate = true; if (!room.events) { room.events = []; for (const key in roomEventMap) { @@ -52,6 +54,40 @@ window.migrationProcess.push({ } room.type = 'room'; } + // Break up tile chunks into individual tiles + for (const room of project.rooms) { + for (const layer of room.tiles) { + layer.tiles = layer.tiles.reduce((tiles, tile) => { + const tex = project.textures.find(t => t.uid === tile.texture); + if (!tile.grid) { + tiles.push(tile); + return tiles; + } + // grid: [tileX, tileY, tileSpanX, tileSpanY] + // eslint-disable-next-line prefer-destructuring + for (let x = tile.grid[0]; x < tile.grid[0] + tile.grid[2]; x++) { + // eslint-disable-next-line prefer-destructuring + for (let y = tile.grid[1]; y < tile.grid[1] + tile.grid[3]; y++) { + tiles.push({ + x: tile.x + tex.axis[0], + y: tile.y + tex.axis[1], + opacity: tile.opacity ?? 1, + tint: tile.tint ?? 0xffffff, + scale: { + x: tile.scale?.x ?? 1, + y: tile.scale?.y ?? 1 + }, + frame: x + y * tex.grid[0], + rotation: tile.rotation ?? 0, + texture: tile.texture + }); + } + } + delete tile.grid; + return tiles; + }, []); + } + } resolve(); }) }); diff --git a/src/js/projectMigrationScripts/2.2.0.js b/src/js/projectMigrationScripts/2.2.0.js new file mode 100644 index 000000000..f8d8c2117 --- /dev/null +++ b/src/js/projectMigrationScripts/2.2.0.js @@ -0,0 +1,24 @@ +window.migrationProcess = window.migrationProcess || []; + +window.migrationProcess.push({ + version: '2.2.0', + // eslint-disable-next-line max-lines-per-function + process: project => new Promise(resolve => { + for (const room of project.rooms) { + room.isUi = room.extends.isUi; + delete room.extends.isUi; + for (const copy of room.copies) { + copy.exts = copy.exts ?? {}; + copy.customProperties = copy.customProperties ?? {}; + copy.opacity = copy.opacity ?? 1; + copy.tint = copy.tint ?? 0xffffff; + copy.rotation = copy.rotation ?? 0; + copy.scale = copy.scale ?? { + x: 1, + y: 1 + }; + } + } + resolve(); + }) +}); diff --git a/src/node_requires/3rdparty/soundbox.ts b/src/node_requires/3rdparty/soundbox.ts new file mode 100644 index 000000000..9633cd912 --- /dev/null +++ b/src/node_requires/3rdparty/soundbox.ts @@ -0,0 +1,55 @@ +'use strict'; + +type knownSoundNames = 'Success' | 'Failure' | 'Wood_Start' | 'Wood_End'; + +class SoundBox { + static version: string; + sounds: Record = {}; + instances: HTMLAudioElement[] = []; + default_volume = 1; + get mute() { + return localStorage.disableSounds === 'on'; + } + load(name: knownSoundNames, url: string, callback?: (this: HTMLAudioElement, ev: Event) => any) { + this.sounds[name] = new Audio(url); + if ("function" == typeof callback) { + this.sounds[name].addEventListener("canplaythrough", callback); + } else { + return new Promise((c, b) => { + this.sounds[name].addEventListener("canplaythrough", c); + this.sounds[name].addEventListener("error", b) + }) + } + } + remove(name: knownSoundNames) { + "undefined" != typeof this.sounds && delete this.sounds[name] + } + play(name: knownSoundNames, callback?: (this: HTMLAudioElement, ev: Event) => any, volume?: number) { + if (this.mute) { + return; + } + if ("undefined" == typeof this.sounds[name]) { + return console.error(`Can't find sound called '${name}'.`), false; + } + var audioTag = this.sounds[name].cloneNode(true) as HTMLAudioElement; + audioTag.volume = volume ?? this.default_volume; + audioTag.play(); + this.instances.push(audioTag); + audioTag.addEventListener("ended", () => { + let a = this.instances.indexOf(audioTag); - 1 != a && this.instances.splice(a, 1) + }); + return "function" == typeof callback ? (audioTag.addEventListener("ended", callback), !0) : new Promise((a, b) => audioTag.addEventListener("ended", a)) + } + stop_all() { + let a = this.instances.slice(); + for (let b of a) b.pause(), b.dispatchEvent(new Event("ended")) + } +} +SoundBox.version = "0.3.4"; + +export const soundbox = new SoundBox(); + +soundbox.load('Success', 'data/Success.wav'); +soundbox.load('Failure', 'data/Failure.wav'); +soundbox.load('Wood_Start', 'data/Wood_Start.wav'); +soundbox.load('Wood_End', 'data/Wood_End.wav'); diff --git a/src/node_requires/IMenuItem.d.ts b/src/node_requires/IMenuItem.d.ts index 14634b731..91bfca164 100644 --- a/src/node_requires/IMenuItem.d.ts +++ b/src/node_requires/IMenuItem.d.ts @@ -46,11 +46,14 @@ declare interface IMenuItem { submenu?: IMenu, /** - * E.g. 'Control+c' + * E.g. 'Control+c'. Binds the element to hotkey.js library. */ hotkey?: string, /** * A human-readable variant, e.g. 'Ctrl+C'. Fallbacks to `hotkey`. + * If set without `hotkey`, still shows the label but does not + * bind the element to the hotkey.js library. + * The label is wrapped into (parentheses) automatically. */ hotkeyLabel?: string } diff --git a/src/node_requires/events/index.ts b/src/node_requires/events/index.ts index 33097c8da..2422f9e09 100644 --- a/src/node_requires/events/index.ts +++ b/src/node_requires/events/index.ts @@ -278,7 +278,7 @@ const unloadAllEvents = (): void => { } }; -export = { +export { categories, events, bakeCategories, diff --git a/src/node_requires/exporter/index.ts b/src/node_requires/exporter/index.ts index 7417b171f..a123d06e4 100644 --- a/src/node_requires/exporter/index.ts +++ b/src/node_requires/exporter/index.ts @@ -205,6 +205,7 @@ const exportCtProject = async ( projdir: string, production: boolean ): Promise => { + window.signals.trigger('exportProject'); currentProject = project; await removeBrokenModules(project); resetEventsCache(); diff --git a/src/node_requires/exporter/rooms.ts b/src/node_requires/exporter/rooms.ts index c3aee985b..d45477774 100644 --- a/src/node_requires/exporter/rooms.ts +++ b/src/node_requires/exporter/rooms.ts @@ -40,8 +40,16 @@ interface IExportedTile { x: number, y: number, width: number, - height: number + height: number, + opacity: number, + rotation: number, + scale: { + x: number, + y: number + }, + tint: number } +type ExportedCopy = Omit & {template: string}; // eslint-disable-next-line max-lines-per-function const stringifyRooms = (proj: IProject): IScriptablesFragment => { @@ -52,12 +60,14 @@ const stringifyRooms = (proj: IProject): IScriptablesFragment => { let rootRoomOnLeave = ''; for (const r of proj.rooms) { - const roomCopy = JSON.parse(JSON.stringify(r.copies)); - const objs = []; - for (const copy of roomCopy) { - copy.template = proj.templates[glob.templatemap[copy.uid]].name; - delete copy.uid; - objs.push(copy); + const objs: ExportedCopy[] = []; + for (const copy of r.copies) { + const exportableCopy = { + ...copy, + template: proj.templates[glob.templatemap[copy.uid]].name + }; + delete exportableCopy.uid; + objs.push(exportableCopy); } const bgsCopy = JSON.parse(JSON.stringify(r.backgrounds)); for (const bg in bgsCopy) { @@ -75,19 +85,22 @@ const stringifyRooms = (proj: IProject): IScriptablesFragment => { extends: tileLayer.extends ? getUnwrappedExtends(tileLayer.extends) : {} }; for (const tile of tileLayer.tiles) { - for (let x = 0; x < tile.grid[2]; x++) { - for (let y = 0; y < tile.grid[3]; y++) { - const texture = glob.texturemap[tile.texture].g; - layer.tiles.push({ - texture: texture.name, - frame: tile.grid[0] + x + (y + tile.grid[1]) * texture.grid[0], - x: tile.x + x * (texture.width + texture.marginx), - y: tile.y + y * (texture.height + texture.marginy), - width: texture.width, - height: texture.height - }); - } - } + const texture = glob.texturemap[tile.texture].g; + layer.tiles.push({ + texture: texture.name, + frame: tile.frame, + x: tile.x, + y: tile.y, + width: texture.width, + height: texture.height, + opacity: tile.opacity, + rotation: tile.rotation, + scale: { + x: tile.scale.x, + y: tile.scale.y + }, + tint: tile.tint + }); } tileLayers.push(layer); } @@ -119,6 +132,7 @@ ct.rooms.templates['${r.name}'] = { onCreate() { ${scriptableCode.thisOnCreate} }, + isUi: ${r.isUi}, extends: ${r.extends ? JSON.stringify(getUnwrappedExtends(r.extends), null, 4) : '{}'} } `; diff --git a/src/node_requires/exporter/templates.ts b/src/node_requires/exporter/templates.ts index db84234ec..960b09440 100644 --- a/src/node_requires/exporter/templates.ts +++ b/src/node_requires/exporter/templates.ts @@ -17,6 +17,7 @@ const stringifyTemplates = function (proj: IProject): IScriptablesFragment { ct.templates.templates["${template.name}"] = { depth: ${template.depth}, blendMode: PIXI.BLEND_MODES.${template.blendMode?.toUpperCase() ?? 'NORMAL'}, + animationFPS: ${template.animationFPS ?? 60}, playAnimationOnStart: ${Boolean(template.playAnimationOnStart)}, loopAnimation: ${Boolean(template.loopAnimation)}, ${template.texture !== -1 ? 'texture: "' + getTextureFromId(template.texture).name + '",' : ''} diff --git a/src/node_requires/extendGlobals.d.ts b/src/node_requires/extendGlobals.d.ts index 794d1b480..d832d8f3e 100644 --- a/src/node_requires/extendGlobals.d.ts +++ b/src/node_requires/extendGlobals.d.ts @@ -11,6 +11,8 @@ declare global { var monaco: any; var currentProject: IProject; var projdir: string; + var migrationProcess: any[]; + var riot: any; function showOpenDialog(options: any): Promise; function showSaveDialog(options: any): Promise; interface Window { diff --git a/src/node_requires/hotkeys.js b/src/node_requires/hotkeys.js index 116be69a0..057fb9000 100644 --- a/src/node_requires/hotkeys.js +++ b/src/node_requires/hotkeys.js @@ -12,6 +12,13 @@ on key presses in a declarative way, based on HTML markup plus a couple of scopi On each key press, the lib queries the document for the resulting key combination. The selector is `[data-hotkey="Your-Code"]` +Your-Code is in form `Ctrl+Alt+Meta+X`, where X is a pressed letter. +If you need the Shift key modifier, the letter will be an uppercase one. +If not, use the lowercase letter. +The code for Shift+S is just `S`, the code for just S key is `s`. +Every modifier is optional. Examples: `l`, `5`, `Ctrl+1`, `Ctrl+C`. +Do follow the order of modifiers: `Ctrl+Alt+c` is valid, but `Alt+Ctrl+c` is not. + You can narrow the scope of the query by calling `hotkey.push(scope)`. (See more methods below.) The scope is nested, forming a stack. If a scope is specified, it is read from the most recently added part to the outer scope, trying to call the elements that are inside a scoped parent. @@ -67,6 +74,7 @@ const hotkeyRef = Symbol('hotkey'); class Hotkeys { constructor(doc) { + this.isFormField = isFormField; this.document = doc; this.document[hotkeyRef] = this; this.scopeStack = []; @@ -74,8 +82,12 @@ class Hotkeys { this[offDomEventsRef] = new Map(); this[listenerRef] = e => { + // Ignore key events when typing into form fields + if (isFormField(e.target)) { + return; + } const code = getCode(e); - this.trigger(code); + this.trigger(code, e); }; this.document.body.addEventListener('keydown', this[listenerRef]); } @@ -98,12 +110,13 @@ class Hotkeys { this[offDomEventsRef].set(code, []); } } - trigger(code) { + trigger(code, event) { const offDom = this[offDomEventsRef].get(code); if (offDom) { for (const event of offDom) { event(); } + event.preventDefault(); } // querySelectorAll returns a NodeList, which is not a sortable array. Convert by spreading. @@ -128,6 +141,7 @@ class Hotkeys { } else { elt.click(); } + event.preventDefault(); return; } } @@ -143,6 +157,7 @@ class Hotkeys { } else { elt.click(); } + event.preventDefault(); return; } } diff --git a/src/node_requires/resources/emitterTandems/index.ts b/src/node_requires/resources/emitterTandems/index.ts index 885ba209e..0237ec0ac 100644 --- a/src/node_requires/resources/emitterTandems/index.ts +++ b/src/node_requires/resources/emitterTandems/index.ts @@ -2,7 +2,7 @@ const getThumbnail = function getThumbnail(): string { return 'sparkles'; }; const getById = function getById(id: string): ITandem { - const tandem = (window as Window).currentProject.tandems.find((t: ITandem) => t.uid === id); + const tandem = (window as Window).currentProject.emitterTandems.find((t: ITandem) => t.uid === id); if (!tandem) { throw new Error(`Attempt to get a non-existent tandem with ID ${id}`); } diff --git a/src/node_requires/resources/modules/typedefs.ts b/src/node_requires/resources/modules/typedefs.ts index 7dbfd07a1..074afa003 100644 --- a/src/node_requires/resources/modules/typedefs.ts +++ b/src/node_requires/resources/modules/typedefs.ts @@ -55,7 +55,7 @@ const resetTypedefs = function resetTypedefs(): void { } }; -export = { +export { addTypedefs, removeTypedefs, loadAllTypedefs, diff --git a/src/node_requires/resources/projects/IProject.d.ts b/src/node_requires/resources/projects/IProject.d.ts index ba44fc28e..c0d420fd5 100644 --- a/src/node_requires/resources/projects/IProject.d.ts +++ b/src/node_requires/resources/projects/IProject.d.ts @@ -3,8 +3,44 @@ declare interface IProject { templates: ITemplate[]; sounds: ISound[]; rooms: IRoom[]; - tandems: ITandem[]; + emitterTandems: ITandem[]; fonts: IFont[]; styles: IStyle[]; + settings: { + authoring: { + author: string, + site: string, + title: string + version: [number, number, number], + versionPostfix: string, + appId: string + }, + rendering: { + usePixiLegacy: boolean, + maxFPS: 60, + pixelatedrender: boolean, + highDensity: boolean, + desktopMode: 'maximized' | 'fullscreen' | 'windowed', + hideCursor: boolean, + mobileScreenOrientation: 'unspecified' | 'landscape' | 'portrait' + }, + export: { + windows: boolean, + linux: boolean, + mac: boolean, + functionWrap: boolean, + codeModifier: 'none' | 'minify' | 'obfuscate' + }, + branding: { + icon: assetRef, + /** A hex color */ + accent: string, + invertPreloaderScheme: boolean, + splashScreen: assetRef, + forceSmoothIcons: boolean, + forceSmoothSplashScreen: boolean, + hideLoadingLogo: boolean + } + }; [key: string]: any; } diff --git a/src/node_requires/resources/projects/defaultProject.js b/src/node_requires/resources/projects/defaultProject.ts similarity index 75% rename from src/node_requires/resources/projects/defaultProject.js rename to src/node_requires/resources/projects/defaultProject.ts index d41306d80..a8a23f089 100644 --- a/src/node_requires/resources/projects/defaultProject.js +++ b/src/node_requires/resources/projects/defaultProject.ts @@ -1,4 +1,4 @@ -const defaultProjectTemplate = { +const defaultProjectTemplate: IProject = { ctjsVersion: process.versions.ctjs, notes: '/* empty */', libs: { @@ -19,6 +19,7 @@ const defaultProjectTemplate = { templates: [], sounds: [], styles: [], + fonts: [], rooms: [], actions: [], emitterTandems: [], @@ -40,14 +41,17 @@ const defaultProjectTemplate = { site: '', title: '', version: [0, 0, 0], - versionPostfix: '' + versionPostfix: '', + appId: '' }, rendering: { usePixiLegacy: true, maxFPS: 60, pixelatedrender: false, highDensity: true, - desktopMode: 'maximized' + desktopMode: 'maximized', + hideCursor: false, + mobileScreenOrientation: 'unspecified' }, export: { windows: true, @@ -57,15 +61,19 @@ const defaultProjectTemplate = { codeModifier: 'none' }, branding: { - icon: -1, accent: '#446adb', // ct.js' crystal blue - invertPreloaderScheme: true + invertPreloaderScheme: true, + icon: -1, + splashScreen: -1, + forceSmoothIcons: false, + forceSmoothSplashScreen: false, + hideLoadingLogo: false } } }; module.exports = { - get() { - return JSON.parse(JSON.stringify(defaultProjectTemplate)); + get(): IProject { + return JSON.parse(JSON.stringify(defaultProjectTemplate)) as IProject; } }; diff --git a/src/node_requires/resources/projects/index.ts b/src/node_requires/resources/projects/index.ts index 3323e1893..c4484b45e 100644 --- a/src/node_requires/resources/projects/index.ts +++ b/src/node_requires/resources/projects/index.ts @@ -1,3 +1,250 @@ +import {populatePixiTextureCache, setPixelart} from '../textures'; +import {loadAllTypedefs, resetTypedefs} from '../modules/typedefs'; +import {unloadAllEvents, loadAllModulesEvents} from '../../events'; +import * as path from 'path'; + +const fs = require('fs-extra'); + +// @see https://semver.org/ +const semverRegex = /(\d+)\.(\d+)\.(\d+)(-[A-Za-z.-]*(\d+)?[A-Za-z.-]*)?/; +const semverToArray = (string: string) => { + const raw = semverRegex.exec(string); + return [ + raw[1], + raw[2], + raw[3], + // -next- versions and other postfixes will count as a fourth component. + // They all will apply before regular versions + raw[4] ? raw[5] || 1 : null + ]; +}; + +interface IMigrationPlan { + version: string, + process: (project: Partial) => Promise; +} + +/** +* Applies migration scripts to older projects. +*/ +const adapter = async (project: Partial) => { + var version = semverToArray(project.ctjsVersion || '0.2.0'); + + const migrationToExecute = window.migrationProcess + // Sort all the patches chronologically + .sort((m1: IMigrationPlan, m2: IMigrationPlan) => { + const m1Version = semverToArray(m1.version); + const m2Version = semverToArray(m2.version); + + for (let i = 0; i < 4; i++) { + if (m1Version[i] < m2Version[i] || m1Version[i] === null) { + return -1; + } else if (m1Version[i] > m2Version[i]) { + return 1; + } + } + // eslint-disable-next-line no-console + console.warn(`Two equivalent versions found for migration, ${m1.version} and ${m2.version}.`); + return 0; + }) + // Throw out patches for current and previous versions + .filter((migration: IMigrationPlan) => { + const migrationVersion = semverToArray(migration.version); + for (let i = 0; i < 3; i++) { + // if any of the first three version numbers is lower than project's, + // skip the patch + if (migrationVersion[i] < version[i]) { + return false; + } + if (migrationVersion[i] > version[i]) { + return true; + } + } + // a lazy check for equal base versions + if (migrationVersion.slice(0, 3).join('.') === version.slice(0, 3).join('.')) { + // handle the case with two postfixed versions + if (migrationVersion[3] !== null && version[3] !== null) { + return migrationVersion[3] > version[3]; + } + // postfixed source, unpostfixed patch + if (migrationVersion[3] === null && version[3] !== null) { + return true; + } + return false; + } + return true; + }); + + if (migrationToExecute.length) { + // eslint-disable-next-line no-console + console.debug(`Applying migration sequence: patches ${migrationToExecute.map((m: IMigrationPlan) => m.version).join(', ')}.`); + } + for (const migration of migrationToExecute) { + // eslint-disable-next-line no-console + console.debug(`Migrating project from version ${project.ctjsVersion || '0.2.0'} to ${migration.version}`); + // We do need to apply updates in a sequence + // eslint-disable-next-line no-await-in-loop + await migration.process(project); + } + + // Unfortunately, recent versions of eslint give false positives on this line + // @see https://github.com/eslint/eslint/issues/11900 + // @see https://github.com/eslint/eslint/issues/11899 + // eslint-disable-next-line require-atomic-updates + project.ctjsVersion = process.versions.ctjs; +}; + +/** +* Opens the project and refreshes the whole app. +* +* @param {IProject} projectData Loaded JSON file, in js object form +* @returns {Promise} +*/ +const loadProject = async (projectData: IProject): Promise => { + const glob = require('../../glob'); + window.currentProject = projectData; + window.alertify.log(window.languageJSON.intro.loadingProject); + glob.modified = false; + + try { + await adapter(projectData); + fs.ensureDir(global.projdir); + fs.ensureDir(global.projdir + '/img'); + fs.ensureDir(global.projdir + '/snd'); + + const lastProjects = localStorage.lastProjects ? localStorage.lastProjects.split(';') : []; + if (lastProjects.indexOf(path.normalize(global.projdir + '.ict')) !== -1) { + lastProjects.splice(lastProjects.indexOf(path.normalize(global.projdir + '.ict')), 1); + } + lastProjects.unshift(path.normalize(global.projdir + '.ict')); + if (lastProjects.length > 15) { + lastProjects.pop(); + } + localStorage.lastProjects = lastProjects.join(';'); + + if (window.currentProject.settings.authoring.title) { + document.title = window.currentProject.settings.authoring.title + ' — ct.js'; + } + + glob.scriptTypings = {}; + for (const script of window.currentProject.scripts) { + glob.scriptTypings[script.name] = [ + monaco.languages.typescript.javascriptDefaults.addExtraLib(script.code), + monaco.languages.typescript.typescriptDefaults.addExtraLib(script.code) + ]; + } + + resetTypedefs(); + loadAllTypedefs(); + + unloadAllEvents(); + setPixelart(projectData.settings.rendering.pixelatedrender); + await Promise.all([ + loadAllModulesEvents(), + populatePixiTextureCache(projectData) + ]); + + window.signals.trigger('projectLoaded'); + setTimeout(() => { + window.riot.update(); + }, 0); + } catch (err) { + window.alertify.alert(window.languageJSON.intro.loadingProjectError + err); + } +}; + +/** +* Checks file format and loads it +* +* @param {String} proj The path to the file. +* @returns {void} +*/ +const readProjectFile = async (proj: string) => { + const textProjData = await fs.readFile(proj, 'utf8'); + let projectData; + // Before v1.3, projects were stored in JSON format + try { + if (textProjData.indexOf('{') === 0) { // First, make a silly check for JSON files + projectData = JSON.parse(textProjData); + } else { + try { + const YAML = require('js-yaml'); + projectData = YAML.load(textProjData); + } catch (e) { + // whoopsie, wrong window + // eslint-disable-next-line no-console + console.warn(`Tried to load a file ${proj} as a YAML, but got an error (see below). Falling back to JSON.`); + console.error(e); + projectData = JSON.parse(textProjData); + } + } + } catch (e) { + window.alertify.error(e); + throw e; + } + if (!projectData) { + window.alertify.error(window.languageJSON.common.wrongFormat); + return; + } + try { + loadProject(projectData); + } catch (e) { + window.alertify.error(e); + throw e; + } +}; + +/** + * Opens a project file, checking for recovery files and asking + * a user about them if needed. + * This is the method that should be used for opening ct.js projects + * from within UI. + */ +const openProject = async (proj: string): Promise => { + if (!proj) { + const baseMessage = 'An attempt to open a project with an empty path.'; + alertify.error(baseMessage + ' See the console for the call stack.'); + const err = new Error(baseMessage); + throw err; + } + sessionStorage.projname = path.basename(proj); + global.projdir = path.dirname(proj) + path.sep + path.basename(proj, '.ict'); + + // Check for recovery files + let recoveryStat; + try { + recoveryStat = await fs.stat(proj + '.recovery'); + } catch (err) { + // no recovery file found + void 0; + } + if (recoveryStat && recoveryStat.isFile()) { + const targetStat = await fs.stat(proj); + const voc = window.languageJSON.intro.recovery; + const userResponse = await window.alertify + .okBtn(voc.loadRecovery) + .cancelBtn(voc.loadTarget) + /* {0} — target file date + {1} — target file state (newer/older) + {2} — recovery file date + {3} — recovery file state (newer/older) + */ + .confirm(voc.message + .replace('{0}', targetStat.mtime.toLocaleString()) + .replace('{1}', targetStat.mtime < recoveryStat.mtime ? voc.older : voc.newer) + .replace('{2}', recoveryStat.mtime.toLocaleString()) + .replace('{3}', recoveryStat.mtime < targetStat.mtime ? voc.older : voc.newer)); + window.alertify + .okBtn(window.languageJSON.common.ok) + .cancelBtn(window.languageJSON.common.cancel); + if (userResponse.buttonClicked === 'ok') { + return readProjectFile(proj + '.recovery'); + } + return readProjectFile(proj); + } + return readProjectFile(proj); +}; + const defaultProject = require('./defaultProject'); /** @@ -55,7 +302,7 @@ const getProjectDir = function (projPath: string): string { * @param {string} projPath * @param {boolean} [fs] Whether to return a filesystem path (true) or a URL (false; default). */ -const getProjectThumbnail = function (projPath: string, fs?: boolean) { +const getProjectThumbnail = function (projPath: string, fs?: boolean): string { const path = require('path'); projPath = getProjectDir(projPath); if (fs) { @@ -77,6 +324,7 @@ const getProjectIct = function (projPath: string): string { }; export { + openProject, defaultProject, getDefaultProjectDir, getProjectThumbnail, diff --git a/src/node_requires/resources/rooms/IRoom.d.ts b/src/node_requires/resources/rooms/IRoom.d.ts index 5ad1b0330..8dbd837f3 100644 --- a/src/node_requires/resources/rooms/IRoom.d.ts +++ b/src/node_requires/resources/rooms/IRoom.d.ts @@ -3,31 +3,45 @@ type canvasPatternRepeat = 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat'; interface IRoomBackground { depth: number, texture: assetRef, - extends: { - parallaxX?: number, - parallaxY?: number, - shiftX?: number, - shiftY?: number, - repeat: canvasPatternRepeat - [key: string]: unknown - } + parallaxX: number, + parallaxY: number, + shiftX: number, + shiftY: number, + movementX: number, + movementY: number, + scaleX: number, + scaleY: number, + repeat: canvasPatternRepeat } interface IRoomCopy { x: number, y: number, - uid: assetRef, - tx?: number, - ty?: number, + uid: string, + scale: { + x: number, + y: number + }, + rotation?: number, + tint?: number, + opacity?: number, exts: { [key: string]: unknown - } + }, + customProperties: Record } interface ITileTemplate { x: number; y: number; - grid: number[]; + opacity: number; + tint: number; + frame: number; + scale: { + x: number, + y: number + }; + rotation: number; texture: string; } @@ -35,24 +49,28 @@ interface ITileLayerTemplate { depth: number; tiles: Array, extends?: Record + hidden?: boolean; } interface IRoom extends IScriptable { - width: number, - height: number, + width: number; + height: number; /** A CSS color */ - backgroundColor: string, - backgrounds: Array, - copies: Array, - tiles: Array - gridX: number, - gridY: number, - restrictCamera?: boolean, - restrictMinX?: number, - restrictMinY?: number, - restrictMaxX?: number, - restrictMaxY?: number, + backgroundColor: string; + backgrounds: Array; + copies: Array; + tiles: Array; + gridX: number; + gridY: number; + diagonalGrid: boolean; + simulate: boolean; + restrictCamera?: boolean; + restrictMinX?: number; + restrictMinY?: number; + restrictMaxX?: number; + restrictMaxY?: number; + isUi: boolean; extends: { [key: string]: unknown - } + }; } diff --git a/src/node_requires/resources/rooms/defaultRoom.ts b/src/node_requires/resources/rooms/defaultRoom.ts index b5373a7b7..1a1320c9d 100644 --- a/src/node_requires/resources/rooms/defaultRoom.ts +++ b/src/node_requires/resources/rooms/defaultRoom.ts @@ -8,8 +8,11 @@ const room = { onleave: '', gridX: 64, gridY: 64, + diagonalGrid: false, + simulate: true, width: 1280, - height: 720 + height: 720, + isUi: false }; const get = function (): IRoom { diff --git a/src/node_requires/resources/rooms/index.ts b/src/node_requires/resources/rooms/index.ts index ec035a573..85d9460d9 100644 --- a/src/node_requires/resources/rooms/index.ts +++ b/src/node_requires/resources/rooms/index.ts @@ -1,3 +1,5 @@ +import {outputCanvasToFile} from '../../utils/imageUtils'; + const getDefaultRoom = require('./defaultRoom').get; const fs = require('fs-extra'); const path = require('path'); @@ -31,7 +33,6 @@ const getById = getRoomFromId; * Retrieves the full path to a thumbnail of a given room. * @param {string|IRoom} room Either the id of the room, or its ct.js object * @param {boolean} [x2] If set to true, returns a 340x256 image instead of 64x64. - * (Not implemented, actually!) * @param {boolean} [fs] If set to true, returns a file system path, not a URI. * @returns {string} The full path to the thumbnail. */ @@ -44,16 +45,39 @@ const getRoomPreview = (room: assetRef | IRoom, x2: boolean, fs: boolean): strin room = getRoomFromId(room); } if (fs) { - return `${(global as any).projdir}/img/r${room.uid}.png`; + return `${(global as any).projdir}/img/r${room.uid}${x2 ? '@r' : ''}.png`; } - return `file://${(global as any).projdir}/img/r${room.uid}.png?${room.lastmod}`; + return `file://${(global as any).projdir}/img/r${room.uid}${x2 ? '@r' : ''}.png?${room.lastmod}`; }; const getThumbnail = getRoomPreview; +const writeRoomPreview = ( + room: assetRef | IRoom, + canvas: HTMLCanvasElement, + x2: boolean +): Promise | Promise => { + if (typeof room === 'number') { + throw new Error('Cannot write a room preview for a room -1'); + } + if (typeof room === 'string') { + room = getRoomFromId(room); + } + const path = `${(global as any).projdir}/img/r${room.uid}${x2 ? '@r' : ''}.png`; + if (x2) { + const splash = `${(global as any).projdir}/img/splash.png`; + return Promise.all([ + outputCanvasToFile(canvas, path), + outputCanvasToFile(canvas, splash) + ]); + } + return outputCanvasToFile(canvas, path); +}; + export { createNewRoom, getRoomFromId, getById, getRoomPreview, - getThumbnail + getThumbnail, + writeRoomPreview }; diff --git a/src/node_requires/resources/templates/ITemplate.d.ts b/src/node_requires/resources/templates/ITemplate.d.ts index 0a0923480..415fb4448 100644 --- a/src/node_requires/resources/templates/ITemplate.d.ts +++ b/src/node_requires/resources/templates/ITemplate.d.ts @@ -7,6 +7,7 @@ interface ITemplate extends IScriptable { blendMode?: PixiBlendMode, playAnimationOnStart: boolean, loopAnimation: boolean, + animationFPS: number, extends: { [key: string]: unknown } diff --git a/src/node_requires/resources/templates/defaultTemplate.ts b/src/node_requires/resources/templates/defaultTemplate.ts index 24a410f3f..59a0a81de 100644 --- a/src/node_requires/resources/templates/defaultTemplate.ts +++ b/src/node_requires/resources/templates/defaultTemplate.ts @@ -7,6 +7,7 @@ const defaultTemplate = { texture: -1 as assetRef, playAnimationOnStart: false, loopAnimation: true, + animationFPS: 30, visible: true }; diff --git a/src/node_requires/resources/templates/index.ts b/src/node_requires/resources/templates/index.ts index 9df58908e..70ecb62b7 100644 --- a/src/node_requires/resources/templates/index.ts +++ b/src/node_requires/resources/templates/index.ts @@ -1,4 +1,5 @@ const getDefaultTemplate = require('./defaultTemplate').get; +import {getPixiTexture as getTexturePixiTexture} from '../textures'; const createNewTemplate = function createNewTemplate(name: string): ITemplate { const template = getDefaultTemplate(); @@ -47,11 +48,22 @@ const getTemplatePreview = function getTemplatePreview( }; const getThumbnail = getTemplatePreview; +const getPixiTexture = (template: ITemplate | assetRef): PIXI.Texture[] => { + if (typeof template === 'string') { + template = getTemplateFromId(template); + } + if (template === -1) { + throw new Error('Cannot work with -1 assetRefs'); + } + return getTexturePixiTexture(template.texture, void 0, false); +}; + export { getDefaultTemplate, getTemplateFromId, getById, getTemplatePreview, getThumbnail, - createNewTemplate + createNewTemplate, + getPixiTexture }; diff --git a/src/node_requires/resources/textures/ITexture.d.ts b/src/node_requires/resources/textures/ITexture.d.ts index e78d9c212..c02c29149 100644 --- a/src/node_requires/resources/textures/ITexture.d.ts +++ b/src/node_requires/resources/textures/ITexture.d.ts @@ -3,6 +3,7 @@ interface ITexture extends IAsset { uid: string; name: string; origname: string; + /* Number of columns and rows, accordigly */ grid: [number, number]; axis: [number, number]; width: number; @@ -23,4 +24,5 @@ interface ITexture extends IAsset { top?: number; bottom?: number; tiled?: boolean; + ignoreTiledUse?: boolean; } diff --git a/src/node_requires/resources/textures/index.ts b/src/node_requires/resources/textures/index.ts index abaef5b4a..8c7b43b00 100644 --- a/src/node_requires/resources/textures/index.ts +++ b/src/node_requires/resources/textures/index.ts @@ -71,6 +71,7 @@ const baseTextureFromTexture = (texture: ITexture): Promise => }); }); +const unknownTexture = PIXI.Texture.from('data/img/unknown.png'); const pixiTextureCache: Record} An array of PIXI.Textures */ @@ -108,11 +111,20 @@ const texturesFromCtTexture = async function (tex: ITexture) { } } } + pixiTextureCache[tex.name] = { + lastmod: tex.lastmod, + texture: frames + }; return frames; }; - -let defaultTexture: PIXI.Texture; - +const populatePixiTextureCache = async (project: IProject): Promise => { + clearPixiTextureCache(); + const promises = []; + for (const texture of project.textures) { + promises.push(texturesFromCtTexture(texture)); + } + await Promise.all(promises); +}; // async const getDOMImage = function ( texture: assetRef | ITexture, @@ -136,24 +148,36 @@ const getDOMImage = function ( }; /** - * @param {string|-1|any} texture Either a uid of a texture, or a ct.js texture object + * @param {assetRef | ITexture} texture Either a uid of a texture, or a ct.js texture object * @param {number} [frame] The frame to extract. If not defined, will return an array of all frames * @param {boolean} [allowMinusOne] Allows the use of `-1` as a texture uid * @returns {Array|PIXI.Texture} An array of textures, or an individual one. */ -const getPixiTexture = async function ( +function getPixiTexture(texture: assetRef | ITexture): PIXI.Texture[]; +function getPixiTexture( + texture: -1 +): never; +function getPixiTexture( texture: assetRef | ITexture, - frame?: number, + frame: void | null, allowMinusOne?: boolean -): Promise { +): PIXI.Texture[]; +function getPixiTexture( + texture: assetRef | ITexture, + frame: number, + allowMinusOne?: boolean +): PIXI.Texture; +// eslint-disable-next-line func-style +function getPixiTexture( + texture: assetRef | ITexture, + frame?: number | void | null, + allowMinusOne?: boolean +): PIXI.Texture | PIXI.Texture[] { if (allowMinusOne && texture === -1) { - if (!defaultTexture) { - defaultTexture = PIXI.Texture.from('data/img/unknown.png'); - } if (frame || frame === 0) { - return defaultTexture; + return unknownTexture; } - return [defaultTexture]; + return [unknownTexture]; } if (texture === -1) { throw new Error('Cannot turn -1 into a pixi texture in getPixiTexture method unless it is explicitly allowed.'); @@ -161,24 +185,11 @@ const getPixiTexture = async function ( if (typeof texture === 'string') { texture = getTextureFromId(texture); } - const {uid} = texture; - if (!pixiTextureCache[uid] || - pixiTextureCache[uid].lastmod !== texture.lastmod - ) { - const tex = await texturesFromCtTexture(texture); - // Everything is constant, and the key gets overridden. - // Where's the race condition? False positive?? - // eslint-disable-next-line require-atomic-updates - pixiTextureCache[uid] = { - lastmod: texture.lastmod, - texture: tex - }; - } if (frame || frame === 0) { - return pixiTextureCache[uid].texture[frame]; + return pixiTextureCache[texture.name].texture[frame]; } - return pixiTextureCache[uid].texture; -}; + return pixiTextureCache[texture.name].texture; +} /** * Returns a texture object by its name. @@ -330,7 +341,13 @@ const importImageToTexture = async ( const getCleanTextureName = (name: string): string => name.replace(isBgPostfixTester, '').replace(texturePostfixParser, ''); -const getTexturePivot = (texture: string | ITexture, inPixels?: boolean): [number, number] => { +const getTexturePivot = (texture: assetRef | ITexture, inPixels?: boolean): [number, number] => { + if (texture === -1) { + if (inPixels) { + return [16, 16]; + } + return [0.5, 0.5]; + } if (typeof texture === 'string') { texture = getTextureFromId(texture); } @@ -340,8 +357,18 @@ const getTexturePivot = (texture: string | ITexture, inPixels?: boolean): [numbe return [texture.axis[0] / texture.width, texture.axis[1] / texture.height]; }; +const setPixelart = (pixelart: boolean): void => { + PIXI.settings.SCALE_MODE = pixelart ? + PIXI.SCALE_MODES.NEAREST : + PIXI.SCALE_MODES.LINEAR; + for (const tex in pixiTextureCache) { + pixiTextureCache[tex].texture[0].baseTexture.scaleMode = PIXI.settings.SCALE_MODE; + } +}; + export { clearPixiTextureCache, + populatePixiTextureCache, getTextureFromId, getById, getTextureFromName, @@ -350,6 +377,8 @@ export { getThumbnail, getTexturePivot, getTextureOrig, + texturesFromCtTexture as updatePixiTexture, + setPixelart, getPixiTexture, getDOMImage, importImageToTexture, diff --git a/src/node_requires/roomEditor/IRoomEditorRiotTag.d.ts b/src/node_requires/roomEditor/IRoomEditorRiotTag.d.ts new file mode 100644 index 000000000..9d7b9f6b6 --- /dev/null +++ b/src/node_requires/roomEditor/IRoomEditorRiotTag.d.ts @@ -0,0 +1,39 @@ +import {TileLayer} from './entityClasses/TileLayer'; + +import {ITilePatch} from './interactions/tiles/ITilePatch'; +import {RoomEditor} from '.'; + +type tool = 'select' | 'addCopies' | 'addTiles' | 'manageBackgrounds' | 'roomProperties'; + +export interface IRoomEditorRiotTag { + update(): void; + tilePatch: ITilePatch; + currentTileLayer: TileLayer; + opts: { + room: IRoom; + }; + refs: { + propertiesPanel?: { + updatePropList(): void; + applyChanges(): void; + update(): void; + }, + tileEditor?: { + update(): void; + }, + backgroundsEditor?: { + update(): void; + }, + zoomLabel: HTMLSpanElement + }; + root: HTMLElement; + pixiEditor: RoomEditor; + zoom: number; + gridOn: boolean; + freePlacementMode: boolean; + controlMode: boolean; + currentTool: tool; + currentTemplate: ITemplate | -1; + setTool(tool: tool): () => void; + changeSelectedTemplate(templateId: string): void; +} diff --git a/src/node_requires/roomEditor/common.ts b/src/node_requires/roomEditor/common.ts new file mode 100644 index 000000000..ead9589f5 --- /dev/null +++ b/src/node_requires/roomEditor/common.ts @@ -0,0 +1,116 @@ +interface ISimplePoint { + x: number; + y: number; +} + +export const defaultTextStyle = new PIXI.TextStyle({ + dropShadow: true, + dropShadowDistance: 2, + dropShadowAlpha: 0.35, + fill: '#fff', + fontFamily: '\'Open Sans\',sans-serif,serif', + fontSize: 16 +}); + +/** Converts global coordinates to coordinates on a diagonal grid */ +export const toDiagonal = (pos: ISimplePoint, gridX: number, gridY: number): ISimplePoint => ({ + x: pos.y / gridY + pos.x / gridX, + y: -pos.x / gridX + pos.y / gridY +}); +/** Converts coordinates on a diagonal grid to global coordinates */ +export const fromDiagonal = (pos: ISimplePoint, gridX: number, gridY: number): ISimplePoint => ({ + x: 0.5 * gridX * (pos.x - pos.y), + y: 0.5 * gridY * (pos.x + pos.y) +}); +/** Converts global coordinates to coordinates on a rectangular grid */ +export const toRectangular = (pos: ISimplePoint, gridX: number, gridY: number): ISimplePoint => ({ + x: pos.x / gridX, + y: pos.y / gridY +}); +/** Converts coordinates on a rectangular grid to global coordinates */ +export const fromRectangular = (pos: ISimplePoint, gridX: number, gridY: number): ISimplePoint => ({ + x: pos.x * gridX, + y: pos.y * gridY +}); + +export const snapToDiagonalGrid = ( + pos: ISimplePoint, + gridX: number, + gridY: number +): ISimplePoint => { + const diag = toDiagonal({ + x: pos.x, + y: pos.y + }, gridX, gridY); + return fromDiagonal({ + x: Math.round(diag.x), + y: Math.round(diag.y) + }, gridX, gridY); +}; +export const snapToRectangularGrid = ( + pos: ISimplePoint, + gridX: number, + gridY: number +): ISimplePoint => ({ + x: Math.round(pos.x / gridX) * gridX, + y: Math.round(pos.y / gridY) * gridY +}); + +export const eraseCursor = 'url("data/cursorErase.svg") 1 1, default'; +export const rotateCursor = 'url("data/cursorRotate.svg") 12 12, pointer'; + +/** + * Six filters that recolor any DisplayObject to one of the primary+secondary colors. + * The colors: + * + * 0: Red + * 1: Yellow + * 2: Green + * 3: Aqua + * 4: Blue + * 5: Magenta + */ +export const recolorFilters: PIXI.filters.ColorMatrixFilter[] = []; +for (let i = 0; i < 6; i++) { + const filter = new PIXI.filters.ColorMatrixFilter(); + recolorFilters.push(filter); +} +/* eslint-disable array-element-newline, no-underscore-dangle, no-multi-spaces */ +recolorFilters[0]._loadMatrix([ + 1, 1, 1, 0, 0, + 0.1, 0.1, 0.1, 0, 0, + 0.1, 0.1, 0.1, 0, 0, + 0, 0, 0, 1, 0 +], false); +recolorFilters[1]._loadMatrix([ + 0.5, 0.5, 0.5, 0, 0, + 0.5, 0.5, 0.5, 0, 0, + 0.1, 0.1, 0.1, 0, 0, + 0, 0, 0, 1, 0 +], false); +recolorFilters[2]._loadMatrix([ + 0.1, 0.1, 0.1, 0, 0, + 1, 1, 1, 0, 0, + 0.1, 0.1, 0.1, 0, 0, + 0, 0, 0, 1, 0 +], false); +recolorFilters[3]._loadMatrix([ + 0.1, 0.1, 0.1, 0, 0, + 0.5, 0.5, 0.5, 0, 0, + 0.5, 0.5, 0.5, 0, 0, + 0, 0, 0, 1, 0 +], false); +recolorFilters[4]._loadMatrix([ + 0.1, 0.1, 0.1, 0, 0, + 0.1, 0.1, 0.1, 0, 0, + 1, 1, 1, 0, 0, + 0, 0, 0, 1, 0 +], false); +recolorFilters[5]._loadMatrix([ + 0.5, 0.5, 0.5, 0, 0, + 0.1, 0.1, 0.1, 0, 0, + 0.5, 0.5, 0.5, 0, 0, + 0, 0, 0, 1, 0 +], false); +/* eslint-enable array-element-newline, no-underscore-dangle, no-multi-spaces*/ + diff --git a/src/node_requires/roomEditor/entityClasses/Background.ts b/src/node_requires/roomEditor/entityClasses/Background.ts new file mode 100644 index 000000000..e48586aae --- /dev/null +++ b/src/node_requires/roomEditor/entityClasses/Background.ts @@ -0,0 +1,125 @@ +import {getPixiTexture} from '../../resources/textures'; +import {RoomEditor} from '..'; + +class Background extends PIXI.TilingSprite { + bgTexture: assetRef; + editor: RoomEditor; + shiftX = 0; + shiftY = 0; + parallaxX = 1; + parallaxY = 1; + movementX = 0; + movementY = 0; + simulatedMovedX = 0; + simulatedMovedY = 0; + repeat: canvasPatternRepeat = 'repeat'; + + constructor(bgInfo: IRoomBackground, editor: RoomEditor) { + super(getPixiTexture(bgInfo.texture, 0, true)); + this.anchor.x = this.anchor.y = 0; + this.editor = editor; + this.deserialize(bgInfo); + this.tick(0); + } + destroy(): void { + const ind = this.editor.backgrounds.indexOf(this); + if (ind !== -1) { + this.editor.backgrounds.splice(ind, 1); + this.editor.riotEditor.refs.backgroundsEditor.update(); + } + super.destroy(); + } + detach(): this { + const ind = this.editor.backgrounds.indexOf(this); + if (ind === -1) { + throw new Error('Attempt to detach an off-screen background'); + } + this.editor.backgrounds.splice(ind, 1); + this.parent.removeChild(this); + this.editor.riotEditor.refs.backgroundsEditor.update(); + return this; + } + restore(): this { + this.editor.backgrounds.push(this); + this.editor.room.addChild(this); + this.editor.riotEditor.refs.backgroundsEditor.update(); + return this; + } + + changeTexture(id: assetRef): void { + this.texture = getPixiTexture(id, 0, true); + this.anchor.x = this.anchor.y = 0; + this.bgTexture = id; + } + + serialize(): IRoomBackground { + return { + depth: this.zIndex, + texture: this.bgTexture as string, + shiftX: this.shiftX, + shiftY: this.shiftY, + parallaxX: this.parallaxX, + parallaxY: this.parallaxY, + movementX: this.movementX, + movementY: this.movementY, + scaleX: this.tileScale.x, + scaleY: this.tileScale.y, + repeat: this.repeat + }; + } + deserialize(bg: IRoomBackground): void { + this.zIndex = bg.depth; + this.bgTexture = bg.texture; + this.shiftX = bg.shiftX; + this.shiftY = bg.shiftY; + this.parallaxX = bg.parallaxX; + this.parallaxY = bg.parallaxY; + this.movementX = bg.movementX; + this.movementY = bg.movementY; + this.tileScale.set(bg.scaleX, bg.scaleY); + this.repeat = bg.repeat; + } + refreshTexture(): void { + this.texture = getPixiTexture(this.bgTexture, 0); + this.anchor.x = this.anchor.y = 0; + } + tick(deltaTime: number): void { + const {camera, screen} = this.editor; + const cameraBounds = { + x: camera.x - screen.width / 2 * camera.scale.x, + y: camera.y - screen.height / 2 * camera.scale.y, + width: screen.width * camera.scale.x, + height: screen.height * camera.scale.y + }; + if (this.movementX === 0) { + this.simulatedMovedX = 0; + } + if (this.movementY === 0) { + this.simulatedMovedY = 0; + } + if (deltaTime > 0) { + this.simulatedMovedX += deltaTime * this.movementX; + this.simulatedMovedY += deltaTime * this.movementY; + } + if (this.repeat !== 'repeat-x' && this.repeat !== 'no-repeat') { + this.y = cameraBounds.y; + this.tilePosition.y = -this.y * this.parallaxY + this.shiftY + this.simulatedMovedY; + this.height = cameraBounds.height + 1; + } else { + this.y = this.shiftY + this.simulatedMovedY + cameraBounds.y * (this.parallaxY - 1); + this.height = this.texture.height * this.tileScale.y; + this.tilePosition.y = 0; + } + if (this.repeat !== 'repeat-y' && this.repeat !== 'no-repeat') { + this.x = cameraBounds.x; + this.tilePosition.x = -this.x * this.parallaxX + this.shiftX + this.simulatedMovedX; + this.width = cameraBounds.width + 1; + } else { + this.x = this.shiftX + this.simulatedMovedX + cameraBounds.x * (this.parallaxX - 1); + this.width = this.texture.width * this.tileScale.x; + this.tilePosition.x = 0; + } + } +} + +export {Background}; diff --git a/src/node_requires/roomEditor/entityClasses/Copy.ts b/src/node_requires/roomEditor/entityClasses/Copy.ts new file mode 100644 index 000000000..cb53bab6c --- /dev/null +++ b/src/node_requires/roomEditor/entityClasses/Copy.ts @@ -0,0 +1,104 @@ +import {RoomEditor} from '..'; +import {getPixiTexture, getTemplateFromId} from '../../resources/templates'; +import {getTexturePivot} from '../../resources/textures'; + +/** + * @notice This class automatically adds and removes itself from editor's copy list + */ +class Copy extends PIXI.AnimatedSprite { + templateId: string; + copyExts: Record; + copyCustomProps: Record; + cachedTemplate: ITemplate; + isGhost: boolean; + editor: RoomEditor; + autoUpdate: boolean; + update: (deltaTime: number) => void; + + constructor(copyInfo: IRoomCopy, editor: RoomEditor, isGhost?: boolean) { + super(getPixiTexture(copyInfo.uid)); + this.editor = editor; + this.templateId = copyInfo.uid; + this.autoUpdate = false; + const t = getTemplateFromId(this.templateId as string); + this.cachedTemplate = t; + this.deserialize(copyInfo); + this.isGhost = Boolean(isGhost); + this.interactive = !this.isGhost; + if (t.playAnimationOnStart) { + this.play(); + } + if (!this.isGhost) { + editor.copies.add(this); + } + } + destroy(): void { + if (!this.isGhost) { + this.editor.copies.delete(this); + } + super.destroy(); + } + detach(): this { + this.editor.copies.delete(this); + this.editor.room.removeChild(this); + return this; + } + restore(): this { + this.editor.copies.add(this); + this.editor.room.addChild(this); + return this; + } + + get animated(): boolean { + return getTemplateFromId(this.templateId as string).playAnimationOnStart; + } + + serialize(): IRoomCopy { + return { + x: this.x, + y: this.y, + opacity: this.alpha, + tint: this.tint, + scale: { + x: this.scale.x, + y: this.scale.y + }, + rotation: this.rotation, + uid: this.templateId, + exts: this.copyExts, + customProperties: this.copyCustomProps + }; + } + deserialize(copy: IRoomCopy): void { + this.x = copy.x; + this.y = copy.y; + this.zIndex = this.cachedTemplate.depth; + this.alpha = copy.opacity ?? 1; + this.tint = copy.tint ?? 0xffffff; + this.scale.x = copy.scale?.x ?? 1; + this.scale.y = copy.scale?.y ?? 1; + this.rotation = copy.rotation ?? 0; + this.templateId = copy.uid; + this.copyExts = copy.exts ?? {}; + this.copyCustomProps = copy.customProperties ?? {}; + const t = getTemplateFromId(this.templateId as string); + this.animationSpeed = t.animationFPS / 60; + this.loop = t.loopAnimation ?? true; + if (t.texture !== -1) { + [this.anchor.x, this.anchor.y] = getTexturePivot(t.texture); + } else { + this.anchor.x = this.anchor.y = 0.5; + } + } + refreshTexture(): void { + const t = this.cachedTemplate; + this.textures = getPixiTexture(t); + if (t.texture !== -1) { + [this.anchor.x, this.anchor.y] = getTexturePivot(t.texture); + } else { + this.anchor.x = this.anchor.y = 0.5; + } + } +} + +export {Copy}; diff --git a/src/node_requires/roomEditor/entityClasses/MarqueeBox.ts b/src/node_requires/roomEditor/entityClasses/MarqueeBox.ts new file mode 100644 index 000000000..947b9560f --- /dev/null +++ b/src/node_requires/roomEditor/entityClasses/MarqueeBox.ts @@ -0,0 +1,24 @@ +import {RoomEditor} from '..'; +import {getPixiSwatch} from '../../themes'; + +export class MarqueeBox extends PIXI.Graphics { + editor: RoomEditor; + constructor(editor: RoomEditor, x: number, y: number, width: number, height: number) { + super(); + this.editor = editor; + this.redrawBox(x, y, width, height); + } + redrawBox(x: number, y: number, width: number, height: number): void { + const x1 = x, + x2 = x + width, + y1 = y, + y2 = y + height; + this.x = Math.min(x1, x2); + this.y = Math.min(y1, y2); + this.clear(); + this.lineStyle(3 * this.editor.camera.scale.x, getPixiSwatch('act')) + .drawRoundedRect(0, 0, Math.abs(width), Math.abs(height), 0.1); + this.lineStyle(this.editor.camera.scale.x, getPixiSwatch('background')) + .drawRoundedRect(0, 0, Math.abs(width), Math.abs(height), 0.1); + } +} diff --git a/src/node_requires/roomEditor/entityClasses/SnapTarget.ts b/src/node_requires/roomEditor/entityClasses/SnapTarget.ts new file mode 100644 index 000000000..64db149d9 --- /dev/null +++ b/src/node_requires/roomEditor/entityClasses/SnapTarget.ts @@ -0,0 +1,95 @@ +import {getPixiSwatch} from '../../themes'; +import {RoomEditor} from '..'; +import {snapToRectangularGrid, snapToDiagonalGrid} from '../common'; +import {getPixiTexture, getTextureFromId, getTexturePivot} from '../../resources/textures'; +import {createTilePatch} from '../interactions/tiles/placeTile'; + +const unknownTextures = getPixiTexture(-1, void 0, true); + +export class SnapTarget extends PIXI.Container { + editor: RoomEditor; + circle = new PIXI.Graphics(); + ghost = new PIXI.AnimatedSprite(unknownTextures); + ghostCompound = new PIXI.Container(); + prevGhostTex: ITexture; + prevTilePatch: string; + constructor(editor: RoomEditor) { + super(); + this.editor = editor; + this.ghost.visible = false; + this.ghost.alpha = 0.5; + [this.ghost.anchor.x, this.ghost.anchor.y] = [0.5, 0.5]; + this.addChild(this.ghost, this.ghostCompound); + this.circle.beginFill(getPixiSwatch('act')); + this.circle.drawCircle(0, 0, 4); + this.addChild(this.circle); + } + getPatchString(): string { + const {tilePatch} = this.editor.riotEditor; + return `${tilePatch.texture.uid}:${tilePatch.startX}:${tilePatch.startY}:${tilePatch.spanX}:${tilePatch.spanY}`; + } + update(): void { + this.circle.scale.x = this.editor.camera.scale.x; + this.circle.scale.y = this.editor.camera.scale.y; + + const {riotEditor} = this.editor; + const {currentTemplate} = riotEditor; + if (riotEditor.currentTool === 'addCopies' && currentTemplate !== -1) { + this.ghost.visible = true; + if (currentTemplate.texture === -1 && + this.ghost.textures !== unknownTextures + ) { + this.updateGhost(-1); + this.ghost.textures = unknownTextures; + } + if (currentTemplate.texture !== -1 && + this.prevGhostTex !== getTextureFromId(currentTemplate.texture) + ) { + this.updateGhost(currentTemplate.texture); + this.prevGhostTex = getTextureFromId(currentTemplate.texture); + } + } else { + this.ghost.visible = false; + } + if (riotEditor.currentTool === 'addTiles' && riotEditor.tilePatch?.texture) { + if (this.prevTilePatch !== this.getPatchString()) { + this.ghostCompound.removeChildren(); + this.ghostCompound.visible = true; + this.ghostCompound.addChild(...createTilePatch(riotEditor.tilePatch, { + x: 0, + y: 0 + }, this.editor, true)); + this.prevTilePatch = this.getPatchString(); + } + } else { + this.ghostCompound.visible = false; + if (this.ghostCompound.children.length) { + this.ghostCompound.removeChildren(); + } + } + + const {mouse} = this.editor.renderer.plugins.interaction; + mouse.getLocalPosition(this.editor.overlays, this.position); + if (!riotEditor.gridOn || riotEditor.freePlacementMode) { + return; + } + let snappedPos; + if (this.editor.ctRoom.diagonalGrid) { + snappedPos = snapToDiagonalGrid({ + x: this.x, + y: this.y + }, this.editor.ctRoom.gridX, this.editor.ctRoom.gridY); + } else { + snappedPos = snapToRectangularGrid({ + x: this.x, + y: this.y + }, this.editor.ctRoom.gridX, this.editor.ctRoom.gridY); + } + this.x = snappedPos.x; + this.y = snappedPos.y; + } + updateGhost(texture: assetRef | ITexture): void { + this.ghost.textures = getPixiTexture(texture, void 0, true); + [this.ghost.anchor.x, this.ghost.anchor.y] = getTexturePivot(texture); + } +} diff --git a/src/node_requires/roomEditor/entityClasses/Tile.ts b/src/node_requires/roomEditor/entityClasses/Tile.ts new file mode 100644 index 000000000..07b01162c --- /dev/null +++ b/src/node_requires/roomEditor/entityClasses/Tile.ts @@ -0,0 +1,85 @@ +import {getPixiTexture, getTexturePivot} from '../../resources/textures'; +import {RoomEditor} from '..'; +import {TileLayer} from './TileLayer'; + +/** + * @notice This class automatically adds and removes itself from editor's tile list + */ +class Tile extends PIXI.Sprite { + tileTexture: assetRef; + tileFrame: number; + parent: TileLayer | null; + editor: RoomEditor; + isGhost: boolean; + + constructor(tileInfo: ITileTemplate, editor: RoomEditor, isGhost?: boolean) { + super(getPixiTexture(tileInfo.texture, tileInfo.frame, false)); + this.editor = editor; + this.deserialize(tileInfo); + this.isGhost = Boolean(isGhost); + this.interactive = !this.isGhost; + if (this.isGhost) { + this.alpha *= 0.5; + } else { + editor.tiles.add(this); + } + } + destroy(): void { + if (!this.isGhost) { + this.editor.tiles.delete(this); + } + super.destroy(); + } + detach(): this { + this.editor.tiles.delete(this); + this.parent.removeChild(this); + return this; + } + restore(parent: TileLayer): this { + this.editor.tiles.add(this); + parent.addChild(this); + return this; + } + + serialize(): ITileTemplate { + return { + x: this.x, + y: this.y, + opacity: this.alpha, + tint: this.tint, + scale: { + x: this.scale.x, + y: this.scale.y + }, + frame: this.tileFrame, + rotation: this.rotation, + texture: this.tileTexture as string + }; + } + deserialize(tile: ITileTemplate): void { + this.x = tile.x; + this.y = tile.y; + this.alpha = tile.opacity ?? 1; + this.tint = tile.tint ?? 0xffffff; + this.scale.x = tile.scale?.x ?? 1; + this.scale.y = tile.scale?.y ?? 1; + this.rotation = tile.rotation ?? 0; + this.tileTexture = tile.texture; + this.tileFrame = tile.frame; + [this.anchor.x, this.anchor.y] = getTexturePivot(this.tileTexture); + } + refreshTexture(): void { + const frame = getPixiTexture(this.tileTexture, this.tileFrame); + if (frame) { + this.texture = frame; + } else { + // eslint-disable-next-line no-console + console.warn(`Frame ${this.tileFrame} does not exist in the texture ${this.tileTexture}. Removing the tile.`); + // Invalid tile. Desintegrate! + this.destroy(); + } + [this.anchor.x, this.anchor.y] = getTexturePivot(this.tileTexture); + } +} + +export {Tile}; diff --git a/src/node_requires/roomEditor/entityClasses/TileLayer.ts b/src/node_requires/roomEditor/entityClasses/TileLayer.ts new file mode 100644 index 000000000..f773ec73d --- /dev/null +++ b/src/node_requires/roomEditor/entityClasses/TileLayer.ts @@ -0,0 +1,68 @@ +import {Tile} from './Tile'; +import {RoomEditor} from '..'; + +let idCounter = 0; + +export class TileLayer extends PIXI.Container { + extends: Record; + children: Tile[]; + editor: RoomEditor; + id: number; + constructor(tileLayer: ITileLayerTemplate, editor: RoomEditor) { + super(); + this.editor = editor; + this.id = idCounter++; + this.deserialize(tileLayer); + } + destroy(): void { + if (this.parent) { + this.parent.removeChild(this); + } + const ind = this.editor.tileLayers.indexOf(this); + if (ind !== -1) { + this.editor.tileLayers.splice(ind, 1); + } + super.destroy({ + children: true + }); + } + detach(writeToHistory?: boolean): this { + const ind = this.editor.tileLayers.indexOf(this); + if (ind !== -1) { + // eslint-disable-next-line no-console + console.warn('Detaching a layer that was not in the editor\'s tileLayer list', this); + this.editor.tileLayers.splice(ind, 1); + } + for (const tile of this.children) { + this.editor.tiles.delete(tile); + } + this.parent.removeChild(this); + if (writeToHistory) { + this.editor.history.pushChange({ + type: 'tileLayerDeletion', + deleted: this + }); + } + return this; + } + restore(): this { + this.editor.addTileLayer(this); + return this; + } + serialize(): ITileLayerTemplate { + return { + depth: this.zIndex, + tiles: this.children.map(c => c.serialize()), + extends: this.extends, + hidden: !this.visible + }; + } + deserialize(tileLayer: ITileLayerTemplate): void { + this.zIndex = tileLayer.depth; + this.extends = tileLayer.extends || {}; + for (const tile of tileLayer.tiles) { + const pixiTile = new Tile(tile, this.editor); + this.addChild(pixiTile); + } + } +} diff --git a/src/node_requires/roomEditor/entityClasses/Transformer.ts b/src/node_requires/roomEditor/entityClasses/Transformer.ts new file mode 100644 index 000000000..f51cba670 --- /dev/null +++ b/src/node_requires/roomEditor/entityClasses/Transformer.ts @@ -0,0 +1,289 @@ +import {RoomEditor} from '..'; + +import {getPixiSwatch} from '../../themes'; +import {rotateCursor} from '../common'; +import {ease} from 'node_modules/pixi-ease'; + +import {rotateRad, pdc} from '../../utils/trigo'; + +export class Handle extends PIXI.Graphics { + constructor() { + super(); + this.beginFill(getPixiSwatch('act')); + this.drawCircle(0, 0, 6); + this.interactive = true; + this.cursor = 'pointer'; + } +} +type transformSubset = { + x: number; + y: number; + scale: { + x: number; + y: number; + }; + rotation: number; +} + +export class Transformer extends PIXI.Container { + handleTL = new Handle(); + handleTR = new Handle(); + handleT = new Handle(); + handleL = new Handle(); + handleR = new Handle(); + handleCenter = new Handle(); + handleBL = new Handle(); + handleBR = new Handle(); + handleB = new Handle(); + handleRotate = new Handle(); + scaleHandles = [ + this.handleTL, + this.handleTR, + this.handleT, + this.handleL, + this.handleR, + this.handleBL, + this.handleBR, + this.handleB + ]; + frame = new PIXI.Graphics(); + + applyRotation: number; + applyScaleX: number; + applyScaleY: number; + applyTranslateX: number; + applyTranslateY: number; + transformPivotX: number; + transformPivotY: number; + frameWidth: number; + frameHeight: number; + initialTransforms = new Map(); + + editor: RoomEditor; + + constructor(editor: RoomEditor) { + super(); + this.editor = editor; + this.handleRotate.cursor = rotateCursor; + + this.handleCenter.scale.set(1.25, 1.25); + this.handleCenter.cursor = 'move'; + + this.addChild( + this.frame, + this.handleTL, + this.handleTR, + this.handleT, + this.handleL, + this.handleR, + this.handleCenter, + this.handleBL, + this.handleBR, + this.handleB, + this.handleRotate + ); + this.setup(); + } + + setup(skipHistoryUpdate?: boolean): void { + this.initialTransforms.clear(); + this.applyRotation = 0; + this.applyScaleX = this.applyScaleY = 1; + this.applyTranslateX = this.applyTranslateY = 0; + if (this.editor.currentSelection.size === 0) { + this.visible = false; + return; + } + this.visible = true; + + let rect; + for (const elt of this.editor.currentSelection) { + const w = elt.width, + h = elt.height, + // IDK why this works + px = Math.sign(elt.scale.x) === -1 ? 1 - elt.anchor.x : elt.anchor.x, + py = Math.sign(elt.scale.y) === -1 ? 1 - elt.anchor.y : elt.anchor.y; + const tl = rotateRad(-w * px, -h * py, elt.rotation), + tr = rotateRad(w * (1 - px), -h * py, elt.rotation), + bl = rotateRad(-w * px, h * (1 - py), elt.rotation), + br = rotateRad(w * (1 - px), h * (1 - py), elt.rotation); + const x1 = Math.min(tl[0], bl[0], tr[0], br[0]), + x2 = Math.max(tl[0], bl[0], tr[0], br[0]), + y1 = Math.min(tl[1], bl[1], tr[1], br[1]), + y2 = Math.max(tl[1], bl[1], tr[1], br[1]); + if (!rect) { + rect = new PIXI.Rectangle(x1, y1, x2 - x1, y2 - y1); + rect.x += elt.x; + rect.y += elt.y; + } else { + const newRect = new PIXI.Rectangle(x1, y1, x2 - x1, y2 - y1); + newRect.x += elt.x; + newRect.y += elt.y; + rect.enlarge(newRect); + } + this.initialTransforms.set(elt, { + x: elt.x, + y: elt.y, + scale: { + x: elt.scale.x || 1, + y: elt.scale.y || 1 + }, + rotation: elt.rotation + }); + } + this.frameWidth = rect.width; + this.frameHeight = rect.height; + this.transformPivotX = rect.x + rect.width / 2; + this.transformPivotY = rect.y + rect.height / 2; + if (!skipHistoryUpdate) { + this.editor.history.initiateTransformChange(); + } + this.updateFrame(); + } + clear(): void { + this.editor.currentSelection.clear(); + this.setup(true); + } + + applyTransforms(): void { + for (const elt of this.editor.currentSelection) { + const initial = this.initialTransforms.get(elt); + const delta = { + x: ((initial.x + this.applyTranslateX) - this.transformPivotX) * this.applyScaleX, + y: ((initial.y + this.applyTranslateY) - this.transformPivotY) * this.applyScaleY + }; + const rotatedDelta = rotateRad(delta.x, delta.y, this.applyRotation); + elt.x = this.transformPivotX + rotatedDelta[0]; + elt.y = this.transformPivotY + rotatedDelta[1]; + elt.rotation = initial.rotation + this.applyRotation; + + // Skew isn't in ct.js, so something fancy is introduced as an alternative. + // Works great at 0, 90, 180, 270 degrees. + // Works q̴̿ͅu̵͈͑e̵̖͒s̵͈̎t̸̪̽i̶͈͌o̴̳͊ñ̶̪a̵͜͝b̶̮̈́ĺ̸̲y̸͒͜ on other angles. + const flip = Math.sign(this.applyScaleX) !== Math.sign(this.applyScaleY) ? -1 : 1; + const sin = Math.sin(initial.rotation), + cos = Math.cos(initial.rotation); + + elt.scale.set( + initial.scale.x * + (cos ** 2 * this.applyScaleX + sin ** 2 * this.applyScaleY * flip), + initial.scale.y * + (sin ** 2 * this.applyScaleX * flip + cos ** 2 * this.applyScaleY) + ); + } + } + + outlineSelected(): void { + for (const elt of this.editor.currentSelection) { + const w = elt.width, + h = elt.height, + // IDK why this works + px = Math.sign(elt.scale.x) === -1 ? 1 - elt.anchor.x : elt.anchor.x, + py = Math.sign(elt.scale.y) === -1 ? 1 - elt.anchor.y : elt.anchor.y, + {x, y} = this.editor.room.toGlobal(elt.position), + sx = this.editor.camera.scale.x, + sy = this.editor.camera.scale.y; + const tl = rotateRad(-w * px, -h * py, elt.rotation), + tr = rotateRad(w * (1 - px), -h * py, elt.rotation), + bl = rotateRad(-w * px, h * (1 - py), elt.rotation), + br = rotateRad(w * (1 - px), h * (1 - py), elt.rotation); + // this.frame.lineStyle(3, getPixiSwatch('act')); + this.frame.lineStyle(1, getPixiSwatch('background')); + this.frame.beginFill(getPixiSwatch('act'), 0.15); + this.frame.moveTo(x + tl[0] / sx, y + tl[1] / sy); + this.frame.lineTo(x + tr[0] / sx, y + tr[1] / sy); + this.frame.lineTo(x + br[0] / sx, y + br[1] / sy); + this.frame.lineTo(x + bl[0] / sx, y + bl[1] / sy); + this.frame.lineTo(x + tl[0] / sx, y + tl[1] / sy); + this.frame.endFill(); + // this.frame.lineStyle(1, getPixiSwatch('background')); + // this.frame.moveTo(x + tl[0] / sx, y + tl[1] / sy); + // this.frame.lineTo(x + tr[0] / sx, y + tr[1] / sy); + // this.frame.lineTo(x + br[0] / sx, y + br[1] / sy); + // this.frame.lineTo(x + bl[0] / sx, y + bl[1] / sy); + // this.frame.lineTo(x + tl[0] / sx, y + tl[1] / sy); + } + } + updateFrame(): void { + const halfDiagonalScaled = { + x: this.frameWidth / 2 * this.applyScaleX / this.editor.camera.scale.x, + y: this.frameHeight / 2 * this.applyScaleY / this.editor.camera.scale.y + }; + // Compute position of four corners of the selection frame + const TR = rotateRad(halfDiagonalScaled.x, -halfDiagonalScaled.y, this.applyRotation), + TL = rotateRad(-halfDiagonalScaled.x, -halfDiagonalScaled.y, this.applyRotation), + BR = rotateRad(halfDiagonalScaled.x, halfDiagonalScaled.y, this.applyRotation), + BL = rotateRad(-halfDiagonalScaled.x, halfDiagonalScaled.y, this.applyRotation); + const globalFrom = this.editor.room.toGlobal(new PIXI.Point( + this.transformPivotX, + this.transformPivotY + )); + + TR[0] += globalFrom.x; + TL[0] += globalFrom.x; + BL[0] += globalFrom.x; + BR[0] += globalFrom.x; + TR[1] += globalFrom.y; + TL[1] += globalFrom.y; + BL[1] += globalFrom.y; + BR[1] += globalFrom.y; + + // Move handles to appropriate places + this.handleCenter.x = globalFrom.x; + this.handleCenter.y = globalFrom.y; + [this.handleTL.x, this.handleTL.y] = TL; + [this.handleTR.x, this.handleTR.y] = TR; + [this.handleBL.x, this.handleBL.y] = BL; + [this.handleBR.x, this.handleBR.y] = BR; + this.handleT.x = (TL[0] + TR[0]) / 2; + this.handleT.y = (TL[1] + TR[1]) / 2; + this.handleL.x = (TL[0] + BL[0]) / 2; + this.handleL.y = (TL[1] + BL[1]) / 2; + this.handleR.x = (TR[0] + BR[0]) / 2; + this.handleR.y = (TR[1] + BR[1]) / 2; + this.handleB.x = (BL[0] + BR[0]) / 2; + this.handleB.y = (BL[1] + BR[1]) / 2; + // Rotation handle is placed a bit outside the frame + const shift = rotateRad(21 * Math.sign(this.applyScaleX), 0, this.applyRotation); + this.handleRotate.x = this.handleR.x + shift[0]; + this.handleRotate.y = this.handleR.y + shift[1]; + + // Hide middle handles if they're placed way too close to other elements + if (pdc(TL[0], TL[1], BL[0], BL[1]) < 32 || pdc(TL[0], TL[1], TR[0], TR[1]) <= 16) { + this.handleL.visible = this.handleR.visible = false; + } else { + this.handleL.visible = this.handleR.visible = true; + } + if (pdc(TL[0], TL[1], TR[0], TR[1]) < 32 || pdc(TL[0], TL[1], BL[0], BL[1]) <= 16) { + this.handleT.visible = this.handleB.visible = false; + } else { + this.handleT.visible = this.handleB.visible = true; + } + + this.frame.clear(); + // Outline the selected elements + this.outlineSelected(); + // Draw the frame + this.frame.lineStyle(4, getPixiSwatch('act')); + this.frame.moveTo(TL[0] - 0.5, TL[1] - 0.5); + this.frame.lineTo(TR[0] + 0.5, TR[1] - 0.5); + this.frame.lineTo(BR[0] + 0.5, BR[1] + 0.5); + this.frame.lineTo(BL[0] - 0.5, BL[1] + 0.5); + this.frame.lineTo(TL[0] - 0.5, TL[1] - 0.5); + this.frame.lineStyle(2, getPixiSwatch('background')); + this.frame.moveTo(TL[0] - 0.5, TL[1] - 0.5); + this.frame.lineTo(TR[0] + 0.5, TR[1] - 0.5); + this.frame.lineTo(BR[0] + 0.5, BR[1] + 0.5); + this.frame.lineTo(BL[0] - 0.5, BL[1] + 0.5); + this.frame.lineTo(TL[0] - 0.5, TL[1] - 0.5); + } + + blink(): void { + this.alpha = 0; + ease.add(this, { + alpha: 1 + }, { + duration: 150 + }); + } +} diff --git a/src/node_requires/roomEditor/entityClasses/Viewport.ts b/src/node_requires/roomEditor/entityClasses/Viewport.ts new file mode 100644 index 000000000..a07d4fd6b --- /dev/null +++ b/src/node_requires/roomEditor/entityClasses/Viewport.ts @@ -0,0 +1,58 @@ +import {getPixiSwatch} from '../../themes'; +import {RoomEditor} from '..'; + +interface ICtViewport { + width: number; + height: number; + x?: number; + y?: number; +} + +class Viewport extends PIXI.Graphics { + view: ICtViewport; + starting: boolean; + startingIcon: PIXI.Graphics; + editor: RoomEditor; + + constructor(view: ICtViewport, starting: boolean, editor: RoomEditor) { + super(); + this.editor = editor; + this.starting = starting; + this.view = view; + this.x = this.view.x ?? 0; + this.y = this.view.y ?? 0; + this.startingIcon = new PIXI.Graphics(); + this.startingIcon + .lineStyle(2, getPixiSwatch('orange'), 1, 0.5) + .moveTo(0, 0) + .lineTo(17, 10) + .lineTo(0, 20) + .lineTo(0, 0) + .closePath(); + this.startingIcon.y = 16; + this.startingIcon.visible = this.starting; + this.addChild(this.startingIcon); + this.redrawFrame(); + } + destroy(): void { + this.editor.viewports.delete(this); + super.destroy(); + } + + redrawFrame(): void { + this.x = this.view.x ?? 0; + this.y = this.view.y ?? 0; + this.startingIcon.scale.set(this.editor.camera.scale.x); + this.startingIcon.visible = this.starting && + (this.view.height / this.editor.camera.scale.x > 48); + this.clear(); + this.lineStyle(4 * this.editor.camera.scale.x, getPixiSwatch('act')) + .drawRoundedRect(0, 0, this.view.width, this.view.height, 0.1); + this.lineStyle(2 * this.editor.camera.scale.x, getPixiSwatch('background')) + .drawRoundedRect(0, 0, this.view.width, this.view.height, 0.1); + this.startingIcon.x = this.view.width - (16 + 17) * this.editor.camera.scale.x; + this.startingIcon.y = 16 * this.editor.camera.scale.y; + } +} + +export {Viewport}; diff --git a/src/node_requires/roomEditor/entityClasses/ViewportRestriction.ts b/src/node_requires/roomEditor/entityClasses/ViewportRestriction.ts new file mode 100644 index 000000000..a4bd8bce6 --- /dev/null +++ b/src/node_requires/roomEditor/entityClasses/ViewportRestriction.ts @@ -0,0 +1,57 @@ +import {getPixiSwatch} from '../../themes'; +import {RoomEditor} from '..'; + +export class ViewportRestriction extends PIXI.Graphics { + icon: PIXI.Graphics; + editor: RoomEditor; + constructor(editor: RoomEditor) { + super(); + this.editor = editor; + this.x = this.editor.ctRoom.restrictMinX; + this.y = this.editor.ctRoom.restrictMinY; + this.icon = new PIXI.Graphics(); + this.icon + .lineStyle(2, getPixiSwatch('orange'), 1, 0.5); + this.icon.drawRect(0, 10, 20, 14); + this.icon.arc(10, 6, 6, Math.PI, Math.PI * 2); + this.icon.moveTo(10, 14); + this.icon.lineTo(10, 20); + this.icon.moveTo(4, 6); + this.icon.lineTo(4, 10); + this.icon.moveTo(4 + 12, 6); + this.icon.lineTo(4 + 12, 10); + this.icon.y = 16; + this.addChild(this.icon); + this.redrawFrame(); + } + + redrawFrame(): void { + if (!this.editor.ctRoom.restrictCamera) { + this.visible = false; + return; + } + this.visible = true; + this.x = this.editor.ctRoom.restrictMinX; + this.y = this.editor.ctRoom.restrictMinY; + let width = this.editor.ctRoom.restrictMaxX - this.editor.ctRoom.restrictMinX, + height = this.editor.ctRoom.restrictMaxY - this.editor.ctRoom.restrictMinY; + if (width < 0) { + width = Math.abs(width); + this.x = this.editor.ctRoom.restrictMaxX; + } + if (height < 0) { + height = Math.abs(height); + this.y = this.editor.ctRoom.restrictMaxY; + } + this.icon.scale.set(this.editor.camera.scale.x); + this.icon.visible = (height / this.editor.camera.scale.x > 48); + this.clear(); + this.lineStyle(4 * this.editor.camera.scale.x, getPixiSwatch('act')) + .drawRoundedRect(0, 0, width, height, 0.1); + this.lineStyle(2 * this.editor.camera.scale.x, getPixiSwatch('background')) + .drawRoundedRect(0, 0, width, height, 0.1); + this.icon.x = width - (16 + 20) * this.editor.camera.scale.x; + this.icon.y = 16 * this.editor.camera.scale.y; + } +} + diff --git a/src/node_requires/roomEditor/history.ts b/src/node_requires/roomEditor/history.ts new file mode 100644 index 000000000..1efebcb1e --- /dev/null +++ b/src/node_requires/roomEditor/history.ts @@ -0,0 +1,290 @@ +import {Copy} from './entityClasses/Copy'; +import {Tile} from './entityClasses/Tile'; +import {TileLayer} from './entityClasses/TileLayer'; +import {Background} from './entityClasses/Background'; +import {RoomEditor} from '.'; + +type transformationSnapshot = { + position: { + x: number; + y: number; + } + rotation: number; + scale: { + x: number; + y: number; + } + tint: number; + alpha: number; +}; + +/** + * In order: The entity that was changed, transformation before the change, and after it. + */ +type transformation = { + type: 'transformation', + transformations: Map +}; + +type deletion = { + type: 'deletion', + deleted: Set<[Copy | Tile, TileLayer?]> +}; +type creation = { + type: 'creation', + created: Set<[Copy | Tile, TileLayer?]> +}; + +type tileLayerCreation = { + type: 'tileLayerCreation', + created: TileLayer +} +type tileLayerDeletion = { + type: 'tileLayerDeletion', + deleted: TileLayer +} + +type backgroundCreation = { + type: 'backgroundCreation', + created: Background +} +type backgroundDeletion = { + type: 'backgroundDeletion', + deleted: Background +} + +type propChange = { + type: 'propChange', + key: string, + target: unknown, + before: unknown, + after: unknown +} + +export type change = transformation | deletion | creation | + tileLayerCreation | tileLayerDeletion | + backgroundCreation | backgroundDeletion | + propChange; + +const snapshotTransform = (entity: PIXI.Sprite): transformationSnapshot => ({ + position: { + x: entity.position.x, + y: entity.position.y + }, + rotation: entity.rotation, + scale: { + x: entity.scale.x, + y: entity.scale.y + }, + tint: entity.tint, + alpha: entity.alpha +}); + +export class History { + stack: change[] = []; + /** + * Describes the last change made in the given period of time in history. + * Undo operation undos this change, while redo operation redos the change next to it. + */ + currentChange?: change; + editor: RoomEditor; + + constructor(editor: RoomEditor) { + this.editor = editor; + } + + undo(): boolean { + if (!this.currentChange) { + return false; + } + const change = this.currentChange; + // eslint-disable-next-line default-case + switch (change.type) { + case 'transformation': + this.editor.transformer.clear(); + for (const transform of change.transformations) { + const [entity, [before]] = transform; + entity.position.set(before.position.x, before.position.y); + entity.scale.set(before.scale.x, before.scale.y); + entity.alpha = before.alpha; + entity.rotation = before.rotation; + entity.tint = before.tint; + this.editor.currentSelection.add(entity); + } + this.editor.transformer.setup(true); + this.editor.riotEditor.refs.propertiesPanel?.updatePropList(); + break; + case 'deletion': + for (const deletion of change.deleted) { + const [entity, parent] = deletion; + entity.restore(parent); + } + break; + case 'creation': + this.editor.transformer.clear(); + for (const creation of change.created) { + const [entity] = creation; + entity.detach(); + } + break; + case 'tileLayerCreation': + change.created.detach(); + if (!this.editor.tileLayers.includes(this.editor.riotEditor.currentTileLayer)) { + [this.editor.riotEditor.currentTileLayer] = this.editor.tileLayers; + } + this.editor.riotEditor.refs.tileEditor?.update(); + break; + case 'tileLayerDeletion': + change.deleted.restore(); + this.editor.riotEditor.currentTileLayer = change.deleted; + this.editor.riotEditor.refs.tileEditor?.update(); + break; + case 'backgroundCreation': + change.created.detach(); + this.editor.riotEditor.refs.backgroundsEditor?.update(); + break; + case 'backgroundDeletion': + change.deleted.restore(); + this.editor.riotEditor.refs.backgroundsEditor?.update(); + break; + case 'propChange': + (change.target as Record)[change.key] = change.before; + this.updateUiFor(change); + break; + } + const prevChangeType = change.type; + this.currentChange = this.stack[this.stack.indexOf(change) - 1]; + if (prevChangeType === 'transformation' && !change) { + // If we reached history's end with transformation reversal, leave + // this last change as current one, as the transformer selects the same set + // of entities and initial transforms are totally correct. + [this.currentChange] = this.stack; + } + this.editor.riotEditor.update(); + return true; + } + redo(): boolean { + if (this.currentChange === this.stack[this.stack.length - 1]) { + return false; + } + const newChange = this.stack[this.stack.indexOf(this.currentChange) + 1]; + // eslint-disable-next-line default-case + switch (newChange.type) { + case 'transformation': + this.editor.transformer.clear(); + for (const change of newChange.transformations) { + const [entity, [, after]] = change; + entity.position.set(after.position.x, after.position.y); + entity.scale.set(after.scale.x, after.scale.y); + entity.alpha = after.alpha; + entity.rotation = after.rotation; + entity.tint = after.tint; + this.editor.currentSelection.add(entity); + } + this.editor.transformer.setup(true); + this.editor.riotEditor.refs.propertiesPanel?.updatePropList(); + break; + case 'deletion': + this.editor.transformer.clear(); + for (const deletion of newChange.deleted) { + const [entity] = deletion; + entity.detach(); + } + break; + case 'creation': + for (const creation of newChange.created) { + const [entity, parent] = creation; + entity.restore(parent); + } + break; + case 'tileLayerCreation': + newChange.created.restore(); + this.editor.riotEditor.currentTileLayer = newChange.created; + this.editor.riotEditor.refs.tileEditor?.update(); + break; + case 'tileLayerDeletion': + newChange.deleted.detach(); + if (!this.editor.tileLayers.includes(newChange.deleted)) { + [this.editor.riotEditor.currentTileLayer] = this.editor.tileLayers; + } + this.editor.riotEditor.refs.tileEditor?.update(); + break; + case 'backgroundCreation': + newChange.created.restore(); + this.editor.riotEditor.refs.backgroundsEditor?.update(); + break; + case 'backgroundDeletion': + newChange.deleted.detach(); + this.editor.riotEditor.refs.backgroundsEditor?.update(); + break; + case 'propChange': + (newChange.target as Record)[newChange.key] = newChange.after; + this.updateUiFor(newChange); + break; + } + this.currentChange = newChange; + this.editor.riotEditor.update(); + return true; + } + pushChange(change: change): void { + const id = this.stack.indexOf(this.currentChange); + this.stack = this.stack.slice(0, id + 1); + this.stack.push(change); + this.currentChange = change; + if (this.stack.length > 30) { + this.stack.shift(); + } + this.editor.riotEditor.update(); + } + initiateTransformChange(): void { + const transform: change = { + type: 'transformation', + transformations: new Map() + }; + for (const entity of this.editor.currentSelection) { + const initialTransform = snapshotTransform(entity); + transform.transformations.set(entity as Copy | Tile, [ + initialTransform, + { + ...initialTransform + } + ]); + } + this.pushChange(transform); + } + snapshotTransforms(): void { + if (this.currentChange.type !== 'transformation') { + throw new Error('Cannot snapshot transforms as the current change\'s type is not "transformation"'); + } + for (const [entity, value] of this.currentChange.transformations) { + value[1] = snapshotTransform(entity); + } + void this; + } + updateUiFor(change: propChange): void { + const {target, key} = change, + {editor} = this, + riot = editor.riotEditor; + if (target instanceof TileLayer) { + riot.refs.tileEditor?.update(); + } else if (target === editor.ctRoom) { + riot.refs.propertiesPanel?.update(); + if (key === 'backgroundColor') { + editor.renderer.backgroundColor = + PIXI.utils.string2hex(editor.ctRoom.backgroundColor); + } + } else if (target instanceof Background) { + if (key === 'bgTexture') { + target.changeTexture(target.bgTexture); + } + riot.refs.backgroundsEditor?.update(); + } + } + + get canUndo(): boolean { + return Boolean(this.currentChange); + } + get canRedo(): boolean { + return this.currentChange !== this.stack[this.stack.length - 1]; + } +} diff --git a/src/node_requires/roomEditor/index.ts b/src/node_requires/roomEditor/index.ts new file mode 100644 index 000000000..d5bcaaf38 --- /dev/null +++ b/src/node_requires/roomEditor/index.ts @@ -0,0 +1,561 @@ +import {History} from './history'; + +import {Copy} from './entityClasses/Copy'; +import {Tile} from './entityClasses/Tile'; +import {TileLayer} from './entityClasses/TileLayer'; +import {Background} from './entityClasses/Background'; +import {Viewport} from './entityClasses/Viewport'; + +import {SnapTarget} from './entityClasses/SnapTarget'; +import {MarqueeBox} from './entityClasses/MarqueeBox'; +import {Transformer} from './entityClasses/Transformer'; +import {ViewportRestriction} from './entityClasses/ViewportRestriction'; + +import {IRoomEditorRiotTag} from './IRoomEditorRiotTag'; +import {IRoomEditorInteraction, AllowedListener, allowedListeners, interactions} from './interactions'; +import {getPixiSwatch} from './../themes'; +import {defaultTextStyle, recolorFilters, eraseCursor} from './common'; +import {ease} from 'node_modules/pixi-ease'; + +const roomEditorDefaults = { + width: 10, + height: 10, + autoDensity: true, + transparent: false, + sharedLoader: true, + sharedTicker: false, + resolution: devicePixelRatio, + antialias: true, + preserveDrawingBuffer: true +}; + +export type tileClipboardData = ['tile', ITileTemplate, TileLayer]; +export type copyClipboardData = ['copy', IRoomCopy]; + +class RoomEditor extends PIXI.Application { + history = new History(this); + riotEditor: IRoomEditorRiotTag; + ctRoom: IRoom; + currentSelection: Set = new Set(); + clipboard: Set = new Set(); + /** A sprite that catches any click events */ + clicktrap = new PIXI.Sprite(PIXI.Texture.WHITE); + /** A small circle that shows currently snapped position and a ghost for copy/tile placement */ + snapTarget = new SnapTarget(this); + /** A secondary ghost container for more complex prefabs and constructions */ + compoundGhost = new PIXI.Container(); + /** + * An empty Container that will be used as a camera. + * The camera provides inverted transforms for proper view placement. + */ + camera = new PIXI.Container(); + /** An overlay showing current rectangular selection */ + marqueeBox = new MarqueeBox(this, 0, 0, 10, 10); + /** A container for all the room's entities */ + room = new PIXI.Container(); + /** A container for viewport boxes, grid, and other overlays */ + overlays = new PIXI.Container(); + /** A free transform widget that exists in **global** coordinates. */ + transformer = new Transformer(this); + primaryViewport: Viewport; + restrictViewport: ViewportRestriction; + grid = new PIXI.Graphics(); + /** + * A label that will display current mouse coords relative to a room, + * in a left-bottom corner. Useful for lining up things in a level. + */ + pointerCoords = new PIXI.Text('(0;0)', defaultTextStyle); + /** + * Whether the room editor currently processes a user interaction. + * While this is true, no new interactions can be started. + */ + interacting = false; + interactions: IRoomEditorInteraction[]; + currentInteraction: IRoomEditorInteraction; + affixedInteractionData: unknown; + + copies = new Set(); + tiles = new Set(); + backgrounds: Background[] = []; + viewports = new Set(); + tileLayers: TileLayer[] = []; + + /** + * Creates a pixi.js app — a room editor + * Its `stage` manipulates the camera, managing panning and zooming. + * All the CRUD operations are in the Room class. + * + * @param {Object} opts A partial set of PIXI.Application options that must + * include a mounting point (`view`) + * @param {RiotTag} editor a tag instance of a `room-editor` + */ + constructor(opts: unknown, editor: IRoomEditorRiotTag, pixelart: boolean) { + super(Object.assign({}, roomEditorDefaults, opts, { + resizeTo: editor.root, + roundPixels: pixelart + })); + this.ticker.maxFPS = 60; + + const {room} = editor.opts; + this.ctRoom = room; + this.riotEditor = editor; + + this.clicktrap.alpha = 0; + this.stage.addChild(this.clicktrap); + this.resizeClicktrap(); + this.room.sortableChildren = true; + + this.camera.x = room.width / 2; + this.camera.y = room.height / 2; + this.stage.addChild(this.camera); + + this.stage.addChild(this.room); + this.redrawGrid(); + this.overlays.addChild(this.grid); + this.stage.addChild(this.overlays); + this.compoundGhost.alpha = 0.5; + this.overlays.addChild(this.compoundGhost); + this.marqueeBox.visible = false; + this.overlays.addChild(this.marqueeBox); + this.overlays.addChild(this.snapTarget); + this.deserialize(editor.opts.room); + this.stage.addChild(this.transformer); + + this.pointerCoords.zIndex = Infinity; + this.pointerCoords.x = 8; + this.stage.addChild(this.pointerCoords); + + this.ticker.add(() => { + this.resizeClicktrap(); + this.realignCamera(); + this.snapTarget.update(); + this.repositionCoordLabel(); + this.tickBackgrounds(); + this.tickCopies(); + if (this.transformer.visible) { + this.transformer.updateFrame(); + } + if (['addCopies', 'addTiles'].includes(this.riotEditor.currentTool)) { + if (this.riotEditor.controlMode && ['default', 'inherit'].includes(this.view.style.cursor)) { + this.view.style.cursor = eraseCursor; + this.snapTarget.visible = false; + } + if (!this.riotEditor.controlMode && this.view.style.cursor.includes('Erase')) { + this.view.style.cursor = 'default'; + this.snapTarget.visible = true; + } + } + }); + + this.stage.interactive = true; + this.interactions = interactions; + for (const event of allowedListeners) { + this.stage.on(event, (e: PIXI.InteractionEvent) => { + this.listen(e, event); + }); + } + + // Riot's observable objects lose function's context, so pass an anonymous function instead + this.updateTexturesHandle = (textureId: string) => this.updateTextures(textureId); + this.updateCopiesHandle = (templateId: string) => this.updateCopies(templateId); + window.signals.on('pixiTextureChanged', this.updateTexturesHandle); + window.signals.on('templateChanged', this.updateCopiesHandle); + } + destroy(removeView: boolean, stageOptions: { + children?: boolean; + texture?: boolean; + baseTexture?: boolean; + }): void { + window.signals.off('pixiTextureChanged', this.updateTexturesHandle); + window.signals.off('templateChanged', this.updateCopiesHandle); + super.destroy(removeView, stageOptions); + } + + listen(event: PIXI.InteractionEvent, listener: AllowedListener): void { + var callback = () => { + this.interacting = false; + this.currentInteraction = this.affixedInteractionData = void 0; + }; + for (const interaction of this.interactions) { + if (!this.interacting && interaction.ifListener === listener) { + if (interaction.if.apply(this, [event, this.riotEditor])) { + this.interacting = true; + this.currentInteraction = interaction; + this.affixedInteractionData = {}; + break; + } + } + } + for (const interaction of this.interactions) { + if (this.interacting && this.currentInteraction === interaction) { + if (listener in this.currentInteraction.listeners) { + this.currentInteraction.listeners[listener].apply( + this, + [event, this.riotEditor, this.affixedInteractionData, callback] + ); + } + } + } + } + + deserialize(room: IRoom): void { + this.simulate = room.simulate ?? true; + this.renderer.backgroundColor = PIXI.utils.string2hex(room.backgroundColor); + // Add primary viewport + this.primaryViewport = new Viewport(room, true, this); + this.restrictViewport = new ViewportRestriction(this); + this.overlays.addChild(this.restrictViewport); + this.overlays.addChild(this.primaryViewport); + this.viewports.add(this.primaryViewport); + // Add the remaining entities + for (const bg of room.backgrounds) { + this.addBackground(bg); + } + for (const copy of room.copies) { + const pixiCopy = new Copy(copy, this); + this.room.addChild(pixiCopy); + } + for (const tileLayer of room.tiles) { + this.addTileLayer(tileLayer); + } + } + serialize(): void { + this.ctRoom.copies = [...this.copies].map(c => c.serialize()); + this.ctRoom.tiles = this.tileLayers.map(tl => tl.serialize()); + this.ctRoom.backgrounds = this.backgrounds.map(bg => bg.serialize()); + this.ctRoom.lastmod = Number(new Date()); + } + + /** + * It is separated into a method to be usable externally. + * room-tile-editor uses it to add new layers. + */ + addTileLayer(tileLayer: ITileLayerTemplate | TileLayer, writeToHistory?: boolean): TileLayer { + const pixiTileLayer = tileLayer instanceof TileLayer ? + tileLayer : + new TileLayer(tileLayer, this); + if (this.colorizeTileLayers) { + pixiTileLayer.filters = [recolorFilters[pixiTileLayer.id % 6]]; + } + this.room.addChild(pixiTileLayer); + this.tileLayers.push(pixiTileLayer); + this.tileLayers.sort((a, b) => b.zIndex - a.zIndex); + if (pixiTileLayer.children) { + for (const tile of pixiTileLayer.children) { + this.tiles.add(tile); + } + } + if (writeToHistory) { + this.history.pushChange({ + type: 'tileLayerCreation', + created: pixiTileLayer + }); + } + return pixiTileLayer; + } + + /** + * It is separated as well to be usable by UI written with Riot. + */ + addBackground(bgTemplate: IRoomBackground): Background { + const bg = new Background(bgTemplate, this); + this.backgrounds.push(bg); + this.room.addChild(bg); + return bg; + } + + resizeClicktrap(): void { + this.clicktrap.width = this.screen.width; + this.clicktrap.height = this.screen.height; + } + redrawGrid(): void { + const w = this.ctRoom.gridX, + h = this.ctRoom.gridY; + this.grid.clear(); + if (!this.riotEditor.gridOn) { + // Grid isn't needed + return; + } + // Don't draw too fine grid + if (w / this.camera.scale.x < 8 || h / this.camera.scale.y < 8) { + return; + } + this.grid.lineStyle(this.camera.scale.x, getPixiSwatch('act'), 0.3); + // Camera boundaries with extra tiles around the border + const cw = this.screen.width * this.camera.scale.x + w * 2, + ch = this.screen.height * this.camera.scale.y + h * 2, + cx = this.camera.x - this.screen.width / 2 * this.camera.scale.x - w, + cy = this.camera.y - this.screen.height / 2 * this.camera.scale.y - h; + const xstart = cx; + const ystart = cy; + this.grid.x = -(cx % w); + this.grid.y = -(cy % h); + if (this.ctRoom.diagonalGrid) { + const angle1 = Math.atan(h / w); + const angle2 = Math.PI - angle1; + const cos1 = Math.cos(angle1), + cos2 = Math.cos(angle2), + sin1 = Math.sin(angle1), + sin2 = Math.sin(angle2); + const max = Math.sqrt(cw * cw + ch * ch); + for (let x = xstart; x < cx + cw; x += w) { + this.grid.moveTo(x, cy); + this.grid.lineTo(x + cos1 * max, cy + sin1 * max); + this.grid.moveTo(x, cy); + this.grid.lineTo(x + cos2 * max, cy + sin2 * max); + } + for (let y = ystart + h; y < cy + ch; y += h) { + this.grid.moveTo(cx, y); + this.grid.lineTo(cx + cos1 * max, y + sin1 * max); + } + const cwCorrected = Math.ceil(cw / w) * w; + for (let y = ystart; y < cy + ch; y += h) { + this.grid.moveTo(cx + cwCorrected, y); + this.grid.lineTo(cx + cwCorrected + cos2 * max, y + sin2 * max); + } + } else { + for (let x = xstart; x < cx + cw; x += w) { + this.grid.moveTo(x, cy); + this.grid.lineTo(x, cy + ch); + } + for (let y = ystart; y < cy + ch; y += h) { + this.grid.moveTo(cx, y); + this.grid.lineTo(cx + cw, y); + } + } + } + redrawViewports(): void { + for (const viewport of this.viewports) { + viewport.redrawFrame(); + } + this.restrictViewport.redrawFrame(); + } + /** + * Updates room position based on the camera position. + * @returns {void} + */ + realignCamera(): void { + this.room.transform.setFromMatrix(this.camera.worldTransform + .clone() + .invert() + .translate( + this.view.width / devicePixelRatio / 2, + this.view.height / devicePixelRatio / 2 + )); + this.overlays.transform = this.room.transform; + this.redrawGrid(); + this.redrawViewports(); + } + tickBackgrounds(): void { + for (const background of this.backgrounds) { + background.tick(this.ticker.deltaTime); + } + } + tickCopies(): void { + if (!this.simulate) { + return; + } + for (const copy of this.copies) { + copy.update(this.ticker.deltaTime); + } + } + repositionCoordLabel(): void { + this.pointerCoords.y = this.screen.height - 30; + } + + updateTextures(textureId: string): void { + for (const child of this.room.children) { + if (child instanceof Copy) { + if (child.cachedTemplate.texture === textureId) { + child.refreshTexture(); + } + } else if (child instanceof Tile) { + if (child.tileTexture === textureId) { + child.refreshTexture(); + } + } else if (child instanceof Background) { + if (child.bgTexture === textureId) { + child.refreshTexture(); + } + } + } + } + updateCopies(templateId: string): void { + for (const child of this.room.children) { + if (child instanceof Copy) { + if (child.templateId === templateId) { + child.refreshTexture(); + } + } + } + } + updateTexturesHandle: (textureId: string) => void; + updateCopiesHandle: (templateId: string) => void; + + #simulate: boolean; + get simulate(): boolean { + return this.#simulate; + } + set simulate(value: boolean) { + this.#simulate = value; + if (this.#simulate) { + this.ticker.speed = 1; + } else { + this.ticker.speed = 0; + } + } + + #copiesVisible = true; + #tilesVisible = true; + #backgroundsVisible = true; + #xrayMode = false; + #colorizeTileLayers = false; + get copiesVisible(): boolean { + return this.#copiesVisible; + } + set copiesVisible(value: boolean) { + value = Boolean(value); + if (value === this.#copiesVisible) { + return; + } + this.#copiesVisible = value; + for (const copy of this.copies) { + copy.visible = this.#copiesVisible; + } + } + get tilesVisible(): boolean { + return this.#tilesVisible; + } + set tilesVisible(value: boolean) { + value = Boolean(value); + if (value === this.#tilesVisible) { + return; + } + this.#tilesVisible = value; + for (const layer of this.tileLayers) { + layer.visible = this.#tilesVisible; + } + } + get backgroundsVisible(): boolean { + return this.#backgroundsVisible; + } + set backgroundsVisible(value: boolean) { + value = Boolean(value); + if (value === this.#backgroundsVisible) { + return; + } + this.#backgroundsVisible = value; + for (const bg of this.backgrounds) { + bg.visible = this.#backgroundsVisible; + } + } + get xrayMode(): boolean { + return this.#xrayMode; + } + set xrayMode(value: boolean) { + this.#xrayMode = Boolean(value); + this.room.alpha = this.#xrayMode ? 0.5 : 1; + } + get colorizeTileLayers(): boolean { + return this.#colorizeTileLayers; + } + set colorizeTileLayers(value: boolean) { + this.#colorizeTileLayers = Boolean(value); + for (const layer of this.tileLayers) { + if (this.#colorizeTileLayers) { + layer.filters = [recolorFilters[layer.id % 6]]; + } else { + layer.filters = []; + } + layer.visible = this.#tilesVisible; + } + } + + #selectCopies = true; + #selectTiles = true; + get selectCopies(): boolean { + return this.#selectCopies; + } + set selectCopies(value: boolean) { + value = Boolean(value); + this.#selectCopies = value; + } + get selectTiles(): boolean { + return this.#selectTiles; + } + set selectTiles(value: boolean) { + value = Boolean(value); + this.#selectTiles = value; + } + + goHome(): void { + this.riotEditor.zoom = 1; + ease.add(this.camera, { + x: this.ctRoom.width / 2, + y: this.ctRoom.height / 2, + scale: 1 + }, { + duration: 500 + }) + .on('each', () => { + this.riotEditor.refs.zoomLabel.innerHTML = `${Math.round(this.getZoom())}%`; + }); + } + zoomTo(zoom: number): void { + const scale = 1 / zoom * 100; + ease.add(this.camera, { + scale + }, { + duration: 500 + }) + .on('each', () => { + this.riotEditor.refs.zoomLabel.innerHTML = `${Math.round(this.getZoom())}%`; + }); + } + getZoom(): number { + // Somehow, when updating a room-editor tag, it loses camera but not the editor itself + return this.camera ? (1 / this.camera.scale.x * 100) : 100; + } + + /** + * Returns a base64 string of a room's main viewport. + * It is expected to be run when the room editor is no longer needed, + * as it repositions the room. + */ + getSplashScreen(big: boolean): HTMLCanvasElement { + const w = big ? 340 : 64, + h = big ? 256 : 64; + const renderTexture = PIXI.RenderTexture.create({ + width: w, + height: h + }); + this.overlays.visible = false; + this.transformer.visible = false; + this.pointerCoords.visible = false; + this.clicktrap.width = w; + this.clicktrap.height = h; + this.clicktrap.alpha = 1; + this.clicktrap.tint = this.renderer.backgroundColor; + this.room.scale.set(Math.min(w / this.ctRoom.width, h / this.ctRoom.height)); + this.room.x = (w - this.ctRoom.width * this.room.scale.x) / 2; + this.room.y = (h - this.ctRoom.height * this.room.scale.y) / 2; + this.renderer.render(this.stage, renderTexture); + return this.renderer.extract.canvas(renderTexture); + } +} + + +const setup = (canvas: HTMLCanvasElement, editorTag: IRoomEditorRiotTag): RoomEditor => { + const pixelart = Boolean(currentProject.settings.rendering.pixelatedrender); + editorTag.pixiEditor = new RoomEditor({ + view: canvas + }, editorTag, pixelart); + return editorTag.pixiEditor; +}; + + +export { + setup, + IRoomEditorInteraction, + RoomEditor +}; diff --git a/src/node_requires/roomEditor/interactions/camera/home.ts b/src/node_requires/roomEditor/interactions/camera/home.ts new file mode 100644 index 000000000..9f5f81583 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/camera/home.ts @@ -0,0 +1,16 @@ +import {IRoomEditorInteraction} from '..'; + +const goHome: IRoomEditorInteraction = { + ifListener: 'home', + if() { + return true; + }, + listeners: { + home(e, riotTag, affixedData, callback) { + this.goHome(); + callback(); + } + } +}; + +export {goHome}; diff --git a/src/node_requires/roomEditor/interactions/camera/move.ts b/src/node_requires/roomEditor/interactions/camera/move.ts new file mode 100644 index 000000000..603a8b427 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/camera/move.ts @@ -0,0 +1,46 @@ +import {IRoomEditorInteraction} from '..'; + +interface IMoveCameraAffixedData { + cameraFromPos: { + x: number, + y: number + }, + fromOffsetPos: { + x: number, + y: number + } +} + +const moveCameraOnWheelPress: IRoomEditorInteraction = { + ifListener: 'pointerdown', + if(e) { + // Checks for a pressed mouse wheel + return e.data.button === 1; + }, + listeners: { + pointerdown(e, roomTag, affixedData) { + affixedData.cameraFromPos = { + x: this.camera.x, + y: this.camera.y + }; + affixedData.fromOffsetPos = { + x: e.data.global.x, + y: e.data.global.y + }; + }, + pointermove(e, roomTag, affixedData) { + const cfp = affixedData.cameraFromPos, + op = affixedData.fromOffsetPos; + this.camera.x = cfp.x + (op.x - e.data.global.x) * this.camera.scale.x; + this.camera.y = cfp.y + (op.y - e.data.global.y) * this.camera.scale.y; + this.camera.updateTransform(); + this.tickBackgrounds(); + }, + pointerup: (e, roomTag, affixedData, callback) => { + callback(); + } + } +}; +moveCameraOnWheelPress.listeners.pointerupoutside = moveCameraOnWheelPress.listeners.pointerup; + +export {moveCameraOnWheelPress}; diff --git a/src/node_requires/roomEditor/interactions/camera/zoom.ts b/src/node_requires/roomEditor/interactions/camera/zoom.ts new file mode 100644 index 000000000..d3bcb17f2 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/camera/zoom.ts @@ -0,0 +1,48 @@ +import {IRoomEditorInteraction} from '../..'; +import {ease, Easing} from 'node_modules/pixi-ease'; + +interface IZoomData { + ease: Easing +} + +const zoomInteraction: IRoomEditorInteraction = { + ifListener: 'wheel', + if() { + return true; + }, + listeners: { + wheel(e, roomTag, affixedData, finishCallback) { + const oldZoom = roomTag.zoom; + const dx = this.screen.width / 2 - e.data.global.x, + dy = this.screen.height / 2 - e.data.global.y; + let newZoom; + if ((e.data.originalEvent as WheelEvent).deltaY < 0) { + newZoom = oldZoom * 0.75; + } else { + newZoom = oldZoom * 1.25; + } + if (Math.abs(newZoom - 1) < 0.1) { + newZoom = 1; + } + roomTag.zoom = newZoom; + if (affixedData.ease) { + affixedData.ease.remove(); + } + affixedData.ease = ease.add(this.camera, { + scale: newZoom, + x: this.camera.x + dx * (newZoom - this.camera.scale.x), + y: this.camera.y + dy * (newZoom - this.camera.scale.y) + }, { + duration: 200 + }) + .on('each', () => { + this.camera.updateTransform(); + this.realignCamera(); + roomTag.refs.zoomLabel.innerHTML = `${Math.round(this.getZoom())}%`; + }) + .once('complete', finishCallback); + } + } +}; + +export {zoomInteraction}; diff --git a/src/node_requires/roomEditor/interactions/copies/deleteCopies.ts b/src/node_requires/roomEditor/interactions/copies/deleteCopies.ts new file mode 100644 index 000000000..b442bb650 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/copies/deleteCopies.ts @@ -0,0 +1,40 @@ +import {IRoomEditorInteraction} from '../..'; +import {Copy} from '../../entityClasses/Copy'; + +type affixedData = { + deleted: Set<[Copy]>; +} + +export const deleteCopies: IRoomEditorInteraction = { + ifListener: 'pointerdown', + if(e) { + if (this.riotEditor.currentTool !== 'addCopies') { + return false; + } + return e.data.button === 0 && e.data.originalEvent.ctrlKey; + }, + listeners: { + pointerdown(e, riotTag, affixedData) { + affixedData.deleted = new Set(); + if (e.target instanceof Copy) { + affixedData.deleted.add([e.target.detach()]); + } + }, + pointermove(e, riotTag, affixedData) { + if (e.target instanceof Copy) { + affixedData.deleted.add([e.target.detach()]); + } + }, + pointerup(e, roomTag, affixedData, callback) { + if (affixedData.deleted.size) { + this.history.pushChange({ + type: 'deletion', + deleted: affixedData.deleted + }); + } + callback(); + } + } +}; + +deleteCopies.listeners.pointerupoutside = deleteCopies.listeners.pointerup; diff --git a/src/node_requires/roomEditor/interactions/copies/placeCopy.ts b/src/node_requires/roomEditor/interactions/copies/placeCopy.ts new file mode 100644 index 000000000..16f83a4ce --- /dev/null +++ b/src/node_requires/roomEditor/interactions/copies/placeCopy.ts @@ -0,0 +1,148 @@ +import {IRoomEditorInteraction, RoomEditor} from '../..'; +import {Copy} from '../../entityClasses/Copy'; + +import {calcPlacement} from '../placementCalculator'; + +import {soundbox} from '../../../3rdparty/soundbox'; + +interface IAffixedData { + mode: 'free' | 'straight'; + startPos: PIXI.IPoint; + prevPos: PIXI.IPoint; + prevLength: number; + stepX: number; + stepY: number; + diagonalGrid: boolean; + gridX: number; + gridY: number; + noGrid: boolean; + created: Set<[Copy]>; +} + +const createCopy = ( + pos: PIXI.IPoint, + template: ITemplate, + editor: RoomEditor, + ghost?: boolean +) => new Copy({ + x: pos.x, + y: pos.y, + exts: {}, + customProperties: {}, + scale: { + x: 1, + y: 1 + }, + uid: template.uid, + opacity: 1, + rotation: 0, + tint: 0xffffff +}, editor, ghost); + +export const placeCopy: IRoomEditorInteraction = { + ifListener: 'pointerdown', + if(e, riotTag) { + if (this.riotEditor.currentTool !== 'addCopies') { + return false; + } + if (e.data.button !== 0) { + return false; + } + return riotTag.currentTemplate && riotTag.currentTemplate !== -1; + }, + listeners: { + pointerdown(e, roomTag, affixedData) { + this.compoundGhost.removeChildren(); + affixedData.created = new Set(); + // Two possible modes: placing in straight vertical/horizontal/diagonal lines + // and in a free form, like drawing with a brush. + // Straight method creates a ghost preview before actually creating all the copies, + // while the free form places copies as a user moves their cursor. + if (e.data.originalEvent.shiftKey) { + affixedData.mode = 'straight'; + affixedData.prevLength = 1; + } else { + affixedData.mode = 'free'; + } + affixedData.gridX = this.ctRoom.gridX; + affixedData.gridY = this.ctRoom.gridY; + affixedData.diagonalGrid = this.ctRoom.diagonalGrid; + affixedData.startPos = affixedData.prevPos = this.snapTarget.position.clone(); + affixedData.noGrid = !roomTag.gridOn || roomTag.freePlacementMode; + const newCopy = createCopy( + affixedData.startPos, + this.riotEditor.currentTemplate as ITemplate, + this + ); + affixedData.created.add([newCopy]); + this.room.addChild(newCopy); + affixedData.stepX = affixedData.stepY = 1; + soundbox.play('Wood_Start'); + }, + pointermove(e, roomTag, affixedData) { + affixedData.noGrid = !roomTag.gridOn || roomTag.freePlacementMode; + const newPos = this.snapTarget.position.clone(); + const ghosts = calcPlacement( + newPos, + affixedData, + ((position): Copy => { + soundbox.play('Wood_Start'); + const copy = createCopy( + position, + this.riotEditor.currentTemplate as ITemplate, + this + ); + this.room.addChild(copy); + affixedData.created.add([copy]); + return copy; + }) + ); + // Play feedback sound on length change + if (ghosts.length !== affixedData.prevLength) { + affixedData.prevLength = ghosts.length; + soundbox.play('Wood_Start'); + } + // Remove excess ghost instances + if (this.compoundGhost.children.length > ghosts.length) { + this.compoundGhost.removeChildren(ghosts.length); + } + // Add missing ghost instances + while (this.compoundGhost.children.length < ghosts.length) { + this.compoundGhost.addChild(createCopy( + affixedData.startPos, + this.riotEditor.currentTemplate as ITemplate, + this, + true + )); + } + for (let i = 0; i < ghosts.length; i++) { + const ghost = this.compoundGhost.children[i]; + ghost.x = ghosts[i].x; + ghost.y = ghosts[i].y; + } + }, + pointerup(e, roomTag, affixedData, callback) { + if (affixedData.mode === 'straight') { + // Replace all the preview copies with real ones + for (const ghost of this.compoundGhost.children) { + const copy = createCopy( + ghost.position, + this.riotEditor.currentTemplate as ITemplate, + this + ); + this.room.addChild(copy); + affixedData.created.add([copy]); + } + } + soundbox.play('Wood_End'); + this.compoundGhost.removeChildren(); + this.history.pushChange({ + type: 'creation', + created: affixedData.created + }); + callback(); + } + } +}; + +placeCopy.listeners.pointerupoutside = placeCopy.listeners.pointerup; diff --git a/src/node_requires/roomEditor/interactions/copyPaste.ts b/src/node_requires/roomEditor/interactions/copyPaste.ts new file mode 100644 index 000000000..fbdd3cf12 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/copyPaste.ts @@ -0,0 +1,126 @@ +import {IRoomEditorInteraction} from '..'; +import {Copy} from '../entityClasses/Copy'; +import {Tile} from '../entityClasses/Tile'; +import {TileLayer} from '../entityClasses/TileLayer'; + +import {snapToDiagonalGrid, snapToRectangularGrid} from '../common'; + +import {getTemplateFromId} from '../../resources/templates'; + +export const copy: IRoomEditorInteraction = { + ifListener: 'copy', + if() { + return this.riotEditor.currentTool === 'select' && this.currentSelection.size > 0; + }, + listeners: { + copy(e, riotEditor, affixedData, callback) { + this.clipboard.clear(); + for (const stuff of this.currentSelection) { + if (stuff instanceof Copy) { + this.clipboard.add([ + 'copy', + stuff.serialize() + ]); + } else if (stuff instanceof Tile) { + this.clipboard.add([ + 'tile', + stuff.serialize(), + stuff.parent + ]); + } + } + this.transformer.blink(); + callback(); + } + } +}; + +export const paste: IRoomEditorInteraction = { + ifListener: 'paste', + if() { + return this.clipboard.size > 0; + }, + listeners: { + paste(e, riotEditor, affixedData, callback) { + const createdSet = new Set<[Copy | Tile, TileLayer?]>(); + if (riotEditor.currentTool === 'select' && + this.currentSelection.size && + this.history.currentChange?.type === 'transformation' + ) { + this.history.snapshotTransforms(); + } + this.transformer.clear(); + const extraTileLayer = this.tileLayers.find(tl => tl.zIndex === 0) || new TileLayer({ + depth: 0, + tiles: [] + }, this); + for (const copied of this.clipboard) { + let created; + if (copied[0] === 'tile') { + const [, template, layer] = copied; + const target = this.tileLayers.includes(layer) ? layer : extraTileLayer; + created = new Tile(template, this, false); + target.addChild(created); + createdSet.add([created, target]); + } else if (copied[0] === 'copy') { + const [, template] = copied; + // Skip copies that no longer exist in the project + try { + getTemplateFromId(template.uid); + created = new Copy(template, this, false); + this.room.addChild(created); + createdSet.add([created]); + } catch (_) { + continue; + } + } else { + // Unsupported selectable entity + continue; + } + this.currentSelection.add(created); + } + if (extraTileLayer.children.length && !this.tileLayers.includes(extraTileLayer)) { + this.addTileLayer(extraTileLayer); + this.history.pushChange({ + type: 'tileLayerCreation', + created: extraTileLayer + }); + } else { + extraTileLayer.destroy(); + } + this.history.pushChange({ + type: 'creation', + created: createdSet + }); + if (riotEditor.currentTool !== 'select') { + riotEditor.setTool('select')(); + riotEditor.update(); + } + this.transformer.setup(true); + + // place the stuff under mouse cursor but do take the grid into account + const {mouse} = this.renderer.plugins.interaction; + const mousePos = mouse.getLocalPosition(this.room); + let dx = mousePos.x - this.transformer.transformPivotX, + dy = mousePos.y - this.transformer.transformPivotY; + if (this.riotEditor.gridOn) { + const snap = this.ctRoom.diagonalGrid ? snapToDiagonalGrid : snapToRectangularGrid; + const snapped = snap({ + x: dx, + y: dy + }, this.ctRoom.gridX, this.ctRoom.gridY); + dx = snapped.x; + dy = snapped.y; + } + this.transformer.transformPivotX += dx; + this.transformer.transformPivotY += dy; + this.transformer.applyTranslateX += dx; + this.transformer.applyTranslateY += dy; + this.transformer.applyTransforms(); + this.transformer.setup(); + riotEditor.refs.propertiesPanel.updatePropList(); + this.transformer.blink(); + callback(); + } + } +}; diff --git a/src/node_requires/roomEditor/interactions/history.ts b/src/node_requires/roomEditor/interactions/history.ts new file mode 100644 index 000000000..c5040c91a --- /dev/null +++ b/src/node_requires/roomEditor/interactions/history.ts @@ -0,0 +1,37 @@ +import {IRoomEditorInteraction} from '..'; + +export const undo: IRoomEditorInteraction = { + ifListener: 'undo', + if() { + // History object has its internal checks, + // and this is the only interaction that uses this listener, + // so always returning true is okay. + return true; + }, + listeners: { + undo(e, roomTag, affixedData, callback) { + if (this.history.undo()) { + e.data.originalEvent.preventDefault(); + } + callback(); + } + } +}; + +export const redo: IRoomEditorInteraction = { + ifListener: 'redo', + if() { + // History object has its internal checks, + // and this is the only interaction that uses this listener, + // so always returning true is okay. + return true; + }, + listeners: { + redo(e, roomTag, affixedData, callback) { + if (this.history.redo()) { + e.data.originalEvent.preventDefault(); + } + callback(); + } + } +}; diff --git a/src/node_requires/roomEditor/interactions/index.ts b/src/node_requires/roomEditor/interactions/index.ts new file mode 100644 index 000000000..9c4f228ba --- /dev/null +++ b/src/node_requires/roomEditor/interactions/index.ts @@ -0,0 +1,96 @@ +/* slint-disable no-use-before-define */ +import {RoomEditor} from '..'; +import {IRoomEditorRiotTag} from '../IRoomEditorRiotTag'; + +export enum EAllowedListeners { + pointertap, + pointerup, + pointerupoutside, + pointerdown, + pointermove, + pointerleave, + + // Custom events below + wheel, + + home, + + delete, + copy, + paste, + undo, + redo, + + nudgeright, + nudgeleft, + nudgeup, + nudgedown +} +export type AllowedListener = keyof typeof EAllowedListeners; +export const allowedListeners: AllowedListener[] = + Object.keys(EAllowedListeners) as AllowedListener[]; + +export type InteractionListener = ( + this: RoomEditor, + e: PIXI.InteractionEvent, + roomTag: IRoomEditorRiotTag, + affixedData: affixedInterface, + finishCallback: () => void +) => void; +export interface IRoomEditorInteraction { + ifListener: AllowedListener; + /** + * Checks if it is possible to run an interaction now. + * If this method returns true, all the further interactions will be skipped + * until the startingAction calls the provided finishCallback + */ + if: (this: RoomEditor, e: PIXI.InteractionEvent, roomTag: IRoomEditorRiotTag) => boolean; + listeners: Partial>>; +} + +import {updateMousePosition} from './mousePosLabel'; +import {moveCameraOnWheelPress} from './camera/move'; +import {goHome} from './camera/home'; +import {zoomInteraction} from './camera/zoom'; +import {placeCopy} from './copies/placeCopy'; +import {deleteCopies} from './copies/deleteCopies'; +import {placeTile} from './tiles/placeTile'; +import {deleteTiles} from './tiles/deleteTiles'; +import {select} from './transformer/select'; +import {nudgeLeft, nudgeRight, nudgeUp, nudgeDown} from './transformer/nudge'; +import {rotateSelection} from './transformer/rotate'; +import {moveSelection} from './transformer/move'; +import {scaleSelection} from './transformer/scale'; +import {deleteSelected} from './transformer/delete'; +import {copy, paste} from './copyPaste'; +import {undo, redo} from './history'; + +export const interactions = [ + updateMousePosition, // Ambient interaction — never blocks the queue + + nudgeLeft, + nudgeRight, + nudgeUp, + nudgeDown, + + copy, + paste, + undo, + redo, + + rotateSelection, + moveSelection, + scaleSelection, + deleteSelected, + select, + + deleteCopies, + placeCopy, + + deleteTiles, + placeTile, + + zoomInteraction, + moveCameraOnWheelPress, + goHome +]; diff --git a/src/node_requires/roomEditor/interactions/mousePosLabel.ts b/src/node_requires/roomEditor/interactions/mousePosLabel.ts new file mode 100644 index 000000000..52a0def47 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/mousePosLabel.ts @@ -0,0 +1,15 @@ +import {IRoomEditorInteraction} from '..'; + +const updateMousePosition: IRoomEditorInteraction = { + ifListener: 'pointermove', + if() { + const x = Math.round(this.snapTarget.x * 100) / 100, + y = Math.round(this.snapTarget.y * 100) / 100; + this.pointerCoords.text = `(${x}; ${y})`; + // This interaction never actually marks itself as valid + return false; + }, + listeners: {} +}; + +export {updateMousePosition}; diff --git a/src/node_requires/roomEditor/interactions/placementCalculator.ts b/src/node_requires/roomEditor/interactions/placementCalculator.ts new file mode 100644 index 000000000..0c3ff99ad --- /dev/null +++ b/src/node_requires/roomEditor/interactions/placementCalculator.ts @@ -0,0 +1,136 @@ +/* +This code was separated for DRY principle, as almost the same code +is used while placing tiles and copies. + +This code is intended to be used as part of pointermove event, +and returns a list of locations of ghost objects, which is not empty +only while using parallel placement. + +During the free placement, it may call placeImmediately as a callback +when a new object should be placed. +*/ + +import {fromRectangular, fromDiagonal, toRectangular, toDiagonal} from '../common'; + +type PlacementData = { + mode: 'free' | 'straight'; + startPos: PIXI.IPoint; + prevPos: PIXI.IPoint; + prevLength: number; + stepX: number; + stepY: number; + diagonalGrid: boolean; + gridX: number; + gridY: number; + noGrid: boolean; +}; + +type ghostPoints = { + x: number; + y: number; +}[]; +type ISimplePoint = { + x: number; + y: number; +}; + +// eslint-disable-next-line max-lines-per-function +export const calcPlacement = ( + newPos: PIXI.IPoint, + affixedData: PlacementData, + placeImmediately: (position: PIXI.IPoint) => void +): ghostPoints => { + const from = affixedData.diagonalGrid ? fromDiagonal : fromRectangular; + const to = affixedData.diagonalGrid ? toDiagonal : toRectangular; + // Correct placement position based on step size. + const newPosOnGrid = to(newPos, affixedData.gridX, affixedData.gridY), + startPosOnGrid = to(affixedData.startPos, affixedData.gridX, affixedData.gridY); + let newPosWSkips: ISimplePoint | PIXI.IPoint = from({ + x: newPosOnGrid.x - ((startPosOnGrid.x - newPosOnGrid.x) % affixedData.stepX), + y: newPosOnGrid.y - ((startPosOnGrid.y - newPosOnGrid.y) % affixedData.stepY) + }, affixedData.gridX, affixedData.gridY); + newPosWSkips = new PIXI.Point(newPosWSkips.x, newPosWSkips.y); + + // Free placement (drawing) + if (affixedData.mode === 'free') { + // When working without a grid, place copies in a strip, + // spacing of which depends on the grid size. + if (affixedData.noGrid) { + let dx = (newPos.x - affixedData.prevPos.x) / affixedData.gridX, + dy = (newPos.y - affixedData.prevPos.y) / affixedData.gridY; + const l = Math.sqrt(dx * dx + dy * dy); + if (l >= 1) { + dx /= l; + dy /= l; + const placeX = dx * affixedData.gridX + affixedData.prevPos.x, + placeY = dy * affixedData.gridY + affixedData.prevPos.y; + const newPlace = new PIXI.Point(placeX, placeY); + placeImmediately(newPlace); + affixedData.prevPos = newPlace; + } + return []; + } + // Skip spawning if position on-grid didn't change from the previous frame. + if (newPosWSkips.x === affixedData.prevPos.x && newPosWSkips.y === affixedData.prevPos.y) { + return []; + } + placeImmediately(newPosWSkips as PIXI.Point); + affixedData.prevPos = newPosWSkips as PIXI.Point; + return []; + } + + affixedData.prevPos = newPos; + + // Straight-line placement + const startGrid = to( + affixedData.startPos, + affixedData.gridX, + affixedData.gridY + ); + const endGrid = to( + newPos, + affixedData.gridX, + affixedData.gridY + ); + const dx = Math.abs(startGrid.x - endGrid.x), + dy = Math.abs(startGrid.y - endGrid.y); + const straightEndGrid: ISimplePoint = { + x: 0, + y: 0 + }; + const angle = Math.atan2(dy, dx); + if (Math.abs(angle) > Math.PI * 0.375 && Math.abs(angle) < Math.PI * 0.525) { + // Seems to be a vertical line + straightEndGrid.x = startGrid.x; + straightEndGrid.y = endGrid.y; + } else if (Math.abs(angle) < Math.PI * 0.125) { + // Seems to be a horizontal line + straightEndGrid.x = endGrid.x; + straightEndGrid.y = startGrid.y; + } else { + // It is more or so diagonal + const max = Math.max(dx, dy); + straightEndGrid.x = endGrid.x > startGrid.x ? + startGrid.x + max : + startGrid.x - max; + straightEndGrid.y = endGrid.y > startGrid.y ? + startGrid.y + max : + startGrid.y - max; + } + const incX = Math.sign(straightEndGrid.x - startGrid.x) * affixedData.stepX, + incY = Math.sign(straightEndGrid.y - startGrid.y) * affixedData.stepY; + const l = Math.max(dx / affixedData.stepX, dy / affixedData.stepY); + const ghosts = []; + // Calculate ghost positions + for (let i = 0, {x, y} = startGrid; + i < l; + i++, x += incX, y += incY + ) { + const localPos = from({ + x: x + incX, + y: y + incY + }, affixedData.gridX, affixedData.gridY); + ghosts.push(localPos); + } + return ghosts; +}; diff --git a/src/node_requires/roomEditor/interactions/tiles/ITilePatch.d.ts b/src/node_requires/roomEditor/interactions/tiles/ITilePatch.d.ts new file mode 100644 index 000000000..675800135 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/tiles/ITilePatch.d.ts @@ -0,0 +1,7 @@ +export interface ITilePatch { + startX: number; + startY: number; + spanX: number; + spanY: number; + texture: ITexture; +} diff --git a/src/node_requires/roomEditor/interactions/tiles/deleteTiles.ts b/src/node_requires/roomEditor/interactions/tiles/deleteTiles.ts new file mode 100644 index 000000000..f8e3a2ae5 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/tiles/deleteTiles.ts @@ -0,0 +1,43 @@ +import {IRoomEditorInteraction} from '../..'; +import {Tile} from '../../entityClasses/Tile'; +import {TileLayer} from '../../entityClasses/TileLayer'; + +type affixedData = { + deleted: Set<[Tile, TileLayer]>; +} + +export const deleteTiles: IRoomEditorInteraction = { + ifListener: 'pointerdown', + if(e) { + if (this.riotEditor.currentTool !== 'addTiles' || !this.riotEditor.currentTileLayer) { + return false; + } + return e.data.button === 0 && e.data.originalEvent.ctrlKey; + }, + listeners: { + pointerdown(e, riotTag, affixedData) { + affixedData.deleted = new Set(); + if (e.target instanceof Tile && e.target.parent === riotTag.currentTileLayer) { + const {parent} = e.target; + affixedData.deleted.add([e.target.detach(), parent]); + } + }, + pointermove(e, riotTag, affixedData) { + if (e.target instanceof Tile && e.target.parent === riotTag.currentTileLayer) { + const {parent} = e.target; + affixedData.deleted.add([e.target.detach(), parent]); + } + }, + pointerup(e, roomTag, affixedData, callback) { + if (affixedData.deleted.size) { + this.history.pushChange({ + type: 'deletion', + deleted: affixedData.deleted + }); + } + callback(); + } + } +}; + +deleteTiles.listeners.pointerupoutside = deleteTiles.listeners.pointerup; diff --git a/src/node_requires/roomEditor/interactions/tiles/placeTile.ts b/src/node_requires/roomEditor/interactions/tiles/placeTile.ts new file mode 100644 index 000000000..399863478 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/tiles/placeTile.ts @@ -0,0 +1,192 @@ +import {Tile} from '../../entityClasses/Tile'; +import {TileLayer} from '../../entityClasses/TileLayer'; +import {IRoomEditorInteraction, RoomEditor} from '../..'; +import {calcPlacement} from '../placementCalculator'; +import {ITilePatch} from './ITilePatch'; + +import {soundbox} from '../../../3rdparty/soundbox'; + +interface IAffixedData { + mode: 'free' | 'straight'; + startPos: PIXI.IPoint; + prevPos: PIXI.IPoint; + prevLength: number; + stepX: number; + stepY: number; + diagonalGrid: boolean; + gridX: number; + gridY: number; + noGrid: boolean; + created: Set<[Tile, TileLayer]>; +} + +interface ISimplePoint { + x: number; + y: number; +} + +const createTile = ( + pos: ISimplePoint, + texture: string, + frame: number, + editor: RoomEditor, + ghost?: boolean +) => new Tile({ + x: pos.x, + y: pos.y, + scale: { + x: 1, + y: 1 + }, + opacity: 1, + rotation: 0, + tint: 0xffffff, + frame, + texture +}, editor, ghost); + +export const createTilePatch = ( + tilePatch: ITilePatch, + startPos: ISimplePoint, + editor: RoomEditor, + ghost?: boolean +): Tile[] => { + const tiles: Tile[] = []; + const {texture} = tilePatch; + for (let x = 0; x < tilePatch.spanX; x++) { + for (let y = 0; y < tilePatch.spanY; y++) { + const frame = x + tilePatch.startX + + (y + tilePatch.startY) * texture.grid[0]; + tiles.push(createTile({ + x: startPos.x + x * texture.width, + y: startPos.y + y * texture.height + }, texture.uid, frame, editor, ghost)); + } + } + return tiles; +}; + +export const placeTile: IRoomEditorInteraction = { + ifListener: 'pointerdown', + if(e, riotTag) { + if (this.riotEditor.currentTool !== 'addTiles' || !this.riotEditor.currentTileLayer) { + return false; + } + if (e.data.button !== 0) { + return false; + } + return Boolean(riotTag.tilePatch?.texture); + }, + listeners: { + pointerdown(e, riotTag, affixedData) { + this.compoundGhost.removeChildren(); + affixedData.created = new Set(); + // Two possible modes: placing in straight vertical/horizontal/diagonal lines + // and in a free form, like drawing with a brush. + // Straight method creates a ghost preview before actually creating all the copies, + // while the free form places copies as a user moves their cursor. + if (e.data.originalEvent.shiftKey) { + affixedData.mode = 'straight'; + affixedData.prevLength = 1; + } else { + affixedData.mode = 'free'; + } + affixedData.gridX = this.ctRoom.gridX; + affixedData.gridY = this.ctRoom.gridY; + const {texture} = riotTag.tilePatch; + // Allow skipping grid cells if texture's and room's grids match + if (this.ctRoom.gridX === texture.width && this.ctRoom.gridY === texture.height) { + affixedData.stepX = riotTag.tilePatch.spanX; + affixedData.stepY = riotTag.tilePatch.spanY; + } else { + affixedData.stepX = affixedData.stepY = 1; + } + affixedData.diagonalGrid = this.ctRoom.diagonalGrid; + affixedData.noGrid = !riotTag.gridOn || riotTag.freePlacementMode; + affixedData.startPos = affixedData.prevPos = this.snapTarget.position.clone(); + const newTiles = createTilePatch( + riotTag.tilePatch, + affixedData.startPos, + this + ); + riotTag.currentTileLayer.addChild(...newTiles); + for (const tile of newTiles) { + affixedData.created.add([tile, tile.parent]); + } + soundbox.play('Wood_Start'); + }, + pointermove(e, riotTag, affixedData) { + affixedData.noGrid = !riotTag.gridOn || riotTag.freePlacementMode; + const newPos = this.snapTarget.position.clone(); + const ghosts = calcPlacement( + newPos, + affixedData, + (position => { + soundbox.play('Wood_Start'); + const newTiles = createTilePatch( + riotTag.tilePatch, + position, + this + ); + riotTag.currentTileLayer.addChild(...newTiles); + for (const tile of newTiles) { + affixedData.created.add([tile, tile.parent]); + } + }) + ); + // Play feedback sound on length change + if (ghosts.length !== affixedData.prevLength) { + affixedData.prevLength = ghosts.length; + soundbox.play('Wood_Start'); + } + // Remove excess ghost instances + if (this.compoundGhost.children.length > ghosts.length) { + this.compoundGhost.removeChildren(ghosts.length); + } + // Add missing ghost instances + // Tile patches are grouped into containers + while (this.compoundGhost.children.length < ghosts.length) { + const ghostTilePatch = new PIXI.Container(); + ghostTilePatch.addChild(...createTilePatch(riotTag.tilePatch, { + x: 0, + y: 0 + }, this, true)); + this.compoundGhost.addChild(ghostTilePatch); + } + // Reposition ghost containers + for (let i = 0; i < ghosts.length; i++) { + const ghost = this.compoundGhost.children[i]; + ghost.x = ghosts[i].x; + ghost.y = ghosts[i].y; + } + }, + pointerup(e, riotTag, affixedData, callback) { + if (affixedData.mode === 'straight') { + // Replace all the preview copies with real ones + for (const ghost of this.compoundGhost.children) { + const newTiles = createTilePatch( + riotTag.tilePatch, + ghost.position, + this + ); + riotTag.currentTileLayer.addChild(...newTiles); + for (const tile of newTiles) { + affixedData.created.add([tile, tile.parent]); + } + } + } + soundbox.play('Wood_End'); + this.compoundGhost.removeChildren(); + this.stage.interactive = true; // Causes to rediscover nested elements + if (affixedData.created.size) { + this.history.pushChange({ + type: 'creation', + created: affixedData.created + }); + } + callback(); + } + } +}; + +placeTile.listeners.pointerupoutside = placeTile.listeners.pointerup; diff --git a/src/node_requires/roomEditor/interactions/transformer/delete.ts b/src/node_requires/roomEditor/interactions/transformer/delete.ts new file mode 100644 index 000000000..da6ac95e0 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/transformer/delete.ts @@ -0,0 +1,32 @@ +import {IRoomEditorInteraction} from '..'; +import {Copy} from '../../entityClasses/Copy'; +import {Tile} from '../../entityClasses/Tile'; +import {TileLayer} from '../../entityClasses/TileLayer'; + +export const deleteSelected: IRoomEditorInteraction = { + ifListener: 'delete', + if() { + return this.riotEditor.currentTool === 'select' && this.currentSelection.size > 0; + }, + listeners: { + delete(e, riotEditor, affixedData, callback) { + const changes = new Set<[Copy | Tile, TileLayer?]>(); + for (const stuff of this.currentSelection) { + if (stuff instanceof Tile) { + const {parent} = stuff; + changes.add([stuff.detach(), parent]); + } else if (stuff instanceof Copy) { + changes.add([stuff.detach()]); + } + } + this.history.pushChange({ + type: 'deletion', + deleted: changes + }); + this.transformer.clear(); + riotEditor.refs.propertiesPanel.updatePropList(); + + callback(); + } + } +}; diff --git a/src/node_requires/roomEditor/interactions/transformer/move.ts b/src/node_requires/roomEditor/interactions/transformer/move.ts new file mode 100644 index 000000000..d31176eae --- /dev/null +++ b/src/node_requires/roomEditor/interactions/transformer/move.ts @@ -0,0 +1,57 @@ +import {IRoomEditorInteraction} from '../..'; +import {snapToRectangularGrid, snapToDiagonalGrid} from '../../common'; + +interface IAffixedData { + startingGlobalPos: PIXI.Point; + startingTranslateX: number; + startingTranslateY: number; + startingPivotX: number; + startingPivotY: number; +} + +export const moveSelection: IRoomEditorInteraction = { + ifListener: 'pointerdown', + if(e) { + if (this.riotEditor.currentTool !== 'select') { + return false; + } + if (this.currentSelection.size === 0) { + return false; + } + return e.target === this.transformer.handleCenter; + }, + listeners: { + pointerdown(e, roomTag, affixed) { + affixed.startingGlobalPos = e.data.global.clone(); + affixed.startingTranslateX = this.transformer.applyTranslateX; + affixed.startingTranslateY = this.transformer.applyTranslateY; + affixed.startingPivotX = this.transformer.transformPivotX; + affixed.startingPivotY = this.transformer.transformPivotY; + }, + pointermove(e, roomTag, affixed) { + let delta = { + x: (e.data.global.x - affixed.startingGlobalPos.x) * this.camera.scale.x, + y: (e.data.global.y - affixed.startingGlobalPos.y) * this.camera.scale.x + }; + // Ignore grid when alt key is pressed, or when the grid is disabled + if (!e.data.originalEvent.altKey && roomTag.gridOn) { + // Otherwise, snap delta to a grid + if (this.ctRoom.diagonalGrid) { + delta = snapToDiagonalGrid(delta, this.ctRoom.gridX, this.ctRoom.gridY); + } else { + delta = snapToRectangularGrid(delta, this.ctRoom.gridX, this.ctRoom.gridY); + } + } + this.transformer.applyTranslateX = affixed.startingTranslateX + delta.x; + this.transformer.applyTranslateY = affixed.startingTranslateY + delta.y; + this.transformer.transformPivotX = affixed.startingPivotX + delta.x; + this.transformer.transformPivotY = affixed.startingPivotY + delta.y; + this.transformer.applyTransforms(); + this.riotEditor.refs.propertiesPanel.updatePropList(); + }, + pointerup(e, roomTag, affixedData, callback) { + this.history.snapshotTransforms(); + callback(); + } + } +}; diff --git a/src/node_requires/roomEditor/interactions/transformer/nudge.ts b/src/node_requires/roomEditor/interactions/transformer/nudge.ts new file mode 100644 index 000000000..addc0bc2c --- /dev/null +++ b/src/node_requires/roomEditor/interactions/transformer/nudge.ts @@ -0,0 +1,66 @@ +import {IRoomEditorInteraction} from '..'; + +export const nudgeDown: IRoomEditorInteraction = { + ifListener: 'nudgedown', + if() { + return Boolean(this.riotEditor.currentTool === 'select' && this.currentSelection.size); + }, + listeners: { + nudgedown(e, roomTag, affixedData, callback) { + const delta = e.data.originalEvent.ctrlKey ? 1 : this.ctRoom.gridY; + this.transformer.applyTranslateY += delta; + this.transformer.transformPivotY += delta; + this.transformer.applyTransforms(); + callback(); + } + } +}; + +export const nudgeUp: IRoomEditorInteraction = { + ifListener: 'nudgeup', + if() { + return Boolean(this.riotEditor.currentTool === 'select' && this.currentSelection.size); + }, + listeners: { + nudgeup(e, roomTag, affixedData, callback) { + const delta = e.data.originalEvent.ctrlKey ? 1 : this.ctRoom.gridY; + this.transformer.applyTranslateY -= delta; + this.transformer.transformPivotY -= delta; + this.transformer.applyTransforms(); + callback(); + } + } +}; + +export const nudgeLeft: IRoomEditorInteraction = { + ifListener: 'nudgeleft', + if() { + return Boolean(this.riotEditor.currentTool === 'select' && this.currentSelection.size); + }, + listeners: { + nudgeleft(e, roomTag, affixedData, callback) { + const delta = e.data.originalEvent.ctrlKey ? 1 : this.ctRoom.gridX; + this.transformer.applyTranslateX -= delta; + this.transformer.transformPivotX -= delta; + this.transformer.applyTransforms(); + callback(); + } + } +}; + +export const nudgeRight: IRoomEditorInteraction = { + ifListener: 'nudgeright', + if() { + return Boolean(this.riotEditor.currentTool === 'select' && this.currentSelection.size); + }, + listeners: { + nudgeright(e, roomTag, affixedData, callback) { + const delta = e.data.originalEvent.ctrlKey ? 1 : this.ctRoom.gridX; + this.transformer.applyTranslateX += delta; + this.transformer.transformPivotX += delta; + this.transformer.applyTransforms(); + this.history.snapshotTransforms(); + callback(); + } + } +}; diff --git a/src/node_requires/roomEditor/interactions/transformer/rotate.ts b/src/node_requires/roomEditor/interactions/transformer/rotate.ts new file mode 100644 index 000000000..d77c3a3c3 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/transformer/rotate.ts @@ -0,0 +1,46 @@ +import {IRoomEditorInteraction} from '../..'; + +import {pdn, degToRad, radToDeg} from '../../../utils/trigo'; + +export const rotateSelection: IRoomEditorInteraction = { + ifListener: 'pointerdown', + if(e) { + if (this.riotEditor.currentTool !== 'select') { + return false; + } + if (this.currentSelection.size === 0) { + return false; + } + return e.target === this.transformer.handleRotate; + }, + listeners: { + pointermove(e) { + const globalPivot = this.room.toGlobal(new PIXI.Point( + this.transformer.transformPivotX, + this.transformer.transformPivotY + )); + let rad = pdn( + globalPivot.x, + globalPivot.y, + e.data.global.x, + e.data.global.y + ); + // When the group is mirrored horizontally, + // the handle appears on the left side, not on the right side. + if (this.transformer.applyScaleX < 0) { + rad += Math.PI; + } + if (e.data.originalEvent.shiftKey) { + // Snap at 15 degrees + rad = degToRad(Math.round(radToDeg(rad) / 15) * 15); + } + this.transformer.applyRotation = rad; + this.transformer.applyTransforms(); + this.riotEditor.refs.propertiesPanel.updatePropList(); + }, + pointerup(e, roomTag, affixedData, callback) { + this.history.snapshotTransforms(); + callback(); + } + } +}; diff --git a/src/node_requires/roomEditor/interactions/transformer/scale.ts b/src/node_requires/roomEditor/interactions/transformer/scale.ts new file mode 100644 index 000000000..75d46bc24 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/transformer/scale.ts @@ -0,0 +1,154 @@ +import {IRoomEditorInteraction} from '../..'; +import {Handle} from '../../entityClasses/Transformer'; + +import {rotateRad} from '../../../utils/trigo'; +import {snapToRectangularGrid, snapToDiagonalGrid} from '../../common'; + +interface IAffixedData { + axes: 'x' | 'y' | 'xy'; + startingSX: number; + startingSY: number; + startingTX: number; + startingTY: number; + startingPX: number; + startingPY: number; + startDragRoom: PIXI.IPoint; +} + +export const scaleSelection: IRoomEditorInteraction = { + ifListener: 'pointerdown', + if(e) { + if (this.riotEditor.currentTool !== 'select') { + return false; + } + if (this.currentSelection.size === 0) { + return false; + } + return this.transformer.scaleHandles.includes(e.target as Handle); + }, + listeners: { + pointerdown(e, roomTag, affixedData) { + // Fix scaling to zero + affixedData.startingSX = this.transformer.applyScaleX || 1; + affixedData.startingSY = this.transformer.applyScaleY || 1; + affixedData.startingTX = this.transformer.applyTranslateX; + affixedData.startingTY = this.transformer.applyTranslateY; + affixedData.startingPX = this.transformer.transformPivotX; + affixedData.startingPY = this.transformer.transformPivotY; + affixedData.startDragRoom = this.room.toLocal(e.target.position); + const t = this.transformer; + if ([t.handleBL, t.handleBR, t.handleTL, t.handleTR].includes(e.target as Handle)) { + affixedData.axes = 'xy'; + } else if ([t.handleR, t.handleL].includes(e.target as Handle)) { + affixedData.axes = 'x'; + } else { + affixedData.axes = 'y'; + } + }, + // eslint-disable-next-line max-lines-per-function + pointermove(e, riotTag, affixedData) { + const {transformer} = this; + const scaleSymmetrically = e.data.originalEvent.ctrlKey; + const proportionalScale = e.data.originalEvent.shiftKey && affixedData.axes === 'xy'; + const snapToGrid = !e.data.originalEvent.altKey && riotTag.gridOn; + const {axes} = affixedData; + const dragEndRoom = this.room.toLocal(e.data.global); + if (snapToGrid) { + if (this.ctRoom.diagonalGrid) { + ({x: dragEndRoom.x, y: dragEndRoom.y} = snapToDiagonalGrid( + dragEndRoom, + this.ctRoom.gridX, + this.ctRoom.gridY + )); + } else { + ({x: dragEndRoom.x, y: dragEndRoom.y} = snapToRectangularGrid( + dragEndRoom, + this.ctRoom.gridX, + this.ctRoom.gridY + )); + } + } + + const startUnrotated = rotateRad( + affixedData.startDragRoom.x - affixedData.startingPX, + affixedData.startDragRoom.y - affixedData.startingPY, + -transformer.applyRotation + ); + const endUnrotated = rotateRad( + dragEndRoom.x - affixedData.startingPX, + dragEndRoom.y - affixedData.startingPY, + -transformer.applyRotation + ); + // kx and ky describe how much a container expanded relative to its starting width + let kx = endUnrotated[0] / startUnrotated[0], + ky = endUnrotated[1] / startUnrotated[1]; + if (!Number.isFinite(kx)) { + kx = 1; + } + if (!Number.isFinite(ky)) { + ky = 1; + } + if (proportionalScale) { + const max = Math.max(Math.abs(kx), Math.abs(ky)); + kx = (Math.sign(kx) || 1) * max; + ky = (Math.sign(ky) || 1) * max; + if (Math.sign(kx * ky) === -1) { + // Constraint handles to its own diagonal + kx *= -1; + } + // Project an end point along a diagonal + endUnrotated[0] = kx * startUnrotated[0]; + endUnrotated[1] = ky * startUnrotated[1]; + } + if (!scaleSymmetrically) { + kx = kx * 0.5 + 0.5; + ky = ky * 0.5 + 0.5; + } + transformer.applyTranslateX = affixedData.startingTX; + transformer.applyTranslateY = affixedData.startingTY; + transformer.transformPivotX = affixedData.startingPX; + transformer.transformPivotY = affixedData.startingPY; + if (axes === 'x' || axes === 'xy') { + transformer.applyScaleX = affixedData.startingSX * kx || 1; + if (!scaleSymmetrically) { + // Shift the pivot and update the translate values of the transformer widget + // so entities slide in the specified direction, + // keeping the opposing side in its place + const shift = rotateRad( + endUnrotated[0] - startUnrotated[0], + 0, + transformer.applyRotation + ); + transformer.applyTranslateX += shift[0] / 2; + transformer.applyTranslateY += shift[1] / 2; + transformer.transformPivotX += shift[0] / 2; + transformer.transformPivotY += shift[1] / 2; + } + } + if (axes === 'y' || axes === 'xy') { + transformer.applyScaleY = affixedData.startingSY * ky || 1; + if (!scaleSymmetrically) { + // Shift the pivot and update the translate values of the transformer widget + // so entities slide in the specified direction, + // keeping the opposing side in its place + const shift = rotateRad( + 0, + endUnrotated[1] - startUnrotated[1], + transformer.applyRotation + ); + transformer.applyTranslateX += shift[0] * 0.5; + transformer.applyTranslateY += shift[1] * 0.5; + transformer.transformPivotX += shift[0] * 0.5; + transformer.transformPivotY += shift[1] * 0.5; + } + } + + transformer.applyTransforms(); + this.riotEditor.refs.propertiesPanel.updatePropList(); + }, + pointerup(e, roomTag, affixedData, callback) { + this.history.snapshotTransforms(); + callback(); + } + } +}; diff --git a/src/node_requires/roomEditor/interactions/transformer/select.ts b/src/node_requires/roomEditor/interactions/transformer/select.ts new file mode 100644 index 000000000..12964be48 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/transformer/select.ts @@ -0,0 +1,162 @@ +import {IRoomEditorInteraction} from '../..'; +import {Copy} from '../../entityClasses/Copy'; +import {Tile} from '../../entityClasses/Tile'; + +interface IAffixedData { + startRoomPos: PIXI.IPoint; + startClientPos: PIXI.IPoint; + startSelected: PIXI.DisplayObject; + mode: 'pick' | 'add' | 'remove' | 'toggle'; +} + +const modifySet = ( + set: Set, + delta: PIXI.DisplayObject[], + mode: IAffixedData['mode'] +) => { + switch (mode) { + case 'add': + for (const elt of delta) { + set.add(elt); + } + break; + case 'remove': + for (const elt of delta) { + set.delete(elt); + } + break; + case 'toggle': + for (const elt of delta) { + if (set.has(elt)) { + set.delete(elt); + } else { + set.add(elt); + } + } + break; + default: + set.clear(); + for (const elt of delta) { + set.add(elt); + } + break; + } +}; + +const getCenter = function (obj: PIXI.DisplayObject, room: PIXI.Container): PIXI.IPoint { + const bounds = obj.getBounds(); + const centerGlobal = new PIXI.Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); + return room.toLocal(centerGlobal); +}; + +/** + * An interaction that selects individual objects on clicks + * and multiple ones on click+drag, in a rectangular selection. + * Manages RoomEditor.marqueeBox position. + * + * Depending on what keys were pressed on selection start (ctrl, shift, alt), + * the operation shifts between three different modes (toggle, add, remove) + */ +const select: IRoomEditorInteraction = { + ifListener: 'pointerdown', + if(e) { + if (e.data.button !== 0) { + return false; + } + return this.riotEditor.currentTool === 'select'; + }, + listeners: { + pointerdown(e, riotTag, affixedData) { + if (e.data.originalEvent.shiftKey) { + affixedData.mode = 'add'; + } else if (e.data.originalEvent.ctrlKey) { + affixedData.mode = 'toggle'; + } else if (e.data.originalEvent.altKey) { + affixedData.mode = 'remove'; + } else { + affixedData.mode = 'pick'; + } + affixedData.startClientPos = e.data.global.clone(); + affixedData.startRoomPos = this.room.toLocal(e.data.global); + this.marqueeBox.redrawBox(affixedData.startRoomPos.x, affixedData.startRoomPos.y, 0, 0); + affixedData.startSelected = e.target; + }, + pointermove(e, riotTag, affixedData) { + const roomPos = this.room.toLocal(e.data.global); + this.marqueeBox.visible = true; + this.marqueeBox.redrawBox( + affixedData.startRoomPos.x, + affixedData.startRoomPos.y, + roomPos.x - affixedData.startRoomPos.x, + roomPos.y - affixedData.startRoomPos.y + ); + }, + pointerup(e, riotTag, affixedData, callback) { + // Apply any possible property changes to the previous selectio set + this.riotEditor.refs.propertiesPanel.applyChanges(); + + const selectMap: [boolean, Iterable][] = [ + [this.selectCopies, this.copies], + [this.selectTiles, this.tiles] + ]; + const roomPos = this.room.toLocal(e.data.global); + const dxClient = e.data.global.x - affixedData.startClientPos.x, + dyClient = e.data.global.y - affixedData.startClientPos.y; + const lClient = Math.sqrt(dxClient ** 2 + dyClient ** 2); + // Too small selections on a client scale count as clicks + if (lClient < 8) { + let s = affixedData.startSelected, + currentSelection; + // Pick a suitable entity under the cursor (try both from pointerdown and pointerup) + if ((s instanceof Copy && this.selectCopies) || + (s instanceof Tile && this.selectTiles) + ) { + currentSelection = s; + } else { + s = e.target; + if ((s instanceof Copy && this.selectCopies) || + (s instanceof Tile && this.selectTiles) + ) { + currentSelection = s; + } + } + if (currentSelection) { + modifySet(this.currentSelection, [s], affixedData.mode); + } else if (affixedData.mode === 'pick') { + // If no keyboard modifiers were active and a user clicked, clear the selection + this.currentSelection.clear(); + } + } else { + // Rectangular selection + // Loop through all the selectable elements in the room and put into the selection + // those which *visible* centers are inside the rectangle (ignore pivots). + const delta = []; + const rect = new PIXI.Rectangle( + Math.min(affixedData.startRoomPos.x, roomPos.x), + Math.min(affixedData.startRoomPos.y, roomPos.y), + Math.abs(roomPos.x - affixedData.startRoomPos.x), + Math.abs(roomPos.y - affixedData.startRoomPos.y) + ); + for (const selectType of selectMap) { + if (selectType[0]) { + for (const object of selectType[1]) { + const {x, y} = getCenter(object, this.room); + if (rect.contains(x, y)) { + delta.push(object); + } + } + } + } + modifySet(this.currentSelection, delta, affixedData.mode); + } + this.transformer.setup(); + this.marqueeBox.visible = false; + this.riotEditor.refs.propertiesPanel.updatePropList(); + callback(); + } + } +}; + +select.listeners.pointerupoutside = select.listeners.pointerup; + +export {select}; diff --git a/src/node_requires/themes/index.ts b/src/node_requires/themes/index.ts index 6900086a8..7c67350bc 100644 --- a/src/node_requires/themes/index.ts +++ b/src/node_requires/themes/index.ts @@ -1,4 +1,4 @@ -const path = require('path'); +import {join} from 'path'; const defaultTheme = 'Day'; const defaultMonacoTheme = defaultTheme; @@ -25,6 +25,21 @@ interface ITheme { css: string; } +// @see https://mmazzarolo.com/blog/2021-10-10-on-toggling-stylesheets/ +const waitForStylesheet = (): Promise => { + const stylesheet = [...document.styleSheets].find(s => s.ownerNode === document.getElementById('themeCSS')); + const oldHref = stylesheet?.href; + return new Promise((resolve) => { + const interval = setInterval(() => { + const stylesheet2 = [...document.styleSheets].find(s => s.ownerNode === document.getElementById('themeCSS')); + if (stylesheet2 && (!oldHref || (stylesheet2.href && oldHref !== stylesheet2.href))) { + clearInterval(interval); + resolve(); + } + }, 20); + }); +}; + var currentSwatches: Record; const registeredThemes: ITheme[] = []; @@ -37,7 +52,7 @@ const updateSwatches = (): void => { swatchTester.style.display = 'none'; document.body.appendChild(swatchTester); swatchTester.innerText = 'sausage'; - for (const swatch of ['act', 'acttext', 'accent1', 'borderPale', 'borderBright', 'text', 'backgroundDeeper', 'act-contrast', 'acttext-contrast', 'accent1-contrast']) { + for (const swatch of ['act', 'acttext', 'accent1', 'borderPale', 'borderBright', 'text', 'background', 'backgroundDeeper', 'act-contrast', 'acttext-contrast', 'accent1-contrast', 'red', 'green', 'orange']) { swatchTester.setAttribute('css-swatch', swatch); const style = window.getComputedStyle(swatchTester); currentSwatches[swatch] = style.getPropertyValue('color'); @@ -52,12 +67,12 @@ const mod = { } let monacoTheme; try { - monacoTheme = require(path.join('./data/node_requires/monaco-themes', `${name}.json`)); + monacoTheme = require(join('./data/node_requires/monaco-themes', `${name}.json`)); (window as Window).monaco.editor.defineTheme(name, monacoTheme); } catch (e) { // eslint-disable-next-line no-console console.warn('Could not load a monaco theme due to an error:', e, '\nFalling back to the default theme.'); - monacoTheme = require(path.join('./data/node_requires/monaco-themes', `${defaultMonacoTheme}.json`)); + monacoTheme = require(join('./data/node_requires/monaco-themes', `${defaultMonacoTheme}.json`)); (window as Window).monaco.editor.defineTheme(name, monacoTheme); } const css = `./data/theme${name}.css`; @@ -92,20 +107,19 @@ const mod = { } await fs.lstat(theme.css); const link = (document.getElementById('themeCSS') as HTMLLinkElement); - link.addEventListener('load', () => { - updateSwatches(); - }, { - once: true - }); // Avoid flickering on startup theme reloading if (link.href !== theme.css) { + const theWait = waitForStylesheet(); link.href = theme.css; + await theWait; + updateSwatches(); } (window as Window).monaco.editor.setTheme(theme.name); (window as Window).signals.trigger('UIThemeChanged', name); localStorage.UItheme = name; } catch (oO) { (window as Window).alertify.error(`Could not load theme ${name}. Rolling back to the default ${defaultTheme}.`); + console.error(oO); await mod.switchToTheme(defaultTheme); } }, @@ -126,6 +140,18 @@ const mod = { ...currentSwatches }; }, + getPixiSwatch(color: string): number { + if (!currentSwatches) { + updateSwatches(); + } + return PIXI.utils.rgb2hex(currentSwatches[color].split(', ').map(i => parseInt(i.replace(/[^0-9]/g, ''), 10) / 255)); + }, + getSwatch(color: string): string { + if (!currentSwatches) { + updateSwatches(); + } + return currentSwatches[color]; + }, updateSwatches }; diff --git a/src/node_requires/imageUtils.ts b/src/node_requires/utils/imageUtils.ts similarity index 100% rename from src/node_requires/imageUtils.ts rename to src/node_requires/utils/imageUtils.ts diff --git a/src/node_requires/utils/propUtils.ts b/src/node_requires/utils/propUtils.ts new file mode 100644 index 000000000..108357dc0 --- /dev/null +++ b/src/node_requires/utils/propUtils.ts @@ -0,0 +1,30 @@ +const getProp = function (context: unknown, path: string): unknown { + var way = path.split(/(? 1) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context = (context as any)[way[0]]; + way.shift(); + } + return context; +}; + +const writeProp = function (context: unknown, path: string, value: unknown): void { + var way = path.split(/(? 2) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (!(way[0] in (context as any))) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (context as any)[way[0]] = {}; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context = (context as any)[way[0]]; + way.shift(); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (context as any)[way[0]] = value; +}; + +export { + getProp, + writeProp +}; diff --git a/src/node_requires/utils/trigo.ts b/src/node_requires/utils/trigo.ts new file mode 100644 index 000000000..9e460f5f4 --- /dev/null +++ b/src/node_requires/utils/trigo.ts @@ -0,0 +1,51 @@ +/** lengthdir_x */ +export var ldx = function ldx(l: number, d: number): number { + return l * Math.cos(d); +}; +/** lengthdir_y */ +export var ldy = function ldy(l: number, d: number): number { + return l * Math.sin(d); +}; +/** Point-point DirectioN */ +export var pdn = function pdn(x1: number, y1: number, x2: number, y2: number): number { + return Math.atan2(y2 - y1, x2 - x1); +}; +/** Point-point DistanCe */ +export var pdc = function pdc(x1: number, y1: number, x2: number, y2: number): number { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); +}; +export var degToRad = function degToRad(deg: number): number { + return deg * Math.PI / -180; +}; +export var radToDeg = function radToDeg(rad: number): number { + return rad / Math.PI * -180; +}; +/** + * Rotates a vector (x; y) by rad around (0; 0) + * @param {number} x The x component + * @param {number} y The y component + * @param {number} rad The radian value to rotate by + * @returns {Array} A pair of new `x` and `y` parameters. + */ +export var rotateRad = function rotateRad(x: number, y: number, rad: number): [number, number] { + const sin = Math.sin(rad), + cos = Math.cos(rad); + return [ + cos * x - sin * y, + cos * y + sin * x + ]; +}; +export var deltaDirRad = function deltaDirRad(dir1: number, dir2: number): number { + dir1 = ((dir1 % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2); + dir2 = ((dir2 % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2); + var t = dir1, + h = dir2, + ta = h - t; + if (ta > Math.PI) { + ta -= Math.PI * 2; + } + if (ta < -Math.PI) { + ta += Math.PI * 2; + } + return ta; +}; diff --git a/src/pug/index.pug b/src/pug/index.pug index b3b3e1314..01b528b00 100644 --- a/src/pug/index.pug +++ b/src/pug/index.pug @@ -30,6 +30,15 @@ html process.versions.ctjs = require('./package.json').version; // Use the legacy version to support low-spec and dated devices running ct.IDE script(src="node_modules/pixi.js-legacy/dist/pixi-legacy.min.js") + script. + // PIXI-mousewheel plugin from https://github.com/Mwni/pixi-mousewheel + /* example usage: + displayObject.interactiveMousewheel = true + displayObject.on('mousewheel', (delta, event) => { + myOtherDisplayObject.y += delta * 100 + }) + */ + "use strict";!function(){var e=function(){function e(e){var t=this;this.app=e,this.eventHandler=function(e){return t.onMouseWheel(e)},this.app.view.addEventListener("mousewheel",this.eventHandler,{passive:!1}),this.app.view.addEventListener("DOMMouseScroll",this.eventHandler,{passive:!1})}var t=e.prototype;return t.onMouseWheel=function(e){var t=this.findScrollTarget({x:e.offsetX,y:e.offsetY});t&&(e.preventDefault(),t.emit("mousewheel",this.deriveNormalizedWheelDelta(e),e))},t.findScrollTarget=function(e){var t=this.app.renderer.plugins.interaction.hitTest(e);if(t&&t.interactiveMousewheel)return t},t.deriveNormalizedWheelDelta=function(e){return e.detail?e.wheelDelta?e.wheelDelta/e.detail/40*(e.detail>0?1:-1):-e.detail/3:e.wheelDelta/120},t.destroy=function(){this.app.view.removeEventListener("mousewheel",this.eventHandler),this.app.view.removeEventListener("DOMMouseScroll",this.eventHandler)},e}();Object.defineProperty(PIXI.DisplayObject.prototype,"interactiveMousewheel",{get:function(){return this._interactiveMousewheel},set:function(e){this._interactiveMousewheel=e,e&&!this.interactive&&(this.interactive=!0)}}),PIXI.Application.registerPlugin({init:function(t){this._mousewheelPlugin=new e(this)},destroy:function(){this._mousewheelPlugin.destroy()}})}(); script(src="data/ct.release/DragonBones.min.js") script(src="node_modules/pixi-particles/dist/pixi-particles.min.js") script. diff --git a/src/riotTags/debugger/debugger-toolbar.tag b/src/riotTags/debugger/debugger-toolbar.tag index 0d32664d4..00502ef38 100644 --- a/src/riotTags/debugger/debugger-toolbar.tag +++ b/src/riotTags/debugger/debugger-toolbar.tag @@ -159,9 +159,8 @@ debugger-toolbar const buff = new Buffer(shotBase64, 'base64'); const stream = fs.createWriteStream(fullPath); stream.on('finish', () => { - if (localStorage.disableSounds !== 'on') { - window.soundbox.play('Success'); - } + const {soundbox} = require('./data/node_requires/3rdparty/soundbox'); + soundbox.play('Success'); // eslint-disable-next-line no-new new Notification('Done!', { body: `Saved to ${fullPath} 👌`, @@ -211,4 +210,4 @@ debugger-toolbar nw.Window.get().close(); }; - nw.Window.get().on('close', this.closeItself); \ No newline at end of file + nw.Window.get().on('close', this.closeItself); diff --git a/src/riotTags/main-menu/export-mobile-panel.tag b/src/riotTags/main-menu/export-mobile-panel.tag index a9ddeb3ce..c18836bee 100644 --- a/src/riotTags/main-menu/export-mobile-panel.tag +++ b/src/riotTags/main-menu/export-mobile-panel.tag @@ -211,7 +211,7 @@ export-mobile-panel.aDimmer const {getDOMImage} = require('./data/node_requires/resources/textures'); const iconsSplashesPromises = []; const {imageCover, imageContain, imagePlaceInRect, imageRound, outputCanvasToFile} = - require('./data/node_requires/imageUtils'); + require('./data/node_requires/utils/imageUtils'); const projIconImage = await getDOMImage(projSettings.branding.icon || -1, 'ct_ide.png'); for (const name in androidIcons) { const icon = imageContain( diff --git a/src/riotTags/main-menu/main-menu-latest-projects.tag b/src/riotTags/main-menu/main-menu-latest-projects.tag index cc1765bf5..a89d6f840 100644 --- a/src/riotTags/main-menu/main-menu-latest-projects.tag +++ b/src/riotTags/main-menu/main-menu-latest-projects.tag @@ -21,8 +21,9 @@ main-menu-latest-projects this.loadLatestProject = projPath => { alertify.confirm(window.languageJSON.common.reallyExitConfirm, e => { if (e) { + const {openProject} = require('./data/node_requires/resources/projects'); window.signals.trigger('resetAll'); - window.loadProject(projPath); + openProject(projPath); } }); }; diff --git a/src/riotTags/main-menu/main-menu-project.tag b/src/riotTags/main-menu/main-menu-project.tag index 5a8f86834..5ae1c39c7 100644 --- a/src/riotTags/main-menu/main-menu-project.tag +++ b/src/riotTags/main-menu/main-menu-project.tag @@ -86,7 +86,8 @@ main-menu-project return; } window.signals.trigger('resetAll'); - window.loadProject(projFile); + const {openProject} = require('./data/node_requires/resources/projects'); + openProject(projFile); }); }; diff --git a/src/riotTags/notepad-panel.tag b/src/riotTags/notepad-panel.tag index 56f4a5b92..bb783dfa0 100644 --- a/src/riotTags/notepad-panel.tag +++ b/src/riotTags/notepad-panel.tag @@ -59,7 +59,9 @@ notepad-panel#notepad.aPanel.dockright(class="{opened: opened}") setTimeout(() => { if (this.tab && this.refs[this.tab] && this.refs[this.tab].codeEditor) { this.refs[this.tab].codeEditor.layout(); - this.refs[this.tab].codeEditor.focus(); + if (this.opened) { + this.refs[this.tab].codeEditor.focus(); + } } }, 0); }); diff --git a/src/riotTags/project-selector.tag b/src/riotTags/project-selector.tag index 603423827..ae3d5861a 100644 --- a/src/riotTags/project-selector.tag +++ b/src/riotTags/project-selector.tag @@ -133,6 +133,7 @@ project-selector script. const fs = require('fs-extra'), path = require('path'); + const {openProject} = require('./data/node_requires/resources/projects'); this.ctjsVersion = process.versions.ctjs; this.requirePath = path; this.namespace = 'intro'; @@ -243,7 +244,7 @@ project-selector } }); }, 0); - window.loadProject(path.join(way, codename + '.ict')); + openProject(path.join(way, codename + '.ict')); }; /** @@ -251,7 +252,7 @@ project-selector */ this.loadProjectByPath = e => { const projectPath = e.item.project; - window.loadProject(projectPath); + openProject(projectPath); }; /** * Prompts user to clone a project into a different folder/under a different name. @@ -273,7 +274,7 @@ project-selector } await fs.copy(project, newIctLocation); await fs.copy(project.slice(0, -4), newIctLocation.slice(0, -4)); - window.loadProject(newIctLocation); + openProject(newIctLocation); })(); }; /** @@ -330,7 +331,7 @@ project-selector return; } if (path.extname(proj).toLowerCase() === '.ict') { - window.loadProject(proj); + openProject(proj); sessionStorage.projname = path.basename(proj); global.projdir = path.dirname(proj) + path.sep + path.basename(proj, '.ict'); } else { diff --git a/src/riotTags/project-settings/tabs/rendering-settings.tag b/src/riotTags/project-settings/tabs/rendering-settings.tag index f6fc61ec3..e03500a2f 100644 --- a/src/riotTags/project-settings/tabs/rendering-settings.tag +++ b/src/riotTags/project-settings/tabs/rendering-settings.tag @@ -7,7 +7,7 @@ rendering-settings input.short(type="number" min="1" value="{renderSettings.maxFPS || 60}" onchange="{wire('this.renderSettings.maxFPS')}") fieldset label.block.checkbox - input(type="checkbox" value="{renderSettings.pixelatedrender}" checked="{renderSettings.pixelatedrender}" onchange="{wire('this.renderSettings.pixelatedrender')}") + input(type="checkbox" value="{renderSettings.pixelatedrender}" checked="{renderSettings.pixelatedrender}" onchange="{wireAndUpdatePixelated('this.renderSettings.pixelatedrender')}") span {voc.pixelatedRender} label.block.checkbox input(type="checkbox" value="{renderSettings.highDensity}" checked="{renderSettings.highDensity}" onchange="{wire('this.renderSettings.highDensity')}") @@ -40,3 +40,9 @@ rendering-settings this.mixin(window.riotWired); this.currentProject = global.currentProject; this.renderSettings = this.currentProject.settings.rendering; + + this.wireAndUpdatePixelated = path => e => { + this.wire(path)(e); + const {setPixelart} = require('./data/node_requires/resources/textures'); + setPixelart(currentProject.settings.rendering.pixelatedrender); + } diff --git a/src/riotTags/rooms/copy-custom-properties-modal.tag b/src/riotTags/rooms/copy-custom-properties-modal.tag deleted file mode 100644 index 39c3023ef..000000000 --- a/src/riotTags/rooms/copy-custom-properties-modal.tag +++ /dev/null @@ -1,93 +0,0 @@ -// - Shows a table of a copy's extensions object that is modifiable - - @attribute closestcopy (object) - The copy to show its extensions object - - @attribute showme (function) - A function that changes whether this modal is shown - -copy-custom-properties-modal - .aPanel.flexfix(ref="widget" style='overflow: auto; max-height: 600px') - h2.flexfix-header {voc.customProperties} - .flexfix-body - table.wide.aPaddedTable - tr - th {voc.property} - th {voc.value} - th - tr(each="{val, prop in opts.closestcopy.exts}") - td - input.wide(name="copyCustomProp" type="text" value="{prop}" onchange="{onTableChange}") - td - input.wide(name="copyCustomValue" type="text" value="{JSON.stringify(val)}" onchange="{onTableChange}") - td - button.toright.square.inline(onclick="{deleteCustomProperty.bind(null, prop)}" title="{voc.delete}") - svg.feather - use(xlink:href="#trash") - .clear - p - .flexrow.flexfix-footer - .filler - button.nogrow(onclick="{addCustomProperty}") - svg.feather - use(xlink:href="#plus") - span {voc.addProperty} - button.nogrow(onclick="{finished}") - svg.feather - use(xlink:href="#check") - span {vocGlob.apply} - script. - this.namespace = 'copyCustomProperties'; - this.mixin(window.riotVoc); - if (!this.opts.closestcopy.exts) { - this.opts.closestcopy.exts = {}; - } - // an ID to use as newly created property names - this.currentId = 1; - - this.onTableChange = () => { - // read all properties from the table - var propElements = document.getElementsByName('copyCustomProp'); - var props = []; - for (const prop of propElements) { - props.push(prop.value); - } - - // read all values from the table - var valueElements = document.getElementsByName('copyCustomValue'); - var values = []; - - for (const value of valueElements) { - // attempt to parse the value - // only strings will be unparsable with the JSON.parse method - var trueValue; - try { - trueValue = JSON.parse(value.value); // JSON, number, boolean - } catch { - trueValue = value.value; // string - } - values.push(trueValue); - } - - var newExts = {}; - - props.forEach((prop, index) => { - newExts[prop] = values[index]; - }); - - this.opts.closestcopy.exts = newExts; - }; - - this.addCustomProperty = () => { - this.opts.closestcopy.exts['newProperty' + this.currentId] = ''; - this.currentId++; - }; - - this.deleteCustomProperty = (prop) => { - delete this.opts.closestcopy.exts[prop]; - }; - - this.finished = () => { - this.opts.showme(false); - }; diff --git a/src/riotTags/rooms/room-backgrounds-editor.tag b/src/riotTags/rooms/room-backgrounds-editor.tag index 366d30d38..614fbce31 100644 --- a/src/riotTags/rooms/room-backgrounds-editor.tag +++ b/src/riotTags/rooms/room-backgrounds-editor.tag @@ -1,74 +1,157 @@ -room-backgrounds-editor.room-editor-Backgrounds.tabbed.tall - ul - li.bg(each="{background, ind in opts.room.backgrounds}" oncontextmenu="{onContextMenu}") - img(src="{getTexturePreview(background.texture)}" onclick="{onChangeBgTexture}") - span - span(class="{active: detailedBackground === background}" onclick="{editBackground}") - svg.feather - use(xlink:href="#settings") - | {getTextureFromId(background.texture).name} ({background.depth}) - .clear - .anErrorNotice(if="{background.texture && background.texture !== -1 && !getTextureFromId(background.texture).tiled && !getTextureFromId(background.texture).ignoreTiledUse}") - | {voc.notBackgroundTextureWarning} +// + @attribute backgrounds (Background[]) + @attribute addbackground (riot function) + @attribute room (IRoom) + @attribute history (History) + +room-backgrounds-editor + collapsible-section( + each="{background, ind in opts.backgrounds}" + icon="settings" + ).aPanel + yield(to="header") + asset-input( + assettype="textures" + assetid="{background.bgTexture}" + compact="true" + onchanged="{parent.changeBgTexture(background)}" + ref="assetInput" + ) + error-notice( + if="{background.bgTexture && background.bgTexture !== -1 && !parent.getTextureFromId(background.bgTexture).tiled && !parent.getTextureFromId(background.bgTexture).ignoreTiledUse}" + target="{refs.assetInput}" + ) + | {parent.parent.voc.notBackgroundTextureWarning} | - span.a(onclick="{fixTexture(background)}") {voc.fixBackground} + span.a(onclick="{parent.parent.fixTexture}") {parent.parent.voc.fixBackground} | | - span.a(onclick="{dismissWarning(background)}") {voc.dismissWarning} - div(if="{detailedBackground === background}") - .clear - label - b {voc.depth} - input.wide(type="number" value="{background.depth || 0}" step="0" oninput="{onChangeBgDepth}") - - b {voc.shift} - .clear - label.fifty.npl.npt - input.wide(type="number" value="{background.extends.shiftX || 0}" step="8" oninput="{wire('this.detailedBackground.extends.shiftX')}") - label.fifty.npr.npt - input.wide(type="number" value="{background.extends.shiftY || 0}" step="8" oninput="{wire('this.detailedBackground.extends.shiftY')}") - - b {voc.scale} - .clear - label.fifty.npl.npt - input.wide(type="number" value="{background.extends.scaleX || 1}" step="0.01" oninput="{wire('this.detailedBackground.extends.scaleX')}") - label.fifty.npr.npt - input.wide(type="number" value="{background.extends.scaleY || 1}" step="0.01" oninput="{wire('this.detailedBackground.extends.scaleY')}") - - b {voc.movement} - .clear - label.fifty.npl.npt - input.wide(type="number" value="{background.extends.movementX || 0}" step="0.1" oninput="{wire('this.detailedBackground.extends.movementX')}") - label.fifty.npr.npt - input.wide(type="number" value="{background.extends.movementY || 0}" step="0.1" oninput="{wire('this.detailedBackground.extends.movementY')}") - - b {voc.parallax} - .clear - label.fifty.npl.npt - input.wide(type="number" value="{background.extends.parallaxX !== void 0 ? background.extends.parallaxX : 1}" step="0.01" oninput="{wire('this.detailedBackground.extends.parallaxX')}") - label.fifty.npr.npt - input.wide(type="number" value="{background.extends.parallaxY !== void 0 ? background.extends.parallaxY : 1}" step="0.01" oninput="{wire('this.detailedBackground.extends.parallaxY')}") - .clear - - b {voc.repeat} - select(onchange="{wire('this.detailedBackground.extends.repeat')}") - option(value="repeat" selected="{detailedBackground.extends.repeat === 'repeat'}") repeat - option(value="repeat-x" selected="{detailedBackground.extends.repeat === 'repeat-x'}") repeat-x - option(value="repeat-y" selected="{detailedBackground.extends.repeat === 'repeat-y'}") repeat-y - option(value="no-repeat" selected="{detailedBackground.extends.repeat === 'no-repeat'}") no-repeat - + span.a(onclick="{parent.parent.dismissWarning}") {parent.parent.voc.dismissWarning} + fieldset + label + b {parent.voc.depth} + input.wide( + type="number" step="1" + onfocus="{parent.rememberValue}" + value="{background.zIndex}" + oninput="{parent.tweak(background, 'zIndex')}" + onchange="{parent.recordChange(background, 'zIndex')}" + ) + fieldset + b {parent.voc.shift} + .aPoint2DInput.compact.wide + label.flexrow + span.nogrow X: + input.nmr( + type="number" step="8" placeholder="0" + onfocus="{parent.rememberValue}" + oninput="{parent.tweak(background, 'shiftX')}" + onchange="{parent.recordChange(background, 'shiftX')}" + value="{background.shiftX}" + ) + .aSpacer.noshrink.nogrow + label.flexrow + span.nogrow Y: + input.nmr( + type="number" step="8" placeholder="0" + onfocus="{parent.rememberValue}" + oninput="{parent.tweak(background, 'shiftY')}" + onchange="{parent.recordChange(background, 'shiftY')}" + value="{background.shiftY}" + ) + b {parent.voc.scale} + .aPoint2DInput.compact.wide + label.flexrow + span.nogrow X: + input.nmr( + type="number" step="0.1" placeholder="1" + onfocus="{parent.rememberValue}" + oninput="{parent.tweak(background.tileScale, 'x')}" + onchange="{parent.recordChange(background.tileScale, 'x')}" + value="{background.tileScale.x}" + ) + .aSpacer.noshrink.nogrow + label.flexrow + span.nogrow Y: + input.nmr( + type="number" step="0.1" placeholder="1" + onfocus="{parent.rememberValue}" + oninput="{parent.tweak(background.tileScale, 'y')}" + onchange="{parent.recordChange(background.tileScale, 'y')}" + value="{background.tileScale.y}" + ) + fieldset + b {parent.voc.movement} + .aPoint2DInput.compact.wide + label.flexrow + span.nogrow X: + input.nmr( + type="number" step="1" placeholder="0" + onfocus="{parent.rememberValue}" + oninput="{parent.tweak(background, 'movementX')}" + onchange="{parent.recordChange(background, 'movementX')}" + value="{background.movementX}" + ) + .aSpacer.noshrink.nogrow + label.flexrow + span.nogrow Y: + input.nmr( + type="number" step="1" placeholder="0" + onfocus="{parent.rememberValue}" + oninput="{parent.tweak(background, 'movementY')}" + onchange="{parent.recordChange(background, 'movementY')}" + value="{background.movementY}" + ) + b {parent.voc.parallax} + .aPoint2DInput.compact.wide + label.flexrow + span.nogrow X: + input.nmr( + type="number" step="0.1" placeholder="1" + onfocus="{parent.rememberValue}" + oninput="{parent.tweak(background, 'parallaxX')}" + onchange="{parent.recordChange(background, 'parallaxX')}" + value="{background.parallaxX}" + ) + .aSpacer.noshrink.nogrow + label.flexrow + span.nogrow Y: + input.nmr( + type="number" step="0.1" placeholder="1" + onfocus="{parent.rememberValue}" + oninput="{parent.tweak(background, 'parallaxY')}" + onchange="{parent.recordChange(background, 'parallaxY')}" + value="{background.parallaxY}" + ) + fieldset + b {parent.voc.repeat} + | + select( + onfocus="{parent.rememberValue}" + onchange="{parent.recordAndTweak(background, 'repeat')}" + ) + option(value="repeat" selected="{background.repeat === 'repeat'}") repeat + option(value="repeat-x" selected="{background.repeat === 'repeat-x'}") repeat-x + option(value="repeat-y" selected="{background.repeat === 'repeat-y'}") repeat-y + option(value="no-repeat" selected="{background.repeat === 'no-repeat'}") no-repeat + .aSpacer + button.wide(onclick="{parent.removeBg}") + svg.feather + use(xlink:href="#trash") + span {parent.vocGlob.delete} + .aSpacer(if="{opts.backgrounds.length}") button.inline.wide(onclick="{addBg}") svg.feather use(xlink:href="#plus") span {voc.add} + // Used for selecting a texture for newly created backgrounds asset-selector( ref="texturePicker" - if="{pickingBackground}" + if="{newBg}" assettype="textures" oncancelled="{onTextureCancel}" onselected="{onTextureSelected}" ) - context-menu(menu="{roomBgMenu}" ref="roomBgMenu") script. const glob = require('./data/node_requires/glob'); this.glob = glob; @@ -80,85 +163,119 @@ room-backgrounds-editor.room-editor-Backgrounds.tabbed.tall this.pickingBackground = false; this.namespace = 'roomBackgrounds'; this.mixin(window.riotVoc); - this.mixin(window.riotWired); - this.on('update', () => { - if (this.parent.tab === 'roombackgrounds') { - this.parent.refreshRoomCanvas(); + + this.tweak = (obj, field) => e => { + const input = e.target; + if (input.type === 'radio' || input.type === 'checkbox') { + obj[field] = input.checked; + } else if (input.type === 'number') { + obj[field] = Number(input.value); + } else { + obj[field] = input.value; } - }); + }; + + // These two are only for newly created backgrounds, for which an asset selection modal + // is automatically created this.onTextureSelected = textureId => { - this.editingBackground.texture = textureId; - this.pickingBackground = false; - this.creatingBackground = false; + this.newBg.changeTexture(textureId); + this.opts.history.pushChange({ + type: 'backgroundCreation', + created: this.newBg + }); + this.newBg = void 0; this.update(); }; this.onTextureCancel = () => { this.pickingBackground = false; - if (this.creatingBackground) { - const bgs = this.opts.room.backgrounds; - bgs.splice(bgs.indexOf(this.editingBackground), 1); - this.parent.resortRoom(); - this.creatingBackground = false; + if (this.newBg) { + this.newBg.destroy(); } + this.newBg = false; this.update(); }; + this.addBg = () => { - var newBg = { + const bgTemplate = { depth: 0, texture: -1, - extends: {} + parallaxX: 1, + parallaxY: 1, + shiftX: 0, + shiftY: 0, + scaleX: 1, + scaleY: 1, + movementX: 0, + movementY: 0, + repeat: 'repeat' }; - this.opts.room.backgrounds.push(newBg); - this.editingBackground = newBg; + const bg = this.opts.addbackground(bgTemplate); + this.newBg = bg; this.pickingBackground = true; - this.creatingBackground = true; - this.opts.room.backgrounds.sort((a, b) => a.depth - b.depth); - this.parent.resortRoom(); - this.update(); }; - this.onContextMenu = e => { - this.editedBg = Number(e.item.ind); - this.refs.roomBgMenu.popup(e.clientX, e.clientY); - e.preventDefault(); + + this.changeBgTexture = background => textureId => { + const prevId = background.bgTexture; + background.changeTexture(textureId); + this.opts.history.pushChange({ + type: 'propChange', + key: 'bgTexture', + target: background, + before: prevId, + after: textureId + }); + this.update(); }; - this.roomBgMenu = { - opened: false, - items: [{ - label: window.languageJSON.common.delete, - click: () => { - this.opts.room.backgrounds.splice(this.editedBg, 1); - this.parent.resortRoom(); - this.update(); - } - }] + + this.removeBg = e => { + const {background} = e.item; + background.detach(); + this.opts.history.pushChange({ + type: 'backgroundDeletion', + deleted: background + }); }; - this.onChangeBgTexture = e => { - this.pickingBackground = true; - this.editingBackground = e.item.background; + + this.fixTexture = e => { + const {background} = e.item; + const tex = getTextureFromId(background.bgTexture); + tex.tiled = true; + e.stopPropagation(); this.update(); }; - this.onChangeBgDepth = e => { - e.item.background.depth = Number(e.target.value); - this.opts.room.backgrounds.sort((a, b) => a.depth - b.depth); - this.parent.resortRoom(); + this.dismissWarning = e => { + const {background} = e.item; + const tex = getTextureFromId(background.bgTexture); + tex.ignoreTiledUse = true; + e.stopPropagation(); + this.update(); }; - this.editBackground = e => { - if (this.detailedBackground === e.item.background) { - this.detailedBackground = void 0; + var prevValue; + this.rememberValue = e => { + if (e.target.type === 'number') { + prevValue = Number(e.target.value); } else { - this.detailedBackground = e.item.background; - if (!('extends' in this.detailedBackground)) { - this.detailedBackground.extends = {}; - } + prevValue = e.target.value; } }; - - this.fixTexture = background => () => { - const tex = getTextureFromId(background.texture); - tex.tiled = true; + this.recordChange = (entity, key) => e => { + if (!this.opts.history) { + return; + } + let value = e.target.value; + if (e.target.type === 'number') { + value = Number(value); + } + this.opts.history.pushChange({ + type: 'propChange', + key, + target: entity, + before: prevValue, + after: value + }); }; - this.dismissWarning = background => () => { - const tex = getTextureFromId(background.texture); - tex.ignoreTiledUse = true; + this.recordAndTweak = (entity, key) => e => { + this.tweak(entity, key)(e); + this.recordChange(entity, key)(e); }; diff --git a/src/riotTags/rooms/room-copy-properties.tag b/src/riotTags/rooms/room-copy-properties.tag deleted file mode 100644 index cdf4520b6..000000000 --- a/src/riotTags/rooms/room-copy-properties.tag +++ /dev/null @@ -1,65 +0,0 @@ -room-copy-properties.aPanel - b {voc.position}: - .aPoint2DInput.compact.wide - label - span X: - | - input.compact( - step="8" type="number" - oninput="{wire('this.opts.copy.x')}" - value="{opts.copy.x}" - ) - .aSpacer - label - span.nogrow Y: - | - input.compact( - step="8" type="number" - oninput="{wire('this.opts.copy.y')}" - value="{opts.copy.y}" - ) - b {voc.scale}: - .aPoint2DInput.compact.wide - label - span X: - | - input.compact( - step="0.1" type="number" - oninput="{wire('this.opts.copy.tx')}" - value="{opts.copy.tx === void 0 ? 1 : opts.copy.tx}" - ) - .aSpacer - label - span.nogrow Y: - | - input.compact( - step="0.1" type="number" - oninput="{wire('this.opts.copy.ty')}" - value="{opts.copy.ty === void 0 ? 1 : opts.copy.ty}" - ) - b {voc.rotation}: - dd - .flexrow - .aSliderWrap - input.compact( - type="range" min="0" max="360" step="1" - value="{opts.copy.tr || 0}" - oninput="{wire('this.opts.copy.tr')}" - ) - .aSpacer - input.compact( - min="0" max="360" step="1" type="number" - value="{opts.copy.tr || 0}" - oninput="{wire('this.opts.copy.tr')}" - ) - extensions-editor(entity="{opts.copy.exts}" type="copy" compact="yes" wide="yup") - script. - this.namespace = 'roomView.copyProperties'; - this.mixin(window.riotVoc); - this.mixin(window.riotWired); - - this.on('update', () => { - if (this.opts.copy && !this.opts.copy.exts) { - this.opts.copy.exts = {}; - } - }); diff --git a/src/riotTags/rooms/room-editor.tag b/src/riotTags/rooms/room-editor.tag index a08798161..cd78cb6eb 100644 --- a/src/riotTags/rooms/room-editor.tag +++ b/src/riotTags/rooms/room-editor.tag @@ -1,805 +1,472 @@ -room-editor.aPanel.aView - .toolbar.tall(style="width: {sidebarWidth}px") - copy-custom-properties-modal(if="{showCopyPropertiesModal}" closestcopy="{closestCopy}" showme="{toggleCopyProperties}") - .settings.nogrow.noshrink - b {voc.name} - br - input.wide(type="text" value="{room.name}" onchange="{wire('this.room.name')}") - .anErrorNotice(if="{nameTaken}" ref="errorNotice") {vocGlob.nameTaken} - button.wide(onclick="{openRoomEvents}") - svg.feather(if="{room.events && room.events.length}") - use(xlink:href="#check") - span {voc.events} - .palette - .tabwrap.flexfix - ul.tabs.aNav.noshrink.nogrow.flexfix-header - li(onclick="{changeTab('roomcopies')}" title="{voc.copies}" class="{active: tab === 'roomcopies'}") - svg.feather - use(xlink:href="#template") - span(if="{sidebarWidth > 500}") {voc.copies} - li(onclick="{changeTab('roombackgrounds')}" title="{voc.backgrounds}" class="{active: tab === 'roombackgrounds'}") - svg.feather - use(xlink:href="#image") - span(if="{sidebarWidth > 500}") {voc.backgrounds} - li(onclick="{changeTab('roomtiles')}" title="{voc.tiles}" class="{active: tab === 'roomtiles'}") - svg.feather - use(xlink:href="#texture") - span(if="{sidebarWidth > 500}") {voc.tiles} - li(onclick="{changeTab('properties')}" title="{voc.properties}" class="{active: tab === 'properties'}") - svg.feather - use(xlink:href="#settings") - span(if="{sidebarWidth > 500}") {voc.properties} - .relative.flexfix-body - room-template-picker(show="{tab === 'roomcopies'}" current="{currentTemplate}") - room-backgrounds-editor(show="{tab === 'roombackgrounds'}" room="{room}") - room-tile-editor(show="{tab === 'roomtiles'}" room="{room}") - .pad.aPanel(show="{tab === 'properties'}") - fieldset - .fifty.npt.npb.npl - b {voc.width} - br - input.wide(type="number" value="{room.width}" onchange="{wireAndRedraw('this.room.width')}") - .fifty.npt.npb.npr - b {voc.height} - br - input.wide(type="number" value="{room.height}" onchange="{wireAndRedraw('this.room.height')}") - .clear - fieldset - label.checkbox - input(type="checkbox" checked="{room.restrictCamera}" onchange="{wireAndRedraw('this.room.restrictCamera')}") - span {voc.restrictCamera} - .aPoint2DInput.compact.wide(if="{room.restrictCamera}") - label - span {voc.minimumX}: - | - input.compact( - step="{room.gridX}" type="number" - oninput="{wireAndRedraw('this.room.restrictMinX')}" - value="{room.restrictMinX === void 0 ? 0 : room.restrictMinX}" - ) - .aSpacer - label - span.nogrow {voc.minimumY}: - | - input.compact( - step="{room.gridY}" type="number" - oninput="{wireAndRedraw('this.room.restrictMinY')}" - value="{room.restrictMinY === void 0 ? 0 : room.restrictMinY}" - ) - .aPoint2DInput.compact.wide(if="{room.restrictCamera}") - label - span {voc.maximumX}: - | - input.compact( - step="{room.gridX}" type="number" - oninput="{wireAndRedraw('this.room.restrictMaxX')}" - value="{room.restrictMaxX === void 0 ? room.width : room.restrictMaxX}" - ) - .aSpacer - label - span.nogrow {voc.maximumY}: - | - input.compact( - step="{room.gridY}" type="number" - oninput="{wireAndRedraw('this.room.restrictMaxY')}" - value="{room.restrictMaxY === void 0 ? room.height : room.restrictMaxY}" - ) - - fieldset - b {voc.backgroundColor} - br - color-input.wide(onchange="{updateRoomBackground}" color="{room.backgroundColor || '#000000'}") - - fieldset - extensions-editor(entity="{room.extends}" type="room" wide="aye" compact="sure") +// + @attribute room + The room to edit + @attribute onclose (riot function) - fieldset - label.block.checkbox - input(type="checkbox" checked="{room.extends.isUi}" onchange="{wire('this.room.extends.isUi')}") - b {voc.isUi} - - .done.nogrow - button.wide#roomviewdone(onclick="{roomSave}") +room-editor.aPanel.aView + canvas(ref="canvas" onwheel="{triggerWheelEvent}") + // Toolbar + .room-editor-aToolsetHolder + .room-editor-aToolbar.aButtonGroup.vertical + button.forcebackground( + onclick="{setTool('select')}" + class="{active: currentTool === 'select'}" + title="{voc.tools.select} (Q)" + data-hotkey="q" + data-hotkey-require-scope="rooms" + ) svg.feather - use(xlink:href="#check") - span {voc.done} - .aResizer.vertical(ref="gutter" onmousedown="{gutterMouseDown}") - .editor(ref="canvaswrap") - canvas( - ref="canvas" - onclick="{onCanvasClick}" - onmousedown="{onCanvasPress}" - onmousemove="{onCanvasMove}" - onmouseup="{onCanvasMouseUp}" - onmouseout="{refreshRoomCanvas}" - onmousewheel="{onCanvasWheel}" - oncontextmenu="{onCanvasContextMenu}" - ) - .shift.flexrow - button.inline.square.forcebackground(title="{voc.shift}" onclick="{roomShift}") + use(xlink:href="#cursor") + button.forcebackground( + onclick="{setTool('addCopies')}" + class="{active: currentTool === 'addCopies'}" + title="{voc.tools.addCopies} (W)" + data-hotkey="w" + data-hotkey-require-scope="rooms" + ) svg.feather - use(xlink:href="#move") - .aButtonGroup - button.inline.square.forcebackground( - title="{voc.sortHorizontally}" - onclick="{sortHorizontally}" - if="{tab === 'roomcopies' || tab === 'roomtiles'}" - ) - svg.feather - use(xlink:href="#sort-horizontal") - button.inline.square.forcebackground( - title="{voc.sortVertically}" - onclick="{sortVertically}" - if="{tab === 'roomcopies' || tab === 'roomtiles'}" + use(xlink:href="#template") + button.forcebackground( + onclick="{setTool('addTiles')}" + class="{active: currentTool === 'addTiles'}" + title="{voc.tools.addTiles} (E)" + data-hotkey="e" + data-hotkey-require-scope="rooms" + ) + svg.feather + use(xlink:href="#grid") + button.forcebackground( + onclick="{setTool('manageBackgrounds')}" + class="{active: currentTool === 'manageBackgrounds'}" + title="{voc.tools.manageBackgrounds} (R)" + data-hotkey="r" + data-hotkey-require-scope="rooms" + ) + svg.feather + use(xlink:href="#image") + button.forcebackground( + onclick="{setTool('roomProperties')}" + class="{active: currentTool === 'roomProperties'}" + title="{voc.tools.roomProperties} (T)" + data-hotkey="t" + data-hotkey-require-scope="rooms" + ) + svg.feather + use(xlink:href="#settings") + + // Contextual panels for tools + .room-editor-aContextPanel( + if="{currentTool === 'select' && pixiEditor}" + ) + h3.nogrow.nm.inlineblock {vocGlob.select}: + .aSpacer.inlineblock + .aButtonGroup.nm + button.inline.square( + each="{lockable in lockableTypes}" + class="{active: parent.pixiEditor && parent.pixiEditor[lockable.key]}" + onclick="{toggleSelectables}" + title="{voc[lockable.hintVocKey]}" ) svg.feather - use(xlink:href="#sort-vertical") - span.aContrastingPlaque(if="{window.innerWidth - sidebarWidth > 940}") {voc.hotkeysNotice} - .zoom.flexrow - b.aContrastingPlaque - span(if="{window.innerWidth - sidebarWidth > 980}") {vocGlob.zoom}: - | - | - span {Math.round(zoomFactor * 100)}% - .aSpacer - zoom-slider(onchanged="{setZoom}" ref="zoomslider" value="{zoomFactor}") - .grid - button#roomgrid.forcebackground(onclick="{roomToggleGrid}" class="{active: room.gridX > 0}") - span {voc[room.gridX > 0? 'gridOff' : 'grid']} - .center - button#roomcenter.forcebackground(onclick="{roomToCenter}") {voc.toCenter} - b.aMouseCoord.aContrastingPlaque(show="{window.innerWidth - sidebarWidth > 470}" ref="mousecoords") ({mouseX}:{mouseY}) - room-copy-properties( - if="{this.selectedCopies && this.selectedCopies.length === 1}" - copy="{this.selectedCopies[0]}" - onchange="{refreshRoomCanvas}" oninput="{refreshRoomCanvas}" + use(xlink:href="#{lockable.icon}") + room-entities-properties(ref="propertiesPanel" pixieditor="{pixiEditor}" ontransformchange="{updateSelectFrame}") + room-properties.room-editor-aContextPanel( + if="{currentTool === 'roomProperties'}" + room="{opts.room}" + history="{pixiEditor?.history}" + updatebg="{changeBgColor}" + ref="propertiesPanel" + ) + room-template-picker.room-editor-aContextPanel( + if="{currentTool === 'addCopies'}" + onselect="{changeSelectedTemplate}" + selected="{currentTemplate}" + ) + room-tile-editor.room-editor-aContextPanel( + if="{currentTool === 'addTiles'}" + layer="{currentTileLayer}" + layers="{pixiEditor.tileLayers}" + onchangetile="{changeTilePatch}" + onchangelayer="{changeTileLayer}" + pixieditor="{pixiEditor}" + removelayer="{pixiEditor?.removeLayer?.bind(pixiEditor)}" + addlayer="{pixiEditor?.addTileLayer?.bind(pixiEditor)}" + ref="tileEditor" ) - room-events-editor(if="{editingCode}" room="{room}") - context-menu(menu="{roomCanvasCopiesMenu}" ref="roomCanvasCopiesMenu") - context-menu(menu="{roomCanvasMenu}" ref="roomCanvasMenu") - context-menu(menu="{roomCanvasTileMenu}" ref="roomCanvasTileMenu") - context-menu(menu="{roomCanvasTilesMenu}" ref="roomCanvasTilesMenu") + room-backgrounds-editor.room-editor-aContextPanel( + if="{currentTool === 'manageBackgrounds'}" + backgrounds="{pixiEditor?.backgrounds}" + addbackground="{pixiEditor?.addBackground?.bind(pixiEditor)}" + room="{opts.room}" + history="{pixiEditor?.history}" + ref="backgroundsEditor" + ) + + // Global controls placed at the top-center + .room-editor-aTopPanel + button.slim(onclick="{pixiEditor?.history.undo.bind(pixiEditor.history)}" title="{vocGlob.undo}" class="{dim: !pixiEditor?.history.canUndo}") + svg.feather + use(xlink:href="#undo") + button.slim(onclick="{pixiEditor?.history.redo.bind(pixiEditor.history)}" title="{vocGlob.redo}" class="{dim: !pixiEditor?.history.canRedo}") + svg.feather + use(xlink:href="#redo") + label.checkbox(title="Shift+S") + input( + type="checkbox" + onchange="{changeSimulated}" + checked="{pixiEditor?.simulate}" + data-hotkey="S" + data-hotkey-require-scope="rooms" + ) + span {voc.simulate} + button(onclick="{openZoomMenu}") + span(ref="zoomLabel") {Math.round(pixiEditor?.getZoom() || 100)}% + button(onclick="{openGridMenu}") + span {voc.grid} + button.slim(onclick="{openVisibilityMenu}") + svg.feather + use(xlink:href="#eye") + button(onclick="{openEventsList}") + span {voc.events} + button(onclick="{saveRoom}") + svg.feather + use(xlink:href="#check") + span {vocGlob.save} + + room-events-editor(if="{editingEvents}" room="{opts.room}" onsave="{closeRoomEvents}") + context-menu(menu="{gridMenu}" ref="gridMenu") + context-menu(menu="{zoomMenu}" ref="zoomMenu") + context-menu(menu="{visibilityMenu}" ref="visibilityMenu" if="{pixiEditor}") script. - const minSizeW = 250; - const getMaxSizeW = () => window.innerWidth - 300; - this.sidebarWidth = Math.max( - minSizeW, - Math.min(getMaxSizeW(), localStorage.roomSidebarWidth || 300) - ); + this.namespace = 'roomView'; + this.mixin(window.riotVoc); - this.gutterMouseDown = () => { - this.draggingGutter = true; - }; - const gutterMove = e => { - if (!this.draggingGutter) { + this.freePlacementMode = false; + const modifiersDownListener = e => { + if (e.repeat || + !window.hotkeys.inScope('rooms') || + window.hotkeys.isFormField(e.target) + ) { return; } - this.sidebarWidth = Math.max(minSizeW, Math.min(getMaxSizeW(), e.clientX)); - localStorage.roomSidebarWidth = this.sidebarWidth; - this.update(); - var {canvas} = this.refs, - sizes = this.refs.canvaswrap.getBoundingClientRect(); - if (canvas.width !== sizes.width || canvas.height !== sizes.height) { - canvas.width = sizes.width; - canvas.height = sizes.height; + if (e.key === 'Alt') { + this.freePlacementMode = true; + e.preventDefault(); + } else if (e.key === 'Control') { + this.controlMode = true; + e.preventDefault(); } - this.refreshRoomCanvas(); }; - const gutterUp = () => { - if (this.draggingGutter) { - this.draggingGutter = false; - // updateCanvasSize(); - // document.body.removeChild(catcher); + const modifiersUpListener = e => { + if (e.repeat || + !window.hotkeys.inScope('rooms') || + window.hotkeys.isFormField(e.target) + ) { + return; + } + if (e.key === 'Alt') { + this.freePlacementMode = false; + e.preventDefault(); + } else if (e.key === 'Control') { + this.controlMode = false; + e.preventDefault(); } }; - document.addEventListener('mousemove', gutterMove); - document.addEventListener('mouseup', gutterUp); - this.on('unmount', () => { - document.removeEventListener('mousemove', gutterMove); - document.removeEventListener('mouseup', gutterUp); - }); - - this.editingCode = false; - this.forbidDrawing = false; - const fs = require('fs-extra'); - const glob = require('./data/node_requires/glob'); - this.namespace = 'roomView'; - this.mixin(window.riotVoc); - this.mixin(window.riotWired); - this.mixin(window.roomCopyTools); - this.mixin(window.roomTileTools); - this.wireAndRedraw = way => e => { - this.wire(way)(e); - this.refreshRoomCanvas(); - }; - - this.room = this.opts.room; - if (!this.room.extends) { - this.room.extends = {}; - } - - this.mouseX = this.mouseY = 0; - this.roomx = this.room.width / 2; - this.roomy = this.room.height / 2; - this.zoomFactor = 1; - this.room.gridX = this.room.gridX || this.room.grid || 64; - this.room.gridY = this.room.gridY || this.room.grid || 64; - this.dragging = false; - this.tab = 'roomcopies'; - - this.toggleCopyProperties = value => { - this.showCopyPropertiesModal = value; - this.update(); - this.refreshRoomCanvas(); - }; - - this.updateRoomBackground = (e, color) => { - this.room.backgroundColor = color; - this.refreshRoomCanvas(); + const blurListener = () => { + // Specifically designed to catch Alt+Tab + this.freePlacementMode = false; }; - - var updateCanvasSize = () => { - // Firstly, check that we don't need to reflow the layout due to window shrinking - const oldSidebarWidth = this.sidebarWidth; - this.sidebarWidth = Math.max(minSizeW, Math.min(getMaxSizeW(), this.sidebarWidth)); - if (oldSidebarWidth !== this.sidebarWidth) { - this.update(); - } - var {canvas} = this.refs, - sizes = this.refs.canvaswrap.getBoundingClientRect(); - if (canvas.width !== sizes.width || canvas.height !== sizes.height) { - canvas.width = sizes.width; - canvas.height = sizes.height; + const tabListener = e => { + if (!window.hotkeys.inScope('rooms') || window.hotkeys.isFormField(e.target)) { + return; } - setTimeout(this.refreshRoomCanvas, 10); + this.gridOn = !this.gridOn; }; - this.on('update', () => { - if (global.currentProject.rooms.find(room => - this.room.name === room.name && this.room !== room)) { - this.nameTaken = true; - } else { - this.nameTaken = false; - } - }); this.on('mount', () => { - this.room = this.opts.room; - this.refs.canvas.x = this.refs.canvas.getContext('2d'); - this.gridCanvas = document.createElement('canvas'); - this.gridCanvas.x = this.gridCanvas.getContext('2d'); - this.redrawGrid(); - window.addEventListener('resize', updateCanvasSize); - updateCanvasSize(); + window.hotkeys.push('roomEditor'); + window.hotkeys.on('Control+g', tabListener); + document.addEventListener('keydown', modifiersDownListener); + document.addEventListener('keyup', modifiersUpListener); + window.addEventListener('blur', blurListener); }); this.on('unmount', () => { - window.removeEventListener('resize', updateCanvasSize); + window.hotkeys.exit('roomEditor'); + window.hotkeys.off('Control+g', tabListener); + document.removeEventListener('keydown', modifiersDownListener); + document.removeEventListener('keyup', modifiersUpListener); + window.addEventListener('blur', blurListener); }); - this.openRoomEvents = () => { - this.editingCode = true; + this.lockableTypes = [{ + icon: 'template', + hintVocKey: 'copies', + key: 'selectCopies' + }, { + icon: 'grid', + hintVocKey: 'tiles', + key: 'selectTiles' + }]; + this.toggleSelectables = e => { + this.pixiEditor[e.item.lockable.key] = !this.pixiEditor[e.item.lockable.key]; }; - - // Навигация по комнате, настройки вида - this.roomToggleZoom = zoomFactor => () => { - this.zoomFactor = zoomFactor; - this.redrawGrid(); - this.refreshRoomCanvas(); - }; - this.roomToCenter = () => { - this.roomx = this.room.width / 2; - this.roomy = this.room.height / 2; - this.refreshRoomCanvas(); - }; - this.redrawGrid = () => { - this.gridCanvas.width = this.room.gridX; - this.gridCanvas.height = this.room.gridY; - this.gridCanvas.x.clearRect(0, 0, this.room.gridX, this.room.gridY); - this.gridCanvas.x.globalAlpha = 0.3; - this.gridCanvas.x.strokeStyle = localStorage.UItheme === 'Night' ? '#44dbb5' : '#446adb'; - this.gridCanvas.x.lineWidth = 1 / this.zoomFactor; - this.gridCanvas.x.strokeRect( - 0.5 / this.zoomFactor, 0.5 / this.zoomFactor, - this.room.gridX, this.room.gridY - ); - }; - this.roomToggleGrid = () => { - if (this.room.gridX === 0) { - window.alertify - .confirm(this.voc.gridSize + '
x ') - .then(e => { - if (e.buttonClicked === 'ok') { - this.room.gridX = Number(document.getElementById('theGridSizeX').value); - this.room.gridY = Number(document.getElementById('theGridSizeY').value); - } - this.redrawGrid(); - this.refreshRoomCanvas(); - this.update(); - }); - } else { - this.refreshRoomCanvas(); - this.room.gridX = 0; - this.room.gridY = 0; - } + this.updateSelectFrame = () => { + this.pixiEditor.transformer.setup(); }; - // Работа с копиями - this.tab = 'roomcopies'; - this.changeTab = tab => () => { - this.tab = tab; - this.selectedCopies = this.selectedTiles = false; - if (tab === 'roombackgrounds' || tab === 'properties') { - this.roomUnpickTemplate(); - } - if (tab !== 'roomcopies') { - this.toggleCopyProperties(false); - } - }; - this.roomUnpickTemplate = () => { - this.currentTemplate = -1; + const setup = require('./data/node_requires/roomEditor').setup; + this.on('mount', () => { + setup(this.refs.canvas, this); + // adds this.pixiEditor + [this.currentTileLayer] = this.pixiEditor.tileLayers; + this.update(); + }); + this.on('unmount', () => { + this.pixiEditor.destroy(false, { + children: true + }); + }); + this.triggerWheelEvent = e => { + e.preventUpdate = true; + // pixi v5 doesn't have a wheel event! we will have to fabricate one. + this.pixiEditor.stage._events.wheel.fn({ + type: 'wheel', + target: this.pixiEditor.stage, + currentTarget: this.pixiEditor.stage, + data: { + global: { + x: e.offsetX, + y: e.offsetY + }, + originalEvent: e + } + }); }; - - /** Преобразовать x на канвасе в x на комнате */ - this.xToRoom = x => (x - Math.floor(this.refs.canvas.width / 2)) / this.zoomFactor + this.roomx; - /** Преобразовать y на канвасе в y на комнате */ - this.yToRoom = y => (y - Math.floor(this.refs.canvas.height / 2)) / this.zoomFactor + this.roomy; - /** Преобразовать x в комнате в x на канвасе */ - this.xToCanvas = x => (x - this.roomx) * this.zoomFactor + Math.floor(this.refs.canvas.width / 2); - /** Преобразовать y в комнате в y на канвасе */ - this.yToCanvas = y => (y - this.roomy) * this.zoomFactor + Math.floor(this.refs.canvas.height / 2); - - this.onCanvasClick = e => { - if (this.tab === 'roomcopies') { - this.onCanvasClickCopies(e); - } else if (this.tab === 'roomtiles') { - this.onCanvasClickTiles(e); + // Keyboard events + const phabricateEvent = (name, e) => { + if (!(name in this.pixiEditor.stage._events)) { + console.error(`An event ${name} was triggered on the room editor, but it is not allowed.`); + return; } + this.pixiEditor.stage._events[name].fn({ + type: name, + target: this.pixiEditor.stage, + currentTarget: this.pixiEditor.stage, + data: { + originalEvent: e + } + }); }; - /** При нажатии на канвас, если не выбрана копия, то начинаем перемещение */ - this.onCanvasPress = e => { - this.mouseDown = true; - this.startx = e.offsetX; - this.starty = e.offsetY; - - if (this.tab === 'roomcopies' && this.onCanvasPressCopies(e)) { + const triggerKeyboardEvent = e => { + if (['input', 'textarea', 'select'].includes(e.target.nodeName.toLowerCase())) { return; } - if ((this.currentTemplate === -1 && !e.shiftKey && this.tab !== 'roomtiles' && e.button === 0 && !e.ctrlKey) || - e.button === 1) { - this.dragging = true; - } - }; - /** и безусловно прекращаем перемещение при отпускании мыши */ - this.onCanvasMouseUp = e => { - this.mouseDown = false; - this.lastCopyX = null; - this.lastCopyY = null; - this.lastTileX = null; - this.lastTileY = null; - if (this.dragging) { - this.dragging = false; - this.roomx = Math.round(this.roomx); - this.roomy = Math.round(this.roomy); - this.refreshRoomCanvas(); - } else if (this.tab === 'roomtiles') { - this.onCanvasMouseUpTiles(e); - } else if (this.tab === 'roomcopies') { - this.onCanvasMouseUpCopies(e); + if (e.key === 'Delete') { + return phabricateEvent('delete', e); + } else if (e.code === 'KeyC' && e.ctrlKey) { + return phabricateEvent('copy', e); + } else if (e.code === 'KeyV' && e.ctrlKey) { + return phabricateEvent('paste', e); + } else if (e.code === 'KeyZ' && e.ctrlKey && e.shiftKey) { + return phabricateEvent('redo', e); + } else if (e.code === 'KeyZ' && e.ctrlKey) { + return phabricateEvent('undo', e); + } else if (e.code === 'KeyH') { + return phabricateEvent('home', e); + } else if (e.key === 'ArrowRight') { + return phabricateEvent('nudgeright', e); + } else if (e.key === 'ArrowLeft') { + return phabricateEvent('nudgeleft', e); + } else if (e.key === 'ArrowUp') { + return phabricateEvent('nudgeup', e); + } else if (e.key === 'ArrowDown') { + return phabricateEvent('nudgedown', e); } - setTimeout(() => { - this.movingStuff = false; - }, 0); - }; - this.drawDeleteCircle = e => { - // Рисовка кружка для удаления копий - var maxdist = Math.max(this.room.gridX, this.room.gridY) || 32; - this.refreshRoomCanvas(e); - var cx = this.refs.canvas.x; - cx.fillStyle = '#F00'; - cx.strokeStyle = '#000'; - cx.globalAlpha = 0.5; - cx.beginPath(); - cx.arc(this.xToRoom(e.offsetX), this.yToRoom(e.offsetY), maxdist, 0, 2 * Math.PI); - cx.fill(); - cx.stroke(); }; + this.on('mount', () => { + window.addEventListener('keydown', triggerKeyboardEvent); + }); + this.on('unmount', () => { + window.removeEventListener('keydown', triggerKeyboardEvent); + }); - /** - * Updating mouse coordinates display at the bottom-left corner - */ - this.updateMouseCoords = function updateMouseCoords(e) { - var dx = Math.floor(this.xToRoom(e.offsetX)), - dy = Math.floor(this.yToRoom(e.offsetY)); - if (this.room.gridX === 0 || e.altKey) { - this.mouseX = dx; - this.mouseY = dy; - } else { - this.mouseX = Math.round(dx / this.room.gridX) * this.room.gridX; - this.mouseY = Math.round(dy / this.room.gridY) * this.room.gridY; - } - this.refs.mousecoords.innerHTML = `(${this.mouseX}:${this.mouseY})`; + this.currentTool = 'select'; + const mandatoryVisibilityMap = { + addCopies: 'copiesVisible', + addTiles: 'tilesVisible', + manageBackgrounds: 'backgroundsVisible' }; - - /** Start moving or show a placement preview **/ - this.onCanvasMove = e => { - e.preventUpdate = true; - if (this.dragging && !this.movingStuff) { - // Drag the viewport - this.roomx -= e.movementX / this.zoomFactor; - this.roomy -= e.movementY / this.zoomFactor; - this.refreshRoomCanvas(e); - } else if ( // Make more tiles or copies if Shift key is down - e.shiftKey && this.mouseDown && - ( - (this.tab === 'roomcopies' && this.currentTemplate !== -1) || - this.tab === 'roomtiles' - ) - ) { - this.onCanvasClick(e); - } else if (this.tab === 'roomcopies') { - this.onCanvasMoveCopies(e); - } else if (this.tab === 'roomtiles') { - this.onCanvasMoveTiles(e); + this.setTool = tool => () => { + this.currentTool = tool; + if (tool in mandatoryVisibilityMap) { + this.pixiEditor[mandatoryVisibilityMap[tool]] = true; } - this.updateMouseCoords(e); - }; - - /** Change zoom on mouse wheel */ - this.onCanvasWheel = e => { - if (e.wheelDelta > 0) { - this.refs.zoomslider.zoomIn(); - } else { - this.refs.zoomslider.zoomOut(); + if (tool !== 'select') { + this.pixiEditor.transformer.clear(); } - }; - this.setZoom = zoom => { - this.zoomFactor = zoom; - this.update(); - this.redrawGrid(); - this.refreshRoomCanvas(); - }; - - this.onCanvasContextMenu = e => { - this.dragging = false; - this.mouseDown = false; - if (this.tab === 'roomcopies') { - if (this.selectedCopies && this.selectedCopies.length) { - this.onCanvasContextMenuMultipleCopies(e); - } else { - this.onCanvasContextMenuCopies(e); - } - } else if (this.tab === 'roomtiles') { - if (this.selectedTiles && this.selectedTiles.length) { - this.onCanvasContextMenuMultipleTiles(e); - } else { - this.onCanvasContextMenuTiles(e); - } + if (tool === 'addTiles' && !this.pixiEditor.tileLayers.includes(this.currentTileLayer)) { + this.currentTileLayer = this.pixiEditor.tileLayers[0]; } - e.preventDefault(); - return true; }; - // Prompts a modal window then and shifts all the copies in a room at once. - this.roomShift = () => { - window.alertify.confirm(` - ${window.languageJSON.roomView.shiftLabel} - - - `) - .then(e => { - if (e.buttonClicked === 'ok') { - var dx = Number(document.getElementById('roomshiftx').value) || 0, - dy = Number(document.getElementById('roomshifty').value) || 0; - for (const copy of this.room.copies) { - copy.x += dx; - copy.y += dy; - } - for (const tileLayer of this.room.tiles) { - for (const tile of tileLayer.tiles) { - tile.x += dx; - tile.y += dy; + this.currentTemplate = -1; + this.changeSelectedTemplate = template => { + this.currentTemplate = template; + }; + + this.tilePatch = void 0; + this.changeTilePatch = tilePatch => { + this.tilePatch = tilePatch; + }; + this.changeTileLayer = layer => { + this.currentTileLayer = layer; + }; + + this.zoom = 1; + + this.changeBgColor = (e, color) => { + this.opts.room.backgroundColor = color; + this.pixiEditor.renderer.backgroundColor = PIXI.utils.string2hex(color); + }; + + this.changeSimulated = () => { + this.pixiEditor.simulate = !this.pixiEditor.simulate; + }; + + this.gridOn = true; + this.gridMenu = { + opened: false, + items: [{ + label: this.voc.gridOff, + click: () => { + this.gridOn = !this.gridOn; + }, + type: 'checkbox', + checked: () => !this.gridOn, + hotkeyLabel: 'Ctrl+G' + }, { + label: this.voc.toggleDiagonalGrid, + click: () => { + this.opts.room.diagonalGrid = !this.opts.room.diagonalGrid; + }, + type: 'checkbox', + checked: () => this.opts.room.diagonalGrid + }, { + label: this.voc.changeGridSize, + click: () => { + window.alertify + .confirm(this.voc.gridSize + `
x `) + .then(e => { + if (e.buttonClicked === 'ok') { + this.opts.room.gridX = Number(document.getElementById('theGridSizeX').value); + this.opts.room.gridY = Number(document.getElementById('theGridSizeY').value); } - } - this.refreshRoomCanvas(); + this.update(); + }); } - }); + }] + }; + this.openGridMenu = e => { + this.refs.gridMenu.popup(e.clientX, e.clientY); }; - this.roomSave = () => { - if (this.nameTaken) { - // animate the error notice - require('./data/node_requires/jellify')(this.refs.errorNotice); - if (localStorage.disableSounds !== 'on') { - window.soundbox.play('Failure'); + this.zoomMenu = { + opened: false, + items: [...[12.5, 25, 50, 100, 200, 400, 800].map(zoom => ({ + label: `${zoom}%`, + click: () => { + this.zoom = 1 / zoom * 100; + this.pixiEditor.zoomTo(zoom); } - return false; - } - this.room.lastmod = Number(new Date()); - this.roomGenSplash() - .then(() => { - glob.modified = true; - this.parent.editing = false; - this.parent.update(); + })), { + type: 'separator' + }, { + label: this.voc.resetView, + click: () => { + this.pixiEditor.goHome(); + }, + hotkeyLabel: 'H' + }] + }; + this.openZoomMenu = e => { + this.refs.zoomMenu.popup(e.clientX, e.clientY); + }; + + const entityToIconMap = { + copies: 'template', + tiles: 'grid', + backgrounds: 'image' + }; + const entityVisibilityItems = []; + for (const entityType in entityToIconMap) { + const icon = entityToIconMap[entityType]; + entityVisibilityItems.push({ + label: this.voc[entityType], + click: () => { + this.pixiEditor[entityType + 'Visible'] = !this.pixiEditor[entityType + 'Visible']; + }, + type: 'checkbox', + checked: () => this.pixiEditor[entityType + 'Visible'] }) - .catch(err => { - console.error(err); - glob.modified = true; - this.parent.editing = false; - this.parent.update(); - }); - return true; }; - - this.sortHorizontally = () => { - if (this.tab === 'roomcopies') { - this.room.copies.sort((a, b) => a.x - b.x); - } else { - // tiles - this.currentTileLayer.tiles.sort((a, b) => a.x - b.x); - } - this.resortRoom(); - this.refreshRoomCanvas(); + this.visibilityMenu = { + opened: false, + items: [ + ...entityVisibilityItems, + { + type: 'separator' + }, { + label: this.voc.xrayMode, + click: () => { + this.pixiEditor.xrayMode = !this.pixiEditor.xrayMode; + }, + type: 'checkbox', + checked: () => this.pixiEditor.xrayMode + }, { + label: this.voc.colorizeTileLayers, + click: () => { + this.pixiEditor.colorizeTileLayers = !this.pixiEditor.colorizeTileLayers; + }, + type: 'checkbox', + checked: () => this.pixiEditor.colorizeTileLayers + } + ] }; - this.sortVertically = () => { - if (this.tab === 'roomcopies') { - this.room.copies.sort((a, b) => a.y - b.y); - } else { - // tiles - this.currentTileLayer.tiles.sort((a, b) => a.y - b.y); - } - this.resortRoom(); - this.refreshRoomCanvas(); + this.openVisibilityMenu = e => { + this.refs.visibilityMenu.popup(e.clientX, e.clientY); }; - this.resortRoom = () => { - // Make an array of all the backgrounds, tile layers and copies, and then sort it. - this.stack = this.room.copies.concat(this.room.backgrounds).concat(this.room.tiles); - const projTemplates = global.currentProject.templates; - this.stack.sort((a, b) => { - const depthA = a.depth !== void 0 ? a.depth : projTemplates[glob.templatemap[a.uid]].depth, - depthB = b.depth !== void 0 ? b.depth : projTemplates[glob.templatemap[b.uid]].depth; - return depthA - depthB; - }); + this.editingEvents = false; + this.openEventsList = () => { + this.editingEvents = true; }; - this.resortRoom(); - var templatesChanged = () => { - this.currentTemplate = -1; - this.resortRoom(); + this.closeRoomEvents = () => { + this.editingEvents = false; + this.update(); }; - window.signals.on('templatesChanged', templatesChanged); - this.on('unmount', () => { - window.signals.off('templatesChanged', templatesChanged); - }); - /** Canvas redrawing, with all the backgrounds, tiles and copies */ - // eslint-disable-next-line max-lines-per-function, complexity - this.refreshRoomCanvas = () => { - if (this.forbidDrawing) { - return; - } - const {canvas} = this.refs, - sizes = this.refs.canvaswrap.getBoundingClientRect(); - // Make sure the canvas size is of correct width - if (Number(canvas.width) !== sizes.width || Number(canvas.height) !== sizes.height) { - canvas.width = sizes.width; - canvas.height = sizes.height; - } - - // Reset drawing transforms - canvas.x.setTransform(1, 0, 0, 1, 0, 0); - canvas.x.globalAlpha = 1; - // Clear the canvas - canvas.x.clearRect(0, 0, canvas.width, canvas.height); - // Fill it with a background color - canvas.x.fillStyle = this.room.backgroundColor || '#000000'; - canvas.x.fillRect(0, 0, canvas.width, canvas.height); - - // Apply camera movement + zoom - canvas.x.translate(Math.floor(canvas.width / 2), Math.floor(canvas.height / 2)); - canvas.x.scale(this.zoomFactor, this.zoomFactor); - canvas.x.translate(-this.roomx, -this.roomy); - - // Disable pixel interpolation, if needed - canvas.x.imageSmoothingEnabled = !global.currentProject.settings.rendering.pixelatedrender; - - for (let i = 0, li = this.stack.length; i < li; i++) { - if (this.stack[i].tiles) { // a tile layer - const layer = this.stack[i]; - if (!layer.hidden) { - for (const tile of layer.tiles) { - const img = glob.texturemap[tile.texture], - tex = img.g; - const x = tex.offx + (tex.width + tex.marginx) * tile.grid[0] - tex.marginx, - y = tex.offy + (tex.height + tex.marginy) * tile.grid[1] - tex.marginy, - w = (tex.width + tex.marginx) * tile.grid[2] - tex.marginx, - h = (tex.height + tex.marginy) * tile.grid[3] - tex.marginy; - canvas.x.drawImage( - img, - x, y, w, h, - tile.x, tile.y, w, h - ); - } - } - } else if (this.stack[i].texture) { // a background layer - if (this.stack[i].texture !== -1) { - if (!('extends' in this.stack[i])) { - this.stack[i].extends = {}; - } - const scx = this.stack[i].extends.scaleX || 1, - scy = this.stack[i].extends.scaleY || 1, - shx = this.stack[i].extends.shiftX || 0, - shy = this.stack[i].extends.shiftY || 0; - canvas.x.save(); - canvas.x.fillStyle = canvas.x.createPattern(glob.texturemap[this.stack[i].texture], this.stack[i].extends.repeat || 'repeat'); - canvas.x.translate(shx, shy); - canvas.x.scale(scx, scy); - canvas.x.fillRect( - (this.xToRoom(0) - shx) / scx, (this.yToRoom(0) - shy) / scy, - canvas.width / scx / this.zoomFactor, - canvas.height / scy / this.zoomFactor - ); - canvas.x.restore(); - } - } else { // A copy - const copy = this.stack[i], - template = global.currentProject.templates[glob.templatemap[copy.uid]]; - let texture, gra, w, h, ox, oy, - grax, gray; // texture's drawing center - if (template.texture !== -1) { - texture = glob.texturemap[template.texture]; - gra = glob.texturemap[template.texture].g; - w = gra.width; - h = gra.height; - ox = gra.offx; - oy = gra.offy; - [grax, gray] = gra.axis; - } else { - texture = glob.texturemap[-1]; - w = h = 32; - grax = gray = 16; - ox = oy = 0; - } - if ((copy.tx || copy.tx === 0) || - (copy.ty || copy.ty === 0) || - (copy.tr && copy.tr !== 0)) { - canvas.x.save(); - canvas.x.translate(copy.x, copy.y); - canvas.x.rotate((copy.tr || 0) * Math.PI / -180); - canvas.x.scale(copy.tx ?? 1, copy.ty ?? 1); - canvas.x.drawImage( - texture, - ox, oy, w, h, - -grax, -gray, w, h - ); - canvas.x.restore(); - } else { - const tex = glob.texturemap[template.texture].g; - canvas.x.drawImage( - texture, - tex.offx, tex.offy, w, h, - copy.x - grax, copy.y - gray, w, h - ); - } - } - } - - // Grid drawing - if (this.room.gridX > 1) { - canvas.x.globalCompositeOperation = 'exclusion'; - canvas.x.fillStyle = canvas.x.createPattern(this.gridCanvas, 'repeat'); - canvas.x.fillRect( - this.xToRoom(0), this.yToRoom(0), - canvas.width / this.zoomFactor, canvas.height / this.zoomFactor - ); - canvas.x.globalCompositeOperation = 'source-over'; - } - // Outline selected tiles - if (this.tab === 'roomtiles' && this.selectedTiles && this.selectedTiles.length) { - for (const tile of this.selectedTiles) { - const {g} = glob.texturemap[tile.texture]; - this.drawSelection( - tile.x, - tile.y, - tile.x + g.width * tile.grid[2], - tile.y + g.height * tile.grid[3] - ); - } - } - // Outline selected copies - if (this.tab === 'roomcopies' && this.selectedCopies && this.selectedCopies.length) { - for (const copy of this.selectedCopies) { - this.drawSelection(copy); - } - } - - // Outline the starting viewport frame - this.drawSelection(-1.5, -1.5, this.room.width + 1.5, this.room.height + 1.5); - - // Outline room's limits - if (this.room.restrictCamera) { - this.drawSelection( - (this.room.restrictMinX || 0) - 1.5, - (this.room.restrictMinY || 0) - 1.5, - (this.room.restrictMaxX === void 0 ? this.room.width : this.room.restrictMaxX) + 1.5, - (this.room.restrictMaxY === void 0 ? this.room.height : this.room.restrictMaxY) + 1.5 - ); - } + this.saveRoom = async () => { + const {writeRoomPreview} = require('./data/node_requires/resources/rooms'); + this.pixiEditor.serialize(); + await Promise.all([ + writeRoomPreview(this.opts.room, this.pixiEditor.getSplashScreen(true), true), + writeRoomPreview(this.opts.room, this.pixiEditor.getSplashScreen(false), false) + ]); + this.opts.onclose(); }; - this.drawSelection = (x1, y1, x2, y2) => { - const cx = this.refs.canvas.x; - cx.lineJoin = 'round'; - cx.lineCap = 'round'; - if (typeof x1 !== 'number') { - const copy = x1, - template = global.currentProject.templates[glob.templatemap[copy.uid]], - texture = glob.texturemap[template.texture].g; - var left, top, height, width; - if (copy.tr) { - cx.strokeStyle = localStorage.UItheme === 'Night' ? '#44dbb5' : '#446adb'; - cx.lineWidth = 3; - cx.beginPath(); - cx.moveTo(copy.x - 32, copy.y); - cx.lineTo(copy.x + 32, copy.y); - cx.moveTo(copy.x, copy.y - 32); - cx.lineTo(copy.x, copy.y + 32); - cx.stroke(); - cx.strokeStyle = localStorage.UItheme === 'Night' ? '#1C2B42' : '#fff'; - cx.lineWidth = 1; - cx.moveTo(copy.x - 32, copy.y); - cx.lineTo(copy.x + 32, copy.y); - cx.moveTo(copy.x, copy.y - 32); - cx.lineTo(copy.x, copy.y + 32); - cx.stroke(); - return; - } - if (template.texture !== -1) { - left = copy.x - texture.axis[0] * (copy.tx ?? 1) - 1.5; - top = copy.y - texture.axis[1] * (copy.ty ?? 1) - 1.5; - width = texture.width * (copy.tx ?? 1) + 3; - height = texture.height * (copy.ty ?? 1) + 3; - } else { - left = copy.x - 16 - 1.5; - top = copy.y - 16 - 1.5; - height = 32 + 3; - width = 32 + 3; - } - x1 = left; - y1 = top; - x2 = left + width; - y2 = top + height; - } - cx.strokeStyle = localStorage.UItheme === 'Night' ? '#44dbb5' : '#446adb'; - cx.lineWidth = 3; - cx.strokeRect(x1, y1, x2 - x1, y2 - y1); - cx.strokeStyle = localStorage.UItheme === 'Night' ? '#1C2B42' : '#fff'; - cx.lineWidth = 1; - cx.strokeRect(x1, y1, x2 - x1, y2 - y1); + const resizeEditor = () => { + setTimeout(() => { + this.pixiEditor.resize(); + }, 10); }; - - /** - * Генерирует миниатюру комнаты - */ - this.roomGenSplash = function roomGenSplash() { - const {imageCover, toBuffer} = require('./data/node_requires/imageUtils'); - return new Promise((accept, decline) => { - const c = imageCover(this.refs.canvas, 340, 256); - const buf = toBuffer(c); - const roomSplashName = global.projdir + '/img/r' + this.room.uid + '.png'; - fs.writeFile(roomSplashName, buf, err => { - if (err) { - decline(err); - } else { - accept(roomSplashName); - } - }); - const projSplashName = global.projdir + '/img/splash.png'; - fs.writeFile(projSplashName, buf, err => { - if (err) { - decline(err); - } - }); - }); + const serialize = () => { + this.pixiEditor.serialize(); }; + this.on('mount', () => { + window.signals.on('exportProject', serialize); + window.signals.on('roomsFocus', resizeEditor); + }); + this.on('unmount', () => { + window.signals.off('exportProject', serialize); + window.signals.off('roomsFocus', resizeEditor); + }); diff --git a/src/riotTags/rooms/room-entities-properties.tag b/src/riotTags/rooms/room-entities-properties.tag new file mode 100644 index 000000000..1d3870f59 --- /dev/null +++ b/src/riotTags/rooms/room-entities-properties.tag @@ -0,0 +1,482 @@ +// + @attribute pixieditor (RoomEditor) + The reference to the current pixi editor + + @attribute ontransformchange (riot function) + This function is called when this widget makes transformation + changes to selected objects. Used to update the selection box. + + @method applyChanges + Call this on selection change to apply changes to the current selection + @method updatePropList + Call this on selection change to re-scan the selection for values + +room-entities-properties + div(if="{opts.pixieditor?.currentSelection.size}") + // Basic properties + virtual(each="{prop in basicProps}") + b {voc.copyProperties[prop.vocKey]}: + // Point2D + .aPoint2DInput.compact.wide(if="{prop.type === 'xy'}") + label.flexrow + span.nogrow X: + input.nmr( + type="number" + oninput="{wireAndApply('this.changes.basic.' + prop.key + '.x')}" + onchange="{memorizeChanges}" + value="{changes.basic[prop.key].x}" + placeholder="{String(changes.basic[prop.key].x)}" + step="{prop.step}" + ) + .aSpacer.noshrink.nogrow + label.flexrow + span.nogrow Y: + input.nmr( + type="number" + oninput="{wireAndApply('this.changes.basic.' + prop.key + '.y')}" + onchange="{memorizeChanges}" + value="{changes.basic[prop.key].y}" + placeholder="{String(changes.basic[prop.key].y)}" + step="{prop.step}" + ) + .flexrow(if="{prop.type === 'slider'}") + .aSliderWrap + input.compact( + type="range" min="{prop.from}" max="{prop.to}" step="{prop.step}" + value="{(changes.basic[prop.key] === parent.multipleType) ? 0 : (changes.basic[prop.key] || 0)}" + oninput="{wireAndApply('this.changes.basic.' + prop.key)}" + onchange="{memorizeChanges}" + list="{prop.datalist}" + ) + .DataTicks(if="{prop.datalist}") + .aDataTick(each="{value in [-180, -90, 0, 90, 180]}" style="left: {(value + 180) / 3.6}%") + .aSpacer + input.compact( + min="{prop.from}" max="{prop.to}" step="{prop.step}" type="number" + value="{String(changes.basic[prop.key])}" + oninput="{wireAndApply('this.changes.basic.' + prop.key)}" + onchange="{memorizeChanges}" + placeholder="{String(changes.basic[prop.key])}" + ) + color-input( + if="{prop.type === 'color'}" + color="{PIXI.utils.hex2string(changes.basic[prop.key])}" + onchange="{writeColor(prop.key)}" + onapply="{writeColorAndMemorize(prop.key)}" + hidealpha="true" + ) + + // Copies' extensions that come from mods + extensions-editor(show="{hasCopies}" entity="{changes.exts}" ref="exts" type="copy" compact="yes" wide="yup") + + // Custom properties for copies + div(if="{hasCopies}") + h3 {voc.customProperties} + table.wide.aPaddedTable.cellsmiddle + tr + th {voc.copyCustomProperties.property} + th {voc.copyCustomProperties.value} + th + tr(each="{val, prop in changes.customProps}") + td + input.wide( + type="text" + value="{prop}" + onchange="{renameProp}" + ) + p.anErrorNotice(if="{reservedProps.includes(prop)}") {voc.copyCustomProperties.nameOccupied} + td + input.wide( + type="text" + value="{val === multipleType ? '' : JSON.stringify(val)}" + placeholder="{val === multipleType ? String(multipleType) : ''}" + onchange="{changeValue}" + ) + td + button.toright.square.inline.nm(onclick="{deleteCustomProperty(prop)}" title="{vocGlob.delete}") + svg.feather + use(xlink:href="#trash") + .clear + button.nogrow(onclick="{addCustomProperty}") + svg.feather + use(xlink:href="#plus") + span {voc.copyCustomProperties.addProperty} + datalist#theDatalistDegrees + option(value="-180") + option(value="-90") + option(value="0") + option(value="90") + option(value="180") + script. + this.namespace = 'roomView'; + this.mixin(window.riotVoc); + this.mixin(window.riotWired); + + const {Copy} = require('./data/node_requires/roomEditor/entityClasses/Copy'); + + this.changes = { + exts: {}, + basic: {}, + customProps: {} + }; + + this.basicProps = [{ + // An i18n key to look for in roomView.copyProperties, for a label + vocKey: 'position', + // A key to write to + key: 'position', + // The type of this field. `xy` stands for 2D point input + type: 'xy', + step: 8 + }, { + vocKey: 'scale', + key: 'scale', + type: 'xy', + step: 0.1 + }, { + vocKey: 'rotation', + key: 'angle', + type: 'slider', + from: -180, + to: 180, + step: 1, + datalist: 'theDatalistDegrees' + }, { + vocKey: 'opacity', + key: 'alpha', + type: 'slider', + from: 0, + to: 1, + step: 0.01 + }, { + vocKey: 'tint', + key: 'tint', + type: 'color' + }]; + + // Utilities for writing basic properties + const typeWrap = (prop, entity) => { + if (prop.type == 'xy') { + return { + x: entity[prop.key].x, + y: entity[prop.key].y + }; + } + return entity[prop.key]; + }; + this.writeColor = key => (e, value) => { + this.changes.basic[key] = PIXI.utils.string2hex(value); + this.applyChanges(); + }; + this.wireAndApply = path => e => { + this.wire(path)(e); + this.applyChanges(); + this.opts.ontransformchange(); + e.stopPropagation(); + }; + + const Magic = function Magic() { + void 'sparkles'; + }; + Magic.prototype.toString = () => this.voc.copyProperties.multipleValues; + Magic.prototype.toNumber = () => 0; + this.multipleType = new Magic(); + + this.firstRun = true; + /** + * Rescans the list of selected items and forms a list of matching and different properties. + * Different changes are reflected as this.multipleType. + * + * Before the rescan, it always writes applied changes to the previous selection. + */ + this.updatePropList = () => { + this.firstRun = false; + this.changes = { + exts: {}, + basic: {}, + customProps: {} + }; + + this.hasCopies = false; + const {basic} = this.changes; + const selection = this.opts.pixieditor.currentSelection; + // Quicker run for built-in properties + for (const property of this.basicProps) { + for (const entity of selection) { + if (!(property.key in basic)) { + basic[property.key] = typeWrap(property, entity); + } else if (property.type === 'xy') { + if (basic[property.key].x !== this.multipleType) { + if (basic[property.key].x !== entity[property.key].x) { + basic[property.key].x = this.multipleType; + } + } + if (basic[property.key].y !== this.multipleType) { + if (basic[property.key].y !== entity[property.key].y) { + basic[property.key].y = this.multipleType; + } + } + if (basic[property.key].x === this.multipleType && + basic[property.key].y === this.multipleType + ) { + break; + } + } else if (basic[property.key] !== entity[property.key]) { + basic[property.key] = this.multipleType; + break; + } + } + } + let copyCount = 0; + const propCount = {}; + // Separate run for custom properties and extensions + for (const entity of selection) { + // Skip stuff that doesn't support custom properties + if (!(entity instanceof Copy)) { + continue; + } + this.hasCopies = true; + copyCount++; + for (const property in entity.copyCustomProps) { + if (!(property in this.changes.customProps)) { + this.changes.customProps[property] = entity.copyCustomProps[property]; + propCount[property] = 1; + } else if (this.changes.customProps[property] !== entity.copyCustomProps[property]) { + this.changes.customProps[property] = this.multipleType; + propCount[property] ++; + } else { + propCount[property] ++; + } + } + } + // check if some copies did not have particular custom properties and mark such as (Multiple) + for (const property in propCount) { + if (propCount[property] !== copyCount) { + this.changes.customProps[property] = this.multipleType; + console.log(`Marked property '${property}' as multiple, got ${propCount[property]} properties for ${copyCount} copies`); + } + } + this.update(); + }; + + // an ID to use as newly created property names + this.currentId = 1; + this.renameProp = e => { + const {prop, val} = e.item; + const newName = e.target.value.trim(); + delete this.changes.customProps[prop]; + this.changes.customProps[newName] = val; + }; + this.changeValue = e => { + const {prop} = e.item; + // attempt to parse the value + // only strings will be unparsable with the JSON.parse method + let trueValue; + try { + trueValue = JSON.parse(e.target.value); // JSON, number, boolean + } catch { + trueValue = e.target.value; // string + } + this.changes.customProps[prop] = trueValue; + }; + this.addCustomProperty = () => { + this.changes.customProps['newProperty' + this.currentId] = ''; + this.currentId++; + }; + this.deleteCustomProperty = (prop) => e => { + delete this.changes.customProps[prop]; + this.applyChanges(); + }; + + this.reservedProps = [ + '_accessibleActive', + '_accessibleDiv', + '_bounds', + '_boundsID', + '_boundsRect', + '_cachedTint', + '_destroyed', + '_enabledFilters', + '_height', + '_lastSortedIndex', + '_localBounds', + '_localBoundsRect', + '_mask', + '_tempDisplayObjectParent', + '_tintedCanvas', + '_width', + '_zIndex', + 'accessible', + 'accessibleChildren', + 'accessibleHint', + 'accessiblePointerEvents', + 'accessibleTitle', + 'accessibleType', + 'alpha', + 'anchor', + 'angle', + 'animationSpeed', + 'autoUpdate', + 'blendMode', + 'buttonMode', + 'cacheAsBitmap', + 'children', + 'currentFrame', + 'cursor', + 'filterArea', + 'filters', + 'height', + 'hitArea', + 'interactive', + 'interactiveChildren', + 'isMask', + 'isSprite', + 'localTransform', + 'loop', + 'mask', + 'onComplete', + 'onFrameChange', + 'onLoop', + 'parent', + 'pivot', + 'playing', + 'pluginName', + 'position', + 'renderable', + 'rotation', + 'roundPixels', + 'scale', + 'skew', + 'sortableChildren', + 'sortDirty', + 'texture', + 'textures', + 'tint', + 'totalFrames', + 'transform', + 'updateAnchor', + 'visible', + 'width', + 'worldAlpha', + 'worldTransform', + 'worldVisible', + 'x', + 'y', + 'zIndex', + 'fromFrames', + 'fromImages', + '_calculateBounds', + '_onTextureUpdate', + '_recursivePostUpdateTransform', + '_render', + 'addChild', + 'addChildAt', + 'calculateBounds', + 'calculateTrimmedVertices', + 'calculateVertices', + 'containerUpdateTransform', + 'containsPoint', + 'destroy', + 'disableTempParent', + 'displayObjectUpdateTransform', + 'enableTempParent', + 'getBounds', + 'getChildAt', + 'getChildByName', + 'getChildIndex', + 'getGlobalPosition', + 'getLocalBounds', + 'gotoAndPlay', + 'gotoAndStop', + 'onChildrenChange', + 'play', + 'removeChild', + 'removeChildAt', + 'removeChildren', + 'render', + 'renderAdvanced', + 'renderAdvancedWebGL', + 'renderCanvas', + 'renderWebGL', + 'setChildIndex', + 'setParent', + 'setTransform', + 'sortChildren', + 'stop', + 'swapChildren', + 'toGlobal', + 'toLocal', + 'update', + 'updateTransform' + ]; + + this.applyChanges = () => { + if (this.firstRun) { + return; + } + const selection = this.opts.pixieditor.currentSelection || []; + for (const entity of selection) { + // basic properties are applied to everything + for (const property in this.changes.basic) { + const value = this.changes.basic[property]; + if (value === this.multipleType) { + continue; + } + const type = this.basicProps.find(prop => prop.key === property).type; + switch (type) { + case 'xy': + if (value.x !== this.multipleType) { + entity[property].x = value.x; + } + if (value.y !== this.multipleType) { + entity[property].y = value.y; + } + break; + case 'color': + case 'slider': + entity[property] = value; + break; + default: + console.error(`Ignoring unknown property type: ${type}`); + break; + } + } + // Extensions and custom properties are supported for copies only + if (!(entity instanceof Copy)) { + continue; + } + // Write custom properties + for (const property in this.changes.customProps) { + const value = this.changes.customProps[property]; + if (value === this.multipleType) { + continue; + } + entity.copyCustomProps[property] = value; + } + // Custom properties that are missing from the changeset are removed + for (const property in entity.copyCustomProps) { + if (!(property in this.changes.customProps)) { + delete entity.copyCustomProps[property]; + } + } + // Write modded extensions + if (!this.refs.exts) { + // Nothing to write + continue; + } + for (const extension in this.refs.exts.extensions) { + const value = this.changes.exts[extension.key]; + if (value === this.multipleType) { + continue; + } + entity.copyExts[extension.key] = value; + } + } + }; + this.memorizeChanges = () => { + this.opts.pixieditor.history.snapshotTransforms(); + }; + this.writeColorAndMemorize = key => (e, value) => { + this.writeColor(key)(e, value); + this.opts.pixieditor.history.snapshotTransforms(); + } diff --git a/src/riotTags/rooms/room-events-editor.tag b/src/riotTags/rooms/room-events-editor.tag index 97c21783c..1fd2d54e9 100644 --- a/src/riotTags/rooms/room-events-editor.tag +++ b/src/riotTags/rooms/room-events-editor.tag @@ -1,3 +1,6 @@ +// + @attribute room (IRoom) + @attribute onsave (riot function) room-events-editor.aView.flexrow.pad .tall.flexfix.aPanel.pad.room-events-editor-Properties.nogrow .flexfix-body @@ -53,6 +56,5 @@ room-events-editor.aView.flexrow.pad }; this.roomSaveEvents = () => { - this.parent.editingCode = false; - this.parent.update(); + this.opts.onsave(); }; diff --git a/src/riotTags/rooms/room-properties.tag b/src/riotTags/rooms/room-properties.tag new file mode 100644 index 000000000..e1d22a274 --- /dev/null +++ b/src/riotTags/rooms/room-properties.tag @@ -0,0 +1,161 @@ +// + @attribute room + @attribute updatebg (riot function) + @attribute history (History) +room-properties + fieldset + label + b {voc.name} + br + input.wide( + type="text" + onfocus="{rememberValue}" + oninput="{wire('this.opts.room.name')}" + onchange="{recordChange(opts.room, 'name')}" + value="{opts.room.name}" + ) + fieldset + .aPoint2DInput.compact.wide + label + b {voc.width} + br + input.wide( + type="number" min="1" step="8" + onfocus="{rememberValue}" + oninput="{wire('this.opts.room.width')}" + onchange="{recordChange(opts.room, 'width')}" + value="{opts.room.width}" + ) + .aSpacer + label + b {voc.height} + br + input.wide( + type="number" min="1" step="8" + onfocus="{rememberValue}" + oninput="{wire('this.opts.room.height')}" + onchange="{recordChange(opts.room, 'height')}" + value="{opts.room.height}" + ) + fieldset + label.checkbox + input( + type="checkbox" + checked="{opts.room.restrictCamera}" + onchange="{handleToggle(opts.room, 'restrictCamera')}" + ) + span {voc.restrictCamera} + fieldset + .aPoint2DInput.compact.wide(if="{opts.room.restrictCamera}") + label + b {voc.minimumX}: + | + input.compact( + step="{opts.room.gridX}" type="number" + onfocus="{rememberValue}" + oninput="{wire('this.opts.room.restrictMinX')}" + onchange="{recordChange(opts.room, 'restrictMinX')}" + value="{opts.room.restrictMinX === void 0 ? 0 : opts.room.restrictMinX}" + ) + .aSpacer + label + b.nogrow {voc.minimumY}: + | + input.compact( + step="{opts.room.gridY}" type="number" + onfocus="{rememberValue}" + oninput="{wire('this.opts.room.restrictMinY')}" + onchange="{recordChange(opts.room, 'restrictMinY')}" + value="{opts.room.restrictMinY === void 0 ? 0 : opts.room.restrictMinY}" + ) + .aPoint2DInput.compact.wide(if="{opts.room.restrictCamera}") + label + b {voc.maximumX}: + | + input.compact( + step="{opts.room.gridX}" type="number" + onfocus="{rememberValue}" + oninput="{wire('this.opts.room.restrictMaxX')}" + onchange="{recordChange(opts.room, 'restrictMaxX')}" + value="{opts.room.restrictMaxX === void 0 ? opts.room.width : opts.room.restrictMaxX}" + ) + .aSpacer + label + b.nogrow {voc.maximumY}: + | + input.compact( + step="{opts.room.gridY}" type="number" + onfocus="{rememberValue}" + oninput="{wire('this.opts.room.restrictMaxY')}" + onchange="{recordChange(opts.room, 'restrictMaxY')}" + value="{opts.room.restrictMaxY === void 0 ? opts.room.height : opts.room.restrictMaxY}" + ) + + fieldset + b {voc.backgroundColor} + br + color-input.wide(onchange="{changeBgColor}" color="{opts.room.backgroundColor || '#000000'}") + + fieldset + label.block.checkbox + input( + type="checkbox" checked="{opts.room.isUi}" + onchange="{handleToggle(opts.room, 'isUi')}" + ) + b {voc.isUi} + + fieldset + extensions-editor(entity="{opts.room.extends}" type="room" wide="true" compact="true") + + script. + this.namespace = 'roomView'; + this.mixin(window.riotVoc); + this.mixin(window.riotWired); + + var prevValue; + this.rememberValue = e => { + if (e.target.type === 'number') { + prevValue = Number(e.target.value); + } else { + prevValue = e.target.value; + } + }; + this.recordChange = (entity, key) => e => { + if (!this.opts.history) { + return; + } + let value = e.target.value; + if (e.target.type === 'number') { + value = Number(value); + } + this.opts.history.pushChange({ + type: 'propChange', + key, + target: entity, + before: prevValue, + after: value + }); + }; + + this.changeBgColor = (e, color) => { + const prevChange = this.opts.room.backgroundColor; + this.opts.updatebg(e, color); + this.opts.history.pushChange({ + type: 'propChange', + key: 'backgroundColor', + target: this.opts.room, + before: prevChange, + after: this.opts.room.backgroundColor + }); + }; + this.handleToggle = (entity, key) => e => { + const prevValue = entity[key]; + entity[key] = !entity[key]; + this.opts.history.pushChange({ + type: 'propChange', + key, + target: entity, + before: prevValue, + after: entity[key] + }); + }; diff --git a/src/riotTags/rooms/room-template-picker.tag b/src/riotTags/rooms/room-template-picker.tag new file mode 100644 index 000000000..2be51b586 --- /dev/null +++ b/src/riotTags/rooms/room-template-picker.tag @@ -0,0 +1,23 @@ +// + @attribute onselect (riot function) + This function is called when a template is selected. + The only argument passed to this function is the selected template. + @attribute selected (ITemplate | -1) + Currently selected template, piped back from the room editor +room-template-picker + asset-viewer( + collection="{currentProject.templates}" + namespace="templates" + assettype="templates" + forcelayout="list" + click="{selectTemplate}" + thumbnails="{thumbnails}" + compact="true" + shownone="true" + selected="{opts.selected}" + ) + script. + this.thumbnails = require('./data/node_requires/resources/templates').getTemplatePreview; + this.selectTemplate = template => () => { + this.opts.onselect(template); + }; diff --git a/src/riotTags/rooms/room-tile-editor.tag b/src/riotTags/rooms/room-tile-editor.tag index e6bd5224f..8e4a1e43b 100644 --- a/src/riotTags/rooms/room-tile-editor.tag +++ b/src/riotTags/rooms/room-tile-editor.tag @@ -1,34 +1,56 @@ -room-tile-editor.room-editor-Tiles.tabbed.tall.flexfix +// + @attribute layer (TileLayer) + A pixi.js container with all its tiles. + @attribute layers (Iterable) + All exising tile layers in the current room. + @attribute pixieditor (RoomEditor) + When other attributes are not enough + + @attribute onchangetile (riot function) + Called with ITileSelection instance as its only argument + @attribute onchangelayer (riot function) + Called with TileLayer as its only argument. + + @attribute addlayer (riot function) + Called when a user wants to add a layer. + The only argument is an ITileLayerTemplate instance. + +room-tile-editor.room-editor-Tiles.flexfix(class="{opts.class}") + .flexfix-header + button.inline.wide(onclick="{openTextureSelector}") + svg.feather + use(xlink:href="#search") + span {voc.findTileset} .flexfix-body - canvas( + canvas.room-tile-editor-aCanvas( ref="tiledImage" - onmousedown="{startTileSelection}" - onmouseup="{stopTileSelection}" - onmousemove="{moveTileSelection}" + onpointerdown="{startTileSelection}" + onpointerup="{stopTileSelection}" + onpointermove="{moveTileSelection}" ) .flexfix-footer - button.inline.wide(onclick="{switchTiledImage}") + ul.aMenu.room-tile-editor-aLayerList + li.checkbox(each="{layer in opts.layers}" onclick="{changeTileLayer}" class="{active: layer === parent.opts.layer}") + input(type="checkbox" name="tileLayers" checked="{layer.alpha === 1}" onchange="{toggleTileLayer}") + b {layer.zIndex} + span.a(title="{parent.voc.moveTileLayer}") + svg.feather(onclick="{moveTileLayer}") + use(xlink:href="#shuffle") + span.a(title="{parent.vocGlob.delete}") + svg.feather(onclick="{deleteTileLayer}") + use(xlink:href="#trash") + button.inline.wide(onclick="{addTileLayer}") svg.feather - use(xlink:href="#search") - span {voc.findTileset} - .flexrow - select.wide(onchange="{changeTileLayer}" value="{parent.currentTileLayerId}") - option(each="{layer, ind in opts.room.tiles}" selected="{parent.currentTileLayerId === ind}" value="{ind}") {layer.hidden? '❌' : '✅'} {layer.depth} - - span.act(title="{vocGlob.delete}" onclick="{deleteTileLayer}") - svg.feather - use(xlink:href="#trash") - span.act(title="{parent.currentTileLayer.hidden? voc.show: voc.hide}" onclick="{toggleTileLayer}") - svg.feather - use(xlink:href="#{parent.currentTileLayer.hidden? 'eye' : 'eye-off'}") - span.act(title="{voc.moveTileLayer}" onclick="{moveTileLayer}") - svg.feather - use(xlink:href="#shuffle") - span.act(title="{vocGlob.add}" onclick="{addTileLayer}") - svg.feather - use(xlink:href="#plus") + use(xlink:href="#plus") + span {voc.addTileLayer || vocGlob.add} .block - extensions-editor(type="tileLayer" entity="{parent.currentTileLayer.extends}" compact="yep" wide="sure") + extensions-editor( + if="{opts.layer}" + type="tileLayer" + entity="{opts.layer.extends}" + compact="true" + wide="true" + ) asset-selector( ref="tilesetPicker" if="{pickingTileset}" @@ -37,43 +59,35 @@ room-tile-editor.room-editor-Tiles.tabbed.tall.flexfix onselected="{onTilesetSelected}" ) script. - this.parent.tileX = 0; - this.parent.tileY = 0; - this.parent.tileSpanX = 1; - this.parent.tileSpanY = 1; - if (!('tiles' in this.opts.room) || !this.opts.room.tiles.length) { - this.opts.room.tiles = [{ - depth: -10, - tiles: [], - extends: {} - }]; - this.parent.resortRoom(); - } - [this.parent.currentTileLayer] = this.opts.room.tiles; - this.parent.currentTileLayerId = 0; + this.tileX = 0; + this.tileY = 0; + this.tileSpanX = 1; + this.tileSpanY = 1; this.namespace = 'roomTiles'; this.mixin(window.riotVoc); this.mixin(window.riotWired); - this.deleteTileLayer = () => { + this.on('update', () => { + if (!this.opts.layer && this.opts.layers.length) { + this.opts.onchangelayer(this.opts.layers[0]); + this.update(); + } else if (this.opts.layer && !this.opts.layers.length) { + this.opts.onchangelayer(void 0); + this.update(); + } + }); + + this.deleteTileLayer = e => { + const {layer} = e.item; window.alertify .okBtn(window.languageJSON.common.delete) .cancelBtn(window.languageJSON.common.cancel) .confirm(window.languageJSON.common.confirmDelete.replace('{0}', window.languageJSON.common.tileLayer)) .then(e => { if (e.buttonClicked === 'ok') { - var tiles = this.opts.room; - var index = tiles.tiles.indexOf(this.parent.currentTileLayer); - tiles.tiles.splice(index, 1); - if (tiles.tiles.length) { - [this.parent.currentTileLayer] = tiles.tiles; - this.parent.currentTileLayerId = 0; - } else { - this.parent.currentTileLayer = false; - } - this.parent.resortRoom(); - this.parent.refreshRoomCanvas(); + layer.detach(true); + this.opts.onchangelayer(this.opts.layers[0]); this.update(); window.alertify .okBtn(window.languageJSON.common.ok) @@ -81,15 +95,25 @@ room-tile-editor.room-editor-Tiles.tabbed.tall.flexfix } }); }; - this.moveTileLayer = () => { + this.moveTileLayer = e => { + const {layer} = e.item; window.alertify - .defaultValue(this.parent.currentTileLayer.depth) + .defaultValue(layer.zIndex) + .okBtn(window.languageJSON.common.ok) + .cancelBtn(window.languageJSON.common.cancel) .prompt(window.languageJSON.roomView.newDepth) .then(e => { if (e.inputValue && Number(e.inputValue)) { - this.parent.currentTileLayer.depth = Number(e.inputValue); - this.parent.resortRoom(); - this.parent.refreshRoomCanvas(); + const before = layer.zIndex; + layer.zIndex = Number(e.inputValue); + this.opts.layers.sort((a, b) => b.zIndex - a.zIndex); + this.opts.pixieditor.history.pushChange({ + type: 'propChange', + key: 'zIndex', + target: layer, + before, + after: layer.zIndex + }); this.update(); } }); @@ -100,71 +124,68 @@ room-tile-editor.room-editor-Tiles.tabbed.tall.flexfix .prompt(window.languageJSON.roomView.newDepth) .then(e => { if (e.inputValue && Number(e.inputValue)) { - var layer = { + const layer = { depth: Number(e.inputValue), tiles: [], extends: {} }; - this.opts.room.tiles.push(layer); - this.parent.currentTileLayer = layer; - this.parent.currentTileLayerId = this.opts.room.tiles.length - 1; - this.parent.resortRoom(); + const pixiTileLayer = this.opts.addlayer(layer, true); + this.opts.onchangelayer(pixiTileLayer); this.update(); } }); }; this.toggleTileLayer = () => { - this.parent.currentTileLayer.hidden = !this.parent.currentTileLayer.hidden; - this.parent.refreshRoomCanvas(); + this.opts.layer.visible = !this.opts.layer.visible; }; this.changeTileLayer = e => { - this.parent.currentTileLayer = this.opts.room.tiles[Number(e.target.value)]; - if (!this.parent.currentTileLayer.extends) { - this.parent.currentTileLayer.extends = {}; - } - this.parent.currentTileLayerId = Number(e.target.value); + this.opts.onchangelayer(e.item.layer); }; - this.switchTiledImage = () => { + this.openTextureSelector = () => { this.pickingTileset = true; }; this.onTilesetCancel = () => { this.pickingTileset = false; this.update(); }; - this.onTilesetSelected = textureId => { - const textures = require('./data/node_requires/resources/textures'); - this.parent.currentTileset = textures.getById(textureId); + this.onTilesetSelected = async textureId => { + const {getById, getDOMImage} = require('./data/node_requires/resources/textures'); + this.currentTexture = getById(textureId); this.pickingTileset = false; + this.update(); + this.currentTextureImg = await getDOMImage(this.currentTexture); this.redrawTileset(); + this.tileX = this.tileY = 0; + this.tileSpanX = this.tileSpanY = 1; + this.sendTileSelection(); this.update(); }; this.redrawTileset = () => { - const glob = require('./data/node_requires/glob'); var c = this.refs.tiledImage, cx = c.getContext('2d'), - g = this.parent.currentTileset, - i = glob.texturemap[g.uid]; - c.width = i.width; - c.height = i.height; + tex = this.currentTexture, + img = this.currentTextureImg; + c.width = img.width; + c.height = img.height; if (global.currentProject.settings.rendering.pixelatedrender) { c.style.imageRendering = 'pixelated'; } else { c.style.imageRendering = 'unset'; } cx.globalAlpha = 1; - cx.drawImage(i, 0, 0); + cx.drawImage(img, 0, 0); cx.strokeStyle = '#0ff'; cx.lineWidth = 1; cx.globalAlpha = 0.5; cx.globalCompositeOperation = 'exclusion'; - for (let i = 0, l = Math.min(g.grid[0] * g.grid[1], g.untill || Infinity); i < l; i++) { - const xx = i % g.grid[0], - yy = Math.floor(i / g.grid[0]), - x = g.offx + xx * (g.marginx + g.width), - y = g.offy + yy * (g.marginy + g.height), - w = g.width, - h = g.height; + for (let i = 0, l = Math.min(tex.grid[0] * tex.grid[1], tex.untill || Infinity); i < l; i++) { + const xx = i % tex.grid[0], + yy = Math.floor(i / tex.grid[0]), + x = tex.offx + xx * (tex.marginx + tex.width), + y = tex.offy + yy * (tex.marginy + tex.height), + w = tex.width, + h = tex.height; cx.strokeRect(x, y, w, h); } cx.globalCompositeOperation = 'source-over'; @@ -172,33 +193,33 @@ room-tile-editor.room-editor-Tiles.tabbed.tall.flexfix cx.lineJoin = 'round'; cx.strokeStyle = localStorage.UItheme === 'Night' ? '#44dbb5' : '#446adb'; cx.lineWidth = 3; - const selX = g.offx + this.parent.tileX * (g.width + g.marginx), - selY = g.offy + this.parent.tileY * (g.height + g.marginy), - selW = g.width * this.parent.tileSpanX + g.marginx * (this.parent.tileSpanX - 1), - selH = g.height * this.parent.tileSpanY + g.marginy * (this.parent.tileSpanY - 1); + const selX = tex.offx + this.tileX * (tex.width + tex.marginx), + selY = tex.offy + this.tileY * (tex.height + tex.marginy), + selW = tex.width * this.tileSpanX + tex.marginx * (this.tileSpanX - 1), + selH = tex.height * this.tileSpanY + tex.marginy * (this.tileSpanY - 1); cx.strokeRect(-0.5 + selX, -0.5 + selY, selW + 1, selH + 1); cx.strokeStyle = localStorage.UItheme === 'Night' ? '#1C2B42' : '#fff'; cx.lineWidth = 1; cx.strokeRect(-0.5 + selX, -0.5 + selY, selW + 1, selH + 1); }; this.startTileSelection = e => { - if (!this.parent.currentTileset) { + if (!this.currentTexture) { return; } // Adjust the pointer coordinates to account for potential scaling const bbox = e.target.getBoundingClientRect(); const px = e.layerX / bbox.width * e.target.width, py = e.layerY / bbox.height * e.target.height; - var g = this.parent.currentTileset; - this.parent.tileSpanX = 1; - this.parent.tileSpanY = 1; + const tex = this.currentTexture; + this.tileSpanX = 1; + this.tileSpanY = 1; this.selectingTile = true; - this.tileStartX = Math.round((px - g.offx - g.width * 0.5) / (g.width + g.marginx)); - this.tileStartX = Math.max(0, Math.min(g.grid[0], this.tileStartX)); - this.tileStartY = Math.round((py - g.offy - g.height * 0.5) / (g.height + g.marginy)); - this.tileStartY = Math.max(0, Math.min(g.grid[1], this.tileStartY)); - this.parent.tileX = this.tileStartX; - this.parent.tileY = this.tileStartY; + this.tileStartX = Math.round((px - tex.offx - tex.width * 0.5) / (tex.width + tex.marginx)); + this.tileStartX = Math.max(0, Math.min(tex.grid[0], this.tileStartX)); + this.tileStartY = Math.round((py - tex.offy - tex.height * 0.5) / (tex.height + tex.marginy)); + this.tileStartY = Math.max(0, Math.min(tex.grid[1], this.tileStartY)); + this.tileX = this.tileStartX; + this.tileY = this.tileStartY; this.redrawTileset(); }; this.moveTileSelection = e => { @@ -209,21 +230,32 @@ room-tile-editor.room-editor-Tiles.tabbed.tall.flexfix const bbox = e.target.getBoundingClientRect(); const px = e.layerX / bbox.width * e.target.width, py = e.layerY / bbox.height * e.target.height; - var g = this.parent.currentTileset; - this.tileEndX = Math.round((px - g.offx - g.width * 0.5) / (g.width + g.marginx)); - this.tileEndX = Math.max(0, Math.min(g.grid[0], this.tileEndX)); - this.tileEndY = Math.round((py - g.offy - g.height * 0.5) / (g.height + g.marginy)); - this.tileEndY = Math.max(0, Math.min(g.grid[1], this.tileEndY)); - this.parent.tileSpanX = 1 + Math.abs(this.tileStartX - this.tileEndX); - this.parent.tileSpanY = 1 + Math.abs(this.tileStartY - this.tileEndY); - this.parent.tileX = Math.min(this.tileStartX, this.tileEndX); - this.parent.tileY = Math.min(this.tileStartY, this.tileEndY); + const tex = this.currentTexture; + this.tileEndX = Math.round((px - tex.offx - tex.width * 0.5) / (tex.width + tex.marginx)); + this.tileEndX = Math.max(0, Math.min(tex.grid[0], this.tileEndX)); + this.tileEndY = Math.round((py - tex.offy - tex.height * 0.5) / (tex.height + tex.marginy)); + this.tileEndY = Math.max(0, Math.min(tex.grid[1], this.tileEndY)); + this.tileSpanX = 1 + Math.abs(this.tileStartX - this.tileEndX); + this.tileSpanY = 1 + Math.abs(this.tileStartY - this.tileEndY); + this.tileX = Math.min(this.tileStartX, this.tileEndX); + this.tileY = Math.min(this.tileStartY, this.tileEndY); this.redrawTileset(); }; + this.stopTileSelection = e => { if (!this.selectingTile) { return; } this.moveTileSelection(e); + this.sendTileSelection(); this.selectingTile = false; }; + this.sendTileSelection = () => { + this.opts.onchangetile({ + startX: this.tileX, + startY: this.tileY, + spanX: this.tileSpanX, + spanY: this.tileSpanY, + texture: this.currentTexture + }); + }; diff --git a/src/riotTags/rooms/room-type-picker.tag b/src/riotTags/rooms/room-type-picker.tag deleted file mode 100644 index c20849b85..000000000 --- a/src/riotTags/rooms/room-type-picker.tag +++ /dev/null @@ -1,89 +0,0 @@ -room-template-picker.room-editor-TemplateSwatches.tabbed.tall - .aSearchWrap - input.inline(type="text" onkeyup="{fuseSearch}" ref="fusesearch") - svg.feather - use(xlink:href="#search") - .room-editor-aTemplateSwatch( - if="{!searchResults}" - onclick="{parent.roomUnpickTemplate}" - class="{active: opts.current === -1}" - ) - span {voc.selectAndMove} - svg.feather - use(xlink:href="#move") - .room-editor-aTemplateSwatch( - each="{template in (searchResults? searchResults : templates)}" - title="{template.name}" - onclick="{selectTemplate(template)}" - class="{active: parent.opts.current === template}" - ) - span {template.name} - img( - src="{template.texture === -1? 'data/img/notexture.png' : (glob.texturemap[template.texture].src.split('?')[0] + '_prev.png?' + getTextureRevision(template))}" - draggable="false" - ) - .room-editor-aTemplateSwatch.filler - .room-editor-aTemplateSwatch.filler - .room-editor-aTemplateSwatch.filler - .room-editor-aTemplateSwatch.filler - .room-editor-aTemplateSwatch.filler - .room-editor-aTemplateSwatch.filler - .room-editor-aTemplateSwatch.filler - - script. - const glob = require('./data/node_requires/glob'); - this.glob = glob; - this.namespace = 'roomView'; - this.mixin(window.riotVoc); - this.mixin(window.riotWired); - - this.getTextureRevision = template => glob.texturemap[template.texture].g.lastmod; - - const fuseOptions = { - shouldSort: true, - tokenize: true, - threshold: 0.5, - location: 0, - distance: 100, - maxPatternLength: 32, - minMatchCharLength: 1, - keys: ['name'] - }; - const Fuse = require('fuse.js'); - this.fuseSearch = e => { - if (!this.mounted) { - this.searchResults = null; - return; - } - var val = (e ? e.target.value : this.refs.fusesearch.value).trim(); - if (val) { - var fuse = new Fuse(this.templates, fuseOptions); - this.searchResults = fuse.search(val); - } else { - this.searchResults = null; - } - }; - this.selectTemplate = template => () => { - this.parent.currentTemplate = template; - this.parent.selectedCopies = false; - }; - - this.updateTemplateList = () => { - this.templates = [...global.currentProject.templates]; - this.templates.sort((a, b) => a.name.localeCompare(b.name)); - this.fuseSearch(); - }; - this.updateTemplateList(); - var templatesChanged = () => { - this.updateTemplateList(); - this.update(); - }; - - window.signals.on('templatesChanged', templatesChanged); - this.on('unmount', () => { - window.signals.off('templatesChanged', templatesChanged); - }); - - this.on('mount', () => { - this.mounted = true; - }); diff --git a/src/riotTags/rooms/rooms-panel.tag b/src/riotTags/rooms/rooms-panel.tag index 8fdbc8a03..914d7b7f9 100644 --- a/src/riotTags/rooms/rooms-panel.tag +++ b/src/riotTags/rooms/rooms-panel.tag @@ -15,7 +15,7 @@ rooms-panel.aPanel.aView svg.feather use(xlink:href="#plus") span {parent.voc.create} - room-editor(if="{editing}" room="{editingRoom}") + room-editor(if="{editing}" room="{editingRoom}" onclose="{closeRoomEditor}") context-menu(menu="{roomMenu}" ref="roomMenu") script. const generateGUID = require('./data/node_requires/generateGUID'); @@ -44,6 +44,11 @@ rooms-panel.aPanel.aView this.editingRoom = room; this.editing = true; }; + this.closeRoomEditor = () => { + this.editingRoom = void 0; + this.editing = false; + this.update(); + }; this.roomMenu = { items: [{ diff --git a/src/riotTags/shared/asset-input.tag b/src/riotTags/shared/asset-input.tag index 9ed4fc1f4..499383d00 100644 --- a/src/riotTags/shared/asset-input.tag +++ b/src/riotTags/shared/asset-input.tag @@ -78,11 +78,12 @@ asset-input } this.openAsset = e => { - e.stopPropagation(); window.orders.trigger('openAsset', `${this.opts.assettype}/${this.currentAsset.uid}`); + e.stopPropagation(); }; - this.openSelector = () => { + this.openSelector = e => { this.showingSelector = true; + e.stopPropagation(); }; this.closeSelector = () => { this.showingSelector = false; diff --git a/src/riotTags/shared/asset-selector.tag b/src/riotTags/shared/asset-selector.tag index c4e40c558..cd65f5ca6 100644 --- a/src/riotTags/shared/asset-selector.tag +++ b/src/riotTags/shared/asset-selector.tag @@ -58,8 +58,9 @@ asset-selector.aDimmer.pointer.pad.fadein(onclick="{closeOnDimmer}" ref="dimmer" this.opts.oncancelled(); } } + e.stopPropagation(); }; - this.onAssetPicked = asset => () => { + this.onAssetPicked = asset => e => { if (this.opts.onselected) { if (asset === -1) { this.opts.onselected(-1); @@ -67,4 +68,5 @@ asset-selector.aDimmer.pointer.pad.fadein(onclick="{closeOnDimmer}" ref="dimmer" this.opts.onselected(asset.uid); } } + e.stopPropagation(); }; diff --git a/src/riotTags/shared/asset-viewer.tag b/src/riotTags/shared/asset-viewer.tag index c4e27f517..d8f3d4ae2 100644 --- a/src/riotTags/shared/asset-viewer.tag +++ b/src/riotTags/shared/asset-viewer.tag @@ -7,38 +7,45 @@ @attribute class (string) This tag has its own CSS classes, but allows arbitrary ones added as an attribute. - @attribute namespace (string) + @attribute [namespace] (string) A unique namespace used to store settings. Fallbacks to 'default'. - @attribute defaultlayout (string) + @attribute [defaultlayout] (string) The default listing layout used if the user has not selected one yet. Can be "cards", "list", "largeCards". + @attribute [forcelayout] (string) + Similar to [defaultlayout] + @attribute [compact] (atomic) + If set, the viewer hides several elements to fit in a more tight layout. @attribute assettype (string) The type of assets shown. The attribute is needed for groups to function. @attribute collection (riot function) A collection of items to iterate over while generating markup, sorting and firing events. - @attribute names (riot function) + @attribute [shownone] (atomic) + If set, shows a "none" asset that returns -1 in opts.click event. + @attribute [selected] (IAsset | -1) + Currently selected asset. If set, it will be highlighted in UI. + + @attribute [names] (riot function) A mapping funtion that takes a collection object and returns its human-readable name. Fallbacks to `item.name` if not defined. - @attribute thumbnails (riot function) + @attribute [thumbnails] (riot function) A mapping funtion that takes a collection object and returns a url for its thumbnail. The function is passed with the collection object, `true` or `false` depending on whether the large-card view is active, and `false`, which match the arguments used to get a URL for the thumbnail of the proper size in many get{X}Preview methods. - @attribute useicons (atomic) + @attribute [useicons] (atomic) Tells the asset viewer to use SVG icons instead of img tag. The `thumbnails` function should then return the name of the SVG icon. - @attribute shownone (atomic) - If set, shows a "none" asset that returns -1 in opts.click event. - - @attribute icons (riot function) + @attribute [icons] (riot function) A mapping funtion that takes a collection object and returns an array of icon names. The icons are shown near the asset's name and are used to convey some metadata. + @attribute click (riot function) A two-fold callback (item => e => {…}) fired when a user clicks on an item, passing the associated collection object as its only argument in the first function, and a MouseEvent in a second function - @attribute contextmenu (riot function) + @attribute [contextmenu] (riot function) A two-fold callback (item => e => {…}) that is given a collection object as its only argument in the first function, and a MouseEvent in a second function, when a user tries to call a context menu on an item. @@ -51,11 +58,11 @@ category is selected. Otherwise, groups are meant to be distinguished by their `uid` property. -asset-viewer.flexfix(class="{opts.namespace} {opts.class}") +asset-viewer.flexfix(class="{opts.namespace} {opts.class} {compact: opts.compact}") .flexfix-header - .toright - b {vocGlob.sort} - .aButtonGroup + .toright(class="{flexrow: opts.compact}") + b(if="{!opts.compact}") {vocGlob.sort} + .aButtonGroup.nml button.inline.square( onclick="{switchSort('date')}" class="{selected: sort === 'date' && !searchResults}" @@ -68,17 +75,17 @@ asset-viewer.flexfix(class="{opts.namespace} {opts.class}") ) svg.feather use(xlink:href="#sort-alphabetically") - .aSearchWrap + .aSearchWrap(style="{opts.compact ? 'width: auto;' : ''}") input.inline(type="text" onkeyup="{fuseSearch}") svg.feather use(xlink:href="#search") - button.inline.square(onclick="{switchLayout}") + button.inline.square(if="{!opts.forcelayout}" onclick="{switchLayout}") svg.feather use(xlink:href="#{layoutToIconMap[currentLayout]}") - button.inline.square.nogrow(onclick="{addNewGroup}") + button.inline.square.nogrow.nmr(onclick="{addNewGroup}") svg.feather use(xlink:href="#folder-plus") - span {voc.addNewGroup} + span(if="{!opts.compact}") {voc.addNewGroup} .toleft .clear @@ -86,6 +93,7 @@ asset-viewer.flexfix(class="{opts.namespace} {opts.class}") .aSpacer ul.Cards.nmt( if="{opts.assettype}" + class="{list: opts.compact}" ) li.aCard( onclick="{openGroup({isUngroupedGroup: true})}" @@ -116,14 +124,15 @@ asset-viewer.flexfix(class="{opts.namespace} {opts.class}") use(xlink:href="data/img/weirdFoldersIllustration.svg#illustration") br span {vocGlob.nothingToShowFiller} - ul.Cards(class="{layoutToClassListMap[currentLayout]}") - li.aCard(if="{opts.shownone}" onclick="{opts.click && opts.click(-1)}") + ul.Cards(class="{layoutToClassListMap[opts.forcelayout || currentLayout]}") + li.aCard(if="{opts.shownone}" onclick="{opts.click && opts.click(-1)}" class="{active: opts.selected === -1}") .aCard-aThumbnail img(src="data/img/notexture.png") .aCard-Properties span {vocGlob.none} li.aCard( each="{asset in (searchResults? searchResults : getGrouped(collection))}" + class="{active: opts.selected === asset}" oncontextmenu="{parent.opts.contextmenu && parent.opts.contextmenu(asset)}" onlong-press="{parent.opts.contextmenu && parent.opts.contextmenu(asset)}" onclick="{parent.opts.click && parent.opts.click(asset)}" @@ -143,7 +152,7 @@ asset-viewer.flexfix(class="{opts.namespace} {opts.class}") .asset-viewer-Icons(if="{parent.opts.icons}") svg.feather(each="{icon in parent.opts.icons(asset)}" class="feather-{icon}") use(xlink:href="#{icon}") - span.date(if="{asset.lastmod}") {niceTime(asset.lastmod)} + span.date(if="{asset.lastmod && !parent.opts.compact}") {niceTime(asset.lastmod)} group-editor( if="{showingGroupEditor}" onapply="{closeGroupEditor}" diff --git a/src/riotTags/shared/collapsible-section.tag b/src/riotTags/shared/collapsible-section.tag index c66631211..52a0460a7 100644 --- a/src/riotTags/shared/collapsible-section.tag +++ b/src/riotTags/shared/collapsible-section.tag @@ -11,33 +11,35 @@ @attribute [heading] (string) The heading to display - @attribute hlevel (integer) + @attribute [hlevel] (integer) A heading level from 1 to 7. Can be empty; if it is, a regular h3 is shown. - @attribute defaultstate ("opened"|"closed") + @attribute [icon] (string) + An icon that will be displayed instead of the default chevron. + @attribute [defaultstate] ("opened"|"closed") Sets the default state of the section. If it is not set, the section will appear closed. - @attribute storestatekey (string) + @attribute [storestatekey] (string) If set, remembers the state of this section in localStorage under the specified key. - @attribute preservedom (atomic) + @attribute [preservedom] (atomic) Whether or not to hide the content instead of removing it from DOM. It is recommended to turn this on for larger layouts. - @attribute ontoggle (riot function) + @attribute [ontoggle] (riot function) A callback that triggers when a user folds/unfolds the section. Passes the new state and this tag as two arguments. collapsible-section(class="{opts.class} {opened ? 'opened' : 'closed'}") - .flexrow(onclick="{toggle}") - span(if="{opts.heading}") - h1(if="{opts.hlevel == 1}") {opts.heading} - h2(if="{opts.hlevel == 2}") {opts.heading} - h3(if="{opts.hlevel == 3 || !opts.hlevel}") {opts.heading} - h4(if="{opts.hlevel == 4}") {opts.heading} - h5(if="{opts.hlevel == 5}") {opts.heading} - h6(if="{opts.hlevel == 6}") {opts.heading} - h7(if="{opts.hlevel == 7}") {opts.heading} - yield(from="header") + .collapsible-section-aHeader(onclick="{toggle}") + span + h1(if="{opts.heading && opts.hlevel == 1}") {opts.heading} + h2(if="{opts.heading && opts.hlevel == 2}") {opts.heading} + h3(if="{opts.heading && (opts.hlevel == 3 || !opts.hlevel)}") {opts.heading} + h4(if="{opts.heading && opts.hlevel == 4}") {opts.heading} + h5(if="{opts.heading && opts.hlevel == 5}") {opts.heading} + h6(if="{opts.heading && opts.hlevel == 6}") {opts.heading} + h7(if="{opts.heading && opts.hlevel == 7}") {opts.heading} + yield(from="header") svg.feather.a(class="{rotated: this.opened}") - use(xlink:href="#chevron-up") + use(xlink:href="#{opts.icon ? opts.icon : 'chevron-up'}") .collapsible-section-aWrapper(if="{opened || opts.preservedom}" hide="{!opened && opts.preservedom}") script. diff --git a/src/riotTags/shared/context-menu.tag b/src/riotTags/shared/context-menu.tag index ddda3478d..7c3941462 100644 --- a/src/riotTags/shared/context-menu.tag +++ b/src/riotTags/shared/context-menu.tag @@ -31,7 +31,7 @@ context-menu(class="{opened: opts.menu.opened}" ref="root" style="{opts.menu.col use(xlink:href="#{item.icon instanceof Function? item.icon() : item.icon}") input(type="checkbox" checked="{item.checked instanceof Function? item.checked() : item.checked}" if="{item.type === 'checkbox'}") span(if="{!item.type !== 'separator'}") {item.label} - span.hotkey(if="{!item.type !== 'separator' && item.hotkey}") ({item.hotkeyLabel || item.hotkey}) + span.hotkey(if="{!item.type !== 'separator' && (item.hotkey || item.hotkeyLabel)}") ({item.hotkeyLabel || item.hotkey}) svg.feather.context-menu-aChevron(if="{item.submenu && item.type !== 'separator'}") use(xlink:href="#chevron-right") context-menu(if="{item.submenu && item.type !== 'separator'}" menu="{item.submenu}") diff --git a/src/riotTags/shared/error-notice.tag b/src/riotTags/shared/error-notice.tag new file mode 100644 index 000000000..8e8799fd7 --- /dev/null +++ b/src/riotTags/shared/error-notice.tag @@ -0,0 +1,21 @@ +// + @slot + @attribute target +error-notice.anErrorNotice + + script. + const reposition = () => { + let {target} = this.opts; + if (target.root) { + target = target.root; + } + const box = target.getBoundingClientRect(); + this.root.style.top = (box.top + box.height) + 'px'; + this.root.style.left = (box.left + box.width / 2) + 'px'; + }; + this.on('update', reposition); + this.on('mount', reposition); + const interval = setInterval(reposition, 150); + this.on('unmount', () => { + clearInterval(interval); + }); diff --git a/src/riotTags/shared/zoom-slider.tag b/src/riotTags/shared/zoom-slider.tag index f5d25e28b..577dd898d 100644 --- a/src/riotTags/shared/zoom-slider.tag +++ b/src/riotTags/shared/zoom-slider.tag @@ -5,6 +5,8 @@ Calls the funtion when a user changes the zoom value. Passes the new zoom value as the one argument. + @method setZoom + Call this with a new zoom value in percents to manually update this zoom slider. @method zoomIn Call this property to advance the zoom value. This will call opts.onchage callback. @method zoomOut @@ -65,6 +67,11 @@ zoom-slider this.opts.onchanged(this.rawToZoom(e.target.value)); } }; + this.setZoom = newZoom => { + const rawValue = this.zoomToRaw(newZoom); + this.refs.zoomslider.value = rawValue; + this.update(); + }; this.zoomIn = () => { const rawValue = Math.min(Number(this.refs.zoomslider.value) + 10, 100); diff --git a/src/riotTags/sounds/sound-editor.tag b/src/riotTags/sounds/sound-editor.tag index f72fca24e..51d38f37b 100644 --- a/src/riotTags/sounds/sound-editor.tag +++ b/src/riotTags/sounds/sound-editor.tag @@ -53,9 +53,8 @@ sound-editor.aDimmer if (this.nameTaken) { // animate the error notice require('./data/node_requires/jellify')(this.refs.errorNotice); - if (localStorage.disableSounds !== 'on') { - soundbox.play('Failure'); - } + const {soundbox} = require('./data/node_requires/3rdparty/soundbox'); + soundbox.play('Failure'); return false; } diff --git a/src/riotTags/style-editor.tag b/src/riotTags/style-editor.tag index f213fd470..728ab8409 100644 --- a/src/riotTags/style-editor.tag +++ b/src/riotTags/style-editor.tag @@ -284,9 +284,8 @@ style-editor.aPanel.aView if (this.nameTaken) { // animate the error notice require('./data/node_requires/jellify')(this.refs.errorNotice); - if (localStorage.disableSounds !== 'on') { - soundbox.play('Failure'); - } + const {soundbox} = require('./data/node_requires/3rdparty/soundbox'); + soundbox.play('Failure'); return false; } this.styleobj.lastmod = Number(new Date()); diff --git a/src/riotTags/template-editor.tag b/src/riotTags/template-editor.tag index 7c5111e5d..a62718302 100644 --- a/src/riotTags/template-editor.tag +++ b/src/riotTags/template-editor.tag @@ -27,6 +27,10 @@ mixin templateProperties option(value="multiply" selected="{parent.template.blendMode === 'multiply'}") {parent.voc.blendModes.multiply} option(value="screen" selected="{parent.template.blendMode === 'screen'}") {parent.voc.blendModes.screen} fieldset + label.flexrow + b.nogrow.alignmiddle {parent.voc.animationFPS} + .aSpacer.nogrow + input.alignmiddle(type="number" max="60" min="1" step="1" value="{parent.template.animationFPS ?? 60}" onchange="{parent.wire('this.template.animationFPS')}") label.block.checkbox input(type="checkbox" checked="{parent.template.playAnimationOnStart}" onchange="{parent.wire('this.template.playAnimationOnStart')}") span {parent.voc.playAnimationOnStart} @@ -160,9 +164,8 @@ template-editor.aPanel.aView.flexrow this.update(); // animate the error notice require('./data/node_requires/jellify')(this.refs.errorNotice); - if (localStorage.disableSounds !== 'on') { - soundbox.play('Failure'); - } + const {soundbox} = require('./data/node_requires/3rdparty/soundbox'); + soundbox.play('Failure'); return false; } glob.modified = true; @@ -171,6 +174,7 @@ template-editor.aPanel.aView.flexrow this.parent.fillTemplateMap(); this.parent.update(); window.signals.trigger('templatesChanged'); + window.signals.trigger('templateChanged', this.template.uid); return true; }; this.changeCodeTab = scriptableEvent => { diff --git a/src/riotTags/textures/texture-editor.tag b/src/riotTags/textures/texture-editor.tag index 7ef4e1cdf..f835c92f1 100644 --- a/src/riotTags/textures/texture-editor.tag +++ b/src/riotTags/textures/texture-editor.tag @@ -856,15 +856,18 @@ texture-editor.aPanel.aView if (this.nameTaken) { // animate the error notice require('./data/node_requires/jellify')(this.refs.errorNotice); - if (localStorage.disableSounds !== 'on') { - soundbox.play('Failure'); - } + const {soundbox} = require('./data/node_requires/3rdparty/soundbox'); + soundbox.play('Failure'); return false; } this.parent.fillTextureMap(); glob.modified = true; this.texture.lastmod = Number(new Date()); - const {textureGenPreview} = require('./data/node_requires/resources/textures'); + const {textureGenPreview, updatePixiTexture} = require('./data/node_requires/resources/textures'); + updatePixiTexture(this.texture) + .then(() => { + window.signals.trigger('pixiTextureChanged', this.texture.uid); + }); textureGenPreview(this.texture, global.projdir + '/img/' + this.texture.origname + '_prev@2.png', 128); textureGenPreview(this.texture, global.projdir + '/img/' + this.texture.origname + '_prev.png', 64) .then(() => { diff --git a/src/riotTags/textures/texture-generator.tag b/src/riotTags/textures/texture-generator.tag index 3a8a2f19d..d9d3bc2c8 100644 --- a/src/riotTags/textures/texture-generator.tag +++ b/src/riotTags/textures/texture-generator.tag @@ -195,9 +195,8 @@ texture-generator.aView if (this.nameTaken) { this.update(); require('./data/node_requires/jellify')(this.refs.errorNotice); - if (localStorage.disableSounds !== 'on') { - soundbox.play('Failure'); - } + const {soundbox} = require('./data/node_requires/3rdparty/soundbox'); + soundbox.play('Failure'); return false; } diff --git a/src/styl/buildingBlocks.styl b/src/styl/buildingBlocks.styl index 4a50fe26d..000d37210 100644 --- a/src/styl/buildingBlocks.styl +++ b/src/styl/buildingBlocks.styl @@ -1,6 +1,9 @@ .aSpacer width 1rem height @width + &.small + width 0.5rem + height @width .aDimmer position fixed @@ -277,6 +280,8 @@ .aCard background background padding 0.8em + &.compact + padding 0.4em 0.8em margin 0 box-sizing border-box border 1px solid borderPale @@ -332,6 +337,16 @@ pointer-events none &:hover img background rgba(act, 0.35) + .aCard.compact & + width 1.5rem + svg + width 1.5rem + height 1.5rem + stroke-width 2 + img + width 2rem + height 2rem + margin -0.25rem 0 -0.25rem -0.25rem .aCard-Actions position absolute top 0.5rem @@ -380,6 +395,9 @@ sounds-panel, rooms-panel th border-bottom 1px solid borderBright text-align left + &.cellsmiddle + td, th + vertical-align middle .aNiceTable margin 1rem 0 border 1px solid borderBright diff --git a/src/styl/common.styl b/src/styl/common.styl index e6b4bcc39..0200d7a60 100644 --- a/src/styl/common.styl +++ b/src/styl/common.styl @@ -188,3 +188,11 @@ icon(icon = 'slash', color = act) color text [css-swatch="backgroundDeeper"] color backgroundDeeper +[css-swatch="background"] + color background +[css-swatch="red"] + color red +[css-swatch="green"] + color green +[css-swatch="orange"] + color orange diff --git a/src/styl/inputs.styl b/src/styl/inputs.styl index 3973dfe81..eea69c34f 100644 --- a/src/styl/inputs.styl +++ b/src/styl/inputs.styl @@ -222,6 +222,25 @@ input[type="reset"], margin 0 button, .button flex 1 1 auto + &.vertical + flex-flow column nowrap + button, .button + margin 0 + border-radius 0 + border-bottom-width 0 + border-right-width 1px + if themeThickBorders + border-right-width 2px + @extend button.square + &:first-child + border-top-left-radius br + border-top-right-radius br + &:last-child + border-bottom-left-radius br + border-bottom-right-radius br + border-bottom-width 1px + if themeThickBorders + border-bottom-width 2px button, input[type="button"], input[type="submit"], @@ -336,6 +355,8 @@ fieldset &::after content '' icon(check, background) + // a hack to fix pixelization of icons in certain context menus + transform scale(1) width 0.85rem height 1rem position absolute diff --git a/src/styl/tags/rooms/room-backgrounds-editor.styl b/src/styl/tags/rooms/room-backgrounds-editor.styl new file mode 100644 index 000000000..f5d4ea756 --- /dev/null +++ b/src/styl/tags/rooms/room-backgrounds-editor.styl @@ -0,0 +1,3 @@ +room-backgrounds-editor + collapsible-section asset-input > .aButtonGroup + margin-right 0 diff --git a/src/styl/tags/rooms/room-copy-properties.styl b/src/styl/tags/rooms/room-copy-properties.styl deleted file mode 100644 index 1d7f834ca..000000000 --- a/src/styl/tags/rooms/room-copy-properties.styl +++ /dev/null @@ -1,9 +0,0 @@ -room-copy-properties - display block - padding 1rem - room-editor &.aPanel - {shadamb} - position absolute - right 1rem - top 4rem - width 20rem \ No newline at end of file diff --git a/src/styl/tags/rooms/room-editor.styl b/src/styl/tags/rooms/room-editor.styl index c86cd3814..e61b31318 100644 --- a/src/styl/tags/rooms/room-editor.styl +++ b/src/styl/tags/rooms/room-editor.styl @@ -1,173 +1,101 @@ room-editor - display flex - flex-direction row - .room-editor-TemplateSwatches .aSearchWrap - display block - flex 1 1 100% - margin 0 0 3px - input - box-sizing border-box - width 100% - font-size 90% - border-radius 0 - .toolbar - display flex - flex-direction column - padding 0.5em - width 17em - box-sizing border-box - flex 0 0 auto - flex-grow 0 - .settings .fifty - padding 0.5rem - .palette - flex 1 1 auto - .settings button - margin 0.5em 0 - .aNav - border-bottom-right-radius 0 - border-bottom-left-radius 0 - .palette - position relative - .tabwrap - position absolute - left 0 - right 0 - top 0 - bottom 0 - .editor - display flex - position relative - flex 1 1 auto - .shift - position absolute - top 0.5rem - left 0.5rem - .zoom - position absolute - top 0.5rem - right 0.5rem - zoom-slider - width 20rem - .grid - position absolute - bottom 0.5rem - right 0.5rem - .center - position absolute - bottom 0.5rem - left 0.5rem - canvas - width 100% - height 100% - -.room-editor-TemplateSwatches, .room-editor-Backgrounds, .room-editor-Tiles - overflow-y scroll - position absolute - width 100% -.room-editor-TemplateSwatches - padding 0 0 0 1px !important - display flex - flex-flow row wrap - align-content start - align-items flex-start -.room-editor-aTemplateSwatch - list-style none - border 1px solid borderPale - flex 1 0 5rem - display inline-block - box-sizing border-box - text-align center - margin -1px 0 0 -1px - cursor pointer - padding 1.75rem 0.4em 0 - font-size 80% - line-height 1.7 - {trans} - position relative - z-index 1 - svg - width 2.5rem - height @width - color act - line-height 3rem - margin 1.35rem auto 0 - display block - padding-bottom 0.5rem - span - display block - text-overflow ellipsis - overflow hidden - width auto + background backgroundDeeper + & > canvas position absolute - left 0.5rem - top 0.25rem - right 0.5rem - &:hover - border-color act - z-index 10 - {transshort} - &.active - border-color accent1 - z-index 10 - img - height 64px - width 64px - &.filler - height 0 - visibility hidden - -.room-editor-Backgrounds - &.tabbed - padding 0.5rem - ul + left 0 + top 0 + width 100% + height 100% + .&-aToolsetHolder + pointer-events none + & > * + pointer-events initial + position absolute + left 0 + right 0 + top 0 + bottom 0 + overflow hidden + padding 1rem + display grid + grid-template-columns auto auto 1fr + grid-gap 0 1rem + align-items start + .&-aToolbar + border-radius br margin 0 - padding 0 - .bg - list-style none - padding 0.3em 0.8em + {shad} + button + border-color borderBright + .&-aContextPanel + @extends .aPanel + display block + width 18rem + padding 1rem border-radius br - border 1px solid borderBright - margin-bottom 0.2em - img - float left - width 64px - height 64px - border-radius br - cursor pointer - margin-right 1rem - & > span - display block - vertical-align middle - margin-top 1em - span - cursor pointer - {trans} - float right - margin-left 0.5rem - .active - color act - label + label - margin-top 0 - .fifty - padding 0.5rem 0.25rem - -.room-editor-Tiles - canvas - cursor pointer - min-width 100% - .act - color text - cursor pointer + overflow auto + z-index 2 + {shadamb} + resize both + min-width 15rem + max-width 50vw // why would you need it any longer + min-height 4rem + max-height calc(100% - 2rem) + .&-aTopPanel + border-radius br + border borderBright + {shad} + display flex + flex-flow row nowrap + background background + width max-content + justify-self center + grid-column 3 + & > * + padding 0.25rem 1rem + box-shadow none + border-top 0 + border-bottom 0 + border-left 0 + border-right 1px solid borderBright + margin 0 + border-radius 0 + &:first-child + border-radius br 0 0 br + &:last-child + border 0 + border-radius 0 br br 0 + & > .slim + padding 0.25rem 0.5rem + & > .checkbox + padding-left 2.5rem + [type="checkbox"] + top 50% + margin-left 1rem + transform translate(0, -50%) + & > zoom-slider + width 10rem + .&-aSelectionToggle display inline-block - padding 0.35rem 0 - line-height 1 - margin-left 0.5rem - {trans} - &:hover - color act - {transshort} - .flexfix-footer button - margin-bottom 0.25rem - select - padding 0.25rem + position relative + padding 0 1rem 0.5rem 0 + cursor pointer + &.active + svg + color act + &:last-child + color accent1 + svg + {trans} + &:last-child + width 1rem + height @width + padding 0.25rem + display block + //background background + border-radius 100% + overflow visible + position absolute + bottom 0 + right 0 + //{shad} diff --git a/src/styl/tags/rooms/room-tile-editor.styl b/src/styl/tags/rooms/room-tile-editor.styl new file mode 100644 index 000000000..ee05996a1 --- /dev/null +++ b/src/styl/tags/rooms/room-tile-editor.styl @@ -0,0 +1,26 @@ +room-tile-editor + .&-aCanvas + min-width 100% + margin 1rem 0 + display block + .&-aLayerList + max-height 10rem + li.checkbox + border-radius br + display flex + padding-left 2rem + padding-right 0.5rem + &:hover + padding-left 2rem + padding-right 0.5rem + [type="checkbox"] + left 0.5rem + * + flex 0 0 auto + b + flex 1 1 auto + .a + margin-left 0.5rem + &.active + background rgba(act, 0.25) + color text diff --git a/src/styl/tags/shared/asset-input.styl b/src/styl/tags/shared/asset-input.styl index 770badcd8..938fe3516 100644 --- a/src/styl/tags/shared/asset-input.styl +++ b/src/styl/tags/shared/asset-input.styl @@ -1,4 +1,20 @@ asset-input + line-height 0 + & > * + line-height 1.5 + & > .aButtonGroup + max-width 100% + :first-child + display flex + flex-flow row nowrap + align-items center + overflow hidden + & img, & svg + flex 0 0 auto + & span + flex 1 1 auto + overflow hidden + text-overflow ellipsis &.wide & > .aButtonGroup @extends .aButtonGroup.wide diff --git a/src/styl/tags/shared/asset-viewer.styl b/src/styl/tags/shared/asset-viewer.styl index adc408f30..cfcd1506f 100644 --- a/src/styl/tags/shared/asset-viewer.styl +++ b/src/styl/tags/shared/asset-viewer.styl @@ -22,3 +22,9 @@ asset-viewer height @width & + svg margin-left 0.5rem + &.compact + .aSearchWrap + flex 1 1 auto + input + width 100% + box-sizing border-box diff --git a/src/styl/tags/shared/collapsible-section.styl b/src/styl/tags/shared/collapsible-section.styl index ca1de5bd3..0adf8c73f 100644 --- a/src/styl/tags/shared/collapsible-section.styl +++ b/src/styl/tags/shared/collapsible-section.styl @@ -3,33 +3,36 @@ collapsible-section display block &:first-child margin-top 0 - & > .flexrow + .&-aHeader padding 0.5rem 0 - align-items center - cursor pointer + align-items center border-bottom 1px solid borderBright - h1, h2, h3, h4, h5, h6 - padding 0 - margin 0 - flex 1 1 auto + display grid + grid-template-columns 1fr auto + grid-gap 0.5rem + & > :first-child + display contents + & > h1, & > h2, & > h3, & > h4, & > h5, & > h6 + padding 0 + margin 0 + overflow hidden + text-overflow ellipsis + white-space nowrap + asset-input overflow hidden - text-overflow ellipsis - white-space nowrap - svg + & > svg {trans} - margin-left 0.5rem flex 0 0 auto - align-self flex-end + cursor pointer &.rotated transform rotate(180deg) .&-aWrapper padding 1rem 0 0.5rem &.aPanel - & > .flexrow - h1, h2, h3, h4, h5, h6 + .collapsible-section-aHeader + padding 0.5rem 1rem + & > h1, & > h2, & > h3, & > h4, & > h5, & > h6 margin 0 1rem - svg - margin-right 0.5rem &.closed > .flexrow border-bottom 0 .collapsible-section-aWrapper diff --git a/src/styl/tags/shared/error-notice.styl b/src/styl/tags/shared/error-notice.styl new file mode 100644 index 000000000..cc1689dd2 --- /dev/null +++ b/src/styl/tags/shared/error-notice.styl @@ -0,0 +1,6 @@ +error-notice.anErrorNotice + position fixed + width 15rem + z-index 10 + transform translate(-50%, 0) + {trans} diff --git a/src/styl/themeSpringStream.styl b/src/styl/themeSpringStream.styl index cbcfa372b..6e07d11ab 100644 --- a/src/styl/themeSpringStream.styl +++ b/src/styl/themeSpringStream.styl @@ -109,6 +109,8 @@ input[type="reset"], color background &.selected background acttext +.aButtonGroup.vertical button, .aButtonGroup.vertical .button + border 0 #theIntroBg background introBg diff --git a/tsconfig.json b/tsconfig.json index 2d4568437..14a6200fb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,16 @@ "module": "umd", "noImplicitAny": true, "moduleResolution": "node", - "baseUrl": "./" + "baseUrl": "./", + "typeRoots": [ + "./node_modules/@types/", + "./app/node_modules/@types/" + ], + "paths": { + "node_modules/*": [ + "./app/node_modules/*", + "./node_modules/*" + ], + } } }