Merge branch 'develop' of ssh://git.mnml.gg:40022/~/mnml into develop

This commit is contained in:
Mashy 2019-09-15 20:16:10 +10:00
commit d4ae38e733
21 changed files with 341 additions and 152 deletions

View File

@ -1 +1 @@
1.4.4 1.4.5

View File

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

View File

@ -53,7 +53,7 @@ aside {
border-color: forestgreen; border-color: forestgreen;
} }
&:active, &:focus { &:active, &:focus, &.enabled {
background: forestgreen; background: forestgreen;
color: black; color: black;
border-color: forestgreen; border-color: forestgreen;

View File

@ -286,3 +286,9 @@ li {
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
} }
#clipboard {
width: 1px;
height: 1px;
padding: 0px;
}

View File

@ -1,6 +1,6 @@
{ {
"name": "mnml-client", "name": "mnml-client",
"version": "1.4.4", "version": "1.4.5",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -29,6 +29,7 @@
"preact-compat": "^3.19.0", "preact-compat": "^3.19.0",
"preact-context": "^1.1.3", "preact-context": "^1.1.3",
"preact-redux": "^2.1.0", "preact-redux": "^2.1.0",
"query-string": "^6.8.3",
"react-string-replace": "^0.4.4", "react-string-replace": "^0.4.4",
"react-stripe-elements": "^3.0.0", "react-stripe-elements": "^3.0.0",
"redux": "^4.0.0" "redux": "^4.0.0"

View File

@ -19,6 +19,7 @@ export const setConstructRename = value => ({ type: 'SET_CONSTRUCT_RENAME', valu
export const setGame = value => ({ type: 'SET_GAME', value }); export const setGame = value => ({ type: 'SET_GAME', 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 setInstance = value => ({ type: 'SET_INSTANCE', value }); export const setInstance = value => ({ type: 'SET_INSTANCE', value });
export const setInstances = value => ({ type: 'SET_INSTANCES', value }); export const setInstances = value => ({ type: 'SET_INSTANCES', value });
export const setItemEquip = value => ({ type: 'SET_ITEM_EQUIP', value }); export const setItemEquip = value => ({ type: 'SET_ITEM_EQUIP', value });

View File

@ -76,7 +76,9 @@ function Controls(args) {
background: displayColour, background: displayColour,
}; };
const timer = ( const timer = instance.phase !== 'InProgress'
? null
: (
<div class="timer-container"> <div class="timer-container">
<div class="timer" style={timerStyles} >&nbsp;</div> <div class="timer" style={timerStyles} >&nbsp;</div>
</div> </div>

View File

@ -1,11 +1,14 @@
const preact = require('preact'); const preact = require('preact');
const { connect } = require('preact-redux'); const { connect } = require('preact-redux');
const { errorToast, infoToast } = require('../utils');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws, ws,
instances, instances,
invite,
} = state; } = state;
function sendInstanceState(id) { function sendInstanceState(id) {
@ -20,12 +23,18 @@ const addState = connect(
ws.sendInstanceQueue(); ws.sendInstanceQueue();
} }
function sendInstanceInvite() {
ws.sendInstanceInvite();
}
return { return {
instances, instances,
invite,
sendInstanceState, sendInstanceState,
sendInstanceQueue, sendInstanceQueue,
sendInstancePractice, sendInstancePractice,
sendInstanceInvite,
}; };
} }
); );
@ -33,10 +42,12 @@ const addState = connect(
function JoinButtons(args) { function JoinButtons(args) {
const { const {
instances, instances,
invite,
sendInstanceState, sendInstanceState,
sendInstanceQueue, sendInstanceQueue,
sendInstancePractice, sendInstancePractice,
sendInstanceInvite,
} = args; } = args;
if (instances.length) { if (instances.length) {
@ -55,6 +66,48 @@ function JoinButtons(args) {
); );
} }
const inviteBtn = () => {
if (!invite) {
return (
<button
class='pvp ready'
onClick={() => sendInstanceInvite()}
type="submit">
Invite
</button>
);
}
function copyClick(e) {
const link = `${document.location.origin}#join=${invite}`;
const textArea = document.createElement('textarea', { id: '#clipboard' });
textArea.value = link;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
infoToast('Invite link copied.');
} catch (err) {
console.error('link copy error', err);
errorToast('Invite link copy error.');
}
document.body.removeChild(textArea);
return true;
}
return (
<button
class='pvp ready enabled'
onClick={copyClick}
type="submit">
Copy Link
</button>
);
};
return ( return (
<aside class='play-ctrl'> <aside class='play-ctrl'>
<div class="timer-container"></div> <div class="timer-container"></div>
@ -65,6 +118,7 @@ function JoinButtons(args) {
type="submit"> type="submit">
PVP PVP
</button> </button>
{inviteBtn()}
<button <button
class='practice ready' class='practice ready'
onClick={() => sendInstancePractice()} onClick={() => sendInstancePractice()}

View File

@ -92,13 +92,14 @@ function Play(args) {
<section class="top"> <section class="top">
<div class="news"> <div class="news">
<h1>v{VERSION}</h1> <h1>v{VERSION}</h1>
<p>use the buttons on the right to join an instance.</p> <p>Use the buttons on the right to join an instance.</p>
<p> <p>
select <b>PVP</b> to play against other players.<br /> Select <b>PVP</b> to play against other players.<br />
click <b>LEARN</b> to practice the game without time controls. Select <b>INVITE</b> then click <b>COPY LINK</b> to generate an instance invitation for a friend.<br />
Click <b>LEARN</b> to practice the game without time controls.
</p> </p>
<p> <p>
if you enjoy the game please support its development by <b>subscribing</b> or purchasing <b>credits</b>.<br /> If you enjoy the game please support its development by <b>subscribing</b> or purchasing <b>credits</b>.<br />
glhf glhf
</p> </p>
<p>--ntr & mashy</p> <p>--ntr & mashy</p>

View File

@ -1,3 +1,5 @@
const querystring = require('query-string');
const eachSeries = require('async/eachSeries'); const eachSeries = require('async/eachSeries');
const sample = require('lodash/sample'); const sample = require('lodash/sample');
@ -168,9 +170,20 @@ function registerEvents(store) {
return store.dispatch(actions.setInstances(v)); return store.dispatch(actions.setInstances(v));
} }
function setInvite(code) {
if (!code) return store.dispatch(actions.setInvite(null));
navigator.clipboard.writeText(code).then(() => {
notify(`your invite code ${code} was copied to the clipboard.`);
}, () => {});
return store.dispatch(actions.setInvite(code));
}
function setInstance(v) { function setInstance(v) {
const { account, instance, ws } = store.getState(); const { account, instance, ws } = store.getState();
if (v) { if (v) {
setInvite(null);
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));
@ -272,6 +285,16 @@ function registerEvents(store) {
} */ } */
// setup / localstorage // setup / localstorage
function urlHashChange() {
const { ws } = store.getState();
const cmds = querystring.parse(location.hash);
if (cmds.join) ws.sendInstanceJoin(cmds.join);
return true;
}
window.addEventListener('hashchange', urlHashChange, false);
return { return {
clearCombiner, clearCombiner,
clearConstructRename, clearConstructRename,
@ -289,12 +312,15 @@ function registerEvents(store) {
setEmail, setEmail,
setInstance, setInstance,
setItemInfo, setItemInfo,
setInvite,
setPing, setPing,
setShop, setShop,
setTeam, setTeam,
setSubscription, setSubscription,
setWs, setWs,
urlHashChange,
notify, notify,
}; };
} }

View File

@ -31,6 +31,7 @@ module.exports = {
constructRename: createReducer(null, 'SET_CONSTRUCT_RENAME'), constructRename: createReducer(null, 'SET_CONSTRUCT_RENAME'),
game: createReducer(null, 'SET_GAME'), game: createReducer(null, 'SET_GAME'),
email: createReducer(null, 'SET_EMAIL'), email: createReducer(null, 'SET_EMAIL'),
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'),
instances: createReducer([], 'SET_INSTANCES'), instances: createReducer([], 'SET_INSTANCES'),

View File

@ -126,6 +126,14 @@ function createSocket(events) {
send(['InstanceQueue', {}]); send(['InstanceQueue', {}]);
} }
function sendInstanceInvite() {
send(['InstanceInvite', {}]);
}
function sendInstanceJoin(code) {
send(['InstanceJoin', { code }]);
}
function sendInstanceReady(instanceId) { function sendInstanceReady(instanceId) {
send(['InstanceReady', { instance_id: instanceId }]); send(['InstanceReady', { instance_id: instanceId }]);
} }
@ -239,6 +247,9 @@ function createSocket(events) {
QueueRequested: () => events.notify('pvp queue request received'), QueueRequested: () => events.notify('pvp queue request received'),
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'),
Invite: code => events.setInvite(code),
Joining: () => events.notify('searching for instance...'),
Error: errHandler, Error: errHandler,
}; };
@ -284,6 +295,8 @@ function createSocket(events) {
sendPing(); sendPing();
sendItemInfo(); sendItemInfo();
events.urlHashChange();
return true; return true;
} }
@ -318,6 +331,7 @@ function createSocket(events) {
ws.addEventListener('message', onMessage); ws.addEventListener('message', onMessage);
ws.addEventListener('error', onError); ws.addEventListener('error', onError);
ws.addEventListener('close', onClose); ws.addEventListener('close', onClose);
return ws; return ws;
} }
@ -338,6 +352,8 @@ function createSocket(events) {
sendInstancePractice, sendInstancePractice,
sendInstanceQueue, sendInstanceQueue,
sendInstanceState, sendInstanceState,
sendInstanceInvite,
sendInstanceJoin,
sendVboxAccept, sendVboxAccept,
sendVboxApply, sendVboxApply,

View File

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

View File

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

View File

@ -46,7 +46,6 @@ impl Colours {
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct ConstructSkill { pub struct ConstructSkill {
pub skill: Skill, pub skill: Skill,
pub self_targeting: bool,
pub cd: Cooldown, pub cd: Cooldown,
// used for Uon client // used for Uon client
pub disabled: bool, pub disabled: bool,
@ -56,7 +55,6 @@ impl ConstructSkill {
pub fn new(skill: Skill) -> ConstructSkill { pub fn new(skill: Skill) -> ConstructSkill {
ConstructSkill { ConstructSkill {
skill, skill,
self_targeting: skill.self_targeting(),
cd: skill.base_cd(), cd: skill.base_cd(),
disabled: false, disabled: false,
} }

View File

@ -12,6 +12,7 @@ use account;
use account::Account; use account::Account;
use game; use game;
use instance; use instance;
use names;
use pg::{Db, PgPool}; use pg::{Db, PgPool};
use rpc::RpcMessage; use rpc::RpcMessage;
@ -54,6 +55,9 @@ pub enum Event {
// client events // client events
Queue(Id), Queue(Id),
Invite(Id),
Join(Id, String),
Joined(Id),
} }
struct WsClient { struct WsClient {
@ -62,6 +66,7 @@ struct WsClient {
tx: Sender<RpcMessage>, tx: Sender<RpcMessage>,
subs: HashSet<Uuid>, subs: HashSet<Uuid>,
pvp: bool, pvp: bool,
invite: Option<String>,
} }
impl Events { impl Events {
@ -115,7 +120,7 @@ impl Events {
None => None, None => None,
}; };
let client = WsClient { id, tx, account: account_id, subs: HashSet::new(), pvp: false }; let client = WsClient { id, tx, account: account_id, subs: HashSet::new(), pvp: false, invite: None };
self.clients.insert(id, client); self.clients.insert(id, client);
info!("clients={:?}", self.clients.len()); info!("clients={:?}", self.clients.len());
@ -223,6 +228,59 @@ impl Events {
info!("joined game queue id={:?} account={:?}", requester.id, requester.account); info!("joined game queue id={:?} account={:?}", requester.id, requester.account);
return Ok(()); return Ok(());
}, },
Event::Invite(id) => {
// check whether request is valid
let c = self.clients.get_mut(&id)
.ok_or(format_err!("connection not found id={:?}", id))?;
if let None = c.account {
return Err(err_msg("cannot join pvp queue anonymously"));
}
let code = names::name().split_whitespace().collect::<Vec<&str>>().join("-");
info!("pvp invite request id={:?} account={:?} code={:?}", c.id, c.account, code);
c.invite = Some(code.clone());
c.tx.send(RpcMessage::Invite(code))?;
return Ok(());
},
Event::Join(id, code) => {
// check whether request is valid
let c = self.clients.get(&id)
.ok_or(format_err!("connection not found id={:?}", id))?;
if let None = c.account {
return Err(err_msg("cannot join pvp queue anonymously"));
}
info!("pvp join request id={:?} account={:?} code={:?}", c.id, c.account, code);
let inv = self.clients.iter()
.filter(|(_id, c)| c.invite.is_some())
.find(|(_id, c)| match c.invite {
Some(ref c) => *c == code,
None => false,
})
.map(|(_id, c)| PvpRequest { id: c.id, account: c.account.unwrap(), tx: c.tx.clone() })
.ok_or(format_err!("invite not found code={:?}", code))?;
let join = PvpRequest { id: c.id, account: c.account.unwrap(), tx: c.tx.clone() };
self.warden.send(GameEvent::Match((join, inv)))?;
return Ok(());
},
Event::Joined(id) => {
// check whether request is valid
let c = self.clients.get_mut(&id)
.ok_or(format_err!("connection not found id={:?}", id))?;
c.pvp = false;
c.invite = None;
return Ok(());
},
} }
} }
} }

View File

@ -236,7 +236,7 @@ impl Game {
target = find_target(); target = find_target();
} }
pve_skills.push((mobs.id, mob.id, Some(target.id), s)); pve_skills.push((mobs.id, mob.id, target.id, s));
}, },
None => continue, None => continue,
}; };
@ -258,7 +258,7 @@ impl Game {
self self
} }
fn add_skill(&mut self, player_id: Uuid, source_construct_id: Uuid, target_construct_id: Option<Uuid>, skill: Skill) -> Result<&mut Game, Error> { fn add_skill(&mut self, player_id: Uuid, source_construct_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Result<&mut Game, Error> {
// check player in game // check player in game
self.player_by_id(player_id)?; self.player_by_id(player_id)?;
@ -266,17 +266,9 @@ impl Game {
return Err(err_msg("game not in skill phase")); return Err(err_msg("game not in skill phase"));
} }
let final_target_id = match skill.self_targeting() {
true => source_construct_id,
false => match target_construct_id {
Some(t) => t,
None => return Err(err_msg("skill requires a target")),
}
};
// target checks // target checks
{ {
let target = match self.construct_by_id(final_target_id) { let target = match self.construct_by_id(target_construct_id) {
Some(c) => c, Some(c) => c,
None => return Err(err_msg("target construct not in game")), None => return Err(err_msg("target construct not in game")),
}; };
@ -318,7 +310,7 @@ impl Game {
self.stack.remove(s); self.stack.remove(s);
} }
let skill = Cast::new(source_construct_id, player_id, final_target_id, skill); let skill = Cast::new(source_construct_id, player_id, target_construct_id, skill);
self.stack.push(skill); self.stack.push(skill);
return Ok(self); return Ok(self);
@ -887,7 +879,7 @@ fn game_json_file_write(g: &Game) -> Result<String, Error> {
Ok(dest) Ok(dest)
} }
pub fn game_skill(tx: &mut Transaction, account: &Account, game_id: Uuid, construct_id: Uuid, target_construct_id: Option<Uuid>, skill: Skill) -> Result<Game, Error> { pub fn game_skill(tx: &mut Transaction, account: &Account, game_id: Uuid, construct_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Result<Game, Error> {
let mut game = game_get(tx, game_id)?; let mut game = game_get(tx, game_id)?;
game.add_skill(account.id, construct_id, target_construct_id, skill)?; game.add_skill(account.id, construct_id, target_construct_id, skill)?;
@ -1039,8 +1031,8 @@ mod tests {
let x_construct = x_player.constructs[0].clone(); let x_construct = x_player.constructs[0].clone();
let y_construct = y_player.constructs[0].clone(); let y_construct = y_player.constructs[0].clone();
game.add_skill(x_player.id, x_construct.id, Some(y_construct.id), Skill::Attack).unwrap(); game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap();
game.add_skill(y_player.id, y_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
@ -1068,8 +1060,8 @@ mod tests {
game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns();
} }
game.add_skill(x_player.id, x_construct.id, Some(y_construct.id), Skill::Stun).unwrap(); game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap();
game.add_skill(y_player.id, y_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
@ -1105,8 +1097,8 @@ mod tests {
// remove all mitigation // remove all mitigation
game.player_by_id(x_player.id).unwrap().construct_by_id(x_construct.id).unwrap().red_life.force(0); game.player_by_id(x_player.id).unwrap().construct_by_id(x_construct.id).unwrap().red_life.force(0);
game.add_skill(x_player.id, x_construct.id, Some(y_construct.id), Skill::Stun).unwrap(); game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap();
game.add_skill(y_player.id, y_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
@ -1135,8 +1127,8 @@ mod tests {
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_some());
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).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, Some(y_construct.id), Skill::Attack).unwrap(); game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap();
game.add_skill(y_player.id, y_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
@ -1149,8 +1141,8 @@ mod tests {
// second round // second round
// now we block and it should go back on cd // now we block and it should go back on cd
// game.add_skill(x_player.id, x_construct.id, Some(y_construct.id), Skill::Stun).unwrap(); // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap();
game.add_skill(y_player.id, y_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
@ -1179,8 +1171,8 @@ mod tests {
game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns();
} }
game.add_skill(x_player.id, x_construct.id, None, Skill::Counter).unwrap(); game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Counter).unwrap();
game.add_skill(y_player.id, y_construct.id, Some(x_construct.id), Skill::Stun).unwrap(); game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Stun).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
@ -1214,14 +1206,14 @@ mod tests {
} }
// apply buff // apply buff
game.add_skill(x_player.id, x_construct.id, Some(x_construct.id), Skill::Electrify).unwrap(); game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Electrify).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
game = game.resolve_phase_start(); game = game.resolve_phase_start();
assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Electric)); assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Electric));
// attack and receive debuff // attack and receive debuff
game.add_skill(y_player.id, y_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
game = game.resolve_phase_start(); game = game.resolve_phase_start();
@ -1246,7 +1238,7 @@ mod tests {
} }
// apply buff // apply buff
game.add_skill(x_player.id, x_construct.id, Some(y_construct.id), Skill::Link).unwrap(); game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Link).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
game = game.resolve_phase_start(); game = game.resolve_phase_start();
@ -1265,7 +1257,7 @@ mod tests {
} }
// attack and receive link hit // attack and receive link hit
game.add_skill(y_player.id, y_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
game = game.resolve_phase_start(); game = game.resolve_phase_start();
@ -1296,14 +1288,14 @@ mod tests {
// } // }
// // apply buff // // apply buff
// game.add_skill(x_player.id, x_construct.id, Some(x_construct.id), Skill::Absorb).unwrap(); // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Absorb).unwrap();
// game.player_ready(x_player.id).unwrap(); // game.player_ready(x_player.id).unwrap();
// game.player_ready(y_player.id).unwrap(); // game.player_ready(y_player.id).unwrap();
// game = game.resolve_phase_start(); // game = game.resolve_phase_start();
// assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Absorb)); // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Absorb));
// // attack and receive debuff // // attack and receive debuff
// game.add_skill(y_player.id, y_construct.id, Some(x_construct.id), Skill::TestAttack).unwrap(); // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::TestAttack).unwrap();
// game.player_ready(x_player.id).unwrap(); // game.player_ready(x_player.id).unwrap();
// game.player_ready(y_player.id).unwrap(); // game.player_ready(y_player.id).unwrap();
// game = game.resolve_phase_start(); // game = game.resolve_phase_start();
@ -1330,10 +1322,10 @@ mod tests {
game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns();
} }
game.add_skill(i_player.id, i_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap();
game.add_skill(i_player.id, j_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap();
game.add_skill(x_player.id, x_construct.id, Some(i_construct.id), Skill::Ruin).unwrap(); game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Ruin).unwrap();
game.add_skill(x_player.id, y_construct.id, Some(i_construct.id), Skill::Attack).unwrap(); game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap();
game.player_ready(i_player.id).unwrap(); game.player_ready(i_player.id).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
@ -1380,10 +1372,10 @@ mod tests {
game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns();
} }
game.add_skill(i_player.id, i_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap();
game.add_skill(i_player.id, j_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap();
game.add_skill(x_player.id, x_construct.id, Some(i_construct.id), Skill::Intercept).unwrap(); game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Intercept).unwrap();
game.add_skill(x_player.id, y_construct.id, Some(i_construct.id), Skill::Attack).unwrap(); game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap();
game.player_ready(i_player.id).unwrap(); game.player_ready(i_player.id).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
@ -1411,10 +1403,10 @@ mod tests {
let x_construct = x_player.constructs[0].clone(); let x_construct = x_player.constructs[0].clone();
let y_construct = x_player.constructs[1].clone(); let y_construct = x_player.constructs[1].clone();
game.add_skill(i_player.id, i_construct.id, Some(x_construct.id), Skill::Attack).unwrap() game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap()
.add_skill(i_player.id, j_construct.id, Some(x_construct.id), Skill::Attack).unwrap() .add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap()
.add_skill(x_player.id, x_construct.id, Some(i_construct.id), Skill::Attack).unwrap() .add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).unwrap()
.add_skill(x_player.id, y_construct.id, Some(i_construct.id), Skill::Attack).unwrap() .add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap()
.player_ready(i_player.id).unwrap() .player_ready(i_player.id).unwrap()
.player_ready(x_player.id).unwrap(); .player_ready(x_player.id).unwrap();
@ -1430,10 +1422,10 @@ mod tests {
assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2);
// add some more skills // add some more skills
game.add_skill(i_player.id, j_construct.id, Some(x_construct.id), Skill::Attack).unwrap(); game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap();
game.add_skill(x_player.id, x_construct.id, Some(j_construct.id), Skill::Attack).unwrap(); game.add_skill(x_player.id, x_construct.id, j_construct.id, Skill::Attack).unwrap();
game.add_skill(x_player.id, y_construct.id, Some(j_construct.id), Skill::Attack).unwrap(); game.add_skill(x_player.id, y_construct.id, j_construct.id, Skill::Attack).unwrap();
assert!(game.add_skill(x_player.id, x_construct.id, Some(i_construct.id), Skill::Attack).is_err()); assert!(game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).is_err());
game.player_ready(i_player.id).unwrap(); game.player_ready(i_player.id).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
@ -1475,7 +1467,7 @@ mod tests {
} }
// apply buff // apply buff
game.add_skill(x_player.id, x_construct.id, Some(y_construct.id), Skill::Decay).unwrap(); game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
game = game.resolve_phase_start(); game = game.resolve_phase_start();
@ -1490,7 +1482,7 @@ mod tests {
game.resolved.clear(); game.resolved.clear();
// remove // remove
game.add_skill(y_player.id, y_construct.id, Some(y_construct.id), Skill::Purify).unwrap(); game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
game = game.resolve_phase_start(); game = game.resolve_phase_start();
@ -1503,14 +1495,14 @@ mod tests {
} }
}; };
game.add_skill(y_player.id, x_construct.id, Some(y_construct.id), Skill::Siphon).unwrap(); game.add_skill(y_player.id, x_construct.id, y_construct.id, Skill::Siphon).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
game = game.resolve_phase_start(); game = game.resolve_phase_start();
game.resolved.clear(); game.resolved.clear();
game.add_skill(y_player.id, y_construct.id, Some(y_construct.id), Skill::Purify).unwrap(); game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap();
game.player_ready(x_player.id).unwrap(); game.player_ready(x_player.id).unwrap();
game.player_ready(y_player.id).unwrap(); game.player_ready(y_player.id).unwrap();
game = game.resolve_phase_start(); game = game.resolve_phase_start();

View File

@ -117,7 +117,7 @@ pub fn shapes_write(id: Uuid) -> Result<Uuid, Error> {
(ConstructShapes::Circle, 10), (ConstructShapes::Circle, 10),
(ConstructShapes::Line, 10), (ConstructShapes::Line, 10),
(ConstructShapes::V, 10), (ConstructShapes::V, 10),
// (ConstructShapes::Tri, 3), (ConstructShapes::Tri, 10),
// (ConstructShapes::Plus, 5), // (ConstructShapes::Plus, 5),
(ConstructShapes::Blank, 1), (ConstructShapes::Blank, 1),
]; ];
@ -155,6 +155,9 @@ pub fn shapes_write(id: Uuid) -> Result<Uuid, Error> {
let size = rng.gen_range(20.0, 50.0); let size = rng.gen_range(20.0, 50.0);
write!(&mut svg, "<rect fill=\"{fill}\" x=\"-{x}\" y=\"-{y}\" width=\"{width}\" height=\"{height}\" transform=\"translate({x_t}, {y_t}) rotate({rotation})\" />", write!(&mut svg, "<rect fill=\"{fill}\" x=\"-{x}\" y=\"-{y}\" width=\"{width}\" height=\"{height}\" transform=\"translate({x_t}, {y_t}) rotate({rotation})\" />",
fill = colour, x = size / 2.0, y = size / 2.0, width = size, height = size, x_t = x_translate, y_t = y_translate, rotation = rotation)?; fill = colour, x = size / 2.0, y = size / 2.0, width = size, height = size, x_t = x_translate, y_t = y_translate, rotation = rotation)?;
if scalar == 0.0 && rng.gen_bool(0.5) {
continue;
}
write!(&mut svg, "<rect fill=\"{fill}\" x=\"-{x}\" y=\"-{y}\" width=\"{width}\" height=\"{height}\" transform=\"translate({x_t}, {y_t}) rotate({rotation})\" />", write!(&mut svg, "<rect fill=\"{fill}\" x=\"-{x}\" y=\"-{y}\" width=\"{width}\" height=\"{height}\" transform=\"translate({x_t}, {y_t}) rotate({rotation})\" />",
fill = colour, x = size / 2.0, y = size / 2.0, width = size, height = size, x_t = -x_translate, y_t = -y_translate, rotation = rotation)?; fill = colour, x = size / 2.0, y = size / 2.0, width = size, height = size, x_t = -x_translate, y_t = -y_translate, rotation = rotation)?;
}, },
@ -163,6 +166,9 @@ pub fn shapes_write(id: Uuid) -> Result<Uuid, Error> {
let b = rng.gen_range(20.0, 50.0); let b = rng.gen_range(20.0, 50.0);
write!(&mut svg, "<polygon fill=\"{fill}\" x=\"{x}\" y=\"{y}\" points=\"{x0} {y0}, {x1} {y1}, {x2} {y2}\" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />", write!(&mut svg, "<polygon fill=\"{fill}\" x=\"{x}\" y=\"{y}\" points=\"{x0} {y0}, {x1} {y1}, {x2} {y2}\" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />",
fill = colour, x = -b / 2.0, y = h / 2.0, x0 = -b / 2.0, y0 = -h / 2.0, x1 = 0, y1 = b / 2.0, x2 = b / 2.0, y2 = -h / 2.0, rotation = rotation, x_translate = x_translate, y_translate = y_translate)?; fill = colour, x = -b / 2.0, y = h / 2.0, x0 = -b / 2.0, y0 = -h / 2.0, x1 = 0, y1 = b / 2.0, x2 = b / 2.0, y2 = -h / 2.0, rotation = rotation, x_translate = x_translate, y_translate = y_translate)?;
if scalar == 0.0 && rng.gen_bool(0.5) {
continue;
}
write!(&mut svg, "<polygon fill=\"{fill}\" x=\"{x}\" y=\"{y}\" points=\"{x0} {y0}, {x1} {y1}, {x2} {y2}\" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />", write!(&mut svg, "<polygon fill=\"{fill}\" x=\"{x}\" y=\"{y}\" points=\"{x0} {y0}, {x1} {y1}, {x2} {y2}\" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />",
fill = colour, x = -b / 2.0, y = h / 2.0, x0 = -b / 2.0, y0 = -h / 2.0, x1 = 0, y1 = b / 2.0, x2 = b / 2.0, y2 = -h / 2.0, rotation = rotation + 180, x_translate = -x_translate, y_translate = -y_translate)?; fill = colour, x = -b / 2.0, y = h / 2.0, x0 = -b / 2.0, y0 = -h / 2.0, x1 = 0, y1 = b / 2.0, x2 = b / 2.0, y2 = -h / 2.0, rotation = rotation + 180, x_translate = -x_translate, y_translate = -y_translate)?;
}, },
@ -178,6 +184,9 @@ pub fn shapes_write(id: Uuid) -> Result<Uuid, Error> {
let height = rng.gen_range(20.0, 50.0); let height = rng.gen_range(20.0, 50.0);
write!(&mut svg, "<rect fill=\"{fill}\" x=\"-{x}\" y=\"-{y}\" width=\"{width}\" height=\"{height}\" transform=\"translate({x_t}, {y_t}) rotate({rotation})\" />", write!(&mut svg, "<rect fill=\"{fill}\" x=\"-{x}\" y=\"-{y}\" width=\"{width}\" height=\"{height}\" transform=\"translate({x_t}, {y_t}) rotate({rotation})\" />",
fill = colour, x = width / 2.0, y = height / 2.0, width = width, height = height, x_t = x_translate, y_t = y_translate, rotation = rotation)?; fill = colour, x = width / 2.0, y = height / 2.0, width = width, height = height, x_t = x_translate, y_t = y_translate, rotation = rotation)?;
if scalar == 0.0 && rng.gen_bool(0.5) {
continue;
}
write!(&mut svg, "<rect fill=\"{fill}\" x=\"-{x}\" y=\"-{y}\" width=\"{width}\" height=\"{height}\" transform=\"translate({x_t}, {y_t}) rotate({rotation})\" />", write!(&mut svg, "<rect fill=\"{fill}\" x=\"-{x}\" y=\"-{y}\" width=\"{width}\" height=\"{height}\" transform=\"translate({x_t}, {y_t}) rotate({rotation})\" />",
fill = colour, x = width / 2.0, y = height / 2.0, width = width, height = height, x_t = -x_translate, y_t = -y_translate, rotation = rotation)?; fill = colour, x = width / 2.0, y = height / 2.0, width = width, height = height, x_t = -x_translate, y_t = -y_translate, rotation = rotation)?;
}, },
@ -188,11 +197,30 @@ pub fn shapes_write(id: Uuid) -> Result<Uuid, Error> {
write!(&mut svg, "<polyline fill=\"none\" stroke=\"{fill}\" stroke-width=\"{width}\" x=\"{x}\" y=\"{y}\" points=\"{x0} {y0}, {x1} {y1}, {x2} {y2}\" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />", write!(&mut svg, "<polyline fill=\"none\" stroke=\"{fill}\" stroke-width=\"{width}\" x=\"{x}\" y=\"{y}\" points=\"{x0} {y0}, {x1} {y1}, {x2} {y2}\" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />",
fill = colour, width = width, x = -b / 2.0, y = h / 2.0, x0 = -b / 2.0, y0 = -h / 2.0, x1 = 0, y1 = b / 2.0, x2 = b / 2.0, y2 = -h / 2.0, rotation = rotation, x_translate = x_translate, y_translate = y_translate)?; fill = colour, width = width, x = -b / 2.0, y = h / 2.0, x0 = -b / 2.0, y0 = -h / 2.0, x1 = 0, y1 = b / 2.0, x2 = b / 2.0, y2 = -h / 2.0, rotation = rotation, x_translate = x_translate, y_translate = y_translate)?;
if scalar == 0.0 && rng.gen_bool(0.5) {
continue;
}
write!(&mut svg, "<polyline fill=\"none\" stroke=\"{fill}\" stroke-width=\"{width}\" x=\"{x}\" y=\"{y}\" points=\"{x0} {y0}, {x1} {y1}, {x2} {y2}\" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />", write!(&mut svg, "<polyline fill=\"none\" stroke=\"{fill}\" stroke-width=\"{width}\" x=\"{x}\" y=\"{y}\" points=\"{x0} {y0}, {x1} {y1}, {x2} {y2}\" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />",
fill = colour, width = width, x = -b / 2.0, y = h / 2.0, x0 = -b / 2.0, y0 = -h / 2.0, x1 = 0, y1 = b / 2.0, x2 = b / 2.0, y2 = -h / 2.0, rotation = rotation + 180, x_translate = -x_translate, y_translate = -y_translate)?; fill = colour, width = width, x = -b / 2.0, y = h / 2.0, x0 = -b / 2.0, y0 = -h / 2.0, x1 = 0, y1 = b / 2.0, x2 = b / 2.0, y2 = -h / 2.0, rotation = rotation + 180, x_translate = -x_translate, y_translate = -y_translate)?;
}, },
ConstructShapes::Tri => { ConstructShapes::Tri => {
let width = rng.gen_range(2.0, 4.0);
let length = rng.gen_range(12.5, 25.0);
let x0 = (0.0 as f64).cos() * length;
let y0 = (0.0 as f64).sin() * length;
let x1 = ((f64::consts::PI * 2.0) / 3.0).cos() * length;
let y1 = ((f64::consts::PI * 2.0) / 3.0).sin() * length;
let x2 = ((f64::consts::PI * 4.0) / 3.0).cos() * length;
let y2 = ((f64::consts::PI * 4.0) / 3.0).sin() * length;
write!(&mut svg, "<path stroke=\"{fill}\" stroke-width=\"{width}\" d=\"M{x0} {y0}L 0 0 M{x1} {y1}L 0 0 M{x2} {y2}L 0 0 \" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />",
fill = colour, width = width, x0 = x0, y0 = y0, x1 = x1, y1 = y1, x2 = x2, y2 = y2, rotation = rotation, x_translate = x_translate, y_translate = y_translate)?;
if scalar == 0.0 && rng.gen_bool(0.5) {
continue;
}
write!(&mut svg, "<path stroke=\"{fill}\" stroke-width=\"{width}\" d=\"M{x0} {y0}L 0 0 M{x1} {y1}L 0 0 M{x2} {y2}L 0 0 \" transform=\"translate({x_translate}, {y_translate}) rotate({rotation})\" />",
fill = colour, width = width, x0 = x0, y0 = y0, x1 = x1, y1 = y1, x2 = x2, y2 = y2, rotation = rotation, x_translate = -x_translate, y_translate = -y_translate)?;
}, },
ConstructShapes::Plus => { ConstructShapes::Plus => {

View File

@ -62,6 +62,10 @@ pub enum RpcMessage {
QueueJoined(()), QueueJoined(()),
QueueCancelled(()), QueueCancelled(()),
InviteRequested(()),
Invite(String),
Joining(()),
Error(String), Error(String),
} }
@ -78,7 +82,7 @@ pub enum RpcRequest {
GameState { id: Uuid }, GameState { id: Uuid },
GameReady { id: Uuid }, GameReady { id: Uuid },
GameSkill { game_id: Uuid, construct_id: Uuid, target_construct_id: Option<Uuid>, skill: Skill }, GameSkill { game_id: Uuid, construct_id: Uuid, target_construct_id: Uuid, skill: Skill },
GameSkillClear { game_id: Uuid }, GameSkillClear { game_id: Uuid },
AccountState {}, AccountState {},
@ -91,6 +95,8 @@ pub enum RpcRequest {
SubscriptionState {}, SubscriptionState {},
EmailState {}, EmailState {},
InstanceInvite {},
InstanceJoin { code: String },
InstanceQueue {}, InstanceQueue {},
InstancePractice {}, InstancePractice {},
InstanceAbandon { instance_id: Uuid }, InstanceAbandon { instance_id: Uuid },
@ -136,21 +142,27 @@ impl Connection {
None => return Err(err_msg("auth required")), None => return Err(err_msg("auth required")),
}; };
// evented but authorization required
match v {
RpcRequest::InstanceQueue {} => {
self.events.send(Event::Queue(self.id))?;
return Ok(RpcMessage::QueueRequested(()));
},
_ => (),
};
// all good, let's make a tx and process
let mut tx = db.transaction()?;
let request = v.clone(); let request = v.clone();
let response = match v { let response = match v {
// evented but authorization required
RpcRequest::InstanceQueue {} => {
self.events.send(Event::Queue(self.id))?;
Ok(RpcMessage::QueueRequested(()))
},
RpcRequest::InstanceInvite {} => {
self.events.send(Event::Invite(self.id))?;
Ok(RpcMessage::InviteRequested(()))
},
RpcRequest::InstanceJoin { code } => {
self.events.send(Event::Join(self.id, code))?;
Ok(RpcMessage::Joining(()))
},
_ => {
// all good, let's make a tx and process
let mut tx = db.transaction()?;
let res = match v {
RpcRequest::AccountState {} => RpcRequest::AccountState {} =>
Ok(RpcMessage::AccountState(account.clone())), Ok(RpcMessage::AccountState(account.clone())),
RpcRequest::AccountConstructs {} => RpcRequest::AccountConstructs {} =>
@ -228,6 +240,9 @@ impl Connection {
}; };
tx.commit()?; tx.commit()?;
res
}
};
info!("request={:?} account={:?} duration={:?}", request, account.name, begin.elapsed()); info!("request={:?} account={:?} duration={:?}", request, account.name, begin.elapsed());

View File

@ -1213,20 +1213,6 @@ impl Skill {
} }
} }
pub fn self_targeting(&self) -> bool {
match self {
Skill::Block |
Skill::Sustain|
Skill::SustainPlus |
Skill::SustainPlusPlus |
Skill::Counter|
Skill::CounterPlus |
Skill::CounterPlusPlus => true,
_ => false,
}
}
pub fn defensive(&self) -> bool { pub fn defensive(&self) -> bool {
let mut rng = thread_rng(); let mut rng = thread_rng();

View File

@ -90,6 +90,10 @@ impl Warden {
fn on_match(&mut self, pair: Pair) -> Result<(), Error> { fn on_match(&mut self, pair: Pair) -> Result<(), Error> {
info!("received pair={:?}", pair); info!("received pair={:?}", pair);
// clear pvp status
self.events.send(Event::Joined(pair.0.id))?;
self.events.send(Event::Joined(pair.1.id))?;
let db = self.pool.get()?; let db = self.pool.get()?;
let mut tx = db.transaction()?; let mut tx = db.transaction()?;