diff --git a/images/SimpleGUIMacro-01.jpg b/images/SimpleGUIMacro-01.jpg new file mode 100644 index 0000000..32a0a15 Binary files /dev/null and b/images/SimpleGUIMacro-01.jpg differ diff --git a/tokenmagic/fx/Anime.js b/tokenmagic/fx/Anime.js index 9c4ab63..31b9571 100644 --- a/tokenmagic/fx/Anime.js +++ b/tokenmagic/fx/Anime.js @@ -517,7 +517,7 @@ export class Anime { } static _attachToTicker() { - canvas.app.ticker.add(Anime.tick, this); + canvas.app.ticker.add(Anime.tick, this, PIXI.UPDATE_PRIORITY.LOW + 1); Anime._lastTime = canvas.app.ticker.lastTime; Anime._prevTime = Anime._lastTime; } diff --git a/tokenmagic/fx/assets/gem-1.png b/tokenmagic/fx/assets/gem-1.png new file mode 100644 index 0000000..82771fa Binary files /dev/null and b/tokenmagic/fx/assets/gem-1.png differ diff --git a/tokenmagic/fx/assets/gem-2.png b/tokenmagic/fx/assets/gem-2.png new file mode 100644 index 0000000..24eb4f2 Binary files /dev/null and b/tokenmagic/fx/assets/gem-2.png differ diff --git a/tokenmagic/fx/assets/pentagram.png b/tokenmagic/fx/assets/pentagram.png new file mode 100644 index 0000000..49e28dc Binary files /dev/null and b/tokenmagic/fx/assets/pentagram.png differ diff --git a/tokenmagic/fx/filters/CustomFilter.js b/tokenmagic/fx/filters/CustomFilter.js new file mode 100644 index 0000000..9c464e2 --- /dev/null +++ b/tokenmagic/fx/filters/CustomFilter.js @@ -0,0 +1,69 @@ +const _tempRect = new PIXI.Rectangle(); + +export class CustomFilter extends PIXI.Filter { + constructor(...args) { + super(...args); + + if (this.program.uniformData.filterMatrix || this.program.uniformData.filterMatrixInverse) + this.uniforms.filterMatrix = new PIXI.Matrix(); + + if (this.program.uniformData.filterMatrixInverse) + this.uniforms.filterMatrixInverse = new PIXI.Matrix(); + } + + apply(filterManager, input, output, clear) { + const filterMatrix = this.uniforms.filterMatrix; + + if (filterMatrix) { + const { sourceFrame, destinationFrame, target } = filterManager.activeState; + + filterMatrix.set( + destinationFrame.width, 0, 0, destinationFrame.height, sourceFrame.x, sourceFrame.y); + + const worldTransform = PIXI.Matrix.TEMP_MATRIX; + + const localBounds = target.getLocalBounds(_tempRect); + + if (this.sticky) { + worldTransform.copyFrom(target.transform.worldTransform); + worldTransform.invert(); + + const rotation = target.transform.rotation; + const sin = Math.sin(rotation); + const cos = Math.cos(rotation); + const scaleX = Math.hypot(cos * worldTransform.a + sin * worldTransform.c, cos * worldTransform.b + sin * worldTransform.d); + const scaleY = Math.hypot(-sin * worldTransform.a + cos * worldTransform.c, -sin * worldTransform.b + cos * worldTransform.d); + + localBounds.pad(scaleX * this.boundsPadding.x, scaleY * this.boundsPadding.y); + } else { + const transform = target.transform; + worldTransform.a = transform.scale.x; + worldTransform.b = 0; + worldTransform.c = 0; + worldTransform.d = transform.scale.y; + worldTransform.tx = transform.position.x - transform.pivot.x * transform.scale.x; + worldTransform.ty = transform.position.y - transform.pivot.y * transform.scale.y; + worldTransform.prepend(target.parent.transform.worldTransform); + worldTransform.invert(); + + const scaleX = Math.hypot(worldTransform.a, worldTransform.b); + const scaleY = Math.hypot(worldTransform.c, worldTransform.d); + + localBounds.pad(scaleX * this.boundsPadding.x, scaleY * this.boundsPadding.y); + } + + filterMatrix.prepend(worldTransform); + filterMatrix.translate(-localBounds.x, -localBounds.y); + filterMatrix.scale(1.0 / localBounds.width, 1.0 / localBounds.height); + + const filterMatrixInverse = this.uniforms.filterMatrixInverse; + + if (filterMatrixInverse) { + filterMatrixInverse.copyFrom(filterMatrix); + filterMatrixInverse.invert(); + } + } + + filterManager.applyFilter(this, input, output, clear); + } +} \ No newline at end of file diff --git a/tokenmagic/fx/filters/FilterBlur.js b/tokenmagic/fx/filters/FilterBlur.js index a87357a..f4b4b80 100644 --- a/tokenmagic/fx/filters/FilterBlur.js +++ b/tokenmagic/fx/filters/FilterBlur.js @@ -7,7 +7,6 @@ export class FilterBlur extends PIXI.filters.BlurFilter { this.enabled = false; this.blur = 2; this.quality = 4; - this.resolution = 1; this.zOrder = 290; this.repeatEdgePixels = false; this.animated = {}; diff --git a/tokenmagic/fx/filters/FilterDistortion.js b/tokenmagic/fx/filters/FilterDistortion.js index 12f4e96..3c42e6b 100644 --- a/tokenmagic/fx/filters/FilterDistortion.js +++ b/tokenmagic/fx/filters/FilterDistortion.js @@ -13,16 +13,22 @@ export class FilterDistortion extends PIXI.filters.DisplacementFilter { // Configuring distortion sprite this.sprite = displacementSpriteMask; this.wrapMode = PIXI.WRAP_MODES.REPEAT; + this.position = new PIXI.Point(); + this.skew = new PIXI.Point(); + this.pivot = new PIXI.Point(); this.anchorSet = 0.5; this.transition = null; this.padding = 15; // conf this.enabled = false; + this.maskSpriteX = 0; + this.maskSpriteY = 0; this.maskSpriteScaleX = 4; this.maskSpriteScaleY = 4; this.maskSpriteSkewX = 0; this.maskSpriteSkewY = 0; this.maskSpriteRotation = 0; this.zOrder = 4000; + this.sticky = true; this.animated = {}; this.setTMParams(params); @@ -31,65 +37,107 @@ export class FilterDistortion extends PIXI.filters.DisplacementFilter { this.normalizeTMParams(); this.sprite.anchor.set(this.anchorSet); this.sprite.texture.baseTexture.wrapMode = this.wrapMode; - this.placeableImg.addChild(this.sprite); - this.sprite.x = this.placeableImg.width / 2; - this.sprite.y = this.placeableImg.height / 2; } } set maskSpriteX(value) { - this.maskSprite.x = value; + this.position.x = value; } set maskSpriteY(value) { - this.maskSprite.y = value; + this.position.y = value; } get maskSpriteX() { - return this.maskSprite.x; + return this.position.x; } get maskSpriteY() { - return this.maskSprite.y; + return this.position.y; } set maskSpriteScaleX(value) { - this.sprite.scale.x = value; + this.scale.x = value; } set maskSpriteScaleY(value) { - this.sprite.scale.y = value; + this.scale.y = value; } get maskSpriteScaleX() { - return this.sprite.scale.x; + return this.scale.x; } get maskSpriteScaleY() { - return this.sprite.scale.y; + return this.scale.y; } set maskSpriteRotation(value) { - this.sprite.rotation = value; + this.rotation = value; } get maskSpriteRotation() { - return this.sprite.rotation; + return this.rotation; } set maskSpriteSkewX(value) { - this.sprite.skew.x = value; + this.skew.x = value; } get maskSpriteSkewX() { - return this.sprite.skew.x; + return this.skew.x; } set maskSpriteSkewY(value) { - this.sprite.skew.y = value; + this.skew.y = value; } get maskSpriteSkewY() { - return this.sprite.skew.y; + return this.skew.y; + } + + set maskSpritePivotX(value) { + this.pivot.x = value; + } + + get maskSpritePivotX() { + return this.pivot.x; + } + + set maskSpritePivotY(value) { + this.pivot.y = value; + } + + get maskSpritePivotY() { + return this.pivot.y; + } + + handleTransform() { + this.sprite.position.x = this.targetPlaceable.x + this.placeableImg.x + this.position.x; + this.sprite.position.y = this.targetPlaceable.y + this.placeableImg.y + this.position.y; + this.sprite.skew.x = this.skew.x; + this.sprite.skew.x = this.skew.y; + this.sprite.rotation = this.rotation; + this.sprite.pivot.x = this.pivot.x; + this.sprite.pivot.y = this.pivot.y; + + if (this.sticky) + this.sprite.rotation += this.placeableImg.rotation; + + this.sprite.transform.updateTransform(canvas.stage.transform); + } + + apply(filterManager, input, output, clear) { + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x; + this.uniforms.scale.y = this.scale.y; + + const wt = this.maskSprite.worldTransform; + this.uniforms.rotation[0] = wt.a; + this.uniforms.rotation[1] = wt.b; + this.uniforms.rotation[2] = wt.c; + this.uniforms.rotation[3] = wt.d; + + filterManager.applyFilter(this, input, output, clear); } } \ No newline at end of file diff --git a/tokenmagic/fx/filters/FilterElectric.js b/tokenmagic/fx/filters/FilterElectric.js index e2faa9e..fc375e2 100644 --- a/tokenmagic/fx/filters/FilterElectric.js +++ b/tokenmagic/fx/filters/FilterElectric.js @@ -1,9 +1,10 @@ import { zapElectricity } from '../glsl/fragmentshaders/electricity.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterElectric extends PIXI.Filter { +export class FilterElectric extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterFire.js b/tokenmagic/fx/filters/FilterFire.js index 51907a3..d172264 100644 --- a/tokenmagic/fx/filters/FilterFire.js +++ b/tokenmagic/fx/filters/FilterFire.js @@ -1,9 +1,10 @@ import { burnFire } from '../glsl/fragmentshaders/fire.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterFire extends PIXI.Filter { +export class FilterFire extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterFlood.js b/tokenmagic/fx/filters/FilterFlood.js index da33e8a..d049761 100644 --- a/tokenmagic/fx/filters/FilterFlood.js +++ b/tokenmagic/fx/filters/FilterFlood.js @@ -1,9 +1,10 @@ import { seaFlood } from '../glsl/fragmentshaders/flood.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterFlood extends PIXI.Filter { +export class FilterFlood extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterFog.js b/tokenmagic/fx/filters/FilterFog.js index a6f496f..f0cec34 100644 --- a/tokenmagic/fx/filters/FilterFog.js +++ b/tokenmagic/fx/filters/FilterFog.js @@ -1,8 +1,9 @@ import { innerFog } from '../glsl/fragmentshaders/fog.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; -export class FilterFog extends PIXI.Filter { +export class FilterFog extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterForceField.js b/tokenmagic/fx/filters/FilterForceField.js index 82027b5..a1154d8 100644 --- a/tokenmagic/fx/filters/FilterForceField.js +++ b/tokenmagic/fx/filters/FilterForceField.js @@ -1,9 +1,10 @@ import { forceField } from '../glsl/fragmentshaders/forcefield.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterForceField extends PIXI.Filter { +export class FilterForceField extends CustomFilter { constructor(params) { let { time, @@ -160,14 +161,6 @@ export class FilterForceField extends PIXI.Filter { this.uniforms.discardThreshold = value; } - get _ratio() { - return this.uniforms.ratio; - } - - set _ratio(value) { - this.uniforms.ratio = value; - } - get alphaDiscard() { return this.uniforms.alphaDiscard; } @@ -187,44 +180,6 @@ export class FilterForceField extends PIXI.Filter { this.uniforms.chromatic = value; } } - - // override - calculatePadding() { - return; - } - - apply(filterManager, input, output, clear) { - - if (!this.dummy) { - let imgSize = Math.max(this.placeableImg.width, this.placeableImg.height); - - if (this.gridPadding > 0) { - const toSize = (canvas.dimensions.size >= imgSize - ? canvas.dimensions.size - imgSize - : imgSize % canvas.dimensions.size); - - this.currentPadding = - (this.targetPlaceable.worldTransform.a * (this.gridPadding - 1) - * canvas.dimensions.size) - + ((toSize * this.targetPlaceable.worldTransform.a) / 2); - - } else { - - this.currentPadding = - this.placeableImg.parent.worldTransform.a - * this.rawPadding; - } - - const placeablePadding = this.targetPlaceable._TMFXgetPlaceablePadding(); - - if (this.currentPadding >= placeablePadding) this._ratio = 1; - else { - imgSize *= this.targetPlaceable.worldTransform.a; - this._ratio = (imgSize + 2 * this.currentPadding) / (imgSize + 2 * placeablePadding); - } - } - filterManager.applyFilter(this, input, output, clear); - } } FilterForceField.defaults = { @@ -243,7 +198,6 @@ FilterForceField.defaults = { chromatic: false, discardThreshold: 0.25, alphaDiscard: false, - _ratio: 1, }; diff --git a/tokenmagic/fx/filters/FilterFumes.js b/tokenmagic/fx/filters/FilterFumes.js index 0df7e60..071c431 100644 --- a/tokenmagic/fx/filters/FilterFumes.js +++ b/tokenmagic/fx/filters/FilterFumes.js @@ -1,9 +1,10 @@ import { fumes } from '../glsl/fragmentshaders/fumes.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterFumes extends PIXI.Filter { +export class FilterFumes extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterGleamingGlow.js b/tokenmagic/fx/filters/FilterGleamingGlow.js index ce534d2..8c9bb7a 100644 --- a/tokenmagic/fx/filters/FilterGleamingGlow.js +++ b/tokenmagic/fx/filters/FilterGleamingGlow.js @@ -1,9 +1,10 @@ import { magicGlow } from '../glsl/fragmentshaders/magicglow.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterGleamingGlow extends PIXI.Filter { +export class FilterGleamingGlow extends CustomFilter { constructor(params) { @@ -105,11 +106,9 @@ export class FilterGleamingGlow extends PIXI.Filter { } apply(filterManager, input, output, clear) { - if (!this.dummy) { - this.uniforms.thickness[0] = (this.thickness * this.placeableImg.parent.worldTransform.a) / input._frame.width; - this.uniforms.thickness[1] = (this.thickness * this.placeableImg.parent.worldTransform.a) / input._frame.height; - filterManager.applyFilter(this, input, output, clear); - } + this.uniforms.thickness[0] = (this.thickness * this.placeableImg.parent.worldTransform.a) / input._frame.width; + this.uniforms.thickness[1] = (this.thickness * this.placeableImg.parent.worldTransform.a) / input._frame.height; + super.apply(filterManager, input, output, clear); } } diff --git a/tokenmagic/fx/filters/FilterGlobes.js b/tokenmagic/fx/filters/FilterGlobes.js index a4d3c0a..11ce2f4 100644 --- a/tokenmagic/fx/filters/FilterGlobes.js +++ b/tokenmagic/fx/filters/FilterGlobes.js @@ -1,9 +1,10 @@ import { globes } from '../glsl/fragmentshaders/globes.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterGlobes extends PIXI.Filter { +export class FilterGlobes extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterLiquid.js b/tokenmagic/fx/filters/FilterLiquid.js index 233750a..822c8b7 100644 --- a/tokenmagic/fx/filters/FilterLiquid.js +++ b/tokenmagic/fx/filters/FilterLiquid.js @@ -1,9 +1,10 @@ import { liquid } from '../glsl/fragmentshaders/liquid.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterLiquid extends PIXI.Filter { +export class FilterLiquid extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterMirrorImages.js b/tokenmagic/fx/filters/FilterMirrorImages.js index 9b75a3c..3dcfe50 100644 --- a/tokenmagic/fx/filters/FilterMirrorImages.js +++ b/tokenmagic/fx/filters/FilterMirrorImages.js @@ -1,9 +1,10 @@ import { mirrorImages } from '../glsl/fragmentshaders/mirrorimages.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterMirrorImages extends PIXI.Filter { +export class FilterMirrorImages extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterPixelate.js b/tokenmagic/fx/filters/FilterPixelate.js index 7225406..74ef3b3 100644 --- a/tokenmagic/fx/filters/FilterPixelate.js +++ b/tokenmagic/fx/filters/FilterPixelate.js @@ -33,9 +33,7 @@ export class FilterPixelate extends PIXI.filters.PixelateFilter { //} handleTransform() { - if (!this.dummy) { - this.size.x = this.sizeX * this.placeableImg.parent.worldTransform.a; - this.size.y = this.sizeY * this.placeableImg.parent.worldTransform.a; - } + this.size.x = this.sizeX * this.placeableImg.parent.worldTransform.a; + this.size.y = this.sizeY * this.placeableImg.parent.worldTransform.a; } } diff --git a/tokenmagic/fx/filters/FilterPolymorph.js b/tokenmagic/fx/filters/FilterPolymorph.js index 7f104c1..7829a62 100644 --- a/tokenmagic/fx/filters/FilterPolymorph.js +++ b/tokenmagic/fx/filters/FilterPolymorph.js @@ -1,9 +1,10 @@ import { polymorph } from '../glsl/fragmentshaders/polymorph.js'; import { customVertex2DSampler } from '../glsl/vertexshaders/customvertex2DSampler.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterPolymorph extends PIXI.Filter { +export class FilterPolymorph extends CustomFilter { constructor(params) { let { @@ -22,7 +23,7 @@ export class FilterPolymorph extends PIXI.Filter { this.uniforms.targetUVMatrix = targetSpriteMatrix; // fragment uniforms - this.uniforms.filterClampTarget = new Float32Array([0, 0, 0, 0]); + this.uniforms.inputClampTarget = new Float32Array([0, 0, 0, 0]); // to store sprite matrix from the filter manager (and send to vertex) this.targetSpriteMatrix = targetSpriteMatrix; @@ -96,23 +97,21 @@ export class FilterPolymorph extends PIXI.Filter { // override apply(filterManager, input, output, clear) { - if (!this.dummy) { - - const targetSprite = this.targetSprite; - const tex = targetSprite._texture; - - if (tex.valid) { - if (!tex.uvMatrix) tex.uvMatrix = new PIXI.TextureMatrix(tex, 0.0); - tex.uvMatrix.update(); - - this.uniforms.uSamplerTarget = tex; - this.uniforms.targetUVMatrix = - filterManager.calculateSpriteMatrix(this.targetSpriteMatrix, targetSprite) - .prepend(tex.uvMatrix.mapCoord); - this.uniforms.filterClampTarget = tex.uvMatrix.uClampFrame; - } + const targetSprite = this.targetSprite; + const tex = targetSprite._texture; + + if (tex.valid) { + if (!tex.uvMatrix) tex.uvMatrix = new PIXI.TextureMatrix(tex, 0.0); + tex.uvMatrix.update(); + + this.uniforms.uSamplerTarget = tex; + this.uniforms.targetUVMatrix = + filterManager.calculateSpriteMatrix(this.targetSpriteMatrix, targetSprite) + .prepend(tex.uvMatrix.mapCoord); + this.uniforms.inputClampTarget = tex.uvMatrix.uClampFrame; } - filterManager.applyFilter(this, input, output, clear); + + super.apply(filterManager, input, output, clear); } // override diff --git a/tokenmagic/fx/filters/FilterRays.js b/tokenmagic/fx/filters/FilterRays.js index e22fd00..ff58870 100644 --- a/tokenmagic/fx/filters/FilterRays.js +++ b/tokenmagic/fx/filters/FilterRays.js @@ -1,9 +1,10 @@ import { cosmicRayFrag } from '../glsl/fragmentshaders/cosmicray.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterRays extends PIXI.Filter { +export class FilterRays extends CustomFilter { constructor(params) { let { @@ -22,8 +23,8 @@ export class FilterRays extends PIXI.Filter { super(customVertex2D, cosmicRayFrag); this.uniforms.color = new Float32Array([1.0, 0.4, 0.1, 0.55]); - this.uniforms.anchor = new Float32Array([0.5,0.5]); - this.uniforms.dimensions = new Float32Array([100.0,100.0]); + this.uniforms.anchor = new Float32Array([0.5, 0.5]); + this.uniforms.dimensions = new Float32Array([1.0, 1.0]); Object.assign(this, { time, color, divisor, alpha, anchorX, anchorY, dimX, dimY, alphaDiscard diff --git a/tokenmagic/fx/filters/FilterRemoveShadow.js b/tokenmagic/fx/filters/FilterRemoveShadow.js index 10d355d..a091645 100644 --- a/tokenmagic/fx/filters/FilterRemoveShadow.js +++ b/tokenmagic/fx/filters/FilterRemoveShadow.js @@ -1,8 +1,9 @@ import { removeShadowFrag } from '../glsl/fragmentshaders/removeshadow.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterRemoveShadow extends PIXI.Filter { +export class FilterRemoveShadow extends CustomFilter { constructor(params) { let { alphaTolerance } = Object.assign({}, FilterRemoveShadow.defaults, params); diff --git a/tokenmagic/fx/filters/FilterShockWave.js b/tokenmagic/fx/filters/FilterShockWave.js index 4463d8f..b225b57 100644 --- a/tokenmagic/fx/filters/FilterShockWave.js +++ b/tokenmagic/fx/filters/FilterShockWave.js @@ -29,10 +29,10 @@ export class FilterShockwave extends PIXI.filters.ShockwaveFilter { this.center[0] = (this.placeableImg.localTransform.tx * this.placeableImg.parent.worldTransform.a * scale) - + this.padding; + + this.boundsPadding.x; this.center[1] = (this.placeableImg.localTransform.ty * this.placeableImg.parent.worldTransform.a * scale) - + this.padding; + + this.boundsPadding.y; } } diff --git a/tokenmagic/fx/filters/FilterSmoke.js b/tokenmagic/fx/filters/FilterSmoke.js index 16e70aa..fce14ca 100644 --- a/tokenmagic/fx/filters/FilterSmoke.js +++ b/tokenmagic/fx/filters/FilterSmoke.js @@ -1,9 +1,10 @@ import { innerSmoke } from '../glsl/fragmentshaders/smoke.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterSmoke extends PIXI.Filter { +export class FilterSmoke extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterSolarRipples.js b/tokenmagic/fx/filters/FilterSolarRipples.js index a8f5a69..d55f8ec 100644 --- a/tokenmagic/fx/filters/FilterSolarRipples.js +++ b/tokenmagic/fx/filters/FilterSolarRipples.js @@ -1,9 +1,10 @@ import { solarRipples } from '../glsl/fragmentshaders/ripples.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterSolarRipples extends PIXI.Filter { +export class FilterSolarRipples extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterSpiderWeb.js b/tokenmagic/fx/filters/FilterSpiderWeb.js index 671c47d..1c40c31 100644 --- a/tokenmagic/fx/filters/FilterSpiderWeb.js +++ b/tokenmagic/fx/filters/FilterSpiderWeb.js @@ -1,9 +1,10 @@ import { spiderWeb } from '../glsl/fragmentshaders/spiderweb.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterSpiderWeb extends PIXI.Filter { +export class FilterSpiderWeb extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterSplash.js b/tokenmagic/fx/filters/FilterSplash.js index 5cbd469..19205e4 100644 --- a/tokenmagic/fx/filters/FilterSplash.js +++ b/tokenmagic/fx/filters/FilterSplash.js @@ -1,9 +1,10 @@ import { splash } from '../glsl/fragmentshaders/splash.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterSplash extends PIXI.Filter { +export class FilterSplash extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterSprite.js b/tokenmagic/fx/filters/FilterSprite.js new file mode 100644 index 0000000..fd214ef --- /dev/null +++ b/tokenmagic/fx/filters/FilterSprite.js @@ -0,0 +1,276 @@ +import { sprite } from '../glsl/fragmentshaders/sprite.js'; +import { customVertex2DSampler } from '../glsl/vertexshaders/customvertex2DSampler.js'; +import { CustomFilter } from './CustomFilter.js'; +import { Anime } from "../Anime.js"; +import "./proto/FilterProto.js"; + +export class FilterSprite extends CustomFilter { + + constructor(params) { + let { + imagePath, + color, + colorize, + inverse, + top, + rotation, + twRadiusPercent, + twAngle, + twRotation, + bpRadiusPercent, + bpStrength, + scale, + scaleX, + scaleY, + translationX, + translationY + } = Object.assign({}, FilterSprite.defaults, params); + + const targetSpriteMatrix = new PIXI.Matrix(); + + // using specific vertex shader and fragment shader + super(customVertex2DSampler, sprite); + + // vertex uniforms + this.uniforms.targetUVMatrix = targetSpriteMatrix; + + // fragment uniforms + this.uniforms.inputClampTarget = new Float32Array([0, 0, 0, 0]); + this.uniforms.color = new Float32Array([0.0, 0.0, 0.0]); + this.uniforms.scale = new Float32Array([1.0, 1.0]); + this.uniforms.translation = new Float32Array([0.0, 0.0]); + + // to store sprite matrix from the filter manager (and send to vertex) + this.targetSpriteMatrix = targetSpriteMatrix; + + Object.assign(this, { + imagePath, + color, + colorize, + inverse, + top, + rotation, + twRadiusPercent, + twAngle, + twRotation, + bpRadiusPercent, + bpStrength, + scale, + scaleX, + scaleY, + translationX, + translationY + }); + + this.zOrder = 0; + this.animated = {}; + this.setTMParams(params); + if (!this.dummy) { + this.anime = new Anime(this); + this.normalizeTMParams(); + this.assignTexture(); + } + } + + get color() { + return PIXI.utils.rgb2hex(this.uniforms.color); + } + + set color(value) { + PIXI.utils.hex2rgb(value, this.uniforms.color); + } + + get colorize() { + return this.uniforms.colorize; + } + + set colorize(value) { + if (!(value == null) && typeof value === "boolean") { + this.uniforms.colorize = value; + } + } + + get inverse() { + return this.uniforms.inverse; + } + + set inverse(value) { + if (!(value == null) && typeof value === "boolean") { + this.uniforms.inverse = value; + } + } + + get top() { + return this.uniforms.top; + } + + set top(value) { + if (!(value == null) && typeof value === "boolean") { + this.uniforms.top = value; + } + } + + get rotation() { + return this.uniforms.rotation; + } + + set rotation(value) { + this.uniforms.rotation = value; + } + + get twRadiusPercent() { + return this.uniforms.twRadius * 200; + } + + set twRadiusPercent(value) { + this.uniforms.twRadius = value / 200; + } + + get twAngle() { + return this.uniforms.twAngle; + } + + set twAngle(value) { + this.uniforms.twAngle = value; + } + + get twRotation() { + return this.uniforms.twAngle * (180 / Math.PI); + } + + set twRotation(value) { + this.uniforms.twAngle = value * (Math.PI / 180); + } + + get bpRadiusPercent() { + return this.uniforms.bpRadius * 200; + } + + set bpRadiusPercent(value) { + this.uniforms.bpRadius = value / 200; + } + + get bpStrength() { + return this.uniforms.bpStrength; + } + + set bpStrength(value) { + this.uniforms.bpStrength = value; + } + + get scale() { + // a little hack (we get only x) + return this.uniforms.scale[0]; + } + + set scale(value) { + this.uniforms.scale[1] = this.uniforms.scale[0] = value; + } + + get scaleX() { + return this.uniforms.scale[0]; + } + + set scaleX(value) { + this.uniforms.scale[0] = value; + } + + get scaleY() { + return this.uniforms.scale[1]; + } + + set scaleY(value) { + this.uniforms.scale[1] = value; + } + + get translationX() { + return this.uniforms.translation[0]; + } + + set translationX(value) { + this.uniforms.translation[0] = value; + } + + get translationY() { + return this.uniforms.translation[1]; + } + + set translationY(value) { + this.uniforms.translation[1] = value; + } + + get uSamplerTarget() { + return this.uniforms.uSamplerTarget; + } + + set uSamplerTarget(value) { + this.uniforms.uSamplerTarget = value; + } + + assignTexture() { + if (this.hasOwnProperty("imagePath")) { + let tex = PIXI.Texture.from(this.imagePath); + let sprite = new PIXI.Sprite(tex); + + sprite.renderable = false; + if (this.placeableImg.hasOwnProperty("_texture")) { + sprite.width = this.placeableImg._texture.baseTexture.realWidth; + sprite.height = this.placeableImg._texture.baseTexture.realHeight; + sprite.anchor.set(0.5); + } else { + sprite.width = this.placeableImg.width; + sprite.height = this.placeableImg.height; + } + + this.targetSprite = sprite; + this.uSamplerTarget = sprite._texture; + this.placeableImg.addChild(sprite); + } + } + + // override + apply(filterManager, input, output, clear) { + const targetSprite = this.targetSprite; + const tex = targetSprite._texture; + + if (tex.valid) { + if (!tex.uvMatrix) tex.uvMatrix = new PIXI.TextureMatrix(tex, 0.0); + tex.uvMatrix.update(); + + this.uniforms.uSamplerTarget = tex; + this.uniforms.targetUVMatrix = + filterManager.calculateSpriteMatrix(this.targetSpriteMatrix, targetSprite) + .prepend(tex.uvMatrix.mapCoord); + this.uniforms.inputClampTarget = tex.uvMatrix.uClampFrame; + } + + super.apply(filterManager, input, output, clear); + } + + // override + destroy() { + super.destroy(); + if (this.placeableImg && !this.placeableImg._destroyed) this.placeableImg.removeChild(this.targetSprite); + this.targetSprite.destroy({ children: true, texture: false, baseTexture: false }); + } +} + +FilterSprite.defaults = { + color: 0x000000, + colorize: false, + inverse: false, + top: false, + rotation: 0.0, + twRadiusPercent: 0, + twAngle: 0, + bpRadiusPercent: 0, + bpStrength: 0, + scaleX: 1, + scaleY: 1, + translationX: 0, + translationY: 0, +}; + + + + diff --git a/tokenmagic/fx/filters/FilterTransform.js b/tokenmagic/fx/filters/FilterTransform.js index 8f549a7..ef18768 100644 --- a/tokenmagic/fx/filters/FilterTransform.js +++ b/tokenmagic/fx/filters/FilterTransform.js @@ -1,9 +1,10 @@ import { matrix } from '../glsl/fragmentshaders/matrix.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterTransform extends PIXI.Filter { +export class FilterTransform extends CustomFilter { constructor(params) { let { @@ -13,8 +14,6 @@ export class FilterTransform extends PIXI.Filter { twRotation, bpRadiusPercent, bpStrength, - spRadiusPercent, - spStrength, scale, scaleX, scaleY, @@ -37,8 +36,6 @@ export class FilterTransform extends PIXI.Filter { twRotation, bpRadiusPercent, bpStrength, - spRadiusPercent, - spStrength, scale, scaleX, scaleY, @@ -170,8 +167,6 @@ FilterTransform.defaults = { twAngle: 0, bpRadiusPercent: 0, bpStrength: 0, - spRadiusPercent: 0, - spStrength: 0, scaleX: 1, scaleY: 1, pivotX: 0.5, diff --git a/tokenmagic/fx/filters/FilterWaves.js b/tokenmagic/fx/filters/FilterWaves.js index 7865539..ecc4b92 100644 --- a/tokenmagic/fx/filters/FilterWaves.js +++ b/tokenmagic/fx/filters/FilterWaves.js @@ -1,9 +1,10 @@ import { magicWaves } from '../glsl/fragmentshaders/waves.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterWaves extends PIXI.Filter { +export class FilterWaves extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterXFire.js b/tokenmagic/fx/filters/FilterXFire.js index 983a340..3565fd7 100644 --- a/tokenmagic/fx/filters/FilterXFire.js +++ b/tokenmagic/fx/filters/FilterXFire.js @@ -1,9 +1,10 @@ import { burnXFire } from '../glsl/fragmentshaders/xfire.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterXFire extends PIXI.Filter { +export class FilterXFire extends CustomFilter { constructor(params) { let { time, diff --git a/tokenmagic/fx/filters/FilterXFog.js b/tokenmagic/fx/filters/FilterXFog.js index d67654f..e2255f2 100644 --- a/tokenmagic/fx/filters/FilterXFog.js +++ b/tokenmagic/fx/filters/FilterXFog.js @@ -1,8 +1,9 @@ import { xFog } from '../glsl/fragmentshaders/xfog.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; -export class FilterXFog extends PIXI.Filter { +export class FilterXFog extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterXRays.js b/tokenmagic/fx/filters/FilterXRays.js index 8317dd6..c2f0e26 100644 --- a/tokenmagic/fx/filters/FilterXRays.js +++ b/tokenmagic/fx/filters/FilterXRays.js @@ -1,9 +1,10 @@ import { xRay } from '../glsl/fragmentshaders/xray.js'; import { customVertex2D } from '../glsl/vertexshaders/customvertex2D.js'; +import { CustomFilter } from './CustomFilter.js'; import { Anime } from "../Anime.js"; import "./proto/FilterProto.js"; -export class FilterXRays extends PIXI.Filter { +export class FilterXRays extends CustomFilter { constructor(params) { let { diff --git a/tokenmagic/fx/filters/FilterZoomBlur.js b/tokenmagic/fx/filters/FilterZoomBlur.js index 04c4ad7..f7a31a2 100644 --- a/tokenmagic/fx/filters/FilterZoomBlur.js +++ b/tokenmagic/fx/filters/FilterZoomBlur.js @@ -19,11 +19,11 @@ export class FilterZoomBlur extends PIXI.filters.ZoomBlurFilter { handleTransform() { this.center[0] = - this.padding + + this.boundsPadding.x + ((this.placeableImg.localTransform.tx * this.placeableImg.parent.worldTransform.a) * this.placeableImg.parent.data.scale); this.center[1] = - this.padding + + this.boundsPadding.y + ((this.placeableImg.localTransform.ty * this.placeableImg.parent.worldTransform.a) * this.placeableImg.parent.data.scale); this.radius = diff --git a/tokenmagic/fx/filters/proto/FilterProto.js b/tokenmagic/fx/filters/proto/FilterProto.js index 3b1e6cd..ea87ee4 100644 --- a/tokenmagic/fx/filters/proto/FilterProto.js +++ b/tokenmagic/fx/filters/proto/FilterProto.js @@ -1,20 +1,34 @@ import { objectAssign, getPlaceableById, getMinPadding, PlaceableType, Magic } from "../../../module/tokenmagic.js"; import "../../../module/proto/PlaceableObjectProto.js"; +import { CustomFilter } from '../CustomFilter.js'; PIXI.Filter.prototype.setTMParams = function (params) { this.autoDisable = false; this.autoDestroy = false; - this.padding = 0; this.gridPadding = 0; - this.rawPadding = 0; + this.boundsPadding = new PIXI.Point(0, 0); + this.currentPadding = 0; + this.recalculatePadding = true; this.dummy = false; objectAssign(this, params); - this.autoFit = false; if (!this.dummy) { - this.rawPadding = this.padding; - this.originalPadding = Math.max(this.padding, getMinPadding()); + this.rawPadding = this.rawPadding ?? this.padding ?? 0; + this.originalPadding = Math.max(this.rawPadding, getMinPadding()); this.assignPlaceable(); this.activateTransform(); + Object.defineProperty(this, "padding", { + get: function () { + if (this.recalculatePadding) + this.calculatePadding(); + return this.currentPadding; + }, + set: function (padding) { + this.rawPadding = padding; + this.originalPadding = Math.max(padding, getMinPadding()); + } + }); + } else { + this.apply = function () { } } } @@ -27,23 +41,49 @@ PIXI.Filter.prototype.getPlaceableType = function () { } PIXI.Filter.prototype.calculatePadding = function () { - if (this.gridPadding > 0) { - const imgSize = Math.max(this.placeableImg.width, this.placeableImg.height); - const toSize = (canvas.dimensions.size >= imgSize - ? canvas.dimensions.size - imgSize - : imgSize % canvas.dimensions.size); + const target = this.placeableImg; + + let width; + let height; - this.currentPadding = - (this.placeableImg.parent.worldTransform.a * (this.gridPadding - 1) - * canvas.dimensions.size) - + ((toSize * this.placeableImg.parent.worldTransform.a) / 2); + { + const ang = !this.sticky && this.placeableType !== PlaceableType.TOKEN ? target.rotation : 0; + const sin = Math.sin(ang); + const cos = Math.cos(ang); + width = Math.abs(target.width * cos) + Math.abs(target.height * sin); + height = Math.abs(target.width * sin) + Math.abs(target.height * cos); + } + + if (this.gridPadding > 0) { + const gridSize = canvas.dimensions.size; + + this.boundsPadding.x = this.boundsPadding.y = (this.gridPadding - 1) * gridSize; + this.boundsPadding.x += (gridSize - 1 - (width + gridSize - 1) % gridSize) / 2; + this.boundsPadding.y += (gridSize - 1 - (height + gridSize - 1) % gridSize) / 2; } else { + this.boundsPadding.x = this.boundsPadding.y = this.rawPadding; + } - this.currentPadding = - this.placeableImg.parent.worldTransform.a - * this.originalPadding; + { + const ang = this.sticky ? target.rotation : 0; + const sin = Math.sin(ang); + const cos = Math.cos(ang); + + this.currentPadding = Math.max( + Math.abs(this.boundsPadding.x * cos) + Math.abs(this.boundsPadding.y * sin), + Math.abs(this.boundsPadding.x * sin) + Math.abs(this.boundsPadding.y * cos) + ) + (this.originalPadding - this.rawPadding); } + + this.boundsPadding.x += (width - target.width) / 2; + this.boundsPadding.y += (height - target.height) / 2; + + const scale = this.targetPlaceable.worldTransform.a; + + this.boundsPadding.x *= scale; + this.boundsPadding.y *= scale; + this.currentPadding *= scale; } PIXI.Filter.prototype.assignPlaceable = function () { @@ -57,20 +97,20 @@ PIXI.Filter.prototype.assignPlaceable = function () { PIXI.Filter.prototype.activateTransform = function () { this.preComputation = this.filterTransform; this.filterTransform(); + + const apply = this.apply; + this.apply = function () { + if ("handleTransform" in this) { + this.handleTransform(); + } + return apply.apply(this, arguments); + } } PIXI.Filter.prototype.filterTransform = function () { - this.calculatePadding(); - if (this.hasOwnProperty("zIndex")) { this.placeableImg.parent.zIndex = this.zIndex; } - - this.padding = this.currentPadding; - - if ("handleTransform" in this) { - this.handleTransform(); - } } PIXI.Filter.prototype.normalizeTMParams = function () { diff --git a/tokenmagic/fx/glsl/fragmentshaders/flood.js b/tokenmagic/fx/glsl/fragmentshaders/flood.js index 897807e..5580365 100644 --- a/tokenmagic/fx/glsl/fragmentshaders/flood.js +++ b/tokenmagic/fx/glsl/fragmentshaders/flood.js @@ -8,11 +8,11 @@ uniform float billowy; uniform float tintIntensity; uniform vec2 shift; uniform vec3 waterColor; + varying vec2 vTextureCoord; varying vec2 vFilterCoord; -varying vec4 vInputSize; -varying vec4 vOutputFrame; uniform sampler2D uSampler; +uniform mat3 filterMatrixInverse; const float timeSpeed = 3.; @@ -70,7 +70,8 @@ vec4 water( vec2 fragCoord ) vec2 p = vec2(result, result2)*0.019 + (cos(uv*1.1 - sin(uv.yx + time*timeSpeed/20.))*0.012); uv.x -= shift.x; uv.y -= shift.y; - vec4 pixel = texture2D( uSampler , ((uv*vOutputFrame.zw)/vInputSize.xy) + (p*billowy) ); + uv += p * billowy; + vec4 pixel = texture2D( uSampler , (filterMatrixInverse * vec3(uv, 1.0)).xy ); return (vec4(result)*0.9 + pixel)*pixel.a; } diff --git a/tokenmagic/fx/glsl/fragmentshaders/forcefield.js b/tokenmagic/fx/glsl/fragmentshaders/forcefield.js index 7487d0c..94e2669 100644 --- a/tokenmagic/fx/glsl/fragmentshaders/forcefield.js +++ b/tokenmagic/fx/glsl/fragmentshaders/forcefield.js @@ -14,7 +14,6 @@ uniform float scale; uniform float radius; uniform float hideRadius; uniform float discardThreshold; -uniform float ratio; uniform bool chromatic; uniform bool alphaDiscard; uniform sampler2D uSampler; @@ -541,7 +540,7 @@ vec4 galaxy(vec2 suv) vec2 getSphere(out float alpha, out float r) { vec2 tc = vFilterCoord.xy; - vec2 p = (-1.0 + 2. * tc) * (1.01 / (radius*ratio)); + vec2 p = (-1.0 + 2. * tc) * (1.01 / radius); r = dot(p,p); r > 0.943 ? alpha = max(min(40.*log(1./r),1.),0.) : alpha = 1.; float f = (1.0-sqrt(1.0-r))/(r); @@ -554,7 +553,7 @@ vec2 getSphere(out float alpha, out float r) void computeHideAlpha(out float alpha) { vec2 tc = vFilterCoord.xy; - vec2 p = (-1.0 + 2. * tc) * (1.01 / (hideRadius*ratio)); + vec2 p = (-1.0 + 2. * tc) * (1.01 / hideRadius); float r = dot(p,p); r > 0.9 ? alpha = 1.-max(min(40.*log(1./r),1.),0.) : alpha = 0.; } diff --git a/tokenmagic/fx/glsl/fragmentshaders/liquid.js b/tokenmagic/fx/glsl/fragmentshaders/liquid.js index 59339aa..78ef980 100644 --- a/tokenmagic/fx/glsl/fragmentshaders/liquid.js +++ b/tokenmagic/fx/glsl/fragmentshaders/liquid.js @@ -3,6 +3,7 @@ precision mediump float; precision mediump int; uniform sampler2D uSampler; +uniform mat3 filterMatrixInverse; uniform float time; uniform float intensity; uniform float scale; @@ -13,8 +14,6 @@ uniform vec3 color; varying vec2 vFilterCoord; varying vec2 vTextureCoord; -varying vec4 vInputSize; -varying vec4 vOutputFrame; #define PI 3.14159265359 @@ -88,7 +87,7 @@ void main() { uv *= 1. + 0.11*(cos(sqrt(max(distortion1, distortion2))+1.)*0.5); uv -= vec2(0.036,0.81); - vec2 mappedCoord = (uv*vOutputFrame.zw) / vInputSize.xy; + vec2 mappedCoord = (filterMatrixInverse * vec3(uv, 1.0)).xy; vec4 pixel = texture2D(uSampler, mappedCoord); vec3 aColor = color; diff --git a/tokenmagic/fx/glsl/fragmentshaders/magicglow.js b/tokenmagic/fx/glsl/fragmentshaders/magicglow.js index c64dc4f..47a1c05 100644 --- a/tokenmagic/fx/glsl/fragmentshaders/magicglow.js +++ b/tokenmagic/fx/glsl/fragmentshaders/magicglow.js @@ -11,8 +11,8 @@ uniform int auraType; uniform bool holes; uniform vec2 thickness; uniform vec4 color; -uniform vec4 filterArea; -uniform vec4 filterClamp; +uniform vec4 inputSize; +uniform vec4 inputClamp; varying vec2 vTextureCoord; varying vec2 vFilterCoord; @@ -62,7 +62,7 @@ vec4 outlining() for (float angle = 0.; angle <= TWOPI; angle += 0.3141592653) { displaced.x = vTextureCoord.x + thickness.x * cos(angle); displaced.y = vTextureCoord.y + thickness.y * sin(angle); - curColor = texture2D(uSampler, clamp(displaced, filterClamp.xy, filterClamp.zw)); + curColor = texture2D(uSampler, clamp(displaced, inputClamp.xy, inputClamp.zw)); maxAlpha = max(maxAlpha, curColor.a); } float resultAlpha = max(maxAlpha, ownColor.a); @@ -72,7 +72,7 @@ vec4 outlining() vec4 glowing() { - vec2 px = vec2(1.0 / filterArea.x, 1.0 / filterArea.y); + vec2 px = inputSize.zw; float totalAlpha = 0.0; float outerStrength = 6.; @@ -86,7 +86,7 @@ vec4 glowing() for (float curDistance = 0.0; curDistance < 10.; curDistance++) { displaced = clamp(vTextureCoord + direction * - (curDistance + 1.0), filterClamp.xy, filterClamp.zw); + (curDistance + 1.0), inputClamp.xy, inputClamp.zw); curColor = texture2D(uSampler, displaced); totalAlpha += (10. - curDistance) * curColor.a; diff --git a/tokenmagic/fx/glsl/fragmentshaders/matrix.js b/tokenmagic/fx/glsl/fragmentshaders/matrix.js index d8abb48..0b7da82 100644 --- a/tokenmagic/fx/glsl/fragmentshaders/matrix.js +++ b/tokenmagic/fx/glsl/fragmentshaders/matrix.js @@ -9,16 +9,15 @@ uniform float bpStrength; uniform vec2 scale; uniform vec2 translation; uniform vec2 pivot; -uniform vec4 filterClamp; +uniform vec4 inputClamp; uniform sampler2D uSampler; +uniform mat3 filterMatrixInverse; varying vec2 vFilterCoord; -varying vec4 vInputSize; -varying vec4 vOutputFrame; -float angle = -radians(rotation); +const float PI = 3.1415927; -vec2 morphing(vec2 uv) { +vec2 morphing(in vec2 uv) { float dist = length(uv); // twist effect @@ -43,21 +42,22 @@ vec2 morphing(vec2 uv) { return uv; } -void transform(out vec2 uv) { +vec2 transform(in vec2 uv) { + float angle = -(PI * rotation * 0.005555555555); uv -= pivot; - uv *= mat2(cos(angle),-sin(angle), - sin(angle),cos(angle)); - uv *= mat2(scale.x,0.0, - 0.0,scale.y); + uv *= mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); + uv *= mat2(scale.x, 0.0, 0.0, scale.y); uv = morphing(uv); uv += pivot; + + return uv; } void main() { vec2 uv = vFilterCoord + translation; - transform(uv); - vec2 mappedCoord = (uv*vOutputFrame.zw) / vInputSize.xy; - vec4 pixel = texture2D(uSampler,clamp(mappedCoord, filterClamp.xy, filterClamp.zw)); + uv = transform(uv); + vec2 mappedCoord = (filterMatrixInverse * vec3(uv, 1.0)).xy; + vec4 pixel = texture2D(uSampler,clamp(mappedCoord, inputClamp.xy, inputClamp.zw)); gl_FragColor = pixel; } `; \ No newline at end of file diff --git a/tokenmagic/fx/glsl/fragmentshaders/mirrorimages.js b/tokenmagic/fx/glsl/fragmentshaders/mirrorimages.js index 0e04b1b..e18543f 100644 --- a/tokenmagic/fx/glsl/fragmentshaders/mirrorimages.js +++ b/tokenmagic/fx/glsl/fragmentshaders/mirrorimages.js @@ -9,7 +9,7 @@ uniform float ampY; uniform int blend; uniform int nbImage; uniform sampler2D uSampler; -uniform vec4 filterClamp; +uniform vec4 inputClamp; varying vec2 vTextureCoord; varying vec2 vFilterCoord; @@ -50,7 +50,7 @@ vec4 renderMirror(vec2 translation, vec4 prevpix) { vec2 displaced = vTextureCoord + translation; return blender(blend, prevpix, - texture2D(uSampler, clamp(displaced, filterClamp.xy, filterClamp.zw))); + texture2D(uSampler, clamp(displaced, inputClamp.xy, inputClamp.zw))); } void main() @@ -62,7 +62,7 @@ void main() if (nbImage >= 1) { translation = vec2(x,y); - renderedPixel = texture2D(uSampler, clamp(vTextureCoord + translation, filterClamp.xy, filterClamp.zw)); + renderedPixel = texture2D(uSampler, clamp(vTextureCoord + translation, inputClamp.xy, inputClamp.zw)); } if (nbImage >= 2) { translation = 0.90*vec2(-x,y*0.5); diff --git a/tokenmagic/fx/glsl/fragmentshaders/polymorph.js b/tokenmagic/fx/glsl/fragmentshaders/polymorph.js index faaea65..ba84a40 100644 --- a/tokenmagic/fx/glsl/fragmentshaders/polymorph.js +++ b/tokenmagic/fx/glsl/fragmentshaders/polymorph.js @@ -4,14 +4,11 @@ precision mediump float; uniform float progress; uniform float magnify; uniform int type; -uniform vec2 imgToTex; -uniform vec2 texShift; -uniform vec4 filterClamp; -uniform vec4 filterClampTarget; -varying vec4 vInputSize; -varying vec4 vOutputFrame; +uniform vec4 inputClamp; +uniform vec4 inputClampTarget; uniform sampler2D uSampler; uniform sampler2D uSamplerTarget; +uniform mat3 filterMatrixInverse; varying vec2 vTextureCoord; varying vec2 vTextureCoordExtra; @@ -21,18 +18,18 @@ const float PI = 3.14159265358; float getClip(vec2 uv) { return step(3.5, - step(filterClampTarget.x, uv.x) + - step(filterClampTarget.y, uv.y) + - step(uv.x, filterClampTarget.z) + - step(uv.y, filterClampTarget.w)); + step(inputClampTarget.x, uv.x) + + step(inputClampTarget.y, uv.y) + + step(uv.x, inputClampTarget.z) + + step(uv.y, inputClampTarget.w)); } vec4 getFromColor(vec2 uv) { - return texture2D(uSampler,clamp(uv,filterClamp.xy,filterClamp.zw)); + return texture2D(uSampler,clamp(uv,inputClamp.xy,inputClamp.zw)); } vec4 getToColor(vec2 uv) { - return texture2D(uSamplerTarget,clamp(uv,filterClampTarget.xy,filterClampTarget.zw))*getClip(uv); + return texture2D(uSamplerTarget,clamp(uv,inputClampTarget.xy,inputClampTarget.zw))*getClip(uv); } float rand(vec2 co) { @@ -66,7 +63,7 @@ vec4 morph(vec2 uv, vec2 uvt) { vec2 oc = mix(oa,ob,0.5)*0.1; float w0 = progress; float w1 = 1.0-w0; - vec2 sourceMappedCoord = ((vFilterCoord+(oc*0.4)*w0)*vOutputFrame.zw) / vInputSize.xy; + vec2 sourceMappedCoord = (filterMatrixInverse * vec3(vFilterCoord+(oc*0.4)*w0, 1.0)).xy; vec4 fromcol = getFromColor(sourceMappedCoord); vec4 tocol = getToColor(uvt-oc*w1); float a = mix(ca.a, cb.a, progress); @@ -80,7 +77,7 @@ vec4 waterdrop(vec2 uv, vec2 uvt) { return mix(getFromColor(uv), getToColor(uvt), progress); } else { vec2 shiftuvt = dirt * sin(distt * 60. - progress * 20.); - vec2 fuv = ((vFilterCoord + (shiftuvt*(1.-progress)))*vOutputFrame.zw) / vInputSize.xy; + vec2 fuv = (filterMatrixInverse * vec3(vFilterCoord + (shiftuvt*(1.-progress)), 1.0)).xy; return mix(getFromColor(fuv), getToColor(uvt + (shiftuvt*(1.-progress))), progress); } } @@ -112,7 +109,7 @@ vec2 swirluv(vec2 uv) { vec4 swirl(vec2 uv, vec2 uvt) { vec2 suvfrom = swirluv(vFilterCoord); vec2 suvto = swirluv(uvt); - vec2 sourceMappedCoord = (suvfrom*vOutputFrame.zw) / vInputSize.xy; + vec2 sourceMappedCoord = (filterMatrixInverse * vec3(suvfrom, 1.0)).xy; vec4 fscol = getFromColor(sourceMappedCoord); vec4 ftcol = getToColor(suvto); return mix( fscol, ftcol, progress ); diff --git a/tokenmagic/fx/glsl/fragmentshaders/sprite.js b/tokenmagic/fx/glsl/fragmentshaders/sprite.js new file mode 100644 index 0000000..60910a6 --- /dev/null +++ b/tokenmagic/fx/glsl/fragmentshaders/sprite.js @@ -0,0 +1,114 @@ +export const sprite = ` +precision mediump float; + +uniform float rotation; +uniform float twRadius; +uniform float twAngle; +uniform float bpRadius; +uniform float bpStrength; + +uniform bool inverse; +uniform bool top; +uniform bool colorize; + +uniform vec2 scale; +uniform vec2 translation; + +uniform vec3 color; + +uniform vec4 inputClamp; +uniform vec4 inputClampTarget; + +uniform sampler2D uSampler; +uniform sampler2D uSamplerTarget; + +varying vec2 vTextureCoord; +varying vec2 vTextureCoordExtra; +varying vec2 vFilterCoord; +varying mat3 vTargetUVMatrix; + +const float PI = 3.14159265358; + +float getClip(in vec2 uv) { + return step(3.5, + step(inputClampTarget.x, uv.x) + + step(inputClampTarget.y, uv.y) + + step(uv.x, inputClampTarget.z) + + step(uv.y, inputClampTarget.w)); +} + +vec2 morphing(in vec2 uv) { + float dist = length(uv); + + // twist effect + if (dist < twRadius) { + float ratioDist = (twRadius - dist) / twRadius; + float angleMod = ratioDist * ratioDist * twAngle; + float s = sin(angleMod); + float c = cos(angleMod); + uv = vec2(uv.x * c - uv.y * s, uv.x * s + uv.y * c); + } + + // bulge pinch effect + if (dist < bpRadius) { + float percent = dist / bpRadius; + if (bpStrength > 0.) { + uv *= mix(1.0, smoothstep(0., bpRadius / dist, percent), bpStrength * 0.75); + } else { + uv *= mix(1.0, pow(percent, 1.0 + bpStrength * 0.75) * bpRadius / dist, 1.0 - percent); + } + } + + return uv; +} + +vec4 colorization(in vec4 col) { + vec3 wcol = col.rgb; + if (inverse) { + wcol = (vec3(1.0) - wcol) * col.a; + } + float avg = (wcol.r + wcol.g + wcol.b) / 3.0; + return vec4(vec3(color * avg), col.a); +} + +vec2 transform(in vec2 uv) { + float angle = -(PI * rotation * 0.005555555555); + uv -= 0.5; + uv *= mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); + uv *= mat2(1.0 / scale.x, 0.0, 0.0, 1.0 / scale.y); + uv = morphing(uv); + uv += 0.5; + + return uv; +} + +vec4 getFromColor(in vec2 uv) { + return texture2D(uSampler, clamp(uv, inputClamp.xy, inputClamp.zw)); +} + +vec4 getToColor(in vec2 uv) { + return texture2D(uSamplerTarget, clamp(uv, inputClampTarget.xy, inputClampTarget.zw)) * getClip(uv); +} + +void main() { + + vec4 fcolor; + + // UV transformations + vec2 uvTex = transform(vTextureCoordExtra); + + // get samplers color + vec4 tcolor = getToColor(uvTex + translation); + vec4 icolor = getFromColor(vTextureCoord); + + // colorize if necessary + if (colorize) { + tcolor = colorization(tcolor); + } + + if (top) fcolor = mix(tcolor, icolor, 1.0 - tcolor.a); + else fcolor = mix(icolor, tcolor, 1.0 - icolor.a); + + gl_FragColor = fcolor; +} +`; \ No newline at end of file diff --git a/tokenmagic/fx/glsl/vertexshaders/customvertex2D.js b/tokenmagic/fx/glsl/vertexshaders/customvertex2D.js index 9726348..fdcd61a 100644 --- a/tokenmagic/fx/glsl/vertexshaders/customvertex2D.js +++ b/tokenmagic/fx/glsl/vertexshaders/customvertex2D.js @@ -6,13 +6,12 @@ precision mediump float; attribute vec2 aVertexPosition; uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; uniform vec4 inputSize; uniform vec4 outputFrame; varying vec2 vTextureCoord; varying vec2 vFilterCoord; -varying vec4 vInputSize; -varying vec4 vOutputFrame; vec4 filterVertexPosition( void ) { @@ -30,8 +29,6 @@ void main(void) { gl_Position = filterVertexPosition(); vTextureCoord = filterTextureCoord(); - vFilterCoord = vTextureCoord * inputSize.xy / outputFrame.zw; - vInputSize = inputSize; - vOutputFrame = outputFrame; + vFilterCoord = (filterMatrix * vec3(vTextureCoord, 1.0)).xy; } `; \ No newline at end of file diff --git a/tokenmagic/fx/glsl/vertexshaders/customvertex2DSampler.js b/tokenmagic/fx/glsl/vertexshaders/customvertex2DSampler.js index 15f7b0a..54176dc 100644 --- a/tokenmagic/fx/glsl/vertexshaders/customvertex2DSampler.js +++ b/tokenmagic/fx/glsl/vertexshaders/customvertex2DSampler.js @@ -6,13 +6,12 @@ precision mediump float; attribute vec2 aVertexPosition; uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; uniform mat3 targetUVMatrix; varying vec2 vTextureCoord; varying vec2 vTextureCoordExtra; varying vec2 vFilterCoord; -varying vec4 vInputSize; -varying vec4 vOutputFrame; uniform vec4 inputSize; uniform vec4 outputFrame; @@ -34,8 +33,6 @@ void main(void) gl_Position = filterVertexPosition(); vTextureCoord = filterTextureCoord(); vTextureCoordExtra = (targetUVMatrix * vec3(vTextureCoord, 1.0)).xy; - vFilterCoord = vTextureCoord * inputSize.xy / outputFrame.zw; - vInputSize = inputSize; - vOutputFrame = outputFrame; + vFilterCoord = (filterMatrix * vec3(vTextureCoord, 1.0)).xy; } `; \ No newline at end of file diff --git a/tokenmagic/gui/icons/simple-gui-macro.svg b/tokenmagic/gui/icons/simple-gui-macro.svg new file mode 100644 index 0000000..cd1d005 --- /dev/null +++ b/tokenmagic/gui/icons/simple-gui-macro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tokenmagic/gui/macros/SImpleGUIMacro.js b/tokenmagic/gui/macros/SImpleGUIMacro.js new file mode 100644 index 0000000..7cf1787 --- /dev/null +++ b/tokenmagic/gui/macros/SImpleGUIMacro.js @@ -0,0 +1,207 @@ +const MyCompendium = 'TokenMagic-Fav'; + +const macroVersion = 'v0.1'; +/* Simple GUI - Token Magic +Features +- Detect token selected/targered +- Realtime preview +Source: +Icon: +*/ +// +let filterType; +(async () => { + let msgtoken = ``; + if (canvas.tokens.controlled != 0) { + msgtoken = `

Token Selected: ${canvas.tokens.controlled[0].name}

`; + filterType = 'selected'; + } else if (game.user.targets.size != 0) { + let t = game.user.targets.values().next().value; + msgtoken = `

Token Targered: ${t.name}

`; + filterType = 'targered'; + } else { + ui.notifications.warn("Select a token!"); + return; + } + + let template = + ` + + ${msgtoken} + + +
+ + +
+ + + + + `; + + new Dialog({ + title: `Token Magic - ${macroVersion}`, + content: template, + buttons: { + yes: { + icon: "", + label: "Apply", + callback: () => { } + }, + clear: { + icon: "", + label: "Clear", + callback: async (html) => { + removeOnSelected(html); + } + } + }, + default: "yes", + close: html => { + } + }, { id: 'tokenmagic-quickpreview' }).render(true); + //} ).render(true); +})() + +// ============================== +// Main +// ============================== + + +// ============================== +// Common Functions +// ============================== +async function removeOnSelected() { + await TokenMagic.deleteFilters(_token); + await TokenMagic.deleteFiltersOnSelected(); // Delete all filters on the selected tokens/tiles +} + +async function loadMacroButton() { + const list_compendium = await game.packs.filter(p => p.entity == 'Macro'); + const inside = await list_compendium.filter(p => p.metadata.label == MyCompendium)[0].getContent(); + let msg = ``; + let tmp; + let counter = 1; + + inside.map((el) => { + tmp = el.data.name.split(' ').join(''); + msg += `
  • `; + counter += 1; + }); + + return msg; +} + +async function loadMacros(filterType) { + const list_compendium = await game.packs.filter(p => p.entity == 'Macro'); + const inside = await list_compendium.filter(p => p.metadata.label == MyCompendium)[0].getContent(); + let msg = ``; + let tmp; + let counter = 1; + + inside.map((el) => { + let macro = el.data.command; + if (filterType == 'selected') { + macro = macro.replace("OnTargeted", "OnSelected"); + } else { + macro = macro.replace("OnSelected", "OnTargeted"); + } + tmp = el.data.name.split(' ').join(''); + msg += `async function effect${counter}() {`; + msg += `await removeOnSelected();`; + msg += `${macro}`; + msg += `}`; + counter += 1; + }); + return msg; +} + +function removeAll() { + const clean = `async function removeOnSelected() {await TokenMagic.deleteFilters(_token);await TokenMagic.deleteFiltersOnSelected()}`; + return clean; +} diff --git a/tokenmagic/lang/en.json b/tokenmagic/lang/en.json index 7543472..f8f9d28 100644 --- a/tokenmagic/lang/en.json +++ b/tokenmagic/lang/en.json @@ -19,6 +19,8 @@ "TMFX.template.opacity": "Inner Opacity:", "TMFX.template.fx": "Special Effects:", "TMFX.template.tint": "Effect Tint:", + "TMFX.settings.autoFPS.name": "Auto framerate", + "TMFX.settings.autoFPS.hint": "Check this option to let the browser (or the application) choose the framerate limit. May suppress jerky animations on some configurations. (Warning : this option overrides Foundry framerate limit)", "TMFX.settings.useMaxPadding.name": "FX in additive padding mode", "TMFX.settings.useMaxPadding.hint": "By default, FX paddings are additives when applied on a given placeable. If the checkbox is unchecked, the maximum padding is used.", "TMFX.settings.minPadding.name": "Minimum padding", @@ -36,7 +38,9 @@ "TMFX.settings.autoTemplateEnabled.name": "Enable automatic template effects", "TMFX.settings.autoTemplateEnabled.hint": "Disabling this option will disable all automatic template effects. NOTE: Only affects newly placed templates.", "TMFX.settings.defaultTemplateOnHover.name": "Default template grid on hover", - "TMFX.settings.defaultTemplateOnHover.hint": "Display the default template grid only when hovering on the template layer.", + "TMFX.settings.defaultTemplateOnHover.hint": "Display the default template grid only when hovering.", + "TMFX.settings.autohideTemplateElements.name": "Automatically hide template elements", + "TMFX.settings.autohideTemplateElements.hint": "Display nonessential template elements only when hovering on the template layer.", "TMFX.settings.autoTemplateSettings.button.name": "Automatic Template Effects", "TMFX.settings.autoTemplateSettings.button.label": "Template Settings", "TMFX.settings.autoTemplateSettings.button.hint": "On game systems that are supported, template effects can be enabled based on the type of spell area and damage type, or with overrides for specific spells.", diff --git a/tokenmagic/lang/es.json b/tokenmagic/lang/es.json index d8a06b8..5289a5b 100644 --- a/tokenmagic/lang/es.json +++ b/tokenmagic/lang/es.json @@ -1,51 +1,58 @@ { + "I18N.LANGUAGE": "Español", + "I18N.MAINTAINERS": "@Viriato139ac#0342", + "TMFX.TokenMagic": "TokenMagic", - "TMFX.preset.add.success": "FX añadido con éxito a la biblioteca de parámetros predeterminados.", - "TMFX.preset.add.permission.failure": "No tienes permisos para añadir el FX a la biblioteca de parámetros predeterminados.", - "TMFX.preset.add.params.failure": "El FX no ha podido ser añadido a la biblioteca de parámetros predeterminados. Parámetros incorrectos.", - "TMFX.preset.add.duplicate.failure": "El FX no ha podido ser añadido a la biblioteca de parámetros predeterminados. Ya existe un FX con el mismo nombre.", - "TMFX.preset.delete.success": "Se ha eliminado correctamente el FX de la biblioteca de parámetros predeterminados.", - "TMFX.preset.delete.permission.failure": "No tienes permisos para borrar el FX de la biblioteca de parámetros predeterminados.", - "TMFX.preset.delete.params.failure": "Fallo en la eliminación : el parámetro \"name\" debe ser un carácter.", - "TMFX.preset.delete.notfound.failure": "El FX que se quiere borrar no existe en la biblioteca de parámetros predeterminados.", + "TMFX.preset.add.success": "FX añadido con éxito a la biblioteca de parámetros predeterminados", + "TMFX.preset.add.permission.failure": "No tiene permisos para añadir el FX a la biblioteca de parámetros predeterminados", + "TMFX.preset.add.params.failure": "El FX no se ha podido añadir a la biblioteca de parámetros predeterminados. Parámetros incorrectos", + "TMFX.preset.add.duplicate.failure": "El FX no se ha podido añadir a la biblioteca de parámetros predeterminados. Ya existe un FX con el mismo nombre", + "TMFX.preset.delete.success": "Se ha eliminado correctamente el FX de la biblioteca de parámetros predeterminados", + "TMFX.preset.delete.permission.failure": "No tiene permisos para borrar el FX de la biblioteca de parámetros predeterminados", + "TMFX.preset.delete.params.failure": "Fallo en la eliminación : el parámetro \"name\" debe ser un carácter", + "TMFX.preset.delete.notfound.failure": "El FX que se quiere borrar no existe en la biblioteca de parámetros predeterminados", "TMFX.preset.delete.empty.failure": "Fallo : ¡ la biblioteca de parámetros predeterminados ya está vacía !", - "TMFX.preset.import.format.failure": "Importación no completada. Formato inválido.", - "TMFX.preset.import.success": "La importación de FX ha sido completada con éxito.", - "TMFX.preset.import.failure": "La importación no pudo ser completada.", + "TMFX.preset.import.format.failure": "Importación no completada. Formato inválido", + "TMFX.preset.import.success": "La importación de FX ha sido completada con éxito", + "TMFX.preset.import.failure": "La importación no pudo ser completada", "TMFX.preset.reset.message": "¿Está seguro de que quiere reiniciar su bibloteca de parámetros predeterminados?", - "TMFX.preset.reset.success": "Su biblioteca de parámetros predeterminados ha sido reiniciada con éxito.", + "TMFX.preset.reset.success": "Su biblioteca de parámetros predeterminados ha sido reiniciada con éxito", "TMFX.settings.importOverwrite.name": "Sobrescribir al importar", - "TMFX.settings.importOverwrite.hint": "Marca esta opción si quieres reemplazar los parámetros predeterminados que tengan los mismos nombres al importar.", + "TMFX.settings.importOverwrite.hint": "Marque esta opción si quiere reemplazar los parámetros predeterminados que tengan los mismos nombres al importar", "TMFX.template.opacity": "Opacidad Interior:", "TMFX.template.fx": "Efectos Especiales:", "TMFX.template.tint": "Efecto de Tono:", + "TMFX.settings.autoFPS.name": "Fotogramas por segundo automático", + "TMFX.settings.autoFPS.hint": "Habilite esta opción para que el navegador (o el cliente) elija automáticamente el límite de fotogramas por segundo (fps). En algunas configuraciones puede evitar que se entrecorten las animaciones (Aviso: esta opción sobrescribe el límite de fps de Foundry)", "TMFX.settings.useMaxPadding.name": "FX en modo de relleno aditivo", - "TMFX.settings.useMaxPadding.hint": "Por defecto, los rellenos de FX son aditivos al aplicarlos a un determinado contenedor. Si se desmarca la casilla, se usa el relleno máximo.", + "TMFX.settings.useMaxPadding.hint": "Por defecto, los rellenos de FX son aditivos al aplicarlos a un determinado contenedor. Si se desmarca la casilla, se usa el relleno máximo", "TMFX.settings.minPadding.name": "Relleno mínimo", - "TMFX.settings.minPadding.hint": "El relleno mínimo aplicado a un FX.", + "TMFX.settings.minPadding.hint": "El relleno mínimo aplicado a un FX", "TMFX.settings.fxPlayerPermission.name": "Modo permisivo", - "TMFX.settings.fxPlayerPermission.hint": "Si se marca esta opción, los usuarios no GM pueden añadir, modificar y borrar FX en contenedores que no son de su propiedad. El GM debe estar conectado.", + "TMFX.settings.fxPlayerPermission.hint": "Si se marca esta opción, los usuarios no GM pueden añadir, modificar y borrar FX en contenedores que no son de su propiedad. El GM debe estar conectado", "TMFX.settings.useZOrder.name": "Ordenar FX", - "TMFX.settings.useZOrder.hint": "Marca esta opción si quieres que los efectos se apliquen en el orden de su propiedad zOrder. Se aplican primero los efectos con el zOrder menor (leer la documentación)", + "TMFX.settings.useZOrder.hint": "Marque esta opción si quiere que los efectos se apliquen en el orden de su propiedad zOrder. Se aplican primero los efectos con el zOrder menor (leer la documentación)", "TMFX.settings.disableAnimations.name": "Deshabilitar Animaciones FX", - "TMFX.settings.disableAnimations.hint": "Marca esta opción si quieres deshabilitar las animaciones FX. Requiere que se recargue Foundry.", + "TMFX.settings.disableAnimations.hint": "Marque esta opción si quiere deshabilitar las animaciones FX. Requiere que se recargue Foundry", "TMFX.settings.disableCaching.name": "Deshabilitar caché de filtros al inicio", - "TMFX.settings.disableCaching.hint": "Marca esta opción si quieres deshabilitar la caché de filtros al inicio de Foundry.", + "TMFX.settings.disableCaching.hint": "Marque esta opción si quiere deshabilitar la caché de filtros al inicio de Foundry", + "TMFX.settings.disableVideo.name": "Deshabilitar soporte de vídeo en las plantillas", + "TMFX.settings.disableVideo.hint": "Marque esta opción si quiere deshabilitar soporte de vídeo y reescalado de texturas en las plantillas", "TMFX.settings.autoTemplateEnabled.name": "Habilitar efectos de plantilla automáticos", - "TMFX.settings.autoTemplateEnabled.hint": "Desmarcar esta opción deshabilitará todos los efectos de plantilla automáticos. NOTA: Solo afecta a las plantillas colocadas recientemente.", + "TMFX.settings.autoTemplateEnabled.hint": "Desmarcar esta opción deshabilitará todos los efectos de plantilla automáticos. NOTA: Solo afecta a las plantillas colocadas recientemente", "TMFX.settings.defaultTemplateOnHover.name": "Rejilla de plantilla por defecto al pasar el ratón", - "TMFX.settings.defaultTemplateOnHover.hint": "Muestra la rejilla de plantilla por defecto solo cuando se pasa el ratón por encima de la capa de plantillas.", + "TMFX.settings.defaultTemplateOnHover.hint": "Muestra la rejilla de plantilla por defecto al pasar el ratón", "TMFX.settings.autoTemplateSettings.button.name": "Efectos de Plantilla Automáticos", "TMFX.settings.autoTemplateSettings.button.label": "Ajustes de Plantilla", - "TMFX.settings.autoTemplateSettings.button.hint": "En los sistemas que lo soporten, los efectos de plantilla pueden activarse en base al tipo de hechizo de área de efecto y tipo de daño, o sobrescribiendo para ciertos hechizos.", + "TMFX.settings.autoTemplateSettings.button.hint": "En los sistemas que lo soporten, los efectos de plantilla pueden activarse en base al tipo de hechizo de área de efecto y tipo de daño, o sobrescribiendo para ciertos hechizos", "TMFX.settings.autoTemplateSettings.tabs.general": "General", "TMFX.settings.autoTemplateSettings.tabs.categories": "Categorías", "TMFX.settings.autoTemplateSettings.tabs.overrides": "Sobrescribir", "TMFX.settings.autoTemplateSettings.texture.label": "Textura (opcional):", "TMFX.settings.autoTemplateSettings.target.label": "Objetivo:", - "TMFX.settings.autoTemplateSettings.texture.hint": "Especificar opcionalmente una textura para el filtro. Si no se especifica ninguna, se utilizará una textura apropiada.", + "TMFX.settings.autoTemplateSettings.texture.hint": "Especificar opcionalmente una textura para el filtro. Si no se especifica ninguna, se utilizará una textura apropiada", "TMFX.settings.autoTemplateSettings.unsupported": "característica no soportada por el sistema, ¡Cualquier contribución para habilitar más sistemas es bienvenida!", - "TMFX.settings.autoTemplateSettings.overrides.introduction": "Aquí puedes configurar la sobrescritura para objetos/hechizos individuales. Escribe en el Campo Objetivo el nombre del objeto/hechizo tal y como aparece en tus Objetos/Compendios y configura los ajustes que prefieras.", + "TMFX.settings.autoTemplateSettings.overrides.introduction": "Aquí puede configurar la sobrescritura para objetos/hechizos individuales. Escriba en el Campo Objetivo el nombre del objeto/hechizo tal y como aparece en tus Objetos/Compendios y configure los ajustes que prefiera", "TMFX.settings.autoTemplateSettings.overrides.add": "Añadir sobreescritura", "TMFX.settings.autoTemplateSettings.dialog.title": "Ajustes de plantilla de Token Magic", "TMFX.save": "Guardar" diff --git a/tokenmagic/lang/fr.json b/tokenmagic/lang/fr.json index f23662a..9e82d0f 100644 --- a/tokenmagic/lang/fr.json +++ b/tokenmagic/lang/fr.json @@ -25,6 +25,8 @@ "TMFX.settings.autoTemplateEnabled.hint": "Décocher cette option désactivera tous les effets automatiques sur les templates. REMARQUE: affecte uniquement les templates nouvellement placés.", "TMFX.settings.defaultTemplateOnHover.name": "Grille sur demande", "TMFX.settings.defaultTemplateOnHover.hint": "Afficher uniquement la grille des templates en passant le curseur de la souris sur le sélecteur (dans la couche des templates.)", + "TMFX.settings.autoFPS.name": "Framerate automatique", + "TMFX.settings.autoFPS.hint": "Cochez cette option pour laisser le navigateur (ou l'application) décider de la limite du framerate. Peut supprimer les saccades sur certaines configurations. (Attention : cette option surcharge le paramètre de Foundry)", "TMFX.settings.autoTemplateSettings.button.name": "Effets automatiques des templates", "TMFX.settings.autoTemplateSettings.button.label": "Configuration des templates", "TMFX.settings.autoTemplateSettings.button.hint": "Pour les systèmes de jeu supportés, les effets de template peuvent être activés en tenant compte du type de zone et de dégâts, ou en surchargeant des sorts spécifiques.", diff --git a/tokenmagic/lang/ko.json b/tokenmagic/lang/ko.json new file mode 100644 index 0000000..bb0454f --- /dev/null +++ b/tokenmagic/lang/ko.json @@ -0,0 +1,54 @@ +{ + "TMFX.TokenMagic": "TokenMagic", + "TMFX.preset.add.success": "FX가 프리셋 라이브러리에 성공적으로 추가되었습니다.", + "TMFX.preset.add.permission.failure": "FX를 프리셋 라이브러리에 추가하지 못했습니다.", + "TMFX.preset.add.params.failure": "FX를 프리셋 라이브러리에 추가하지 못했습니다. 패러미터가 잘못 되었습니다.", + "TMFX.preset.add.duplicate.failure": "FX를 프리셋 라이브러리에 추가하지 못했습니다. 같은 이름의 FX가 이미 존재합니다.", + "TMFX.preset.delete.success": "FX가 프리셋 라이브러리에서 성공적으로 제거되었습니다.", + "TMFX.preset.delete.permission.failure": "프리셋 라이브러리에서 FX를 삭제할 수 없습니다.", + "TMFX.preset.delete.params.failure": "삭제 실패 : \"name\" 패러미터는 문자열이어야 합니다.", + "TMFX.preset.delete.notfound.failure": "삭제할 FX가 프리셋 라이브러리에 존재하지 않습니다.", + "TMFX.preset.delete.empty.failure": "실패 : 프리셋 라이브러리가 이미 비어있습니다!", + "TMFX.preset.import.format.failure": "가져오기를 완료할 수 없습니다. 잘못된 형식입니다.", + "TMFX.preset.import.success": "FX 가져오기가 성공적으로 완료되었습니다.", + "TMFX.preset.import.failure": "가져오기를 완료할 수 없습니다.", + "TMFX.preset.reset.message": "프리셋 라이브러리를 재설정하시겠습니까?", + "TMFX.preset.reset.success": "프리셋 라이브러리가 성공적으로 재설정 되었습니다.", + "TMFX.settings.importOverwrite.name": "가져오기시 덮어쓰기", + "TMFX.settings.importOverwrite.hint": "가져오기시 프리셋을 동일한 이름으로 대체하려면 이 옵션을 선택하십시오.", + "TMFX.template.opacity": "내부 불투명도:", + "TMFX.template.fx": "특수 효과:", + "TMFX.template.tint": "효과 색조:", + "TMFX.settings.useMaxPadding.name": "부가적 패딩 모드 FX", + "TMFX.settings.useMaxPadding.hint": "기본적으로 FX 패딩은 지정된 장소에 적용될 때의 부가적 요소이다. 이 체크박스를 선택하지 않으면 최대치의 패딩이 사용된다.", + "TMFX.settings.minPadding.name": "패딩 최소화", + "TMFX.settings.minPadding.hint": "FX에 최소한의 패딩이 적용된다.", + "TMFX.settings.fxPlayerPermission.name": "허가 모드", + "TMFX.settings.fxPlayerPermission.hint": "이 옵션을 선택하면 GM 외 플레이어는 자신이 소유하고 있지 않은 지정성 FX를 추가 및 수정할 수 있다. GM이 반드시 접속해야 한다.", + "TMFX.settings.useZOrder.name": "FX 순서", + "TMFX.settings.useZOrder.hint": "zOrder 속성의 순서대로 효과를 적용하려면 이 옵션을 선택한다. zOrder는 가장 작은 효과를 먼저 적용한다.(문서 참조)", + "TMFX.settings.disableAnimations.name": "FX 애니메이션 비활성화", + "TMFX.settings.disableAnimations.hint": "FX 애니메이션을 비활성화하려면 이 옵션을 선택한다. Foundry 리로드가 필요하다.", + "TMFX.settings.disableCaching.name": "시작시 필터 캐싱 비활성화", + "TMFX.settings.disableCaching.hint": "Foundry 시작시 필터 캐싱을 비활성화하려면 이 옵션을 선택한다.", + "TMFX.settings.disableVideo.name": "템플릿에서 비디오 지원 비활성화", + "TMFX.settings.disableVideo.hint": "템플릿에서 비디오 지원 및 텍스처 자동 크기 재조정을 비활성화 하려면 이 옵션을 선택한다.", + "TMFX.settings.autoTemplateEnabled.name": "자동 템플릿 효과 활성화", + "TMFX.settings.autoTemplateEnabled.hint": "이 옵션을 비활성화하면 모든 자동 템플릿 효과가 비활성화된다. 참고: 새로 배치된 템플릿에만 유효하다.", + "TMFX.settings.defaultTemplateOnHover.name": "호버시 기본 템플릿 그리드", + "TMFX.settings.defaultTemplateOnHover.hint": "템플릿 레이어에 호버링 할 동안에만 기본 템플릿 그리드를 표시한다.", + "TMFX.settings.autoTemplateSettings.button.name": "자동적 템플릿 효과", + "TMFX.settings.autoTemplateSettings.button.label": "템플릿 세팅", + "TMFX.settings.autoTemplateSettings.button.hint": "지원되는 게임 시스템에서 주문 영역 유형과 피해 유형에 따라 템플릿 효과를 활성화하거나 특정 주문에 대한 덮어쓰기를 할 수 있다.", + "TMFX.settings.autoTemplateSettings.tabs.general": "보통", + "TMFX.settings.autoTemplateSettings.tabs.categories": "카테고리", + "TMFX.settings.autoTemplateSettings.tabs.overrides": "덮어쓰기", + "TMFX.settings.autoTemplateSettings.texture.label": "텍스처 (옵션):", + "TMFX.settings.autoTemplateSettings.target.label": "대상:", + "TMFX.settings.autoTemplateSettings.texture.hint": "필터에 따른 텍스처를 선택 지정하십시오. 텍스처가 지정되지 않은 경우 적절한 텍스처가 사용됩니다.", + "TMFX.settings.autoTemplateSettings.unsupported": "이 기능은 현재 게임 시스템에 지원되지 않으며, 더 많은 시스템을 활성화하기 위한 기여는 언제든지 환영입니다!", + "TMFX.settings.autoTemplateSettings.overrides.introduction": "개별 아이템/주문에 대한 재설정을 여기서 할 수 있습니다. 대상 필드에 아이템/컴펜디엄에 표시되는 아이템/주문의 이름을 입력하고 원하는 설정을 구성하십시오.", + "TMFX.settings.autoTemplateSettings.overrides.add": "덮어쓰기 추가", + "TMFX.settings.autoTemplateSettings.dialog.title": "Token Magic 템플릿 설정", + "TMFX.save": "저장" +} diff --git a/tokenmagic/module.json b/tokenmagic/module.json index cf5dfd1..f8d64fc 100644 --- a/tokenmagic/module.json +++ b/tokenmagic/module.json @@ -1,107 +1,119 @@ { - "name": "tokenmagic", - "title": "Token Magic FX", - "description": "

    Add special effects and animations on your tokens, tiles, drawings and templates.

    ", - "version": "0.4.4", - "compatibleCoreVersion": "0.6.6", - "minimumCoreVersion": "0.6.0", - "author": "SecretFire and sPOiDar (Auto-Templates)", - "authors": [ - { - "name": "SecretFire" - }, - { - "name": "sPOiDar" - }, - { - "name": "Zimm" - }, - { - "name": "Lozalojo" - } - ], - "scripts": [ - "libs/filters/pixi-filters.js" - ], - "esmodules": [ - "module/settings.js", - "module/tokenmagic.js", - "module/proto/PlaceableObjectProto.js", - "fx/Anime.js", - "fx/filters/FilterBevel.js", - "fx/filters/FilterAdjustment.js", - "fx/filters/FilterAdvancedBloom.js", - "fx/filters/FilterDistortion.js", - "fx/filters/FilterGlow.js", - "fx/filters/FilterOldFilm.js", - "fx/filters/FilterDropShadow.js", - "fx/filters/FilterOutline.js", - "fx/filters/FilterTwist.js", - "fx/filters/FilterZoomBlur.js", - "fx/filters/FilterBlur.js", - "fx/filters/FilterBulgePinch.js", - "fx/filters/FilterRemoveShadow.js", - "fx/filters/FilterRays.js", - "fx/filters/FilterFog.js", - "fx/filters/FilterXFog.js", - "fx/filters/FilterElectric.js", - "fx/filters/FilterShockWave.js", - "fx/filters/FilterFumes.js", - "fx/filters/FilterWaves.js", - "fx/filters/FilterFire.js", - "fx/filters/FilterFlood.js", - "fx/filters/FilterSmoke.js", - "fx/filters/FilterForceField.js", - "fx/filters/FilterMirrorImages.js", - "fx/filters/FilterXRays.js", - "fx/filters/FilterLiquid.js", - "fx/filters/FilterGleamingGlow.js", - "fx/filters/FilterPixelate.js", - "fx/filters/FilterSpiderWeb.js", - "fx/filters/FilterSolarRipples.js", - "fx/filters/FilterGlobes.js", - "fx/filters/FilterTransform.js", - "fx/filters/FilterSplash.js", - "fx/filters/FilterPolymorph.js", - "fx/filters/FilterXFire.js", - "fx/filters/proto/FilterProto.js" - ], - "styles": [ - "styles/tokenmagic.css" - ], - "packs": [ - { - "name": "tmMacros", - "label": "TokenMagic Portfolio", - "path": "packs/token-magic-portfolio.db", - "entity": "Macro", - "module": "tokenmagic" - } - ], - "languages": [ - { - "lang": "en", - "name": "English", - "path": "lang/en.json" - }, - { - "lang": "fr", - "name": "French (FRANCE)", - "path": "lang/fr.json" - }, - { - "lang": "pt-BR", - "name": "Portugus (Brasil)", - "path": "lang/pt-BR.json" - }, - { - "lang": "es", - "name": "Spanish", - "path": "lang/es.json" - } - ], - "socket": true, - "url": "https://github.com/Feu-Secret/Tokenmagic", - "manifest": "https://raw.githubusercontent.com/Feu-Secret/Tokenmagic/master/tokenmagic/module.json", - "download": "https://github.com/Feu-Secret/Tokenmagic/releases/download/v0.4.4-alpha/Tokenmagic.zip" + "name": "tokenmagic", + "title": "Token Magic FX", + "description": "

    Add special effects and animations on your tokens, tiles, drawings and templates.

    ", + "version": "0.4.5", + "compatibleCoreVersion": "0.7.5", + "minimumCoreVersion": "0.6.6", + "author": "SecretFire, sPOiDar, Moerill", + "authors": [ + { + "name": "SecretFire" + }, + { + "name": "sPOiDar" + }, + { + "name": "Zimm" + }, + { + "name": "Lozalojo" + }, + { + "name": "Moerill" + }, + { + "name": "KLO" + } + ], + "scripts": [ + "libs/filters/pixi-filters.js" + ], + "esmodules": [ + "module/settings.js", + "module/tokenmagic.js", + "module/proto/PlaceableObjectProto.js", + "fx/Anime.js", + "fx/filters/FilterBevel.js", + "fx/filters/FilterAdjustment.js", + "fx/filters/FilterAdvancedBloom.js", + "fx/filters/FilterDistortion.js", + "fx/filters/FilterGlow.js", + "fx/filters/FilterOldFilm.js", + "fx/filters/FilterDropShadow.js", + "fx/filters/FilterOutline.js", + "fx/filters/FilterTwist.js", + "fx/filters/FilterZoomBlur.js", + "fx/filters/FilterBlur.js", + "fx/filters/FilterBulgePinch.js", + "fx/filters/FilterRemoveShadow.js", + "fx/filters/FilterRays.js", + "fx/filters/FilterFog.js", + "fx/filters/FilterXFog.js", + "fx/filters/FilterElectric.js", + "fx/filters/FilterShockWave.js", + "fx/filters/FilterFumes.js", + "fx/filters/FilterWaves.js", + "fx/filters/FilterFire.js", + "fx/filters/FilterFlood.js", + "fx/filters/FilterSmoke.js", + "fx/filters/FilterForceField.js", + "fx/filters/FilterMirrorImages.js", + "fx/filters/FilterXRays.js", + "fx/filters/FilterLiquid.js", + "fx/filters/FilterGleamingGlow.js", + "fx/filters/FilterPixelate.js", + "fx/filters/FilterSpiderWeb.js", + "fx/filters/FilterSolarRipples.js", + "fx/filters/FilterGlobes.js", + "fx/filters/FilterTransform.js", + "fx/filters/FilterSplash.js", + "fx/filters/FilterPolymorph.js", + "fx/filters/FilterXFire.js", + "fx/filters/FilterSprite.js", + "fx/filters/proto/FilterProto.js" + ], + "styles": [ + "styles/tokenmagic.css" + ], + "packs": [ + { + "name": "tmMacros", + "label": "TokenMagic Portfolio", + "path": "packs/token-magic-portfolio.db", + "entity": "Macro", + "module": "tokenmagic" + } + ], + "languages": [ + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + }, + { + "lang": "fr", + "name": "French (FRANCE)", + "path": "lang/fr.json" + }, + { + "lang": "pt-BR", + "name": "Português (Brasil)", + "path": "lang/pt-BR.json" + }, + { + "lang": "es", + "name": "Spanish", + "path": "lang/es.json" + }, + { + "lang": "ko", + "name": "Korean", + "path": "lang/ko.json" + } + ], + "socket": true, + "url": "https://github.com/Feu-Secret/Tokenmagic", + "manifest": "https://raw.githubusercontent.com/Feu-Secret/Tokenmagic/master/tokenmagic/module.json", + "download": "https://github.com/Feu-Secret/Tokenmagic/releases/download/v0.4.5-alpha/Tokenmagic.zip" } diff --git a/tokenmagic/module/autoTemplate/dnd5e.js b/tokenmagic/module/autoTemplate/dnd5e.js index eacc14a..cd067eb 100644 --- a/tokenmagic/module/autoTemplate/dnd5e.js +++ b/tokenmagic/module/autoTemplate/dnd5e.js @@ -94,97 +94,99 @@ export class AutoTemplateDND5E { } constructor() { - this._origFromItem = null; - this._importedJS = null; - this._abilityTemplate = null; + this._enabled = false; } - async configure(enabled = false) { - if (!this._isDnd5e) return; + configure(enabled = false) { + if (game.system.id !== "dnd5e") return; if (!enabled) { - if (this._origFromItem !== null) { - // Restoring the original function when setting toggled off is unsafe - // in the case that there is another module patching the same function - // and they're loaded after us, so resort to refresh here. - /* - this._abilityTemplate.fromItem = this._origFromItem; - this._origFromItem = null; - */ - window.location.reload(); + if (this._enabled) { + if (game.modules.get("lib-wrapper")?.active) { + libWrapper.unregister("tokenmagic", "game.dnd5e.canvas.AbilityTemplate.fromItem"); + } else { + window.location.reload(); + } + } + } else { + if (!this._enabled) { + if (game.modules.get("lib-wrapper")?.active) { + libWrapper.register("tokenmagic", "game.dnd5e.canvas.AbilityTemplate.fromItem", fromItem, "WRAPPER"); + } else { + const origFromItem = game.dnd5e.canvas.AbilityTemplate.fromItem; + game.dnd5e.canvas.AbilityTemplate.fromItem = function () { + return fromItem.call(this, origFromItem.bind(this), ...arguments); + } + } } - return; - } - - if (this._importedJS === null) { - this._importedJS = (await import('../../../../systems/dnd5e/module/pixi/ability-template.js')); - } - if (this._abilityTemplate === null) { - this._abilityTemplate = this._importedJS.default || this._importedJS.AbilityTemplate; } - this._origFromItem = this._abilityTemplate.fromItem; - this._abilityTemplate.fromItem = this._fromItemFn(); + this._enabled = enabled; } +} - get _isDnd5e() { - return game.system.id === 'dnd5e'; +function fromConfig(config, template) { + if (config.preset && config.preset !== '' && config.preset !== emptyPreset) { + template.data.tmfxPreset = config.preset; } + if (config.texture && config.texture !== '') { + template.data.texture = config.texture; + } + if (config.tint && config.tint !== '') { + template.data.tmfxTint = config.tint; + } + template.data.tmfxTextureAlpha = config.opacity; +} - _fromConfig(config, template) { - if (config.preset && config.preset !== '' && config.preset !== emptyPreset) { - template.data.tmfxPreset = config.preset; - } - if (config.texture && config.texture !== '') { - template.data.texture = config.texture; - } - if (config.tint && config.tint !== '') { - template.data.tmfxTint = config.tint; - } - template.data.tmfxTextureAlpha = config.opacity; +function fromOverrides(overrides = [], item, template) { + let config = overrides.find((el) => el.target.toLowerCase() === item.name.toLowerCase()); + if (!config) { + return false; } + fromConfig(config, template); + return true; +} - _fromOverrides(overrides = [], item, template) { - let config = overrides.find((el) => el.target.toLowerCase() === item.name.toLowerCase()); - if (!config) { - return false; - } - this._fromConfig(config, template); - return true; +function fromCategories(categories = {}, item, template) { + if (!item.hasDamage) { + return false; } - _fromCategories(categories = {}, item, template) { - if (!item.hasDamage) { - return false; - } - let dmgSettings = categories[item.data.data.damage.parts[0][1]] || {}; - let config = dmgSettings[template.data.t]; - if (!config) { - return false; + let config, dmgSettings; + + // some items/spells have multiple damage types + // this loop looks over all the types until it finds one with a valid fx preset + for (const [_, dmgType] of item.data.data.damage.parts) { + dmgSettings = categories[dmgType] || {}; + config = dmgSettings[template.data.t]; + + if (config && config.preset !== emptyPreset) { + break; } - this._fromConfig(mergeObject(config, { opacity: dmgSettings.opacity, tint: dmgSettings.tint }, true, true), template); - return true; } + if (!config) { + return false; + } + fromConfig(mergeObject(config, { opacity: dmgSettings.opacity, tint: dmgSettings.tint }, true, true), template); + return true; +} - _fromItemFn() { - const self = this; - return function (item) { - const template = self._origFromItem.apply(this, [item]); - if (!template) { - return template; - } - let hasPreset = template.hasOwnProperty("tmfxPreset"); - if (hasPreset) { - return template; - } - const settings = game.settings.get('tokenmagic', 'autoTemplateSettings'); - let updated = self._fromOverrides(Object.values(settings.overrides), item, template); - if (!updated) { - self._fromCategories(settings.categories, item, template); - } - return template; - } +function fromItem(wrapped, ...args) { + const [ item ] = args; + const template = wrapped(...args); + if (!template) { + return template; + } + let hasPreset = template.hasOwnProperty("tmfxPreset"); + if (hasPreset) { + return template; + } + const settings = game.settings.get('tokenmagic', 'autoTemplateSettings'); + let updated = fromOverrides(Object.values(settings.overrides), item, template); + if (!updated) { + fromCategories(settings.categories, item, template); } + return template; } -export const dnd5eTemplates = new AutoTemplateDND5E(); \ No newline at end of file +export const dnd5eTemplates = new AutoTemplateDND5E(); diff --git a/tokenmagic/module/proto/DefaultTemplateOnHover.js b/tokenmagic/module/proto/DefaultTemplateOnHover.js deleted file mode 100644 index 36422d4..0000000 --- a/tokenmagic/module/proto/DefaultTemplateOnHover.js +++ /dev/null @@ -1,40 +0,0 @@ -class DefaultTemplateOnHover { - constructor() { - this._origRefresh = null; - } - - configure(enabled = false) { - if (!enabled) { - if (this._origRefresh !== null) { - // Restoring the original function when setting toggled off is unsafe - // in the case that there is another module patching the same function - // and they're loaded after us, so resort to refresh here. - /* - MeasuredTemplate.prototype.refresh = this._origRefresh; - this._origRefresh = null; - */ - window.location.reload(); - } - return; - } - this._origRefresh = MeasuredTemplate.prototype.refresh; - MeasuredTemplate.prototype.refresh = this.refreshFn(); - } - - refreshFn() { - const self = this; - return function () { - if (game.settings.get('tokenmagic', 'defaultTemplateOnHover')) { - const hl = canvas.grid.getHighlightLayer(`Template.${this.id}`); - if (this.texture && this.texture !== '') { - hl.renderable = this._hover; - } else { - hl.renderable = true; - } - } - self._origRefresh.apply(this); - }; - } -} - -export const defaultTemplateOnHover = new DefaultTemplateOnHover(); \ No newline at end of file diff --git a/tokenmagic/module/proto/PlaceableObjectProto.js b/tokenmagic/module/proto/PlaceableObjectProto.js index ca9d0e0..b794b25 100644 --- a/tokenmagic/module/proto/PlaceableObjectProto.js +++ b/tokenmagic/module/proto/PlaceableObjectProto.js @@ -1,4 +1,4 @@ -import { PlaceableType, Magic, broadcast, SocketAction, mustBroadCast, isZOrderConfig, isVideoDisabled } from "../tokenmagic.js"; +import { PlaceableType, Magic, broadcast, SocketAction, mustBroadCast, isZOrderConfig } from "../tokenmagic.js"; import { emptyPreset, autoMinRank } from '../constants.js'; export var gMaxRank = autoMinRank; @@ -74,6 +74,8 @@ PlaceableObject.prototype._TMFXgetPlaceablePadding = function () { if (filters instanceof Array) { // "for (const) of" has performance advantage for (const filter of filters) { + if (!filter.enabled) + continue; if (canvas.app.renderer.filter.useMaxPadding) { accPadding = Math.max(accPadding, filter.padding); } else { @@ -182,140 +184,3 @@ PlaceableObject.prototype._TMFXgetPlaceableType = function () { return PlaceableType.NOT_SUPPORTED; } - -MeasuredTemplate.prototype.update = (function () { - const cachedMTU = MeasuredTemplate.prototype.update; - return async function (data, options) { - const hasTexture = data.hasOwnProperty("texture"); - const hasPresetData = data.hasOwnProperty("tmfxPreset"); - if (hasPresetData && data.tmfxPreset !== emptyPreset) { - let defaultTexture = Magic._getPresetTemplateDefaultTexture(data.tmfxPreset); - if (!(defaultTexture == null)) { - if (data.texture === '' || data.texture.includes('/modules/tokenmagic/fx/assets/templates/')) - data.texture = defaultTexture; - } - - } else if (hasTexture && data.texture.includes('/modules/tokenmagic/fx/assets/templates/') - && hasPresetData && data.tmfxPreset === emptyPreset) { - data.texture = ''; - } - - return await cachedMTU.apply(this, [data, options]); - }; -})(); - -MeasuredTemplate.prototype.refresh = (function () { - const cachedMTR = MeasuredTemplate.prototype.refresh; - return function () { - if (this.template && !this.template._destroyed) { - if (isVideoDisabled()) { - return cachedMTR.apply(this); - } else { - // INTEGRATION FROM MESS - // THANKS TO MOERILL !! - let d = canvas.dimensions; - this.position.set(this.data.x, this.data.y); - - // Extract and prepare data - let { direction, distance, angle, width } = this.data; - distance *= (d.size / d.distance); - width *= (d.size / d.distance); - direction = toRadians(direction); - - // Create ray and bounding rectangle - this.ray = Ray.fromAngle(this.data.x, this.data.y, direction, distance); - - // Get the Template shape - switch (this.data.t) { - case "circle": - this.shape = this._getCircleShape(distance); - break; - case "cone": - this.shape = this._getConeShape(direction, angle, distance); - break; - case "rect": - this.shape = this._getRectShape(direction, distance); - break; - case "ray": - this.shape = this._getRayShape(direction, distance, width); - } - - // Draw the Template outline - this.template.clear() - .lineStyle(this._borderThickness, this.borderColor, 0.75) - .beginFill(0x000000, 0.0); - - // Fill Color or Texture - if (this.texture) { - let mat = PIXI.Matrix.IDENTITY; - // rectangle - if (this.shape.width && this.shape.height) - mat.scale(this.shape.width / this.texture.width, this.shape.height / this.texture.height); - else if (this.shape.radius) { - mat.scale(this.shape.radius * 2 / this.texture.height, this.shape.radius * 2 / this.texture.width) - // Circle center is texture start... - mat.translate(-this.shape.radius, -this.shape.radius); - } else if (this.data.t === "ray") { - const d = canvas.dimensions, - height = this.data.width * d.size / d.distance, - width = this.data.distance * d.size / d.distance; - mat.scale(width / this.texture.width, height / this.texture.height); - mat.translate(0, -height * 0.5); - - mat.rotate(toRadians(this.data.direction)); - } else {// cone - const d = canvas.dimensions; - - // Extract and prepare data - let { direction, distance, angle } = this.data; - distance *= (d.size / d.distance); - direction = toRadians(direction); - const width = this.data.distance * d.size / d.distance; - - const angles = [(angle / -2), (angle / 2)]; - distance = distance / Math.cos(toRadians(angle / 2)); - - // Get the cone shape as a polygon - const rays = angles.map(a => Ray.fromAngle(0, 0, direction + toRadians(a), distance + 1)); - const height = Math.sqrt((rays[0].B.x - rays[1].B.x) * (rays[0].B.x - rays[1].B.x) - + (rays[0].B.y - rays[1].B.y) * (rays[0].B.y - rays[1].B.y)); - mat.scale(width / this.texture.width, height / this.texture.height); - mat.translate(0, -height / 2) - mat.rotate(toRadians(this.data.direction)); - } - this.template.beginTextureFill({ - texture: this.texture, - matrix: mat, - alpha: 1.0 - }); - // move into draw or so - const source = getProperty(this.texture, "baseTexture.resource.source") - if (source && (source.tagName === "VIDEO")) { - source.loop = true; - source.muted = true; - game.video.play(source); - } - } - else this.template.beginFill(0x000000, 0.0); - - // Draw the shape - this.template.drawShape(this.shape); - - // Draw origin and destination points - this.template.lineStyle(this._borderThickness, 0x000000) - .beginFill(0x000000, 0.5) - .drawCircle(0, 0, 6) - .drawCircle(this.ray.dx, this.ray.dy, 6); - - // Update visibility - this.controlIcon.visible = this.layer._active; - this.controlIcon.border.visible = this._hover; - - // Draw ruler text - this._refreshRulerText(); - return this; - } - } - return this; - }; -})(); diff --git a/tokenmagic/module/settings.js b/tokenmagic/module/settings.js index de2cdc0..095ef76 100644 --- a/tokenmagic/module/settings.js +++ b/tokenmagic/module/settings.js @@ -1,311 +1,577 @@ import { presets as defaultPresets, PresetsLibrary } from "../fx/presets/defaultpresets.js"; import { DataVersion } from "../migration/migration.js"; -import { TokenMagic } from './tokenmagic.js'; +import { TokenMagic, isVideoDisabled } from './tokenmagic.js'; import { AutoTemplateDND5E, dnd5eTemplates } from "./autoTemplate/dnd5e.js"; -import { defaultTemplateOnHover } from "./proto/DefaultTemplateOnHover.js"; import { defaultOpacity, emptyPreset } from './constants.js'; const Magic = TokenMagic(); export class TokenMagicSettings extends FormApplication { - static init() { - const menuAutoTemplateSettings = { - key: 'autoTemplateSettings', - config: { - name: game.i18n.localize('TMFX.settings.autoTemplateSettings.button.name'), - label: game.i18n.localize('TMFX.settings.autoTemplateSettings.button.label'), - hint: game.i18n.localize('TMFX.settings.autoTemplateSettings.button.hint'), - type: TokenMagicSettings, - restricted: true, - }, - }; - - const settingAutoTemplateSettings = { - key: 'autoTemplateSettings', - config: { - name: game.i18n.localize('TMFX.settings.autoTemplateSettings.name'), - hint: game.i18n.localize('TMFX.settings.autoTemplateSettings.hint'), - scope: 'world', - config: false, - default: {}, - type: Object, - }, - }; - - let hasAutoTemplates = false; - switch (game.system.id) { - case 'dnd5e': - hasAutoTemplates = true; - game.settings.registerMenu('tokenmagic', menuAutoTemplateSettings.key, menuAutoTemplateSettings.config); - game.settings.register( - 'tokenmagic', - settingAutoTemplateSettings.key, - mergeObject(settingAutoTemplateSettings.config, { - default: AutoTemplateDND5E.defaultConfiguration - }, true, true), - ); - break; - default: - break; - } - - game.settings.register('tokenmagic', 'autoTemplateEnabled', { - name: game.i18n.localize('TMFX.settings.autoTemplateEnabled.name'), - hint: game.i18n.localize('TMFX.settings.autoTemplateEnabled.hint'), - scope: 'client', - config: hasAutoTemplates, - default: hasAutoTemplates, - type: Boolean, - onChange: (value) => TokenMagicSettings.configureAutoTemplate(value), - }); - - game.settings.register('tokenmagic', 'defaultTemplateOnHover', { - name: game.i18n.localize('TMFX.settings.defaultTemplateOnHover.name'), - hint: game.i18n.localize('TMFX.settings.defaultTemplateOnHover.hint'), - scope: 'client', - config: true, - default: hasAutoTemplates, - type: Boolean, - onChange: (value) => TokenMagicSettings.configureDefaultTemplateOnHover(value), - }); - - game.settings.register("tokenmagic", "useAdditivePadding", { - name: game.i18n.localize("TMFX.settings.useMaxPadding.name"), - hint: game.i18n.localize("TMFX.settings.useMaxPadding.hint"), - scope: "world", - config: true, - default: false, - type: Boolean - }); - - game.settings.register("tokenmagic", "minPadding", { - name: game.i18n.localize("TMFX.settings.minPadding.name"), - hint: game.i18n.localize("TMFX.settings.minPadding.hint"), - scope: "world", - config: true, - default: 50, - type: Number - }); - - game.settings.register("tokenmagic", "fxPlayerPermission", { - name: game.i18n.localize("TMFX.settings.fxPlayerPermission.name"), - hint: game.i18n.localize("TMFX.settings.fxPlayerPermission.hint"), - scope: "world", - config: true, - default: false, - type: Boolean - }); - - game.settings.register("tokenmagic", "importOverwrite", { - name: game.i18n.localize("TMFX.settings.importOverwrite.name"), - hint: game.i18n.localize("TMFX.settings.importOverwrite.hint"), - scope: "world", - config: true, - default: false, - type: Boolean - }); - - game.settings.register("tokenmagic", "useZOrder", { - name: game.i18n.localize("TMFX.settings.useZOrder.name"), - hint: game.i18n.localize("TMFX.settings.useZOrder.hint"), - scope: "world", - config: true, - default: false, - type: Boolean - }); - - game.settings.register("tokenmagic", "disableAnimations", { - name: game.i18n.localize("TMFX.settings.disableAnimations.name"), - hint: game.i18n.localize("TMFX.settings.disableAnimations.hint"), - scope: "client", - config: true, - default: false, - type: Boolean - }); - - game.settings.register("tokenmagic", "disableCaching", { - name: game.i18n.localize("TMFX.settings.disableCaching.name"), - hint: game.i18n.localize("TMFX.settings.disableCaching.hint"), - scope: "client", - config: true, - default: false, - type: Boolean - }); - - game.settings.register("tokenmagic", "disableVideo", { - name: game.i18n.localize("TMFX.settings.disableVideo.name"), - hint: game.i18n.localize("TMFX.settings.disableVideo.hint"), - scope: "world", - config: true, - default: false, - type: Boolean - }); - - game.settings.register("tokenmagic", "presets", { - name: "Token Magic FX presets", - hint: "Token Magic FX presets", - scope: "world", - config: false, - default: defaultPresets, - type: Object - }); - - game.settings.register("tokenmagic", "migration", { - name: "TMFX Data Version", - hint: "TMFX Data Version", - scope: "world", - config: false, - default: DataVersion.ARCHAIC, - type: String - }); - - loadTemplates([ - 'modules/tokenmagic/templates/settings/settings.html', - 'modules/tokenmagic/templates/settings/dnd5e/categories.html', - 'modules/tokenmagic/templates/settings/dnd5e/overrides.html', - ]); - } - - static configureAutoTemplate(enabled = false) { - switch (game.system.id) { - case 'dnd5e': - dnd5eTemplates.configure(enabled); - break; - default: - break; - } - } - - static configureDefaultTemplateOnHover(enabled = false) { - defaultTemplateOnHover.configure(enabled); - } - - /** @override */ - static get defaultOptions() { - return { - ...super.defaultOptions, - template: 'modules/tokenmagic/templates/settings/settings.html', - height: 'auto', - title: game.i18n.localize('TMFX.settings.autoTemplateSettings.dialog.title'), - width: 600, - classes: ['tokenmagic', 'settings'], - tabs: [ - { - navSelector: '.tabs', - contentSelector: 'form', - initial: 'name', - }, - ], - submitOnClose: false, - }; - } - - constructor(object = {}, options) { - super(object, options); - } - - getSettingsData() { - let settingsData = { - 'autoTemplateEnable': game.settings.get('tokenmagic', 'autoTemplateEnabled'), - } - switch (game.system.id) { - case 'dnd5e': - settingsData['autoTemplateSettings'] = game.settings.get('tokenmagic', 'autoTemplateSettings'); - break; - default: - break; - } - return settingsData; - } - - /** @override */ - getData() { - let data = super.getData(); - data.hasAutoTemplates = false; - data.emptyPreset = emptyPreset; - switch (game.system.id) { - case 'dnd5e': - data.hasAutoTemplates = true - data.dmgTypes = CONFIG.DND5E.damageTypes; - data.templateTypes = CONFIG.MeasuredTemplate.types; - break; - default: - break; - } - data.presets = Magic.getPresets(PresetsLibrary.TEMPLATE).sort(function (a, b) { - if (a.name < b.name) return -1; - if (a.name > b.name) return 1; - return 0; - }); - data.system = { id: game.system.id, title: game.system.data.title }; - data.settings = this.getSettingsData(); - data.submitText = game.i18n.localize('TMFX.save'); - return data; - } - - /** @override */ - async _updateObject(_, formData) { - const data = expandObject(formData); - for (let [key, value] of Object.entries(data)) { - if (key == 'autoTemplateSettings' && value.overrides) { - const compacted = {}; - Object.values(value.overrides).forEach((val, idx) => compacted[idx] = val); - value.overrides = compacted; - } - await game.settings.set('tokenmagic', key, value); - } - } - - /** @override */ - activateListeners(html) { - super.activateListeners(html); - - html.find('button.add-override').click(this._onAddOverride.bind(this)); - html.find('button.remove-override').click(this._onRemoveOverride.bind(this)); - } - - async _onAddOverride(event) { - event.preventDefault(); - let idx = 0; - const entries = event.target.closest('div.tab').querySelectorAll('div.override-entry'); - const last = entries[entries.length - 1]; - if (last) { - idx = last.dataset.idx + 1; - } - let updateData = {} - updateData[`autoTemplateSettings.overrides.${idx}.target`] = ''; - updateData[`autoTemplateSettings.overrides.${idx}.opacity`] = defaultOpacity; - updateData[`autoTemplateSettings.overrides.${idx}.tint`] = null; - updateData[`autoTemplateSettings.overrides.${idx}.preset`] = emptyPreset; - updateData[`autoTemplateSettings.overrides.${idx}.texture`] = null; - await this._onSubmit(event, { updateData: updateData, preventClose: true }); - this.render(); - } - - async _onRemoveOverride(event) { - event.preventDefault(); - let idx = event.target.dataset.idx; - const el = event.target.closest(`div[data-idx="${idx}"]`); - if (!el) { - return true; - } - el.remove(); - await this._onSubmit(event, { preventClose: true }); - this.render(); - } + static init() { + const menuAutoTemplateSettings = { + key: 'autoTemplateSettings', + config: { + name: game.i18n.localize('TMFX.settings.autoTemplateSettings.button.name'), + label: game.i18n.localize('TMFX.settings.autoTemplateSettings.button.label'), + hint: game.i18n.localize('TMFX.settings.autoTemplateSettings.button.hint'), + type: TokenMagicSettings, + restricted: true, + }, + }; + + const settingAutoTemplateSettings = { + key: 'autoTemplateSettings', + config: { + name: game.i18n.localize('TMFX.settings.autoTemplateSettings.name'), + hint: game.i18n.localize('TMFX.settings.autoTemplateSettings.hint'), + scope: 'world', + config: false, + default: {}, + type: Object, + }, + }; + + let hasAutoTemplates = false; + switch (game.system.id) { + case 'dnd5e': + hasAutoTemplates = true; + game.settings.registerMenu('tokenmagic', menuAutoTemplateSettings.key, menuAutoTemplateSettings.config); + game.settings.register( + 'tokenmagic', + settingAutoTemplateSettings.key, + mergeObject(settingAutoTemplateSettings.config, { + default: AutoTemplateDND5E.defaultConfiguration + }, true, true), + ); + break; + default: + break; + } + + game.settings.register('tokenmagic', 'autoTemplateEnabled', { + name: game.i18n.localize('TMFX.settings.autoTemplateEnabled.name'), + hint: game.i18n.localize('TMFX.settings.autoTemplateEnabled.hint'), + scope: "world", + config: hasAutoTemplates, + default: hasAutoTemplates, + type: Boolean, + onChange: (value) => TokenMagicSettings.configureAutoTemplate(value), + }); + + game.settings.register('tokenmagic', 'defaultTemplateOnHover', { + name: game.i18n.localize('TMFX.settings.defaultTemplateOnHover.name'), + hint: game.i18n.localize('TMFX.settings.defaultTemplateOnHover.hint'), + scope: "world", + config: true, + default: hasAutoTemplates, + type: Boolean, + onChange: () => window.location.reload() + }); + + game.settings.register('tokenmagic', 'autohideTemplateElements', { + name: game.i18n.localize('TMFX.settings.autohideTemplateElements.name'), + hint: game.i18n.localize('TMFX.settings.autohideTemplateElements.hint'), + scope: 'world', + config: true, + default: true, + type: Boolean, + onChange: () => window.location.reload() + }); + + game.settings.register('tokenmagic', 'autoFPSEnabled', { + name: game.i18n.localize('TMFX.settings.autoFPS.name'), + hint: game.i18n.localize('TMFX.settings.autoFPS.hint'), + scope: 'client', + config: true, + default: false, + type: Boolean, + onChange: (value) => TokenMagicSettings.configureAutoFPS(value), + }); + + game.settings.register("tokenmagic", "useAdditivePadding", { + name: game.i18n.localize("TMFX.settings.useMaxPadding.name"), + hint: game.i18n.localize("TMFX.settings.useMaxPadding.hint"), + scope: "world", + config: true, + default: false, + type: Boolean + }); + + game.settings.register("tokenmagic", "minPadding", { + name: game.i18n.localize("TMFX.settings.minPadding.name"), + hint: game.i18n.localize("TMFX.settings.minPadding.hint"), + scope: "world", + config: true, + default: 50, + type: Number + }); + + game.settings.register("tokenmagic", "fxPlayerPermission", { + name: game.i18n.localize("TMFX.settings.fxPlayerPermission.name"), + hint: game.i18n.localize("TMFX.settings.fxPlayerPermission.hint"), + scope: "world", + config: true, + default: false, + type: Boolean + }); + + game.settings.register("tokenmagic", "importOverwrite", { + name: game.i18n.localize("TMFX.settings.importOverwrite.name"), + hint: game.i18n.localize("TMFX.settings.importOverwrite.hint"), + scope: "world", + config: true, + default: false, + type: Boolean + }); + + game.settings.register("tokenmagic", "useZOrder", { + name: game.i18n.localize("TMFX.settings.useZOrder.name"), + hint: game.i18n.localize("TMFX.settings.useZOrder.hint"), + scope: "world", + config: true, + default: false, + type: Boolean + }); + + game.settings.register("tokenmagic", "disableAnimations", { + name: game.i18n.localize("TMFX.settings.disableAnimations.name"), + hint: game.i18n.localize("TMFX.settings.disableAnimations.hint"), + scope: "client", + config: true, + default: false, + type: Boolean, + onChange: () => window.location.reload() + }); + + game.settings.register("tokenmagic", "disableCaching", { + name: game.i18n.localize("TMFX.settings.disableCaching.name"), + hint: game.i18n.localize("TMFX.settings.disableCaching.hint"), + scope: "client", + config: true, + default: false, + type: Boolean + }); + + game.settings.register("tokenmagic", "disableVideo", { + name: game.i18n.localize("TMFX.settings.disableVideo.name"), + hint: game.i18n.localize("TMFX.settings.disableVideo.hint"), + scope: "world", + config: true, + default: false, + type: Boolean, + onChange: () => window.location.reload() + }); + + game.settings.register("tokenmagic", "presets", { + name: "Token Magic FX presets", + hint: "Token Magic FX presets", + scope: "world", + config: false, + default: defaultPresets, + type: Object + }); + + game.settings.register("tokenmagic", "migration", { + name: "TMFX Data Version", + hint: "TMFX Data Version", + scope: "world", + config: false, + default: DataVersion.ARCHAIC, + type: String + }); + + loadTemplates([ + 'modules/tokenmagic/templates/settings/settings.html', + 'modules/tokenmagic/templates/settings/dnd5e/categories.html', + 'modules/tokenmagic/templates/settings/dnd5e/overrides.html', + ]); + } + + static configureAutoTemplate(enabled = false) { + switch (game.system.id) { + case 'dnd5e': + dnd5eTemplates.configure(enabled); + break; + default: + break; + } + } + + static configureAutoFPS(enabled = false) { + if (enabled) canvas.app.ticker.maxFPS = 0; + else canvas.app.ticker.maxFPS = game.settings.get("core", "maxFPS"); + } + + /** @override */ + static get defaultOptions() { + return { + ...super.defaultOptions, + template: 'modules/tokenmagic/templates/settings/settings.html', + height: 'auto', + title: game.i18n.localize('TMFX.settings.autoTemplateSettings.dialog.title'), + width: 600, + classes: ['tokenmagic', 'settings'], + tabs: [ + { + navSelector: '.tabs', + contentSelector: 'form', + initial: 'name', + }, + ], + submitOnClose: false, + }; + } + + constructor(object = {}, options) { + super(object, options); + } + + getSettingsData() { + let settingsData = { + 'autoTemplateEnable': game.settings.get('tokenmagic', 'autoTemplateEnabled'), + } + switch (game.system.id) { + case 'dnd5e': + settingsData['autoTemplateSettings'] = game.settings.get('tokenmagic', 'autoTemplateSettings'); + break; + default: + break; + } + return settingsData; + } + + /** @override */ + getData() { + let data = super.getData(); + data.hasAutoTemplates = false; + data.emptyPreset = emptyPreset; + switch (game.system.id) { + case 'dnd5e': + data.hasAutoTemplates = true + data.dmgTypes = CONFIG.DND5E.damageTypes; + data.templateTypes = CONFIG.MeasuredTemplate.types; + break; + default: + break; + } + data.presets = Magic.getPresets(PresetsLibrary.TEMPLATE).sort(function (a, b) { + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + data.system = { id: game.system.id, title: game.system.data.title }; + data.settings = this.getSettingsData(); + data.submitText = game.i18n.localize('TMFX.save'); + return data; + } + + /** @override */ + async _updateObject(_, formData) { + const data = expandObject(formData); + for (let [key, value] of Object.entries(data)) { + if (key == 'autoTemplateSettings' && value.overrides) { + const compacted = {}; + Object.values(value.overrides).forEach((val, idx) => compacted[idx] = val); + value.overrides = compacted; + } + await game.settings.set('tokenmagic', key, value); + } + } + + /** @override */ + activateListeners(html) { + super.activateListeners(html); + + html.find('button.add-override').click(this._onAddOverride.bind(this)); + html.find('button.remove-override').click(this._onRemoveOverride.bind(this)); + } + + async _onAddOverride(event) { + event.preventDefault(); + let idx = 0; + const entries = event.target.closest('div.tab').querySelectorAll('div.override-entry'); + const last = entries[entries.length - 1]; + if (last) { + idx = last.dataset.idx + 1; + } + let updateData = {} + updateData[`autoTemplateSettings.overrides.${idx}.target`] = ''; + updateData[`autoTemplateSettings.overrides.${idx}.opacity`] = defaultOpacity; + updateData[`autoTemplateSettings.overrides.${idx}.tint`] = null; + updateData[`autoTemplateSettings.overrides.${idx}.preset`] = emptyPreset; + updateData[`autoTemplateSettings.overrides.${idx}.texture`] = null; + await this._onSubmit(event, { updateData: updateData, preventClose: true }); + this.render(); + } + + async _onRemoveOverride(event) { + event.preventDefault(); + let idx = event.target.dataset.idx; + const el = event.target.closest(`div[data-idx="${idx}"]`); + if (!el) { + return true; + } + el.remove(); + await this._onSubmit(event, { preventClose: true }); + this.render(); + } } +Hooks.on("canvasReady", () => { + TokenMagicSettings.configureAutoFPS(game.settings.get('tokenmagic', 'autoFPSEnabled')); +}); + Hooks.once("init", () => { - // Extracted from https://github.com/leapfrogtechnology/just-handlebars-helpers/ - Handlebars.registerHelper('concat', function (...params) { - // Ignore the object appended by handlebars. - if (typeof params[params.length - 1] === 'object') { - params.pop(); - } - - return params.join(''); - }); - TokenMagicSettings.init(); - TokenMagicSettings.configureAutoTemplate(game.settings.get('tokenmagic', 'autoTemplateEnabled')); - TokenMagicSettings.configureDefaultTemplateOnHover(game.settings.get('tokenmagic', 'defaultTemplateOnHover')); -}); \ No newline at end of file + // Extracted from https://github.com/leapfrogtechnology/just-handlebars-helpers/ + Handlebars.registerHelper('concat', function (...params) { + // Ignore the object appended by handlebars. + if (typeof params[params.length - 1] === 'object') { + params.pop(); + } + + return params.join(''); + }); + TokenMagicSettings.init(); + TokenMagicSettings.configureAutoTemplate(game.settings.get('tokenmagic', 'autoTemplateEnabled')); + + const wrappedMTU = async function (wrapped, ...args) { + const [data] = args; + + const hasTexture = data.hasOwnProperty("texture"); + const hasPresetData = data.hasOwnProperty("tmfxPreset"); + if (hasPresetData && data.tmfxPreset !== emptyPreset) { + let defaultTexture = Magic._getPresetTemplateDefaultTexture(data.tmfxPreset); + if (!(defaultTexture == null)) { + if (data.texture === '' || data.texture.includes('/modules/tokenmagic/fx/assets/templates/')) + data.texture = defaultTexture; + } + + } else if (hasTexture && data.texture.includes('/modules/tokenmagic/fx/assets/templates/') + && hasPresetData && data.tmfxPreset === emptyPreset) { + data.texture = ''; + } + + return await wrapped(...args); + }; + + let wrappedMTR; + let wrappedMTRType; + + if (!isVideoDisabled()) { + wrappedMTRType = "OVERRIDE"; + wrappedMTR = function () { + if (this.template && !this.template._destroyed) { + // INTEGRATION FROM MESS + // THANKS TO MOERILL !! + let d = canvas.dimensions; + this.position.set(this.data.x, this.data.y); + + // Extract and prepare data + let { direction, distance, angle, width } = this.data; + distance *= (d.size / d.distance); + width *= (d.size / d.distance); + direction = toRadians(direction); + + // Create ray and bounding rectangle + this.ray = Ray.fromAngle(this.data.x, this.data.y, direction, distance); + + // Get the Template shape + switch (this.data.t) { + case "circle": + this.shape = this._getCircleShape(distance); + break; + case "cone": + this.shape = this._getConeShape(direction, angle, distance); + break; + case "rect": + this.shape = this._getRectShape(direction, distance); + break; + case "ray": + this.shape = this._getRayShape(direction, distance, width); + } + + // Draw the Template outline + this.template.clear() + .lineStyle(this._borderThickness, this.borderColor, 0.75) + .beginFill(0x000000, 0.0); + + // Fill Color or Texture + if (this.texture) { + let mat = PIXI.Matrix.IDENTITY; + // rectangle + if (this.shape.width && this.shape.height) + mat.scale(this.shape.width / this.texture.width, this.shape.height / this.texture.height); + else if (this.shape.radius) { + mat.scale(this.shape.radius * 2 / this.texture.height, this.shape.radius * 2 / this.texture.width) + // Circle center is texture start... + mat.translate(-this.shape.radius, -this.shape.radius); + } else if (this.data.t === "ray") { + const d = canvas.dimensions, + height = this.data.width * d.size / d.distance, + width = this.data.distance * d.size / d.distance; + mat.scale(width / this.texture.width, height / this.texture.height); + mat.translate(0, -height * 0.5); + + mat.rotate(toRadians(this.data.direction)); + } else {// cone + const d = canvas.dimensions; + + // Extract and prepare data + let { direction, distance, angle } = this.data; + distance *= (d.size / d.distance); + direction = toRadians(direction); + const width = this.data.distance * d.size / d.distance; + + const angles = [(angle / -2), (angle / 2)]; + distance = distance / Math.cos(toRadians(angle / 2)); + + // Get the cone shape as a polygon + const rays = angles.map(a => Ray.fromAngle(0, 0, direction + toRadians(a), distance + 1)); + const height = Math.sqrt((rays[0].B.x - rays[1].B.x) * (rays[0].B.x - rays[1].B.x) + + (rays[0].B.y - rays[1].B.y) * (rays[0].B.y - rays[1].B.y)); + mat.scale(width / this.texture.width, height / this.texture.height); + mat.translate(0, -height / 2) + mat.rotate(toRadians(this.data.direction)); + } + this.template.beginTextureFill({ + texture: this.texture, + matrix: mat, + alpha: 1.0 + }); + // move into draw or so + const source = getProperty(this.texture, "baseTexture.resource.source") + if (source && (source.tagName === "VIDEO")) { + source.loop = true; + source.muted = true; + game.video.play(source); + } + } + else this.template.beginFill(0x000000, 0.0); + + // Draw the shape + this.template.drawShape(this.shape); + + // Draw origin and destination points + this.template.lineStyle(this._borderThickness, 0x000000) + .beginFill(0x000000, 0.5) + .drawCircle(0, 0, 6) + .drawCircle(this.ray.dx, this.ray.dy, 6); + + // Update visibility + this.controlIcon.visible = this.layer._active; + this.controlIcon.border.visible = this._hover; + + // Draw ruler text + this._refreshRulerText(); + return this; + } + return this; + }; + } + + if (game.settings.get('tokenmagic', 'autohideTemplateElements')) { + const autohideTemplateElements = function (wrapped, ...args) { + // Save texture and border thickness + const texture = this.texture; + const borderThickness = this._borderThickness; + + // Hide template texture while moving + if (this._original || this.parent === canvas.templates.preview) { + this.texture = null; + } + + // Show border outline only on hover if the template is textured + if (this.texture && this.texture !== '' && !this._hover) { + this._borderThickness = 0; + } + + const retVal = wrapped(...args); + + // Restore texture and border thickness + this.texture = texture; + this._borderThickness = borderThickness; + + { + // Show the origin/destination points and ruler text only on hover or while creating but not while moving + const template = this._original ?? this; + const show = !this._original && (this._hover || this.parent === canvas.templates.preview); + + if (!show) { + // Hide origin and destination points, i.e., hide everything except the template shape + for (const data of template.template.geometry.graphicsData) { + if (data.shape !== template.shape) { + data.fillStyle.visible = false; + data.lineStyle.visible = false; + } + } + template.template.geometry.invalidate(); + } + + template.ruler.renderable = show; + + template.controlIcon.renderable = template.owner; + + if (template.handle) { + template.handle.renderable = template.owner; + } + } + + return retVal; + } + + if (wrappedMTR) { + const _wrappedMTR = wrappedMTR; + wrappedMTR = function() { + return autohideTemplateElements.call(this, _wrappedMTR.bind(this), ...arguments); + } + } else { + wrappedMTRType = "WRAPPER"; + wrappedMTR = autohideTemplateElements; + } + } + + if (game.settings.get('tokenmagic', 'defaultTemplateOnHover')) { + Hooks.on("canvasReady", () => { + canvas.stage.on("mousemove", event => { + const {x: mx, y: my} = event.data.getLocalPosition(canvas.templates); + for (const template of canvas.templates.placeables) { + const hl = canvas.grid.getHighlightLayer(`Template.${template.id}`); + const opacity = template.getFlag("tokenmagic", "templateData")?.opacity ?? 1; + if (template.texture && template.texture !== '') { + const {x: cx, y: cy} = template.center; + const mouseover = template.shape.contains(mx - cx, my - cy); + hl.renderable = mouseover; + template.template.alpha = (mouseover ? 0.5: 1.0) * opacity; + } else { + hl.renderable = true; + template.template.alpha = opacity; + } + } + }); + }); + } + + if (game.modules.get("lib-wrapper")?.active) { + libWrapper.register("tokenmagic", "MeasuredTemplate.prototype.update", wrappedMTU, "WRAPPER"); + + if (wrappedMTR) { + libWrapper.register("tokenmagic", "MeasuredTemplate.prototype.refresh", wrappedMTR, wrappedMTRType); + } + } else { + const cachedMTU = MeasuredTemplate.prototype.update; + MeasuredTemplate.prototype.update = function () { + return wrappedMTU.call(this, cachedMTU.bind(this), ...arguments); + }; + + if (wrappedMTR) { + if (wrappedMTRType && wrappedMTRType !== "OVERRIDE") { + const cachedMTR = MeasuredTemplate.prototype.refresh; + MeasuredTemplate.prototype.refresh = function () { + return wrappedMTR.call(this, cachedMTR.bind(this), ...arguments); + }; + } else { + MeasuredTemplate.prototype.refresh = wrappedMTR; + } + } + } +}); diff --git a/tokenmagic/module/tokenmagic.js b/tokenmagic/module/tokenmagic.js index bf76d89..9f60ca4 100644 --- a/tokenmagic/module/tokenmagic.js +++ b/tokenmagic/module/tokenmagic.js @@ -34,6 +34,7 @@ import { FilterTransform } from "../fx/filters/FilterTransform.js"; import { FilterSplash } from "../fx/filters/FilterSplash.js"; import { FilterPolymorph } from "../fx/filters/FilterPolymorph.js"; import { FilterXFire } from "../fx/filters/FilterXFire.js"; +import { FilterSprite } from "../fx/filters/FilterSprite.js"; import { Anime } from "../fx/Anime.js"; import { allPresets, PresetsLibrary } from "../fx/presets/defaultpresets.js"; import { tmfxDataMigration } from "../migration/migration.js"; @@ -88,6 +89,7 @@ export const FilterType = { splash: FilterSplash, polymorph: FilterPolymorph, xfire: FilterXFire, + sprite: FilterSprite, }; export const PlaceableType = { @@ -1314,31 +1316,35 @@ function initSocketListener() { }; function initFurnaceDrawingsException() { - if (isFurnaceDrawingsActive) { - DrawingConfig.prototype.refresh = (function () { - const cachedDCR = DrawingConfig.prototype.refresh; - return async function (html) { - - // Clear animations and filters if needed - let tmfxUpdate = false; - if (this.object.data.hasOwnProperty("flags") - && this.object.data.flags.hasOwnProperty("tokenmagic") - && this.object.data.flags.tokenmagic.hasOwnProperty("filters")) { - tmfxUpdate = true; - Anime.removeAnimation(this.object.id); - Magic._clearImgFiltersByPlaceable(this.object); - } + if (isFurnaceDrawingsActive()) { + const wrappedDCR = async function (wrapped, ...args) { + // Clear animations and filters if needed + let tmfxUpdate = false; + if (this.object.data.hasOwnProperty("flags") + && this.object.data.flags.hasOwnProperty("tokenmagic") + && this.object.data.flags.tokenmagic.hasOwnProperty("filters")) { + tmfxUpdate = true; + Anime.removeAnimation(this.object.id); + Magic._clearImgFiltersByPlaceable(this.object); + } - // Furnace function apply (updating data and full redraw : destruction/reconstruction) - cachedDCR.apply(this, arguments); + // Furnace function apply (updating data and full redraw : destruction/reconstruction) + await wrapped(...args); - // Reapply the filters if needed - if (tmfxUpdate) { - Magic._singleLoadFilters(this.object); - } + // Reapply the filters if needed + if (tmfxUpdate) { + Magic._singleLoadFilters(this.object); + } + }; + if (game.modules.get("lib-wrapper")?.active) { + libWrapper.register("tokenmagic", "FurnaceDrawingConfig.prototype.refresh", wrappedDCR, "WRAPPER"); + } else { + const cachedDCR = FurnaceDrawingConfig.prototype.refresh; + FurnaceDrawingConfig.prototype.refresh = function () { + return wrappedDCR.call(this, cachedDCR.bind(this), ...arguments); }; - })(); + } } } @@ -1474,6 +1480,9 @@ Hooks.on("ready", () => { initFurnaceDrawingsException(); window.TokenMagic = Magic; + if (!game.modules.get("lib-wrapper")?.active && game.user.isGM) + ui.notifications.warn("The 'Token Magic FX' module recommends to install and activate the 'libWrapper' module."); + Hooks.on("renderMeasuredTemplateConfig", onMeasuredTemplateConfig); });