Merge tag '1.11.0' into develop
1.11.0
This commit is contained in:
commit
02b25f92b7
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-client",
|
"name": "mnml-client",
|
||||||
"version": "1.10.1",
|
"version": "1.11.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
2684
client/assets/mnml.awards.svg
Normal file
2684
client/assets/mnml.awards.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 106 KiB |
2568
client/assets/mnml.logo.text.svg
Normal file
2568
client/assets/mnml.logo.text.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 95 KiB |
@ -3,16 +3,25 @@
|
|||||||
|
|
||||||
div {
|
div {
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
|
// display: flex;
|
||||||
|
// flex-flow: column;
|
||||||
|
line-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
// text-transform: uppercase;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 2.5em;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 3em;
|
height: 2.5em;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
@white: #f5f5f5; // whitesmoke
|
@white: #f5f5f5; // whitesmoke
|
||||||
@purple: #9355b5; // 6lack - that far cover
|
@purple: #9355b5; // 6lack - that far cover
|
||||||
@yellow: #ffa100;
|
@yellow: #ffa100;
|
||||||
@silver: #c0c0c0;
|
@silver: #2c2c2c;
|
||||||
|
|
||||||
@black: black;
|
@black: black;
|
||||||
@gray: #222;
|
@gray: #222;
|
||||||
|
|||||||
@ -54,11 +54,7 @@
|
|||||||
|
|
||||||
button {
|
button {
|
||||||
&.highlight {
|
&.highlight {
|
||||||
color: black;
|
|
||||||
background: @silver;
|
background: @silver;
|
||||||
// border: 1px solid @white; (this bangs around the vbox)
|
|
||||||
|
|
||||||
// overwrite the classes on white svg elements
|
|
||||||
svg {
|
svg {
|
||||||
stroke-width: 0.75em;
|
stroke-width: 0.75em;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,25 +75,11 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
border: 0.1em solid #222;
|
border: 0.1em solid #222;
|
||||||
&:not(:last-child) {
|
|
||||||
border-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
|
|
||||||
.terms {
|
|
||||||
display: inline;
|
|
||||||
margin: 0 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
@ -108,62 +94,14 @@ section {
|
|||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
|
||||||
letter-spacing: 0.25em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
display: grid;
|
|
||||||
// grid-template-columns: repeat(4, 1fr);
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-gap: 1em;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
align-items: flex-end;
|
|
||||||
button {
|
|
||||||
border-radius: 0.25em;
|
|
||||||
// height: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sub {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.play {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
&.rejoin {
|
|
||||||
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 {
|
|
||||||
// background: forestgreen;
|
|
||||||
// color: black;
|
|
||||||
// border-color: forestgreen;
|
|
||||||
|
|
||||||
// &:hover {
|
|
||||||
// color: forestgreen;
|
|
||||||
// border-color: forestgreen;
|
|
||||||
// background: 0;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.panes {
|
.panes {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
|
||||||
figure {
|
figure {
|
||||||
letter-spacing: 0.25em;
|
letter-spacing: 0.25em;
|
||||||
@ -172,61 +110,92 @@ section {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
letter-spacing: 0.25em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: grid;
|
||||||
|
// grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-gap: 1em;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
&.sub {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.play {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
&.rejoin {
|
||||||
|
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 {
|
||||||
|
// background: forestgreen;
|
||||||
|
// color: black;
|
||||||
|
// border-color: forestgreen;
|
||||||
|
|
||||||
|
// &:hover {
|
||||||
|
// color: forestgreen;
|
||||||
|
// border-color: forestgreen;
|
||||||
|
// background: 0;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo {
|
|
||||||
margin-top: 1em;
|
|
||||||
|
|
||||||
display: block;
|
.login {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
|
||||||
|
.terms {
|
||||||
|
display: inline;
|
||||||
|
margin: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
pointer-events: none;
|
padding: 0 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
grid-area: hdr;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
flex: 0 1 10%;
|
||||||
|
margin-right: 1em;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
button {
|
||||||
margin-bottom: 0.5em;
|
flex: 1;
|
||||||
|
border-top: 0;
|
||||||
div:first-child {
|
border: 0.1em solid #222;
|
||||||
padding-right: 1em;
|
&:last-child {
|
||||||
}
|
float: right;
|
||||||
}
|
|
||||||
|
|
||||||
.construct-section {
|
|
||||||
.construct-list {
|
|
||||||
height: 25em;
|
|
||||||
grid-area: unset;
|
|
||||||
|
|
||||||
.instance-construct {
|
|
||||||
// border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.colour-info {
|
|
||||||
grid-area: vinfo;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
div {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
flex: 1;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-demo {
|
|
||||||
.game {
|
|
||||||
height: 25em;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
|
|
||||||
.game-construct {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.intro {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|||||||
@ -27,23 +27,6 @@ html body {
|
|||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mnml {
|
|
||||||
/* this is the sweet nectar to keep it full page*/
|
|
||||||
height: 100vh;
|
|
||||||
max-height: 100vh;
|
|
||||||
min-height: 100vh;
|
|
||||||
|
|
||||||
/* stops inspector going skitz*/
|
|
||||||
overflow-x: hidden;
|
|
||||||
// overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @media (min-width: 1921px) {
|
|
||||||
// html, body, #mnml {
|
|
||||||
// font-size: 16pt;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -108,11 +91,37 @@ dl {
|
|||||||
|
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
|
|
||||||
|
/* this is the sweet nectar to keep it full page*/
|
||||||
|
height: 100vh;
|
||||||
|
max-height: 100vh;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
/* stops inspector going skitz*/
|
||||||
|
overflow-x: hidden;
|
||||||
|
// overflow-y: hidden;
|
||||||
|
|
||||||
|
|
||||||
&.animations-test {
|
&.animations-test {
|
||||||
aside button {
|
aside button {
|
||||||
font-size: 50%;
|
font-size: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.front-page {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 0 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin: 2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
@ -129,7 +138,7 @@ button, input {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border-radius: 0.5em;
|
border-radius: 0;
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
padding-right: 0.1em;
|
padding-right: 0.1em;
|
||||||
padding-left: 0.1em;
|
padding-left: 0.1em;
|
||||||
@ -150,9 +159,12 @@ button, input {
|
|||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
/*colour necesary to bash skellington*/
|
/*colour necesary to bash skellington*/
|
||||||
|
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// &:active {
|
||||||
|
// filter: url("#noiseFilter");
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -261,28 +273,12 @@ figure.gray {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
|
||||||
.options {
|
|
||||||
font-size: 200%;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
height: 2em;
|
|
||||||
// border-radius: 0.1em;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
.options {
|
||||||
button {
|
button {
|
||||||
&.highlight {
|
&.highlight {
|
||||||
color: @white;
|
color: @white;
|
||||||
box-shadow: inset 0px 5px 0px 0px @white;
|
box-shadow: inset 0px 5px 0px 0px @white;
|
||||||
border: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,11 +296,20 @@ li {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
height: 2em;
|
height: 4em;
|
||||||
background-image: url("../../assets/mnml.logo.trim.svg");
|
filter: url("#noiseFilter");
|
||||||
|
background-image: url("../../assets/mnml.logo.text.svg");
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: left;
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.awards {
|
||||||
|
height: 100%;
|
||||||
|
background-image: url("../../assets/mnml.awards.svg");
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-btn {
|
.discord-btn {
|
||||||
@ -316,8 +321,13 @@ li {
|
|||||||
|
|
||||||
.mnni {
|
.mnni {
|
||||||
background-image: url("./../mnni.svg");
|
background-image: url("./../mnni.svg");
|
||||||
|
filter: url("#noiseFilter");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// .highlight {
|
||||||
|
// filter: url("#noiseFilter");
|
||||||
|
// }
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
grid-area: avatar;
|
grid-area: avatar;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
@ -328,6 +338,10 @@ li {
|
|||||||
// pointer-events: none;
|
// pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
// font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
#clipboard {
|
#clipboard {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
@ -359,4 +373,8 @@ li {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#noise {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@import 'styles.mobile.less';
|
@import 'styles.mobile.less';
|
||||||
|
|||||||
@ -7,6 +7,12 @@
|
|||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
&.front-page {
|
||||||
|
main {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.instance {
|
.instance {
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"vbox vbox"
|
"vbox vbox"
|
||||||
@ -164,12 +170,21 @@
|
|||||||
|
|
||||||
|
|
||||||
// portrait menu or small size vertical in landscape
|
// portrait menu or small size vertical in landscape
|
||||||
@media (max-width: 550px) and (max-height: 800px) {
|
@media (max-width: 550px) and (max-height: 800px) and (orientation: portrait) {
|
||||||
#mnml {
|
#mnml {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"main"
|
"main";
|
||||||
|
|
||||||
|
&.front-page {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
@ -264,6 +279,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-combiner {
|
.info-combiner {
|
||||||
|
max-height: 7em;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -147,16 +147,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.highlight {
|
&.highlight {
|
||||||
color: black;
|
|
||||||
background: @silver;
|
background: @silver;
|
||||||
// overwrite the classes on white svg elements
|
// overwrite the classes on white svg elements
|
||||||
svg {
|
svg {
|
||||||
stroke-width: 0.75em;
|
stroke-width: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.white {
|
|
||||||
stroke: black;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-client",
|
"name": "mnml-client",
|
||||||
"version": "1.10.1",
|
"version": "1.11.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export const setAccount = value => ({ type: 'SET_ACCOUNT', value });
|
export const setAccount = value => ({ type: 'SET_ACCOUNT', value });
|
||||||
|
export const setAuthenticated = value => ({ type: 'SET_AUTHENTICATED', value });
|
||||||
|
|
||||||
export const setAnimating = value => ({ type: 'SET_ANIMATING', value });
|
export const setAnimating = value => ({ type: 'SET_ANIMATING', value });
|
||||||
export const setAnimFocus = value => ({ type: 'SET_ANIM_FOCUS', value });
|
export const setAnimFocus = value => ({ type: 'SET_ANIM_FOCUS', value });
|
||||||
@ -7,8 +8,6 @@ export const setAnimSource = value => ({ type: 'SET_ANIM_SOURCE', value });
|
|||||||
export const setAnimTarget = value => ({ type: 'SET_ANIM_TARGET', value });
|
export const setAnimTarget = value => ({ type: 'SET_ANIM_TARGET', value });
|
||||||
export const setResolution = value => ({ type: 'SET_RESOLUTION', value });
|
export const setResolution = value => ({ type: 'SET_RESOLUTION', value });
|
||||||
|
|
||||||
export const setDemo = value => ({ type: 'SET_DEMO', value });
|
|
||||||
|
|
||||||
export const setChatShow = value => ({ type: 'SET_CHAT_SHOW', value });
|
export const setChatShow = value => ({ type: 'SET_CHAT_SHOW', value });
|
||||||
export const setChatWheel = value => ({ type: 'SET_CHAT_WHEEL', value });
|
export const setChatWheel = value => ({ type: 'SET_CHAT_WHEEL', value });
|
||||||
export const setInstanceChat = value => ({ type: 'SET_INSTANCE_CHAT', value });
|
export const setInstanceChat = value => ({ type: 'SET_INSTANCE_CHAT', value });
|
||||||
|
|||||||
@ -33,6 +33,10 @@ const ws = createSocket(events);
|
|||||||
ws.connect();
|
ws.connect();
|
||||||
events.setWs(ws);
|
events.setWs(ws);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'development') {
|
||||||
|
LogRocket.init('yh0dy3/mnml');
|
||||||
|
}
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
{window.Stripe
|
{window.Stripe
|
||||||
|
|||||||
@ -152,11 +152,9 @@ class AccountStatus extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section class='account top' onClick={tlClick}>
|
<section class='account top' onClick={tlClick}>
|
||||||
|
{subInfo()}
|
||||||
<div>
|
<div>
|
||||||
{subInfo()}
|
<h3>Email</h3>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="email">Email Settings:</label>
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Recovery Email</dt>
|
<dt>Recovery Email</dt>
|
||||||
<dd>{email ? email.email : 'No email set'}</dd>
|
<dd>{email ? email.email : 'No email set'}</dd>
|
||||||
@ -174,6 +172,7 @@ class AccountStatus extends Component {
|
|||||||
<button onClick={() => sendSetEmail(emailState)}>Update</button>
|
<button onClick={() => sendSetEmail(emailState)}>Update</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<h3>Password</h3>
|
||||||
<label for="current">Password:</label>
|
<label for="current">Password:</label>
|
||||||
<input
|
<input
|
||||||
class="login-input"
|
class="login-input"
|
||||||
@ -208,6 +207,7 @@ class AccountStatus extends Component {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<h3>Other</h3>
|
||||||
<figure>
|
<figure>
|
||||||
<figcaption>spawn new construct</figcaption>
|
<figcaption>spawn new construct</figcaption>
|
||||||
<button onClick={() => sendConstructSpawn()} type="submit">
|
<button onClick={() => sendConstructSpawn()} type="submit">
|
||||||
|
|||||||
@ -1,34 +1,17 @@
|
|||||||
const preact = require('preact');
|
const preact = require('preact');
|
||||||
const { Component } = require('preact');
|
const { Component } = require('preact');
|
||||||
const anime = require('animejs').default;
|
const anime = require('animejs').default;
|
||||||
|
const times = require('lodash/times');
|
||||||
|
|
||||||
const { TIMES } = require('../../constants');
|
const { TIMES } = require('../../constants');
|
||||||
|
|
||||||
function projectile(x, y, radius, colour) {
|
const GREEN = '#1FF01F';
|
||||||
return (
|
const RED = '#a52a2a';
|
||||||
<circle
|
|
||||||
cx={x}
|
|
||||||
cy={y}
|
|
||||||
stroke="none"
|
|
||||||
r={radius}
|
|
||||||
fill={colour}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sword(colour) {
|
|
||||||
return (
|
|
||||||
<polygon points='150,150 100,75, 150,300, 200,75' stroke="none" fill={colour} id="sword" filter="url(#slayFilter)"></polygon>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Slay extends Component {
|
class Slay extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.animations = [];
|
this.animations = [];
|
||||||
this.colour = '#a52a2a';
|
|
||||||
const points = new Array(30).fill(0);
|
|
||||||
this.charges = points.map(() => projectile(150, 420, 7, '#1FF01F'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -39,13 +22,16 @@ class Slay extends Component {
|
|||||||
id="slay"
|
id="slay"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 300 300">
|
viewBox="0 0 300 300">
|
||||||
<filter id="slayFilter">
|
{times(10, () => (
|
||||||
<feGaussianBlur stdDeviation="4"/>
|
<ellipse
|
||||||
<feTurbulence type="turbulence" baseFrequency="0.001" numOctaves="3" result="turbulence"/>
|
cx={anime.random(100, 200)}
|
||||||
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="A" yChannelSelector="A"/>
|
cy={anime.random(-60, -30)}
|
||||||
</filter>
|
stroke="none"
|
||||||
{sword(this.colour)}
|
rx={anime.random(5, 10)}
|
||||||
{this.charges}
|
ry={10}
|
||||||
|
fill={RED}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -65,60 +51,26 @@ class Slay extends Component {
|
|||||||
|
|
||||||
anime.set('#slay', {
|
anime.set('#slay', {
|
||||||
rotate,
|
rotate,
|
||||||
});
|
|
||||||
|
|
||||||
anime.set('#slay', {
|
|
||||||
translateY: -1 * (window.innerHeight) * 0.35,
|
|
||||||
translateX: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
anime.set('#slayFilter feDisplacementMap', {
|
|
||||||
scale: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
anime.set('#sword', {
|
|
||||||
fill: this.colour,
|
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.animations.push(anime({
|
anime.set('#slay ellipse',{
|
||||||
targets: '#slay',
|
fill: RED,
|
||||||
opacity: [
|
})
|
||||||
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
|
|
||||||
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
|
|
||||||
],
|
|
||||||
translateY: 0,
|
|
||||||
translateX: 0,
|
|
||||||
loop: false,
|
|
||||||
easing: 'easeInQuad',
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.animations.push(anime({
|
this.animations.push(anime({
|
||||||
targets: ['#slayFilter feTurbulence', '#slayFilter feDisplacementMap'],
|
targets: ['#slay ellipse'],
|
||||||
baseFrequency: 10,
|
cx: 150,
|
||||||
scale: 100,
|
cy: 325,
|
||||||
delay: TIMES.TARGET_DURATION_MS * 0.6,
|
duration: TIMES.TARGET_DURATION_MS * 0.2,
|
||||||
duration: TIMES.TARGET_DURATION_MS * 0.3,
|
duration: TIMES.TARGET_DURATION_MS * 0.4,
|
||||||
easing: 'easeInQuad',
|
easing: 'easeOutQuad',
|
||||||
|
direction: 'alternate',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.animations.push(anime({
|
setTimeout(() => anime.set('#slay ellipse',{
|
||||||
targets: '#sword',
|
fill: GREEN,
|
||||||
opacity: 0,
|
}), TIMES.TARGET_DURATION_MS * 0.5);
|
||||||
delay: TIMES.TARGET_DURATION_MS * 0.9,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const projectiles = document.querySelectorAll('#slay circle');
|
|
||||||
projectiles.forEach(proj => {
|
|
||||||
this.animations.push(anime({
|
|
||||||
targets: proj,
|
|
||||||
cx: Math.random() * 250 + 25,
|
|
||||||
cy: Math.random() * 200 - 100,
|
|
||||||
delay: TIMES.TARGET_DURATION_MS * 0.7,
|
|
||||||
duration: TIMES.TARGET_DURATION_MS * 0.3,
|
|
||||||
easing: 'easeInQuad',
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|||||||
@ -40,6 +40,7 @@ class Strike extends Component {
|
|||||||
height: [200, 10, 0],
|
height: [200, 10, 0],
|
||||||
width: [20, 400, 0],
|
width: [20, 400, 0],
|
||||||
duration: TIMES.TARGET_DURATION_MS,
|
duration: TIMES.TARGET_DURATION_MS,
|
||||||
|
delay: TIMES.TARGET_DURATION_MS * 0.2,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.animations.push(anime({
|
this.animations.push(anime({
|
||||||
|
|||||||
@ -13,8 +13,8 @@ const { ConstructAnimation } = require('./animations');
|
|||||||
|
|
||||||
const addState = connect(
|
const addState = connect(
|
||||||
function receiveState(state) {
|
function receiveState(state) {
|
||||||
const { animSource, animTarget, resolution, account } = state;
|
const { animating, animSource, animTarget, resolution, account } = state;
|
||||||
return { animSource, animTarget, resolution, account };
|
return { animating, animSource, animTarget, resolution, account };
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -43,6 +43,7 @@ class ConstructAvatar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClick() {
|
onClick() {
|
||||||
|
if (this.props.animating) return false;
|
||||||
return this.animations.push(wiggle(this.props.construct.id, this.idle));
|
return this.animations.push(wiggle(this.props.construct.id, this.idle));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +66,10 @@ class ConstructAvatar extends Component {
|
|||||||
const { animSource, animTarget, resolution, construct, account } = this.props;
|
const { animSource, animTarget, resolution, construct, account } = this.props;
|
||||||
// a different text object and text construct
|
// a different text object and text construct
|
||||||
if (resolution && resolution !== prevProps.resolution && resolution.event[1].construct === construct.id) {
|
if (resolution && resolution !== prevProps.resolution && resolution.event[1].construct === construct.id) {
|
||||||
return wiggle(construct.id, this.idle);
|
const type = resolution.event[0];
|
||||||
|
// only trigger the wiggle on damage and ko events rather than spam it on everything
|
||||||
|
// also stops wiggle triggering when invert effect is applied
|
||||||
|
if (['Damage', 'Ko'].includes(type)) return wiggle(construct.id, this.idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// different source object and source construct
|
// different source object and source construct
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const addState = connect(
|
|||||||
function receiveState(state) {
|
function receiveState(state) {
|
||||||
const {
|
const {
|
||||||
ws,
|
ws,
|
||||||
|
authenticated,
|
||||||
account,
|
account,
|
||||||
game,
|
game,
|
||||||
instance,
|
instance,
|
||||||
@ -17,6 +18,7 @@ const addState = connect(
|
|||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
authenticated,
|
||||||
account,
|
account,
|
||||||
game,
|
game,
|
||||||
instance,
|
instance,
|
||||||
@ -28,6 +30,7 @@ const addState = connect(
|
|||||||
function Controls(args) {
|
function Controls(args) {
|
||||||
const {
|
const {
|
||||||
game,
|
game,
|
||||||
|
authenticated,
|
||||||
account,
|
account,
|
||||||
instance,
|
instance,
|
||||||
nav,
|
nav,
|
||||||
@ -38,6 +41,7 @@ function Controls(args) {
|
|||||||
|
|
||||||
if (game) return <GameCtrl />;
|
if (game) return <GameCtrl />;
|
||||||
if (instance) return <InstanceCtrl />;
|
if (instance) return <InstanceCtrl />;
|
||||||
|
if (!authenticated) return false;
|
||||||
if (nav === 'play' || nav === 'shop' || nav === 'reshape' || !nav) return <PlayCtrl />
|
if (nav === 'play' || nav === 'shop' || nav === 'reshape' || !nav) return <PlayCtrl />
|
||||||
if (nav === 'team' || nav === 'account') return <TeamCtrl />
|
if (nav === 'team' || nav === 'account') return <TeamCtrl />
|
||||||
|
|
||||||
|
|||||||
@ -1,190 +0,0 @@
|
|||||||
const { connect } = require('preact-redux');
|
|
||||||
const preact = require('preact');
|
|
||||||
|
|
||||||
// const actions = require('../actions');
|
|
||||||
const shapes = require('./shapes');
|
|
||||||
|
|
||||||
const { ConstructAvatar } = require('./construct');
|
|
||||||
// const { ConstructAnimation } = require('./animations');
|
|
||||||
|
|
||||||
const addState = connect(
|
|
||||||
function receiveState(state) {
|
|
||||||
const {
|
|
||||||
account,
|
|
||||||
itemInfo,
|
|
||||||
demo,
|
|
||||||
} = state;
|
|
||||||
|
|
||||||
return {
|
|
||||||
account,
|
|
||||||
itemInfo,
|
|
||||||
demo,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* function receiveDispatch(dispatch) {
|
|
||||||
function setAnimTarget(anim) {
|
|
||||||
dispatch(actions.setAnimTarget(anim));
|
|
||||||
}
|
|
||||||
|
|
||||||
return { setAnimTarget };
|
|
||||||
} */
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
function Demo(args) {
|
|
||||||
const {
|
|
||||||
demo,
|
|
||||||
itemInfo,
|
|
||||||
account,
|
|
||||||
|
|
||||||
// setAnimTarget,
|
|
||||||
} = args;
|
|
||||||
|
|
||||||
if (!demo || !itemInfo.items.length || account) return false;
|
|
||||||
|
|
||||||
const { combiner, items, equipping, equipped, players, combo } = demo;
|
|
||||||
|
|
||||||
const vboxDemo = () => {
|
|
||||||
function stashBtn(i, j) {
|
|
||||||
if (!i) return <button disabled class='empty' > </button>;
|
|
||||||
const highlighted = combiner.indexOf(j) > -1;
|
|
||||||
const classes = `${highlighted ? 'highlight' : ''}`;
|
|
||||||
|
|
||||||
if (shapes[i]) {
|
|
||||||
return <button class={classes} key={j}>{shapes[i]()}</button>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <button class={classes}>{i}</button>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function combinerBtn() {
|
|
||||||
let text = '';
|
|
||||||
|
|
||||||
if (combiner.length < 3) {
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
if (combiner.length > i) {
|
|
||||||
text += '■ ';
|
|
||||||
} else {
|
|
||||||
text += '▫ ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
text = 'combine';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
class='vbox-btn'
|
|
||||||
disabled={combiner.length !== 3}>
|
|
||||||
{text}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stashElement() {
|
|
||||||
return (
|
|
||||||
<div class="vbox">
|
|
||||||
<div class='vbox-section'>
|
|
||||||
<h2 class='colour-info'>
|
|
||||||
VBOX PHASE {shapes.Red()} {shapes.Green()} {shapes.Blue()}
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
Combine colours with base skills and specialisations to build an array of powerful variants.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div> </div>
|
|
||||||
<div class='vbox-section'>
|
|
||||||
<div class='vbox-items'>
|
|
||||||
{items.map((i, j) => stashBtn(i, j))}
|
|
||||||
</div>
|
|
||||||
{combinerBtn()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="news vbox-demo">
|
|
||||||
{stashElement()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const vboxConstructs = () => {
|
|
||||||
const btnClass = equipping
|
|
||||||
? 'equipping empty gray'
|
|
||||||
: 'empty gray';
|
|
||||||
|
|
||||||
const constructEl = c => (
|
|
||||||
<div class="instance-construct">
|
|
||||||
<h2 class="name" >{c.name}</h2>
|
|
||||||
<ConstructAvatar construct={c} />
|
|
||||||
<div class="skills">
|
|
||||||
{equipped
|
|
||||||
? <button>{combo}</button>
|
|
||||||
: <button disabled={!equipping} class={btnClass}>SKILL</button>
|
|
||||||
}
|
|
||||||
<button disabled={!equipping} class={btnClass}>SKILL</button>
|
|
||||||
<button disabled={!equipping} class={btnClass}>SKILL</button>
|
|
||||||
</div>
|
|
||||||
<div class="specs">
|
|
||||||
</div>
|
|
||||||
<div class="stats">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section class="construct-section">
|
|
||||||
<div>
|
|
||||||
<h2>CONSTRUCTS</h2>
|
|
||||||
<p><b>Constructs</b> are the units you control. They are reset every game and their initial appearance is randomly generated.</p>
|
|
||||||
<p><b>Skills</b> and <b>Specs</b> you create in the <b>VBOX Phase</b> are equipped to your constructs to create a build.</p>
|
|
||||||
</div>
|
|
||||||
<div class='construct-list'>
|
|
||||||
{constructEl(players[0].constructs[0])}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const gameDemo = () => {
|
|
||||||
return (
|
|
||||||
<section class="game-demo">
|
|
||||||
<div>
|
|
||||||
<h2>COMBAT PHASE</h2>
|
|
||||||
<p>Battle your opponent using dynamic team builds from the VBOX phase.</p>
|
|
||||||
<p>The skills crafted can be used to damage the opponent or support your team.</p>
|
|
||||||
<p>Simultaneous turn based combat: each team picks targets for their skills during this phase.</p>
|
|
||||||
<p>The damage dealt by skills, cast order and construct life depend on your decisions in the VBOX phase.</p>
|
|
||||||
</div>
|
|
||||||
<div class="game">
|
|
||||||
<div class="game-construct">
|
|
||||||
<div class="left"></div>
|
|
||||||
<div class="right">
|
|
||||||
<ConstructAvatar construct={players[1].constructs[0]} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div></div>
|
|
||||||
<div class="game-construct">
|
|
||||||
<div class="left"></div>
|
|
||||||
<div class="right">
|
|
||||||
<ConstructAvatar construct={players[1].constructs[1]} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section class='demo news top'>
|
|
||||||
{gameDemo()}
|
|
||||||
{vboxDemo()}
|
|
||||||
{vboxConstructs()}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = addState(Demo);
|
|
||||||
85
client/src/components/front.page.jsx
Normal file
85
client/src/components/front.page.jsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// const { connect } = require('preact-redux');
|
||||||
|
const preact = require('preact');
|
||||||
|
const { connect } = require('preact-redux');
|
||||||
|
|
||||||
|
const { errorToast, infoToast } = require('../utils');
|
||||||
|
const actions = require('./../actions');
|
||||||
|
|
||||||
|
const VERSION = process.env.npm_package_version;
|
||||||
|
|
||||||
|
const Welcome = require('./welcome');
|
||||||
|
|
||||||
|
const addState = connect(
|
||||||
|
function receiveState(state) {
|
||||||
|
const {
|
||||||
|
ws,
|
||||||
|
account,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
function sendInstancePractice() {
|
||||||
|
ws.sendInstancePractice();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
account,
|
||||||
|
sendInstancePractice,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function Play(args) {
|
||||||
|
const {
|
||||||
|
account,
|
||||||
|
sendInstancePractice,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
const news = (
|
||||||
|
<div class="list">
|
||||||
|
<div class="intro">
|
||||||
|
<p> MNML is a turn-based 1v1 strategy game in an abstract setting. </p>
|
||||||
|
<p>
|
||||||
|
Build a unique team of 3 constructs from a range of skills and specialisations.<br />
|
||||||
|
Outplay your opponent across multiple rounds by adapting to an always shifting meta. <br />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="awards"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const list = () => {
|
||||||
|
return (
|
||||||
|
<div class='list play'>
|
||||||
|
<figure>
|
||||||
|
<button
|
||||||
|
class="ready"
|
||||||
|
onClick={() => sendInstancePractice()}>
|
||||||
|
Play
|
||||||
|
</button>
|
||||||
|
<figcaption>Learn MNML</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<button
|
||||||
|
class='discord-btn'
|
||||||
|
onClick={() => window.open('https://discord.gg/YJJgurM') }>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
<figcaption>Join the Community</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<div class="logo"/>
|
||||||
|
<hr />
|
||||||
|
{list()}
|
||||||
|
<hr />
|
||||||
|
<Welcome />
|
||||||
|
<hr />
|
||||||
|
{news}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = addState(Play);
|
||||||
@ -41,36 +41,38 @@ class AnimText extends preact.Component {
|
|||||||
|
|
||||||
const generateAnimText = () => {
|
const generateAnimText = () => {
|
||||||
const [type, event] = resolution.event;
|
const [type, event] = resolution.event;
|
||||||
if (type === 'Ko') return <h1><span>KO!</span></h1>;
|
switch (type) {
|
||||||
if (type === 'Disable') {
|
case 'Damage': {
|
||||||
const { disable } = event;
|
const { amount, mitigation, colour } = event;
|
||||||
return <h1><span>{disable}</span></h1>;
|
|
||||||
}
|
|
||||||
if (type === 'Immunity') return <h1><span>IMMUNE</span></h1>;
|
|
||||||
if (type === 'Damage') {
|
|
||||||
const { mitigation, colour } = event;
|
|
||||||
let { amount } = event;
|
|
||||||
amount *= -1;
|
|
||||||
const mitigationText = mitigation ? `(${mitigation})` : '';
|
const mitigationText = mitigation ? `(${mitigation})` : '';
|
||||||
return <h1><span class={colour.toLowerCase()}>{amount} {mitigationText} </span></h1>;
|
return <h1><span class={colour.toLowerCase()}>-{amount} {mitigationText} </span></h1>;
|
||||||
}
|
}
|
||||||
if (type === 'Healing') {
|
case 'Healing': {
|
||||||
const { amount, overhealing, colour } = event;
|
const { amount, overhealing, colour } = event;
|
||||||
return <h1><span class={colour.toLowerCase()}>{amount} ({overhealing} OH)</span></h1>;
|
const overHealingText = overhealing ? `(${overhealing} OH)` : '';
|
||||||
|
return <h1><span class={colour.toLowerCase()}>+{amount} {overHealingText}</span></h1>;
|
||||||
}
|
}
|
||||||
if (type === 'Inversion') return <h1><span>INVERT</span></h1>;
|
case 'Effect': {
|
||||||
if (type === 'Reflection') return <h1><span>REFLECT</span></h1>;
|
|
||||||
if (type === 'Effect') {
|
|
||||||
const { effect, duration } = event;
|
const { effect, duration } = event;
|
||||||
return <h1><span>+{effect} {duration}T</span></h1>;
|
return <h1><span>+{effect} {duration}T</span></h1>;
|
||||||
}
|
}
|
||||||
if (type === 'Removal') {
|
case 'Removal': {
|
||||||
const { effect } = event;
|
const { effect } = event;
|
||||||
if (!effect) return <h1><span>Effect Removal</span></h1>;
|
if (!effect) return <h1><span>Effect Removal</span></h1>;
|
||||||
return <h1><span>{effect}</span></h1>;
|
return <h1><span>-{effect}</span></h1>;
|
||||||
|
}
|
||||||
|
case 'Ko': return <h1><span>KO!</span></h1>;
|
||||||
|
case 'Reflection': return <h1><span>REFLECT</span></h1>;
|
||||||
|
default: return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
// We don't send inversion / disable / immune event text
|
||||||
|
/* case 'Inversion': return <h1><span>INVERT</span></h1>;
|
||||||
|
case 'Disable': {
|
||||||
|
const { disable } = event;
|
||||||
|
return <h1><span>{disable}</span></h1>;
|
||||||
|
}
|
||||||
|
case 'Immunity': return <h1><span>IMMUNE</span></h1>; */
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="combat-text">
|
<div class="combat-text">
|
||||||
|
|||||||
@ -59,11 +59,16 @@ class GameConstruct extends preact.Component {
|
|||||||
player,
|
player,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const ko = construct.green_life.value === 0 ? 'ko' : '';
|
// construct green_life comes from game state and won't update during animations
|
||||||
|
// treat the construct as ko for the remainder of the anims if ko event occurs
|
||||||
|
const ko = construct.green_life.value === 0 || this.ko ? 'ko' : '';
|
||||||
const koEvent = () => {
|
const koEvent = () => {
|
||||||
if (resolution) {
|
if (resolution) {
|
||||||
const [type, variant] = resolution.event;
|
const [type, variant] = resolution.event;
|
||||||
if (variant.construct === construct.id && type === 'Ko') return 'ko-transition';
|
if (variant.construct === construct.id && type === 'Ko') {
|
||||||
|
this.ko = true;
|
||||||
|
return 'ko-transition';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,6 +8,7 @@ const addState = connect(
|
|||||||
const {
|
const {
|
||||||
ws,
|
ws,
|
||||||
account,
|
account,
|
||||||
|
authenticated,
|
||||||
nav,
|
nav,
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ const addState = connect(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
|
authenticated,
|
||||||
nav,
|
nav,
|
||||||
|
|
||||||
sendInstanceState,
|
sendInstanceState,
|
||||||
@ -48,6 +50,7 @@ const addState = connect(
|
|||||||
function Header(args) {
|
function Header(args) {
|
||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
|
authenticated,
|
||||||
nav,
|
nav,
|
||||||
|
|
||||||
sendAccountStates,
|
sendAccountStates,
|
||||||
@ -56,6 +59,8 @@ function Header(args) {
|
|||||||
|
|
||||||
if (!account) return false;
|
if (!account) return false;
|
||||||
|
|
||||||
|
if (!authenticated) return false;
|
||||||
|
|
||||||
function navTo(p) {
|
function navTo(p) {
|
||||||
return setNav(p);
|
return setNav(p);
|
||||||
}
|
}
|
||||||
@ -68,11 +73,6 @@ function Header(args) {
|
|||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<button
|
|
||||||
onClick={() => navTo('play')}
|
|
||||||
class='logo login-btn'>
|
|
||||||
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => navTo('play')}
|
onClick={() => navTo('play')}
|
||||||
class={`login-btn ${nav === 'play' ? 'highlight' : ''}`}>
|
class={`login-btn ${nav === 'play' ? 'highlight' : ''}`}>
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const addState = connect(
|
|||||||
function Top(args) {
|
function Top(args) {
|
||||||
const {
|
const {
|
||||||
nav,
|
nav,
|
||||||
|
authenticated,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
if (nav === 'account') return <AccountTop />;
|
if (nav === 'account') return <AccountTop />;
|
||||||
|
|||||||
@ -4,27 +4,38 @@ const { connect } = require('preact-redux');
|
|||||||
const Main = require('./main');
|
const Main = require('./main');
|
||||||
// const Nav = require('./nav');
|
// const Nav = require('./nav');
|
||||||
const Controls = require('./controls');
|
const Controls = require('./controls');
|
||||||
|
const FrontPage = require('./front.page');
|
||||||
|
const Noise = require('./noise');
|
||||||
|
|
||||||
const addState = connect(
|
const addState = connect(
|
||||||
({ game, instance }) => ({ game, instance })
|
({ game, instance, authenticated }) => ({ game, instance, authenticated })
|
||||||
);
|
);
|
||||||
|
|
||||||
function Mnml(args) {
|
function Mnml(args) {
|
||||||
const {
|
const {
|
||||||
game,
|
game,
|
||||||
instance,
|
instance,
|
||||||
|
authenticated,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
const rotateClass = (game || instance) && window.innerHeight < 900 && window.innerWidth < window.innerHeight
|
const rotateClass = (game || instance) && window.innerHeight < 900 && window.innerWidth < window.innerHeight
|
||||||
? 'show'
|
? 'show'
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
if (!authenticated && !instance && !game) return (
|
||||||
|
<div id="mnml" class='front-page'>
|
||||||
|
<Noise />
|
||||||
|
<FrontPage />
|
||||||
|
<div id="rotate" class={rotateClass} ></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="mnml">
|
<div id="mnml">
|
||||||
<Main />
|
<Main />
|
||||||
<Controls />
|
<Controls />
|
||||||
<div id="rotate" class={rotateClass} >
|
<Noise />
|
||||||
</div>
|
<div id="rotate" class={rotateClass} ></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
75
client/src/components/noise.jsx
Normal file
75
client/src/components/noise.jsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const preact = require('preact');
|
||||||
|
const { Component } = require('preact');
|
||||||
|
const anime = require('animejs').default;
|
||||||
|
|
||||||
|
class Noise extends Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.animations = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="noise"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 400 400">
|
||||||
|
<filter id='noiseFilter'>
|
||||||
|
<feTurbulence type="turbulence" baseFrequency="0.2" numOctaves="2" result="turbulence"></feTurbulence>
|
||||||
|
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="2" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap>
|
||||||
|
</filter>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.animations.push(anime({
|
||||||
|
targets: ['#noiseFilter feTurbulence', '#noiseFilter feDisplacementMap'],
|
||||||
|
easing: 'linear',
|
||||||
|
loop: true,
|
||||||
|
keyframes: [
|
||||||
|
{
|
||||||
|
baseFrequency: 0.5,
|
||||||
|
duration: () => anime.random(1000, 2000),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.animations.push(anime({
|
||||||
|
targets: ['#noiseFilter feDisplacementMap'],
|
||||||
|
easing: 'linear',
|
||||||
|
loop: true,
|
||||||
|
keyframes: [
|
||||||
|
{
|
||||||
|
scale: 2,
|
||||||
|
duration: () => anime.random(2000, 5000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scale: 4,
|
||||||
|
duration: () => anime.random(150, 250),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scale: 2,
|
||||||
|
duration: () => anime.random(100, 150),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scale: 4,
|
||||||
|
duration: () => anime.random(150, 250),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is necessary because
|
||||||
|
// skipping / timing / unmounting race conditions
|
||||||
|
// can cause the animations to cut short, this will ensure the values are reset
|
||||||
|
// because preact will recycle all these components
|
||||||
|
componentWillUnmount() {
|
||||||
|
for (let i = this.animations.length - 1; i >= 0; i--) {
|
||||||
|
this.animations[i].reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Noise;
|
||||||
@ -71,7 +71,6 @@ class Combos extends preact.Component {
|
|||||||
<div class="combos">
|
<div class="combos">
|
||||||
<div class="combo-header">
|
<div class="combo-header">
|
||||||
<h2>COMBOS</h2>
|
<h2>COMBOS</h2>
|
||||||
Combine colours and items.
|
|
||||||
</div>
|
</div>
|
||||||
<div class="combo-list"
|
<div class="combo-list"
|
||||||
onMouseOver={e => e.stopPropagation()}
|
onMouseOver={e => e.stopPropagation()}
|
||||||
|
|||||||
@ -5,10 +5,9 @@ const Login = require('./welcome.login');
|
|||||||
const Register = require('./welcome.register');
|
const Register = require('./welcome.register');
|
||||||
const Help = require('./welcome.help');
|
const Help = require('./welcome.help');
|
||||||
// const About = require('./welcome.about');
|
// const About = require('./welcome.about');
|
||||||
const Demo = require('./demo');
|
|
||||||
|
|
||||||
function Welcome() {
|
function Welcome() {
|
||||||
const page = this.state.page || 'register';
|
const page = this.state.page || 'login';
|
||||||
|
|
||||||
const pageEl = () => {
|
const pageEl = () => {
|
||||||
if (page === 'login') return <Login />;
|
if (page === 'login') return <Login />;
|
||||||
@ -17,65 +16,32 @@ function Welcome() {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const news = (
|
const form = <div>{pageEl()}</div>;
|
||||||
<div class="news">
|
|
||||||
<p> Welcome to mnml.</p>
|
|
||||||
|
|
||||||
<p> MNML is a turn-based 1v1 strategy game in an abstract setting. </p>
|
|
||||||
<p>
|
|
||||||
Build a unique team of 3 constructs from a range of skills and specialisations.<br />
|
|
||||||
Outplay your opponent in multiple rounds by adapting to an always shifting meta. <br />
|
|
||||||
Simple rules, complex interactions and unique mechanics.<br />
|
|
||||||
</p>
|
|
||||||
<p> Free to play, no pay to win. Register to start playing.<br /></p>
|
|
||||||
|
|
||||||
<a href='https://www.youtube.com/watch?v=VtZLlkpJuS8'>Tutorial Playthrough on YouTube</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const main = (['login', 'register', 'help'].includes(page))
|
|
||||||
? <section>{news}{pageEl()}</section>
|
|
||||||
: <Demo />;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main class="menu welcome">
|
<header>
|
||||||
<header>
|
<div class="options">
|
||||||
<div class="options">
|
<button
|
||||||
<button
|
class={`login-btn ${page === 'login' ? 'highlight' : ''}`}
|
||||||
onClick={() => this.setState({ page: 'login' })}
|
disabled={page === 'login'}
|
||||||
class='logo login-btn'>
|
onClick={() => this.setState({ page: 'login' })}>
|
||||||
|
Login
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class={`login-btn ${page === 'login' ? 'highlight' : ''}`}
|
class={`login-btn ${page === 'register' ? 'highlight' : ''}`}
|
||||||
disabled={page === 'login'}
|
disabled={page === 'register'}
|
||||||
onClick={() => this.setState({ page: 'login' })}>
|
onClick={() => this.setState({ page: 'register' })}>
|
||||||
Login
|
Register
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class={`login-btn ${page === 'register' ? 'highlight' : ''}`}
|
class={`login-btn ${page === 'help' ? 'highlight' : ''}`}
|
||||||
disabled={page === 'register'}
|
disabled={page === 'help'}
|
||||||
onClick={() => this.setState({ page: 'register' })}>
|
onClick={() => this.setState({ page: 'help' })}>
|
||||||
Register
|
Help
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
class={`login-btn ${page === 'info' ? 'highlight' : ''}`}
|
|
||||||
disabled={page === 'info'}
|
|
||||||
onClick={() => this.setState({ page: 'info' })}>
|
|
||||||
Info
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class={`login-btn ${page === 'help' ? 'highlight' : ''}`}
|
|
||||||
disabled={page === 'help'}
|
|
||||||
onClick={() => this.setState({ page: 'help' })}>
|
|
||||||
Help
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="top">
|
|
||||||
{main}
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
{form}
|
||||||
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,6 @@ function registerEvents(store) {
|
|||||||
|
|
||||||
function clearTutorial() {
|
function clearTutorial() {
|
||||||
store.dispatch(actions.setTutorial(null));
|
store.dispatch(actions.setTutorial(null));
|
||||||
localStorage.setItem('tutorial-complete', true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -35,7 +34,6 @@ function registerEvents(store) {
|
|||||||
store.dispatch(actions.setTutorialGame(null));
|
store.dispatch(actions.setTutorialGame(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function setPing(ping) {
|
function setPing(ping) {
|
||||||
store.dispatch(actions.setPing(ping));
|
store.dispatch(actions.setPing(ping));
|
||||||
}
|
}
|
||||||
@ -106,16 +104,21 @@ function registerEvents(store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setAccount(account) {
|
function setAccount(account) {
|
||||||
if (account && process.env.NODE_ENV !== 'development') {
|
store.dispatch(actions.setAccount(account));
|
||||||
LogRocket.init('yh0dy3/mnml');
|
}
|
||||||
LogRocket.identify(account.id, account);
|
|
||||||
|
|
||||||
if (window.Notification) {
|
function setAuthenticated(account) {
|
||||||
window.Notification.requestPermission();
|
if (account && window.Notification) {
|
||||||
}
|
window.Notification.requestPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'development') {
|
||||||
|
LogRocket.identify(account.id, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch(actions.setAccount(account));
|
store.dispatch(actions.setAccount(account));
|
||||||
|
store.dispatch(actions.setTutorial(null));
|
||||||
|
store.dispatch(actions.setAuthenticated(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setEmail(email) {
|
function setEmail(email) {
|
||||||
@ -180,18 +183,14 @@ function registerEvents(store) {
|
|||||||
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 (v.phase === 'Finished') {
|
if (v.phase === 'Finished') {
|
||||||
ws.sendAccountInstances();
|
ws.sendAccountInstances();
|
||||||
}
|
}
|
||||||
|
|
||||||
// instance.mobile.less hides info at @media 1000
|
|
||||||
if (localStorage.getItem('tutorial-complete') || window.innerWidth <= 1100) {
|
|
||||||
store.dispatch(actions.setTutorial(null));
|
|
||||||
} else if (v.time_control === 'Practice' && v.rounds.length === 1 && tutorial) {
|
|
||||||
tutorialVbox(player, store, tutorial);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return store.dispatch(actions.setInstance(v));
|
return store.dispatch(actions.setInstance(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,94 +206,6 @@ function registerEvents(store) {
|
|||||||
return store.dispatch(actions.setItemInfo(v));
|
return store.dispatch(actions.setItemInfo(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDemo(d) {
|
|
||||||
|
|
||||||
const vboxDemo = {
|
|
||||||
players: d,
|
|
||||||
combiner: [],
|
|
||||||
equipped: false,
|
|
||||||
equipping: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const startDemo = () => {
|
|
||||||
const { account, itemInfo } = store.getState();
|
|
||||||
if (account) return false;
|
|
||||||
if (!itemInfo || itemInfo.items.length === 0) return setTimeout(startDemo, 500);
|
|
||||||
store.dispatch(actions.setAnimTarget(null));
|
|
||||||
const bases = ['Attack', 'Stun', 'Buff', 'Debuff', 'Block'];
|
|
||||||
const combo = sample(itemInfo.combos.filter(i => bases.some(b => i.components.includes(b))));
|
|
||||||
vboxDemo.combo = combo.item;
|
|
||||||
vboxDemo.items = combo.components;
|
|
||||||
store.dispatch(actions.setDemo(vboxDemo));
|
|
||||||
|
|
||||||
|
|
||||||
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0] }))), 500);
|
|
||||||
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0, 1] }))), 1000);
|
|
||||||
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0, 1, 2] }))), 1500);
|
|
||||||
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [], items: [vboxDemo.combo, '', ''] }))), 2500);
|
|
||||||
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0], items: [vboxDemo.combo, '', ''], equipping: true }))), 3000);
|
|
||||||
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [], items: ['', '', ''], equipped: true, equipping: false }))), 4000);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
return store.dispatch(actions.setAnimTarget({
|
|
||||||
skill: sample(itemInfo.items.filter(i => i.skill)).item,
|
|
||||||
constructId: d[1].constructs[0].id,
|
|
||||||
player: false,
|
|
||||||
direction: 0,
|
|
||||||
}));
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
return store.dispatch(actions.setAnimTarget({
|
|
||||||
skill: sample(itemInfo.items.filter(i => i.skill)).item,
|
|
||||||
constructId: d[1].constructs[1].id,
|
|
||||||
player: true,
|
|
||||||
direction: 0,
|
|
||||||
}));
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
return setTimeout(startDemo, 5000);
|
|
||||||
};
|
|
||||||
|
|
||||||
startDemo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// store.subscribe(setInfo);
|
|
||||||
// store.on('SET_INFO', setInfo);
|
|
||||||
|
|
||||||
// events.on('SET_PLAYER', setInstance);
|
|
||||||
|
|
||||||
// events.on('SEND_SKILL', function skillActive(gameId, constructId, targetConstructId, skill) {
|
|
||||||
// ws.sendGameSkill(gameId, constructId, targetConstructId, skill);
|
|
||||||
// setConstructStatusUpdate(constructId, skill, targetConstructId);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// events.on('CONSTRUCT_ACTIVE', function constructActiveCb(construct) {
|
|
||||||
// for (let i = 0; i < constructs.length; i += 1) {
|
|
||||||
// if (constructs[i].id === construct.id) constructs[i].active = !constructs[i].active;
|
|
||||||
// }
|
|
||||||
// return setConstructs(constructs);
|
|
||||||
// });
|
|
||||||
|
|
||||||
/* function errorPrompt(type) {
|
|
||||||
const message = errMessages[type];
|
|
||||||
const OK_BUTTON = '<button type="submit">OK</button>';
|
|
||||||
toast.error({
|
|
||||||
theme: 'dark',
|
|
||||||
color: 'black',
|
|
||||||
timeout: false,
|
|
||||||
drag: false,
|
|
||||||
position: 'center',
|
|
||||||
maxWidth: window.innerWidth / 2,
|
|
||||||
close: false,
|
|
||||||
buttons: [
|
|
||||||
[OK_BUTTON, (instance, thisToast) => instance.hide({ transitionOut: 'fadeOut' }, thisToast)],
|
|
||||||
],
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
} */
|
|
||||||
// setup / localstorage
|
|
||||||
|
|
||||||
function urlHashChange() {
|
function urlHashChange() {
|
||||||
const { ws } = store.getState();
|
const { ws } = store.getState();
|
||||||
const cmds = querystring.parse(location.hash);
|
const cmds = querystring.parse(location.hash);
|
||||||
@ -303,6 +214,11 @@ function registerEvents(store) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startTutorial() {
|
||||||
|
store.dispatch(actions.setTutorial(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener('hashchange', urlHashChange, false);
|
window.addEventListener('hashchange', urlHashChange, false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -313,11 +229,11 @@ function registerEvents(store) {
|
|||||||
clearTutorial,
|
clearTutorial,
|
||||||
clearTutorialGame,
|
clearTutorialGame,
|
||||||
setAccount,
|
setAccount,
|
||||||
|
setAuthenticated,
|
||||||
setAccountInstances,
|
setAccountInstances,
|
||||||
setActiveItem,
|
setActiveItem,
|
||||||
setActiveSkill,
|
setActiveSkill,
|
||||||
setChatWheel,
|
setChatWheel,
|
||||||
setDemo,
|
|
||||||
setConstructList,
|
setConstructList,
|
||||||
setNewConstruct,
|
setNewConstruct,
|
||||||
setGame,
|
setGame,
|
||||||
@ -333,6 +249,8 @@ function registerEvents(store) {
|
|||||||
setSubscription,
|
setSubscription,
|
||||||
setWs,
|
setWs,
|
||||||
|
|
||||||
|
startTutorial,
|
||||||
|
|
||||||
urlHashChange,
|
urlHashChange,
|
||||||
|
|
||||||
notify,
|
notify,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ function createReducer(defaultState, actionType) {
|
|||||||
/* eslint-disable key-spacing */
|
/* eslint-disable key-spacing */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
account: createReducer(null, 'SET_ACCOUNT'),
|
account: createReducer(null, 'SET_ACCOUNT'),
|
||||||
|
authenticated: createReducer(null, 'SET_AUTHENTICATED'),
|
||||||
activeItem: createReducer(null, 'SET_ACTIVE_VAR'),
|
activeItem: createReducer(null, 'SET_ACTIVE_VAR'),
|
||||||
activeSkill: createReducer(null, 'SET_ACTIVE_SKILL'),
|
activeSkill: createReducer(null, 'SET_ACTIVE_SKILL'),
|
||||||
|
|
||||||
@ -20,8 +21,6 @@ module.exports = {
|
|||||||
|
|
||||||
resolution: createReducer(null, 'SET_RESOLUTION'),
|
resolution: createReducer(null, 'SET_RESOLUTION'),
|
||||||
|
|
||||||
demo: createReducer(null, 'SET_DEMO'),
|
|
||||||
|
|
||||||
chatShow: createReducer(null, 'SET_CHAT_SHOW'),
|
chatShow: createReducer(null, 'SET_CHAT_SHOW'),
|
||||||
chatWheel: createReducer([], 'SET_CHAT_WHEEL'),
|
chatWheel: createReducer([], 'SET_CHAT_WHEEL'),
|
||||||
|
|
||||||
|
|||||||
@ -256,10 +256,6 @@ function createSocket(events) {
|
|||||||
events.setItemInfo(info);
|
events.setItemInfo(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDemo(v) {
|
|
||||||
events.setDemo(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
let pongTimeout;
|
let pongTimeout;
|
||||||
function onPong() {
|
function onPong() {
|
||||||
events.setPing(Date.now() - ping);
|
events.setPing(Date.now() - ping);
|
||||||
@ -274,6 +270,7 @@ function createSocket(events) {
|
|||||||
// this object wraps the reply types to a function
|
// this object wraps the reply types to a function
|
||||||
const handlers = {
|
const handlers = {
|
||||||
AccountState: onAccount,
|
AccountState: onAccount,
|
||||||
|
AccountAuthenticated: account => events.setAuthenticated(account),
|
||||||
AccountConstructs: onAccountConstructs,
|
AccountConstructs: onAccountConstructs,
|
||||||
AccountTeam: onAccountTeam,
|
AccountTeam: onAccountTeam,
|
||||||
AccountInstances: onAccountInstances,
|
AccountInstances: onAccountInstances,
|
||||||
@ -285,7 +282,6 @@ function createSocket(events) {
|
|||||||
InstanceState: onInstanceState,
|
InstanceState: onInstanceState,
|
||||||
ItemInfo: onItemInfo,
|
ItemInfo: onItemInfo,
|
||||||
Pong: onPong,
|
Pong: onPong,
|
||||||
Demo: onDemo,
|
|
||||||
|
|
||||||
// QueueRequested: () => events.notify('PVP queue request received.'),
|
// QueueRequested: () => events.notify('PVP queue request received.'),
|
||||||
QueueRequested: () => true,
|
QueueRequested: () => true,
|
||||||
@ -304,6 +300,8 @@ function createSocket(events) {
|
|||||||
ChatWheel: wheel => events.setChatWheel(wheel),
|
ChatWheel: wheel => events.setChatWheel(wheel),
|
||||||
// Joining: () => events.notify('Searching for instance...'),
|
// Joining: () => events.notify('Searching for instance...'),
|
||||||
|
|
||||||
|
StartTutorial: () => events.startTutorial(),
|
||||||
|
|
||||||
Processing: () => true,
|
Processing: () => true,
|
||||||
Error: errHandler,
|
Error: errHandler,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -115,8 +115,9 @@ function tutorialStage(tutorial, clearTutorial, instance) {
|
|||||||
if (tutorial === 1) {
|
if (tutorial === 1) {
|
||||||
return (
|
return (
|
||||||
<div class='info-item'>
|
<div class='info-item'>
|
||||||
<h2>Tutorial</h2>
|
<h1>Welcome to MNML</h1>
|
||||||
<p> Welcome to the vbox phase tutorial.</p>
|
<p> This is the <b>VBOX Phase</b> tutorial.</p>
|
||||||
|
<p> In the <b>VBOX Phase</b> you customise your constructs' skills and specialisations. </p>
|
||||||
<p> Colours are used to create powerful combinations with base items. </p>
|
<p> Colours are used to create powerful combinations with base items. </p>
|
||||||
<p> Buy the two colours from the store to continue. </p>
|
<p> Buy the two colours from the store to continue. </p>
|
||||||
</div>
|
</div>
|
||||||
@ -126,9 +127,9 @@ function tutorialStage(tutorial, clearTutorial, instance) {
|
|||||||
if (tutorial === 2) {
|
if (tutorial === 2) {
|
||||||
return (
|
return (
|
||||||
<div class='info-item'>
|
<div class='info-item'>
|
||||||
<h2>Tutorial</h2>
|
<h2>Combining Items</h2>
|
||||||
<p> You start the game with the base <b>Attack</b> skill item. </p>
|
<p> You start the game with the base <b>Attack</b> skill item. </p>
|
||||||
<p> Highlight all three items then click combine.</p>
|
<p> Highlight the <b>Attack</b> and the two <b> colours</b> then click <b> combine</b> </p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -137,11 +138,11 @@ function tutorialStage(tutorial, clearTutorial, instance) {
|
|||||||
const constructOne = instance.players[0].constructs[0].name;
|
const constructOne = instance.players[0].constructs[0].name;
|
||||||
return (
|
return (
|
||||||
<div class='info-item'>
|
<div class='info-item'>
|
||||||
<h2>Tutorial</h2>
|
<h2>Equipping Items</h2>
|
||||||
<p> The first construct on your team is <b>{constructOne}</b>. </p>
|
<p> The first construct on your team is <b>{constructOne}</b>. </p>
|
||||||
<p> Skill items can be equipped to your constructs to be used in the combat phase. </p>
|
<p> Skill items can be equipped to your constructs to be used in the combat phase. </p>
|
||||||
<p> Click your new skill from the stash. <br />
|
<p> Click your new skill from the stash. <br />
|
||||||
Once selected click the flashing <b>SKILL</b> slot to equip the skill. </p>
|
Once selected click the flashing <b>SKILL</b> slot or the construct img to equip the skill. </p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -149,7 +150,7 @@ function tutorialStage(tutorial, clearTutorial, instance) {
|
|||||||
if (tutorial === 4) {
|
if (tutorial === 4) {
|
||||||
return (
|
return (
|
||||||
<div class='info-item'>
|
<div class='info-item'>
|
||||||
<h2>Tutorial</h2>
|
<h2>Specialisations</h2>
|
||||||
<p> You can also buy specialisation items for your constructs. <br />
|
<p> You can also buy specialisation items for your constructs. <br />
|
||||||
Specialisation items increase stats including power, speed and life. </p>
|
Specialisation items increase stats including power, speed and life. </p>
|
||||||
<p> Buy the specialisation item from the store to continue. </p>
|
<p> Buy the specialisation item from the store to continue. </p>
|
||||||
@ -160,11 +161,12 @@ function tutorialStage(tutorial, clearTutorial, instance) {
|
|||||||
if (tutorial === 5) {
|
if (tutorial === 5) {
|
||||||
return (
|
return (
|
||||||
<div class='info-item'>
|
<div class='info-item'>
|
||||||
<h2>Tutorial</h2>
|
<h2>Specialisations</h2>
|
||||||
<p> Equipping specialisation items will increase the stats of your constructs.</p>
|
<p> Equipping specialisation items will increase the stats of your constructs.</p>
|
||||||
<p> These can also be combined with colours for further specialisation. </p>
|
<p> These can also be combined with colours for further specialisation. </p>
|
||||||
<p> Click the specialisation item in the stash.<br />
|
<p> Click the specialisation item in the stash.<br />
|
||||||
Once selected click the flashing <b>SPEC</b> slot to equip the specialisation. </p>
|
Once selected click the flashing <b>SPEC</b> slot to equip the specialisation. </p>
|
||||||
|
<p> <b>PRO TIP:</b> while selecting an item in the shop, click on your construct to buy and equip in one step. </p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -174,11 +176,12 @@ function tutorialStage(tutorial, clearTutorial, instance) {
|
|||||||
const constructThree = instance.players[0].constructs[2].name;
|
const constructThree = instance.players[0].constructs[2].name;
|
||||||
return (
|
return (
|
||||||
<div class='info-item'>
|
<div class='info-item'>
|
||||||
<h2>Tutorial</h2>
|
<h2>Skills</h2>
|
||||||
<p> You have now created a construct with an upgraded skill and base spec. </p>
|
<p> You have now created a construct with an upgraded skill and base spec. </p>
|
||||||
<p> The goal is to create three powerful constructs for combat. </p>
|
<p> The goal is to create three powerful constructs for combat. </p>
|
||||||
<p> Equip your other constructs <b>{constructTwo}</b> and <b>{constructThree}</b> with the Attack skill. <br />
|
<p> Equip your other constructs <b>{constructTwo}</b> and <b>{constructThree}</b> with the Attack skill. <br />
|
||||||
Ensure each construct has a single skill to continue. </p>
|
Ensure each construct has a single skill to continue. </p>
|
||||||
|
<p> <b>PRO TIP:</b> Select a skill or spec on a construct and click another construct to swap it. </p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -186,7 +189,7 @@ function tutorialStage(tutorial, clearTutorial, instance) {
|
|||||||
if (tutorial === 7) {
|
if (tutorial === 7) {
|
||||||
return (
|
return (
|
||||||
<div class='info-item'>
|
<div class='info-item'>
|
||||||
<h2>Tutorial</h2>
|
<h2>Economy</h2>
|
||||||
<p> Each round you start with 30 bits and a store full of different skills, specs and colours. </p>
|
<p> Each round you start with 30 bits and a store full of different skills, specs and colours. </p>
|
||||||
<p> Bits are your currency for buying items. <br />
|
<p> Bits are your currency for buying items. <br />
|
||||||
You can refill the store by pressing the refill button for 2b. <br />
|
You can refill the store by pressing the refill button for 2b. <br />
|
||||||
@ -203,22 +206,22 @@ function tutorialStage(tutorial, clearTutorial, instance) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class='info-item'>
|
<div class='info-item'>
|
||||||
<h2>Tutorial</h2>
|
<h2>GLHF</h2>
|
||||||
<p>That completes the VBOX Tutorial.</p>
|
<p>That completes the VBOX Tutorial.</p>
|
||||||
<p>Press <b>READY</b> to progress to the <b>GAME PHASE</b> <br />
|
<p>Press the green <b>READY</b> button in the bottom right to progress to the <b>GAME PHASE</b> <br />
|
||||||
You can continue creating new items to upgrade your constructs further. </p>
|
or continue creating new items to upgrade your constructs further. </p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const classes = tutorial === 8 ? 'focus' : '';
|
const exitTutorial = tutorial === 8 ?
|
||||||
const text = tutorial === 8 ? 'Continue' : 'Skip Tutorial'
|
<button
|
||||||
const exitTutorial = <button
|
class='focus'
|
||||||
class={classes}
|
onClick={e => e.stopPropagation()}
|
||||||
onClick={e => e.stopPropagation()}
|
onMouseDown={exit}> Continue </button>
|
||||||
onMouseDown={exit}> {text} </button>;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class='tutorial'>
|
<div class='tutorial'>
|
||||||
|
|||||||
@ -241,30 +241,15 @@ function convertItem(v) {
|
|||||||
|
|
||||||
function effectInfo(i) {
|
function effectInfo(i) {
|
||||||
// FIX ME
|
// FIX ME
|
||||||
return 'effect info to be fixed';
|
const hybridBlast = 25;
|
||||||
|
|
||||||
/*const hybridBlast = 25;
|
|
||||||
const hasteStrike = 30;
|
const hasteStrike = 30;
|
||||||
function multiplier(s) { // Update later to use server info in future
|
function multiplier(s) { // Update later to use server info in future
|
||||||
if (s === 'CounterAttack') return 120;
|
if (s === 'CounterAttack') return 115;
|
||||||
if (s === 'CounterAttack+') return 160;
|
if (s === 'CounterAttack+') return 130;
|
||||||
if (s === 'CounterAttack++') return 230;
|
if (s === 'CounterAttack++') return 160;
|
||||||
|
if (s === 'Electrocute') return 80;
|
||||||
if (s === 'DecayTick') return 33;
|
if (s === 'Electrocute+') return 90;
|
||||||
if (s === 'DecayTick+') return 45;
|
if (s === 'Electrocute++') return 100;
|
||||||
if (s === 'DecayTick++') return 70;
|
|
||||||
|
|
||||||
if (s === 'SiphonTick') return 25;
|
|
||||||
if (s === 'SiphonTick+') return 30;
|
|
||||||
if (s === 'SiphonTick++') return 40;
|
|
||||||
|
|
||||||
if (s === 'TriageTick') return 75;
|
|
||||||
if (s === 'TriageTick+') return 110;
|
|
||||||
if (s === 'TriageTick++') return 140;
|
|
||||||
|
|
||||||
if (s === 'Electrocute' || s === 'ElectrocuteTick') return 80;
|
|
||||||
if (s === 'Electrocute+' || s === 'ElectrocuteTick+') return 100;
|
|
||||||
if (s === 'Electrocute++' || s === 'ElectrocuteTick++') return 130;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,16 +274,16 @@ function effectInfo(i) {
|
|||||||
case 'Vulnerable': return `Construct will take ${i.meta[1] - 100}% increased red damage`;
|
case 'Vulnerable': return `Construct will take ${i.meta[1] - 100}% increased red damage`;
|
||||||
case 'Silence': return 'Disable construct from casting any blue skills';
|
case 'Silence': return 'Disable construct from casting any blue skills';
|
||||||
case 'Wither': return `Construct will take ${100 - i.meta[1]}% reduced healing`; //
|
case 'Wither': return `Construct will take ${100 - i.meta[1]}% reduced healing`; //
|
||||||
case 'Decay': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower as blue damage each turn.`; //
|
case 'Decay': return `Construct will take ${i.meta[1].amount} blue damage each turn.`; //
|
||||||
case 'Electric': return `Attacks against this construct will apply Electrocute dealing ${multiplier(i.meta[1])}% of construct BluePower as blue damage each turn.`;
|
case 'Electric': return `Attacks against this construct will apply Electrocute dealing ${multiplier(i.meta[1])}% of construct BluePower as blue damage each turn.`;
|
||||||
case 'Electrocute': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower as blue damage each turn.`;
|
case 'Electrocute': return `Construct will take ${i.meta[1].amount} blue damage each turn.`;
|
||||||
case 'Absorb': return 'If construct takes damage, Absorption will be applied increasing RedPower and BluePower based on damage taken.';
|
case 'Absorb': return 'If construct takes damage, Absorption will be applied increasing RedPower and BluePower based on damage taken.';
|
||||||
case 'Absorption': return `Increasing construct RedPower and BluePower by ${i.meta[1]}`;
|
case 'Absorption': return `Increasing construct RedPower and BluePower by ${i.meta[1]}`;
|
||||||
case 'Triage': return `Construct will be healed for ${multiplier(i.tick.skill)}% of caster's GreenPower each turn.`;
|
case 'Triage': return `Construct will be healed for ${i.meta[1].amount} green life each turn.`;
|
||||||
case 'Siphon': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower + GreenPower as blue damage each turn, healing the caster.`;
|
case 'Siphon': return `Construct will take ${i.meta[1].amount} blue damage each turn, healing the caster.`;
|
||||||
|
|
||||||
default: return 'Missing Effect Text';
|
default: return 'Missing Effect Text';
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mnml_core"
|
name = "mnml_core"
|
||||||
version = "1.10.0"
|
version = "1.11.0"
|
||||||
authors = ["ntr <ntr@smokestack.io>", "mashy <mashy@mnml.gg>"]
|
authors = ["ntr <ntr@smokestack.io>", "mashy <mashy@mnml.gg>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
# FIXME
|
# FIXME
|
||||||
check silence skill multiplier
|
|
||||||
game ready not auto starting resolve phase
|
game ready not auto starting resolve phase
|
||||||
|
|
||||||
cooldowns set after cast
|
remove big header and move to rhs of news pane
|
||||||
cooldowns reduced after 1 complete cast
|
add big logo w/ noise when you mouseover stuff etc
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
use std::iter;
|
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
|
||||||
@ -435,12 +433,14 @@ impl Construct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn skill_set_cd(&mut self, skill: Skill) -> &mut Construct {
|
pub fn skill_set_cd(&mut self, skill: Skill) -> &mut Construct {
|
||||||
|
// println!("{:?} {:?} skill cooldown set", self.name, skill);
|
||||||
|
|
||||||
// tests force resolve some skills
|
// tests force resolve some skills
|
||||||
// which cause the game to attempt to put them on cd
|
// which cause the game to attempt to put them on cd
|
||||||
// even though the construct doesn't know the skill
|
// even though the construct doesn't know the skill
|
||||||
if let Some(i) = self.skills.iter().position(|s| s.skill == skill) {
|
if let Some(i) = self.skills.iter().position(|s| s.skill == skill) {
|
||||||
self.skills.remove(i);
|
self.skills.remove(i);
|
||||||
self.skills.push(ConstructSkill::new(skill));
|
self.skills.insert(i, ConstructSkill::new(skill));
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
@ -493,11 +493,13 @@ impl Construct {
|
|||||||
self.effects = self.effects.clone().into_iter().filter_map(|mut effect| {
|
self.effects = self.effects.clone().into_iter().filter_map(|mut effect| {
|
||||||
effect.duration = effect.duration.saturating_sub(1);
|
effect.duration = effect.duration.saturating_sub(1);
|
||||||
|
|
||||||
|
// println!("{:?}", effect);
|
||||||
|
|
||||||
if effect.duration == 0 {
|
if effect.duration == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// info!("reduced effect {:?}", effect);
|
info!("reduced effect {:?}", effect);
|
||||||
return Some(effect);
|
return Some(effect);
|
||||||
}).collect::<Vec<ConstructEffect>>();
|
}).collect::<Vec<ConstructEffect>>();
|
||||||
|
|
||||||
@ -946,7 +948,10 @@ impl Construct {
|
|||||||
if self.is_ko() { return vec![Event::TargetKo { construct: self.id }] }
|
if self.is_ko() { return vec![Event::TargetKo { construct: self.id }] }
|
||||||
|
|
||||||
if let Some(p) = self.effects.iter().position(|ce| ce.effect == effect) {
|
if let Some(p) = self.effects.iter().position(|ce| ce.effect == effect) {
|
||||||
self.effects.remove(p);
|
let ce = self.effects.remove(p);
|
||||||
|
|
||||||
|
if ce.effect.hidden() { return vec![] }
|
||||||
|
|
||||||
return vec![Event::Removal {
|
return vec![Event::Removal {
|
||||||
construct: self.id,
|
construct: self.id,
|
||||||
effect: effect,
|
effect: effect,
|
||||||
@ -982,11 +987,13 @@ impl Construct {
|
|||||||
if self.is_ko() { return vec![Event::TargetKo { construct: self.id }] }
|
if self.is_ko() { return vec![Event::TargetKo { construct: self.id }] }
|
||||||
|
|
||||||
while let Some(ce) = self.effects.pop() {
|
while let Some(ce) = self.effects.pop() {
|
||||||
removals.push(Event::Removal {
|
if !ce.effect.hidden() {
|
||||||
construct: self.id,
|
removals.push(Event::Removal {
|
||||||
effect: ce.effect,
|
construct: self.id,
|
||||||
display: EventConstruct::new(self),
|
effect: ce.effect,
|
||||||
});
|
display: EventConstruct::new(self),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return removals;
|
return removals;
|
||||||
@ -1012,7 +1019,7 @@ impl Construct {
|
|||||||
|
|
||||||
match meta {
|
match meta {
|
||||||
Some(EffectMeta::CastOnHit(skill)) =>
|
Some(EffectMeta::CastOnHit(skill)) =>
|
||||||
casts.push(Cast::new(self.id, self.account, cast.target, *skill)),
|
casts.push(Cast::new(self.id, self.account, cast.source, *skill)),
|
||||||
_ => panic!("no electrify skill {:?}", meta),
|
_ => panic!("no electrify skill {:?}", meta),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1119,9 +1126,9 @@ mod tests {
|
|||||||
|
|
||||||
construct.apply_modifiers(&player_colours);
|
construct.apply_modifiers(&player_colours);
|
||||||
|
|
||||||
assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(35));
|
assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(15));
|
||||||
assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(50));
|
assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(24));
|
||||||
assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(70));
|
assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(37));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1170,7 +1177,7 @@ mod tests {
|
|||||||
|
|
||||||
let colours = Colours::from_construct(&construct);
|
let colours = Colours::from_construct(&construct);
|
||||||
assert!(colours.red == 4);
|
assert!(colours.red == 4);
|
||||||
assert!(colours.blue == 20);
|
assert!(colours.blue == 10);
|
||||||
assert!(colours.green == 2);
|
assert!(colours.green == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1191,9 +1198,9 @@ mod tests {
|
|||||||
|
|
||||||
construct.apply_modifiers(&player_colours);
|
construct.apply_modifiers(&player_colours);
|
||||||
|
|
||||||
assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(35));
|
assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(15));
|
||||||
assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(25));
|
assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(10));
|
||||||
assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(25));
|
assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(10));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,38 +75,19 @@ impl Effect {
|
|||||||
match self {
|
match self {
|
||||||
Effect::Banish => true,
|
Effect::Banish => true,
|
||||||
|
|
||||||
// delete sustain immunitiy???
|
// these provide immunity for the ticks
|
||||||
/*Effect::Sustain => [
|
// the base skills will still resolve
|
||||||
Skill::Stun,
|
// but they have early return checks
|
||||||
Skill::Silence,
|
// to ensure the effect is reapplied but damage is not
|
||||||
Skill::SilencePlus,
|
|
||||||
Skill::SilencePlusPlus,
|
|
||||||
Skill::Ruin,
|
|
||||||
Skill::RuinPlus,
|
|
||||||
Skill::RuinPlusPlus,
|
|
||||||
Skill::Restrict,
|
|
||||||
Skill::RestrictPlus,
|
|
||||||
Skill::RestrictPlusPlus
|
|
||||||
].contains(&skill),*/
|
|
||||||
|
|
||||||
Effect::Siphoned => [
|
Effect::Siphoned => [
|
||||||
Skill::Siphon,
|
|
||||||
Skill::SiphonPlus,
|
|
||||||
Skill::SiphonPlusPlus,
|
|
||||||
Skill::SiphonTick,
|
Skill::SiphonTick,
|
||||||
].contains(&skill),
|
].contains(&skill),
|
||||||
|
|
||||||
Effect::Decayed => [
|
Effect::Decayed => [
|
||||||
Skill::Decay,
|
|
||||||
Skill::DecayPlus,
|
|
||||||
Skill::DecayPlusPlus,
|
|
||||||
Skill::DecayTick,
|
Skill::DecayTick,
|
||||||
].contains(&skill),
|
].contains(&skill),
|
||||||
|
|
||||||
Effect::Triaged => [
|
Effect::Triaged => [
|
||||||
Skill::Triage,
|
|
||||||
Skill::TriagePlus,
|
|
||||||
Skill::TriagePlusPlus,
|
|
||||||
Skill::TriageTick,
|
Skill::TriageTick,
|
||||||
].contains(&skill),
|
].contains(&skill),
|
||||||
|
|
||||||
|
|||||||
384
core/src/game.rs
384
core/src/game.rs
@ -135,20 +135,6 @@ impl Game {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_construct(&mut self, construct: &mut Construct) -> &mut Game {
|
|
||||||
match self.players.iter_mut().find(|t| t.constructs.iter().any(|c| c.id == construct.id)) {
|
|
||||||
Some(player) => {
|
|
||||||
let index = player.constructs.iter().position(|t| t.id == construct.id).unwrap();
|
|
||||||
player.constructs.remove(index);
|
|
||||||
player.constructs.push(construct.clone());
|
|
||||||
player.constructs.sort_unstable_by_key(|c| c.id);
|
|
||||||
},
|
|
||||||
None => panic!("construct not in game"),
|
|
||||||
};
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn can_start(&self) -> bool {
|
pub fn can_start(&self) -> bool {
|
||||||
return self.players.len() == self.player_num
|
return self.players.len() == self.player_num
|
||||||
&& self.players.iter().all(|t| t.constructs.len() == self.player_constructs)
|
&& self.players.iter().all(|t| t.constructs.len() == self.player_constructs)
|
||||||
@ -350,7 +336,11 @@ impl Game {
|
|||||||
|
|
||||||
|
|
||||||
pub fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
|
pub fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
|
||||||
self.player_by_id(player_id)?;
|
let player = self.player_by_id(player_id)?;
|
||||||
|
if player.ready {
|
||||||
|
return Err(err_msg("cannot clear skills while ready"));
|
||||||
|
}
|
||||||
|
|
||||||
if self.phase != Phase::Skill {
|
if self.phase != Phase::Skill {
|
||||||
return Err(err_msg("game not in skill phase"));
|
return Err(err_msg("game not in skill phase"));
|
||||||
}
|
}
|
||||||
@ -398,6 +388,11 @@ impl Game {
|
|||||||
let mut sorted = self.stack.clone();
|
let mut sorted = self.stack.clone();
|
||||||
sorted.iter_mut()
|
sorted.iter_mut()
|
||||||
.for_each(|s| {
|
.for_each(|s| {
|
||||||
|
|
||||||
|
// we do not modify the speed of ticks
|
||||||
|
// as they are considered to be pinned to the speed
|
||||||
|
// that they were initially cast
|
||||||
|
|
||||||
if !s.skill.is_tick() {
|
if !s.skill.is_tick() {
|
||||||
let caster = self.construct_by_id(s.source).unwrap();
|
let caster = self.construct_by_id(s.source).unwrap();
|
||||||
let speed = caster.skill_speed(s.skill);
|
let speed = caster.skill_speed(s.skill);
|
||||||
@ -462,7 +457,15 @@ impl Game {
|
|||||||
self.skill_phase_start(r_animation_ms)
|
self.skill_phase_start(r_animation_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modify_cast(&self, cast: Cast) -> Vec<Cast> {
|
fn modify_cast(&self, mut cast: Cast) -> Vec<Cast> {
|
||||||
|
|
||||||
|
// reassign the speeds based on the caster
|
||||||
|
// for test purposes
|
||||||
|
if !cast.skill.is_tick() {
|
||||||
|
let speed = self.construct(cast.source).skill_speed(cast.skill);
|
||||||
|
cast.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
let target_player = self.players.iter()
|
let target_player = self.players.iter()
|
||||||
.find(|t| t.constructs.iter().any(|c| c.id == cast.target))
|
.find(|t| t.constructs.iter().any(|c| c.id == cast.target))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -508,17 +511,19 @@ impl Game {
|
|||||||
self.resolve(Cast { skill, ..cast });
|
self.resolve(Cast { skill, ..cast });
|
||||||
}
|
}
|
||||||
|
|
||||||
// for aoe events send the source / target animations before each set of casts
|
let casts = self.modify_cast(cast);
|
||||||
if cast.skill.aoe() {
|
|
||||||
if cast.skill.cast_animation() {
|
let castable = casts
|
||||||
let event = self.cast(cast);
|
.iter()
|
||||||
self.add_resolution(&cast, &event);
|
.any(|c| !self.construct(c.target).is_ko() && !self.construct(c.target).immune(c.skill).is_some());
|
||||||
|
|
||||||
|
if castable {
|
||||||
|
self.action(cast, Action::Cast);
|
||||||
|
if cast.skill.aoe() {
|
||||||
|
self.action(cast, Action::HitAoe);
|
||||||
}
|
}
|
||||||
let event = self.hit_aoe(cast);
|
|
||||||
self.add_resolution(&cast, &event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let casts = self.modify_cast(cast);
|
|
||||||
for cast in casts {
|
for cast in casts {
|
||||||
self.execute(cast);
|
self.execute(cast);
|
||||||
}
|
}
|
||||||
@ -550,6 +555,10 @@ impl Game {
|
|||||||
return self.resolve(Cast { target: cast.source, ..cast });
|
return self.resolve(Cast { target: cast.source, ..cast });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !cast.skill.aoe() {
|
||||||
|
self.action(cast, Action::Hit);
|
||||||
|
}
|
||||||
|
|
||||||
cast.resolve(self);
|
cast.resolve(self);
|
||||||
|
|
||||||
self
|
self
|
||||||
@ -559,6 +568,7 @@ impl Game {
|
|||||||
let new_events = match action {
|
let new_events = match action {
|
||||||
Action::Cast => vec![self.cast(cast)],
|
Action::Cast => vec![self.cast(cast)],
|
||||||
Action::Hit => vec![self.hit(cast)],
|
Action::Hit => vec![self.hit(cast)],
|
||||||
|
Action::HitAoe => vec![self.hit_aoe(cast)],
|
||||||
|
|
||||||
Action::Damage { construct, amount, colour } => self.damage(construct, amount, colour),
|
Action::Damage { construct, amount, colour } => self.damage(construct, amount, colour),
|
||||||
Action::Heal { construct, amount, colour } => self.heal(construct, amount, colour),
|
Action::Heal { construct, amount, colour } => self.heal(construct, amount, colour),
|
||||||
@ -642,11 +652,14 @@ impl Game {
|
|||||||
|
|
||||||
Value::TickDamage { construct, effect } =>
|
Value::TickDamage { construct, effect } =>
|
||||||
self.construct(construct).stat(Stat::TickDamage(effect)),
|
self.construct(construct).stat(Stat::TickDamage(effect)),
|
||||||
|
|
||||||
// Skills { construct: Uuid, colour: Colour },
|
// Skills { construct: Uuid, colour: Colour },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn affected(&self, construct: Uuid, effect: Effect) -> bool {
|
||||||
|
self.construct(construct).affected(effect)
|
||||||
|
}
|
||||||
|
|
||||||
fn cast(&mut self, cast: Cast) -> Event {
|
fn cast(&mut self, cast: Cast) -> Event {
|
||||||
Event::Cast { construct: cast.source, player: cast.player, target: cast.target, skill: cast.skill, direction: self.direction(cast) }
|
Event::Cast { construct: cast.source, player: cast.player, target: cast.target, skill: cast.skill, direction: self.direction(cast) }
|
||||||
}
|
}
|
||||||
@ -843,11 +856,13 @@ pub enum Value {
|
|||||||
Removals { construct: Uuid },
|
Removals { construct: Uuid },
|
||||||
DamageReceived { construct: Uuid, colour: Colour },
|
DamageReceived { construct: Uuid, colour: Colour },
|
||||||
TickDamage { construct: Uuid, effect: Effect },
|
TickDamage { construct: Uuid, effect: Effect },
|
||||||
|
// Affected { construct: Uuid, effect: Effect }, // not an int :(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone,PartialEq)]
|
#[derive(Debug,Clone,PartialEq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Hit,
|
Hit,
|
||||||
|
HitAoe,
|
||||||
Cast,
|
Cast,
|
||||||
Heal { construct: Uuid, amount: usize, colour: Colour },
|
Heal { construct: Uuid, amount: usize, colour: Colour },
|
||||||
Damage { construct: Uuid, amount: usize, colour: Colour },
|
Damage { construct: Uuid, amount: usize, colour: Colour },
|
||||||
@ -1033,6 +1048,7 @@ mod tests {
|
|||||||
.learn(Skill::Siphon)
|
.learn(Skill::Siphon)
|
||||||
.learn(Skill::Amplify)
|
.learn(Skill::Amplify)
|
||||||
.learn(Skill::Stun)
|
.learn(Skill::Stun)
|
||||||
|
.learn(Skill::Ruin)
|
||||||
.learn(Skill::Block)
|
.learn(Skill::Block)
|
||||||
.learn(Skill::Sleep)
|
.learn(Skill::Sleep)
|
||||||
.learn(Skill::Decay);
|
.learn(Skill::Decay);
|
||||||
@ -1251,6 +1267,32 @@ mod tests {
|
|||||||
assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none());
|
assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ruin_cooldown_test() {
|
||||||
|
let mut game = create_test_game();
|
||||||
|
|
||||||
|
let x_player = game.players[0].clone();
|
||||||
|
let y_player = game.players[1].clone();
|
||||||
|
|
||||||
|
let x_construct = x_player.constructs[0].clone();
|
||||||
|
let y_construct = y_player.constructs[0].clone();
|
||||||
|
|
||||||
|
while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Ruin).is_some() {
|
||||||
|
game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Ruin).unwrap();
|
||||||
|
|
||||||
|
game.player_ready(x_player.id).unwrap();
|
||||||
|
game.player_ready(y_player.id).unwrap();
|
||||||
|
|
||||||
|
game = game.resolve_phase_start();
|
||||||
|
|
||||||
|
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Ruin).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// #[cfg(test)]
|
// #[cfg(test)]
|
||||||
// mod tests {
|
// mod tests {
|
||||||
// use skill::*;
|
// use skill::*;
|
||||||
@ -1595,55 +1637,6 @@ mod tests {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sleep_cooldown_test() {
|
|
||||||
let mut game = create_test_game();
|
|
||||||
|
|
||||||
let x_player = game.players[0].clone();
|
|
||||||
let y_player = game.players[1].clone();
|
|
||||||
|
|
||||||
let x_construct = x_player.constructs[0].clone();
|
|
||||||
let y_construct = y_player.constructs[0].clone();
|
|
||||||
|
|
||||||
|
|
||||||
for _n in 1..10 {
|
|
||||||
// should auto progress back to skill phase
|
|
||||||
assert!(game.phase == Phase::Skill);
|
|
||||||
|
|
||||||
// Sleep 2T CD
|
|
||||||
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none());
|
|
||||||
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some());
|
|
||||||
|
|
||||||
game.player_ready(x_player.id).unwrap();
|
|
||||||
game.player_ready(y_player.id).unwrap();
|
|
||||||
game = game.resolve_phase_start();
|
|
||||||
|
|
||||||
// Sleep 1T CD
|
|
||||||
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none());
|
|
||||||
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some());
|
|
||||||
|
|
||||||
game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap();
|
|
||||||
// game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap();
|
|
||||||
game.player_ready(x_player.id).unwrap();
|
|
||||||
game.player_ready(y_player.id).unwrap();
|
|
||||||
game = game.resolve_phase_start();
|
|
||||||
|
|
||||||
// Sleep 0T CD (we use it here)
|
|
||||||
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none());
|
|
||||||
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_none());
|
|
||||||
|
|
||||||
game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Sleep).unwrap();
|
|
||||||
game.player_ready(x_player.id).unwrap();
|
|
||||||
game.player_ready(y_player.id).unwrap();
|
|
||||||
game = game.resolve_phase_start();
|
|
||||||
|
|
||||||
// Sleep back to 2T CD
|
|
||||||
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none());
|
|
||||||
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn counter_test() {
|
// fn counter_test() {
|
||||||
// let mut game = create_test_game();
|
// let mut game = create_test_game();
|
||||||
@ -1712,56 +1705,6 @@ mod tests {
|
|||||||
// assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Electrocute));
|
// assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Electrocute));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // #[test]
|
|
||||||
// // fn link_test() {
|
|
||||||
// // let mut game = create_test_game();
|
|
||||||
|
|
||||||
// // let x_player = game.players[0].clone();
|
|
||||||
// // let y_player = game.players[1].clone();
|
|
||||||
|
|
||||||
// // let x_construct = x_player.constructs[0].clone();
|
|
||||||
// // let y_construct = y_player.constructs[0].clone();
|
|
||||||
|
|
||||||
// // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Link);
|
|
||||||
|
|
||||||
// // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Link).is_some() {
|
|
||||||
// // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns();
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // // apply buff
|
|
||||||
// // 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(y_player.id).unwrap();
|
|
||||||
// // game = game.resolve_phase_start();
|
|
||||||
// // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Link));
|
|
||||||
|
|
||||||
// // let Resolution { source: _, target: _, Resolution, stages: _ } = game.Resolutions.last.unwrap().pop().unwrap();
|
|
||||||
// // match Resolution {
|
|
||||||
// // Resolution::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Link),
|
|
||||||
// // _ => panic!("not siphon"),
|
|
||||||
// // };
|
|
||||||
|
|
||||||
// // let Resolution { source: _, target: _, Resolution, stages: _ } = game.Resolutions.last.unwrap().pop().unwrap();
|
|
||||||
// // match Resolution {
|
|
||||||
// // Resolution::Recharge { red: _, blue: _, skill: _ } => (),
|
|
||||||
// // _ => panic!("link result was not recharge"),
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // // attack and receive link hit
|
|
||||||
// // 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(y_player.id).unwrap();
|
|
||||||
// // game = game.resolve_phase_start();
|
|
||||||
|
|
||||||
// // let Resolution { source: _, target, Resolution, stages: _ } = game.Resolutions.last.unwrap().pop().unwrap();
|
|
||||||
// // assert_eq!(target.id, y_construct.id);
|
|
||||||
// // match Resolution {
|
|
||||||
// // Resolution::Damage { amount, skill: _, mitigation: _, colour: _} =>
|
|
||||||
// // assert_eq!(amount, x_construct.red_power().pct(Skill::Attack.multiplier()) >> 1),
|
|
||||||
// // _ => panic!("not damage link"),
|
|
||||||
// // };
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // #[test]
|
// // #[test]
|
||||||
// // fn absorb_test() {
|
// // fn absorb_test() {
|
||||||
// // let mut game = create_test_game();
|
// // let mut game = create_test_game();
|
||||||
@ -2015,7 +1958,7 @@ mod tests {
|
|||||||
let mut game = create_2v2_test_game();
|
let mut game = create_2v2_test_game();
|
||||||
game.players[0].set_ready(true);
|
game.players[0].set_ready(true);
|
||||||
game.phase_end = Some(Utc::now().checked_sub_signed(Duration::seconds(500)).unwrap());
|
game.phase_end = Some(Utc::now().checked_sub_signed(Duration::seconds(500)).unwrap());
|
||||||
game = game.upkeep();
|
game.upkeep();
|
||||||
// assert!(game.players[1].warnings == 1);
|
// assert!(game.players[1].warnings == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2026,7 +1969,7 @@ mod tests {
|
|||||||
let source = game.players[0].constructs[0].id;
|
let source = game.players[0].constructs[0].id;
|
||||||
let target = game.players[1].constructs[0].id;
|
let target = game.players[1].constructs[0].id;
|
||||||
game.add_skill(player_id, source, target, Skill::Attack).unwrap();
|
game.add_skill(player_id, source, target, Skill::Attack).unwrap();
|
||||||
game = game.resolve_phase_start();
|
game.resolve_phase_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2122,6 +2065,43 @@ mod tests {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn link_test() {
|
||||||
|
let mut game = create_2v2_test_game();
|
||||||
|
let player_id = game.players[0].id;
|
||||||
|
let source = game.players[0].constructs[0].id;
|
||||||
|
let target = game.players[1].constructs[0].id;
|
||||||
|
game.players[1].constructs[0].blue_life.force(0);
|
||||||
|
|
||||||
|
game.new_resolve(Cast::new(source, player_id, target, Skill::Link));
|
||||||
|
|
||||||
|
let last = game.resolutions.len() - 1;
|
||||||
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
|
assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
Event::Damage { construct, colour, amount, mitigation: _, display: _ } =>
|
||||||
|
construct == target && amount == 320.pct(50) && colour == Colour::Blue,
|
||||||
|
_ => false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
game = game.resolve_phase_start();
|
||||||
|
game.new_resolve(Cast::new(source, player_id, target, Skill::Triage));
|
||||||
|
game.new_resolve(Cast::new(source, player_id, target, Skill::Link));
|
||||||
|
|
||||||
|
let last = game.resolutions.len() - 1;
|
||||||
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
|
assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
Event::Damage { construct, colour, amount, mitigation: _, display: _ } =>
|
||||||
|
construct == target && amount == 320.pct(75) && colour == Colour::Blue,
|
||||||
|
_ => false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn siphon_test() {
|
fn siphon_test() {
|
||||||
let mut game = create_2v2_test_game();
|
let mut game = create_2v2_test_game();
|
||||||
@ -2158,6 +2138,9 @@ mod tests {
|
|||||||
|
|
||||||
// que ota?
|
// que ota?
|
||||||
game.resolve(Cast::new(source, player_id, target, Skill::Siphon));
|
game.resolve(Cast::new(source, player_id, target, Skill::Siphon));
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Siphon));
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Siphon));
|
||||||
|
|
||||||
let last = game.resolutions.len() - 1;
|
let last = game.resolutions.len() - 1;
|
||||||
let resolutions = &game.resolutions[last];
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
@ -2166,7 +2149,75 @@ mod tests {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}).count();
|
}).count();
|
||||||
|
|
||||||
|
let effect_events = resolutions.iter().filter(|r| match r.event {
|
||||||
|
Event::Effect { construct, effect, duration: _, display: _ } =>
|
||||||
|
construct == target && effect == Effect::Siphon,
|
||||||
|
_ => false,
|
||||||
|
}).count();
|
||||||
|
|
||||||
|
// Deal siphon dmg once
|
||||||
assert_eq!(damage_events, 1);
|
assert_eq!(damage_events, 1);
|
||||||
|
// 3 new applications of siphon
|
||||||
|
assert_eq!(effect_events, 3);
|
||||||
|
// Siphon + Siphoned
|
||||||
|
assert!(game.players[1].constructs[0].effects.len() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hybrid_test() {
|
||||||
|
let mut game = create_2v2_test_game();
|
||||||
|
let player_id = game.players[0].id;
|
||||||
|
let source = game.players[0].constructs[0].id;
|
||||||
|
let target = game.players[1].constructs[0].id;
|
||||||
|
|
||||||
|
game.players[1].constructs[0].blue_life.force(0);
|
||||||
|
|
||||||
|
|
||||||
|
game.resolve(Cast::new(source, player_id, source, Skill::Hybrid));
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Siphon));
|
||||||
|
|
||||||
|
let last = game.resolutions.len() - 1;
|
||||||
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
|
assert!(resolutions.iter().any(|r| match r.skill {
|
||||||
|
Skill::HybridBlast => true,
|
||||||
|
_ => false
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert!(resolutions.iter().filter(|r| match r.event {
|
||||||
|
Event::Damage { construct: _, colour: _, amount: _, mitigation: _, display: _ } => true,
|
||||||
|
_ => false,
|
||||||
|
}).count() == 2);
|
||||||
|
|
||||||
|
|
||||||
|
let _siphon_dmg = resolutions.iter().find_map(|r| match r.skill {
|
||||||
|
Skill::Siphon => {
|
||||||
|
match r.event {
|
||||||
|
Event::Damage { construct: _, colour: _, amount, mitigation: _, display: _ } => Some(amount),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}).expect("no siphon dmg");
|
||||||
|
|
||||||
|
// let hybrid_dmg = resolutions.iter().find_map(|r| match r.skill {
|
||||||
|
// Skill::HybridBlast => {
|
||||||
|
// match r.event {
|
||||||
|
// Event::Damage { construct: _, colour: _, amount, mitigation: _, display: _ } => Some(amount),
|
||||||
|
// _ => None,
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// _ => None
|
||||||
|
// }).expect("no hybrid dmg");
|
||||||
|
|
||||||
|
// assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
// Event::Healing { construct, colour, amount, overhealing, display: _ } => {
|
||||||
|
// construct == source && (amount + overhealing) == siphon_dmg && colour == Colour::Green
|
||||||
|
// // this works
|
||||||
|
// // construct == source && (amount + overhealing) == (siphon_dmg + hybrid_dmg) && colour == Colour::Green
|
||||||
|
// },
|
||||||
|
// _ => false,
|
||||||
|
// }));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2189,6 +2240,85 @@ mod tests {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn electrify_test() {
|
||||||
|
let mut game = create_2v2_test_game();
|
||||||
|
let player_id = game.players[0].id;
|
||||||
|
let source = game.players[0].constructs[0].id;
|
||||||
|
let target = game.players[1].constructs[0].id;
|
||||||
|
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Electrify));
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Blast));
|
||||||
|
|
||||||
|
let last = game.resolutions.len() - 1;
|
||||||
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
|
assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
Event::Damage { construct, colour, amount, mitigation: _, display: _ } =>
|
||||||
|
construct == source && amount > 0 && colour == Colour::Blue,
|
||||||
|
_ => false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn triage_test() {
|
||||||
|
let mut game = create_2v2_test_game();
|
||||||
|
let player_id = game.players[0].id;
|
||||||
|
let source = game.players[0].constructs[0].id;
|
||||||
|
let target = game.players[1].constructs[0].id;
|
||||||
|
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Strike));
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Triage));
|
||||||
|
|
||||||
|
let last = game.resolutions.len() - 1;
|
||||||
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
|
assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
Event::Healing { construct, colour, amount, overhealing: _, display: _ } =>
|
||||||
|
construct == target && amount > 0 && colour == Colour::Green,
|
||||||
|
_ => false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// it's hidden
|
||||||
|
// assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
// Event::Effect { construct, effect, duration: _, display: _ } =>
|
||||||
|
// construct == target && effect == Effect::Triaged,
|
||||||
|
// _ => false,
|
||||||
|
// }));
|
||||||
|
|
||||||
|
game.progress_durations(); // pretend it's a new turn
|
||||||
|
game = game.resolve_phase_start();
|
||||||
|
|
||||||
|
let last = game.resolutions.len() - 1;
|
||||||
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
|
assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
Event::Healing { construct, colour, amount: _, overhealing, display: _ } =>
|
||||||
|
construct == target && overhealing > 0 && colour == Colour::Green,
|
||||||
|
_ => false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn counter_test() {
|
||||||
|
let mut game = create_2v2_test_game();
|
||||||
|
let player_id = game.players[0].id;
|
||||||
|
let source = game.players[0].constructs[0].id;
|
||||||
|
let target = game.players[1].constructs[0].id;
|
||||||
|
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Counter));
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Strike));
|
||||||
|
|
||||||
|
let last = game.resolutions.len() - 1;
|
||||||
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
|
assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
Event::Damage { construct, colour, amount, mitigation: _, display: _ } =>
|
||||||
|
construct == source && amount > 0 && colour == Colour::Red,
|
||||||
|
_ => false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn absorb_test() {
|
fn absorb_test() {
|
||||||
let mut game = create_2v2_test_game();
|
let mut game = create_2v2_test_game();
|
||||||
@ -2376,6 +2506,8 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(siphon_tick_speed > 0);
|
||||||
|
assert!(siphon_speed > 0);
|
||||||
assert_eq!(siphon_tick_dmg, siphon_dmg);
|
assert_eq!(siphon_tick_dmg, siphon_dmg);
|
||||||
assert_eq!(siphon_tick_speed, siphon_speed);
|
assert_eq!(siphon_tick_speed, siphon_speed);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ use chrono::prelude::*;
|
|||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
|
|
||||||
use player::{Player, Score};
|
use player::{Player, Score};
|
||||||
use mob::{bot_player, instance_mobs};
|
|
||||||
use game::{Game};
|
use game::{Game};
|
||||||
use item::{Item};
|
use item::{Item};
|
||||||
use vbox;
|
use vbox;
|
||||||
@ -144,8 +143,8 @@ impl Instance {
|
|||||||
.collect::<Vec<Uuid>>()
|
.collect::<Vec<Uuid>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// time out lobbies that have been open too long
|
||||||
pub fn upkeep(mut self) -> (Instance, Option<Game>) {
|
pub fn upkeep(mut self) -> (Instance, Option<Game>) {
|
||||||
// time out lobbies that have been open too long
|
|
||||||
if self.phase == InstancePhase::Lobby && self.phase_timed_out() {
|
if self.phase == InstancePhase::Lobby && self.phase_timed_out() {
|
||||||
self.finish();
|
self.finish();
|
||||||
return (self, None);
|
return (self, None);
|
||||||
@ -504,6 +503,7 @@ impl Instance {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use mob::{bot_player, instance_mobs};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn instance_pve_test() {
|
fn instance_pve_test() {
|
||||||
|
|||||||
@ -716,7 +716,7 @@ impl Item {
|
|||||||
Item::HealPlusPlus |
|
Item::HealPlusPlus |
|
||||||
Item::Triage |
|
Item::Triage |
|
||||||
Item::TriagePlus |
|
Item::TriagePlus |
|
||||||
Item::TriagePlusPlus |
|
Item::TriagePlusPlus |
|
||||||
Item::Break |
|
Item::Break |
|
||||||
Item::BreakPlus |
|
Item::BreakPlus |
|
||||||
Item::BreakPlusPlus |
|
Item::BreakPlusPlus |
|
||||||
@ -735,7 +735,7 @@ impl Item {
|
|||||||
Item::Invert |
|
Item::Invert |
|
||||||
Item::InvertPlus |
|
Item::InvertPlus |
|
||||||
Item::InvertPlusPlus |
|
Item::InvertPlusPlus |
|
||||||
Item::Decay |
|
Item::Decay |
|
||||||
Item::DecayPlus |
|
Item::DecayPlus |
|
||||||
Item::DecayPlusPlus |
|
Item::DecayPlusPlus |
|
||||||
Item::Siphon|
|
Item::Siphon|
|
||||||
@ -817,7 +817,7 @@ impl Item {
|
|||||||
|
|
||||||
Item::AmplifyPlus => vec![Item::Amplify, Item::Amplify],
|
Item::AmplifyPlus => vec![Item::Amplify, Item::Amplify],
|
||||||
Item::AmplifyPlusPlus => vec![Item::AmplifyPlus, Item::AmplifyPlus],
|
Item::AmplifyPlusPlus => vec![Item::AmplifyPlus, Item::AmplifyPlus],
|
||||||
|
|
||||||
Item::HastePlus => vec![Item::Haste, Item::Haste],
|
Item::HastePlus => vec![Item::Haste, Item::Haste],
|
||||||
Item::HastePlusPlus => vec![Item::HastePlus, Item::HastePlus],
|
Item::HastePlusPlus => vec![Item::HastePlus, Item::HastePlus],
|
||||||
|
|
||||||
@ -832,9 +832,9 @@ impl Item {
|
|||||||
Item::Curse => vec![Item::Debuff, Item::Red, Item::Blue],
|
Item::Curse => vec![Item::Debuff, Item::Red, Item::Blue],
|
||||||
Item::Decay => vec![Item::Debuff, Item::Green, Item::Blue],
|
Item::Decay => vec![Item::Debuff, Item::Green, Item::Blue],
|
||||||
|
|
||||||
Item::PurgePlus => vec![Item::Purge, Item::Purge],
|
Item::PurgePlus => vec![Item::Purge, Item::Purge],
|
||||||
Item::PurgePlusPlus => vec![Item::PurgePlus, Item::PurgePlus],
|
Item::PurgePlusPlus => vec![Item::PurgePlus, Item::PurgePlus],
|
||||||
|
|
||||||
Item::InvertPlus => vec![Item::Invert, Item::Invert],
|
Item::InvertPlus => vec![Item::Invert, Item::Invert],
|
||||||
Item::InvertPlusPlus => vec![Item::InvertPlus, Item::InvertPlus],
|
Item::InvertPlusPlus => vec![Item::InvertPlus, Item::InvertPlus],
|
||||||
|
|
||||||
@ -843,10 +843,10 @@ impl Item {
|
|||||||
|
|
||||||
Item::SilencePlus => vec![Item::Silence, Item::Silence],
|
Item::SilencePlus => vec![Item::Silence, Item::Silence],
|
||||||
Item::SilencePlusPlus => vec![Item::SilencePlus, Item::SilencePlus],
|
Item::SilencePlusPlus => vec![Item::SilencePlus, Item::SilencePlus],
|
||||||
|
|
||||||
Item::CursePlus => vec![Item::Curse, Item::Curse],
|
Item::CursePlus => vec![Item::Curse, Item::Curse],
|
||||||
Item::CursePlusPlus => vec![Item::CursePlus, Item::CursePlus],
|
Item::CursePlusPlus => vec![Item::CursePlus, Item::CursePlus],
|
||||||
|
|
||||||
Item::DecayPlus => vec![Item::Decay, Item::Decay],
|
Item::DecayPlus => vec![Item::Decay, Item::Decay],
|
||||||
Item::DecayPlusPlus => vec![Item::DecayPlus, Item::DecayPlus],
|
Item::DecayPlusPlus => vec![Item::DecayPlus, Item::DecayPlus],
|
||||||
|
|
||||||
@ -1427,20 +1427,13 @@ mod tests {
|
|||||||
assert_eq!(Item::StrikePlus.components(), vec![
|
assert_eq!(Item::StrikePlus.components(), vec![
|
||||||
Item::Red, Item::Red, Item::Attack,
|
Item::Red, Item::Red, Item::Attack,
|
||||||
Item::Red, Item::Red, Item::Attack,
|
Item::Red, Item::Red, Item::Attack,
|
||||||
Item::Red, Item::Red, Item::Attack,
|
|
||||||
]);
|
]);
|
||||||
assert_eq!(Item::StrikePlusPlus.components(), vec![
|
assert_eq!(Item::StrikePlusPlus.components(), vec![
|
||||||
Item::Red, Item::Red, Item::Attack,
|
Item::Red, Item::Red, Item::Attack,
|
||||||
Item::Red, Item::Red, Item::Attack,
|
Item::Red, Item::Red, Item::Attack,
|
||||||
Item::Red, Item::Red, Item::Attack,
|
|
||||||
|
|
||||||
Item::Red, Item::Red, Item::Attack,
|
Item::Red, Item::Red, Item::Attack,
|
||||||
Item::Red, Item::Red, Item::Attack,
|
Item::Red, Item::Red, Item::Attack,
|
||||||
Item::Red, Item::Red, Item::Attack,
|
|
||||||
|
|
||||||
Item::Red, Item::Red, Item::Attack,
|
|
||||||
Item::Red, Item::Red, Item::Attack,
|
|
||||||
Item::Red, Item::Red, Item::Attack,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,11 @@ pub fn bot_player() -> Player {
|
|||||||
Player::new(bot_id, None, &name(), constructs).set_bot(true)
|
Player::new(bot_id, None, &name(), constructs).set_bot(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn anon_player(id: Uuid) -> Player {
|
||||||
|
let constructs = instance_mobs(id);
|
||||||
|
Player::new(id, None, &"player".to_string(), constructs)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn anim_test_game(skill: Skill) -> Game {
|
pub fn anim_test_game(skill: Skill) -> Game {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let mut game = Game::new();
|
let mut game = Game::new();
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use std::collections::{HashMap};
|
use std::collections::{HashMap};
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use rand::prelude::*;
|
|
||||||
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::err_msg;
|
use failure::err_msg;
|
||||||
@ -130,7 +129,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn autobuy(&mut self) -> &mut Player {
|
pub fn autobuy(&mut self) -> &mut Player {
|
||||||
let mut rng = thread_rng();
|
// let mut rng = thread_rng();
|
||||||
|
|
||||||
// skill buying phase
|
// skill buying phase
|
||||||
while self.constructs.iter().any(|c| c.skills.len() < 3) {
|
while self.constructs.iter().any(|c| c.skills.len() < 3) {
|
||||||
|
|||||||
@ -42,13 +42,6 @@ impl Cast {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve(self, game: &mut Game) {
|
pub fn resolve(self, game: &mut Game) {
|
||||||
if !self.skill.aoe() {
|
|
||||||
if self.skill.cast_animation() {
|
|
||||||
game.action(self, Action::Cast);
|
|
||||||
}
|
|
||||||
game.action(self, Action::Hit);
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.skill {
|
match self.skill {
|
||||||
Skill::Attack => attack(self, game, Attack::Base),
|
Skill::Attack => attack(self, game, Attack::Base),
|
||||||
Skill::Block => block(self, game, Block::Base),
|
Skill::Block => block(self, game, Block::Base),
|
||||||
@ -193,9 +186,6 @@ impl Cast {
|
|||||||
|
|
||||||
Skill::TriageTick => triage_tick(self, game),
|
Skill::TriageTick => triage_tick(self, game),
|
||||||
};
|
};
|
||||||
|
|
||||||
// actions.append(&mut rest);
|
|
||||||
// return actions;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -647,6 +637,9 @@ impl Skill {
|
|||||||
Skill::Heal |
|
Skill::Heal |
|
||||||
Skill::HealPlus |
|
Skill::HealPlus |
|
||||||
Skill::HealPlusPlus |
|
Skill::HealPlusPlus |
|
||||||
|
Skill::Hybrid |
|
||||||
|
Skill::HybridPlus |
|
||||||
|
Skill::HybridPlusPlus |
|
||||||
Skill::Absorb |
|
Skill::Absorb |
|
||||||
Skill::AbsorbPlus |
|
Skill::AbsorbPlus |
|
||||||
Skill::AbsorbPlusPlus |
|
Skill::AbsorbPlusPlus |
|
||||||
@ -722,7 +715,7 @@ impl Skill {
|
|||||||
.collect::<Vec<Colour>>();
|
.collect::<Vec<Colour>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base(&self) -> Skill {
|
fn _base(&self) -> Skill {
|
||||||
let bases = [Item::Attack, Item::Stun, Item::Buff, Item::Debuff, Item::Block];
|
let bases = [Item::Attack, Item::Stun, Item::Buff, Item::Debuff, Item::Block];
|
||||||
match self.components()
|
match self.components()
|
||||||
.iter()
|
.iter()
|
||||||
@ -996,6 +989,10 @@ fn siphon(cast: Cast, game: &mut Game, values: Siphon) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// should only reapply the dot if they have already been hit by the dmg
|
||||||
|
// from either this or the tick
|
||||||
|
if game.affected(cast.target, Effect::Siphoned) { return; }
|
||||||
|
|
||||||
game.action(cast,
|
game.action(cast,
|
||||||
Action::Damage {
|
Action::Damage {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
@ -1587,6 +1584,8 @@ fn decay(cast: Cast, game: &mut Game, values: Decay) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if game.affected(cast.target, Effect::Decayed) { return; }
|
||||||
|
|
||||||
game.action(cast,
|
game.action(cast,
|
||||||
Action::Effect {
|
Action::Effect {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
@ -1685,19 +1684,27 @@ impl Electrocute {
|
|||||||
fn electrocute(cast: Cast, game: &mut Game, values: Electrocute) {
|
fn electrocute(cast: Cast, game: &mut Game, values: Electrocute) {
|
||||||
let amount = game.value(Value::Stat { construct: cast.source, stat: Stat::BluePower }).pct(values.damage_multiplier());
|
let amount = game.value(Value::Stat { construct: cast.source, stat: Stat::BluePower }).pct(values.damage_multiplier());
|
||||||
|
|
||||||
game.action(cast,
|
|
||||||
Action::Remove {
|
|
||||||
construct: cast.source,
|
|
||||||
effect: Effect::Electrocute,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
game.action(cast,
|
game.action(cast,
|
||||||
Action::Effect {
|
Action::Effect {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
effect: ConstructEffect { effect: Effect::Electric, duration: values.duration(), meta:
|
effect: ConstructEffect { effect: Effect::Electrocute, duration: values.duration(), meta:
|
||||||
Some(EffectMeta::CastTick { source: cast.source, target: cast.target, skill: Skill::ElectrocuteTick, speed: cast.speed, amount }) },
|
Some(EffectMeta::CastTick { source: cast.source, target: cast.target, skill: Skill::ElectrocuteTick, speed: cast.speed, amount }) },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
game.action(cast,
|
||||||
|
Action::Damage {
|
||||||
|
construct: cast.target,
|
||||||
|
colour: Colour::Blue,
|
||||||
|
amount,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
game.action(cast,
|
||||||
|
Action::Remove {
|
||||||
|
construct: cast.source,
|
||||||
|
effect: Effect::Electric,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn electrocute_tick(cast: Cast, game: &mut Game) {
|
fn electrocute_tick(cast: Cast, game: &mut Game) {
|
||||||
@ -1847,11 +1854,16 @@ fn link(cast: Cast, game: &mut Game, values: Link) {
|
|||||||
effect: ConstructEffect { effect: Effect::Stun, duration: values.duration(), meta: None },
|
effect: ConstructEffect { effect: Effect::Stun, duration: values.duration(), meta: None },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let bp = game.value(Value::Stat { construct: cast.source, stat: Stat::BluePower }).pct(values.blue_dmg_base());
|
||||||
|
let links = game.value(Value::Effects { construct: cast.target });
|
||||||
|
let amount = bp.pct(100 + 100usize.saturating_mul(links));
|
||||||
|
|
||||||
game.action(cast,
|
game.action(cast,
|
||||||
Action::Damage {
|
Action::Damage {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
colour: Colour::Blue,
|
colour: Colour::Blue,
|
||||||
amount: game.value(Value::Effects { construct: cast.target }).pct(values.blue_dmg_base()),
|
amount,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2089,7 +2101,7 @@ fn sleep(cast: Cast, game: &mut Game, values: Sleep) {
|
|||||||
Action::Heal {
|
Action::Heal {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
amount: game.value(Value::Stat { construct: cast.source, stat: Stat::GreenPower }).pct(values.green_heal_multi()),
|
amount: game.value(Value::Stat { construct: cast.source, stat: Stat::GreenPower }).pct(values.green_heal_multi()),
|
||||||
colour: Colour::Blue,
|
colour: Colour::Green,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2188,6 +2200,9 @@ fn triage(cast: Cast, game: &mut Game, values: Triage) {
|
|||||||
Some(EffectMeta::CastTick { source: cast.source, target: cast.target, skill: Skill::TriageTick, speed: cast.speed, amount }) },
|
Some(EffectMeta::CastTick { source: cast.source, target: cast.target, skill: Skill::TriageTick, speed: cast.speed, amount }) },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if game.affected(cast.target, Effect::Triaged) { return; }
|
||||||
|
|
||||||
game.action(cast,
|
game.action(cast,
|
||||||
Action::Heal {
|
Action::Heal {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
@ -2208,7 +2223,7 @@ fn triage_tick(cast: Cast, game: &mut Game) {
|
|||||||
Action::Heal {
|
Action::Heal {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
colour: Colour::Green,
|
colour: Colour::Green,
|
||||||
amount: game.value(Value::TickDamage { construct: cast.target, effect: Effect::Electrocute }),
|
amount: game.value(Value::TickDamage { construct: cast.target, effect: Effect::Triage }),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
game.action(cast,
|
game.action(cast,
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@ -11,9 +9,6 @@ use rand::distributions::{WeightedIndex};
|
|||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::err_msg;
|
use failure::err_msg;
|
||||||
|
|
||||||
use instance::{Instance};
|
|
||||||
use construct::{Colours};
|
|
||||||
|
|
||||||
use item::*;
|
use item::*;
|
||||||
|
|
||||||
pub type VboxIndices = Option<HashMap<ItemType, Vec<String>>>;
|
pub type VboxIndices = Option<HashMap<ItemType, Vec<String>>>;
|
||||||
@ -40,9 +35,9 @@ const STARTING_ATTACK_COUNT: usize = 3;
|
|||||||
|
|
||||||
impl Vbox {
|
impl Vbox {
|
||||||
pub fn new() -> Vbox {
|
pub fn new() -> Vbox {
|
||||||
let mut colours: HashMap<String, Item> = HashMap::new();
|
let colours: HashMap<String, Item> = HashMap::new();
|
||||||
let mut skills: HashMap<String, Item> = HashMap::new();
|
let skills: HashMap<String, Item> = HashMap::new();
|
||||||
let mut specs: HashMap<String, Item> = HashMap::new();
|
let specs: HashMap<String, Item> = HashMap::new();
|
||||||
|
|
||||||
let store = [
|
let store = [
|
||||||
(ItemType::Colours, colours),
|
(ItemType::Colours, colours),
|
||||||
@ -220,6 +215,7 @@ impl Vbox {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use construct::{Colours};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn combine_test() {
|
fn combine_test() {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-ops",
|
"name": "mnml-ops",
|
||||||
"version": "1.10.1",
|
"version": "1.11.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mnml"
|
name = "mnml"
|
||||||
version = "1.10.1"
|
version = "1.11.0"
|
||||||
authors = ["ntr <ntr@smokestack.io>"]
|
authors = ["ntr <ntr@smokestack.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -25,6 +25,7 @@ dotenv = "0.9.0"
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
fern = { version = "0.5", features = ["colored", "syslog-4"] }
|
fern = { version = "0.5", features = ["colored", "syslog-4"] }
|
||||||
syslog = "4"
|
syslog = "4"
|
||||||
|
log-panics = "2"
|
||||||
|
|
||||||
iron = "0.6"
|
iron = "0.6"
|
||||||
bodyparser = "0.8"
|
bodyparser = "0.8"
|
||||||
|
|||||||
@ -42,7 +42,17 @@ impl Account {
|
|||||||
false => None,
|
false => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Player::new(self.id, Some(self.img), &self.name, constructs))
|
Ok(Player::new(self.id, img, &self.name, constructs))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn anonymous() -> Account {
|
||||||
|
Account {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
img: Uuid::new_v4(),
|
||||||
|
name: "you".to_string(),
|
||||||
|
balance: 0,
|
||||||
|
subscribed: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +103,7 @@ pub fn chat_wheel(_db: &Db, _id: Uuid) -> Result<Vec<String>, Error> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_name(db: &Db, name: &String) -> Result<Account, Error> {
|
pub fn _select_name(db: &Db, name: &String) -> Result<Account, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
SELECT id, name, balance, subscribed, img
|
SELECT id, name, balance, subscribed, img
|
||||||
FROM accounts
|
FROM accounts
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use std::time;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::{err_msg, format_err};
|
use failure::{format_err};
|
||||||
|
|
||||||
use crossbeam_channel::{Sender, Receiver};
|
use crossbeam_channel::{Sender, Receiver};
|
||||||
|
|
||||||
@ -15,10 +15,9 @@ use names;
|
|||||||
|
|
||||||
use rpc::RpcMessage;
|
use rpc::RpcMessage;
|
||||||
use warden::{GameEvent};
|
use warden::{GameEvent};
|
||||||
use mail::Mail;
|
|
||||||
|
|
||||||
pub type EventsTx = Sender<Event>;
|
pub type EventsTx = Sender<Event>;
|
||||||
type Id = usize;
|
type Id = Uuid;
|
||||||
|
|
||||||
// this is pretty heavyweight
|
// this is pretty heavyweight
|
||||||
// but it makes the ergonomics easy
|
// but it makes the ergonomics easy
|
||||||
@ -35,7 +34,6 @@ pub struct Events {
|
|||||||
pub tx: Sender<Event>,
|
pub tx: Sender<Event>,
|
||||||
rx: Receiver<Event>,
|
rx: Receiver<Event>,
|
||||||
|
|
||||||
mail: Sender<Mail>,
|
|
||||||
warden: Sender<GameEvent>,
|
warden: Sender<GameEvent>,
|
||||||
clients: HashMap<Id, WsClient>,
|
clients: HashMap<Id, WsClient>,
|
||||||
}
|
}
|
||||||
@ -43,7 +41,7 @@ pub struct Events {
|
|||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
// ws lifecycle
|
// ws lifecycle
|
||||||
Connect(Id, Option<Account>, Sender<RpcMessage>),
|
Connect(Id, Account, Sender<RpcMessage>),
|
||||||
Disconnect(Id),
|
Disconnect(Id),
|
||||||
Subscribe(Id, Uuid),
|
Subscribe(Id, Uuid),
|
||||||
Unsubscribe(Id, Uuid),
|
Unsubscribe(Id, Uuid),
|
||||||
@ -64,7 +62,6 @@ pub enum Event {
|
|||||||
|
|
||||||
struct WsClient {
|
struct WsClient {
|
||||||
id: Id,
|
id: Id,
|
||||||
account: Option<Uuid>,
|
|
||||||
tx: Sender<RpcMessage>,
|
tx: Sender<RpcMessage>,
|
||||||
subs: HashSet<Uuid>,
|
subs: HashSet<Uuid>,
|
||||||
chat: Option<(Uuid, String)>,
|
chat: Option<(Uuid, String)>,
|
||||||
@ -73,12 +70,11 @@ struct WsClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Events {
|
impl Events {
|
||||||
pub fn new(tx: Sender<Event>, rx: Receiver<Event>, warden: Sender<GameEvent>, mail: Sender<Mail>) -> Events {
|
pub fn new(tx: Sender<Event>, rx: Receiver<Event>, warden: Sender<GameEvent>) -> Events {
|
||||||
Events {
|
Events {
|
||||||
tx,
|
tx,
|
||||||
rx,
|
rx,
|
||||||
warden,
|
warden,
|
||||||
mail,
|
|
||||||
clients: HashMap::new(),
|
clients: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,30 +98,13 @@ impl Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_client(&mut self, id: Id) -> Result<&mut WsClient, Error> {
|
|
||||||
match self.clients.get_mut(&id) {
|
|
||||||
Some(c) => Ok(c),
|
|
||||||
None => Err(format_err!("connection not found id={:?}", id)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_client(&mut self, id: Id) {
|
|
||||||
self.clients.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event(&mut self, msg: Event) -> Result<(), Error> {
|
fn event(&mut self, msg: Event) -> Result<(), Error> {
|
||||||
match msg {
|
match msg {
|
||||||
Event::Connect(id, account, tx) => {
|
Event::Connect(id, account, tx) => {
|
||||||
info!("connect id={:?} account={:?}", id, account);
|
info!("connect id={:?} account={:?}", id, account);
|
||||||
|
|
||||||
let account_id = match account {
|
|
||||||
Some(a) => Some(a.id),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = WsClient { id,
|
let client = WsClient { id,
|
||||||
tx,
|
tx,
|
||||||
account: account_id,
|
|
||||||
subs: HashSet::new(),
|
subs: HashSet::new(),
|
||||||
pvp: false,
|
pvp: false,
|
||||||
invite: None,
|
invite: None,
|
||||||
@ -164,7 +143,7 @@ impl Events {
|
|||||||
trace!("unsubscribe id={:?} object={:?}", id, obj);
|
trace!("unsubscribe id={:?} object={:?}", id, obj);
|
||||||
|
|
||||||
match self.clients.get_mut(&id) {
|
match self.clients.get_mut(&id) {
|
||||||
Some(mut client) => {
|
Some(client) => {
|
||||||
client.subs.remove(&obj);
|
client.subs.remove(&obj);
|
||||||
trace!("unsubscribe subscriptions removed={:?}", client.subs.len());
|
trace!("unsubscribe subscriptions removed={:?}", client.subs.len());
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -183,13 +162,10 @@ impl Events {
|
|||||||
if client.subs.contains(&id) {
|
if client.subs.contains(&id) {
|
||||||
subs += 1;
|
subs += 1;
|
||||||
|
|
||||||
let redacted = match client.account {
|
let redacted = match msg {
|
||||||
Some(a) => match msg {
|
RpcMessage::InstanceState(ref i) => RpcMessage::InstanceState(i.clone().redact(client.id)),
|
||||||
RpcMessage::InstanceState(ref i) => RpcMessage::InstanceState(i.clone().redact(a)),
|
RpcMessage::GameState(ref i) => RpcMessage::GameState(i.clone().redact(client.id)),
|
||||||
RpcMessage::GameState(ref i) => RpcMessage::GameState(i.clone().redact(a)),
|
_ => msg.clone(),
|
||||||
_ => msg.clone(),
|
|
||||||
}
|
|
||||||
None => msg.clone(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match client.tx.send(redacted) {
|
match client.tx.send(redacted) {
|
||||||
@ -204,7 +180,9 @@ impl Events {
|
|||||||
|
|
||||||
if !dead.is_empty() {
|
if !dead.is_empty() {
|
||||||
trace!("dead connections={:?}", dead.len());
|
trace!("dead connections={:?}", dead.len());
|
||||||
dead.iter().for_each(|id| self.remove_client(*id));
|
for id in dead.iter() {
|
||||||
|
self.clients.remove(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("push subscribers={:?}", subs);
|
trace!("push subscribers={:?}", subs);
|
||||||
@ -218,18 +196,14 @@ impl Events {
|
|||||||
let c = self.clients.get(&id)
|
let c = self.clients.get(&id)
|
||||||
.ok_or(format_err!("connection not found id={:?}", id))?;
|
.ok_or(format_err!("connection not found id={:?}", id))?;
|
||||||
|
|
||||||
if let None = c.account {
|
info!("pvp queue request id={:?} account={:?}", c.id, c.id);
|
||||||
return Err(err_msg("cannot join pvp queue anonymously"));
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("pvp queue request id={:?} account={:?}", c.id, c.account);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the req for the already queued opponent
|
// create the req for the already queued opponent
|
||||||
if let Some(opp_req) = match self.clients.iter_mut().find(|(c_id, c)| c.pvp && **c_id != id) {
|
if let Some(opp_req) = match self.clients.iter_mut().find(|(c_id, c)| c.pvp && **c_id != id) {
|
||||||
Some((q_id, q)) => {
|
Some((q_id, q)) => {
|
||||||
q.pvp = false;
|
q.pvp = false;
|
||||||
Some(PvpRequest { id: *q_id, account: q.account.unwrap(), tx: q.tx.clone() })
|
Some(PvpRequest { id: *q_id, account: q.id, tx: q.tx.clone() })
|
||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
} {
|
} {
|
||||||
@ -237,7 +211,7 @@ impl Events {
|
|||||||
let c = self.clients.get_mut(&id)
|
let c = self.clients.get_mut(&id)
|
||||||
.ok_or(format_err!("connection not found id={:?}", id))?;
|
.ok_or(format_err!("connection not found id={:?}", id))?;
|
||||||
|
|
||||||
let player_req = PvpRequest { id: c.id, account: c.account.unwrap(), tx: c.tx.clone() };
|
let player_req = PvpRequest { id: c.id, account: c.id, tx: c.tx.clone() };
|
||||||
self.warden.send(GameEvent::Match((opp_req, player_req)))?;
|
self.warden.send(GameEvent::Match((opp_req, player_req)))?;
|
||||||
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
@ -247,7 +221,7 @@ impl Events {
|
|||||||
let requester = self.clients.get_mut(&id).unwrap();
|
let requester = self.clients.get_mut(&id).unwrap();
|
||||||
requester.pvp = true;
|
requester.pvp = true;
|
||||||
requester.tx.send(RpcMessage::QueueJoined(()))?;
|
requester.tx.send(RpcMessage::QueueJoined(()))?;
|
||||||
info!("joined game queue id={:?} account={:?}", requester.id, requester.account);
|
info!("joined game queue id={:?} account={:?}", requester.id, requester.id);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -256,12 +230,8 @@ impl Events {
|
|||||||
let c = self.clients.get_mut(&id)
|
let c = self.clients.get_mut(&id)
|
||||||
.ok_or(format_err!("connection not found id={:?}", 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("-");
|
let code = names::name().split_whitespace().collect::<Vec<&str>>().join("-");
|
||||||
info!("pvp invite request id={:?} account={:?} code={:?}", c.id, c.account, code);
|
info!("pvp invite request id={:?} account={:?} code={:?}", c.id, c.id, code);
|
||||||
c.invite = Some(code.clone());
|
c.invite = Some(code.clone());
|
||||||
c.tx.send(RpcMessage::Invite(code))?;
|
c.tx.send(RpcMessage::Invite(code))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -272,11 +242,7 @@ impl Events {
|
|||||||
let c = self.clients.get(&id)
|
let c = self.clients.get(&id)
|
||||||
.ok_or(format_err!("connection not found id={:?}", id))?;
|
.ok_or(format_err!("connection not found id={:?}", id))?;
|
||||||
|
|
||||||
if let None = c.account {
|
info!("pvp join request id={:?} account={:?} code={:?}", c.id, c.id, code);
|
||||||
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()
|
let inv = self.clients.iter()
|
||||||
.filter(|(_id, c)| c.invite.is_some())
|
.filter(|(_id, c)| c.invite.is_some())
|
||||||
@ -284,10 +250,10 @@ impl Events {
|
|||||||
Some(ref c) => *c == code,
|
Some(ref c) => *c == code,
|
||||||
None => false,
|
None => false,
|
||||||
})
|
})
|
||||||
.map(|(_id, c)| PvpRequest { id: c.id, account: c.account.unwrap(), tx: c.tx.clone() })
|
.map(|(_id, c)| PvpRequest { id: c.id, account: c.id, tx: c.tx.clone() })
|
||||||
.ok_or(format_err!("invite expired code={:?}", code))?;
|
.ok_or(format_err!("invite expired code={:?}", code))?;
|
||||||
|
|
||||||
let join = PvpRequest { id: c.id, account: c.account.unwrap(), tx: c.tx.clone() };
|
let join = PvpRequest { id: c.id, account: c.id, tx: c.tx.clone() };
|
||||||
|
|
||||||
self.warden.send(GameEvent::Match((join, inv)))?;
|
self.warden.send(GameEvent::Match((join, inv)))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -310,7 +276,7 @@ impl Events {
|
|||||||
|
|
||||||
c.pvp = false;
|
c.pvp = false;
|
||||||
c.tx.send(RpcMessage::QueueLeft(()))?;
|
c.tx.send(RpcMessage::QueueLeft(()))?;
|
||||||
info!("left game queue id={:?} account={:?}", c.id, c.account);
|
info!("left game queue id={:?} account={:?}", c.id, c.id);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -337,12 +303,11 @@ impl Events {
|
|||||||
// now collect all listeners of this instance
|
// now collect all listeners of this instance
|
||||||
|
|
||||||
let chat_state: HashMap<Uuid, String> = self.clients.iter()
|
let chat_state: HashMap<Uuid, String> = self.clients.iter()
|
||||||
.filter(|(_id, c)| c.account.is_some())
|
|
||||||
.filter(|(_id, c)| match c.chat {
|
.filter(|(_id, c)| match c.chat {
|
||||||
Some(ref chat) => chat.0 == instance,
|
Some(ref chat) => chat.0 == instance,
|
||||||
None => false,
|
None => false,
|
||||||
})
|
})
|
||||||
.map(|(_id, c)| (c.account.unwrap(), c.chat.clone().unwrap().1))
|
.map(|(_id, c)| (c.id, c.chat.clone().unwrap().1))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
|
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
|
||||||
@ -357,12 +322,11 @@ impl Events {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let chat_state: HashMap<Uuid, String> = self.clients.iter()
|
let chat_state: HashMap<Uuid, String> = self.clients.iter()
|
||||||
.filter(|(_id, c)| c.account.is_some())
|
|
||||||
.filter(|(_id, c)| match c.chat {
|
.filter(|(_id, c)| match c.chat {
|
||||||
Some(ref chat) => chat.0 == instance,
|
Some(ref chat) => chat.0 == instance,
|
||||||
None => false,
|
None => false,
|
||||||
})
|
})
|
||||||
.map(|(_id, c)| (c.account.unwrap(), c.chat.clone().unwrap().1))
|
.map(|(_id, c)| (c.id, c.chat.clone().unwrap().1))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
|
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
|
||||||
|
|||||||
@ -111,7 +111,7 @@ pub fn json_response(status: status::Status, response: Json) -> Response {
|
|||||||
return Response::with((content_type, status, json));
|
return Response::with((content_type, status, json));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn json_object(status: status::Status, object: String) -> Response {
|
pub fn _json_object(status: status::Status, object: String) -> Response {
|
||||||
let content_type = "application/json".parse::<Mime>().unwrap();
|
let content_type = "application/json".parse::<Mime>().unwrap();
|
||||||
return Response::with((content_type, status, object));
|
return Response::with((content_type, status, object));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,7 +72,7 @@ enum ConstructShapes {
|
|||||||
Line,
|
Line,
|
||||||
V,
|
V,
|
||||||
Tri,
|
Tri,
|
||||||
Plus,
|
// Plus,
|
||||||
Blank,
|
Blank,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,9 +203,7 @@ pub fn shapes_write(id: Uuid) -> Result<Uuid, Error> {
|
|||||||
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})\" />",
|
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)?;
|
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 => { },
|
||||||
|
|
||||||
},
|
|
||||||
ConstructShapes::Blank => (),
|
ConstructShapes::Blank => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ extern crate serde_cbor;
|
|||||||
#[macro_use] extern crate failure;
|
#[macro_use] extern crate failure;
|
||||||
|
|
||||||
extern crate fern;
|
extern crate fern;
|
||||||
|
extern crate log_panics;
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
|
|
||||||
extern crate stripe;
|
extern crate stripe;
|
||||||
@ -47,6 +48,8 @@ mod pg;
|
|||||||
mod events;
|
mod events;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
mod warden;
|
mod warden;
|
||||||
|
mod user_authenticated;
|
||||||
|
mod user_anonymous;
|
||||||
|
|
||||||
use std::thread::{spawn};
|
use std::thread::{spawn};
|
||||||
use std::path::{Path};
|
use std::path::{Path};
|
||||||
@ -55,6 +58,8 @@ use crossbeam_channel::{unbounded};
|
|||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn setup_logger() -> Result<(), fern::InitError> {
|
pub fn setup_logger() -> Result<(), fern::InitError> {
|
||||||
|
log_panics::init();
|
||||||
|
|
||||||
let formatter = syslog::Formatter3164 {
|
let formatter = syslog::Formatter3164 {
|
||||||
facility: syslog::Facility::LOG_USER,
|
facility: syslog::Facility::LOG_USER,
|
||||||
hostname: None,
|
hostname: None,
|
||||||
@ -117,11 +122,11 @@ pub fn start() {
|
|||||||
let warden_tick_tx = warden_tx.clone();
|
let warden_tick_tx = warden_tx.clone();
|
||||||
|
|
||||||
let (mail_tx, mail_rx) = unbounded();
|
let (mail_tx, mail_rx) = unbounded();
|
||||||
let http_mail_tx = mail_tx.clone();
|
let _http_mail_tx = mail_tx.clone();
|
||||||
|
|
||||||
// create a clone of the tx so ws handler can tell events
|
// create a clone of the tx so ws handler can tell events
|
||||||
// about connection status
|
// about connection status
|
||||||
let events = events::Events::new(events_tx, events_rx, events_warden_tx, mail_tx);
|
let events = events::Events::new(events_tx, events_rx, events_warden_tx);
|
||||||
let warden = warden::Warden::new(warden_tx, warden_rx, events.tx.clone(), pool.clone());
|
let warden = warden::Warden::new(warden_tx, warden_rx, events.tx.clone(), pool.clone());
|
||||||
|
|
||||||
let pg_pool = pool.clone();
|
let pg_pool = pool.clone();
|
||||||
|
|||||||
@ -261,7 +261,7 @@ pub fn set(tx: &mut Transaction, account: Uuid, email: &String) -> Result<(Uuid,
|
|||||||
return Ok((id, confirm_token));
|
return Ok((id, confirm_token));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listen(rx: Receiver<Mail>) -> SmtpTransport {
|
pub fn listen(_rx: Receiver<Mail>) -> SmtpTransport {
|
||||||
let sender = env::var("MAIL_ADDRESS")
|
let sender = env::var("MAIL_ADDRESS")
|
||||||
.expect("MAIL_ADDRESS must be set");
|
.expect("MAIL_ADDRESS must be set");
|
||||||
|
|
||||||
@ -271,7 +271,7 @@ pub fn listen(rx: Receiver<Mail>) -> SmtpTransport {
|
|||||||
let domain = env::var("MAIL_DOMAIN")
|
let domain = env::var("MAIL_DOMAIN")
|
||||||
.expect("MAIL_DOMAIN must be set");
|
.expect("MAIL_DOMAIN must be set");
|
||||||
|
|
||||||
let mut mailer = SmtpClient::new_simple("smtp.gmail.com").unwrap()
|
let mailer = SmtpClient::new_simple("smtp.gmail.com").unwrap()
|
||||||
.hello_name(ClientId::Domain(domain))
|
.hello_name(ClientId::Domain(domain))
|
||||||
.credentials(Credentials::new(sender, password))
|
.credentials(Credentials::new(sender, password))
|
||||||
.smtp_utf8(true)
|
.smtp_utf8(true)
|
||||||
@ -281,19 +281,6 @@ pub fn listen(rx: Receiver<Mail>) -> SmtpTransport {
|
|||||||
|
|
||||||
info!("mail connected");
|
info!("mail connected");
|
||||||
|
|
||||||
// loop {
|
|
||||||
// match rx.recv() {
|
|
||||||
// Ok(m) => match send_mail(&mut mailer, m) {
|
|
||||||
// Ok(r) => info!("{:?}", r),
|
|
||||||
// Err(e) => warn!("{:?}", e),
|
|
||||||
// },
|
|
||||||
// Err(e) => {
|
|
||||||
// error!("{:?}", e);
|
|
||||||
// panic!("mail thread cannot continue");
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Explicitly close the SMTP transaction as we enabled connection reuse
|
// Explicitly close the SMTP transaction as we enabled connection reuse
|
||||||
// mailer.close();
|
// mailer.close();
|
||||||
return mailer;
|
return mailer;
|
||||||
|
|||||||
@ -86,7 +86,7 @@ impl Mtx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
|
pub fn _delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
|
||||||
let query = "
|
let query = "
|
||||||
DELETE
|
DELETE
|
||||||
FROM mtx
|
FROM mtx
|
||||||
|
|||||||
@ -22,7 +22,7 @@ use mnml_core::mob::instance_mobs;
|
|||||||
use mnml_core::vbox::{ItemType, VboxIndices};
|
use mnml_core::vbox::{ItemType, VboxIndices};
|
||||||
use mnml_core::item::Item;
|
use mnml_core::item::Item;
|
||||||
use mnml_core::skill::Skill;
|
use mnml_core::skill::Skill;
|
||||||
use mnml_core::mob::bot_player;
|
use mnml_core::mob::{bot_player, anon_player};
|
||||||
use mnml_core::instance::{Instance, TimeControl};
|
use mnml_core::instance::{Instance, TimeControl};
|
||||||
|
|
||||||
use events::{Event};
|
use events::{Event};
|
||||||
@ -143,7 +143,7 @@ pub fn listen(pool: PgPool, events: Sender<Event>) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn construct_delete(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<(), Error> {
|
pub fn _construct_delete(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<(), Error> {
|
||||||
let query = "
|
let query = "
|
||||||
DELETE
|
DELETE
|
||||||
FROM constructs
|
FROM constructs
|
||||||
@ -163,7 +163,7 @@ pub fn construct_delete(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Res
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn construct_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<Construct, Error> {
|
pub fn _construct_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<Construct, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
SELECT data
|
SELECT data
|
||||||
FROM constructs
|
FROM constructs
|
||||||
@ -290,7 +290,7 @@ pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result<Game, Error> {
|
|||||||
return Ok(game);
|
return Ok(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select(db: &Db, id: Uuid) -> Result<Game, Error> {
|
pub fn _game_select(db: &Db, id: Uuid) -> Result<Game, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM games
|
FROM games
|
||||||
@ -312,7 +312,7 @@ pub fn select(db: &Db, id: Uuid) -> Result<Game, Error> {
|
|||||||
return Ok(game);
|
return Ok(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list(db: &Db, number: u32) -> Result<Vec<Game>, Error> {
|
pub fn _game_list(db: &Db, number: u32) -> Result<Vec<Game>, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
SELECT data
|
SELECT data
|
||||||
FROM games
|
FROM games
|
||||||
@ -694,6 +694,35 @@ pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result<Inst
|
|||||||
Ok(instance)
|
Ok(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn instance_demo(account: &Account) -> Result<Instance, Error> {
|
||||||
|
let bot = bot_player();
|
||||||
|
let bot_id = bot.id;
|
||||||
|
|
||||||
|
// generate imgs for the client to see
|
||||||
|
for c in bot.constructs.iter() {
|
||||||
|
img::shapes_write(c.img)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut instance = Instance::new()
|
||||||
|
.set_time_control(TimeControl::Practice)
|
||||||
|
.set_name(bot.name.clone())?;
|
||||||
|
|
||||||
|
let player = anon_player(account.id);
|
||||||
|
|
||||||
|
for c in player.constructs.iter() {
|
||||||
|
img::shapes_write(c.img)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.add_player(player.clone())?;
|
||||||
|
instance.add_player(bot)?;
|
||||||
|
|
||||||
|
instance.player_ready(bot_id)?;
|
||||||
|
// skip faceoff
|
||||||
|
instance.player_ready(player.id)?;
|
||||||
|
|
||||||
|
Ok(instance)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pvp(tx: &mut Transaction, a: &Account, b: &Account) -> Result<Instance, Error> {
|
pub fn pvp(tx: &mut Transaction, a: &Account, b: &Account) -> Result<Instance, Error> {
|
||||||
let mut instance = Instance::new()
|
let mut instance = Instance::new()
|
||||||
// TODO generate nice game names
|
// TODO generate nice game names
|
||||||
@ -786,7 +815,7 @@ pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uu
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bot_instance() -> Instance {
|
pub fn _bot_instance() -> Instance {
|
||||||
let mut instance = Instance::new();
|
let mut instance = Instance::new();
|
||||||
|
|
||||||
let bot_player = bot_player();
|
let bot_player = bot_player();
|
||||||
@ -804,26 +833,6 @@ pub fn bot_instance() -> Instance {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn demo() -> Result<Vec<Player>, Error> {
|
|
||||||
let bot = bot_player();
|
|
||||||
|
|
||||||
// generate bot imgs for the client to see
|
|
||||||
for c in bot.constructs.iter() {
|
|
||||||
img::shapes_write(c.img)?;
|
|
||||||
};
|
|
||||||
|
|
||||||
let bot2 = bot_player();
|
|
||||||
|
|
||||||
// generate bot imgs for the client to see
|
|
||||||
for c in bot2.constructs.iter() {
|
|
||||||
img::shapes_write(c.img)?;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Ok(vec![bot, bot2])
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn vbox_refill(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<Instance, Error> {
|
pub fn vbox_refill(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<Instance, Error> {
|
||||||
let instance = instance_get(tx, instance_id)?
|
let instance = instance_get(tx, instance_id)?
|
||||||
.vbox_refill(account.id)?;
|
.vbox_refill(account.id)?;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use mnml_core::item::ItemInfoCtr;
|
use mnml_core::item::ItemInfoCtr;
|
||||||
use mnml_core::instance::ChatState;
|
use mnml_core::instance::ChatState;
|
||||||
use mnml_core::item::item_info;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::{Instant};
|
use std::time::{Instant};
|
||||||
use std::thread::{spawn};
|
use std::thread::{spawn};
|
||||||
@ -8,12 +8,8 @@ use std::thread::{spawn};
|
|||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use rand::prelude::*;
|
|
||||||
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::err_msg;
|
use serde_cbor::{to_vec};
|
||||||
|
|
||||||
use serde_cbor::{from_slice, to_vec};
|
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
|
|
||||||
use stripe::{Client as StripeClient, Subscription};
|
use stripe::{Client as StripeClient, Subscription};
|
||||||
@ -22,44 +18,24 @@ use crossbeam_channel::{unbounded, Sender as CbSender};
|
|||||||
use ws::{Builder, CloseCode, Message, Handler, Request, Response, Settings, Sender as WsSender};
|
use ws::{Builder, CloseCode, Message, Handler, Request, Response, Settings, Sender as WsSender};
|
||||||
use ws::deflate::DeflateHandler;
|
use ws::deflate::DeflateHandler;
|
||||||
|
|
||||||
use pg::{
|
|
||||||
demo,
|
|
||||||
game_concede,
|
|
||||||
game_offer_draw,
|
|
||||||
game_ready,
|
|
||||||
game_skill,
|
|
||||||
game_skill_clear,
|
|
||||||
game_state,
|
|
||||||
instance_abandon,
|
|
||||||
instance_practice,
|
|
||||||
instance_ready,
|
|
||||||
instance_state,
|
|
||||||
vbox_apply,
|
|
||||||
vbox_buy,
|
|
||||||
vbox_combine,
|
|
||||||
vbox_refill,
|
|
||||||
vbox_refund,
|
|
||||||
vbox_unequip,
|
|
||||||
};
|
|
||||||
|
|
||||||
use account::{Account};
|
use account::{Account};
|
||||||
use account;
|
use account;
|
||||||
use events::{Event};
|
use events::{Event};
|
||||||
|
|
||||||
|
use user_anonymous::{Anonymous};
|
||||||
|
use user_authenticated::{Authorised};
|
||||||
|
|
||||||
use mnml_core::construct::{Construct};
|
use mnml_core::construct::{Construct};
|
||||||
use mnml_core::game::{Game};
|
use mnml_core::game::{Game};
|
||||||
use mnml_core::player::Player;
|
|
||||||
|
|
||||||
use mnml_core::vbox::{ItemType};
|
use mnml_core::vbox::{ItemType};
|
||||||
use mnml_core::item::Item;
|
use mnml_core::item::Item;
|
||||||
use mnml_core::skill::Skill;
|
use mnml_core::skill::Skill;
|
||||||
use mnml_core::mob::{anim_test_game};
|
|
||||||
use mnml_core::instance::{Instance};
|
use mnml_core::instance::{Instance};
|
||||||
|
|
||||||
use mtx;
|
use mtx;
|
||||||
use mail;
|
|
||||||
|
|
||||||
use payments;
|
|
||||||
use mail::Email;
|
use mail::Email;
|
||||||
use pg::{Db};
|
use pg::{Db};
|
||||||
use pg::{PgPool};
|
use pg::{PgPool};
|
||||||
@ -68,13 +44,12 @@ use http::{AUTH_CLEAR, TOKEN_HEADER};
|
|||||||
#[derive(Debug,Clone,Serialize)]
|
#[derive(Debug,Clone,Serialize)]
|
||||||
pub enum RpcMessage {
|
pub enum RpcMessage {
|
||||||
AccountState(Account),
|
AccountState(Account),
|
||||||
|
AccountAuthenticated(Account),
|
||||||
AccountConstructs(Vec<Construct>),
|
AccountConstructs(Vec<Construct>),
|
||||||
AccountTeam(Vec<Construct>),
|
AccountTeam(Vec<Construct>),
|
||||||
AccountInstances(Vec<Instance>),
|
AccountInstances(Vec<Instance>),
|
||||||
AccountShop(mtx::Shop),
|
AccountShop(mtx::Shop),
|
||||||
|
|
||||||
Demo(Vec<Player>),
|
|
||||||
|
|
||||||
ConstructSpawn(Construct),
|
ConstructSpawn(Construct),
|
||||||
GameState(Game),
|
GameState(Game),
|
||||||
ItemInfo(ItemInfoCtr),
|
ItemInfo(ItemInfoCtr),
|
||||||
@ -87,6 +62,7 @@ pub enum RpcMessage {
|
|||||||
SubscriptionState(Option<Subscription>),
|
SubscriptionState(Option<Subscription>),
|
||||||
|
|
||||||
Pong(()),
|
Pong(()),
|
||||||
|
StartTutorial(()),
|
||||||
|
|
||||||
QueueRequested(()),
|
QueueRequested(()),
|
||||||
QueueJoined(()),
|
QueueJoined(()),
|
||||||
@ -149,194 +125,32 @@ pub enum RpcRequest {
|
|||||||
VboxRefund { instance_id: Uuid, index: String },
|
VboxRefund { instance_id: Uuid, index: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>;
|
||||||
|
}
|
||||||
|
|
||||||
struct Connection {
|
struct Connection {
|
||||||
pub id: usize,
|
pub id: Uuid,
|
||||||
pub ws: CbSender<RpcMessage>,
|
pub ws: CbSender<RpcMessage>,
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
stripe: StripeClient,
|
stripe: StripeClient,
|
||||||
account: Option<Account>,
|
// account: Option<Account>,
|
||||||
|
user: Box<dyn User>,
|
||||||
events: CbSender<Event>,
|
events: CbSender<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
fn receive(&self, data: Vec<u8>, db: &Db, begin: Instant) -> Result<RpcMessage, Error> {
|
|
||||||
// cast the msg to this type to receive method name
|
|
||||||
match from_slice::<RpcRequest>(&data) {
|
|
||||||
Ok(v) => {
|
|
||||||
|
|
||||||
// non authenticated
|
|
||||||
// non transactional reqs
|
|
||||||
match v {
|
|
||||||
RpcRequest::Ping {} => return Ok(RpcMessage::Pong(())),
|
|
||||||
RpcRequest::ItemInfo {} => return Ok(RpcMessage::ItemInfo(item_info())),
|
|
||||||
RpcRequest::DevResolve { skill } =>
|
|
||||||
return Ok(RpcMessage::GameState(anim_test_game(skill))),
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
// check for authorization now
|
|
||||||
let account = match self.account {
|
|
||||||
Some(ref account) => account,
|
|
||||||
None => return Err(err_msg("auth required")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let request = v.clone();
|
|
||||||
|
|
||||||
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(()))
|
|
||||||
},
|
|
||||||
RpcRequest::InstanceLeave {} => {
|
|
||||||
self.events.send(Event::Leave(self.id))?;
|
|
||||||
Ok(RpcMessage::Processing(()))
|
|
||||||
},
|
|
||||||
|
|
||||||
RpcRequest::InstanceChat { instance_id, index } => {
|
|
||||||
if !account.subscribed {
|
|
||||||
return Err(err_msg("subscribe to unlock chat"))
|
|
||||||
}
|
|
||||||
|
|
||||||
let wheel = account::chat_wheel(&db, account.id)?;
|
|
||||||
|
|
||||||
if let Some(c) = wheel.get(index) {
|
|
||||||
self.events.send(Event::Chat(self.id, instance_id, c.to_string()))?;
|
|
||||||
} else {
|
|
||||||
return Err(err_msg("invalid chat index"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RpcMessage::Processing(()))
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
// all good, let's make a tx and process
|
|
||||||
let mut tx = db.transaction()?;
|
|
||||||
|
|
||||||
let res = match v {
|
|
||||||
RpcRequest::AccountState {} =>
|
|
||||||
Ok(RpcMessage::AccountState(account.clone())),
|
|
||||||
RpcRequest::AccountConstructs {} =>
|
|
||||||
Ok(RpcMessage::AccountConstructs(account::constructs(&mut tx, &account)?)),
|
|
||||||
RpcRequest::AccountInstances {} =>
|
|
||||||
Ok(RpcMessage::AccountInstances(account::account_instances(&mut tx, account)?)),
|
|
||||||
RpcRequest::AccountSetTeam { ids } =>
|
|
||||||
Ok(RpcMessage::AccountTeam(account::set_team(&mut tx, &account, ids)?)),
|
|
||||||
|
|
||||||
RpcRequest::EmailState {} =>
|
|
||||||
Ok(RpcMessage::EmailState(mail::select_account(&db, account.id)?)),
|
|
||||||
|
|
||||||
RpcRequest::SubscriptionState {} =>
|
|
||||||
Ok(RpcMessage::SubscriptionState(payments::account_subscription(&db, &self.stripe, &account)?)),
|
|
||||||
|
|
||||||
// RpcRequest::AccountShop {} =>
|
|
||||||
// Ok(RpcMessage::AccountShop(mtx::account_shop(&mut tx, &account)?)),
|
|
||||||
|
|
||||||
// RpcRequest::ConstructDelete" => handle_construct_delete(data, &mut tx, account),
|
|
||||||
|
|
||||||
RpcRequest::GameState { id } =>
|
|
||||||
Ok(RpcMessage::GameState(game_state(&mut tx, account, id)?)),
|
|
||||||
|
|
||||||
RpcRequest::GameSkill { game_id, construct_id, target_construct_id, skill } =>
|
|
||||||
Ok(RpcMessage::GameState(game_skill(&mut tx, account, game_id, construct_id, target_construct_id, skill)?)),
|
|
||||||
|
|
||||||
RpcRequest::GameSkillClear { game_id } =>
|
|
||||||
Ok(RpcMessage::GameState(game_skill_clear(&mut tx, account, game_id)?)),
|
|
||||||
|
|
||||||
RpcRequest::GameReady { id } =>
|
|
||||||
Ok(RpcMessage::GameState(game_ready(&mut tx, account, id)?)),
|
|
||||||
|
|
||||||
RpcRequest::GameConcede { game_id } =>
|
|
||||||
Ok(RpcMessage::GameState(game_concede(&mut tx, account, game_id)?)),
|
|
||||||
|
|
||||||
RpcRequest::GameOfferDraw { game_id } =>
|
|
||||||
Ok(RpcMessage::GameState(game_offer_draw(&mut tx, account, game_id)?)),
|
|
||||||
|
|
||||||
RpcRequest::InstancePractice {} =>
|
|
||||||
Ok(RpcMessage::InstanceState(instance_practice(&mut tx, account)?)),
|
|
||||||
|
|
||||||
// these two can return GameState or InstanceState
|
|
||||||
RpcRequest::InstanceReady { instance_id } =>
|
|
||||||
Ok(instance_ready(&mut tx, account, instance_id)?),
|
|
||||||
RpcRequest::InstanceState { instance_id } =>
|
|
||||||
Ok(instance_state(&mut tx, instance_id)?),
|
|
||||||
RpcRequest::InstanceAbandon { instance_id } =>
|
|
||||||
Ok(instance_abandon(&mut tx, account, instance_id)?),
|
|
||||||
|
|
||||||
RpcRequest::VboxBuy { instance_id, group, index, construct_id } =>
|
|
||||||
Ok(RpcMessage::InstanceState(vbox_buy(&mut tx, account, instance_id, group, index, construct_id)?)),
|
|
||||||
|
|
||||||
RpcRequest::VboxApply { instance_id, construct_id, index } =>
|
|
||||||
Ok(RpcMessage::InstanceState(vbox_apply(&mut tx, account, instance_id, construct_id, index)?)),
|
|
||||||
|
|
||||||
RpcRequest::VboxCombine { instance_id, inv_indices, vbox_indices } =>
|
|
||||||
Ok(RpcMessage::InstanceState(vbox_combine(&mut tx, account, instance_id, inv_indices, vbox_indices)?)),
|
|
||||||
|
|
||||||
RpcRequest::VboxRefill { instance_id } =>
|
|
||||||
Ok(RpcMessage::InstanceState(vbox_refill(&mut tx, account, instance_id)?)),
|
|
||||||
|
|
||||||
RpcRequest::VboxRefund { instance_id, index } =>
|
|
||||||
Ok(RpcMessage::InstanceState(vbox_refund(&mut tx, account, instance_id, index)?)),
|
|
||||||
|
|
||||||
RpcRequest::VboxUnequip { instance_id, construct_id, target } =>
|
|
||||||
Ok(RpcMessage::InstanceState(vbox_unequip(&mut tx, account, instance_id, construct_id, target, None)?)),
|
|
||||||
|
|
||||||
RpcRequest::VboxUnequipApply { instance_id, construct_id, target, target_construct_id } =>
|
|
||||||
Ok(RpcMessage::InstanceState(vbox_unequip(&mut tx, account, instance_id, construct_id, target, Some(target_construct_id))?)),
|
|
||||||
|
|
||||||
RpcRequest::MtxConstructSpawn {} =>
|
|
||||||
Ok(RpcMessage::ConstructSpawn(mtx::new_construct(&mut tx, account)?)),
|
|
||||||
|
|
||||||
RpcRequest::MtxConstructApply { mtx, construct_id, name } =>
|
|
||||||
Ok(RpcMessage::AccountTeam(mtx::apply(&mut tx, account, mtx, construct_id, name)?)),
|
|
||||||
|
|
||||||
RpcRequest::MtxAccountApply { mtx } =>
|
|
||||||
Ok(RpcMessage::AccountState(mtx::account_apply(&mut tx, account, mtx)?)),
|
|
||||||
|
|
||||||
RpcRequest::MtxBuy { mtx } =>
|
|
||||||
Ok(RpcMessage::AccountShop(mtx::buy(&mut tx, account, mtx)?)),
|
|
||||||
|
|
||||||
RpcRequest::SubscriptionEnding { ending } =>
|
|
||||||
Ok(RpcMessage::SubscriptionState(payments::subscription_ending(&mut tx, &self.stripe, account, ending)?)),
|
|
||||||
|
|
||||||
_ => Err(format_err!("unknown request request={:?}", request)),
|
|
||||||
};
|
|
||||||
|
|
||||||
tx.commit()?;
|
|
||||||
res
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("request={:?} account={:?} duration={:?}", request, account.name, begin.elapsed());
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
warn!("{:?}", e);
|
|
||||||
Err(err_msg("invalid message"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is where last minute processing happens
|
// this is where last minute processing happens
|
||||||
// use it to modify outgoing messages, update subs, serialize in some way...
|
// use it to modify outgoing messages, update subs, serialize in some way...
|
||||||
fn send(&self, msg: RpcMessage) -> Result<(), Error> {
|
fn send(&self, msg: RpcMessage) -> Result<(), Error> {
|
||||||
let msg = match self.account {
|
let msg = match msg {
|
||||||
Some(ref a) => match msg {
|
RpcMessage::InstanceState(v) => RpcMessage::InstanceState(v.redact(self.id)),
|
||||||
RpcMessage::InstanceState(v) => RpcMessage::InstanceState(v.redact(a.id)),
|
RpcMessage::AccountInstances(v) =>
|
||||||
RpcMessage::AccountInstances(v) =>
|
RpcMessage::AccountInstances(v.into_iter().map(|i| i.redact(self.id)).collect()),
|
||||||
RpcMessage::AccountInstances(v.into_iter().map(|i| i.redact(a.id)).collect()),
|
RpcMessage::GameState(v) => RpcMessage::GameState(v.redact(self.id)),
|
||||||
RpcMessage::GameState(v) => RpcMessage::GameState(v.redact(a.id)),
|
_ => msg,
|
||||||
_ => msg,
|
|
||||||
},
|
|
||||||
None => msg,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.ws.send(msg).unwrap();
|
self.ws.send(msg).unwrap();
|
||||||
@ -351,50 +165,8 @@ impl Connection {
|
|||||||
// when it encounters errors
|
// when it encounters errors
|
||||||
impl Handler for Connection {
|
impl Handler for Connection {
|
||||||
fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
|
fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
|
||||||
info!("websocket connected account={:?}", self.account);
|
let db = self.pool.get().unwrap();
|
||||||
|
self.user.connected(&db, &self.events, &self.ws).unwrap();
|
||||||
// tell events we have connected
|
|
||||||
self.events.send(Event::Connect(self.id, self.account.clone(), self.ws.clone())).unwrap();
|
|
||||||
|
|
||||||
// if user logged in do some prep work
|
|
||||||
if let Some(ref a) = self.account {
|
|
||||||
self.send(RpcMessage::AccountState(a.clone())).unwrap();
|
|
||||||
self.events.send(Event::Subscribe(self.id, a.id)).unwrap();
|
|
||||||
|
|
||||||
// check if they have an image that needs to be generated
|
|
||||||
account::img_check(&a).unwrap();
|
|
||||||
|
|
||||||
let db = self.pool.get().unwrap();
|
|
||||||
let mut tx = db.transaction().unwrap();
|
|
||||||
|
|
||||||
// send account constructs
|
|
||||||
let account_constructs = account::constructs(&mut tx, a).unwrap();
|
|
||||||
self.send(RpcMessage::AccountConstructs(account_constructs)).unwrap();
|
|
||||||
|
|
||||||
// get account instances
|
|
||||||
// and send them to the client
|
|
||||||
let account_instances = account::account_instances(&mut tx, a).unwrap();
|
|
||||||
self.send(RpcMessage::AccountInstances(account_instances)).unwrap();
|
|
||||||
|
|
||||||
let shop = mtx::account_shop(&mut tx, &a).unwrap();
|
|
||||||
self.send(RpcMessage::AccountShop(shop)).unwrap();
|
|
||||||
|
|
||||||
let team = account::team(&mut tx, &a).unwrap();
|
|
||||||
self.send(RpcMessage::AccountTeam(team)).unwrap();
|
|
||||||
|
|
||||||
let wheel = account::chat_wheel(&db, a.id).unwrap();
|
|
||||||
self.send(RpcMessage::ChatWheel(wheel)).unwrap();
|
|
||||||
|
|
||||||
if let Some(instance) = account::tutorial(&mut tx, &a).unwrap() {
|
|
||||||
self.send(RpcMessage::InstanceState(instance)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// tx should do nothing
|
|
||||||
tx.commit().unwrap();
|
|
||||||
} else {
|
|
||||||
self.send(RpcMessage::Demo(demo().unwrap())).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,23 +176,9 @@ impl Handler for Connection {
|
|||||||
let begin = Instant::now();
|
let begin = Instant::now();
|
||||||
let db_connection = self.pool.get().unwrap();
|
let db_connection = self.pool.get().unwrap();
|
||||||
|
|
||||||
match self.receive(msg, &db_connection, begin) {
|
match self.user.receive(msg, &db_connection, begin, &self.events, &self.stripe) {
|
||||||
Ok(reply) => {
|
Ok(msg) => {
|
||||||
// if the user queries the state of something
|
self.user.send(msg, &self.events, &self.ws).unwrap();
|
||||||
// we tell events to push updates to them
|
|
||||||
match reply {
|
|
||||||
RpcMessage::AccountState(ref v) => {
|
|
||||||
self.account = Some(v.clone());
|
|
||||||
self.events.send(Event::Subscribe(self.id, v.id)).unwrap()
|
|
||||||
},
|
|
||||||
RpcMessage::GameState(ref v) =>
|
|
||||||
self.events.send(Event::Subscribe(self.id, v.id)).unwrap(),
|
|
||||||
RpcMessage::InstanceState(ref v) =>
|
|
||||||
self.events.send(Event::Subscribe(self.id, v.id)).unwrap(),
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.send(reply).unwrap();
|
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("{:?}", e);
|
warn!("{:?}", e);
|
||||||
@ -434,7 +192,7 @@ impl Handler for Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_close(&mut self, _: CloseCode, _: &str) {
|
fn on_close(&mut self, _: CloseCode, _: &str) {
|
||||||
info!("websocket disconnected account={:?}", self.account);
|
info!("websocket disconnected id={:?}", self.id);
|
||||||
self.events.send(Event::Disconnect(self.id)).unwrap();
|
self.events.send(Event::Disconnect(self.id)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -462,21 +220,19 @@ 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.account = Some(a),
|
Ok(a) => self.user = Box::new(Authorised { id: a.id, account: a }),
|
||||||
Err(_) => return unauth(),
|
Err(_) => return unauth(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(pool: PgPool, events_tx: CbSender<Event>, stripe: StripeClient) {
|
pub fn start(pool: PgPool, events_tx: CbSender<Event>, stripe: StripeClient) {
|
||||||
let mut rng = thread_rng();
|
let _ws = Builder::new()
|
||||||
|
|
||||||
let ws = Builder::new()
|
|
||||||
.with_settings(Settings {
|
.with_settings(Settings {
|
||||||
max_connections: 10_000,
|
max_connections: 10_000,
|
||||||
..Settings::default()
|
..Settings::default()
|
||||||
@ -506,14 +262,17 @@ pub fn start(pool: PgPool, events_tx: CbSender<Event>, stripe: StripeClient) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let anon_account = Account::anonymous();
|
||||||
|
let id = anon_account.id;
|
||||||
|
|
||||||
DeflateHandler::new(
|
DeflateHandler::new(
|
||||||
Connection {
|
Connection {
|
||||||
id: rng.gen::<usize>(),
|
id,
|
||||||
account: None,
|
|
||||||
ws: tx,
|
ws: tx,
|
||||||
pool: pool.clone(),
|
pool: pool.clone(),
|
||||||
stripe: stripe.clone(),
|
stripe: stripe.clone(),
|
||||||
events: events_tx.clone(),
|
events: events_tx.clone(),
|
||||||
|
user: Box::new(Anonymous { id, account: anon_account, game: None, instance: None })
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
160
server/src/user_anonymous.rs
Normal file
160
server/src/user_anonymous.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use std::time::Instant;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use failure::Error;
|
||||||
|
use failure::err_msg;
|
||||||
|
|
||||||
|
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;
|
||||||
|
use mnml_core::item::item_info;
|
||||||
|
use mnml_core::instance::Instance;
|
||||||
|
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
|
pub struct Anonymous {
|
||||||
|
pub account: Account,
|
||||||
|
pub id: Uuid,
|
||||||
|
pub instance: Option<Instance>,
|
||||||
|
pub game: Option<Game>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User for Anonymous {
|
||||||
|
fn send(&mut self, msg: RpcMessage, _events: &CbSender<Event>, ws: &CbSender<RpcMessage>) -> Result<(), Error> {
|
||||||
|
// if the user queries the state of something
|
||||||
|
// we tell events to push updates to them
|
||||||
|
match msg {
|
||||||
|
RpcMessage::GameState(ref v) =>
|
||||||
|
self.game = Some(v.clone()),
|
||||||
|
RpcMessage::InstanceState(ref v) =>
|
||||||
|
self.instance = Some(v.clone()),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.send(msg)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connected(&mut self, _db: &Db, events: &CbSender<Event>, ws: &CbSender<RpcMessage>) -> Result<(), Error> {
|
||||||
|
info!("anonymous connection");
|
||||||
|
|
||||||
|
self.send(RpcMessage::AccountState(self.account.clone()), events, ws)?;
|
||||||
|
self.send(RpcMessage::StartTutorial(()), events, ws)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive(&mut self, data: Vec<u8>, _db: &Db, _begin: Instant, _events: &CbSender<Event>, _stripe: &StripeClient) -> Result<RpcMessage, Error> {
|
||||||
|
match from_slice::<RpcRequest>(&data) {
|
||||||
|
Ok(v) => {
|
||||||
|
let get_instance = || {
|
||||||
|
match self.instance {
|
||||||
|
Some(ref i) => Ok(i.clone()),
|
||||||
|
None => return Err(err_msg("instance missing")),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let get_game = || {
|
||||||
|
match self.game {
|
||||||
|
Some(ref i) => Ok(i.clone()),
|
||||||
|
None => return Err(err_msg("game missing")),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match v {
|
||||||
|
RpcRequest::Ping {} => return Ok(RpcMessage::Pong(())),
|
||||||
|
|
||||||
|
RpcRequest::ItemInfo {} => return Ok(RpcMessage::ItemInfo(item_info())),
|
||||||
|
RpcRequest::AccountInstances {} => return Ok(RpcMessage::Pong(())),
|
||||||
|
|
||||||
|
RpcRequest::InstancePractice {} =>
|
||||||
|
Ok(RpcMessage::InstanceState(pg::instance_demo(&self.account)?)),
|
||||||
|
|
||||||
|
RpcRequest::InstanceReady { instance_id: _ } => {
|
||||||
|
match get_instance()?.player_ready(self.account.id)? {
|
||||||
|
Some(g) => Ok(RpcMessage::GameState(g)),
|
||||||
|
None => Ok(RpcMessage::InstanceState(get_instance()?)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
RpcRequest::InstanceState { instance_id: _ } =>
|
||||||
|
Ok(RpcMessage::InstanceState(get_instance()?)),
|
||||||
|
|
||||||
|
RpcRequest::InstanceAbandon { instance_id: _ } => {
|
||||||
|
let mut instance = get_instance()?;
|
||||||
|
instance.finish();
|
||||||
|
Ok(RpcMessage::InstanceState(instance))
|
||||||
|
},
|
||||||
|
|
||||||
|
RpcRequest::VboxBuy { instance_id: _, group, index, construct_id } =>
|
||||||
|
Ok(RpcMessage::InstanceState(get_instance()?.vbox_buy(self.account.id, group, index, construct_id)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxApply { instance_id: _, construct_id, index } =>
|
||||||
|
Ok(RpcMessage::InstanceState(get_instance()?.vbox_apply(self.account.id, index, construct_id)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxCombine { instance_id: _, inv_indices, vbox_indices } =>
|
||||||
|
Ok(RpcMessage::InstanceState(get_instance()?.vbox_combine(self.account.id, inv_indices, vbox_indices)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxRefill { instance_id: _ } =>
|
||||||
|
Ok(RpcMessage::InstanceState(get_instance()?.vbox_refill(self.account.id)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxRefund { instance_id: _, index } =>
|
||||||
|
Ok(RpcMessage::InstanceState(get_instance()?.vbox_refund(self.account.id, index)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxUnequip { instance_id: _, construct_id, target } =>
|
||||||
|
Ok(RpcMessage::InstanceState(get_instance()?.vbox_unequip(self.account.id, target, construct_id, None)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxUnequipApply { instance_id: _, construct_id, target, target_construct_id } =>
|
||||||
|
Ok(RpcMessage::InstanceState(get_instance()?.vbox_unequip(self.account.id, target, construct_id, Some(target_construct_id))?)),
|
||||||
|
|
||||||
|
RpcRequest::GameState { id: _ } =>
|
||||||
|
Ok(RpcMessage::GameState(get_game()?)),
|
||||||
|
|
||||||
|
RpcRequest::GameSkill { game_id: _, construct_id, target_construct_id, skill } => {
|
||||||
|
let mut game = get_game()?;
|
||||||
|
game.add_skill(self.account.id, construct_id, target_construct_id, skill)?;
|
||||||
|
Ok(RpcMessage::GameState(game))
|
||||||
|
},
|
||||||
|
|
||||||
|
RpcRequest::GameSkillClear { game_id: _ } => {
|
||||||
|
let mut game = get_game()?;
|
||||||
|
game.clear_skill(self.account.id)?;
|
||||||
|
Ok(RpcMessage::GameState(game))
|
||||||
|
},
|
||||||
|
|
||||||
|
RpcRequest::GameReady { id: _ } => {
|
||||||
|
let mut game = get_game()?;
|
||||||
|
game.player_ready(self.account.id)?;
|
||||||
|
if game.skill_phase_finished() {
|
||||||
|
game = game.resolve_phase_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RpcMessage::GameState(game))
|
||||||
|
},
|
||||||
|
|
||||||
|
RpcRequest::GameConcede { game_id: _ } =>
|
||||||
|
Ok(RpcMessage::GameState(get_game()?.concede(self.account.id)?)),
|
||||||
|
|
||||||
|
RpcRequest::GameOfferDraw { game_id: _ } =>
|
||||||
|
Ok(RpcMessage::GameState(get_game()?.offer_draw(self.account.id)?)),
|
||||||
|
|
||||||
|
_ => Err(format_err!("unhandled anonymous request request={:?}", v)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{:?}", e);
|
||||||
|
Err(err_msg("invalid message"))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
265
server/src/user_authenticated.rs
Normal file
265
server/src/user_authenticated.rs
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
use mnml_core::mob::anim_test_game;
|
||||||
|
use mnml_core::item::item_info;
|
||||||
|
use std::time::Instant;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use failure::Error;
|
||||||
|
use failure::err_msg;
|
||||||
|
|
||||||
|
use crossbeam_channel::{Sender as CbSender};
|
||||||
|
use stripe::{Client as StripeClient};
|
||||||
|
|
||||||
|
use serde_cbor::{from_slice};
|
||||||
|
|
||||||
|
use pg::{
|
||||||
|
game_concede,
|
||||||
|
game_offer_draw,
|
||||||
|
game_ready,
|
||||||
|
game_skill,
|
||||||
|
game_skill_clear,
|
||||||
|
game_state,
|
||||||
|
instance_abandon,
|
||||||
|
instance_practice,
|
||||||
|
instance_ready,
|
||||||
|
instance_state,
|
||||||
|
vbox_apply,
|
||||||
|
vbox_buy,
|
||||||
|
vbox_combine,
|
||||||
|
vbox_refill,
|
||||||
|
vbox_refund,
|
||||||
|
vbox_unequip,
|
||||||
|
};
|
||||||
|
|
||||||
|
use account::{Account};
|
||||||
|
use account;
|
||||||
|
use events::{Event};
|
||||||
|
|
||||||
|
use mtx;
|
||||||
|
use mail;
|
||||||
|
use payments;
|
||||||
|
use pg::{Db};
|
||||||
|
use rpc::{RpcMessage, RpcRequest, User};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
|
pub struct Authorised {
|
||||||
|
pub account: Account,
|
||||||
|
pub id: Uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User for Authorised {
|
||||||
|
fn send(&mut self, msg: RpcMessage, events: &CbSender<Event>, ws: &CbSender<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))?
|
||||||
|
},
|
||||||
|
RpcMessage::GameState(ref v) =>
|
||||||
|
events.send(Event::Subscribe(self.id, v.id))?,
|
||||||
|
RpcMessage::InstanceState(ref v) =>
|
||||||
|
events.send(Event::Subscribe(self.id, v.id))?,
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.send(msg)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connected(&mut self, db: &Db, events: &CbSender<Event>, ws: &CbSender<RpcMessage>) -> Result<(), Error> {
|
||||||
|
info!("authenticated connection account={:?}", self.account);
|
||||||
|
let a = &self.account;
|
||||||
|
|
||||||
|
ws.send(RpcMessage::AccountAuthenticated(a.clone()))?;
|
||||||
|
// tell events we have connected
|
||||||
|
events.send(Event::Connect(self.id, a.clone(), ws.clone()))?;
|
||||||
|
|
||||||
|
ws.send(RpcMessage::AccountState(a.clone()))?;
|
||||||
|
events.send(Event::Subscribe(self.id, a.id))?;
|
||||||
|
|
||||||
|
// check if they have an image that needs to be generated
|
||||||
|
account::img_check(&a)?;
|
||||||
|
|
||||||
|
let mut tx = db.transaction()?;
|
||||||
|
|
||||||
|
// send account constructs
|
||||||
|
let account_constructs = account::constructs(&mut tx, &a)?;
|
||||||
|
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))?;
|
||||||
|
|
||||||
|
let shop = mtx::account_shop(&mut tx, &a)?;
|
||||||
|
ws.send(RpcMessage::AccountShop(shop))?;
|
||||||
|
|
||||||
|
let team = account::team(&mut tx, &a)?;
|
||||||
|
ws.send(RpcMessage::AccountTeam(team))?;
|
||||||
|
|
||||||
|
let wheel = account::chat_wheel(&db, a.id)?;
|
||||||
|
ws.send(RpcMessage::ChatWheel(wheel))?;
|
||||||
|
|
||||||
|
if let Some(instance) = account::tutorial(&mut tx, &a)? {
|
||||||
|
ws.send(RpcMessage::InstanceState(instance))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tx should do nothing
|
||||||
|
tx.commit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive(&mut self, data: Vec<u8>, db: &Db, begin: Instant, events: &CbSender<Event>, stripe: &StripeClient) -> Result<RpcMessage, Error> {
|
||||||
|
// cast the msg to this type to receive method name
|
||||||
|
match from_slice::<RpcRequest>(&data) {
|
||||||
|
Ok(v) => {
|
||||||
|
let request = v.clone();
|
||||||
|
let response = match v {
|
||||||
|
RpcRequest::Ping {} => return Ok(RpcMessage::Pong(())),
|
||||||
|
RpcRequest::ItemInfo {} => return Ok(RpcMessage::ItemInfo(item_info())),
|
||||||
|
RpcRequest::DevResolve { skill } =>
|
||||||
|
return Ok(RpcMessage::GameState(anim_test_game(skill))),
|
||||||
|
|
||||||
|
RpcRequest::InstanceQueue {} => {
|
||||||
|
events.send(Event::Queue(self.id))?;
|
||||||
|
Ok(RpcMessage::QueueRequested(()))
|
||||||
|
},
|
||||||
|
RpcRequest::InstanceInvite {} => {
|
||||||
|
events.send(Event::Invite(self.id))?;
|
||||||
|
Ok(RpcMessage::InviteRequested(()))
|
||||||
|
},
|
||||||
|
RpcRequest::InstanceJoin { code } => {
|
||||||
|
events.send(Event::Join(self.id, code))?;
|
||||||
|
Ok(RpcMessage::Joining(()))
|
||||||
|
},
|
||||||
|
RpcRequest::InstanceLeave {} => {
|
||||||
|
events.send(Event::Leave(self.id))?;
|
||||||
|
Ok(RpcMessage::Processing(()))
|
||||||
|
},
|
||||||
|
|
||||||
|
RpcRequest::InstanceChat { instance_id, index } => {
|
||||||
|
if !self.account.subscribed {
|
||||||
|
return Err(err_msg("subscribe to unlock chat"))
|
||||||
|
}
|
||||||
|
|
||||||
|
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()))?;
|
||||||
|
} else {
|
||||||
|
return Err(err_msg("invalid chat index"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RpcMessage::Processing(()))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// all good, let's make a tx and process
|
||||||
|
let mut tx = db.transaction()?;
|
||||||
|
|
||||||
|
let res = match v {
|
||||||
|
RpcRequest::AccountState {} =>
|
||||||
|
Ok(RpcMessage::AccountState(self.account.clone())),
|
||||||
|
RpcRequest::AccountConstructs {} =>
|
||||||
|
Ok(RpcMessage::AccountConstructs(account::constructs(&mut tx, &self.account)?)),
|
||||||
|
RpcRequest::AccountInstances {} =>
|
||||||
|
Ok(RpcMessage::AccountInstances(account::account_instances(&mut tx, &self.account)?)),
|
||||||
|
RpcRequest::AccountSetTeam { ids } =>
|
||||||
|
Ok(RpcMessage::AccountTeam(account::set_team(&mut tx, &self.account, ids)?)),
|
||||||
|
|
||||||
|
RpcRequest::EmailState {} =>
|
||||||
|
Ok(RpcMessage::EmailState(mail::select_account(&db, self.account.id)?)),
|
||||||
|
|
||||||
|
RpcRequest::SubscriptionState {} =>
|
||||||
|
Ok(RpcMessage::SubscriptionState(payments::account_subscription(db, stripe, &self.account)?)),
|
||||||
|
|
||||||
|
// RpcRequest::AccountShop {} =>
|
||||||
|
// Ok(RpcMessage::AccountShop(mtx::account_shop(&mut tx, &account)?)),
|
||||||
|
|
||||||
|
// RpcRequest::ConstructDelete" => handle_construct_delete(data, &mut tx, account),
|
||||||
|
|
||||||
|
RpcRequest::GameState { id } =>
|
||||||
|
Ok(RpcMessage::GameState(game_state(&mut tx, &self.account, id)?)),
|
||||||
|
|
||||||
|
RpcRequest::GameSkill { game_id, construct_id, target_construct_id, skill } =>
|
||||||
|
Ok(RpcMessage::GameState(game_skill(&mut tx, &self.account, game_id, construct_id, target_construct_id, skill)?)),
|
||||||
|
|
||||||
|
RpcRequest::GameSkillClear { game_id } =>
|
||||||
|
Ok(RpcMessage::GameState(game_skill_clear(&mut tx, &self.account, game_id)?)),
|
||||||
|
|
||||||
|
RpcRequest::GameReady { id } =>
|
||||||
|
Ok(RpcMessage::GameState(game_ready(&mut tx, &self.account, id)?)),
|
||||||
|
|
||||||
|
RpcRequest::GameConcede { game_id } =>
|
||||||
|
Ok(RpcMessage::GameState(game_concede(&mut tx, &self.account, game_id)?)),
|
||||||
|
|
||||||
|
RpcRequest::GameOfferDraw { game_id } =>
|
||||||
|
Ok(RpcMessage::GameState(game_offer_draw(&mut tx, &self.account, game_id)?)),
|
||||||
|
|
||||||
|
RpcRequest::InstancePractice {} =>
|
||||||
|
Ok(RpcMessage::InstanceState(instance_practice(&mut tx, &self.account)?)),
|
||||||
|
|
||||||
|
// these two can return GameState or InstanceState
|
||||||
|
RpcRequest::InstanceReady { instance_id } =>
|
||||||
|
Ok(instance_ready(&mut tx, &self.account, instance_id)?),
|
||||||
|
RpcRequest::InstanceState { instance_id } =>
|
||||||
|
Ok(instance_state(&mut tx, instance_id)?),
|
||||||
|
RpcRequest::InstanceAbandon { instance_id } =>
|
||||||
|
Ok(instance_abandon(&mut tx, &self.account, instance_id)?),
|
||||||
|
|
||||||
|
RpcRequest::VboxBuy { instance_id, group, index, construct_id } =>
|
||||||
|
Ok(RpcMessage::InstanceState(vbox_buy(&mut tx, &self.account, instance_id, group, index, construct_id)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxApply { instance_id, construct_id, index } =>
|
||||||
|
Ok(RpcMessage::InstanceState(vbox_apply(&mut tx, &self.account, instance_id, construct_id, index)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxCombine { instance_id, inv_indices, vbox_indices } =>
|
||||||
|
Ok(RpcMessage::InstanceState(vbox_combine(&mut tx, &self.account, instance_id, inv_indices, vbox_indices)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxRefill { instance_id } =>
|
||||||
|
Ok(RpcMessage::InstanceState(vbox_refill(&mut tx, &self.account, instance_id)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxRefund { instance_id, index } =>
|
||||||
|
Ok(RpcMessage::InstanceState(vbox_refund(&mut tx, &self.account, instance_id, index)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxUnequip { instance_id, construct_id, target } =>
|
||||||
|
Ok(RpcMessage::InstanceState(vbox_unequip(&mut tx, &self.account, instance_id, construct_id, target, None)?)),
|
||||||
|
|
||||||
|
RpcRequest::VboxUnequipApply { instance_id, construct_id, target, target_construct_id } =>
|
||||||
|
Ok(RpcMessage::InstanceState(vbox_unequip(&mut tx, &self.account, instance_id, construct_id, target, Some(target_construct_id))?)),
|
||||||
|
|
||||||
|
RpcRequest::MtxConstructSpawn {} =>
|
||||||
|
Ok(RpcMessage::ConstructSpawn(mtx::new_construct(&mut tx, &self.account)?)),
|
||||||
|
|
||||||
|
RpcRequest::MtxConstructApply { mtx, construct_id, name } =>
|
||||||
|
Ok(RpcMessage::AccountTeam(mtx::apply(&mut tx, &self.account, mtx, construct_id, name)?)),
|
||||||
|
|
||||||
|
RpcRequest::MtxAccountApply { mtx } =>
|
||||||
|
Ok(RpcMessage::AccountState(mtx::account_apply(&mut tx, &self.account, mtx)?)),
|
||||||
|
|
||||||
|
RpcRequest::MtxBuy { mtx } =>
|
||||||
|
Ok(RpcMessage::AccountShop(mtx::buy(&mut tx, &self.account, mtx)?)),
|
||||||
|
|
||||||
|
RpcRequest::SubscriptionEnding { ending } =>
|
||||||
|
Ok(RpcMessage::SubscriptionState(payments::subscription_ending(&mut tx, stripe, &self.account, ending)?)),
|
||||||
|
|
||||||
|
_ => Err(format_err!("unknown request request={:?}", request)),
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.commit()?;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("request={:?} account={:?} duration={:?}", request, self.account.name, begin.elapsed());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{:?}", e);
|
||||||
|
Err(err_msg("invalid message"))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,5 @@
|
|||||||
use std::time::{Duration};
|
use std::time::{Duration};
|
||||||
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crossbeam_channel::{tick, Sender, Receiver};
|
use crossbeam_channel::{tick, Sender, Receiver};
|
||||||
|
|
||||||
// Db Commons
|
// Db Commons
|
||||||
@ -24,13 +22,12 @@ use pg::{
|
|||||||
pvp,
|
pvp,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Id = usize;
|
|
||||||
type Pair = (PvpRequest, PvpRequest);
|
type Pair = (PvpRequest, PvpRequest);
|
||||||
|
|
||||||
pub enum GameEvent {
|
pub enum GameEvent {
|
||||||
Upkeep,
|
Upkeep,
|
||||||
|
|
||||||
Finish(Uuid),
|
// Finish(Uuid),
|
||||||
|
|
||||||
Match(Pair),
|
Match(Pair),
|
||||||
}
|
}
|
||||||
@ -76,10 +73,10 @@ impl Warden {
|
|||||||
match msg {
|
match msg {
|
||||||
GameEvent::Upkeep => self.on_upkeep(),
|
GameEvent::Upkeep => self.on_upkeep(),
|
||||||
GameEvent::Match(pair) => self.on_match(pair),
|
GameEvent::Match(pair) => self.on_match(pair),
|
||||||
GameEvent::Finish(id) => {
|
// GameEvent::Finish(id) => {
|
||||||
info!("game finished id={:?}", id);
|
// info!("game finished id={:?}", id);
|
||||||
Ok(())
|
// Ok(())
|
||||||
},
|
// },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +128,7 @@ impl Warden {
|
|||||||
fn fetch_games(mut tx: Transaction) -> Result<Transaction, Error> {
|
fn fetch_games(mut tx: Transaction) -> Result<Transaction, Error> {
|
||||||
let games = games_need_upkeep(&mut tx)?;
|
let games = games_need_upkeep(&mut tx)?;
|
||||||
|
|
||||||
for mut game in games {
|
for game in games {
|
||||||
let game = game.upkeep();
|
let game = game.upkeep();
|
||||||
match game_update(&mut tx, &game) {
|
match game_update(&mut tx, &game) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
@ -146,7 +143,7 @@ fn fetch_games(mut tx: Transaction) -> Result<Transaction, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_instances(mut tx: Transaction) -> Result<Transaction, Error> {
|
fn fetch_instances(mut tx: Transaction) -> Result<Transaction, Error> {
|
||||||
for mut instance in instances_need_upkeep(&mut tx)? {
|
for instance in instances_need_upkeep(&mut tx)? {
|
||||||
let (instance, new_game) = instance.upkeep();
|
let (instance, new_game) = instance.upkeep();
|
||||||
|
|
||||||
if let Some(game) = new_game {
|
if let Some(game) = new_game {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user