diff --git a/client/animations.html b/client/animations.html index 92e3f98e..584488c7 100644 --- a/client/animations.html +++ b/client/animations.html @@ -9,6 +9,7 @@ + diff --git a/client/animations.test.js b/client/animations.test.js index 670eab45..11f0a948 100644 --- a/client/animations.test.js +++ b/client/animations.test.js @@ -3,7 +3,6 @@ require('./assets/styles/styles.mobile.css'); require('./assets/styles/instance.css'); require('./assets/styles/instance.mobile.css'); require('./assets/styles/game.css'); -require('./assets/styles/anims.css'); // kick it off -require('./src/animations.test'); +require('./src/animations.test.jsx'); diff --git a/client/assets/styles/game.css b/client/assets/styles/game.css index 7f1e4d97..53107eae 100644 --- a/client/assets/styles/game.css +++ b/client/assets/styles/game.css @@ -72,7 +72,7 @@ "name " "stats "; - transition-property: all; + transition-property: translate; transition-duration: 0.25s; transition-delay: 0; transition-timing-function: ease; @@ -186,8 +186,8 @@ .game-construct.ko { animation: none; opacity: 0.35; - filter: grayscale(100%); -} +/* filter: grayscale(100%); +*/} .game-construct.ko button:hover { color: #333; @@ -195,12 +195,12 @@ .game-construct.unfocus { opacity: 0.65; - filter: blur(5px); -} +/* filter: blur(5px); +*/} .game-construct.unfocus.ko { - filter: blur(5px) grayscale(100%); -} +/* filter: blur(5px) grayscale(100%); +*/} .combat-text { font-size: 2em; @@ -227,12 +227,12 @@ } .game-construct.active-skill { - filter: drop-shadow(0 0 0.2em silver); -} +/* filter: drop-shadow(0 0 0.2em silver); +*/} .game-construct.red-damage { - filter: drop-shadow(0 0 0.2em red); - color: red; +/* filter: drop-shadow(0 0 0.2em red); +*/ color: red; /*ensure construct doesn't get opacity lowered because of being KO before the KO animation*/ opacity: 1; @@ -253,8 +253,8 @@ } .game-construct.blue-damage { - filter: drop-shadow(0 0 0.2em blue); - color: blue; +/* filter: drop-shadow(0 0 0.2em blue); +*/ color: blue; opacity: 1; /*border-color: blue;*/ } @@ -273,8 +273,8 @@ } .game-construct.green-damage { - filter: drop-shadow(0 0 0.2em green); - color: green; +/* filter: drop-shadow(0 0 0.2em green); +*/ color: green; opacity: 1; /*border-color: green;*/ } @@ -293,8 +293,8 @@ } .game-construct.purple-damage { - filter: drop-shadow(0 0 0.2em purple); - color: purple; +/* filter: drop-shadow(0 0 0.2em purple); +*/ color: purple; border-color: purple; } diff --git a/client/assets/styles/styles.css b/client/assets/styles/styles.css index 311a781a..7ec37699 100644 --- a/client/assets/styles/styles.css +++ b/client/assets/styles/styles.css @@ -80,6 +80,7 @@ nav { grid-area: nav; padding-left: 2em; margin-right: 2em; + max-height: 100%; } nav h2:first-child { diff --git a/client/src/animations.socket.jsx b/client/src/animations.socket.jsx new file mode 100644 index 00000000..a1125f6c --- /dev/null +++ b/client/src/animations.socket.jsx @@ -0,0 +1,119 @@ +const cbor = require('borc'); + +const toast = require('izitoast'); +const eachSeries = require('async/eachSeries'); + +const actions = require('./actions'); +const { TIMES } = require('./constants'); +const { getCombatSequence } = require('./utils'); + +const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://mnml.gg/api/ws' : 'ws://localhost:40000/api/ws'; + +function createSocket(store) { + let ws = null; + // ------------- + // Outgoing + // ------------- + function send(msg) { + if (msg.method !== 'ping') console.log('outgoing msg', msg); + ws.send(cbor.encode(msg)); + } + + function sendDevResolve(a, b, skill) { + send({ method: 'dev_game_resolve', params: { a, b, skill } }); + } + + function onDevResolutions(newRes) { + const { game: currentGame } = store.getState(); + return eachSeries(newRes, (r, cb) => { + if (['Disable', 'TargetKo'].includes(r.event[0])) return cb(); + // Create sub events for combat animations + const sequence = getCombatSequence(r); + return eachSeries(sequence, (stage, sCb) => { + const { skip } = store.getState(); + if (skip) return sCb('skip'); + const stagedR = Object.create(r); + stagedR.stage = stage; + store.dispatch(actions.setResolution(stagedR)); + + return setTimeout(sCb, TIMES[stage]); + }, err => { + if (err) console.error(err); + // Finished this resolution + return cb(); + }); + }, err => { + if (err) return console.error(err); + store.dispatch(actions.setResolution(null)); + // stop skipping resolutions + store.dispatch(actions.setSkip(false)); + // update the game + store.dispatch(actions.setGame(currentGame)); + // get the latest state and restart polling + return true; + }); + } + + const handlers = { + DevResolutions: onDevResolutions, + }; + + // decodes the cbor and + // calls the handlers defined above based on message type + function onMessage(event) { + // decode binary msg from server + const blob = new Uint8Array(event.data); + const res = cbor.decode(blob); + const [msgType, params] = res; + return handlers[msgType](params); + } + + // Connection opened + function onOpen() { + toast.info({ + message: 'connected', + position: 'topRight', + }); + return true; + } + + function onError(event) { + console.error('WebSocket error', event); + } + + function onClose(event) { + console.error('WebSocket closed', event); + toast.warning({ + message: 'disconnected', + position: 'topRight', + }); + return setTimeout(connect, 5000); + } + + function connect() { + if (ws) { + ws.removeEventListener('open', onOpen); + ws.removeEventListener('message', onMessage); + ws.removeEventListener('error', onError); + ws.removeEventListener('close', onClose); + ws = null; + } + + ws = new WebSocket(SOCKET_URL); + ws.binaryType = 'arraybuffer'; + + // Listen for messages + ws.addEventListener('open', onOpen); + ws.addEventListener('message', onMessage); + ws.addEventListener('error', onError); + ws.addEventListener('close', onClose); + return ws; + } + + return { + sendDevResolve, + connect, + }; +} + +module.exports = createSocket; diff --git a/client/src/animations.test.js b/client/src/animations.test.js deleted file mode 100644 index bc36db15..00000000 --- a/client/src/animations.test.js +++ /dev/null @@ -1,16 +0,0 @@ -const preact = require('preact'); - -const TrippyTriangle = require('./components/svgs/trippy.triangle'); -const Amplify = require('./components/svgs/amplify'); -const Hex = require('./components/svgs/hex'); - -const Animations = () => ( -
- - - -
-); - -// eslint-disable-next-line -preact.render(, document.body); diff --git a/client/src/animations.test.jsx b/client/src/animations.test.jsx new file mode 100644 index 00000000..fc207e40 --- /dev/null +++ b/client/src/animations.test.jsx @@ -0,0 +1,117 @@ +const preact = require('preact'); +const { Provider } = require('preact-redux'); +const { createStore, combineReducers } = require('redux'); + +const reducers = require('./reducers'); +const actions = require('./actions'); +const createSocket = require('./animations.socket'); + +// const TrippyTriangle = require('./components/svgs/trippy.triangle'); +// const Amplify = require('./components/svgs/amplify'); +// const Hex = require('./components/svgs/hex'); +const Game = require('./components/game'); +const testGameBuilder = require('./test.game'); + +const testGame = testGameBuilder('8552e0bf-340d-4fc8-b6fc-cccccccccccc'); +const testAccount = { + id: '8552e0bf-340d-4fc8-b6fc-cccccccccccc', + name: 'ntr', +}; + +// Redux Store +const store = createStore( + combineReducers(reducers) +); + +store.subscribe(() => console.log(store.getState())); +store.dispatch(actions.setAccount(testAccount)); +store.dispatch(actions.setGame(testGame)); + +function animationsNav(ws) { + function useSkill(skill) { + const ateam = Math.round(Math.random()); + const bteam = Math.round(Math.random()); + const acon = Math.floor(Math.random() * 3); + const bcon = Math.floor(Math.random() * 3); + + const a = testGame.players[ateam].constructs[acon].id; + const b = testGame.players[bteam].constructs[bcon].id; + + return ws.sendDevResolve(a, b, skill); + } + + return SKILLS.map((s, i) => ( + + )); +} + +document.fonts.load('16pt "Jura"').then(() => { + const ws = createSocket(store); + ws.connect(); + + const Animations = () => ( + +
+ + +
+
+ ); + + // eslint-disable-next-line + preact.render(, document.body); +}); + +const SKILLS = [ + 'Attack', + 'Debuff', + 'Buff', + 'Block', + 'Stun', + 'AmplifyI', + 'BanishI', + 'BashI', + 'BlastI', + 'ChaosI', + 'ClutchI', + 'CorruptI', + 'CorruptionI', + 'CorruptionTickI', + 'CurseI', + 'DecayI', + 'DecayTickI', + 'HasteI', + 'HasteStrike', + 'HealI', + 'HexI', + 'HatredI', + 'HostilityI', + 'ImpureBlast', + 'ImpurityI', + 'InvertI', + 'ParryI', + 'PurgeI', + 'PurifyI', + 'RechargeI', + 'ReflectI', + 'RiposteI', + 'RuinI', + 'ScatterI', + 'SilenceI', + 'SiphonI', + 'SiphonTickI', + 'SlayI', + 'SleepI', + 'SnareI', + 'StrikeI', + 'TauntI', + 'ThrowI', + 'TriageI', + 'TriageTickI', +]; diff --git a/client/src/components/anims/siphon.tick.jsx b/client/src/components/anims/siphon.tick.jsx index 8361c5c9..f57c294d 100644 --- a/client/src/components/anims/siphon.tick.jsx +++ b/client/src/components/anims/siphon.tick.jsx @@ -64,11 +64,11 @@ class SiphonTick extends Component { componentDidMount() { if (!this.props.team) { anime.set('.skill-anim', { - rotate: Math.random() * 180 + 90, + rotate: 90 }); } else { anime.set('.skill-anim', { - rotate: Math.random() * 180 + 270, + rotate: 270 }); } diff --git a/server/src/rpc.rs b/server/src/rpc.rs index c85d646b..f45be3da 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -13,7 +13,7 @@ use ws::{MnmlSocket}; use construct::{Construct, construct_spawn, construct_delete}; use game::{Game, game_state, game_skill, game_ready}; use account::{Account, account_constructs, account_instances}; -use skill::{Skill}; +use skill::{Skill, dev_resolve, Resolutions}; use instance::{Instance, instance_state, instance_list, instance_new, instance_ready, instance_join}; use vbox::{vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip}; use item::{Item, ItemInfoCtr, item_info}; @@ -39,6 +39,7 @@ pub fn receive(data: Vec, db: &Db, _client: &mut MnmlWs, begin: Instant, acc // if no auth required match v.method.as_ref() { "item_info" => return Ok(RpcResult::ItemInfo(item_info())), + "dev_game_resolve" => return handle_dev_resolve(data), _ => match account { Some(_) => (), None => return Err(err_msg("auth required")), @@ -209,6 +210,11 @@ fn handle_vbox_unequip(data: Vec, tx: &mut Transaction, account: &Account) - Ok(RpcResult::InstanceState(vbox_unequip(msg.params, tx, &account)?)) } +fn handle_dev_resolve(data: Vec) -> Result { + let msg = from_slice::(&data).or(Err(err_msg("invalid params")))?; + Ok(RpcResult::DevResolutions(dev_resolve(msg.params.a, msg.params.b, msg.params.skill))) +} + #[derive(Debug,Clone,Serialize,Deserialize)] pub struct RpcErrorResponse { pub err: String @@ -226,6 +232,8 @@ pub enum RpcResult { InstanceState(Instance), Pong(()), + + DevResolutions(Resolutions), } #[derive(Debug,Clone,Serialize,Deserialize)] @@ -434,6 +442,19 @@ pub struct VboxReclaimParams { pub index: usize, } +#[derive(Debug,Clone,Serialize,Deserialize)] +struct DevResolveMsg { + method: String, + params: DevResolveParams, +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct DevResolveParams { + pub a: Uuid, + pub b: Uuid, + pub skill: Skill, +} + // #[cfg(test)] // mod tests { // use super::*; diff --git a/server/src/skill.rs b/server/src/skill.rs index 22f222f8..c16c944d 100644 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -8,6 +8,14 @@ use item::{Item}; use game::{Game}; use effect::{Effect, Colour, Cooldown}; +pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) -> Resolutions { + let mut a = Construct::new(); + a.id = a_id; + let mut b = Construct::new(); + b.id = b_id; + return resolve(skill, &mut a, &mut b, vec![]); +} + pub fn resolution_steps(cast: &Cast, game: &mut Game) -> Resolutions { let mut resolutions = vec![]; @@ -464,7 +472,7 @@ pub enum Event { Evasion { skill: Skill, evasion_rating: u64 }, } -type Resolutions = Vec; +pub type Resolutions = Vec; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Skill {