Merge branch 'release/1.12.0'

This commit is contained in:
ntr 2020-01-10 12:34:24 +10:00
commit d1606e9117
32 changed files with 460 additions and 150 deletions

View File

@ -1 +1 @@
1.11.2
1.12.0

View File

@ -3,32 +3,16 @@
_ntr_
* can't reset password without knowing password =\
* change cooldowns to delay & recharge
- delay is cooldown before skill can first be used
- recharge is cooldown after using skill
- every x speed reduces delay of skills
* audio
* animation effects
* vbox combine / buy / equip etc
* background music
* effects rework
Siphon =
[
DamageBlue(50%),
Apply(
Siphon(2T)
- Siphoning(2T)
),
]
Hexagon Set
- Pick Colour
- Random Walk
- Draw hex
- Increase intensity for each visit
_mashy_
* combat text
- last 3-4 text events (damage / heal / disable etc)
* rebalance
* speed specs
* life specs
@ -111,6 +95,11 @@ _tba_
* treats
* client animation bpm
* background colour changes depending on time of day
Hexagon Set
- Pick Colour
- Random Walk
- Draw hex
- Increase intensity for each visit
# Mechanics
* 10d chaos maths, not rock paper scissors

View File

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

View File

@ -100,6 +100,12 @@ section {
}
}
.block-text {
letter-spacing: 0.25em;
text-transform: uppercase;
text-align: center;
}
.list {
margin-bottom: 2em;

View File

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

View File

@ -19,8 +19,8 @@ const addState = connect(
} = state;
function sendSetPassword(current, password) {
postData('/account/password', { current, password })
function sendSetPassword(password) {
postData('/account/password', { password })
.then(res => res.json())
.then(data => {
if (data.error) return errorToast(data.error);
@ -74,7 +74,7 @@ class AccountStatus extends Component {
super(props);
this.state = {
passwordState: { current: '', password: '', confirm: ''},
passwordState: { password: '', confirm: ''},
emailState: null,
unsubState: false,
};
@ -105,8 +105,8 @@ class AccountStatus extends Component {
passwordState.password === passwordState.confirm;
const setPasswordDisabled = () => {
const { current, password, confirm } = passwordState;
return !(passwordsEqual() && password && current && confirm);
const { password, confirm } = passwordState;
return !(passwordsEqual() && password && confirm);
}
const tlClick = e => {
@ -173,15 +173,7 @@ class AccountStatus extends Component {
</div>
<div>
<h3>Password</h3>
<label for="current">Password:</label>
<input
class="login-input"
type="password"
name="current"
value={passwordState.current}
onInput={linkState(this, 'passwordState.current')}
placeholder="current"
/>
<label for="current">Set Password:</label>
<input
class="login-input"
type="password"
@ -202,7 +194,7 @@ class AccountStatus extends Component {
/>
<button
disabled={setPasswordDisabled()}
onClick={() => sendSetPassword(passwordState.current, passwordState.password)}>
onClick={() => sendSetPassword(passwordState.password)}>
Set Password
</button>
</div>

View File

@ -14,6 +14,7 @@ const addState = connect(
const {
ws,
account,
tutorial,
} = state;
function sendInstancePractice() {
@ -21,6 +22,7 @@ const addState = connect(
}
return {
promptRegister: tutorial === 99, // see events
account,
sendInstancePractice,
};
@ -30,6 +32,7 @@ const addState = connect(
function Play(args) {
const {
account,
promptRegister,
sendInstancePractice,
} = args;
@ -47,6 +50,17 @@ function Play(args) {
);
const list = () => {
if (promptRegister) {
return (
<div class='block-text'>
<p><b>You just won your first round of MNML.</b></p>
<p>Register below to play a real Bo5 against other players, play a practice round, customise your team & more...</p>
<p>glhf</p>
</div>
)
}
return (
<div class='list play'>
<figure>

View File

@ -9,6 +9,7 @@ const addState = connect(
ws,
game,
account,
authenticated,
chatShow,
animating,
} = state;
@ -33,6 +34,7 @@ const addState = connect(
return {
game,
account,
authenticated,
chatShow,
sendAbandon,
sendGameSkillClear,
@ -65,6 +67,7 @@ function GameCtrlBtns(args) {
animating,
account,
chatShow,
authenticated,
getInstanceState,
sendGameSkillClear,
@ -77,7 +80,9 @@ function GameCtrlBtns(args) {
const finished = game.phase === 'Finished';
function quitClick() {
if (authenticated) {
getInstanceState();
}
quit();
}

View File

@ -9,6 +9,7 @@ const addState = connect(
ws,
game,
animating,
authenticated,
account,
} = state;
@ -27,6 +28,7 @@ const addState = connect(
return {
game,
account,
authenticated,
sendAbandon,
sendDraw,
@ -50,6 +52,7 @@ function GameCtrlTopBtns(args) {
const {
game,
account,
authenticated,
leave,
sendAbandon,
@ -82,6 +85,11 @@ function GameCtrlTopBtns(args) {
setTimeout(() => this.setState({ concedeState: false }), 2000);
};
const authBtn = btn => {
if (authenticated) return btn;
return <button disabled>-</button>
}
const abandonClasses = `abandon ${abandonState ? 'confirming' : ''}`;
const abandonText = abandonState ? 'Confirm' : 'Abandon';
const abandonAction = abandonState ? sendAbandon : abandonStateTrue;
@ -102,9 +110,9 @@ function GameCtrlTopBtns(args) {
return (
<div class="instance-ctrl-btns">
{abandonBtn}
{concedeBtn}
{drawBtn}
{authBtn(abandonBtn)}
{authBtn(concedeBtn)}
{authBtn(drawBtn)}
</div>
);
}

View File

@ -7,6 +7,7 @@ const addState = connect(
function receiveState(state) {
const {
ws,
authenticated,
instance,
tutorial,
} = state;
@ -17,6 +18,7 @@ const addState = connect(
return {
instance,
authenticated,
tutorial,
sendAbandon,
};
@ -39,6 +41,7 @@ const addState = connect(
function InstanceTopBtns(args) {
const {
instance,
authenticated,
leave,
sendAbandon,
@ -61,9 +64,16 @@ function InstanceTopBtns(args) {
const abandonBtn = <button class={abandonClasses} disabled={finished} onClick={abandonAction}>{abandonText}</button>;
const leaveBtn = <button class='abandon confirming' onClick={() => leave(tutorial)}>Leave</button>;
const finalBtn = () => {
// disable for tutorial mode
if (!authenticated) return <button disabled='true'>-</button>;
if (finished) return leaveBtn;
return abandonBtn;
}
return (
<div class="instance-ctrl-btns">
{finished ? leaveBtn : abandonBtn}
{finalBtn()}
</div>
);
}

View File

@ -232,7 +232,6 @@ function Play(args) {
<div>
Join our Discord server to find opponents and talk to the devs. <br />
Message <b>@ntr</b> or <b>@mashy</b> for some credits to get started.<br />
<a href='https://www.youtube.com/watch?v=VtZLlkpJuS8'>Tutorial Playthrough on YouTube</a>
</div>
<br />
<div>

View File

@ -96,7 +96,7 @@ function genItemInfo(item, itemInfo, player) {
itemSourceInfo = reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]());
}
const cooldown = isSkill && fullInfo.cooldown ? <div>{fullInfo.cooldown} Turn delay</div> : null;
const cooldown = isSkill && fullInfo.cooldown ? <div>{fullInfo.delay} turn delay, {fullInfo.cooldown} turn cooldown</div> : null;
const speed = isSkill
? <div> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </div>

View File

@ -1,13 +1,31 @@
// eslint-disable-next-line
const preact = require('preact');
const { connect } = require('preact-redux');
const Login = require('./welcome.login');
const Register = require('./welcome.register');
const Help = require('./welcome.help');
// const About = require('./welcome.about');
function Welcome() {
const page = this.state.page || 'login';
const addState = connect(
function receiveState(state) {
const {
tutorial,
} = state;
return {
promptRegister: tutorial === 99, // see events
};
},
);
function Welcome(args) {
const {
promptRegister,
} = args;
const page = this.state.page || promptRegister && 'register' || 'login';
const pageEl = () => {
if (page === 'login') return <Login />;
@ -45,4 +63,4 @@ function Welcome() {
);
}
module.exports = Welcome;
module.exports = addState(Welcome);

View File

@ -71,7 +71,7 @@ module.exports = {
},
speedStat: {
item: 'SPEED',
description: 'Speed determines the order in which skills resolve.\nCombine SPEED specs to increase speed.',
description: 'Speed determines the order in which skills resolve.\nThe initial delay of skills is reduced by 1 turn for every 250 speed.\nCombine SPEED specs to increase speed.',
},
},
};

View File

@ -218,6 +218,10 @@ function registerEvents(store) {
store.dispatch(actions.setTutorial(1));
}
function promptRegister() {
store.dispatch(actions.setTutorial(99));
store.dispatch(actions.setInstance(null));
}
window.addEventListener('hashchange', urlHashChange, false);
@ -250,6 +254,7 @@ function registerEvents(store) {
setWs,
startTutorial,
promptRegister,
urlHashChange,

View File

@ -301,6 +301,7 @@ function createSocket(events) {
// Joining: () => events.notify('Searching for instance...'),
StartTutorial: () => events.startTutorial(),
PromptRegister: () => events.promptRegister(),
Processing: () => true,
Error: errHandler,

View File

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

View File

@ -52,10 +52,14 @@ impl ConstructSkill {
pub fn new(skill: Skill) -> ConstructSkill {
ConstructSkill {
skill,
cd: skill.base_cd(),
cd: skill.delay(),
disabled: false,
}
}
pub fn set_cooldown(&mut self, cd: Cooldown) -> () {
self.cd = cd;
}
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
@ -432,15 +436,34 @@ impl Construct {
self.skills.iter().find(|s| s.skill == skill && s.cd.is_some())
}
pub fn set_construct_delays(&mut self) -> () {
// for every multiple of speed threshold delays are reduced by 1 at start of game
let speed_threshold = 250;
let delay_reduction = self.speed.value.wrapping_div(speed_threshold);
self.skills
.iter_mut()
.for_each(|s| match s.skill.delay() {
Some(cd) => match cd.saturating_sub(delay_reduction) {
0 => s.set_cooldown(None),
_ => s.set_cooldown(Some(cd.saturating_sub(delay_reduction)))
},
None => s.set_cooldown(None)
});
}
pub fn skill_set_cd(&mut self, skill: Skill) -> &mut Construct {
// println!("{:?} {:?} skill cooldown set", self.name, skill);
// tests force resolve some skills
// which cause the game to attempt to put them on cd
// even though the construct doesn't know the skill
if let Some(i) = self.skills.iter().position(|s| s.skill == skill) {
self.skills.remove(i);
self.skills.insert(i, ConstructSkill::new(skill));
// if let Some(i) = self.skills.iter().position(|s| s.skill == skill) {
// self.skills.remove(i);
// self.skills.insert(i, ConstructSkill::new(skill));
//}
if let Some(sk) = self.skills.iter_mut().find(|s| s.skill == skill) {
sk.set_cooldown(skill.base_cd());
}
self
@ -1218,7 +1241,7 @@ mod tests {
i += 1;
}
assert_eq!(i, Skill::Sleep.base_cd().unwrap());
assert_eq!(i, Skill::Sleep.delay().unwrap());
}
}

View File

@ -104,7 +104,6 @@ impl Game {
// let player_description = player.constructs.iter().map(|c| c.name.clone()).collect::<Vec<String>>().join(", ");
// self.log.push(format!("{:} has joined the game. [{:}]", player.name, player_description));
player.constructs.sort_unstable_by_key(|c| c.id);
self.players.push(player);
Ok(self)
@ -140,12 +139,19 @@ impl Game {
&& self.players.iter().all(|t| t.constructs.len() == self.player_constructs)
}
pub fn start(self) -> Game {
pub fn start(mut self) -> Game {
// both forfeit ddue to no skills
if self.finished() {
return self.finish();
}
self.players
.iter_mut()
.for_each(|p| p.constructs
.iter_mut()
.for_each(|c| c.set_construct_delays())
);
self.skill_phase_start(0)
}
@ -1163,6 +1169,44 @@ mod tests {
return;
}
#[test]
fn delay_test() {
let mut x = Construct::new()
.named(&"pronounced \"creeep\"".to_string())
.learn(Skill::Ruin);
let mut y = Construct::new()
.named(&"lemongrass tea".to_string())
.learn(Skill::Ruin);
// Ruin has 2 turn cd
// 250 speed = 1 cd delay reduction
x.speed.force(499);
y.speed.force(700);
let mut game = Game::new();
game.set_player_num(2).set_player_constructs(1);
let x_player_id = Uuid::new_v4();
x.account = x_player_id;
let x_player = Player::new(x_player_id, None, &"ntr".to_string(), vec![x]);
let y_player_id = Uuid::new_v4();
y.account = y_player_id;
let y_player = Player::new(y_player_id, None, &"mash".to_string(), vec![y]);
game
.player_add(x_player).unwrap()
.player_add(y_player).unwrap();
game = game.start();
assert!(game.players[0].constructs[0].skill_on_cd(Skill::Ruin).is_some());
assert!(game.players[1].constructs[0].skill_on_cd(Skill::Ruin).is_none());
}
#[test]
fn stun_test() {
let mut game = create_test_game();
@ -1240,7 +1284,7 @@ mod tests {
// should auto progress back to skill phase
assert!(game.phase == Phase::Skill);
assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some());
assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_none());
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none());
game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap();

View File

@ -1362,6 +1362,7 @@ pub struct ItemInfo {
pub values: Option<SpecValues>,
pub skill: bool,
pub speed: Option<usize>,
pub delay: Cooldown,
pub cooldown: Cooldown,
pub description: String,
}
@ -1406,6 +1407,10 @@ pub fn item_info() -> ItemInfoCtr {
Some(s) => s.base_cd(),
None => None
},
delay: match v.into_skill() {
Some(s) => s.delay(),
None => None
},
})
.collect::<Vec<ItemInfo>>();

View File

@ -65,7 +65,9 @@ pub struct Player {
}
impl Player {
pub fn new(account: Uuid, img: Option<Uuid>, name: &String, constructs: Vec<Construct>) -> Player {
pub fn new(account: Uuid, img: Option<Uuid>, name: &String, mut constructs: Vec<Construct>) -> Player {
constructs.sort_unstable_by_key(|c| c.id);
Player {
id: account,
img,

View File

@ -522,6 +522,157 @@ impl Skill {
Skill::SustainPlus |
Skill::SustainPlusPlus => Some(1),
Skill::Intercept => Some(2),
Skill::InterceptPlus => Some(2),
Skill::InterceptPlusPlus => Some(2),
Skill::Electrify |
Skill::ElectrifyPlus |
Skill::ElectrifyPlusPlus => None,
Skill::Absorb |
Skill::AbsorbPlus |
Skill::AbsorbPlusPlus => Some(1),
//-----------
// Never cast directly
//---------
// Trigger
Skill::HybridBlast |
Skill::HasteStrike |
Skill::CounterAttack|
Skill::CounterAttackPlus |
Skill::CounterAttackPlusPlus | // counter
Skill::Electrocute|
Skill::ElectrocutePlus |
Skill::ElectrocutePlusPlus |
Skill::Absorption|
Skill::AbsorptionPlus |
Skill::AbsorptionPlusPlus |
// Ticks
Skill::ElectrocuteTick|
Skill::DecayTick|
Skill::SiphonTick|
Skill::TriageTick => None,
}
}
pub fn delay(&self) -> Cooldown {
match self {
Skill::Attack => None,
Skill::Block => None, // reduce damage
Skill::Buff => None,
Skill::Debuff => None,
Skill::Stun => Some(1),
Skill::Strike=> None,
Skill::StrikePlus => None,
Skill::StrikePlusPlus => None,
Skill::Counter|
Skill::CounterPlus |
Skill::CounterPlusPlus => None,
Skill::Restrict |
Skill::RestrictPlus |
Skill::RestrictPlusPlus => Some(1),
Skill::Bash |
Skill::BashPlus |
Skill::BashPlusPlus => Some(1),
Skill::Heal=> None,
Skill::HealPlus => None,
Skill::HealPlusPlus => None,
Skill::Triage=> None, // hot
Skill::TriagePlus => None, // hot
Skill::TriagePlusPlus => None, // hot
Skill::Break | // no damage stun, adds vulnerable
Skill::BreakPlus |
Skill::BreakPlusPlus => Some(1),
Skill::Blast |
Skill::BlastPlus |
Skill::BlastPlusPlus => None,
Skill::Chaos |
Skill::ChaosPlus |
Skill::ChaosPlusPlus => None,
Skill::Amplify |
Skill::AmplifyPlus |
Skill::AmplifyPlusPlus => Some(1),
Skill::Hybrid |
Skill::HybridPlus |
Skill::HybridPlusPlus => Some(1),
Skill::Invert |
Skill::InvertPlus |
Skill::InvertPlusPlus => Some(2),
Skill::Decay => None, // dot
Skill::DecayPlus => None,
Skill::DecayPlusPlus => None,
Skill::Siphon|
Skill::SiphonPlus |
Skill::SiphonPlusPlus => None,
Skill::Curse |
Skill::CursePlus |
Skill::CursePlusPlus => Some(1),
Skill::Link |
Skill::LinkPlus |
Skill::LinkPlusPlus => Some(1),
Skill::Silence |
Skill::SilencePlus |
Skill::SilencePlusPlus => Some(1),
Skill::Purify |
Skill::PurifyPlus |
Skill::PurifyPlusPlus => None,
Skill::Purge |
Skill::PurgePlus |
Skill::PurgePlusPlus => Some(1),
Skill::Banish |
Skill::BanishPlus |
Skill::BanishPlusPlus => Some(1),
Skill::Haste |
Skill::HastePlus |
Skill::HastePlusPlus => Some(1),
Skill::Reflect |
Skill::ReflectPlus |
Skill::ReflectPlusPlus => None,
Skill::Recharge |
Skill::RechargePlus |
Skill::RechargePlusPlus => None,
Skill::Ruin |
Skill::RuinPlus |
Skill::RuinPlusPlus => Some(2),
Skill::Slay=> None,
Skill::SlayPlus => None,
Skill::SlayPlusPlus => None,
Skill::Sleep |
Skill::SleepPlus |
Skill::SleepPlusPlus => Some(1),
Skill::Sustain |
Skill::SustainPlus |
Skill::SustainPlusPlus => Some(1),
Skill::Intercept => Some(1),
Skill::InterceptPlus => Some(1),
Skill::InterceptPlusPlus => Some(1),

View File

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

View File

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

View File

@ -213,42 +213,42 @@ pub fn new_img(tx: &mut Transaction, id: Uuid) -> Result<Account, Error> {
Account::try_from(row)
}
pub fn set_password(tx: &mut Transaction, id: Uuid, current: &String, password: &String) -> Result<String, MnmlHttpError> {
pub fn set_password(tx: &mut Transaction, id: Uuid, password: &String) -> Result<String, MnmlHttpError> {
if password.len() < PASSWORD_MIN_LEN || password.len() > 100 {
return Err(MnmlHttpError::PasswordUnacceptable);
}
let query = "
SELECT id, password
FROM accounts
WHERE id = $1
";
// let query = "
// SELECT id, password
// FROM accounts
// WHERE id = $1
// ";
let result = tx
.query(query, &[&id])?;
// let result = tx
// .query(query, &[&id])?;
let row = match result.iter().next() {
Some(row) => row,
None => {
let mut rng = thread_rng();
let garbage: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.take(64)
.collect();
// let row = match result.iter().next() {
// Some(row) => row,
// None => {
// let mut rng = thread_rng();
// let garbage: String = iter::repeat(())
// .map(|()| rng.sample(Alphanumeric))
// .take(64)
// .collect();
// verify garbage to prevent timing attacks
verify(garbage.clone(), &garbage).ok();
return Err(MnmlHttpError::AccountNotFound);
},
};
// // verify garbage to prevent timing attacks
// verify(garbage.clone(), &garbage).ok();
// return Err(MnmlHttpError::AccountNotFound);
// },
// };
let id: Uuid = row.get(0);
let db_pw: String = row.get(1);
// let id: Uuid = row.get(0);
// let db_pw: String = row.get(1);
// return bad request to prevent being logged out
if !verify(current, &db_pw)? {
return Err(MnmlHttpError::BadRequest);
}
// // return bad request to prevent being logged out
// if !verify(current, &db_pw)? {
// return Err(MnmlHttpError::BadRequest);
// }
let password = hash(&password, PASSWORD_ROUNDS)?;

View File

@ -369,7 +369,7 @@ fn recover(req: &mut Request) -> IronResult<Response> {
#[derive(Debug,Clone,Deserialize)]
struct SetPassword {
current: String,
// current: String,
password: String,
}
@ -385,7 +385,7 @@ fn set_password(req: &mut Request) -> IronResult<Response> {
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
let token = account::set_password(&mut tx, a.id, &params.current, &params.password)?;
let token = account::set_password(&mut tx, a.id, &params.password)?;
tx.commit().or(Err(MnmlHttpError::ServerError))?;

View File

@ -42,10 +42,10 @@ pub enum Mail {
fn recover(email: &String, name: &String, token: &String) -> SendableEmail {
let body = format!("{:},
the link below will recover your account.
please change your password immediately in the account page.
this link will expire in 48 hours or once used.
please change your password immediately in the account page
as this link will expire in 48 hours or once used.
http://mnml.gg/api/account/recover?recover_token={:}
https://mnml.gg/api/account/recover?recover_token={:}
glhf
--mnml", name, token);
@ -63,7 +63,7 @@ glhf
fn confirm(email: &String, name: &String, token: &String) -> SendableEmail {
let confirm_body = format!("{:},
please click the link below to confirm your email
http://mnml.gg/api/account/email/confirm?confirm_token={:}
https://mnml.gg/api/account/email/confirm?confirm_token={:}
glhf
--mnml", name, token);

View File

@ -695,11 +695,17 @@ pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result<Inst
}
pub fn instance_demo(account: &Account) -> Result<Instance, Error> {
let bot = bot_player();
let mut bot = bot_player();
let bot_id = bot.id;
// generate imgs for the client to see
for c in bot.constructs.iter() {
for c in bot.constructs.iter_mut() {
// smash these nubs
c.green_life.force(64);
c.red_life.force(0);
c.blue_life.force(0);
img::shapes_write(c.img)?;
}
@ -709,6 +715,7 @@ pub fn instance_demo(account: &Account) -> Result<Instance, Error> {
let player = anon_player(account.id);
// smash these noobs
for c in player.constructs.iter() {
img::shapes_write(c.img)?;
}

View File

@ -2,7 +2,6 @@ use mnml_core::item::ItemInfoCtr;
use mnml_core::instance::ChatState;
use std::collections::HashMap;
use std::time::{Instant};
use std::thread::{spawn};
use std::str;
@ -23,7 +22,7 @@ use account;
use events::{Event};
use user_anonymous::{Anonymous};
use user_authenticated::{Authorised};
use user_authenticated::{Authenticated};
use mnml_core::construct::{Construct};
use mnml_core::game::{Game};
@ -37,7 +36,7 @@ use mnml_core::instance::{Instance};
use mtx;
use mail::Email;
use pg::{Db};
use pg::{PgPool};
use http::{AUTH_CLEAR, TOKEN_HEADER};
@ -63,6 +62,7 @@ pub enum RpcMessage {
Pong(()),
StartTutorial(()),
PromptRegister(()),
QueueRequested(()),
QueueJoined(()),
@ -126,9 +126,9 @@ pub enum RpcRequest {
}
pub trait User {
fn receive(&mut self, data: Vec<u8>, db: &Db, begin: Instant, events: &CbSender<Event>, stripe: &StripeClient) -> Result<RpcMessage, Error>;
fn connected(&mut self, db: &Db, events: &CbSender<Event>, ws: &CbSender<RpcMessage>) -> Result<(), Error>;
fn send(&mut self, msg: RpcMessage, events: &CbSender<Event>, ws: &CbSender<RpcMessage>) -> Result<(), Error>;
fn receive(&mut self, data: Vec<u8>, stripe: &StripeClient) -> Result<RpcMessage, Error>;
fn connected(&mut self) -> Result<(), Error>;
fn send(&mut self, msg: RpcMessage) -> Result<(), Error>;
}
struct Connection {
@ -165,20 +165,16 @@ impl Connection {
// when it encounters errors
impl Handler for Connection {
fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
let db = self.pool.get().unwrap();
self.user.connected(&db, &self.events, &self.ws).unwrap();
self.user.connected().unwrap();
Ok(())
}
fn on_message(&mut self, msg: Message) -> ws::Result<()> {
match msg {
Message::Binary(msg) => {
let begin = Instant::now();
let db_connection = self.pool.get().unwrap();
match self.user.receive(msg, &db_connection, begin, &self.events, &self.stripe) {
match self.user.receive(msg, &self.stripe) {
Ok(msg) => {
self.user.send(msg, &self.events, &self.ws).unwrap();
self.user.send(msg).unwrap();
},
Err(e) => {
warn!("{:?}", e);
@ -220,7 +216,7 @@ impl Handler for Connection {
if cookie.name() == TOKEN_HEADER {
let db = self.pool.get().unwrap();
match account::from_token(&db, &cookie.value().to_string()) {
Ok(a) => self.user = Box::new(Authorised { id: a.id, account: a }),
Ok(a) => self.user = Box::new(Authenticated::new(a, self.ws.clone(), self.events.clone(), self.pool.clone())),
Err(_) => return unauth(),
}
}
@ -268,11 +264,11 @@ pub fn start(pool: PgPool, events_tx: CbSender<Event>, stripe: StripeClient) {
DeflateHandler::new(
Connection {
id,
ws: tx,
ws: tx.clone(),
pool: pool.clone(),
stripe: stripe.clone(),
events: events_tx.clone(),
user: Box::new(Anonymous { id, account: anon_account, game: None, instance: None })
user: Box::new(Anonymous::new(anon_account, tx))
}
)
})

View File

@ -1,4 +1,3 @@
use std::time::Instant;
use uuid::Uuid;
use failure::Error;
@ -9,11 +8,8 @@ use crossbeam_channel::{Sender as CbSender};
use serde_cbor::{from_slice};
use stripe::{Client as StripeClient};
use account::{Account};
use pg::{Db};
use pg;
use events::{Event};
use rpc::{RpcMessage, RpcRequest, User};
use mnml_core::game::Game;
@ -26,10 +22,24 @@ pub struct Anonymous {
pub id: Uuid,
pub instance: Option<Instance>,
pub game: Option<Game>,
ws: CbSender<RpcMessage>,
}
impl Anonymous {
pub fn new(account: Account, ws: CbSender<RpcMessage>) -> Anonymous {
Anonymous {
id: account.id,
account,
ws,
instance: None,
game: None,
}
}
}
impl User for Anonymous {
fn send(&mut self, msg: RpcMessage, _events: &CbSender<Event>, ws: &CbSender<RpcMessage>) -> Result<(), Error> {
fn send(&mut self, msg: RpcMessage) -> Result<(), Error> {
// if the user queries the state of something
// we tell events to push updates to them
match msg {
@ -40,21 +50,21 @@ impl User for Anonymous {
_ => (),
};
ws.send(msg)?;
self.ws.send(msg)?;
Ok(())
}
fn connected(&mut self, _db: &Db, events: &CbSender<Event>, ws: &CbSender<RpcMessage>) -> Result<(), Error> {
fn connected(&mut self) -> Result<(), Error> {
info!("anonymous connection");
self.send(RpcMessage::AccountState(self.account.clone()), events, ws)?;
self.send(RpcMessage::StartTutorial(()), events, ws)?;
self.ws.send(RpcMessage::AccountState(self.account.clone()))?;
self.ws.send(RpcMessage::StartTutorial(()))?;
Ok(())
}
fn receive(&mut self, data: Vec<u8>, _db: &Db, _begin: Instant, _events: &CbSender<Event>, _stripe: &StripeClient) -> Result<RpcMessage, Error> {
fn receive(&mut self, data: Vec<u8>, _stripe: &StripeClient) -> Result<RpcMessage, Error> {
match from_slice::<RpcRequest>(&data) {
Ok(v) => {
let get_instance = || {
@ -139,6 +149,10 @@ impl User for Anonymous {
game = game.resolve_phase_start();
}
if game.finished() {
self.ws.send(RpcMessage::PromptRegister(()))?;
}
Ok(RpcMessage::GameState(game))
},

View File

@ -37,72 +37,90 @@ use events::{Event};
use mtx;
use mail;
use payments;
use pg::{Db};
use pg::{PgPool};
use rpc::{RpcMessage, RpcRequest, User};
#[derive(Debug,Clone)]
pub struct Authorised {
pub struct Authenticated {
pub account: Account,
pub id: Uuid
pub id: Uuid,
events: CbSender<Event>,
ws: CbSender<RpcMessage>,
pool: PgPool,
}
impl User for Authorised {
fn send(&mut self, msg: RpcMessage, events: &CbSender<Event>, ws: &CbSender<RpcMessage>) -> Result<(), Error> {
impl Authenticated {
pub fn new(account: Account, ws: CbSender<RpcMessage>, events: CbSender<Event>, pool: PgPool) -> Authenticated {
Authenticated {
id: account.id,
account,
ws,
events,
pool,
}
}
}
impl User for Authenticated {
fn send(&mut self, msg: RpcMessage) -> Result<(), Error> {
// if the user queries the state of something
// we tell events to push updates to them
match msg {
RpcMessage::AccountState(ref v) => {
events.send(Event::Subscribe(self.id, v.id))?
self.events.send(Event::Subscribe(self.id, v.id))?
},
RpcMessage::GameState(ref v) =>
events.send(Event::Subscribe(self.id, v.id))?,
self.events.send(Event::Subscribe(self.id, v.id))?,
RpcMessage::InstanceState(ref v) =>
events.send(Event::Subscribe(self.id, v.id))?,
self.events.send(Event::Subscribe(self.id, v.id))?,
_ => (),
};
ws.send(msg)?;
self.ws.send(msg)?;
Ok(())
}
fn connected(&mut self, db: &Db, events: &CbSender<Event>, ws: &CbSender<RpcMessage>) -> Result<(), Error> {
fn connected(&mut self) -> Result<(), Error> {
info!("authenticated connection account={:?}", self.account);
let a = &self.account;
ws.send(RpcMessage::AccountAuthenticated(a.clone()))?;
self.ws.send(RpcMessage::AccountAuthenticated(a.clone()))?;
// tell events we have connected
events.send(Event::Connect(self.id, a.clone(), ws.clone()))?;
self.events.send(Event::Connect(self.id, a.clone(), self.ws.clone()))?;
ws.send(RpcMessage::AccountState(a.clone()))?;
events.send(Event::Subscribe(self.id, a.id))?;
self.ws.send(RpcMessage::AccountState(a.clone()))?;
self.events.send(Event::Subscribe(self.id, a.id))?;
// check if they have an image that needs to be generated
account::img_check(&a)?;
let db = self.pool.get()?;
let mut tx = db.transaction()?;
// send account constructs
let account_constructs = account::constructs(&mut tx, &a)?;
ws.send(RpcMessage::AccountConstructs(account_constructs))?;
self.ws.send(RpcMessage::AccountConstructs(account_constructs))?;
// get account instances
// and send them to the client
let account_instances = account::account_instances(&mut tx, &a)?;
ws.send(RpcMessage::AccountInstances(account_instances))?;
self.ws.send(RpcMessage::AccountInstances(account_instances))?;
let shop = mtx::account_shop(&mut tx, &a)?;
ws.send(RpcMessage::AccountShop(shop))?;
self.ws.send(RpcMessage::AccountShop(shop))?;
let team = account::team(&mut tx, &a)?;
ws.send(RpcMessage::AccountTeam(team))?;
self.ws.send(RpcMessage::AccountTeam(team))?;
let wheel = account::chat_wheel(&db, a.id)?;
ws.send(RpcMessage::ChatWheel(wheel))?;
self.ws.send(RpcMessage::ChatWheel(wheel))?;
if let Some(instance) = account::tutorial(&mut tx, &a)? {
ws.send(RpcMessage::InstanceState(instance))?;
self.ws.send(RpcMessage::InstanceState(instance))?;
}
// tx should do nothing
@ -111,8 +129,11 @@ impl User for Authorised {
Ok(())
}
fn receive(&mut self, data: Vec<u8>, db: &Db, begin: Instant, events: &CbSender<Event>, 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
let begin = Instant::now();
let db = self.pool.get()?;
match from_slice::<RpcRequest>(&data) {
Ok(v) => {
let request = v.clone();
@ -123,19 +144,19 @@ impl User for Authorised {
return Ok(RpcMessage::GameState(anim_test_game(skill))),
RpcRequest::InstanceQueue {} => {
events.send(Event::Queue(self.id))?;
self.events.send(Event::Queue(self.id))?;
Ok(RpcMessage::QueueRequested(()))
},
RpcRequest::InstanceInvite {} => {
events.send(Event::Invite(self.id))?;
self.events.send(Event::Invite(self.id))?;
Ok(RpcMessage::InviteRequested(()))
},
RpcRequest::InstanceJoin { code } => {
events.send(Event::Join(self.id, code))?;
self.events.send(Event::Join(self.id, code))?;
Ok(RpcMessage::Joining(()))
},
RpcRequest::InstanceLeave {} => {
events.send(Event::Leave(self.id))?;
self.events.send(Event::Leave(self.id))?;
Ok(RpcMessage::Processing(()))
},
@ -147,7 +168,7 @@ impl User for Authorised {
let wheel = account::chat_wheel(&db, self.account.id)?;
if let Some(c) = wheel.get(index) {
events.send(Event::Chat(self.id, instance_id, c.to_string()))?;
self.events.send(Event::Chat(self.id, instance_id, c.to_string()))?;
} else {
return Err(err_msg("invalid chat index"));
}
@ -172,7 +193,7 @@ impl User for Authorised {
Ok(RpcMessage::EmailState(mail::select_account(&db, self.account.id)?)),
RpcRequest::SubscriptionState {} =>
Ok(RpcMessage::SubscriptionState(payments::account_subscription(db, stripe, &self.account)?)),
Ok(RpcMessage::SubscriptionState(payments::account_subscription(&db, stripe, &self.account)?)),
// RpcRequest::AccountShop {} =>
// Ok(RpcMessage::AccountShop(mtx::account_shop(&mut tx, &account)?)),

View File

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