From 56479bbb939df42a9eb1b04259825444f2d66637 Mon Sep 17 00:00:00 2001 From: Demur Rumed Date: Thu, 14 Oct 2021 03:58:47 +0000 Subject: [PATCH] Replace react-motion with in-tree Animation/Tween components --- package-lock.json | 53 ---- package.json | 1 - src/Components/index.js | 44 ---- src/rs/src/skill.rs | 2 +- src/views/Match.js | 549 ++++++++++++++++++++++++++-------------- 5 files changed, 357 insertions(+), 292 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a034383..4fa1bdc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "devDependencies": { "@babel/core": "^7.13.15", "@babel/preset-react": "^7.13.13", - "@serprex/react-motion": "^0.6.3", "@wasm-tool/wasm-pack-plugin": "^1.4.0", "babel-loader": "^8.2.2", "html-webpack-plugin": "^5.3.1", @@ -485,17 +484,6 @@ "node": ">=10.0.0" } }, - "node_modules/@serprex/react-motion": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@serprex/react-motion/-/react-motion-0.6.3.tgz", - "integrity": "sha512-TC5QpaMFVLTSKoWisGvUvavNvlnjeDlXvSGR+uNVZRXmj85DZWtL3S1oobbGTXOsbGCo3u2c/IM8cjmPegOIGQ==", - "dev": true, - "dependencies": { - "performance-now": "^2.1.0", - "prop-types": "^15.5.8", - "raf": "^3.1.0" - } - }, "node_modules/@types/eslint": { "version": "7.28.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.1.tgz", @@ -2075,12 +2063,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -2146,15 +2128,6 @@ "node": ">=6" } }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dev": true, - "dependencies": { - "performance-now": "^2.1.0" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3205,17 +3178,6 @@ "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", "dev": true }, - "@serprex/react-motion": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@serprex/react-motion/-/react-motion-0.6.3.tgz", - "integrity": "sha512-TC5QpaMFVLTSKoWisGvUvavNvlnjeDlXvSGR+uNVZRXmj85DZWtL3S1oobbGTXOsbGCo3u2c/IM8cjmPegOIGQ==", - "dev": true, - "requires": { - "performance-now": "^2.1.0", - "prop-types": "^15.5.8", - "raf": "^3.1.0" - } - }, "@types/eslint": { "version": "7.28.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.1.tgz", @@ -4470,12 +4432,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -4529,15 +4485,6 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dev": true, - "requires": { - "performance-now": "^2.1.0" - } - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", diff --git a/package.json b/package.json index facae8a4..7d524888 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "devDependencies": { "@babel/core": "^7.13.15", "@babel/preset-react": "^7.13.13", - "@serprex/react-motion": "^0.6.3", "@wasm-tool/wasm-pack-plugin": "^1.4.0", "babel-loader": "^8.2.2", "html-webpack-plugin": "^5.3.1", diff --git a/src/Components/index.js b/src/Components/index.js index e86d5aaa..626b46d8 100644 --- a/src/Components/index.js +++ b/src/Components/index.js @@ -22,50 +22,6 @@ export function Box(props) { ); } -export class OnDelay extends Component { - constructor(props) { - super(props); - this._timeout = 0; - } - - componentDidMount() { - this._timeout = setTimeout(() => { - if (this._timeout) { - this._timeout = 0; - if (this.props.onTimeout) { - this.props.onTimeout(); - } - } - }, this.props.ms); - } - - componentWillUnmount() { - if (this._timeout) { - clearTimeout(this._timeout); - this._timeout = 0; - } - } - - render() { - return this.props.children || null; - } -} - -export class Delay extends Component { - constructor(props) { - super(props); - this.state = { on: false }; - } - - render() { - return ( - this.setState({ on: true })}> - {this.state.on ? this.props.second : this.props.first} - - ); - } -} - export function CardImage(props) { const { card } = props, bgcol = ui.maybeLightenStr(card); diff --git a/src/rs/src/skill.rs b/src/rs/src/skill.rs index 1250ecc4..7a8c29de 100644 --- a/src/rs/src/skill.rs +++ b/src/rs/src/skill.rs @@ -2908,7 +2908,7 @@ impl Skill { }) { let inst = ctx.new_thing(card::As(ctx.get(c, Stat::card), newcard.code as i32), owner); - ctx.fx(inst, Fx::StartPos(inst)); + ctx.fx(inst, Fx::StartPos(c)); ctx.addPerm(owner, inst); } } diff --git a/src/views/Match.js b/src/views/Match.js index 028764ae..a117192e 100644 --- a/src/views/Match.js +++ b/src/views/Match.js @@ -1,6 +1,5 @@ import { Component } from 'react'; import { connect } from 'react-redux'; -import { Motion, TransitionMotion, spring } from '@serprex/react-motion'; import { playSound } from '../audio.js'; import * as ui from '../ui.js'; @@ -164,6 +163,102 @@ function activeText(c) { return aauto ? skillName(c, aauto) : ''; } +class Tween extends Component { + wait = false; + ms0 = 0; + _mounted = false; + + state = { + next: null, + state: null, + prev: null, + }; + + step = ts => { + if (this._mounted) { + this.setState( + state => { + const newstate = this.props.proc( + ts - this.ms0, + this.state.prev, + this.state.next, + ); + if (newstate !== this.state.next) { + requestAnimationFrame(this.step); + } + return { state: newstate, start: false }; + }, + () => { + this.wait = false; + }, + ); + } + }; + + static getDerivedStateFromProps(props, state) { + return !state.next + ? props.initial + ? { + next: props, + state: props.initial, + prev: props.initial, + start: true, + } + : { next: props, state: props, prev: props, start: true } + : (state.state && props.compare(state.state, props)) || + props.compare(state.next, props) + ? null + : { next: props, prev: state.state, start: true }; + } + + componentDidMount() { + this._mounted = true; + } + + componentWillUnmount() { + this._mounted = false; + } + + componentDidUpdate() { + if (!this.wait && this.state.start) { + this.ms0 = performance.now(); + this.wait = true; + requestAnimationFrame(this.step); + } + } + + render() { + return this.props.children(this.state.state); + } +} + +class Animation extends Component { + state = {}; + + step = ts => { + if (this._mounted) { + this.setState(state => + this.props.proc(ts - this.start, state, this.props), + ); + requestAnimationFrame(this.step); + } + }; + + componentDidMount() { + this.start = performance.now(); + this._mounted = true; + requestAnimationFrame(this.step); + } + + componentWillUnmount() { + this._mounted = false; + } + + render() { + return this.props.children(this.state); + } +} + function PagedModal(props) { return (
({ - key: `${id}`, - style: { - y: spring(540 - (props.spells.length - i) * 20), - opacity: spring(1), - }, - data: spell, - }))} - willEnter={item => ({ y: 0, opacity: 0 })} - willLeave={() => ({ y: spring(600), opacity: spring(0) })}> - {styles => ( - <> - {styles.map(item => { - const p1 = props.idtrack.get(item.data.t); - return ( - props.removeSpell(+item.key)}> - - {p1 && props.playByPlayMode !== 'noline' && ( - - )} - - ); - })} - - )} - - ); + return props.spells.map(({ id, spell, t }, i) => { + const p1 = props.idtrack.get(spell.t); + const y = 540 - (props.spells.length - i) * 20; + return ( + { + let yc, opacity; + if (ms < 50 * Math.PI) { + yc = y * Math.sin(ms / 100); + opacity = 1 - Math.cos(ms / 100); + } else if (ms > 1984) { + yc = y + ms - 1980; + opacity = 1 - (ms - 1980) / (600 - y); + } else { + yc = y; + opacity = 1; + } + if (yc > 600) { + props.removeSpell(id); + return null; + } + return { y: yc, opacity }; + }}> + {item => ( + <> + + {p1 && props.playByPlayMode !== 'noline' && ( + + )} + + )} + + ); + }); } function ArrowLine({ x0, y0, x1, y1, opacity }) { @@ -553,6 +651,143 @@ class ThingInst extends Component { } } +class Things extends Component { + constructor(props) { + super(props); + + this.state = { + things: new Set(props.things), + death: new Map(), + birth: new Map(), + }; + } + + static getDerivedStateFromProps(props, state) { + if ( + props.things.length === state.things.length && + props.things.every(id => state.things.has(id)) + ) { + return null; + } + const things = new Set(props.things), + death = new Map(state.death), + birth = new Map(state.birth); + for (const id of death.keys()) { + if (things.has(id)) { + death.delete(id); + } + } + for (const id of things) { + if (!state.things.has(id)) { + const start = props.startPos.get(id); + let pos; + if (start < 0) { + pos = { + x: 103, + y: -start === props.p1id ? 551 : 258, + }; + } else if (start) { + pos = { x: -99, y: -99, ...props.idtrack.get(start) }; + } + if (!pos) { + pos = ui.tgtToPos(props.game.byId(id), props.p1id); + } + if (pos) { + pos.opacity = 0; + birth.set(id, pos); + } + } + } + for (const id of state.things) { + if (!things.has(id) && props.game.has_id(id)) { + const endpos = props.endPos.get(id); + let pos; + if (endpos < 0) { + pos = { + x: 103, + y: -endpos === props.p1id ? 551 : 258, + }; + } else { + pos = props.idtrack.get(endpos || id); + } + if (pos) death.set(id, pos); + } + } + return { + things, + death, + birth, + }; + } + + makeThing(id, obj, pos, opacity) { + const props = this.props; + return ( + + prev.x === next.x && + prev.y === next.y && + prev.opacity === next.opacity + } + proc={(ms, prev, next) => { + if (ms > 96 * Math.PI) { + if (next.opacity === 0) { + const death = new Set(this.state.death), + birth = new Map(this.state.birth); + death.delete(id); + birth.delete(id); + this.setState({ death, birth }); + } + return next; + } + const pos = { + x: prev.x + (next.x - prev.x) * Math.sin(ms / 192), + y: prev.y + (next.y - prev.y) * Math.sin(ms / 192), + opacity: + prev.opacity + (next.opacity - prev.opacity) * Math.sin(ms / 192), + }; + props.idtrack.set(id, { x: pos.x, y: pos.y }); + return pos; + }}> + {pos => ( + + )} + + ); + } + + render() { + const props = this.props, + children = []; + for (const id of props.things) { + const obj = props.game.byId(id), + pos = ui.tgtToPos(obj, props.p1id); + children.push(this.makeThing(id, obj, pos, 1)); + } + for (const [id, pos] of this.state.death) { + const obj = props.game.byId(id); + children.push(this.makeThing(id, obj, pos, 0)); + } + return children; + } +} + function addNoHealData(game, newdata) { const dataNext = { ...game.data.dataNext, @@ -678,49 +913,43 @@ const MatchView = connect(({ user, opts, nav }) => ({ id, (pos = 0) => (offset = pos) + 16, ); - const pos = this.idtrack.get(id); + const pos = this.idtrack.get(id) ?? { x: -99, y: -99 }; + const y0 = pos.y + offset; const TextEffect = pos && ( - { - this.setState(state => { - const effects = new Set(state.effects); - effects.delete(TextEffect); - const st = { - fxTextPos: updateMap( - state.fxTextPos, - id, - pos => pos && pos - 16, - ), - effects, - }; - return onRest ? onRest(state, st) : st; - }); + proc={ms => { + if (ms > 360) { + this.setState(state => { + const effects = new Set(state.effects); + effects.delete(TextEffect); + const st = { + fxTextPos: updateMap( + state.fxTextPos, + id, + pos => pos && pos - 16, + ), + effects, + }; + return onRest ? onRest(state, st) : st; + }); + return null; + } + const yy = ms / 5; + return { + y: y0 + yy, + fade: 1 - Math.tan(yy / 91), + }; }}> - {pos => ( + {state => ( ({ }} /> )} - + ); return TextEffect; }; @@ -900,27 +1129,23 @@ const MatchView = connect(({ user, opts, nav }) => ({ const playerName = game.data.players[game.byId(id).getIndex()].name; const LastCardEffect = ( - } - second={ - { - this.setState(state => { - const effects = new Set(state.effects); - effects.delete(LastCardEffect); - return { effects }; - }); - }}> - {({ opacity }) => ( - - )} - - } - /> + proc={ms => { + if (ms > 864 * Math.PI) { + this.setState(state => { + const effects = new Set(state.effects); + effects.delete(LastCardEffect); + return { effects }; + }); + return null; + } + return { opacity: Math.min(Math.sin(ms / 864) * 1.25, 1) }; + }}> + {({ opacity }) => ( + + )} + ); newstate.effects.add(LastCardEffect); break; @@ -1335,7 +1560,6 @@ const MatchView = connect(({ user, opts, nav }) => ({ } componentWillUnmount() { - clearInterval(this.gameInterval); document.removeEventListener('keydown', this.onkeydown); window.removeEventListener('beforeunload', this.onbeforeunload); this.props.dispatch(store.setCmds({})); @@ -1614,7 +1838,18 @@ const MatchView = connect(({ user, opts, nav }) => ({ : '' }`; children.push( - + prev.x1 === next.x1 && prev.x2 === next.x2} + proc={(ms, prev, next) => { + if (ms > 96 * Math.PI) return next; + return { + x1: prev.x1 + (next.x1 - prev.x1) * Math.sin(ms / 192), + x2: prev.x2 + (next.x2 - prev.x2) * Math.sin(ms / 192), + }; + }}> {({ x1, x2 }) => ( <>
({ )} )} - , + , ({ ) )} {children} - { - const obj = game.byId(id), - pos = ui.tgtToPos(obj, player1.id), - style = { opacity: spring(1) }; - if (pos) { - style.x = spring(pos.x); - style.y = spring(pos.y); - } - - return { - key: `${id}`, - style, - data: id, - }; - })} - willEnter={item => { - const startpos = this.state.startPos.get(item.data); - let pos; - if (startpos < 0) { - pos = { - x: 103, - y: -startpos === player1.id ? 551 : 258, - }; - } else if (startpos) { - pos = this.idtrack.get(startpos); - } - if (!pos) { - pos = { x: item.style.x?.val ?? 0, y: item.style.y?.val ?? 0 }; - } - - return { - opacity: 0, - ...pos, - }; - }} - willLeave={item => { - if (!game.has_id(item.data)) return null; - const endpos = this.state.endPos.get(item.data); - let pos; - if (endpos < 0) { - pos = { - x: 103, - y: -endpos === player1.id ? 551 : 258, - }; - } else { - pos = this.idtrack.get(endpos || item.data); - } - - return pos - ? { - x: spring(pos.x), - y: spring(pos.y), - opacity: spring(0), - } - : null; - }}> - {interpStyles => ( - <> - {interpStyles.map(item => { - if ( - Number.isFinite(item.style.x) && - Number.isFinite(item.style.y) - ) { - this.idtrack.set(item.data, item.style); - } - return ( - - ); - })} - - )} - + {game.game.has_flooding() && floodsvg}