Merge branch 'release/1.6.0'

This commit is contained in:
ntr 2019-10-18 17:40:31 +11:00
commit 0b55484c14
36 changed files with 601 additions and 74 deletions

View File

@ -2,6 +2,40 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## [1.6.0] - 2019-10-18
### Added
- Subscriber chat!
### Changed
- Made available skill / effect information during the combat phase.
- Highlighting a skill replace the effect area with the skill description including speed multiplier.
- Highlighting an effect will replace the targetting arrow / anim skill text with effect info.
- You can now preview combinations before you create them
- After selecting the three items for a combo hover over the combine button for info
- Damage formula for Slay and Siphon reworked
- Slay now deals red damage based RedPower and GreenPower. Previously only based on RedPower.
- Siphon now deals blue damage based BluePower and GreenPower. Previously only based on BluePower.
### Fixed
- Matchmaking bug where server matches you with yourself
## [1.5.6] - 2019-10-17
We've updated the UI during the vbox / buy phase to give a better indication of valid actions.
### Changed
- Borders for skill combo's represent the base colours.
- Heal (GG) has a green border, Siphon (BG) has an alternating blue / green border etc.
- Borders are shown for items in inventory and as equipped skills during both phases.
- Improvements to making item combo's
- If you select an item in your inventory it will now highlight other items that are valid for combining.
- This includes items that can be bought and in your inventory.
- Improved the indicator for where to click for equipping and buying items where its valid.
- Now slowly flashes between black and grey, previously changed the border once.
## [1.5.5] - 2019-10-15 ## [1.5.5] - 2019-10-15
### Changed ### Changed
* Purge * Purge

View File

@ -1 +1 @@
1.5.6 1.6.0

View File

@ -3,10 +3,7 @@
*PRODUCTION* *PRODUCTION*
* border colours for skills e.g. strike red border, slay half red half green
* rename vbox to shop * rename vbox to shop
* combat phase info system
* drag and drop buy / equip / unequip items
* mobile styles * mobile styles
* mobile info page * mobile info page
@ -16,6 +13,11 @@
* can't reset password without knowing password =\ * can't reset password without knowing password =\
* Invert recharge * Invert recharge
* serde serialize privatise
* chat
* Convert spec 'Plus' -> '+' when it appears as combo text in combiner and in info text
## SOON ## SOON
* equip from shop (buy and equip without putting in your inventory) for bases * equip from shop (buy and equip without putting in your inventory) for bases
@ -23,7 +25,6 @@
* bot game grind * bot game grind
* ACP * ACP
* essential * essential
* serde serialize privatise
* msg pane / chatwheel * msg pane / chatwheel
* audio * audio
* treats * treats
@ -71,6 +72,7 @@ $$$
* Highlight (dota) colour * Highlight (dota) colour
* fx colours + styles * fx colours + styles
* ??? (PROBS NOT) drag and drop buy / equip / unequip items ???
* modules * modules
* troll life -> dmg * troll life -> dmg
* prince of peace * prince of peace

View File

@ -1,6 +1,6 @@
{ {
"name": "mnml-client", "name": "mnml-client",
"version": "1.5.6", "version": "1.6.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -132,10 +132,16 @@ aside {
flex: 0; flex: 0;
} }
.ready, .quit { .ready {
flex: 1; flex: 1;
font-size: 200%; font-size: 200%;
} }
.quit {
flex: 1;
font-size: 200%;
animation: co 0.75s cubic-bezier(0, 0, 1, 1) 0s infinite alternate;
}
} }
.abandon:not([disabled]) { .abandon:not([disabled]) {

View File

@ -3,6 +3,7 @@ footer {
flex-flow: row wrap; flex-flow: row wrap;
grid-area: footer; grid-area: footer;
margin: 0; margin: 0;
z-index: 10;
button { button {
margin: 0; margin: 0;

View File

@ -123,7 +123,6 @@
button { button {
width: 100%; width: 100%;
height: 2em; height: 2em;
height: 25%;
margin-right: 1em; margin-right: 1em;
} }
button.active { button.active {
@ -204,6 +203,7 @@
.resolving-skill { .resolving-skill {
grid-area: target; grid-area: target;
text-align: center;
align-self: center; align-self: center;
height: auto; height: auto;
svg { svg {
@ -213,6 +213,17 @@
} }
} }
.skill-description {
padding-left: 1em;
padding-right: 1em;
text-align: center;
svg {
display: inline;
height: 1em;
margin-right: 0.1em
}
}
/* some stupid bug in chrome makes it fill the entire screen */ /* some stupid bug in chrome makes it fill the entire screen */
@media screen and (-webkit-min-device-pixel-ratio:0) { @media screen and (-webkit-min-device-pixel-ratio:0) {
#targeting { #targeting {
@ -396,6 +407,10 @@
.skills button { .skills button {
font-size: 50%; font-size: 50%;
} }
.skill-description {
font-size: 65%;
}
} }
.player { .player {

View File

@ -45,5 +45,9 @@
grid-area: msg; grid-area: msg;
color: @white; color: @white;
} }
} }
.chat {
justify-content: flex-end;
}

View File

@ -14,7 +14,7 @@ html body {
-ms-user-select: none; -ms-user-select: none;
overflow-x: hidden; overflow-x: hidden;
// overflow-y: hidden; overflow-y: hidden;
} }
#mnml { #mnml {
@ -26,7 +26,7 @@ html body {
/* stops inspector going skitz*/ /* stops inspector going skitz*/
overflow-x: hidden; overflow-x: hidden;
// overflow-y: hidden; overflow-y: hidden;
} }
// @media (min-width: 1921px) { // @media (min-width: 1921px) {

View File

@ -1,6 +1,6 @@
{ {
"name": "mnml-client", "name": "mnml-client",
"version": "1.5.6", "version": "1.6.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -11,14 +11,22 @@ export const setAnimText = value => ({ type: 'SET_ANIM_TEXT', value });
export const setDemo = value => ({ type: 'SET_DEMO', value }); export const setDemo = value => ({ type: 'SET_DEMO', value });
export const setChatShow = value => ({ type: 'SET_CHAT_SHOW', value });
export const setChatWheel = value => ({ type: 'SET_CHAT_WHEEL', value });
export const setInstanceChat = value => ({ type: 'SET_INSTANCE_CHAT', value });
export const setActiveItem = value => ({ type: 'SET_ACTIVE_VAR', value }); export const setActiveItem = value => ({ type: 'SET_ACTIVE_VAR', value });
export const setActiveSkill = (constructId, skill) => ({ type: 'SET_ACTIVE_SKILL', value: constructId ? { constructId, skill } : null }); export const setActiveSkill = (constructId, skill) => ({ type: 'SET_ACTIVE_SKILL', value: constructId ? { constructId, skill } : null });
export const setCombiner = value => ({ type: 'SET_COMBINER', value: Array.from(value) }); export const setCombiner = value => ({ type: 'SET_COMBINER', value: Array.from(value) });
export const setConstructEditId = value => ({ type: 'SET_CONSTRUCT_EDIT_ID', value }); export const setConstructEditId = value => ({ type: 'SET_CONSTRUCT_EDIT_ID', value });
export const setConstructs = value => ({ type: 'SET_CONSTRUCTS', value }); export const setConstructs = value => ({ type: 'SET_CONSTRUCTS', value });
export const setConstructRename = value => ({ type: 'SET_CONSTRUCT_RENAME', value }); export const setConstructRename = value => ({ type: 'SET_CONSTRUCT_RENAME', value });
export const setGame = value => ({ type: 'SET_GAME', value }); export const setGame = value => ({ type: 'SET_GAME', value });
export const setGameSkillInfo = value => ({ type: 'SET_GAME_SKILL_INFO', value });
export const setGameEffectInfo = value => ({ type: 'SET_GAME_EFFECT_INFO', value });
export const setInfo = value => ({ type: 'SET_INFO', value }); export const setInfo = value => ({ type: 'SET_INFO', value });
export const setEmail = value => ({ type: 'SET_EMAIL', value }); export const setEmail = value => ({ type: 'SET_EMAIL', value });
export const setInvite = value => ({ type: 'SET_INVITE', value }); export const setInvite = value => ({ type: 'SET_INVITE', value });
export const setInstance = value => ({ type: 'SET_INSTANCE', value }); export const setInstance = value => ({ type: 'SET_INSTANCE', value });

View File

@ -0,0 +1,65 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const actions = require('../actions');
const addState = connect(
function receiveState(state) {
const {
ws,
chatShow,
chatWheel,
instance,
game,
} = state;
function sendInstanceChat(instance, i) {
return ws.sendInstanceChat(instance, i);
}
return {
instance,
game,
chatShow,
chatWheel,
sendInstanceChat,
};
},
function receiveDispatch(dispatch) {
function setChatShow(v) {
dispatch(actions.setChatShow(v));
}
return {
setChatShow,
};
}
);
function Chat(args) {
const {
instance,
game,
chatShow,
chatWheel,
sendInstanceChat,
setChatShow,
} = args;
function onClick(i) {
sendInstanceChat(instance ? instance.id : game && game.id, i);
setChatShow(false);
return true;
}
return (
<div class={`instance-ctrl-btns chat`}>
{chatWheel.map((c, i) => <button key={i} onClick={() => onClick(i)} >{c}</button>)}
</div>
);
}
module.exports = addState(Chat);

View File

@ -2,10 +2,13 @@ const { connect } = require('preact-redux');
const { Component } = require('preact'); const { Component } = require('preact');
const preact = require('preact'); const preact = require('preact');
const range = require('lodash/range'); const range = require('lodash/range');
const reactStringReplace = require('react-string-replace');
const { STATS } = require('../utils'); const { STATS } = require('../utils');
const { ConstructAvatar, ConstructText } = require('./construct'); const { ConstructAvatar, ConstructText } = require('./construct');
const shapes = require('./shapes'); const shapes = require('./shapes');
const { INFO } = require('./../constants');
const actions = require('../actions');
const SkillBtn = require('./skill.btn'); const SkillBtn = require('./skill.btn');
@ -19,6 +22,8 @@ const addState = connect(
animFocus, animFocus,
animating, animating,
animText, animText,
gameSkillInfo,
itemInfo,
} = state; } = state;
function selectSkillTarget(targetConstructId) { function selectSkillTarget(targetConstructId) {
@ -40,8 +45,19 @@ const addState = connect(
animText, animText,
activeSkill, activeSkill,
selectSkillTarget, selectSkillTarget,
gameSkillInfo,
itemInfo,
}; };
},
function receiveDispatch(dispatch) {
function setGameEffectInfo(info) {
dispatch(actions.setGameEffectInfo(info));
}
return { setGameEffectInfo };
} }
); );
const eventClasses = (animating, animFocus, construct, postSkill) => { const eventClasses = (animating, animFocus, construct, postSkill) => {
@ -77,6 +93,10 @@ class GameConstruct extends Component {
selectSkillTarget, selectSkillTarget,
animFocus, animFocus,
animText, animText,
setGameEffectInfo,
gameSkillInfo,
itemInfo,
} = this.props; } = this.props;
const ko = construct.green_life.value === 0 ? 'ko' : ''; const ko = construct.green_life.value === 0 ? 'ko' : '';
@ -96,9 +116,33 @@ class GameConstruct extends Component {
let crypSkills = <div></div>; let crypSkills = <div></div>;
if (player) crypSkills = (<div class="skills"> {skills} </div>); if (player) crypSkills = (<div class="skills"> {skills} </div>);
const effects = construct.effects.length function hoverInfo(e, info) {
? construct.effects.map(c => <div key={c.effect}>{c.effect} - {c.duration}T</div>) e.stopPropagation();
: null; return setGameEffectInfo(info);
}
const effectBox = () => {
if (gameSkillInfo && gameSkillInfo.constructId === construct.id) {
const fullInfo = itemInfo.items.find(k => k.item === gameSkillInfo.skill) || INFO[gameSkillInfo.skill];
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/;
const infoDescription = reactStringReplace(fullInfo.description, regEx, match => shapes[match]());
const speed = <div> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </div>;
return (
<div class="skill-description">
<h2> {gameSkillInfo.skill} </h2>
<div> {infoDescription} </div>
{speed}
</div>);
}
const effects = construct.effects.length
? construct.effects.map(c =>
<div
key={c.effect}
onMouseOver={e => hoverInfo(e, c)}
onMouseOut={e => hoverInfo(e, null)}
> {c.effect} - {c.duration}T</div>)
: null;
return (<div class="effects"> {effects} </div>);
}
return ( return (
<div <div
@ -107,7 +151,7 @@ class GameConstruct extends Component {
class={`game-construct ${ko} ${classes}`} > class={`game-construct ${ko} ${classes}`} >
<div class="left"> <div class="left">
{crypSkills} {crypSkills}
<div class="effects"> {effects} </div> {effectBox()}
</div> </div>
<div class="right"> <div class="right">
<div class="stats"> {stats} </div> <div class="stats"> {stats} </div>

View File

@ -8,6 +8,8 @@ const addState = connect(
const { const {
ws, ws,
game, game,
account,
chatShow,
animating, animating,
} = state; } = state;
@ -30,6 +32,8 @@ const addState = connect(
return { return {
game, game,
account,
chatShow,
sendAbandon, sendAbandon,
sendGameSkillClear, sendGameSkillClear,
sendReady, sendReady,
@ -45,7 +49,14 @@ const addState = connect(
dispatch(actions.setInstance(null)); dispatch(actions.setInstance(null));
} }
return { quit }; function setChatShow(v) {
dispatch(actions.setChatShow(v));
}
return {
setChatShow,
quit,
};
} }
); );
@ -53,10 +64,13 @@ function GameCtrlBtns(args) {
const { const {
game, game,
animating, animating,
account,
chatShow,
getInstanceState, getInstanceState,
sendGameSkillClear, sendGameSkillClear,
sendReady, sendReady,
setChatShow,
quit, quit,
} = args; } = args;
@ -73,7 +87,7 @@ function GameCtrlBtns(args) {
return ( return (
<div class="game-ctrl-btns"> <div class="game-ctrl-btns">
<button disabled={true} >Chat</button> <button disabled={!account.subscribed} onClick={() => setChatShow(!chatShow)}>Chat</button>
<button disabled={animating} onClick={sendGameSkillClear}>Clear</button> <button disabled={animating} onClick={sendGameSkillClear}>Clear</button>
{finished ? quitBtn : readyBtn} {finished ? quitBtn : readyBtn}
</div> </div>

View File

@ -4,6 +4,7 @@ const { connect } = require('preact-redux');
const actions = require('../actions'); const actions = require('../actions');
const PlayerBox = require('./player.box'); const PlayerBox = require('./player.box');
const Chat = require('./chat');
const GameCtrlButtons = require('./game.ctrl.btns'); const GameCtrlButtons = require('./game.ctrl.btns');
const GameCtrlTopButtons = require('./game.ctrl.btns.top'); const GameCtrlTopButtons = require('./game.ctrl.btns.top');
@ -13,12 +14,16 @@ const addState = connect(
animating, animating,
game, game,
account, account,
chatShow,
instanceChat,
} = state; } = state;
return { return {
animating, animating,
game, game,
account, account,
chatShow,
instanceChat,
}; };
}, },
); );
@ -28,6 +33,8 @@ function Controls(args) {
animating, animating,
account, account,
game, game,
chatShow,
instanceChat,
} = args; } = args;
if (!game) return false; if (!game) return false;
@ -61,13 +68,17 @@ function Controls(args) {
</div> </div>
); );
const bottom = chatShow
? <Chat />
: <PlayerBox player={player} isPlayer={true} chat={instanceChat && instanceChat[player.id]} />;
return ( return (
<aside> <aside>
{timer} {timer}
<div class="controls instance-ctrl"> <div class="controls instance-ctrl">
<GameCtrlTopButtons /> <GameCtrlTopButtons />
<PlayerBox player={opponent}/> <PlayerBox player={opponent} chat={instanceChat && instanceChat[opponent.id]}/>
<PlayerBox player={player} isPlayer={true} /> {bottom}
<GameCtrlButtons /> <GameCtrlButtons />
</div> </div>
</aside> </aside>

View File

@ -7,7 +7,9 @@ const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws, ws,
chatShow,
instance, instance,
account,
} = state; } = state;
function sendReady() { function sendReady() {
@ -21,19 +23,34 @@ const addState = connect(
return { return {
instance, instance,
chatShow,
account,
sendAbandon, sendAbandon,
sendReady, sendReady,
}; };
}, },
function receiveDispatch(dispatch) {
function setChatShow(v) {
dispatch(actions.setChatShow(v));
}
return {
setChatShow,
};
}
); );
function InstanceCtrlBtns(args) { function InstanceCtrlBtns(args) {
const { const {
instance, instance,
chatShow,
account,
sendAbandon, sendAbandon,
sendReady, sendReady,
setChatShow,
} = args; } = args;
const finished = instance && instance.phase === 'Finished'; const finished = instance && instance.phase === 'Finished';
@ -49,7 +66,7 @@ function InstanceCtrlBtns(args) {
return ( return (
<div class="instance-ctrl-btns"> <div class="instance-ctrl-btns">
<button disabled={true} >Chat</button> <button disabled={!account.subscribed} onClick={() => setChatShow(!chatShow)}>Chat</button>
<button disabled={finished} class="ready" onClick={() => sendReady()}>Ready</button> <button disabled={finished} class="ready" onClick={() => sendReady()}>Ready</button>
</div> </div>
); );

View File

@ -1,21 +1,27 @@
const preact = require('preact'); const preact = require('preact');
const { connect } = require('preact-redux'); const { connect } = require('preact-redux');
const actions = require('../actions');
const PlayerBox = require('./player.box'); const PlayerBox = require('./player.box');
const Chat = require('./chat');
const InstanceCtrlBtns = require('./instance.ctrl.btns'); const InstanceCtrlBtns = require('./instance.ctrl.btns');
const InstanceCtrlTopBtns = require('./instance.ctrl.top.btns'); const InstanceCtrlTopBtns = require('./instance.ctrl.top.btns');
const actions = require('../actions');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws, ws,
instance, instance,
instanceChat,
account, account,
chatShow,
} = state; } = state;
return { return {
chatShow,
instance, instance,
instanceChat,
account, account,
}; };
}, },
@ -25,6 +31,8 @@ function Controls(args) {
const { const {
account, account,
instance, instance,
instanceChat,
chatShow,
} = args; } = args;
if (!instance) return false; if (!instance) return false;
@ -58,13 +66,17 @@ function Controls(args) {
</div> </div>
); );
const bottom = chatShow
? <Chat />
: <PlayerBox player={player} isPlayer={true} chat={instanceChat && instanceChat[player.id]} />;
return ( return (
<aside> <aside>
{timer} {timer}
<div class="controls instance-ctrl"> <div class="controls instance-ctrl">
<InstanceCtrlTopBtns /> <InstanceCtrlTopBtns />
<PlayerBox player={opponent} /> <PlayerBox player={opponent} chat={instanceChat && instanceChat[opponent.id]}/>
<PlayerBox player={player} isPlayer={true} /> {bottom}
<InstanceCtrlBtns /> <InstanceCtrlBtns />
</div> </div>
</aside> </aside>

View File

@ -2,7 +2,7 @@ const preact = require('preact');
const { connect } = require('preact-redux'); const { connect } = require('preact-redux');
const Main = require('./main'); const Main = require('./main');
const Nav = require('./nav'); // const Nav = require('./nav');
const Controls = require('./controls'); const Controls = require('./controls');
const Footer = require('./footer'); const Footer = require('./footer');
@ -12,7 +12,6 @@ const addState = connect(
const Mnml = ({ showNav }) => const Mnml = ({ showNav }) =>
<div id="mnml" class={showNav ? 'nav-visible' : ''}> <div id="mnml" class={showNav ? 'nav-visible' : ''}>
<Nav />
<Main /> <Main />
<Controls /> <Controls />
<Footer /> <Footer />

View File

@ -56,6 +56,7 @@ function Scoreboard(args) {
const { const {
isPlayer, isPlayer,
player, player,
chat,
} = args; } = args;
const scoreText = () => { const scoreText = () => {
@ -73,14 +74,14 @@ function Scoreboard(args) {
<div class="score">{scoreText()}</div> <div class="score">{scoreText()}</div>
<div class="name">{player.name}</div> <div class="name">{player.name}</div>
<Img img={player.img} id={player.id} /> <Img img={player.img} id={player.id} />
<div class="msg">&nbsp;</div> <div class="msg">{chat || '\u00A0'}</div>
</div> </div>
); );
} }
return ( return (
<div class={`player-box bottom ${player.ready ? 'ready' : ''}`}> <div class={`player-box bottom ${player.ready ? 'ready' : ''}`}>
<div class="msg">&nbsp;</div> <div class="msg">{chat || '\u00A0'}</div>
<div class="score">{scoreText()}</div> <div class="score">{scoreText()}</div>
<div class="name">{player.name}</div> <div class="name">{player.name}</div>
<Img img={player.img} id={player.id} /> <Img img={player.img} id={player.id} />

View File

@ -23,18 +23,24 @@ const addState = connect(
dispatch(actions.setActiveSkill(constructId, skill)); dispatch(actions.setActiveSkill(constructId, skill));
} }
return { setActiveSkill }; function setGameSkillInfo(info) {
dispatch(actions.setGameSkillInfo(info));
}
return { setActiveSkill, setGameSkillInfo };
} }
); );
function Skill(props) { function Skill(props) {
const { const {
animating,
construct, construct,
game, game,
i, i,
activeSkill, activeSkill,
setActiveSkill, setActiveSkill,
setGameSkillInfo,
} = props; } = props;
if (!game) return false; if (!game) return false;
@ -42,6 +48,11 @@ function Skill(props) {
const s = construct.skills[i]; const s = construct.skills[i];
const ko = construct.green_life.value === 0 ? 'ko' : ''; const ko = construct.green_life.value === 0 ? 'ko' : '';
function hoverInfo(e, info) {
e.stopPropagation();
if (animating) return false;
return setGameSkillInfo(info);
}
if (!s || !game) { if (!s || !game) {
return ( return (
<button <button
@ -76,6 +87,8 @@ function Skill(props) {
<button <button
disabled={cdText || s.disabled || ko} disabled={cdText || s.disabled || ko}
class={`${(targeting || highlight) ? 'active' : ''} ${border}`} class={`${(targeting || highlight) ? 'active' : ''} ${border}`}
onMouseOver={e => hoverInfo(e, { skill: s.skill, constructId: construct.id })}
onMouseOut={e => hoverInfo(e, null)}
type="submit" type="submit"
onClick={onClick}> onClick={onClick}>
{s.skill} {cdText} {s.skill} {cdText}

View File

@ -6,11 +6,11 @@ const reactStringReplace = require('react-string-replace');
const throttle = require('lodash/throttle'); const throttle = require('lodash/throttle');
const shapes = require('./shapes'); const shapes = require('./shapes');
const { removeTier } = require('../utils'); const { effectInfo, removeTier } = require('../utils');
const addState = connect( const addState = connect(
({ game, account, animSkill, animating, itemInfo }) => ({ game, account, animSkill, animating, itemInfo, gameEffectInfo }) =>
({ game, account, animSkill, animating, itemInfo }) ({ game, account, animSkill, animating, itemInfo, gameEffectInfo })
); );
class TargetSvg extends Component { class TargetSvg extends Component {
@ -28,10 +28,24 @@ class TargetSvg extends Component {
} }
render(props, state) { render(props, state) {
const { game, account, animating, animSkill, itemInfo } = props; const { game, account, animating, animSkill, itemInfo, gameEffectInfo } = props;
const { width, height } = state; const { width, height } = state;
if (!game) return false; // game will be null when battle ends if (!game) return false; // game will be null when battle ends
// Whenever someones looking at effects throw it up here
if (gameEffectInfo) {
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/;
const infoString = effectInfo(gameEffectInfo);
const infoDescription = reactStringReplace(infoString, regEx, match => shapes[match]());
return (
<div class="resolving-skill">
<h1>{gameEffectInfo.effect}</h1>
<div> {infoDescription} </div>
</div>
);
}
// resolutions happening // resolutions happening
// just put skill name up // just put skill name up
if (animating) { if (animating) {

View File

@ -20,6 +20,7 @@ const addState = connect(
itemInfo, itemInfo,
itemUnequip, itemUnequip,
navInstance, navInstance,
info,
} = state; } = state;
function sendVboxDiscard() { function sendVboxDiscard() {
@ -56,6 +57,7 @@ const addState = connect(
itemUnequip, itemUnequip,
sendItemUnequip, sendItemUnequip,
navInstance, navInstance,
info,
}; };
}, },
@ -115,6 +117,7 @@ function Vbox(args) {
sendItemUnequip, sendItemUnequip,
setReclaiming, setReclaiming,
info,
} = args; } = args;
if (!player) return false; if (!player) return false;
@ -138,7 +141,7 @@ function Vbox(args) {
// //
function vboxHover(e, v) { function vboxHover(e, v) {
if (v) { if (v) {
setInfo(v); if (info !== v) setInfo(v);
e.stopPropagation(); e.stopPropagation();
} }
return true; return true;
@ -329,7 +332,7 @@ function Vbox(args) {
function combinerBtn() { function combinerBtn() {
let text = ''; let text = '';
let comboItem = '';
if (combiner.length < 3) { if (combiner.length < 3) {
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
if (combiner.length > i) { if (combiner.length > i) {
@ -339,14 +342,18 @@ function Vbox(args) {
} }
} }
} else { } else {
text = 'combine'; // Since theres 3 items in combiner and you can't have invalid combos we can preview it
const combinerItems = combiner.map(j => vbox.bound[j]);
const comboItemObj = itemInfo.combos.find(combo => combinerItems.every(c => combo.components.includes(c)));
comboItem = comboItemObj ? comboItemObj.item : 'refine';
text = `Combine - ${comboItem}`;
} }
return ( return (
<button <button
class='vbox-btn' class='vbox-btn'
disabled={combiner.length !== 3} disabled={combiner.length !== 3}
onMouseOver={e => hoverInfo(e, 'refine')} onMouseOver={e => hoverInfo(e, comboItem)}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
onMouseDown={() => sendVboxCombine()}> onMouseDown={() => sendVboxCombine()}>
{text} {text}
@ -390,9 +397,10 @@ function Vbox(args) {
// //
// EVERYTHING // EVERYTHING
// //
function hoverInfo(e, info) { function hoverInfo(e, newInfo) {
e.stopPropagation(); e.stopPropagation();
return setInfo(info); if (info === newInfo) return true;
return setInfo(newInfo);
} }
const classes = `vbox ${navInstance === 0 ? 'visible' : ''}`; const classes = `vbox ${navInstance === 0 ? 'visible' : ''}`;

View File

@ -109,7 +109,7 @@ function registerEvents(store) {
store.dispatch(actions.setAnimTarget(null)); store.dispatch(actions.setAnimTarget(null));
store.dispatch(actions.setAnimText(null)); store.dispatch(actions.setAnimText(null));
store.dispatch(actions.setAnimating(false)); store.dispatch(actions.setAnimating(false));
store.dispatch(actions.setGameEffectInfo(null));
store.dispatch(actions.setSkip(false)); store.dispatch(actions.setSkip(false));
// set the game state so resolutions don't fire twice // set the game state so resolutions don't fire twice
@ -212,6 +212,14 @@ function registerEvents(store) {
return store.dispatch(actions.setInstance(v)); return store.dispatch(actions.setInstance(v));
} }
function setInstanceChat(v) {
return store.dispatch(actions.setInstanceChat(v));
}
function setChatWheel(v) {
return store.dispatch(actions.setChatWheel(v));
}
function setItemInfo(v) { function setItemInfo(v) {
return store.dispatch(actions.setItemInfo(v)); return store.dispatch(actions.setItemInfo(v));
} }
@ -318,12 +326,14 @@ function registerEvents(store) {
setAccountInstances, setAccountInstances,
setActiveItem, setActiveItem,
setActiveSkill, setActiveSkill,
setChatWheel,
setDemo, setDemo,
setConstructList, setConstructList,
setNewConstruct, setNewConstruct,
setGame, setGame,
setEmail, setEmail,
setInstance, setInstance,
setInstanceChat,
setItemInfo, setItemInfo,
setInvite, setInvite,
setPing, setPing,

View File

@ -24,15 +24,21 @@ module.exports = {
demo: createReducer(null, 'SET_DEMO'), demo: createReducer(null, 'SET_DEMO'),
chatShow: createReducer(null, 'SET_CHAT_SHOW'),
chatWheel: createReducer([], 'SET_CHAT_WHEEL'),
combiner: createReducer([], 'SET_COMBINER'), combiner: createReducer([], 'SET_COMBINER'),
constructs: createReducer([], 'SET_CONSTRUCTS'), constructs: createReducer([], 'SET_CONSTRUCTS'),
constructEditId: createReducer(null, 'SET_CONSTRUCT_EDIT_ID'), constructEditId: createReducer(null, 'SET_CONSTRUCT_EDIT_ID'),
constructRename: createReducer(null, 'SET_CONSTRUCT_RENAME'), constructRename: createReducer(null, 'SET_CONSTRUCT_RENAME'),
game: createReducer(null, 'SET_GAME'), game: createReducer(null, 'SET_GAME'),
gameSkillInfo: createReducer(null, 'SET_GAME_SKILL_INFO'),
gameEffectInfo: createReducer(null, 'SET_GAME_EFFECT_INFO'),
email: createReducer(null, 'SET_EMAIL'), email: createReducer(null, 'SET_EMAIL'),
invite: createReducer(null, 'SET_INVITE'), invite: createReducer(null, 'SET_INVITE'),
info: createReducer(null, 'SET_INFO'), info: createReducer(null, 'SET_INFO'),
instance: createReducer(null, 'SET_INSTANCE'), instance: createReducer(null, 'SET_INSTANCE'),
instanceChat: createReducer(null, 'SET_INSTANCE_CHAT'),
instances: createReducer([], 'SET_INSTANCES'), instances: createReducer([], 'SET_INSTANCES'),
itemEquip: createReducer(null, 'SET_ITEM_EQUIP'), itemEquip: createReducer(null, 'SET_ITEM_EQUIP'),
itemInfo: createReducer({ combos: [], items: [] }, 'SET_ITEM_INFO'), itemInfo: createReducer({ combos: [], items: [] }, 'SET_ITEM_INFO'),

View File

@ -67,6 +67,10 @@ function createSocket(events) {
send(['InstanceState', { instance_id: instanceId }]); send(['InstanceState', { instance_id: instanceId }]);
} }
function sendInstanceChat(instanceId, index) {
send(['InstanceChat', { instance_id: instanceId, index }]);
}
function sendVboxAccept(instanceId, group, index) { function sendVboxAccept(instanceId, group, index) {
send(['VboxAccept', { instance_id: instanceId, group, index }]); send(['VboxAccept', { instance_id: instanceId, group, index }]);
events.clearInstance(); events.clearInstance();
@ -253,8 +257,11 @@ function createSocket(events) {
QueueJoined: () => events.notify('you have joined the pvp queue'), QueueJoined: () => events.notify('you have joined the pvp queue'),
InviteRequested: () => events.notify('pvp queue request received'), InviteRequested: () => events.notify('pvp queue request received'),
Invite: code => events.setInvite(code), Invite: code => events.setInvite(code),
InstanceChat: chat => events.setInstanceChat(chat),
ChatWheel: wheel => events.setChatWheel(wheel),
Joining: () => events.notify('searching for instance...'), Joining: () => events.notify('searching for instance...'),
Processing: () => true,
Error: errHandler, Error: errHandler,
}; };
@ -358,6 +365,7 @@ function createSocket(events) {
sendInstanceState, sendInstanceState,
sendInstanceInvite, sendInstanceInvite,
sendInstanceJoin, sendInstanceJoin,
sendInstanceChat,
sendVboxAccept, sendVboxAccept,
sendVboxApply, sendVboxApply,

View File

@ -237,6 +237,64 @@ function convertItem(v) {
// return; // return;
} }
function effectInfo(i) {
console.log(i);
function multiplier(s) { // Update later to use server info in future
if (s === 'CounterAttack') return 70;
if (s === 'CounterAttack+') return 95;
if (s === 'CounterAttack++') return 120;
if (s === 'DecayTick') return 33;
if (s === 'DecayTick+') return 45;
if (s === 'DecayTick++') return 70;
if (s === 'SiphonTick') return 20;
if (s === 'SiphonTick+') return 25;
if (s === 'SiphonTick++') return 30;
if (s === 'TriageTick') return 75;
if (s === 'TriageTick+') return 110;
if (s === 'TriageTick++') return 140;
if (s === 'Electrocute' || s === 'ElectrocuteTick') return 80;
if (s === 'Electrocute+' || s === 'ElectrocuteTick+') return 100;
if (s === 'Electrocute++' || s === 'ElectrocuteTick++') return 130;
return 0;
}
switch (i.effect) {
case 'Amplify': return `Increases construct RedPower and BluePower by ${i.meta[1] - 100}%`;
case 'Banish': return 'Banished construct cannot cast or take damage';
case 'Block': return `Reduces construct red damage taken by ${100 - i.meta[1]}%`;
case 'Buff': return `Increases construct RedPower and SpeedStat by ${i.meta[1] - 100}%`;
case 'Sustain': return 'Construct cannot be KO while active. Additionally provides immunity to disables';
case 'Curse': return `Construct will take ${i.meta[1] - 100}% increased red and blue damage`;
case 'Haste': return `Construct has ${i.meta[1] - 100}% increased SpeedStat. Red attack skills will trigger a HasteStrike dealing 30% SpeedStat as red damage.`;
case 'Hybrid': return `Construct has ${i.meta[1] - 100}% increased GreenPower. Blue attack skills will trigger a HybridBlast dealing 25% GreenPower as red damage.`;
case 'Invert': return 'Reverses damage and healing. Healing will damage this construct and damage will heal.';
case 'Counter': return `Red damage taken by this construct will trigger a CounterAttack. CounterAttack deals ${multiplier(i.meta[1])}% RedPower as red damage.`;
case 'Purge': return 'Disable construct from casting any green skills';
case 'Reflect': return 'Reflect blue skills back to caster';
case 'Slow': return `Reduces construct SpeedStat by ${100 - i.meta[1]}%`;
case 'Restrict': return 'Disable construct from casting any red skills';
case 'Stun': return 'Stunned construct cannot use skills';
case 'Intercept': return 'Redirect any skills on team to this target construct';
case 'Vulnerable': return `Construct will take ${i.meta[1] - 100}% increased red damage`;
case 'Silence': return 'Disable construct from casting any blue skills';
case 'Wither': return `Construct will take ${100 - i.meta[1]}% reduced healing`; //
case 'Decay': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower as blue damage each turn.`; //
case 'Electric': return `Attacks against this construct will apply Electrocute dealing ${multiplier(i.meta[1])}% of construct BluePower as blue damage each turn.`;
case 'Electrocute': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower as blue damage each turn.`;
case 'Absorb': return 'If construct takes damage, Absorption will be applied increasing RedPower and BluePower based on damage taken.';
case 'Absorption': return `Increasing construct RedPower and BluePower by ${i.meta[1]}`;
case 'Triage': return `Construct will be healed for ${multiplier(i.tick.skill)}% of caster's GreenPower each turn.`;
case 'Siphon': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower + GreenPower as blue damage each turn, healing the caster.`;
default: return 'Missing Effect Text';
}
}
module.exports = { module.exports = {
stringSort, stringSort,
numSort, numSort,
@ -251,4 +309,5 @@ module.exports = {
randomPoints, randomPoints,
removeTier, removeTier,
match, match,
effectInfo,
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "mnml-ops", "name": "mnml-ops",
"version": "1.5.6", "version": "1.6.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1,6 +1,6 @@
[package] [package]
name = "mnml" name = "mnml"
version = "1.5.6" version = "1.6.0"
authors = ["ntr <ntr@smokestack.io>"] authors = ["ntr <ntr@smokestack.io>"]
[dependencies] [dependencies]

View File

@ -65,6 +65,19 @@ pub fn select(db: &Db, id: Uuid) -> Result<Account, Error> {
Account::try_from(row) Account::try_from(row)
} }
pub fn chat_wheel(_db: &Db, _id: Uuid) -> Result<Vec<String>, Error> {
return Ok(vec![
"gl".to_string(),
"hf".to_string(),
"gg".to_string(),
"thx".to_string(),
"nice".to_string(),
"hmm".to_string(),
"ok".to_string(),
"...".to_string(),
])
}
pub fn select_name(db: &Db, name: &String) -> Result<Account, Error> { pub fn select_name(db: &Db, name: &String) -> Result<Account, Error> {
let query = " let query = "
SELECT id, name, balance, subscribed, img SELECT id, name, balance, subscribed, img

View File

@ -1,4 +1,6 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::thread::{spawn, sleep};
use std::time;
// Db Commons // Db Commons
use uuid::Uuid; use uuid::Uuid;
@ -58,6 +60,9 @@ pub enum Event {
Invite(Id), Invite(Id),
Join(Id, String), Join(Id, String),
Joined(Id), Joined(Id),
Chat(Id, Uuid, String),
ChatClear(Id, Uuid),
} }
struct WsClient { struct WsClient {
@ -65,6 +70,7 @@ struct WsClient {
account: Option<Uuid>, account: Option<Uuid>,
tx: Sender<RpcMessage>, tx: Sender<RpcMessage>,
subs: HashSet<Uuid>, subs: HashSet<Uuid>,
chat: Option<(Uuid, String)>,
pvp: bool, pvp: bool,
invite: Option<String>, invite: Option<String>,
} }
@ -120,7 +126,15 @@ impl Events {
None => None, None => None,
}; };
let client = WsClient { id, tx, account: account_id, subs: HashSet::new(), pvp: false, invite: None }; let client = WsClient { id,
tx,
account: account_id,
subs: HashSet::new(),
pvp: false,
invite: None,
chat: None,
};
self.clients.insert(id, client); self.clients.insert(id, client);
info!("clients={:?}", self.clients.len()); info!("clients={:?}", self.clients.len());
@ -171,7 +185,17 @@ impl Events {
for (client_id, client) in self.clients.iter() { for (client_id, client) in self.clients.iter() {
if client.subs.contains(&id) { if client.subs.contains(&id) {
subs += 1; subs += 1;
match client.tx.send(msg.clone()) {
let redacted = match client.account {
Some(a) => match msg {
RpcMessage::InstanceState(ref i) => RpcMessage::InstanceState(i.clone().redact(a)),
RpcMessage::GameState(ref i) => RpcMessage::GameState(i.clone().redact(a)),
_ => msg.clone(),
}
None => msg.clone(),
};
match client.tx.send(redacted) {
Ok(_) => (), Ok(_) => (),
Err(e) => { Err(e) => {
warn!("unable to send msg to client err={:?}", e); warn!("unable to send msg to client err={:?}", e);
@ -205,7 +229,7 @@ impl Events {
} }
// create the req for the already queued opponent // create the req for the already queued opponent
if let Some(opp_req) = match self.clients.iter_mut().find(|(_c_id, c)| c.pvp) { if let Some(opp_req) = match self.clients.iter_mut().find(|(c_id, c)| c.pvp && **c_id != id) {
Some((q_id, q)) => { Some((q_id, q)) => {
q.pvp = false; q.pvp = false;
Some(PvpRequest { id: *q_id, account: q.account.unwrap(), tx: q.tx.clone() }) Some(PvpRequest { id: *q_id, account: q.account.unwrap(), tx: q.tx.clone() })
@ -281,6 +305,58 @@ impl Events {
return Ok(()); return Ok(());
}, },
Event::Chat(id, instance, msg) => {
// set the chat state of this connection
{
let c = self.clients.get_mut(&id)
.ok_or(format_err!("connection not found id={:?}", id))?;
if c.chat.is_some() {
return Ok(());
}
c.chat = Some((instance, msg));
let events_tx = self.tx.clone();
spawn(move || {
sleep(time::Duration::from_secs(3));
events_tx.send(Event::ChatClear(id, instance)).unwrap();
});
}
// now collect all listeners of this instance
let chat_state: HashMap<Uuid, String> = self.clients.iter()
.filter(|(_id, c)| c.account.is_some())
.filter(|(_id, c)| match c.chat {
Some(ref chat) => chat.0 == instance,
None => false,
})
.map(|(_id, c)| (c.account.unwrap(), c.chat.clone().unwrap().1))
.collect();
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
},
Event::ChatClear(id, instance) => {
{
match self.clients.get_mut(&id) {
Some(c) => c.chat = None,
None => (),
};
}
let chat_state: HashMap<Uuid, String> = self.clients.iter()
.filter(|(_id, c)| c.account.is_some())
.filter(|(_id, c)| match c.chat {
Some(ref chat) => chat.0 == instance,
None => false,
})
.map(|(_id, c)| (c.account.unwrap(), c.chat.clone().unwrap().1))
.collect();
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
}
} }
} }
} }

View File

@ -62,6 +62,17 @@ impl Game {
}; };
} }
pub fn redact(mut self, account: Uuid) -> Game {
self.players = self.players.into_iter()
.map(|p| p.redact(account))
.collect();
self.stack
.retain(|s| s.source_player_id == account);
self
}
pub fn set_time_control(&mut self, tc: TimeControl) -> &mut Game { pub fn set_time_control(&mut self, tc: TimeControl) -> &mut Game {
self.time_control = tc; self.time_control = tc;
self.phase_end = Some(tc.lobby_timeout()); self.phase_end = Some(tc.lobby_timeout());
@ -631,8 +642,8 @@ pub fn game_write(tx: &mut Transaction, game: &Game) -> Result<(), Error> {
return Ok(()); return Ok(());
} }
pub fn game_state(tx: &mut Transaction, _account: &Account, id: Uuid) -> Result<Game, Error> { pub fn game_state(tx: &mut Transaction, account: &Account, id: Uuid) -> Result<Game, Error> {
return game_get(tx, id) Ok(game_get(tx, id)?.redact(account.id))
} }
pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result<Game, Error> { pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result<Game, Error> {

View File

@ -1,4 +1,5 @@
use std::fs::File; use std::fs::File;
use std::collections::{HashMap};
use uuid::Uuid; use uuid::Uuid;
@ -31,6 +32,8 @@ enum InstancePhase {
Finished, Finished,
} }
pub type ChatState = HashMap<Uuid, String>;
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]
struct Round { struct Round {
game_id: Option<Uuid>, game_id: Option<Uuid>,
@ -126,6 +129,14 @@ impl Instance {
} }
} }
pub fn redact(mut self, account: Uuid) -> Instance {
self.players = self.players.into_iter()
.map(|p| p.redact(account))
.collect();
self
}
fn phase_timed_out(&self) -> bool { fn phase_timed_out(&self) -> bool {
match self.phase_end { match self.phase_end {
Some(t) => Utc::now().signed_duration_since(t).num_milliseconds() > 0, Some(t) => Utc::now().signed_duration_since(t).num_milliseconds() > 0,

View File

@ -850,7 +850,8 @@ impl Item {
Item::Slay| Item::Slay|
Item::SlayPlus | Item::SlayPlus |
Item::SlayPlusPlus => format!( Item::SlayPlusPlus => format!(
"Deals {:?}% RedPower as red damage and provides self healing based on damage dealt.", "Deals {:?}% RedPower + {:?}% GreenPower as red damage and provides self healing based on damage dealt.",
self.into_skill().unwrap().multiplier(),
self.into_skill().unwrap().multiplier()), self.into_skill().unwrap().multiplier()),
Item::Sleep| Item::Sleep|
@ -884,7 +885,8 @@ impl Item {
Item::Siphon| Item::Siphon|
Item::SiphonPlus | Item::SiphonPlus |
Item::SiphonPlusPlus => format!( Item::SiphonPlusPlus => format!(
"Deals {:?}% BluePower as blue damage each turn and heals caster based on damage dealt. Lasts {:?}T.", "Deals {:?}% BluePower + {:?}% GreenPower as blue damage each turn and heals caster based on damage dealt. Lasts {:?}T.",
self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(),
self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(),
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),

View File

@ -103,6 +103,24 @@ impl Player {
} }
} }
pub fn redact(mut self, account: Uuid) -> Player {
// all g
if account == self.id {
return self;
}
// remove vbox
self.vbox = Vbox::new();
// hide skills
for construct in self.constructs.iter_mut() {
construct.skills = vec![];
construct.specs = vec![];
}
self
}
pub fn set_bot(mut self, bot: bool) -> Player { pub fn set_bot(mut self, bot: bool) -> Player {
self.bot = bot; self.bot = bot;
self self

View File

@ -1,5 +1,7 @@
use std::time::{Instant}; use std::time::{Instant};
use std::thread::spawn; use std::thread::{spawn, sleep};
use std::time;
use std::str; use std::str;
use uuid::Uuid; use uuid::Uuid;
@ -21,7 +23,7 @@ use account;
use construct::{Construct}; use construct::{Construct};
use events::{Event}; use events::{Event};
use game::{Game, game_state, game_skill, game_skill_clear, game_ready}; use game::{Game, game_state, game_skill, game_skill_clear, game_ready};
use instance::{Instance, instance_state, instance_practice, instance_ready, instance_abandon, demo}; use instance::{Instance, ChatState, instance_state, instance_practice, instance_ready, instance_abandon, demo};
use item::{Item, ItemInfoCtr, item_info}; use item::{Item, ItemInfoCtr, item_info};
use mtx; use mtx;
use mail; use mail;
@ -50,6 +52,8 @@ pub enum RpcMessage {
ItemInfo(ItemInfoCtr), ItemInfo(ItemInfoCtr),
InstanceState(Instance), InstanceState(Instance),
InstanceChat(ChatState),
ChatWheel(Vec<String>),
EmailState(Option<Email>), EmailState(Option<Email>),
SubscriptionState(Option<Subscription>), SubscriptionState(Option<Subscription>),
@ -66,6 +70,8 @@ pub enum RpcMessage {
Invite(String), Invite(String),
Joining(()), Joining(()),
Processing(()),
Error(String), Error(String),
} }
@ -102,6 +108,7 @@ pub enum RpcRequest {
InstanceAbandon { instance_id: Uuid }, InstanceAbandon { instance_id: Uuid },
InstanceReady { instance_id: Uuid }, InstanceReady { instance_id: Uuid },
InstanceState { instance_id: Uuid }, InstanceState { instance_id: Uuid },
InstanceChat { instance_id: Uuid, index: usize },
VboxAccept { instance_id: Uuid, group: usize, index: usize }, VboxAccept { instance_id: Uuid, group: usize, index: usize },
VboxDiscard { instance_id: Uuid }, VboxDiscard { instance_id: Uuid },
@ -158,6 +165,22 @@ impl Connection {
self.events.send(Event::Join(self.id, code))?; self.events.send(Event::Join(self.id, code))?;
Ok(RpcMessage::Joining(())) Ok(RpcMessage::Joining(()))
}, },
RpcRequest::InstanceChat { instance_id, index } => {
if !account.subscribed {
return Err(err_msg("subscribe to unlock chat"))
}
let wheel = account::chat_wheel(&db, account.id)?;
if let Some(c) = wheel.get(index) {
self.events.send(Event::Chat(self.id, instance_id, c.to_string()))?;
} else {
return Err(err_msg("invalid chat index"));
}
Ok(RpcMessage::Processing(()))
},
_ => { _ => {
// all good, let's make a tx and process // all good, let's make a tx and process
let mut tx = db.transaction()?; let mut tx = db.transaction()?;
@ -257,6 +280,25 @@ impl Connection {
}, },
} }
} }
// this is where last minute processing happens
// use it to modify outgoing messages, update subs, serialize in some way...
fn send(&self, msg: RpcMessage) -> Result<(), Error> {
let msg = match self.account {
Some(ref a) => match msg {
RpcMessage::InstanceState(v) => RpcMessage::InstanceState(v.redact(a.id)),
RpcMessage::AccountInstances(v) =>
RpcMessage::AccountInstances(v.into_iter().map(|i| i.redact(a.id)).collect()),
RpcMessage::GameState(v) => RpcMessage::GameState(v.redact(a.id)),
_ => msg,
},
None => msg,
};
self.ws.send(msg).unwrap();
Ok(())
}
} }
// we unwrap everything in here cause really // we unwrap everything in here cause really
@ -272,7 +314,7 @@ impl Handler for Connection {
// if user logged in do some prep work // if user logged in do some prep work
if let Some(ref a) = self.account { if let Some(ref a) = self.account {
self.ws.send(RpcMessage::AccountState(a.clone())).unwrap(); self.send(RpcMessage::AccountState(a.clone())).unwrap();
self.events.send(Event::Subscribe(self.id, a.id)).unwrap(); self.events.send(Event::Subscribe(self.id, a.id)).unwrap();
// check if they have an image that needs to be generated // check if they have an image that needs to be generated
@ -283,23 +325,26 @@ impl Handler for Connection {
// send account constructs // send account constructs
let account_constructs = account::constructs(&mut tx, a).unwrap(); let account_constructs = account::constructs(&mut tx, a).unwrap();
self.ws.send(RpcMessage::AccountConstructs(account_constructs)).unwrap(); self.send(RpcMessage::AccountConstructs(account_constructs)).unwrap();
// get account instances // get account instances
// and send them to the client // and send them to the client
let account_instances = account::account_instances(&mut tx, a).unwrap(); let account_instances = account::account_instances(&mut tx, a).unwrap();
self.ws.send(RpcMessage::AccountInstances(account_instances)).unwrap(); self.send(RpcMessage::AccountInstances(account_instances)).unwrap();
let shop = mtx::account_shop(&mut tx, &a).unwrap(); let shop = mtx::account_shop(&mut tx, &a).unwrap();
self.ws.send(RpcMessage::AccountShop(shop)).unwrap(); self.send(RpcMessage::AccountShop(shop)).unwrap();
let team = account::team(&mut tx, &a).unwrap(); let team = account::team(&mut tx, &a).unwrap();
self.ws.send(RpcMessage::AccountTeam(team)).unwrap(); self.send(RpcMessage::AccountTeam(team)).unwrap();
let wheel = account::chat_wheel(&db, a.id).unwrap();
self.send(RpcMessage::ChatWheel(wheel)).unwrap();
// tx should do nothing // tx should do nothing
tx.commit().unwrap(); tx.commit().unwrap();
} else { } else {
self.ws.send(RpcMessage::Demo(demo().unwrap())).unwrap(); self.send(RpcMessage::Demo(demo().unwrap())).unwrap();
} }
Ok(()) Ok(())
@ -327,11 +372,11 @@ impl Handler for Connection {
_ => (), _ => (),
}; };
self.ws.send(reply).unwrap(); self.send(reply).unwrap();
}, },
Err(e) => { Err(e) => {
warn!("{:?}", e); warn!("{:?}", e);
self.ws.send(RpcMessage::Error(e.to_string())).unwrap(); self.send(RpcMessage::Error(e.to_string())).unwrap();
}, },
}; };
}, },

View File

@ -757,13 +757,13 @@ impl Skill {
Skill::HealPlus => 185, //GG Skill::HealPlus => 185, //GG
Skill::HealPlusPlus => 270, //GG Skill::HealPlusPlus => 270, //GG
Skill::SiphonTick=> 40, // GB Skill::SiphonTick=> 20, // GB
Skill::SiphonTickPlus => 50, Skill::SiphonTickPlus => 25,
Skill::SiphonTickPlusPlus => 60, Skill::SiphonTickPlusPlus => 30,
Skill::Slay=> 70, // RG Skill::Slay=> 40, // RG
Skill::SlayPlus => 115, Skill::SlayPlus => 60,
Skill::SlayPlusPlus => 180, Skill::SlayPlusPlus => 90,
Skill::Strike=> 90, //RR Skill::Strike=> 90, //RR
Skill::StrikePlus => 140, Skill::StrikePlus => 140,
@ -1420,9 +1420,7 @@ fn break_(source: &mut Construct, target: &mut Construct, mut results: Resolutio
} }
fn block(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { fn block(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target) results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
.event(target.add_effect(skill, skill.effect()[0]))
.stages(EventStages::StartEnd));
return results; return results;
} }
@ -1473,7 +1471,7 @@ fn restrict(source: &mut Construct, target: &mut Construct, mut results: Resolut
} }
fn slay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { fn slay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_power().pct(skill.multiplier()); let amount = source.red_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier());
let slay_events = target.deal_red_damage(skill, amount); let slay_events = target.deal_red_damage(skill, amount);
for e in slay_events { for e in slay_events {
@ -1734,7 +1732,7 @@ fn siphon(source: &mut Construct, target: &mut Construct, mut results: Resolutio
} }
fn siphon_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { fn siphon_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.blue_power().pct(skill.multiplier()); let amount = source.blue_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier());
let siphon_events = target.deal_blue_damage(skill, amount); let siphon_events = target.deal_blue_damage(skill, amount);
for e in siphon_events { for e in siphon_events {
@ -2039,13 +2037,14 @@ mod tests {
.named(&"camel".to_string()); .named(&"camel".to_string());
x.blue_power.force(256); x.blue_power.force(256);
x.green_power.force(220);
x.green_life.force(1024); x.green_life.force(1024);
x.green_life.reduce(512); x.green_life.reduce(512);
let mut results = resolve(Skill::Siphon, &mut x, &mut y, vec![]); let mut results = resolve(Skill::Siphon, &mut x, &mut y, vec![]);
assert!(y.affected(Effect::Siphon)); assert!(y.affected(Effect::Siphon));
assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()))); assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier())));
let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); let Resolution { source: _, target: _, event, stages: _ } = results.remove(0);
match event { match event {
@ -2055,14 +2054,15 @@ mod tests {
let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); let Resolution { source: _, target: _, event, stages: _ } = results.remove(0);
match event { match event {
Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier())), Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier())
+ 220.pct(Skill::SiphonTick.multiplier())),
_ => panic!("not damage siphon"), _ => panic!("not damage siphon"),
}; };
let Resolution { source: _, target, event, stages: _ } = results.remove(0); let Resolution { source: _, target, event, stages: _ } = results.remove(0);
match event { match event {
Event::Healing { amount, skill: _, overhealing: _ } => { Event::Healing { amount, skill: _, overhealing: _ } => {
assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier())); assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier()));
assert_eq!(target.id, x.id); assert_eq!(target.id, x.id);
}, },
_ => panic!("not healing"), _ => panic!("not healing"),