Compare commits

..

15 Commits

30 changed files with 286 additions and 520 deletions

View File

@ -1,4 +1,15 @@
# WORK WORK # WORK WORK
## AUTO
- server
- unlimited specs
- vbox has colour specs
- skill based on colour count
- UI
- colour count
## NOW ## NOW
_ntr_ _ntr_

View File

@ -6,4 +6,6 @@ sudo -u postgres createuser --encrypted mnml
PG_PASSWORD=$(openssl rand -hex 16) PG_PASSWORD=$(openssl rand -hex 16)
echo "database password is $PG_PASSWORD" echo "database password is $PG_PASSWORD"
sudo -u -E postgres psql -c "alter user mnml with encrypted password '$PG_PASSWORD';" sudo -u postgres psql -c "alter user mnml with encrypted password '$PG_PASSWORD';"
sudo -u postgres psql -c "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"

View File

@ -4,7 +4,7 @@
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
MNML_PATH=$(realpath "$DIR/../") MNML_PATH=$(realpath "$DIR/../")
MNML_CONF="/etc/mnml/mnml.conf" MNML_CONF="/etc/mnml/gs.conf"
if [ ! -f $MNML_CONF ]; then if [ ! -f $MNML_CONF ]; then
echo "-----------------------------------------------" echo "-----------------------------------------------"
@ -26,6 +26,8 @@ source $MNML_CONF
sudo mkdir -p /var/lib/mnml/client sudo mkdir -p /var/lib/mnml/client
sudo mkdir -p /var/lib/mnml/data sudo mkdir -p /var/lib/mnml/data
sudo mkdir -p /var/lib/mnml/public/imgs sudo mkdir -p /var/lib/mnml/public/imgs
sudo mkdir -p /var/lib/mnml/data/instances/
sudo mkdir -p /var/lib/mnml/data/games/
sudo chown -R $MNML_USER:$MNML_USER /var/lib/mnml sudo chown -R $MNML_USER:$MNML_USER /var/lib/mnml
# /var/log/mnml # /var/log/mnml
@ -45,11 +47,14 @@ sudo cp $MNML_PATH/etc/systemd/system/mnml.service /usr/local/systemd/system/
# POSTGRES SETUP # POSTGRES SETUP
sudo -u postgres createdb mnml sudo -u postgres createdb mnml
sudo -u postgres createuser --encrypted mnml sudo -u postgres createuser --encrypted mnml
echo "DATABASE_URL=postgres://mnml:$MNML_PG_PASSWORD@$MNML_PG_HOST/mnml" | sudo tee -a /etc/mnml/gs.conf echo "DATABASE_URL=postgres://mnml:$MNML_PG_PASSWORD@$MNML_PG_HOST/mnml" | sudo tee -a /etc/mnml/gs.conf
sudo -u postgres psql -c "alter user mnml with encrypted password '$MNML_PG_PASSWORD';" sudo -u postgres psql -c "alter user mnml with encrypted password '$MNML_PG_PASSWORD';"
cd $MNML_PATH/ops && npm run migrate cd $MNML_PATH/ops
npm i
npm run migrate
cd $MNML_PATH
# NGINX # NGINX
if [ ! -f $MNML_PATH/etc/nginx/sites-available/mnml.gg.nginx.conf ]; then if [ ! -f $MNML_PATH/etc/nginx/sites-available/mnml.gg.nginx.conf ]; then

View File

@ -5,7 +5,7 @@
.instance { .instance {
overflow: hidden; overflow: hidden;
display: grid; display: grid;
grid-template-rows: min-content 1fr; grid-template-rows: 7fr 12fr;
grid-template-areas: grid-template-areas:
"vbox" "vbox"
@ -31,13 +31,14 @@
.instance-construct { .instance-construct {
flex: 1; flex: 1;
display: grid; display: grid;
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content min-content 1fr min-content; grid-template-rows: min-content min-content min-content 1fr min-content;
grid-template-areas: grid-template-areas:
"name " "name name "
"skills " "skills skills"
"specs " "colours colours"
"avatar " "stats avatar"
"stats "; "defStats defStats";
/*padding: 0.5em;*/ /*padding: 0.5em;*/
border: 0.1em solid #222; border: 0.1em solid #222;
@ -78,7 +79,6 @@
z-index: 2; z-index: 2;
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 0 0.5em; grid-gap: 0 0.5em;
button { button {
@ -89,58 +89,44 @@
} }
} }
.specs { .colours {
grid-area: specs; grid-area: colours;
padding: 0 0.5em; display: flex;
margin-bottom: 0.75em; flex-direction: row;
z-index: 2; text-align: center;
justify-content: center;
div {
margin: 0.5em 0.75em;
} }
display: grid; .offStats {
grid-template-columns: repeat(3, 1fr);
grid-gap: 0 0.5em;
button {
padding: 0.25em 0;
}
figure {
flex: 1;
align-items: center;
text-align: center;
}
figcaption {
// font-size: 75%;
line-height: initial;
}
label {
display: flex;
}
}
.stats {
grid-area: stats; grid-area: stats;
display: flex; display: flex;
flex-flow: row; flex-flow: column;
flex: 1;
border-width: 0px; border-width: 0px;
text-align: center; justify-content: center;
figcaption {
// font-size: 75%;
}
// give speed some space // give speed some space
div:nth-child(4n) {
margin: 0 1em;
}
div { div {
flex: 1; margin: 0.5em 0.75em;
display: flex;
flex-flow: row;
text-align: right;
} }
} }
.defStats {
grid-area: defStats;
display: flex;
flex-direction: row;
text-align: center;
justify-content: center;
div {
margin: 0.5em 0.75em;
}
}
button { button {
margin: 0; margin: 0;

View File

@ -2,11 +2,11 @@
align-content: space-between; align-content: space-between;
grid-area: vbox; grid-area: vbox;
display: grid; display: grid;
grid-template-rows: 3fr minmax(min-content, 2fr); grid-template-rows: 1fr min-content;
grid-template-columns: 1fr 4fr 6fr minmax(min-content, 2fr);; grid-template-columns: 1fr 4fr;
grid-template-areas: grid-template-areas:
"store-hdr store info-combiner combos" "store-hdr info-combiner"
"stash-hdr stash info-combiner combos"; "store store";
margin-bottom: 1em; margin-bottom: 1em;
// immediate children // immediate children
@ -48,53 +48,15 @@
} }
} }
.stash {
grid-area: stash;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 0.5em 1em;
align-items: center;
border: 0.15em solid @gray;
border-left: 0;
}
.stash-hdr {
grid-area: stash-hdr;
display: flex;
flex-flow: column;
text-align: center;
border: 0.15em solid @gray;
border-right: 0;
h2 {
margin-bottom: 0.5em;
}
button {
line-height: 1.6;
letter-spacing: 0.15em;
border-width: 0.1em;
}
}
.vbox-hdr { .vbox-hdr {
grid-area: vbox-hdr;
margin-bottom: 1em; margin-bottom: 1em;
height: 2em; height: 2em;
} }
.vbox-colours {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-gap: 0.5em 1em;
align-items: center;
margin-bottom: 0.5em;
}
.vbox-items { .vbox-items {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(5, 1fr);
grid-gap: 0.5em 1em; grid-gap: 0.5em 1em;
align-items: center; align-items: center;
} }
@ -115,58 +77,6 @@
border-width: 0.1em; border-width: 0.1em;
}; };
} }
.Refunding {
button:not([disabled]) {
&, &:hover, &:active {
background: @red;
color: black;
border: 0.1em solid black;
}
}
svg {
stroke-width: 0.75em;
}
.white {
stroke: black;
}
}
button {
height: 3.5em;
margin: 0;
width: 100%;
// text-transform: none;
&.empty {
border-style: dashed;
}
&.fade {
opacity: 0.4;
}
&.highlight {
background: @silver;
// overwrite the classes on white svg elements
svg {
stroke-width: 0.75em;
}
}
}
// figures don't scale well
figure {
svg {
height: 1.5em;
stroke-width: 0.5em;
}
figcaption {
line-height: initial;
}
}
.info-combiner { .info-combiner {
grid-area: info-combiner; grid-area: info-combiner;
display: grid; display: grid;
@ -202,111 +112,46 @@
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
} }
margin-left: 1em; margin-left: 1em;
} }
.combiner {
grid-area: combiner;
margin: 1em 0.5em;
width: 50%;
line-height: 1.3;
font-size: 1.25em;
letter-spacing: 0.1em;
border: 0.1em solid @gray-exists;
&:hover {
border: 0.1em solid @gray-hover;
}
}
// align-self: flex-end; // align-self: flex-end;
} }
.combos {
display: grid;
grid-area: combos;
margin-left: 0.5em;
margin-right: 0.5em;
grid-template-rows: min-content min-content;
width: 15.5em;
grid-template-areas:
"comboHeader"
"comboList";
h2 {
text-transform: uppercase; button {
height: 5em;
margin: 0;
width: 100%;
// text-transform: none;
&.empty {
border-style: dashed;
} }
svg { &.fade {
display: inline; opacity: 0.4;
height: 1em;
} }
figure { &.highlight {
display: inline; background: @silver;
height: 0.5em; // overwrite the classes on white svg elements
svg { svg {
margin-right: 0.5em; stroke-width: 0.75em;
} }
} }
}
// figures don't scale well
figure {
svg {
height: 1.5em;
stroke-width: 0.5em;
}
figcaption { figcaption {
font-size: 1em; line-height: initial;
display: inline-block;
vertical-align: middle;
}
.combo-header {
text-align: center;
}
.combo-list {
display: grid;
grid-template-rows: min-content min-content min-content;
grid-template-columns: min-content min-content;
grid-gap: 0.5em;
margin-top: 0.5em;
width: 15.5em;
.table-button {
display: grid;
text-align: center;
align-content: center;
grid-template-areas:
"item"
"ingr";
cursor: pointer;
&:hover {
color: whitesmoke;
background-color: @gray;
}
.item {
border-top: 0.1em solid #222;
border-bottom: 0.1em solid #222;
flex: 1;
grid-area: item;
font-weight: bold;
div {
width: 5em;
}
}
div {
border-left: 0.1em solid #222;
border-right: 0.1em solid #222;
height: 1.75em;
width: 7.5em;
svg {
vertical-align: middle;
}
&:last-child {
border-bottom: 0.1em solid #222;
}
}
}
} }
} }
} }

View File

@ -95,7 +95,6 @@ class GameConstruct extends preact.Component {
style={ activeSkill ? { cursor: 'pointer' } : {}} style={ activeSkill ? { cursor: 'pointer' } : {}}
class={`game-construct ${ko} ${koEvent()} ${unfocus} ${highlight}`}> class={`game-construct ${ko} ${koEvent()} ${unfocus} ${highlight}`}>
<div class="left"> <div class="left">
{crypSkills}
<ConstructEffectBox construct={construct} /> <ConstructEffectBox construct={construct} />
</div> </div>
<div class="right"> <div class="right">

View File

@ -5,7 +5,7 @@ const range = require('lodash/range');
const buttons = require('./buttons'); const buttons = require('./buttons');
const shapes = require('./shapes'); const shapes = require('./shapes');
const { STATS } = require('../utils'); const { OFFENSE, DEFENSE } = require('../utils');
const { ConstructAvatar } = require('./construct'); const { ConstructAvatar } = require('./construct');
const actions = require('../actions'); const actions = require('../actions');
const { removeTier } = require('../utils'); const { removeTier } = require('../utils');
@ -125,7 +125,7 @@ function Construct(props) {
const skillList = itemInfo.items.filter(v => v.skill).map(v => v.item); const skillList = itemInfo.items.filter(v => v.skill).map(v => v.item);
const specList = itemInfo.items.filter(v => v.spec).map(v => v.item); const specList = itemInfo.items.filter(v => v.spec).map(v => v.item);
const skills = range(0, 3).map(i => { const skills = range(0, 1).map(i => {
const skill = construct.skills[i]; const skill = construct.skills[i];
const s = skill const s = skill
? skill.skill ? skill.skill
@ -169,50 +169,34 @@ function Construct(props) {
); );
}); });
const specs = range(0, 3).map(i => { const colours = () => {
const s = construct.specs[i];
if (!s) {
const equipping = specList.includes(vbox.stash[itemEquip]) && i === construct.specs.length;
const classes = `${equipping ? 'equipping' : 'gray'} empty`;
return (
<button key={i} class={classes} disabled={!equipping} >
{shapes.None()}
</button>
);
}
function specClick(e) {
if (!s) return false;
e.stopPropagation();
if (itemUnequip.length && itemUnequip[0] === construct.id && itemUnequip[1] === s
&& i === itemUnequip[2]) return setItemUnequip([]);
return setItemUnequip([construct.id, s, i]);
}
const highlight = itemUnequip[0] === construct.id && itemUnequip[1] === s && i === itemUnequip[2];
return ( return (
<label onDragStart={ev => { <div class="colours" onMouseOver={e => hoverInfo(e, 'constructSpecs')} >
ev.dataTransfer.setData('text', ''); <div> {shapes.Red()} {construct.colours.red} </div>
specClick(ev); <div> {shapes.Blue()} {construct.colours.blue} </div>
}} key={i} draggable="true" onDragEnd={() => setItemUnequip([])}> <div> {shapes.Green()} {construct.colours.green} </div>
<button </div>
key={i}
class={`${highlight ? 'highlight' : ''}`}
onClick={specClick}
onMouseOver={e => hoverInfo(e, s)} >
{shapes[s]()}
</button>
</label>
); );
}); };
const stats = Object.keys(STATS).map(s => { const offensiveStats = Object.keys(OFFENSE).map(s => {
const stat = STATS[s]; const stat = OFFENSE[s];
const info = (s === 'SpeedStat' && 'speedStat') const info = (s === 'SpeedStat' && 'speedStat')
|| (s.includes('Power') && 'powerStat') || (s.includes('Power') && 'powerStat');
|| (s.includes('Life') && 'lifeStat'); return <div key={stat.stat}
alt={stat.stat}
class={stat.stat}
onMouseOver={e => hoverInfo(e, info)} >
{shapes[s]()}
<div>{construct[stat.stat].value}</div>
</div>;
});
const defensiveStats = Object.keys(DEFENSE).map(s => {
const stat = DEFENSE[s];
const info = (s.includes('Life') && 'lifeStat');
return <div key={stat.stat} return <div key={stat.stat}
alt={stat.stat} alt={stat.stat}
class={stat.stat} class={stat.stat}
@ -231,11 +215,12 @@ function Construct(props) {
<div class="skills" onMouseOver={e => hoverInfo(e, 'constructSkills')} > <div class="skills" onMouseOver={e => hoverInfo(e, 'constructSkills')} >
{skills} {skills}
</div> </div>
<div class="specs" onMouseOver={e => hoverInfo(e, 'constructSpecs')} > {colours()}
{specs} <div class="offStats">
{offensiveStats}
</div> </div>
<div class="stats"> <div class="defStats">
{stats} {defensiveStats}
</div> </div>
</div> </div>
); );

View File

@ -3,7 +3,7 @@ const { injectStripe } = require('react-stripe-elements');
function subPlan() { function subPlan() {
if (window.location.host === 'mnml.gg') return 'plan_Fjdtsd4i7aVLe1'; if (window.location.host === 'mnml.gg') return 'plan_Fjdtsd4i7aVLe1';
return 'prod_FWSA8RoyMMV3st'; return 'plan_Fhl9r7UdMadjGi';
} }
function bitsSku(d) { function bitsSku(d) {

View File

@ -40,25 +40,13 @@ class TargetSvg extends Component {
render(props, state) { render(props, state) {
const { const {
// Changing State Variables // Changing State Variables
account,
animating,
game, game,
gameEffectInfo, gameEffectInfo,
} = props; } = props;
const { width, height } = state;
if (!game) return false; // game will be null when battle ends if (!game) return false; // game will be null when battle ends
if (game.phase === 'Finished') return false; // Clear everything if its over (in case of abandon) if (game.phase === 'Finished') return false; // Clear everything if its over (in case of abandon)
// First round of a game
if (!game.resolutions.length && game.stack.length === 0) {
return (
<div class="resolving-skill">
<h2><b>Select a skill</b> from each construct, <b>click a target</b> for that skill and then click <b>READY</b>.</h2>
</div>
);
}
// Whenever someones looking at effects throw it up here // Whenever someones looking at effects throw it up here
if (gameEffectInfo) { if (gameEffectInfo) {
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/; const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/;
@ -72,72 +60,7 @@ class TargetSvg extends Component {
</div> </div>
); );
} }
return false;
// resolutions happening
// just put skill name up
if (animating) return false;
const playerTeam = game.players.find(t => t.id === account.id);
const otherTeam = game.players.find(t => t.id !== account.id);
const playerTeamIds = playerTeam.constructs.map(c => c.id);
const outgoing = game.stack.filter(stack => stack.player === account.id);
function getPath(cast) {
const source = playerTeam.constructs.findIndex(c => c.id === cast.source);
const defensive = playerTeamIds.includes(cast.target);
const target = defensive
? playerTeam.constructs.findIndex(c => c.id === cast.target)
: otherTeam.constructs.findIndex(c => c.id === cast.target);
const skillNumber = window.innerWidth <= 800 // mobile styling trigger
? playerTeam.constructs[source].skills.findIndex(s => s.skill === cast.skill)
: 0;
const sourceY = height;
const sourceX = (source * width / 3) + width / 18 + skillNumber * (width / 9);
const targetX = (target * width / 3) + width / 6
+ (defensive ? width / 64 : 0)
+ (source * width / 18);
const targetY = defensive ? height : 0;
const bendStart = height * (0.7 - 0.1 * source);
const bendEnd = height * 0.20;
if (defensive) {
const path = `
M${sourceX},${sourceY}
L${sourceX},${bendStart}
L${targetX},${bendStart}
L${targetX},${targetY}
L${targetX - (width * 0.005)},${height * 0.875}
M${targetX},${targetY}
L${targetX + (width * 0.005)},${height * 0.875}
`;
return <path d={path} />;
}
const path = `
M${sourceX},${sourceY}
L${sourceX},${bendStart}
L${targetX},${bendEnd}
L${targetX},${targetY}
L${targetX - (width * 0.005)},${height * 0.125}
M${targetX},${targetY}
L${targetX + (width * 0.005)},${height * 0.125}
`;
return <path d={path} />;
}
return (
<svg id="targeting"
viewBox={`0 0 ${width} ${height}`}
preserveAspectRatio="none"
class="targeting-arrows">
{outgoing.map(getPath)}
</svg>
);
} }
componentDidMount() { componentDidMount() {

View File

@ -45,10 +45,6 @@ const addState = connect(
return ws.sendVboxRefund(instance.id, i); return ws.sendVboxRefund(instance.id, i);
} }
function sendItemUnequip([constructId, item]) {
return ws.sendVboxUnequip(instance.id, constructId, item);
}
return { return {
itemUnequip, itemUnequip,
instance, instance,
@ -59,7 +55,6 @@ const addState = connect(
itemInfo, itemInfo,
sendInstance, sendInstance,
sendItemUnequip,
sendVboxBuy, sendVboxBuy,
sendVboxCombine, sendVboxCombine,
sendVboxRefill, sendVboxRefill,
@ -112,7 +107,6 @@ class Vbox extends preact.Component {
itemInfo, itemInfo,
// Function Calls // Function Calls
dispatchVboxSelect, dispatchVboxSelect,
sendItemUnequip,
sendInstance, sendInstance,
sendVboxBuy, sendVboxBuy,
sendVboxCombine, sendVboxCombine,
@ -128,7 +122,6 @@ class Vbox extends preact.Component {
const setVboxSelected = v => dispatchVboxSelect(v, { itemInfo, itemUnequip, vbox }); const setVboxSelected = v => dispatchVboxSelect(v, { itemInfo, itemUnequip, vbox });
const clearVboxSelected = () => setVboxSelected({ storeSelect: [], stashSelect: [] }); const clearVboxSelected = () => setVboxSelected({ storeSelect: [], stashSelect: [] });
const vboxBuySelected = () => sendVboxBuy(storeSelect[0][0], storeSelect[0][1]);
const clearTutorial = () => { const clearTutorial = () => {
setTutorial(null); setTutorial(null);
sendInstance(); sendInstance();
@ -147,7 +140,7 @@ class Vbox extends preact.Component {
<div class="store-hdr"> <div class="store-hdr">
<h2 <h2
onTouchStart={e => e.target.scrollIntoView(true)} onTouchStart={e => e.target.scrollIntoView(true)}
onMouseOver={e => vboxHover(e, 'store')}> STORE onMouseOver={e => vboxHover(e, 'store')}> VBOX
</h2> </h2>
<h1 class={`bits ${vbox.bits < 3 ? 'red' : false}`} onMouseOver={e => vboxHover(e, 'bits')}> <h1 class={`bits ${vbox.bits < 3 ? 'red' : false}`} onMouseOver={e => vboxHover(e, 'bits')}>
{vbox.bits}b {vbox.bits}b
@ -168,41 +161,10 @@ class Vbox extends preact.Component {
); );
} }
function stashHdr() {
const refund = storeSelect.length === 0 && stashSelect.length === 1
? itemInfo.items.find(i => i.item === vbox.stash[stashSelect[0]]).cost
: 0;
const tutorialDisabled = tutorial && tutorial < 8
&& instance.time_control === 'Practice' && instance.rounds.length === 1;
const refundBtn = (
<button
disabled={tutorialDisabled || !refund}
class='vbox-btn'
onClick={e => e.stopPropagation()}
onMouseDown={e => {
e.stopPropagation();
sendVboxRefund(vboxSelected.stashSelect[0]);
}}>
refund <br />
{refund}b
</button>
);
return (
<div class='stash-hdr'>
<h2 onTouchStart={e => e.target.scrollIntoView(true)}
onMouseOver={e => vboxHover(e, 'stash')}> STASH
</h2>
{refundBtn}
</div>
);
}
// EVERYTHING // EVERYTHING
return ( return (
<div class='vbox'> <div class='vbox'>
{storeHdr()} {storeHdr()}
{stashHdr()}
<StoreElement <StoreElement
clearVboxSelected={clearVboxSelected} clearVboxSelected={clearVboxSelected}
setVboxSelected={setVboxSelected} setVboxSelected={setVboxSelected}
@ -210,26 +172,11 @@ class Vbox extends preact.Component {
vboxSelected={vboxSelected} vboxSelected={vboxSelected}
vboxHover = {vboxHover} vboxHover = {vboxHover}
/> />
<StashElement
sendItemUnequip={sendItemUnequip}
setInfo={setInfo}
setVboxSelected={setVboxSelected}
vbox={vbox}
vboxBuySelected={vboxBuySelected}
vboxHover={vboxHover}
/>
<div class='info-combiner'> <div class='info-combiner'>
<InfoContainer <InfoContainer
clearTutorial={clearTutorial} clearTutorial={clearTutorial}
/> />
<Combiner
vbox={vbox}
vboxSelected={vboxSelected}
vboxBuySelected={vboxBuySelected}
sendVboxCombine={sendVboxCombine}
/>
</div> </div>
<Combos />
</div> </div>
); );
} }

View File

@ -36,7 +36,6 @@ function specThresholds(player, fullInfo, info) {
const dots = range(start, colourReq).map(j => { const dots = range(start, colourReq).map(j => {
const unmet = teamColours[c] < j + 1; const unmet = teamColours[c] < j + 1;
const reqClass = unmet const reqClass = unmet
? 'unmet' ? 'unmet'
: ''; : '';

View File

@ -80,11 +80,8 @@ class storeElement extends preact.Component {
return ( return (
<div class='store' <div class='store'
onClick={e => e.stopPropagation()}> onClick={e => e.stopPropagation()}>
<div class="vbox-colours">
{range(0, 6).map(i => availableBtn(vbox.store['Colours'][i], 'Colours', i.toString()))}
</div>
<div class="vbox-items"> <div class="vbox-items">
{range(0, 3).map(i => availableBtn(vbox.store['Skills'][i], 'Skills', i.toString()))} {range(0, 2).map(i => availableBtn(vbox.store['Skills'][i], 'Skills', i.toString()))}
{range(0, 3).map(i => availableBtn(vbox.store['Specs'][i], 'Specs', i.toString()))} {range(0, 3).map(i => availableBtn(vbox.store['Specs'][i], 'Specs', i.toString()))}
</div> </div>
</div> </div>

View File

@ -102,7 +102,7 @@ function genItemInfo(item, itemInfo, player) {
? <div> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </div> ? <div> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </div>
: null; : null;
const thresholds = isSpec ? specThresholds(player, fullInfo, item) : null; // const thresholds = isSpec ? specThresholds(player, fullInfo, item) : null;
return ( return (
<div class="info info-item"> <div class="info info-item">
@ -112,7 +112,6 @@ function genItemInfo(item, itemInfo, player) {
{cooldown} {cooldown}
{itemDescription()} {itemDescription()}
{speed} {speed}
{thresholds}
</div> </div>
); );
} }

View File

@ -177,14 +177,15 @@ function registerEvents(store) {
} }
function setInstance(v) { function setInstance(v) {
const { account, ws, tutorial } = store.getState(); const { account, ws } = store.getState();
if (v) { if (v) {
setInvite(null); setInvite(null);
setPvp(false); setPvp(false);
clearTutorial();
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 && v.rounds.length === 1 && v.time_control === 'Practice') tutorialVbox(player, store, tutorial); // if (tutorial && v.rounds.length === 1 && v.time_control === 'Practice') tutorialVbox(player, store, tutorial);
if (v.phase === 'Finished') { if (v.phase === 'Finished') {
ws.sendAccountInstances(); ws.sendAccountInstances();

View File

@ -47,7 +47,7 @@ const numSort = (k, desc) => {
const NULL_UUID = '00000000-0000-0000-0000-000000000000'; const NULL_UUID = '00000000-0000-0000-0000-000000000000';
const STATS = { const OFFENSE = {
RedPower: { RedPower: {
stat: 'red_power', stat: 'red_power',
colour: 'red', colour: 'red',
@ -68,6 +68,8 @@ const STATS = {
colour: 'white', colour: 'white',
svg: shapes.triangle, svg: shapes.triangle,
}, },
};
const DEFENSE = {
RedLife: { RedLife: {
stat: 'red_life', stat: 'red_life',
colour: 'red', colour: 'red',
@ -83,7 +85,7 @@ const STATS = {
colour: 'blue', colour: 'blue',
svg: shapes.square, svg: shapes.square,
}, },
}; }
const COLOURS = [ const COLOURS = [
'#a52a2a', '#a52a2a',
@ -294,7 +296,8 @@ module.exports = {
errorToast, errorToast,
infoToast, infoToast,
NULL_UUID, NULL_UUID,
STATS, OFFENSE,
DEFENSE,
COLOURS, COLOURS,
TARGET_COLOURS, TARGET_COLOURS,
randomPoints, randomPoints,

View File

@ -30,10 +30,10 @@ impl Colours {
v.colours(&mut count); v.colours(&mut count);
} }
for cs in construct.skills.iter() { // for cs in construct.skills.iter() {
let v = Item::from(cs.skill); // let v = Item::from(cs.skill);
v.colours(&mut count); // v.colours(&mut count);
} // }
count count
} }
@ -233,10 +233,10 @@ impl Construct {
blue_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::BluePower }, blue_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::BluePower },
blue_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::BlueLife }, blue_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::BlueLife },
green_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::GreenPower }, green_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::GreenPower },
green_life: ConstructStat { base: 800, value: 800, max: 800, stat: Stat::GreenLife }, green_life: ConstructStat { base: 400, value: 400, max: 400, stat: Stat::GreenLife },
speed: ConstructStat { base: 100, value: 100, max: 100, stat: Stat::Speed }, speed: ConstructStat { base: 100, value: 100, max: 100, stat: Stat::Speed },
// evasion: ConstructStat { base: 0, value: 0, max: 0, stat: Stat::Evasion }, // evasion: ConstructStat { base: 0, value: 0, max: 0, stat: Stat::Evasion },
skills: vec![], skills: vec![ConstructSkill::new(Skill::Attack)],
effects: vec![], effects: vec![],
specs: vec![], specs: vec![],
colours: Colours::new(), colours: Colours::new(),
@ -309,9 +309,9 @@ impl Construct {
} }
pub fn spec_add(&mut self, spec: Spec) -> Result<&mut Construct, Error> { pub fn spec_add(&mut self, spec: Spec) -> Result<&mut Construct, Error> {
if self.specs.len() >= 3 { // if self.specs.len() >= 3 {
return Err(err_msg("maximum specs equipped")); // return Err(err_msg("maximum specs equipped"));
} // }
self.specs.push(spec); self.specs.push(spec);
return Ok(self.calculate_colours()); return Ok(self.calculate_colours());

View File

@ -189,9 +189,9 @@ impl Game {
} }
self.pve_add_skills(); self.pve_add_skills();
if self.skill_phase_finished() { // pve game where both bots will have readied up //if self.skill_phase_finished() { // pve game where both bots will have readied up
return self.resolve_phase_start() // return self.resolve_phase_start()
} //}
self self
} }
@ -201,8 +201,7 @@ impl Game {
let mut rng = thread_rng(); let mut rng = thread_rng();
for mobs in self.players for mobs in self.players
.iter() .iter() {
.filter(|t| t.bot) {
let player_player = self.players.iter().find(|t| t.id != mobs.id).unwrap(); let player_player = self.players.iter().find(|t| t.id != mobs.id).unwrap();
for mob in mobs.constructs.iter() { for mob in mobs.constructs.iter() {

View File

@ -55,9 +55,9 @@ impl TimeControl {
fn game_time_seconds(&self) -> i64 { fn game_time_seconds(&self) -> i64 {
match self { match self {
TimeControl::Standard => 60, TimeControl::Standard => 1,
TimeControl::Slow => 120, TimeControl::Slow => 1,
TimeControl::Practice => panic!("practice game seconds called"), TimeControl::Practice => 1,
} }
} }
@ -77,12 +77,9 @@ impl TimeControl {
} }
pub fn game_phase_end(&self, resolution_time_ms: i64) -> Option<DateTime<Utc>> { pub fn game_phase_end(&self, resolution_time_ms: i64) -> Option<DateTime<Utc>> {
match self { Some(Utc::now()
TimeControl::Practice => None, .checked_add_signed(Duration::milliseconds(self.game_time_seconds() * 1000 + resolution_time_ms))
_ => Some(Utc::now() .expect("could not set game phase end"))
.checked_add_signed(Duration::milliseconds(self.game_time_seconds() * 1000 + resolution_time_ms))
.expect("could not set game phase end")),
}
} }
} }

View File

@ -604,71 +604,59 @@ impl Item {
// Lifes Upgrades // Lifes Upgrades
Item::LifeGG | Item::LifeGG |
Item::LifeGGPlus | Item::LifeGGPlus |
Item::LifeGGPlusPlus => format!("Increases construct GreenLife by {:?}. Item::LifeGGPlusPlus => format!("Increases construct GreenLife by {:?}.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::LifeRR | Item::LifeRR |
Item::LifeRRPlus | Item::LifeRRPlus |
Item::LifeRRPlusPlus => format!("Increases construct RedLife by {:?}. Item::LifeRRPlusPlus => format!("Increases construct RedLife by {:?}.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::LifeBB | Item::LifeBB |
Item::LifeBBPlus | Item::LifeBBPlus |
Item::LifeBBPlusPlus => format!("Increases construct BlueLife by {:?}. Item::LifeBBPlusPlus => format!("Increases construct BlueLife by {:?}.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::LifeRG | Item::LifeRG |
Item::LifeRGPlus | Item::LifeRGPlus |
Item::LifeRGPlusPlus => format!("Increases construct RedLife and GreenLife by {:?}. Item::LifeRGPlusPlus => format!("Increases construct RedLife and GreenLife by {:?}.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::LifeGB | Item::LifeGB |
Item::LifeGBPlus | Item::LifeGBPlus |
Item::LifeGBPlusPlus => format!("Increases construct GreenLife and BlueLife by {:?}. Item::LifeGBPlusPlus => format!("Increases construct GreenLife and BlueLife by {:?}.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::LifeRB | Item::LifeRB |
Item::LifeRBPlus | Item::LifeRBPlus |
Item::LifeRBPlusPlus => format!("Increases construct RedLife and BlueLife by {:?}. Item::LifeRBPlusPlus => format!("Increases construct RedLife and BlueLife by {:?}.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
// Power Upgrades // Power Upgrades
Item::PowerRR | Item::PowerRR |
Item::PowerRRPlus | Item::PowerRRPlus |
Item::PowerRRPlusPlus => format!("Increases construct RedPower by {:?}%. Item::PowerRRPlusPlus => format!("Increases construct RedPower by {:?}%.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::PowerBB | Item::PowerBB |
Item::PowerBBPlus | Item::PowerBBPlus |
Item::PowerBBPlusPlus => format!("Increases construct BluePower by {:?}%. Item::PowerBBPlusPlus => format!("Increases construct BluePower by {:?}%.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::PowerGG | Item::PowerGG |
Item::PowerGGPlus | Item::PowerGGPlus |
Item::PowerGGPlusPlus => format!("Increases construct GreenPower by {:?}%. Item::PowerGGPlusPlus => format!("Increases construct GreenPower by {:?}%.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::PowerRG | Item::PowerRG |
Item::PowerRGPlus | Item::PowerRGPlus |
Item::PowerRGPlusPlus => format!("Increases construct GreenPower and RedPower by {:?}%. Item::PowerRGPlusPlus => format!("Increases construct GreenPower and RedPower by {:?}%.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::PowerGB | Item::PowerGB |
Item::PowerGBPlus | Item::PowerGBPlus |
Item::PowerGBPlusPlus => format!("Increases construct GreenPower and BluePower by {:?}%. Item::PowerGBPlusPlus => format!("Increases construct GreenPower and BluePower by {:?}%.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::PowerRB | Item::PowerRB |
Item::PowerRBPlus | Item::PowerRBPlus |
Item::PowerRBPlusPlus => format!("Increases construct RedPower and BluePower by {:?}%. Item::PowerRBPlusPlus => format!("Increases construct RedPower and BluePower by {:?}%.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
// Speed Upgrades // Speed Upgrades
@ -689,8 +677,7 @@ impl Item {
Item::SpeedGGPlusPlus | Item::SpeedGGPlusPlus |
Item::SpeedRGPlusPlus | Item::SpeedRGPlusPlus |
Item::SpeedGBPlusPlus | Item::SpeedGBPlusPlus |
Item::SpeedRBPlusPlus => format!("Increases construct SpeedStat by {:?}%. Item::SpeedRBPlusPlus => format!("Increases construct SpeedStat by {:?}%.",
If your team meets total colour thresholds the spec provides additional bonuses.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),

View File

@ -7,7 +7,7 @@ use failure::err_msg;
use construct::{Construct, Colours}; use construct::{Construct, Colours};
use vbox::{Vbox, ItemType, VboxIndices}; use vbox::{Vbox, ItemType, VboxIndices};
use item::{Item, ItemEffect}; use item::{Item, ItemEffect, get_combos};
use effect::{Effect}; use effect::{Effect};
const DISCARD_COST: usize = 2; const DISCARD_COST: usize = 2;
@ -297,17 +297,21 @@ impl Player {
Some(ItemEffect::Skill) => { Some(ItemEffect::Skill) => {
let skill = item.into_skill().ok_or(format_err!("item {:?} has no associated skill", item))?; let skill = item.into_skill().ok_or(format_err!("item {:?} has no associated skill", item))?;
let construct = self.construct_get(construct_id)?; let construct = self.construct_get(construct_id)?;
// done here because i teach them a tonne of skills for tests
let max_skills = 3;
if construct.skills.len() >= max_skills {
return Err(format_err!("construct at max skills ({:?})", max_skills));
}
if construct.knows(skill) {
return Err(format_err!("construct already knows skill ({:?})" , skill));
}
construct.skills = vec![];
construct.learn_mut(skill); construct.learn_mut(skill);
// // done here because i teach them a tonne of skills for tests
// let max_skills = 1;
// if construct.skills.len() >= max_skills {
// return Err(format_err!("construct at max skills ({:?})", max_skills));
// }
// if construct.knows(skill) {
// return Err(format_err!("construct already knows skill ({:?})" , skill));
// }
// construct.learn_mut(skill);
}, },
Some(ItemEffect::Spec) => { Some(ItemEffect::Spec) => {
let spec = item.into_spec().ok_or(format_err!("item {:?} has no associated spec", item))?; let spec = item.into_spec().ok_or(format_err!("item {:?} has no associated spec", item))?;
@ -318,15 +322,76 @@ impl Player {
None => return Err(err_msg("item has no effect on constructs")), None => return Err(err_msg("item has no effect on constructs")),
} }
// now the item has been applied let construct = self.construct_get(construct_id)?;
// recalculate the stats of the whole player
let player_colours = self.constructs.iter().fold(Colours::new(), |tc, c| { // force 1 skill for auto
Colours { let mut colour_counts = vec![
red: tc.red + c.colours.red, (Item::Red, construct.colours.red),
green: tc.green + c.colours.green, (Item::Green, construct.colours.green),
blue: tc.blue + c.colours.blue (Item::Blue, construct.colours.blue),
} ];
}); colour_counts.sort_unstable_by_key(|cc| cc.1);
colour_counts.reverse();
println!("{:?}", colour_counts);
let total = (construct.colours.red + construct.colours.green + construct.colours.blue) as f64;
if total != 0.0 {
let colour_pcts = colour_counts.iter_mut()
.map(|cc| (cc.0, cc.1 as f64 / total))
.collect::<Vec<_>>();
println!("{:?}", colour_pcts);
let skill_item = match item.into_skill().is_some() {
true => item,
false => construct.skills[0].skill.base(),
};
let colour_skill = {
// no colours
if colour_pcts[0].1.is_infinite() {
construct.skills[0].skill
} else {
let mut skill_item_combo = {
if colour_pcts[0].1 > 0.70 {
vec![skill_item, colour_pcts[0].0, colour_pcts[0].0]
} else if colour_pcts[1].1 > 0.4 {
vec![skill_item, colour_pcts[0].0, colour_pcts[1].0]
} else {
// special triple skill_item
vec![skill_item, colour_pcts[0].0, colour_pcts[1].0]
}
};
skill_item_combo.sort_unstable();
println!("{:?}", skill_item_combo);
let combos = get_combos();
let combo = combos.iter().find(|c| c.components == skill_item_combo)
.ok_or(err_msg("no combo for colour skill"))?;
combo.item.into_skill()
.ok_or(format_err!("item {:?} has no associated skill", combo.item))?
}
};
// unlearn everything
construct.skills = vec![];
construct.learn_mut(colour_skill);
}
// // now the item has been applied
// // recalculate the stats of the whole player
// let player_colours = self.constructs.iter().fold(Colours::new(), |tc, c| {
// Colours {
// red: tc.red + c.colours.red,
// green: tc.green + c.colours.green,
// blue: tc.blue + c.colours.blue
// }
// });
let player_colours = Colours { red: 0, blue: 0, green: 0 };
for construct in self.constructs.iter_mut() { for construct in self.constructs.iter_mut() {
construct.apply_modifiers(&player_colours); construct.apply_modifiers(&player_colours);

View File

@ -896,12 +896,12 @@ impl Skill {
.collect::<Vec<Colour>>(); .collect::<Vec<Colour>>();
} }
fn _base(&self) -> Skill { pub fn base(&self) -> Item {
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()
.find(|i| bases.contains(i)) { .find(|i| bases.contains(i)) {
Some(i) => i.into_skill().unwrap(), Some(i) => *i,
None => panic!("{:?} has no base item", self), None => panic!("{:?} has no base item", self),
} }
} }

View File

@ -27,11 +27,11 @@ pub enum ItemType {
Specs, Specs,
} }
const STORE_COLOURS_CAPACITY: usize = 6; const STORE_COLOURS_CAPACITY: usize = 0;
const STORE_SKILLS_CAPACITY: usize = 3; const STORE_SKILLS_CAPACITY: usize = 2;
const STORE_SPECS_CAPACITY: usize = 3; const STORE_SPECS_CAPACITY: usize = 3;
const STASH_CAPACITY: usize = 6; const STASH_CAPACITY: usize = 0;
const STARTING_ATTACK_COUNT: usize = 3; const STARTING_ATTACK_COUNT: usize = 0;
impl Vbox { impl Vbox {
pub fn new() -> Vbox { pub fn new() -> Vbox {
@ -92,9 +92,24 @@ impl Vbox {
let skill_dist = WeightedIndex::new(skills.iter().map(|item| item.1)).unwrap(); let skill_dist = WeightedIndex::new(skills.iter().map(|item| item.1)).unwrap();
let specs = vec![ let specs = vec![
(Item::Power, 1), (Item::PowerGG, 1),
(Item::Life, 1), (Item::PowerRR, 1),
(Item::Speed, 1), (Item::PowerBB, 1),
(Item::PowerRG, 1),
(Item::PowerGB, 1),
(Item::PowerRB, 1),
(Item::LifeGG, 1),
(Item::LifeRR, 1),
(Item::LifeBB, 1),
(Item::LifeRG, 1),
(Item::LifeGB, 1),
(Item::LifeRB, 1),
(Item::SpeedGG, 1),
(Item::SpeedRR, 1),
(Item::SpeedBB, 1),
(Item::SpeedRG, 1),
(Item::SpeedGB, 1),
(Item::SpeedRB, 1),
]; ];
let spec_dist = WeightedIndex::new(specs.iter().map(|item| item.1)).unwrap(); let spec_dist = WeightedIndex::new(specs.iter().map(|item| item.1)).unwrap();

View File

@ -8,7 +8,6 @@ upstream mnml_ws {
server 127.0.0.1:40055; server 127.0.0.1:40055;
} }
map $http_upgrade $connection_upgrade { map $http_upgrade $connection_upgrade {
default upgrade; default upgrade;
'' close; '' close;

View File

@ -13,7 +13,7 @@ map $http_upgrade $connection_upgrade {
} }
server { server {
server_name calamarirace.team; server_name sixtysix.pro;
gzip on; gzip on;
gzip_vary on; gzip_vary on;
@ -50,8 +50,8 @@ server {
listen [::]:443 ssl ipv6only=on; # managed by Certbot listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/calamarirace.team/fullchain.pem; # managed by Certbot ssl_certificate /etc/letsencrypt/live/sixtysix.pro/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/calamarirace.team/privkey.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/sixtysix.pro/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
} }

View File

@ -3,7 +3,7 @@ module.exports = {
connection: { connection: {
database: 'mnml', database: 'mnml',
user: 'mnml', user: 'mnml',
password: 'gggggggggg', password: '62d72d2e7905d267a0910cc01164c500',
}, },
pool: { pool: {
min: 2, min: 2,

View File

@ -2,9 +2,9 @@ const uuidv4 = require('uuid/v4');
// give everybody the shapes mtx // give everybody the shapes mtx
exports.up = async knex => { exports.up = async knex => {
await knex.raw(` // await knex.raw(`
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; // CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
`); // `);
await knex.raw(` await knex.raw(`
INSERT INTO mtx (id, account, variant) INSERT INTO mtx (id, account, variant)

View File

@ -14,8 +14,8 @@
"author": "", "author": "",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"knex": "^0.15.2", "knex": "^0.21",
"pg": "^7.4.3", "pg": "^8",
"qrcode-svg": "^1.0.0", "qrcode-svg": "^1.0.0",
"request": "^2.88.0", "request": "^2.88.0",
"sdftosvg": "0.0.4", "sdftosvg": "0.0.4",

View File

@ -15,8 +15,8 @@ chrono = { version = "0.4", features = ["serde"] }
bcrypt = "0.2" bcrypt = "0.2"
postgres = { version = "0.15", features = ["with-uuid", "with-chrono"] } postgres = { version = "0.15", features = ["with-uuid", "with-chrono"] }
r2d2 = "*" r2d2 = "0.8"
r2d2_postgres = "*" r2d2_postgres = "0.14"
fallible-iterator = "0.1" fallible-iterator = "0.1"
failure = "0.1" failure = "0.1"

View File

@ -108,7 +108,7 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
pub fn start() { pub fn start() {
#[cfg(unix)] #[cfg(unix)]
setup_logger().unwrap(); setup_logger().unwrap();
dotenv::from_path(Path::new("/etc/mnml/gs.conf")).ok(); dotenv::from_path(Path::new("/etc/mnml/gs.conf")).unwrap();
info!("starting server"); info!("starting server");
let pool = pg::create_pool(); let pool = pg::create_pool();

View File

@ -66,6 +66,8 @@ pub fn create_pool() -> Pool<PostgresConnectionManager> {
let url = env::var("DATABASE_URL") let url = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set"); .expect("DATABASE_URL must be set");
println!("{:?}", url);
let manager = PostgresConnectionManager::new(url, TlsMode::None) let manager = PostgresConnectionManager::new(url, TlsMode::None)
.expect("could not instantiate pg manager"); .expect("could not instantiate pg manager");