Merge branch 'develop' into face-imgs
This commit is contained in:
commit
d8d5528469
17
CHANGELOG.md
17
CHANGELOG.md
@ -2,6 +2,23 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [1.6.6] - 2019-10-27
|
||||||
|
# Added
|
||||||
|
- Offering of draws
|
||||||
|
- Neither player receives a point if they agree to a draw
|
||||||
|
- Bots automatically agree to draws
|
||||||
|
|
||||||
|
## [1.6.5] - 2019-10-25
|
||||||
|
# Fixed
|
||||||
|
- Stripe being blocked no longer causes unrecoverable error
|
||||||
|
- Automatic ready up is now throttled after abandons
|
||||||
|
- Player width styling
|
||||||
|
|
||||||
|
# Changed
|
||||||
|
- Improved wiggle animation
|
||||||
|
- Intercept is now considered defensive by bots
|
||||||
|
- Password restrictions relaxed
|
||||||
|
|
||||||
## [1.6.4] - 2019-10-24
|
## [1.6.4] - 2019-10-24
|
||||||
### Changed
|
### Changed
|
||||||
- Animations processing on client side reduced.
|
- Animations processing on client side reduced.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-client",
|
"name": "mnml-client",
|
||||||
"version": "1.6.5",
|
"version": "1.6.6",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -151,3 +151,21 @@ button {
|
|||||||
border-color: @green;
|
border-color: @green;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes rgb {
|
||||||
|
0% {
|
||||||
|
color: @red;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
color: @white;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
color: @blue;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
color: @white;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
color: @green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -144,7 +144,15 @@ aside {
|
|||||||
&:active, &.confirming {
|
&:active, &.confirming {
|
||||||
background: @red;
|
background: @red;
|
||||||
color: black;
|
color: black;
|
||||||
border: 2px solid black;
|
border: 2px solid @red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.draw:not([disabled]) {
|
||||||
|
&:active, &.confirming {
|
||||||
|
background: @gray-hover;
|
||||||
|
color: black;
|
||||||
|
border: 2px solid @gray-hover;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -437,11 +437,12 @@
|
|||||||
#targeting, .resolving-skill {
|
#targeting, .resolving-skill {
|
||||||
width: calc(100% - 1em);
|
width: calc(100% - 1em);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player {
|
||||||
|
width: calc(100% - 1em);
|
||||||
|
bottom: 3em;
|
||||||
|
height: calc(50% - 3em);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.player {
|
|
||||||
width: calc(100% - 1em);
|
|
||||||
bottom: 3em;
|
|
||||||
height: calc(50% - 3em);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,6 +90,11 @@
|
|||||||
.login {
|
.login {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
|
|
||||||
|
.terms {
|
||||||
|
display: inline;
|
||||||
|
margin: 0 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -51,6 +51,11 @@
|
|||||||
color: @yellow;
|
color: @yellow;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.name.subscriber {
|
||||||
|
// animation: rgb 4s cubic-bezier(0.5, 0, 0.5, 1) 0s infinite alternate;
|
||||||
|
// font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat {
|
.chat {
|
||||||
|
|||||||
@ -143,7 +143,7 @@ button, input {
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
text-decoration: none;
|
// text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-client",
|
"name": "mnml-client",
|
||||||
"version": "1.6.5",
|
"version": "1.6.6",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "parcel watch index.html --out-dir /var/lib/mnml/public/current",
|
"start": "parcel watch index.html --out-dir /var/lib/mnml/public/current",
|
||||||
"anims": "parcel watch animations.html --no-hmr --out-dir /var/lib/mnml/public/current",
|
"anims": "parcel watch animations.html --no-hmr --out-dir /var/lib/mnml/public/current",
|
||||||
"build": "parcel build index.html",
|
"build": "parcel build index.html --no-source-maps",
|
||||||
"scss": "node-sass --watch assets/scss -o assets/styles",
|
"scss": "node-sass --watch assets/scss -o assets/styles",
|
||||||
"lint": "eslint --fix --ext .jsx src/",
|
"lint": "eslint --fix --ext .jsx src/",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
|||||||
@ -15,10 +15,6 @@ const registerEvents = require('./events');
|
|||||||
|
|
||||||
const Mnml = require('./components/mnml');
|
const Mnml = require('./components/mnml');
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'development') {
|
|
||||||
LogRocket.init('yh0dy3/mnml');
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripeKey() {
|
function stripeKey() {
|
||||||
if (window.location.host === 'mnml.gg') return 'pk_live_fQGrL1uWww2ot8W1G7vTySAv004ygmnMXq';
|
if (window.location.host === 'mnml.gg') return 'pk_live_fQGrL1uWww2ot8W1G7vTySAv004ygmnMXq';
|
||||||
return 'pk_test_Cb49tTqTXpzk7nEmlGzRrNJg00AU0aNZDj';
|
return 'pk_test_Cb49tTqTXpzk7nEmlGzRrNJg00AU0aNZDj';
|
||||||
@ -40,9 +36,12 @@ events.setWs(ws);
|
|||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<StripeProvider apiKey={stripeKey()}>
|
{window.Stripe
|
||||||
<Mnml />
|
? <StripeProvider apiKey={stripeKey()}>
|
||||||
</StripeProvider>
|
<Mnml />
|
||||||
|
</StripeProvider>
|
||||||
|
: <Mnml />
|
||||||
|
}
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -101,11 +101,13 @@ function AccountBox(args) {
|
|||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
const nameClass = `name ${account.subscribed ? 'subscriber' : ''}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class='player-box bottom'>
|
<div class='player-box bottom'>
|
||||||
<div class="msg"> </div>
|
<div class="msg"> </div>
|
||||||
<StateAccountAvatar />
|
<StateAccountAvatar />
|
||||||
<div class="name">{account.name}</div>
|
<div class={nameClass}>{account.name}</div>
|
||||||
<div class="score"> </div>
|
<div class="score"> </div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,22 +1,24 @@
|
|||||||
const anime = require('animejs').default;
|
const anime = require('animejs').default;
|
||||||
|
|
||||||
function wiggle(id, idle) {
|
function wiggle(id, idle) {
|
||||||
|
if (!idle) return true;
|
||||||
const duration = 300;
|
const duration = 300;
|
||||||
const target = document.getElementById(id);
|
const target = document.getElementById(id);
|
||||||
const x = window.innerWidth * 0.01 * (Math.round(Math.random()) ? Math.random() : -Math.random());
|
const x = window.innerWidth * 0.01 * (Math.round(Math.random()) ? Math.random() : -Math.random());
|
||||||
const y = window.innerHeight * 0.01 * (Math.round(Math.random()) ? Math.random() : -Math.random());
|
const y = window.innerHeight * 0.01 * (Math.round(Math.random()) ? Math.random() : -Math.random());
|
||||||
|
|
||||||
|
const originalX = parseFloat(idle.animations[0].currentValue);
|
||||||
|
const originalY = parseFloat(idle.animations[1].currentValue);
|
||||||
// console.log(x, y);
|
// console.log(x, y);
|
||||||
return anime({
|
return anime({
|
||||||
targets: target,
|
targets: target,
|
||||||
rotate: 0,
|
translateX: [originalX + x, originalX - x, originalX],
|
||||||
translateX: [x, -x, 0],
|
translateY: [originalY + y, originalY - y, originalY],
|
||||||
translateY: [y, -y, 0],
|
|
||||||
duration,
|
duration,
|
||||||
easing: 'easeInOutSine',
|
easing: 'easeInOutSine',
|
||||||
// direction: 'alternate',
|
// direction: 'alternate',
|
||||||
begin: idle.pause,
|
begin: idle.pause,
|
||||||
complete: idle.restart,
|
complete: idle.play,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -75,7 +75,7 @@ function GameCtrlBtns(args) {
|
|||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
if (!game) return false;
|
if (!game) return false;
|
||||||
const finished = game.phase === 'Finish';
|
const finished = game.phase === 'Finished';
|
||||||
|
|
||||||
function quitClick() {
|
function quitClick() {
|
||||||
getInstanceState();
|
getInstanceState();
|
||||||
|
|||||||
@ -8,16 +8,23 @@ const addState = connect(
|
|||||||
const {
|
const {
|
||||||
ws,
|
ws,
|
||||||
game,
|
game,
|
||||||
|
account,
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
function sendAbandon() {
|
function sendAbandon() {
|
||||||
return ws.sendInstanceAbandon(game.instance);
|
return ws.sendInstanceAbandon(game.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendDraw() {
|
||||||
|
return ws.sendGameOfferDraw(game.id);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
game,
|
game,
|
||||||
|
account,
|
||||||
|
|
||||||
sendAbandon,
|
sendAbandon,
|
||||||
|
sendDraw,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
function receiveDispatch(dispatch) {
|
function receiveDispatch(dispatch) {
|
||||||
@ -34,13 +41,18 @@ const addState = connect(
|
|||||||
function GameCtrlTopBtns(args) {
|
function GameCtrlTopBtns(args) {
|
||||||
const {
|
const {
|
||||||
game,
|
game,
|
||||||
|
account,
|
||||||
|
|
||||||
leave,
|
leave,
|
||||||
sendAbandon,
|
sendAbandon,
|
||||||
|
sendDraw,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
const finished = game && game.phase === 'Finished';
|
const finished = game && game.phase === 'Finished';
|
||||||
const { abandonState } = this.state;
|
const { abandonState, drawState } = this.state;
|
||||||
|
|
||||||
|
const player = game.players.find(p => p.id === account.id);
|
||||||
|
const drawOffered = player && player.draw_offered;
|
||||||
|
|
||||||
const abandonStateTrue = e => {
|
const abandonStateTrue = e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -48,16 +60,29 @@ function GameCtrlTopBtns(args) {
|
|||||||
setTimeout(() => this.setState({ abandonState: false }), 2000);
|
setTimeout(() => this.setState({ abandonState: false }), 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const drawStateTrue = e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.setState({ drawState: true });
|
||||||
|
setTimeout(() => this.setState({ drawState: false }), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
const abandonClasses = `abandon ${abandonState ? 'confirming' : ''}`;
|
const abandonClasses = `abandon ${abandonState ? 'confirming' : ''}`;
|
||||||
const abandonText = abandonState ? 'Confirm' : 'Abandon';
|
const abandonText = abandonState ? 'Confirm' : 'Abandon';
|
||||||
const abandonAction = abandonState ? sendAbandon : abandonStateTrue;
|
const abandonAction = abandonState ? sendAbandon : abandonStateTrue;
|
||||||
|
|
||||||
const abandonBtn = <button class={abandonClasses} disabled={finished} onClick={abandonAction}>{abandonText}</button>;
|
const abandonBtn = <button class={abandonClasses} disabled={finished} onClick={abandonAction}>{abandonText}</button>;
|
||||||
const leaveBtn = <button class='abandon confirming' onClick={leave}>Leave</button>;
|
|
||||||
|
const drawClasses = `draw ${drawState || drawOffered ? 'confirming' : ''}`;
|
||||||
|
const drawText = drawOffered
|
||||||
|
? 'Offered'
|
||||||
|
: drawState ? 'Draw' : 'Offer';
|
||||||
|
const drawAction = drawState ? sendDraw : drawStateTrue;
|
||||||
|
const drawBtn = <button class={drawClasses} disabled={finished || drawOffered} onClick={drawAction}>{drawText}</button>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="instance-ctrl-btns">
|
<div class="instance-ctrl-btns">
|
||||||
{finished ? leaveBtn : abandonBtn}
|
{abandonBtn}
|
||||||
|
{drawBtn}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,7 @@ function GameFooter(props) {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const end = Date.parse(game.phase_end);
|
const end = Date.parse(game.phase_end);
|
||||||
const timerPct = ((now - zero) / (end - zero) * 100);
|
const timerPct = ((now - zero) / (end - zero) * 100);
|
||||||
const displayPct = game.phase === 'Finish' || !game.phase_end
|
const displayPct = game.phase === 'Finished' || !game.phase_end
|
||||||
? 0
|
? 0
|
||||||
: Math.min(timerPct, 100);
|
: Math.min(timerPct, 100);
|
||||||
|
|
||||||
@ -108,7 +108,7 @@ function GameFooter(props) {
|
|||||||
return (
|
return (
|
||||||
<footer>
|
<footer>
|
||||||
{timer}
|
{timer}
|
||||||
{game.phase === 'Finish' && quitBtn }
|
{game.phase === 'Finished' && quitBtn }
|
||||||
{game.phase === 'Skill' && readyBtn }
|
{game.phase === 'Skill' && readyBtn }
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,8 +3,6 @@ const { connect } = require('preact-redux');
|
|||||||
|
|
||||||
const { errorToast, infoToast } = require('../utils');
|
const { errorToast, infoToast } = require('../utils');
|
||||||
|
|
||||||
const AccountBox = require('./account.box');
|
|
||||||
|
|
||||||
const addState = connect(
|
const addState = connect(
|
||||||
function receiveState(state) {
|
function receiveState(state) {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -203,6 +203,7 @@ function Play(args) {
|
|||||||
<div>
|
<div>
|
||||||
Join our Discord server to find opponents and talk to the devs. <br />
|
Join our Discord server to find opponents and talk to the devs. <br />
|
||||||
Message <b>@ntr</b> or <b>@mashy</b> for some credits to get started.<br />
|
Message <b>@ntr</b> or <b>@mashy</b> for some credits to get started.<br />
|
||||||
|
<a href='https://www.youtube.com/watch?v=VtZLlkpJuS8'>Tutorial Playthrough on YouTube</a>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -68,24 +68,33 @@ function Scoreboard(args) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const winner = player.score === 'Win';
|
const winner = player.score === 'Win';
|
||||||
|
const chatText = chat
|
||||||
|
? chat
|
||||||
|
: player.draw_offered
|
||||||
|
? 'draw'
|
||||||
|
: '\u00A0';
|
||||||
|
|
||||||
if (!isPlayer) {
|
if (!isPlayer) {
|
||||||
|
const nameClass = `name ${player.img ? 'subscriber' : ''}`;
|
||||||
return (
|
return (
|
||||||
<div class={`player-box top ${winner ? 'winner' : player.ready ? 'ready' : ''}`}>
|
<div class={`player-box top ${winner ? 'winner' : player.ready ? 'ready' : ''}`}>
|
||||||
<div></div>
|
<div></div>
|
||||||
<div class="score">{scoreText()}</div>
|
<div class="score">{scoreText()}</div>
|
||||||
<div class="name">{player.name}</div>
|
<div class={nameClass}>{player.name}</div>
|
||||||
<Img img={player.img} id={player.id} />
|
<Img img={player.img} id={player.id} />
|
||||||
<div class="msg">{chat || '\u00A0'}</div>
|
<div class="msg">{chatText}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const boxClass = `player-box bottom ${winner ? 'winner': player.ready ? 'ready' : ''}`;
|
||||||
|
const nameClass = `name ${player.img ? 'subscriber' : ''}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={`player-box bottom ${winner ? 'winner': player.ready ? 'ready' : ''}`}>
|
<div class={boxClass}>
|
||||||
<div class="msg">{chat || '\u00A0'}</div>
|
<div class="msg">{chatText}</div>
|
||||||
<div class="score">{scoreText()}</div>
|
<div class="score">{scoreText()}</div>
|
||||||
<div class="name">{player.name}</div>
|
<div class={nameClass}>{player.name}</div>
|
||||||
<Img img={player.img} id={player.id} />
|
<Img img={player.img} id={player.id} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -48,9 +48,12 @@ function Shop(args) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="credits">¤ {account.balance}</h1>
|
<h1 class="credits">¤ {account.balance}</h1>
|
||||||
<Elements>
|
{window.Stripe
|
||||||
<StripeBtns account={account} />
|
? <Elements>
|
||||||
</Elements>
|
<StripeBtns account={account} />
|
||||||
|
</Elements>
|
||||||
|
: <div>Please unblock Stripe to use the store</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -53,7 +53,7 @@ function Welcome() {
|
|||||||
<p>
|
<p>
|
||||||
Welcome to mnml.
|
Welcome to mnml.
|
||||||
</p>
|
</p>
|
||||||
<p> Turn-based 1v1 strategy game in an abstract setting. </p>
|
<p> MNML is a turn-based 1v1 strategy game in an abstract setting. </p>
|
||||||
<p>
|
<p>
|
||||||
Build a unique team of 3 constructs from a range of skills and specialisations.<br />
|
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 />
|
Outplay your opponent in multiple rounds by adapting to an always shifting meta. <br />
|
||||||
@ -62,6 +62,7 @@ function Welcome() {
|
|||||||
<p>
|
<p>
|
||||||
Free to play, no pay to win. Register to start playing.<br />
|
Free to play, no pay to win. Register to start playing.<br />
|
||||||
</p>
|
</p>
|
||||||
|
<a href='https://www.youtube.com/watch?v=VtZLlkpJuS8'>Tutorial Playthrough on YouTube</a>
|
||||||
</div>
|
</div>
|
||||||
{pageEl()}
|
{pageEl()}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -58,7 +58,7 @@ function Register(args) {
|
|||||||
value={this.state.name}
|
value={this.state.name}
|
||||||
onInput={linkState(this, 'name')}
|
onInput={linkState(this, 'name')}
|
||||||
/>
|
/>
|
||||||
<label for="password">Password - min 12 chars</label>
|
<label for="password">Password - min 4 chars</label>
|
||||||
<input
|
<input
|
||||||
class="login-input"
|
class="login-input"
|
||||||
type="password"
|
type="password"
|
||||||
@ -79,9 +79,11 @@ function Register(args) {
|
|||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
name="terms"
|
||||||
|
id="register-terms"
|
||||||
onInput={linkState(this, 'terms')
|
onInput={linkState(this, 'terms')
|
||||||
}/>
|
}/>
|
||||||
Confirm agreement to terms of service
|
<label class="terms" for="register-terms">Confirm agreement to terms of service.</label>
|
||||||
<button onClick={() => window.open('/tos.html')}>VIEW</button>
|
<button onClick={() => window.open('/tos.html')}>VIEW</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -127,6 +127,7 @@ function registerEvents(store) {
|
|||||||
|
|
||||||
function setAccount(account) {
|
function setAccount(account) {
|
||||||
if (account) {
|
if (account) {
|
||||||
|
LogRocket.init('yh0dy3/mnml');
|
||||||
LogRocket.identify(account.id, account);
|
LogRocket.identify(account.id, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
const toast = require('izitoast');
|
const toast = require('izitoast');
|
||||||
const cbor = require('borc');
|
const cbor = require('borc');
|
||||||
|
|
||||||
|
const throttle = require('lodash/throttle');
|
||||||
|
|
||||||
const SOCKET_URL =
|
const SOCKET_URL =
|
||||||
`${window.location.protocol === 'https:' ? 'wss://' : 'ws://'}${window.location.host}/api/ws`;
|
`${window.location.protocol === 'https:' ? 'wss://' : 'ws://'}${window.location.host}/api/ws`;
|
||||||
|
|
||||||
@ -121,6 +123,11 @@ function createSocket(events) {
|
|||||||
events.setActiveSkill(null);
|
events.setActiveSkill(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendGameOfferDraw(gameId) {
|
||||||
|
send(['GameOfferDraw', { game_id: gameId }]);
|
||||||
|
events.setActiveSkill(null);
|
||||||
|
}
|
||||||
|
|
||||||
function sendGameTarget(gameId, constructId, skillId) {
|
function sendGameTarget(gameId, constructId, skillId) {
|
||||||
send(['GameTarget', { game_id: gameId, construct_id: constructId, skill_id: skillId }]);
|
send(['GameTarget', { game_id: gameId, construct_id: constructId, skill_id: skillId }]);
|
||||||
events.setActiveSkill(null);
|
events.setActiveSkill(null);
|
||||||
@ -360,10 +367,12 @@ function createSocket(events) {
|
|||||||
sendGameReady,
|
sendGameReady,
|
||||||
sendGameSkill,
|
sendGameSkill,
|
||||||
sendGameSkillClear,
|
sendGameSkillClear,
|
||||||
|
sendGameOfferDraw,
|
||||||
sendGameTarget,
|
sendGameTarget,
|
||||||
|
|
||||||
sendInstanceAbandon,
|
sendInstanceAbandon,
|
||||||
sendInstanceReady,
|
// some weird shit happening in face off
|
||||||
|
sendInstanceReady: throttle(sendInstanceReady, 500),
|
||||||
sendInstancePractice,
|
sendInstancePractice,
|
||||||
sendInstanceQueue,
|
sendInstanceQueue,
|
||||||
sendInstanceState,
|
sendInstanceState,
|
||||||
|
|||||||
@ -63,24 +63,6 @@ server {
|
|||||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
|
||||||
server_name minimalstudios.com.au;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /var/lib/mnml/public/press/;
|
|
||||||
index index.html;
|
|
||||||
try_files $uri $uri/ index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
listen 443 ssl; # managed by Certbot
|
|
||||||
ssl_certificate /etc/letsencrypt/live/minimalstudios.com.au/fullchain.pem; # managed by Certbot
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/minimalstudios.com.au/privkey.pem; # managed by Certbot
|
|
||||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
|
||||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# http -> https
|
# http -> https
|
||||||
server {
|
server {
|
||||||
server_name mnml.gg;
|
server_name mnml.gg;
|
||||||
@ -91,8 +73,3 @@ server {
|
|||||||
server_name minimal.gg;
|
server_name minimal.gg;
|
||||||
return 301 https://mnml.gg$request_uri;
|
return 301 https://mnml.gg$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
|
||||||
server_name minimalstudios.com.au;
|
|
||||||
return 301 https://minimalstudios.com.au$request_uri;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-ops",
|
"name": "mnml-ops",
|
||||||
"version": "1.6.5",
|
"version": "1.6.6",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mnml"
|
name = "mnml"
|
||||||
version = "1.6.5"
|
version = "1.6.6"
|
||||||
authors = ["ntr <ntr@smokestack.io>"]
|
authors = ["ntr <ntr@smokestack.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -20,7 +20,7 @@ use img;
|
|||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::{err_msg, format_err};
|
use failure::{err_msg, format_err};
|
||||||
|
|
||||||
static PASSWORD_MIN_LEN: usize = 11;
|
static PASSWORD_MIN_LEN: usize = 3;
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
#[derive(Debug,Clone,Serialize,Deserialize)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
@ -189,7 +189,7 @@ pub fn new_img(tx: &mut Transaction, id: Uuid) -> Result<Account, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_password(tx: &mut Transaction, id: Uuid, current: &String, password: &String) -> Result<String, MnmlHttpError> {
|
pub fn set_password(tx: &mut Transaction, id: Uuid, current: &String, password: &String) -> Result<String, MnmlHttpError> {
|
||||||
if password.len() < PASSWORD_MIN_LEN {
|
if password.len() < PASSWORD_MIN_LEN || password.len() > 100 {
|
||||||
return Err(MnmlHttpError::PasswordUnacceptable);
|
return Err(MnmlHttpError::PasswordUnacceptable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +318,7 @@ pub fn set_subscribed(tx: &mut Transaction, id: Uuid, subscribed: bool) -> Resul
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(name: &String, password: &String, tx: &mut Transaction) -> Result<String, MnmlHttpError> {
|
pub fn create(name: &String, password: &String, tx: &mut Transaction) -> Result<String, MnmlHttpError> {
|
||||||
if password.len() < PASSWORD_MIN_LEN {
|
if password.len() < PASSWORD_MIN_LEN || password.len() > 100 {
|
||||||
return Err(MnmlHttpError::PasswordUnacceptable);
|
return Err(MnmlHttpError::PasswordUnacceptable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,7 +328,7 @@ pub fn create(name: &String, password: &String, tx: &mut Transaction) -> Result<
|
|||||||
|
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
let img = Uuid::new_v4();
|
let img = Uuid::new_v4();
|
||||||
let rounds = 8;
|
let rounds = 12;
|
||||||
let password = hash(&password, rounds)?;
|
let password = hash(&password, rounds)?;
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|||||||
@ -27,7 +27,7 @@ pub enum Phase {
|
|||||||
Start,
|
Start,
|
||||||
Skill,
|
Skill,
|
||||||
Resolve,
|
Resolve,
|
||||||
Finish,
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
#[derive(Debug,Clone,Serialize,Deserialize)]
|
||||||
@ -325,6 +325,30 @@ impl Game {
|
|||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn offer_draw(mut self, player_id: Uuid) -> Result<Game, Error> {
|
||||||
|
if self.phase != Phase::Skill {
|
||||||
|
return Err(err_msg("game not in skill phase"));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let player = self.player_by_id(player_id)?;
|
||||||
|
player.draw_offered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bots automatically accept draws
|
||||||
|
for player in self.players.iter_mut() {
|
||||||
|
if player.bot {
|
||||||
|
player.draw_offered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.players.iter().all(|p| p.draw_offered) {
|
||||||
|
return Ok(self.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(self);
|
||||||
|
}
|
||||||
|
|
||||||
fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
|
fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
|
||||||
self.player_by_id(player_id)?;
|
self.player_by_id(player_id)?;
|
||||||
if self.phase != Phase::Skill {
|
if self.phase != Phase::Skill {
|
||||||
@ -564,15 +588,18 @@ impl Game {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn finished(&self) -> bool {
|
pub fn finished(&self) -> bool {
|
||||||
self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko()))
|
self.phase == Phase::Finished || self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn winner(&self) -> Option<&Player> {
|
pub fn winner(&self) -> Option<&Player> {
|
||||||
self.players.iter().find(|t| t.constructs.iter().any(|c| !c.is_ko()))
|
match self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko())) {
|
||||||
|
true => self.players.iter().find(|t| t.constructs.iter().any(|c| !c.is_ko())),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(mut self) -> Game {
|
fn finish(mut self) -> Game {
|
||||||
self.phase = Phase::Finish;
|
self.phase = Phase::Finished;
|
||||||
// self.log.push(format!("Game finished."));
|
// self.log.push(format!("Game finished."));
|
||||||
|
|
||||||
// {
|
// {
|
||||||
@ -594,7 +621,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn upkeep(mut self) -> Game {
|
pub fn upkeep(mut self) -> Game {
|
||||||
if self.phase == Phase::Finish {
|
if self.phase == Phase::Finished {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -903,6 +930,15 @@ pub fn game_skill(tx: &mut Transaction, account: &Account, game_id: Uuid, constr
|
|||||||
Ok(game)
|
Ok(game)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn game_offer_draw(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result<Game, Error> {
|
||||||
|
let game = game_get(tx, game_id)?
|
||||||
|
.offer_draw(account.id)?;
|
||||||
|
|
||||||
|
game_update(tx, &game)?;
|
||||||
|
|
||||||
|
Ok(game)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn game_skill_clear(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result<Game, Error> {
|
pub fn game_skill_clear(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result<Game, Error> {
|
||||||
let mut game = game_get(tx, game_id)?;
|
let mut game = game_get(tx, game_id)?;
|
||||||
|
|
||||||
@ -1051,7 +1087,7 @@ mod tests {
|
|||||||
|
|
||||||
game = game.resolve_phase_start();
|
game = game.resolve_phase_start();
|
||||||
|
|
||||||
assert!([Phase::Skill, Phase::Finish].contains(&game.phase));
|
assert!([Phase::Skill, Phase::Finished].contains(&game.phase));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1117,7 +1153,7 @@ mod tests {
|
|||||||
game = game.resolve_phase_start();
|
game = game.resolve_phase_start();
|
||||||
|
|
||||||
assert!(!game.player_by_id(y_player.id).unwrap().constructs[0].is_stunned());
|
assert!(!game.player_by_id(y_player.id).unwrap().constructs[0].is_stunned());
|
||||||
assert!(game.phase == Phase::Finish);
|
assert!(game.phase == Phase::Finished);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1423,7 +1459,7 @@ mod tests {
|
|||||||
assert!(game.skill_phase_finished());
|
assert!(game.skill_phase_finished());
|
||||||
game = game.resolve_phase_start();
|
game = game.resolve_phase_start();
|
||||||
|
|
||||||
assert!([Phase::Skill, Phase::Finish].contains(&game.phase));
|
assert!([Phase::Skill, Phase::Finished].contains(&game.phase));
|
||||||
|
|
||||||
// kill a construct
|
// kill a construct
|
||||||
game.player_by_id(i_player.id).unwrap().construct_by_id(i_construct.id).unwrap().green_life.reduce(u64::max_value());
|
game.player_by_id(i_player.id).unwrap().construct_by_id(i_construct.id).unwrap().green_life.reduce(u64::max_value());
|
||||||
|
|||||||
@ -317,7 +317,13 @@ impl Instance {
|
|||||||
self.phase_start = Utc::now();
|
self.phase_start = Utc::now();
|
||||||
self.phase_end = self.time_control.vbox_phase_end();
|
self.phase_end = self.time_control.vbox_phase_end();
|
||||||
|
|
||||||
|
let bits = match self.rounds.len() > 0 {
|
||||||
|
true => 12 + 6 * self.rounds.len(),
|
||||||
|
false => 0,
|
||||||
|
};
|
||||||
|
|
||||||
self.players.iter_mut().for_each(|p| {
|
self.players.iter_mut().for_each(|p| {
|
||||||
|
p.vbox.balance_add(bits.into());
|
||||||
p.set_ready(false);
|
p.set_ready(false);
|
||||||
p.vbox.fill();
|
p.vbox.fill();
|
||||||
});
|
});
|
||||||
@ -329,28 +335,18 @@ impl Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn finish_condition(&mut self) -> bool {
|
fn finish_condition(&mut self) -> bool {
|
||||||
// tennis
|
self.players.iter().any(|p| p.score == Score::Win)
|
||||||
for player in self.players.iter() {
|
|
||||||
if player.score == Score::Win {
|
|
||||||
self.winner = Some(player.id);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Game defaults to lose otherwise
|
|
||||||
if self.rounds.len() < 4 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// both players afk
|
|
||||||
if self.players.iter().all(|p| p.score == Score::Zero) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish(&mut self) -> &mut Instance {
|
pub fn finish(&mut self) -> &mut Instance {
|
||||||
self.phase = InstancePhase::Finished;
|
self.phase = InstancePhase::Finished;
|
||||||
|
|
||||||
|
for player in self.players.iter() {
|
||||||
|
if player.score == Score::Win {
|
||||||
|
self.winner = Some(player.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,38 +408,14 @@ impl Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if you don't win, you lose
|
// if you don't win, you lose
|
||||||
// ties can happen if both players forfeit
|
// ties can happen if both players agree to a draw
|
||||||
// in this case we just finish the game and
|
// or ticks fire and knock everybody out
|
||||||
// dock them 10k mmr
|
if let Some(winner) = game.winner() {
|
||||||
let winner_id = match game.winner() {
|
|
||||||
Some(w) => w.id,
|
|
||||||
None => return Ok(self.finish()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let bits = 12 + 6 * self.rounds.len();
|
|
||||||
|
|
||||||
{
|
|
||||||
let loser = self.players.iter()
|
|
||||||
.find(|p| p.id != winner_id)
|
|
||||||
.map(|p| p.score)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let winner = self.players.iter_mut()
|
let winner = self.players.iter_mut()
|
||||||
.find(|p| p.id == winner_id)
|
.find(|p| p.id == winner.id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
winner.score = winner.score.add_win(&Score::Zero);
|
||||||
winner.score = winner.score.add_win(&loser);
|
};
|
||||||
winner.vbox.balance_add(bits.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let loser = self.players.iter_mut()
|
|
||||||
.find(|p| p.id != winner_id)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
loser.score = loser.score.add_loss();
|
|
||||||
loser.vbox.balance_add(bits.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.all_games_finished() {
|
if self.all_games_finished() {
|
||||||
self.next_round();
|
self.next_round();
|
||||||
@ -799,7 +771,7 @@ pub fn instance_state(tx: &mut Transaction, instance_id: Uuid) -> Result<RpcMess
|
|||||||
let game = game_get(tx, game_id)?;
|
let game = game_get(tx, game_id)?;
|
||||||
|
|
||||||
// return the game until it's finished
|
// return the game until it's finished
|
||||||
if game.phase != Phase::Finish {
|
if game.phase != Phase::Finished {
|
||||||
return Ok(RpcMessage::GameState(game))
|
return Ok(RpcMessage::GameState(game))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,6 +64,7 @@ pub struct Player {
|
|||||||
pub bot: bool,
|
pub bot: bool,
|
||||||
pub ready: bool,
|
pub ready: bool,
|
||||||
pub warnings: u8,
|
pub warnings: u8,
|
||||||
|
pub draw_offered: bool,
|
||||||
pub score: Score,
|
pub score: Score,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ impl Player {
|
|||||||
bot: false,
|
bot: false,
|
||||||
ready: false,
|
ready: false,
|
||||||
warnings: 0,
|
warnings: 0,
|
||||||
|
draw_offered: false,
|
||||||
score: Score::Zero,
|
score: Score::Zero,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -99,6 +101,7 @@ impl Player {
|
|||||||
bot: false,
|
bot: false,
|
||||||
ready: false,
|
ready: false,
|
||||||
warnings: 0,
|
warnings: 0,
|
||||||
|
draw_offered: false,
|
||||||
score: Score::Zero,
|
score: Score::Zero,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ use account::{Account};
|
|||||||
use account;
|
use account;
|
||||||
use construct::{Construct};
|
use construct::{Construct};
|
||||||
use events::{Event};
|
use events::{Event};
|
||||||
use game::{Game, game_state, game_skill, game_skill_clear, game_ready};
|
use game::{Game, game_state, game_skill, game_skill_clear, game_ready, game_offer_draw};
|
||||||
use instance::{Instance, ChatState, instance_state, instance_practice, instance_ready, instance_abandon, demo};
|
use instance::{Instance, ChatState, instance_state, instance_practice, instance_ready, instance_abandon, demo};
|
||||||
use item::{Item, ItemInfoCtr, item_info};
|
use item::{Item, ItemInfoCtr, item_info};
|
||||||
use mtx;
|
use mtx;
|
||||||
@ -90,6 +90,7 @@ pub enum RpcRequest {
|
|||||||
GameReady { id: Uuid },
|
GameReady { id: Uuid },
|
||||||
GameSkill { game_id: Uuid, construct_id: Uuid, target_construct_id: Uuid, skill: Skill },
|
GameSkill { game_id: Uuid, construct_id: Uuid, target_construct_id: Uuid, skill: Skill },
|
||||||
GameSkillClear { game_id: Uuid },
|
GameSkillClear { game_id: Uuid },
|
||||||
|
GameOfferDraw { game_id: Uuid },
|
||||||
|
|
||||||
AccountState {},
|
AccountState {},
|
||||||
AccountShop {},
|
AccountShop {},
|
||||||
@ -218,6 +219,9 @@ impl Connection {
|
|||||||
RpcRequest::GameReady { id } =>
|
RpcRequest::GameReady { id } =>
|
||||||
Ok(RpcMessage::GameState(game_ready(&mut tx, account, id)?)),
|
Ok(RpcMessage::GameState(game_ready(&mut tx, account, id)?)),
|
||||||
|
|
||||||
|
RpcRequest::GameOfferDraw { game_id } =>
|
||||||
|
Ok(RpcMessage::GameState(game_offer_draw(&mut tx, account, game_id)?)),
|
||||||
|
|
||||||
RpcRequest::InstancePractice {} =>
|
RpcRequest::InstancePractice {} =>
|
||||||
Ok(RpcMessage::InstanceState(instance_practice(&mut tx, account)?)),
|
Ok(RpcMessage::InstanceState(instance_practice(&mut tx, account)?)),
|
||||||
|
|
||||||
@ -450,6 +454,7 @@ pub fn start(pool: PgPool, events_tx: CbSender<Event>, stripe: StripeClient) {
|
|||||||
}
|
}
|
||||||
// we done
|
// we done
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
|
// info!("{:?}", e);
|
||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1264,6 +1264,9 @@ impl Skill {
|
|||||||
Skill::Invert|
|
Skill::Invert|
|
||||||
Skill::InvertPlus |
|
Skill::InvertPlus |
|
||||||
Skill::InvertPlusPlus |
|
Skill::InvertPlusPlus |
|
||||||
|
Skill::Intercept|
|
||||||
|
Skill::InterceptPlus |
|
||||||
|
Skill::InterceptPlusPlus |
|
||||||
Skill::Counter|
|
Skill::Counter|
|
||||||
Skill::CounterPlus |
|
Skill::CounterPlus |
|
||||||
Skill::CounterPlusPlus |
|
Skill::CounterPlusPlus |
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user