From 1166e7692845716740ca1f281d825274664a588c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 18 May 2021 10:28:15 +0100 Subject: [PATCH 1/4] Extract and deduplicate chat effect interfaces --- src/effects/effect.ts | 43 +++++++++++++++++++++++ src/effects/index.ts | 79 +++---------------------------------------- 2 files changed, 48 insertions(+), 74 deletions(-) create mode 100644 src/effects/effect.ts diff --git a/src/effects/effect.ts b/src/effects/effect.ts new file mode 100644 index 0000000000..9011b07b61 --- /dev/null +++ b/src/effects/effect.ts @@ -0,0 +1,43 @@ +/* + Copyright 2020 Nurjin Jafar + Copyright 2020 Nordeck IT + Consulting GmbH. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +export type Effect = { + /** + * one or more emojis that will trigger this effect + */ + emojis: Array; + /** + * the matrix message type that will trigger this effect + */ + msgType: string; + /** + * the room command to trigger this effect + */ + command: string; + /** + * a function that returns the translated description of the effect + */ + description: () => string; + /** + * a function that returns the translated fallback message. this message will be shown if the user did not provide a custom message + */ + fallbackMessage: () => string; + /** + * animation options + */ + options: TOptions; +} diff --git a/src/effects/index.ts b/src/effects/index.ts index a22948ebcf..1a6858be08 100644 --- a/src/effects/index.ts +++ b/src/effects/index.ts @@ -15,80 +15,11 @@ limitations under the License. */ import { _t, _td } from "../languageHandler"; - -export type Effect = { - /** - * one or more emojis that will trigger this effect - */ - emojis: Array; - /** - * the matrix message type that will trigger this effect - */ - msgType: string; - /** - * the room command to trigger this effect - */ - command: string; - /** - * a function that returns the translated description of the effect - */ - description: () => string; - /** - * a function that returns the translated fallback message. this message will be shown if the user did not provide a custom message - */ - fallbackMessage: () => string; - /** - * animation options - */ - options: TOptions; -} - -type ConfettiOptions = { - /** - * max confetti count - */ - maxCount: number; - /** - * particle animation speed - */ - speed: number; - /** - * the confetti animation frame interval in milliseconds - */ - frameInterval: number; - /** - * the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) - */ - alpha: number; - /** - * use gradient instead of solid particle color - */ - gradient: boolean; -}; -type FireworksOptions = { - /** - * max fireworks count - */ - maxCount: number; - /** - * gravity value that firework adds to shift from it's start position - */ - gravity: number; -} -type SnowfallOptions = { - /** - * The maximum number of snowflakes to render at a given time - */ - maxCount: number; - /** - * The amount of gravity to apply to the snowflakes - */ - gravity: number; - /** - * The amount of drift (horizontal sway) to apply to the snowflakes. Each snowflake varies. - */ - maxDrift: number; -} +import { ConfettiOptions } from "./confetti"; +import { Effect } from "./effect"; +import { FireworksOptions } from "./fireworks"; +import { SnowfallOptions } from "./snowfall"; +import { SpaceInvadersOptions } from "./spaceinvaders"; /** * This configuration defines room effects that can be triggered by custom message types and emojis From afd984372200c75a9a5787db4dc706ed18c2582a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 18 May 2021 10:28:37 +0100 Subject: [PATCH 2/4] Fix broken string interpolation in chat effects warning --- src/components/views/elements/EffectsOverlay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/EffectsOverlay.tsx b/src/components/views/elements/EffectsOverlay.tsx index 38be8da9a8..7bed0222b0 100644 --- a/src/components/views/elements/EffectsOverlay.tsx +++ b/src/components/views/elements/EffectsOverlay.tsx @@ -37,7 +37,7 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { effect = new Effect(options); effectsRef.current[name] = effect; } catch (err) { - console.warn('Unable to load effect module at \'../../../effects/${name}\'.', err); + console.warn(`Unable to load effect module at '../../../effects/${name}.`, err); } } return effect; From b5a612cd0fb0a50ecf6f831cffc0a8e0678d03ce Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 18 May 2021 10:29:00 +0100 Subject: [PATCH 3/4] Add space invaders chat effect --- src/effects/index.ts | 11 +++ src/effects/spaceinvaders/index.ts | 119 +++++++++++++++++++++++++++++ src/i18n/strings/en_EN.json | 2 + 3 files changed, 132 insertions(+) create mode 100644 src/effects/spaceinvaders/index.ts diff --git a/src/effects/index.ts b/src/effects/index.ts index 1a6858be08..8ecb80020d 100644 --- a/src/effects/index.ts +++ b/src/effects/index.ts @@ -62,6 +62,17 @@ export const CHAT_EFFECTS: Array> = [ maxDrift: 5, }, } as Effect, + { + emojis: ["👾", "🌌"], + msgType: "io.element.effects.space_invaders", + command: "spaceinvaders", + description: () => _td("Sends the given message with a space themed effect"), + fallbackMessage: () => _t("sends space invaders") + " 👾", + options: { + maxCount: 50, + gravity: 0.01, + }, + } as Effect, ]; diff --git a/src/effects/spaceinvaders/index.ts b/src/effects/spaceinvaders/index.ts new file mode 100644 index 0000000000..55eb6dc29f --- /dev/null +++ b/src/effects/spaceinvaders/index.ts @@ -0,0 +1,119 @@ +/* + Copyright 2021 The Matrix.org Foundation C.I.C. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import ICanvasEffect from '../ICanvasEffect'; +import { arrayFastClone } from "../../utils/arrays"; + +export type SpaceInvadersOptions = { + /** + * The maximum number of invaders to render at a given time + */ + maxCount: number; + /** + * The amount of gravity to apply to the invaders + */ + gravity: number; +} + +type Invader = { + x: number; + y: number; + xCol: number; + gravity: number; +} + +export const DefaultOptions: SpaceInvadersOptions = { + maxCount: 50, + gravity: 0.005, +}; + +const KEY_FRAME_INTERVAL = 15; // 15ms, roughly +const GLYPH = "👾"; + +export default class SpaceInvaders implements ICanvasEffect { + private readonly options: SpaceInvadersOptions; + + constructor(options: { [key: string]: any }) { + this.options = {...DefaultOptions, ...options}; + } + + private context: CanvasRenderingContext2D | null = null; + private particles: Array = []; + private lastAnimationTime: number; + + public isRunning: boolean; + + public start = async (canvas: HTMLCanvasElement, timeout = 3000) => { + if (!canvas) { + return; + } + this.context = canvas.getContext('2d'); + this.particles = []; + const count = this.options.maxCount; + while (this.particles.length < count) { + this.particles.push(this.resetParticle({} as Invader, canvas.width, canvas.height)); + } + this.isRunning = true; + requestAnimationFrame(this.renderLoop); + if (timeout) { + window.setTimeout(this.stop, timeout); + } + } + + public stop = async () => { + this.isRunning = false; + } + + private resetParticle = (particle: Invader, width: number, height: number): Invader => { + particle.x = Math.random() * width; + particle.y = Math.random() * -height; + particle.xCol = particle.x; + particle.gravity = this.options.gravity + (Math.random() * 6) + 4; + return particle; + } + + private renderLoop = (): void => { + if (!this.context || !this.context.canvas) { + return; + } + if (this.particles.length === 0) { + this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); + } else { + const timeDelta = Date.now() - this.lastAnimationTime; + if (timeDelta >= KEY_FRAME_INTERVAL || !this.lastAnimationTime) { + // Clear the screen first + this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); + + this.lastAnimationTime = Date.now(); + this.animateAndRenderSnowflakes(); + } + requestAnimationFrame(this.renderLoop); + } + }; + + private animateAndRenderSnowflakes() { + if (!this.context || !this.context.canvas) { + return; + } + this.context.font = "50px Twemoji"; + for (const particle of arrayFastClone(this.particles)) { + particle.y += particle.gravity; + + this.context.save(); + this.context.fillText(GLYPH, particle.x, particle.y); + this.context.restore(); + } + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9d5e17ba2d..5e0cd4c47c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -884,6 +884,8 @@ "sends fireworks": "sends fireworks", "Sends the given message with snowfall": "Sends the given message with snowfall", "sends snowfall": "sends snowfall", + "Sends the given message with a space themed effect": "Sends the given message with a space themed effect", + "sends space invaders": "sends space invaders", "unknown person": "unknown person", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consulting with %(transferTarget)s. Transfer to %(transferee)s", "You held the call Switch": "You held the call Switch", From 177adb9684f7bd15aa59aa3a558204e4773cb881 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 18 May 2021 11:58:15 +0100 Subject: [PATCH 4/4] fix copy-pasta --- src/effects/spaceinvaders/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/effects/spaceinvaders/index.ts b/src/effects/spaceinvaders/index.ts index 55eb6dc29f..9520e8366a 100644 --- a/src/effects/spaceinvaders/index.ts +++ b/src/effects/spaceinvaders/index.ts @@ -97,13 +97,13 @@ export default class SpaceInvaders implements ICanvasEffect { this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); this.lastAnimationTime = Date.now(); - this.animateAndRenderSnowflakes(); + this.animateAndRenderInvaders(); } requestAnimationFrame(this.renderLoop); } }; - private animateAndRenderSnowflakes() { + private animateAndRenderInvaders() { if (!this.context || !this.context.canvas) { return; }