Merge tag '1.12.2' into develop

1.12.2
This commit is contained in:
ntr 2020-01-17 17:34:47 +10:00
commit 8499b3bca8
27 changed files with 135 additions and 78 deletions

View File

@ -1 +1 @@
1.12.1 1.12.2

View File

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

View File

@ -15,3 +15,5 @@ cd $MNML_PATH/ops && npm --allow-same-version --no-git-tag-version version "$VER
cd $MNML_PATH/client && npm --allow-same-version --no-git-tag-version version "$VERSION" cd $MNML_PATH/client && npm --allow-same-version --no-git-tag-version version "$VERSION"
cd $MNML_PATH/acp && npm --allow-same-version --no-git-tag-version version "$VERSION" cd $MNML_PATH/acp && npm --allow-same-version --no-git-tag-version version "$VERSION"
cd $MNML_PATH/studios && npm --allow-same-version --no-git-tag-version version "$VERSION" cd $MNML_PATH/studios && npm --allow-same-version --no-git-tag-version version "$VERSION"
git commit -am "v$VERSION"

View File

@ -112,7 +112,7 @@ section {
figure { figure {
letter-spacing: 0.25em; letter-spacing: 0.25em;
text-transform: uppercase; text-transform: uppercase;
font-size: 125%; font-size: 1.5em;
display: flex; display: flex;
flex-flow: column; flex-flow: column;
} }
@ -138,27 +138,14 @@ section {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
button.ready:enabled {
color: forestgreen;
border-color: forestgreen;
&:hover {
background: forestgreen;
color: black;
border-color: forestgreen;
}
}
// // all green
// button.ready:enabled { // button.ready:enabled {
// background: forestgreen; // color: forestgreen;
// color: black;
// border-color: forestgreen; // border-color: forestgreen;
// &:hover { // &:hover {
// color: forestgreen; // background: forestgreen;
// color: black;
// border-color: forestgreen; // border-color: forestgreen;
// background: 0;
// } // }
// } // }
} }

View File

@ -173,6 +173,19 @@ button, input {
// &:active { // &:active {
// filter: url("#noiseFilter"); // filter: url("#noiseFilter");
// } // }
// all green
&.ready:enabled {
background: forestgreen;
color: black;
border-color: forestgreen;
&:hover {
color: forestgreen;
border-color: forestgreen;
background: 0;
}
}
} }
a { a {
@ -269,6 +282,10 @@ figure.gray {
@media (max-width: 1500px) { @media (max-width: 1500px) {
#mnml { #mnml {
font-size: 75%; font-size: 75%;
&.front-page main {
padding: 0 10%;
}
} }
svg { svg {

View File

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

View File

@ -6,8 +6,8 @@ function sourceCast(id, direction, idle) {
const { x, y } = direction; const { x, y } = direction;
return anime({ return anime({
targets: [document.getElementById(id)], targets: [document.getElementById(id)],
translateX: x * window.screen.width * 0.1, translateX: x * window.innerWidth * 0.1,
translateY: y * window.screen.height * 0.1, translateY: y * window.innerHeight * 0.1,
easing: 'easeInOutElastic', easing: 'easeInOutElastic',
direction: 'alternate', direction: 'alternate',
duration: TIMES.SOURCE_DURATION_MS, duration: TIMES.SOURCE_DURATION_MS,

View File

@ -82,9 +82,11 @@ function Skill(props) {
const border = buttons[removeTier(s.skill)] ? buttons[removeTier(s.skill)]() : ''; const border = buttons[removeTier(s.skill)] ? buttons[removeTier(s.skill)]() : '';
const notSkill = game.phase !== 'Skill';
return ( return (
<button <button
disabled={cdText || s.disabled || ko} disabled={cdText || s.disabled || ko || notSkill}
class={`${(targeting || highlight) ? 'active' : ''} ${border}`} class={`${(targeting || highlight) ? 'active' : ''} ${border}`}
onMouseOver={e => hoverInfo(e, { skill: s.skill, constructId: construct.id })} onMouseOver={e => hoverInfo(e, { skill: s.skill, constructId: construct.id })}
onMouseOut={e => hoverInfo(e, null)} onMouseOut={e => hoverInfo(e, null)}

View File

@ -10,6 +10,7 @@ const addState = connect(
chatShow, chatShow,
instance, instance,
account, account,
tutorial,
} = state; } = state;
function sendReady() { function sendReady() {
@ -21,6 +22,7 @@ const addState = connect(
instance, instance,
chatShow, chatShow,
account, account,
tutorial,
sendReady, sendReady,
}; };
@ -42,17 +44,17 @@ function InstanceCtrlBtns(args) {
instance, instance,
chatShow, chatShow,
account, account,
tutorial,
sendReady, sendReady,
setChatShow, setChatShow,
} = args; } = args;
const finished = instance && instance.phase === 'Finished'; const finished = instance && instance.phase === 'Finished';
const tutorialDisable = tutorial && tutorial < 8;
return ( return (
<div class="instance-ctrl-btns"> <div class="instance-ctrl-btns">
<button disabled={!account.subscribed} onClick={() => setChatShow(!chatShow)}>Chat</button> <button disabled={!account.subscribed} onClick={() => setChatShow(!chatShow)}>Chat</button>
<button disabled={finished} class="ready" onClick={() => sendReady()}>Ready</button> <button disabled={finished || tutorialDisable} class="ready" onClick={() => sendReady()}>Ready</button>
</div> </div>
); );
} }

View File

@ -95,7 +95,7 @@ function Play(args) {
type="submit"> type="submit">
Invite Invite
</button> </button>
<figcaption>Invite a Friend</figcaption> <figcaption>Play against friend</figcaption>
</figure> </figure>
); );
@ -221,13 +221,15 @@ function Play(args) {
<div> <div>
<h1 class="credits">¤ {account.balance}</h1> <h1 class="credits">¤ {account.balance}</h1>
<div class='list'> <div class='list'>
{subscription} <figure>{subscription}</figure>
<button <figure>
onClick={() => setNav('shop')} <button
class="yellow-btn" onClick={() => setNav('shop')}
role="link"> class="yellow-btn"
Get Credits role="link">
</button> Get Credits
</button>
</figure>
</div> </div>
<div> <div>
Join our Discord server to find opponents and talk to the devs. <br /> Join our Discord server to find opponents and talk to the devs. <br />

View File

@ -89,6 +89,7 @@ function Reshape(args) {
return ( return (
<section class="top" onClick={() => setMtxActive(null)}> <section class="top" onClick={() => setMtxActive(null)}>
<div class="news"> <div class="news">
<h1> Customise your Constructs </h1>
<p class="play-p">Use credits to modify your construct names and appearance.</p> <p class="play-p">Use credits to modify your construct names and appearance.</p>
<ul> <ul>
@ -106,19 +107,19 @@ function Reshape(args) {
<div> <div>
<h1 class="credits">¤ {account.balance}</h1> <h1 class="credits">¤ {account.balance}</h1>
<div class='list'> <div class='list'>
{subscription} <figure>{subscription}</figure>
<button <figure>
onClick={() => setNav('shop')} <button
class="yellow-btn" onClick={() => setNav('shop')}
role="link"> class="yellow-btn"
Get Credits role="link">
</button> Get Credits
<div id="error-message"></div> </button>
</div> </figure>
<div class='list'>
{shop.owned.map(useMtx)} {shop.owned.map(useMtx)}
{shop.available.map(availableMtx)} {shop.available.map(availableMtx)}
</div> </div>
<div id="error-message"></div>
</div> </div>
</section> </section>
); );

View File

@ -27,7 +27,7 @@ function Shop(args) {
return ( return (
<section class="top"> <section class="top">
<div class="news"> <div class="news">
<h1>Support the game</h1> <h1>Support MNML</h1>
<p> <p>
<b>Credits</b> are in game currency used to change your team appearance: <b>Credits</b> are in game currency used to change your team appearance:
<ul> <ul>

View File

@ -5,8 +5,8 @@ const { tutorialStage } = require('../tutorial.utils');
const { genItemInfo } = require('./vbox.utils'); const { genItemInfo } = require('./vbox.utils');
const addState = connect( const addState = connect(
({ info, player, tutorial, vboxInfo, itemInfo, instance, comboPreview }) => ({ ({ info, player, tutorial, vboxInfo, itemInfo, instance, comboPreview, authenticated }) => ({
info, player, tutorial, vboxInfo, itemInfo, instance, comboPreview, info, player, tutorial, vboxInfo, itemInfo, instance, comboPreview, authenticated
})); }));
@ -35,12 +35,13 @@ class Info extends preact.Component {
itemInfo, itemInfo,
instance, instance,
comboPreview, comboPreview,
authenticated,
} = props; } = props;
// dispaly priority // dispaly priority
// tutorial -> comboPreview -> vboxInfo -> info // tutorial -> comboPreview -> vboxInfo -> info
if (tutorial) { if (tutorial) {
const tutorialStageInfo = tutorialStage(tutorial, clearTutorial, instance); const tutorialStageInfo = tutorialStage(authenticated, tutorial, clearTutorial, instance);
if (tutorialStageInfo) return tutorialStageInfo; if (tutorialStageInfo) return tutorialStageInfo;
} }
if (comboPreview) return genItemInfo(comboPreview, itemInfo, player); if (comboPreview) return genItemInfo(comboPreview, itemInfo, player);

View File

@ -116,7 +116,6 @@ function registerEvents(store) {
} }
store.dispatch(actions.setAccount(account)); store.dispatch(actions.setAccount(account));
store.dispatch(actions.setTutorial(null));
store.dispatch(actions.setAuthenticated(true)); store.dispatch(actions.setAuthenticated(true));
} }
@ -181,7 +180,7 @@ function registerEvents(store) {
setPvp(false); setPvp(false);
const player = v.players.find(p => p.id === account.id); const player = v.players.find(p => p.id === account.id);
store.dispatch(actions.setPlayer(player)); store.dispatch(actions.setPlayer(player));
if (tutorial) tutorialVbox(player, store, tutorial); if (tutorial) tutorialVbox(player, store, tutorial);
if (v.phase === 'Finished') { if (v.phase === 'Finished') {
@ -189,7 +188,6 @@ function registerEvents(store) {
} }
} }
return store.dispatch(actions.setInstance(v)); return store.dispatch(actions.setInstance(v));
} }

View File

@ -106,7 +106,7 @@ function tutorialVbox(player, store, tutorial) {
store.dispatch(actions.setTutorial(stage)); store.dispatch(actions.setTutorial(stage));
} }
function tutorialStage(tutorial, clearTutorial, instance) { function tutorialStage(authenticated, tutorial, clearTutorial, instance) {
if (!(instance.time_control === 'Practice' && instance.rounds.length === 1)) return false; if (!(instance.time_control === 'Practice' && instance.rounds.length === 1)) return false;
const exit = () => clearTutorial(); const exit = () => clearTutorial();
@ -118,6 +118,7 @@ function tutorialStage(tutorial, clearTutorial, instance) {
<h1>Welcome to MNML</h1> <h1>Welcome to MNML</h1>
<p> This is the <b>VBOX Phase</b> where you customise your team. </p> <p> This is the <b>VBOX Phase</b> where you customise your team. </p>
<p> Buy the two colours from the store to continue. </p> <p> Buy the two colours from the store to continue. </p>
<p> <b>PRO TIP:</b> While selecting an item in the shop, click the stash to buy it. </p>
</div> </div>
); );
} }
@ -128,7 +129,7 @@ function tutorialStage(tutorial, clearTutorial, instance) {
<h2>Combining Items</h2> <h2>Combining Items</h2>
<p> You start the game with the <b>Attack</b> base skill item. </p> <p> You start the game with the <b>Attack</b> base skill item. </p>
<p> Create powerful combinations by combining colours with base items. </p> <p> Create powerful combinations by combining colours with base items. </p>
<p> Select all three items to <b> combine</b>. </p> <p> Select <b>all three items</b> to <b> combine</b>. </p>
</div> </div>
); );
} }
@ -199,7 +200,6 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (window.innerWidth < 1000) { if (window.innerWidth < 1000) {
return exit(); return exit();
} }
return ( return (
<div class='info-item'> <div class='info-item'>
<h2>GLHF</h2> <h2>GLHF</h2>
@ -209,6 +209,7 @@ function tutorialStage(tutorial, clearTutorial, instance) {
</div> </div>
); );
} }
return false; return false;
}; };
@ -219,11 +220,18 @@ function tutorialStage(tutorial, clearTutorial, instance) {
onMouseDown={exit}> Continue </button> onMouseDown={exit}> Continue </button>
: null; : null;
const skipTutorial = authenticated && !exitTutorial ?
<button
onClick={e => e.stopPropagation()}
onMouseDown={exit}> Skip Tutorial </button>
: null;
return ( return (
<div class='tutorial'> <div class='tutorial'>
{tutorialText()} {tutorialText()}
<figure> <figure>
{exitTutorial} {exitTutorial}
{skipTutorial}
</figure> </figure>
</div>); </div>);
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "mnml_core" name = "mnml_core"
version = "1.12.1" version = "1.12.2"
authors = ["ntr <ntr@smokestack.io>", "mashy <mashy@mnml.gg>"] authors = ["ntr <ntr@smokestack.io>", "mashy <mashy@mnml.gg>"]
[dependencies] [dependencies]

View File

@ -911,7 +911,7 @@ impl Construct {
construct: self.id, construct: self.id,
amount: healing, amount: healing,
overhealing, overhealing,
colour: Colour::Red, colour: Colour::Blue,
display: EventConstruct::new(self), display: EventConstruct::new(self),
}); });
} }
@ -933,7 +933,7 @@ impl Construct {
construct: self.id, construct: self.id,
amount: blue_damage_amount, amount: blue_damage_amount,
mitigation: blue_mitigation, mitigation: blue_mitigation,
colour: Colour::Red, colour: Colour::Blue,
display: EventConstruct::new(self), display: EventConstruct::new(self),
}); });
} }

View File

@ -2082,7 +2082,7 @@ impl Purify {
fn purify(cast: Cast, game: &mut Game, values: Purify) { fn purify(cast: Cast, game: &mut Game, values: Purify) {
let gp = game.value(Value::Stat { construct: cast.source, stat: Stat::GreenPower }); let gp = game.value(Value::Stat { construct: cast.source, stat: Stat::GreenPower });
let rms = game.value(Value::Removals { construct: cast.target }); let rms = game.value(Value::Effects { construct: cast.target });
let amount = gp.pct(values.green_heal_base().saturating_mul(rms)); let amount = gp.pct(values.green_heal_base().saturating_mul(rms));
game.action(cast, game.action(cast,

View File

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

View File

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

View File

@ -530,3 +530,24 @@ pub fn img_check(account: &Account) -> Result<Uuid, Error> {
} }
} }
pub fn _tutorial(tx: &mut Transaction, account: &Account) -> Result<Option<Instance>, Error> {
let query = "
SELECT count(id)
FROM players
WHERE account = $1;
";
let result = tx
.query(query, &[&account.id])?;
let row = result.iter().next()
.ok_or(format_err!("unable to fetch joined games account={:?}", account))?;
let count: i64 = row.get(0);
if count == 0 {
return Ok(Some(instance_practice(tx, account)?));
}
return Ok(None);
}

View File

@ -25,7 +25,7 @@ use payments::{stripe};
pub const TOKEN_HEADER: &str = "x-auth-token"; pub const TOKEN_HEADER: &str = "x-auth-token";
pub const AUTH_CLEAR: &str = pub const AUTH_CLEAR: &str =
"x-auth-token=; HttpOnly; SameSite=Strict; Path=/; Max-Age=-1;"; "x-auth-token=; HttpOnly; SameSite=None; Path=/; Max-Age=-1;";
#[derive(Clone, Copy, Fail, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Fail, Debug, Serialize, Deserialize)]
pub enum MnmlHttpError { pub enum MnmlHttpError {
@ -191,7 +191,7 @@ impl AfterMiddleware for ErrorHandler {
fn token_res(token: String) -> Response { fn token_res(token: String) -> Response {
let v = Cookie::build(TOKEN_HEADER, token) let v = Cookie::build(TOKEN_HEADER, token)
.http_only(true) .http_only(true)
.same_site(SameSite::Strict) .same_site(SameSite::None)
.path("/") .path("/")
.max_age(Duration::weeks(1)) // 1 week aligns with db set .max_age(Duration::weeks(1)) // 1 week aligns with db set
.finish(); .finish();
@ -354,7 +354,7 @@ fn recover(req: &mut Request) -> IronResult<Response> {
let v = Cookie::build(TOKEN_HEADER, token) let v = Cookie::build(TOKEN_HEADER, token)
.http_only(true) .http_only(true)
.same_site(SameSite::Strict) .same_site(SameSite::None)
.path("/") .path("/")
.max_age(Duration::weeks(1)) // 1 week aligns with db set .max_age(Duration::weeks(1)) // 1 week aligns with db set
.finish(); .finish();

View File

@ -232,6 +232,12 @@ pub fn set(tx: &mut Transaction, account: Uuid, email: &String) -> Result<(Uuid,
RETURNING id; RETURNING id;
"; ";
let select_query = "
SELECT *
FROM emails
WHERE account = $1;
";
let update_query = " let update_query = "
UPDATE emails UPDATE emails
SET email = $1, confirm_token = $2, confirmed = false, recover_token = $3 SET email = $1, confirm_token = $2, confirmed = false, recover_token = $3
@ -239,18 +245,11 @@ pub fn set(tx: &mut Transaction, account: Uuid, email: &String) -> Result<(Uuid,
RETURNING id; RETURNING id;
"; ";
let result = match tx.query(insert_query, &[&id, &account, &email, &confirm_token, &recover_token]) { let existing = tx.query(select_query, &[&id])?;
Ok(r) => r,
// email update probably let result = match existing.iter().next() {
Err(_) => { Some(_) => tx.query(insert_query, &[&id, &account, &email, &confirm_token, &recover_token])?,
match tx.query(update_query, &[&email, &confirm_token, &recover_token, &account]) { None => tx.query(update_query, &[&email, &confirm_token, &recover_token, &account])?,
Ok(r) => r,
Err(e) => {
warn!("{:?}", e);
return Err(err_msg("no email set"));
},
}
}
}; };
match result.iter().next() { match result.iter().next() {

View File

@ -128,6 +128,7 @@ pub enum RpcRequest {
pub trait User { pub trait User {
fn receive(&mut self, data: Vec<u8>, stripe: &StripeClient) -> Result<RpcMessage, Error>; fn receive(&mut self, data: Vec<u8>, stripe: &StripeClient) -> Result<RpcMessage, Error>;
fn connected(&mut self) -> Result<(), Error>; fn connected(&mut self) -> Result<(), Error>;
fn disconnected(&self) -> Result<(), Error>;
fn send(&mut self, msg: RpcMessage) -> Result<(), Error>; fn send(&mut self, msg: RpcMessage) -> Result<(), Error>;
} }
@ -170,8 +171,8 @@ impl Handler for Connection {
} }
fn on_close(&mut self, _: CloseCode, _: &str) { fn on_close(&mut self, _: CloseCode, _: &str) {
info!("websocket disconnected id={:?}", self.id);
self.events.send(Event::Disconnect(self.id)).unwrap(); self.events.send(Event::Disconnect(self.id)).unwrap();
self.user.disconnected().unwrap();
} }
fn on_request(&mut self, req: &Request) -> ws::Result<Response> { fn on_request(&mut self, req: &Request) -> ws::Result<Response> {
@ -198,7 +199,10 @@ impl Handler for Connection {
if cookie.name() == TOKEN_HEADER { if cookie.name() == TOKEN_HEADER {
let db = self.pool.get().unwrap(); let db = self.pool.get().unwrap();
match account::from_token(&db, &cookie.value().to_string()) { match account::from_token(&db, &cookie.value().to_string()) {
Ok(a) => self.user = Box::new(Authenticated::new(a, self.ws.clone(), self.events.clone(), self.pool.clone())), Ok(a) => {
self.id = a.id;
self.user = Box::new(Authenticated::new(a, self.ws.clone(), self.events.clone(), self.pool.clone()));
},
Err(_) => return unauth(), Err(_) => return unauth(),
} }
} }

View File

@ -74,6 +74,10 @@ impl User for Anonymous {
Ok(()) Ok(())
} }
fn disconnected(&self) -> Result<(), Error> {
Ok(())
}
fn receive(&mut self, data: Vec<u8>, _stripe: &StripeClient) -> Result<RpcMessage, Error> { fn receive(&mut self, data: Vec<u8>, _stripe: &StripeClient) -> Result<RpcMessage, Error> {
match from_slice::<RpcRequest>(&data) { match from_slice::<RpcRequest>(&data) {
Ok(v) => { Ok(v) => {

View File

@ -128,12 +128,21 @@ impl User for Authenticated {
let wheel = account::chat_wheel(&db, a.id)?; let wheel = account::chat_wheel(&db, a.id)?;
self.ws.send(RpcMessage::ChatWheel(wheel))?; self.ws.send(RpcMessage::ChatWheel(wheel))?;
// if let Some(instance) = account::tutorial(&mut tx, &a)? {
// self.ws.send(RpcMessage::InstanceState(instance))?;
// }
// tx should do nothing // tx should do nothing
tx.commit()?; tx.commit()?;
Ok(()) Ok(())
} }
fn disconnected(&self) -> Result<(), Error> {
info!("user disconnected account={:?}", self.account);
Ok(())
}
fn receive(&mut self, data: Vec<u8>, stripe: &StripeClient) -> Result<RpcMessage, Error> { fn receive(&mut self, data: Vec<u8>, stripe: &StripeClient) -> Result<RpcMessage, Error> {
// cast the msg to this type to receive method name // cast the msg to this type to receive method name
let begin = Instant::now(); let begin = Instant::now();

View File

@ -1,6 +1,6 @@
{ {
"name": "mnml-studios", "name": "mnml-studios",
"version": "1.12.1", "version": "1.12.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {