Merge branch 'release/1.5.4'

This commit is contained in:
ntr 2019-10-10 18:33:41 +11:00
commit 74f16bc25c
26 changed files with 1275 additions and 118 deletions

View File

@ -7,6 +7,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed ### Fixed
### Changed ### Changed
## [0.1.5] - YYYY-MM-DD
### Changed
`Recharge` Skill multiplier reduced 85/130/200 -> 70/110/170
`Absorption` Skill duration reduced 5/7/9 -> 3/5/7
## [0.1.4 2019-09-18] ## [0.1.4 2019-09-18]
### Changed ### Changed

View File

@ -1 +1 @@
1.5.3 1.5.4

View File

@ -14,6 +14,7 @@
## SOON (Before or After PAX) ## SOON (Before or After PAX)
* Invert recharge
* bot game grind * bot game grind
* ACP * ACP
* essential * essential

View File

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

View File

@ -13,6 +13,8 @@ rm -rf dist
npm i npm i
npm run build npm run build
cp tos.html dist/
# echo "Building acp version $VERSION" # echo "Building acp version $VERSION"
# cd $MNML_PATH/acp # cd $MNML_PATH/acp
# rm -rf dist # rm -rf dist

View File

@ -50,19 +50,31 @@ aside {
} }
} }
// button.ready:enabled {
// &:hover {
// color: forestgreen;
// border-color: forestgreen;
// }
// &:active, &:focus, &.enabled {
// background: forestgreen;
// color: black;
// border-color: forestgreen;
// }
// }
button.ready:enabled { button.ready:enabled {
&:hover {
color: forestgreen; color: forestgreen;
border-color: forestgreen; border-color: forestgreen;
}
&:active, &:focus, &.enabled { &:hover {
background: forestgreen; background: forestgreen;
color: black; color: black;
border-color: forestgreen; border-color: forestgreen;
} }
} }
.timer-container { .timer-container {
grid-area: timer; grid-area: timer;
@ -94,6 +106,7 @@ aside {
.ready { .ready {
color: forestgreen; color: forestgreen;
// animation: ready 2s linear 0s infinite alternate;
transition-property: color, background; transition-property: color, background;
transition-duration: 0.25s; transition-duration: 0.25s;
transition-timing-function: ease; transition-timing-function: ease;
@ -138,3 +151,13 @@ aside {
border: 2px solid black; border: 2px solid black;
} }
} }
@keyframes ready {
from {
border-color: @gray-exists;
}
to {
border-color: forestgreen;
}
}

View File

@ -17,6 +17,7 @@
@media (max-width: 800px) { @media (max-width: 800px) {
.instance { .instance {
overflow-y: scroll;
font-size: 8pt; font-size: 8pt;
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;

View File

@ -129,13 +129,30 @@ section {
.demo { .demo {
margin-top: 1em; margin-top: 1em;
display: grid; display: block;
grid-template-areas:
"vinfo game"
"vcons game";
grid-template-columns: 1fr 1fr; button {
grid-template-rows: min-content 1fr; pointer-events: none;
}
section {
margin-bottom: 0.5em;
div:first-child {
padding-right: 1em;
}
}
.construct-section {
.construct-list {
height: 25em;
grid-area: unset;
.instance-construct {
// border: 0;
}
}
}
.colour-info { .colour-info {
grid-area: vinfo; grid-area: vinfo;
@ -152,17 +169,9 @@ section {
} }
} }
.vbox-demo {
grid-area: vinfo;
}
.game-demo { .game-demo {
grid-area: game;
display: grid;
grid-template-columns: 1fr 2fr;
.game { .game {
height: 25em;
display: flex; display: flex;
flex-flow: column; flex-flow: column;
@ -171,15 +180,6 @@ section {
} }
} }
} }
.construct-list {
grid-area: vcons;
height: 100%;
svg {
height: 100%;
}
}
} }
@media (max-width: 800px) { @media (max-width: 800px) {
@ -191,24 +191,6 @@ section {
} }
} }
.demo {
grid-template-columns: 1fr;
grid-template-areas:
"vinfo"
"vcons"
"game"
"game";
.construct-list .instance-construct:not(:first-child) {
display: none;
}
.game-demo {
grid-template-columns: 1fr;
}
}
.menu .team { .menu .team {
grid-template-columns: 1fr; grid-template-columns: 1fr;

View File

@ -14,7 +14,7 @@ html body {
-ms-user-select: none; -ms-user-select: none;
overflow-x: hidden; overflow-x: hidden;
overflow-y: hidden; // overflow-y: hidden;
} }
#mnml { #mnml {
@ -26,14 +26,14 @@ html body {
/* stops inspector going skitz*/ /* stops inspector going skitz*/
overflow-x: hidden; overflow-x: hidden;
overflow-y: hidden; // overflow-y: hidden;
} }
@media (min-width: 1921px) { // @media (min-width: 1921px) {
html, body, #mnml { // html, body, #mnml {
font-size: 16pt; // font-size: 16pt;
} // }
} // }
html { html {
box-sizing: border-box; box-sizing: border-box;
@ -162,13 +162,12 @@ svg {
fill: none; fill: none;
stroke: whitesmoke; stroke: whitesmoke;
stroke-width: 0.5em; stroke-width: 0.5em;
height: 2em; height: 1.5em;
} }
table { table {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
/*margin-bottom: 2em;*/
margin-bottom: 0; margin-bottom: 0;
} }

View File

@ -123,4 +123,8 @@
padding: 0; padding: 0;
} }
} }
.play-p {
display: none;
}
} }

View File

@ -16,7 +16,6 @@
], ],
"start_url": "/index.html", "start_url": "/index.html",
"display": "fullscreen", "display": "fullscreen",
"orientation": "portrait",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#000000" "background_color": "#000000"
} }

View File

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

View File

@ -45,8 +45,6 @@ function Demo(args) {
const { combiner, items, equipping, equipped, players } = demo; const { combiner, items, equipping, equipped, players } = demo;
console.log(items);
const vboxDemo = () => { const vboxDemo = () => {
function inventoryBtn(i, j) { function inventoryBtn(i, j) {
if (!i) return <button disabled class='empty' >&nbsp;</button>; if (!i) return <button disabled class='empty' >&nbsp;</button>;
@ -86,7 +84,7 @@ function Demo(args) {
function inventoryElement() { function inventoryElement() {
return ( return (
<div class="vbox"> <div class="vbox visible">
<div class='vbox-section'> <div class='vbox-section'>
<h2 class='colour-info'> <h2 class='colour-info'>
VBOX PHASE {shapes.Red()} {shapes.Green()} {shapes.Blue()} VBOX PHASE {shapes.Red()} {shapes.Green()} {shapes.Blue()}
@ -118,14 +116,12 @@ function Demo(args) {
? 'equipping empty gray' ? 'equipping empty gray'
: 'empty gray'; : 'empty gray';
return ( const constructEl = c => (
<div class='news construct-list'> <div class="instance-construct visible">
{players[0].constructs.map((c, i) => (
<div class="instance-construct" key={i}>
<h2 class="name" >{c.name}</h2> <h2 class="name" >{c.name}</h2>
<ConstructAvatar construct={c} /> <ConstructAvatar construct={c} />
<div class="skills"> <div class="skills">
{i === 0 && equipped {equipped
? <button>Strike</button> ? <button>Strike</button>
: <button disabled={!equipping} class={btnClass}>SKILL</button> : <button disabled={!equipping} class={btnClass}>SKILL</button>
} }
@ -137,20 +133,31 @@ function Demo(args) {
<div class="stats"> <div class="stats">
</div> </div>
</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>
<div class='construct-list'>
{constructEl(players[0].constructs[0])}
</div>
</section>
); );
}; };
const gameDemo = () => { const gameDemo = () => {
return ( return (
<div class="game-demo"> <section class="game-demo">
<div> <div>
<h2>COMBAT PHASE</h2> <h2>COMBAT PHASE</h2>
<p>Battle your opponent using dynamic team builds from the VBOX phase.</p> <p>Battle your opponent using dynamic team builds from the VBOX phase.</p>
<p>Crafted skills can be used to damage the opponent or support your team.</p> <p>The skills crafted can be used to damage the opponent or support your team.</p>
<p>Turn based combat, each team picks targets for their skills during this phase.</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> <p>The damage dealt by skills, cast order and construct life depend on your decisions in the VBOX phase.</p>
</div> </div>
<div class="game"> <div class="game">
<div class="game-construct"> <div class="game-construct">
@ -167,7 +174,7 @@ function Demo(args) {
</div> </div>
</div> </div>
</div> </div>
</div> </section>
); );
}; };

View File

@ -93,12 +93,12 @@ class GameConstruct extends Component {
const skills = range(0, 3) const skills = range(0, 3)
.map(j => <SkillBtn key={j} construct={construct} i={j} j={i} animating={animating} />); .map(j => <SkillBtn key={j} construct={construct} i={j} j={i} animating={animating} />);
let crypSkills = <div> &nbsp; </div>; let crypSkills = <div></div>;
if (player) crypSkills = (<div class="skills"> {skills} </div>); if (player) crypSkills = (<div class="skills"> {skills} </div>);
const effects = construct.effects.length const effects = construct.effects.length
? construct.effects.map(c => <div key={c.effect}>{c.effect} - {c.duration}T</div>) ? construct.effects.map(c => <div key={c.effect}>{c.effect} - {c.duration}T</div>)
: <div>&nbsp;</div>; : null;
return ( return (
<div <div

View File

@ -76,7 +76,7 @@ class Instance extends Component {
} }
return ( return (
<main id="instance" class='instance' onClick={instanceClick} onMouseOver={() => setInfo(null)} onTouchMove={onTouchMove}> <main id="instance" class='instance' onClick={instanceClick} onMouseOver={() => setInfo(null)}>
<Vbox /> <Vbox />
<InfoContainer /> <InfoContainer />
<InstanceConstructsContainer /> <InstanceConstructsContainer />
@ -85,12 +85,19 @@ class Instance extends Component {
} }
componentDidMount() { componentDidMount() {
this.bindSwipes(); if (!this.h) this.bindSwipes();
}
componentDidUpdate() {
if (!this.h) this.bindSwipes();
} }
bindSwipes() { bindSwipes() {
const instance = document.getElementById('instance'); const instance = document.getElementById('instance');
if (!instance) return setTimeout(this.bindSwipes, 50); if (!instance) {
console.log('no instance, binding in 50');
return setTimeout(this.bindSwipes, 50);
}
if (this.h) this.h.destroy(); if (this.h) this.h.destroy();
this.h = new Hammer(instance); this.h = new Hammer(instance);
this.h.on('swiperight', () => { this.h.on('swiperight', () => {

View File

@ -38,6 +38,15 @@ function InstanceCtrlBtns(args) {
const finished = instance && instance.phase === 'Finished'; const finished = instance && instance.phase === 'Finished';
// cheeky to make sure nubs don't just abandon their first game
const beingNub = instance.phase_end
&& instance.phase === 'Lobby'
&& Date.parse(instance.phase_end) - Date.now() < 2000;
if (beingNub) {
sendReady();
}
return ( return (
<div class="instance-ctrl-btns"> <div class="instance-ctrl-btns">
<button disabled={true} >Chat</button> <button disabled={true} >Chat</button>

View File

@ -94,7 +94,7 @@ function Play(args) {
<section class="top"> <section class="top">
<div class="news"> <div class="news">
<h1>v{VERSION}</h1> <h1>v{VERSION}</h1>
<p>Use the buttons on the right to join an instance.</p> <p class="play-p">Use the buttons on the right to join an instance.</p>
<p> <p>
Select <b>PVP</b> to play against other players.<br /> Select <b>PVP</b> to play against other players.<br />
Select <b>INVITE</b> then click <b>COPY LINK</b> to generate an instance invitation for a friend.<br /> Select <b>INVITE</b> then click <b>COPY LINK</b> to generate an instance invitation for a friend.<br />

View File

@ -34,7 +34,7 @@ function Register(args) {
submitRegister, submitRegister,
} = args; } = args;
const { password, confirm, name } = this.state; const { password, confirm, name, terms } = this.state;
const registerSubmit = (event) => { const registerSubmit = (event) => {
event.preventDefault(); event.preventDefault();
@ -45,7 +45,7 @@ function Register(args) {
password === confirm; password === confirm;
const registerDisabled = () => { const registerDisabled = () => {
return !(registerConfirm() && password && name); return !(registerConfirm() && password && name && terms);
} }
return ( return (
@ -74,6 +74,14 @@ function Register(args) {
value={this.state.confirm} value={this.state.confirm}
onInput={linkState(this, 'confirm')} onInput={linkState(this, 'confirm')}
/> />
<div>
<input
type="checkbox"
onInput={linkState(this, 'terms')
}/>
&nbsp; Confirm agreement to terms of service &nbsp;
<button onClick={() => window.open('/tos.html')}>VIEW</button>
</div>
<button <button
class="login-btn" class="login-btn"
disabled={registerDisabled()} disabled={registerDisabled()}

1098
client/tos.html Normal file

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,9 @@ map $http_upgrade $connection_upgrade {
server { server {
server_name sixtysix.pro; server_name sixtysix.pro;
auth_basic "who dis";
auth_basic_user_file /etc/mnml/htpasswd.users;
location / { location / {
root /var/lib/mnml/public/current; root /var/lib/mnml/public/current;
index index.html; index index.html;

View File

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

View File

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

View File

@ -161,12 +161,12 @@ impl Game {
self.skill_phase_start(0) self.skill_phase_start(0)
} }
fn skill_phase_start(mut self, resolution_time: i64) -> Game { fn skill_phase_start(mut self, resolution_animation_ms: i64) -> Game {
self.phase_start = Utc::now() self.phase_start = Utc::now()
.checked_add_signed(Duration::milliseconds(resolution_time)) .checked_add_signed(Duration::milliseconds(resolution_animation_ms))
.expect("could not set phase start"); .expect("could not set phase start");
self.phase_end = self.time_control.game_phase_end(resolution_time); self.phase_end = self.time_control.game_phase_end(resolution_animation_ms);
for player in self.players.iter_mut() { for player in self.players.iter_mut() {
if player.skills_required() == 0 { if player.skills_required() == 0 {
@ -426,12 +426,12 @@ impl Game {
// temp vec of this round's resolving skills // temp vec of this round's resolving skills
// because need to check cooldown use before pushing them into the complete list // because need to check cooldown use before pushing them into the complete list
let mut casts = vec![]; let mut casts = vec![];
let mut resolution_delay = 0; let mut r_animation_ms = 0;
while let Some(cast) = self.stack.pop() { while let Some(cast) = self.stack.pop() {
// info!("{:} casts ", cast); // info!("{:} casts ", cast);
let mut resolutions = resolution_steps(&cast, &mut self); let mut resolutions = resolution_steps(&cast, &mut self);
resolution_delay = resolutions.iter().fold(resolution_delay, |acc, r| acc + r.clone().get_delay()); r_animation_ms = resolutions.iter().fold(r_animation_ms, |acc, r| acc + r.clone().get_delay());
self.resolved.append(&mut resolutions); self.resolved.append(&mut resolutions);
// while let Some(resolution) = resolutions.pop() { // while let Some(resolution) = resolutions.pop() {
@ -457,7 +457,7 @@ impl Game {
return self.finish() return self.finish()
} }
self.skill_phase_start(resolution_delay) self.skill_phase_start(r_animation_ms)
} }
fn progress_durations(&mut self, resolved: &Vec<Cast>) -> &mut Game { fn progress_durations(&mut self, resolved: &Vec<Cast>) -> &mut Game {

View File

@ -530,13 +530,13 @@ pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result<Insta
let instance_bytes = to_vec(&instance)?; let instance_bytes = to_vec(&instance)?;
let query = " let query = "
INSERT INTO instances (id, data) INSERT INTO instances (id, data, upkeep)
VALUES ($1, $2) VALUES ($1, $2, $3)
RETURNING id; RETURNING id;
"; ";
let result = tx let result = tx
.query(query, &[&instance.id, &instance_bytes])?; .query(query, &[&instance.id, &instance_bytes, &instance.phase_end])?;
result.iter().next().ok_or(format_err!("no instances written"))?; result.iter().next().ok_or(format_err!("no instances written"))?;

View File

@ -1,7 +1,7 @@
use rand::prelude::*; use rand::prelude::*;
use rand::{thread_rng}; use rand::{thread_rng};
const FIRSTS: [&'static str; 51] = [ const FIRSTS: [&'static str; 53] = [
"artificial", "artificial",
"ambient", "ambient",
"borean", "borean",
@ -43,9 +43,11 @@ const FIRSTS: [&'static str; 51] = [
"ossified", "ossified",
"orbiting", "orbiting",
"piscine", "piscine",
"polar",
"purified", "purified",
"recalcitrant", "recalcitrant",
"rogue", "rogue",
"sealed",
"subversive", "subversive",
"subterranean", "subterranean",
"supercooled", "supercooled",
@ -55,13 +57,16 @@ const FIRSTS: [&'static str; 51] = [
"weary", "weary",
]; ];
const LASTS: [&'static str; 56] = [ const LASTS: [&'static str; 63] = [
"artifact", "artifact",
"assembly", "assembly",
"antenna",
"alloy", "alloy",
"carrier",
"carbon", "carbon",
"console", "console",
"construct", "construct",
"coordinates",
"craft", "craft",
"core", "core",
"design", "design",
@ -77,18 +82,22 @@ const LASTS: [&'static str; 56] = [
"fossil", "fossil",
"frequency", "frequency",
"function", "function",
"fusion",
"fission",
"information", "information",
"insulator", "insulator",
"layout", "layout",
"lifeform", "lifeform",
"liquid",
"landmass", "landmass",
"lens", "lens",
"mass",
"mantle", "mantle",
"magnetism", "magnetism",
"mechanism", "mechanism",
"mountain", "mountain",
"nectar", "nectar",
"oak", "nebula",
"oxide", "oxide",
"orbit", "orbit",
"pattern", "pattern",

View File

@ -294,7 +294,7 @@ fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) ->
match event { match event {
Event::Damage { amount, skill, mitigation: _, colour: c } => { Event::Damage { amount, skill, mitigation: _, colour: c } => {
if target.affected(Effect::Electric) { if target.affected(Effect::Electric) && !skill.is_tick() {
let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter()
.find(|e| e.effect == Effect::Electric).unwrap().clone(); .find(|e| e.effect == Effect::Electric).unwrap().clone();
match meta { match meta {
@ -786,9 +786,9 @@ impl Skill {
Skill::ReflectPlus => 70, Skill::ReflectPlus => 70,
Skill::ReflectPlusPlus => 100, Skill::ReflectPlusPlus => 100,
Skill::Recharge=> 85, //Recharge red and blue life (heal) Skill::Recharge=> 70, //Recharge red and blue life (heal)
Skill::RechargePlus => 130, Skill::RechargePlus => 110,
Skill::RechargePlusPlus => 200, Skill::RechargePlusPlus => 170,
Skill::Sustain => 120, // Recharge red life (heal) Skill::Sustain => 120, // Recharge red life (heal)
Skill::SustainPlus => 150, Skill::SustainPlus => 150,
@ -907,9 +907,9 @@ impl Skill {
Skill::AbsorbPlusPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 4, Skill::AbsorbPlusPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 4,
meta: Some(EffectMeta::Skill(Skill::AbsorptionPlusPlus)), tick: None}], meta: Some(EffectMeta::Skill(Skill::AbsorptionPlusPlus)), tick: None}],
Skill::Absorption => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}], Skill::Absorption => vec![ConstructEffect {effect: Effect::Absorption, duration: 3, meta: None, tick: None}],
Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 7, meta: None, tick: None}], Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}],
Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 9, meta: None, tick: None}], Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 7, meta: None, tick: None}],
Skill::Hybrid => vec![ConstructEffect {effect: Effect::Hybrid, duration: 2, Skill::Hybrid => vec![ConstructEffect {effect: Effect::Hybrid, duration: 2,
meta: Some(EffectMeta::Multiplier(150)), tick: None }], meta: Some(EffectMeta::Multiplier(150)), tick: None }],