diff --git a/.editorconfig b/.editorconfig index 4d70db8..b81924f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,6 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -[*.{js,css}] +[*.{js,css,js.default}] indent_size = 2 indent_style = space diff --git a/.gitignore b/.gitignore index bbabf79..59680f9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ data public/out public/lib -config.js +config.client.js +config.server.js diff --git a/Makefile b/Makefile index e0e89b8..ee7f684 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,12 @@ .PHONY: all install clean cards score js -all: install clean cards score js +all: install cards score js node := ${CURDIR}/node_modules all_sets := ${CURDIR}/data/AllSets.json traceur := ${node}/.bin/traceur -config := config.js + +client_config := config.client.js +server_config := config.server.js ${traceur}: install @@ -32,14 +34,16 @@ ${all_sets}: curl -so ${all_sets} https://mtgjson.com/json/AllSets.json score: - -node src/make score #ignore errors + -node src/make score -js: ${traceur} ${all_sets} ${config} +js: ${traceur} ${all_sets} ${client_config} ${traceur} --out public/lib/app.js public/src/init.js # "order-only" prerequisite -${config}: | ${config}.default +${client_config}: | ${client_config}.default + cp $| $@ +${server_config}: | ${server_config}.default cp $| $@ -run: js +run: js ${server_config} node run diff --git a/README.md b/README.md index c826cb0..8e219c9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# drafts.ninja +# drafts.ninja [![Stories in Ready](https://badge.waffle.io/arxanas/drafts.ninja.png?label=ready&title=Ready)](https://waffle.io/arxanas/drafts.ninja) [![Gitter chat](https://badges.gitter.im/arxanas/drafts.ninja.png)](https://gitter.im/arxanas/drafts.ninja) [drafts.ninja](http://drafts.ninja) is a fork of aeosynth's `draft` project. It supports all of the features of `draft` and more. Here are some of the diff --git a/app.js b/app.js index 0b01994..7016bcf 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,4 @@ -const CONFIG = require('./config') +const CONFIG = require('./config.server') var http = require('http') var eio = require('engine.io') diff --git a/config.client.js.default b/config.client.js.default new file mode 100644 index 0000000..c90aefa --- /dev/null +++ b/config.client.js.default @@ -0,0 +1,20 @@ +let d = React.DOM + +export let STRINGS = { + BRANDING: { + SITE_NAME: ['drafts', 'ninja'], + DEFAULT_USERNAME: 'ninja', + }, + + PAGE_SECTIONS: { + MOTD: null, // message of the day; can be a React element + + FOOTER: d.div({}, + d.strong({}, 'drafts.ninja'), + ' is a fork of the ', + d.code({}, 'draft'), + ' project by aeosynth. Contributions welcome! ', + d.a({ href: 'https://github.com/arxanas/drafts.ninja' }, + 'https://github.com/arxanas/drafts.ninja')), + }, +} diff --git a/config.js.default b/config.js.default deleted file mode 100644 index c522ecd..0000000 --- a/config.js.default +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - PORT: 1337, - MOTD: null, // message of the day -} diff --git a/config.server.js.default b/config.server.js.default new file mode 100644 index 0000000..e799072 --- /dev/null +++ b/config.server.js.default @@ -0,0 +1,8 @@ +module.exports = { + PORT: 1337, + STRINGS: { + BRANDING: { + DEFAULT_USERNAME: 'ninja', + } + } +} diff --git a/package.json b/package.json index c530ee9..2959712 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node-fetch": "^1.0.3", "send": "^0.11.1", "traceur": "0.0.65", - "utils": "git://github.com/aeosynth/utils" + "utils": "git://github.com/arxanas/utils" }, "devDependencies": { "normalize.css": "^3.0.1", diff --git a/public/index.html b/public/index.html index 92367ed..539847b 100644 --- a/public/index.html +++ b/public/index.html @@ -2,7 +2,6 @@ - drafts.ninja diff --git a/public/src/app.js b/public/src/app.js index ac89c3f..39ed955 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -1,5 +1,6 @@ import _ from '../lib/utils' import EventEmitter from '../lib/ee' +import {STRINGS} from './config' function message(msg) { let args = JSON.parse(msg) @@ -11,7 +12,7 @@ let App = { state: { id: null, - name: 'ninja', + name: STRINGS.BRANDING.DEFAULT_USERNAME, numUsers: 0, numPlayers: 0, @@ -35,7 +36,8 @@ let App = { packs: 3, bots: true, - timer: true, + useTimer: true, + timerLength: 40, // seconds beep: false, chat: true, diff --git a/public/src/cards.js b/public/src/cards.js index 32a877e..0f3de45 100644 --- a/public/src/cards.js +++ b/public/src/cards.js @@ -93,8 +93,8 @@ let events = { App.send('readyToStart', e.target.checked) }, start() { - let {bots, timer} = App.state - let options = [bots, timer] + let {bots, useTimer, timerLength} = App.state + let options = {bots, useTimer, timerLength} App.send('start', options) }, pack(cards) { @@ -390,6 +390,13 @@ function Key(groups, sort) { return o } +function sortLandsBeforeNonLands(lhs, rhs) { + let isLand = x => x.type.toLowerCase().indexOf('land') !== -1 + let lhsIsLand = isLand(lhs) + let rhsIsLand = isLand(rhs) + return rhsIsLand - lhsIsLand +} + export function getZone(zoneName) { let zone = Zones[zoneName] @@ -401,7 +408,7 @@ export function getZone(zoneName) { let {sort} = App.state let groups = _.group(cards, sort) for (let key in groups) - _.sort(groups[key], 'color', 'cmc', 'name') + _.sort(groups[key], sortLandsBeforeNonLands, 'color', 'cmc', 'name') groups = Key(groups, sort) diff --git a/public/src/components/deck-settings.js b/public/src/components/deck-settings.js index 27b3101..4045bc6 100644 --- a/public/src/components/deck-settings.js +++ b/public/src/components/deck-settings.js @@ -12,7 +12,7 @@ function Lands() { let inputs = BASICS.map(cardName => d.td({}, d.input({ - className: 'num-lands', + className: 'number', min: 0, onChange: App._emit('land', zoneName, cardName), type: 'number', @@ -27,7 +27,7 @@ function Lands() { let suggest = d.tr({}, d.td({}, 'deck size'), d.td({}, d.input({ - className: 'num-lands', + className: 'number', min: 0, onChange: App._emit('deckSize'), type: 'number', diff --git a/public/src/components/game.js b/public/src/components/game.js index 9831485..4fd836f 100644 --- a/public/src/components/game.js +++ b/public/src/components/game.js @@ -69,7 +69,22 @@ export default React.createClass({ let startControls = d.div({}, d.div({}, `Format: ${App.state.format}`), LBox('bots', 'bots'), - LBox('timer', 'timer'), + d.div({}, + d.label({}, + d.input({ + type: 'checkbox', + checkedLink: App.link('useTimer'), + }), ' use '), + d.label({}, + d.input({ + className: 'number', + disabled: !App.state.useTimer, + min: 0, + max: 60, + step: 5, + type: 'number', + valueLink: App.link('timerLength'), + }), '-second timer')), d.div({}, startButton, readyReminderText)) return d.fieldset({ className: 'start-controls fieldset' }, diff --git a/public/src/components/grid.js b/public/src/components/grid.js index 60db5c5..3d81a21 100644 --- a/public/src/components/grid.js +++ b/public/src/components/grid.js @@ -1,6 +1,7 @@ import _ from '../../lib/utils' import App from '../app' import {getZone} from '../cards' +import {Spaced} from './spacer' let d = React.DOM export default React.createClass({ @@ -30,9 +31,8 @@ function zone(zoneName) { }))) return d.div({ className: 'zone' }, - d.h1({}, + d.h1({}, Spaced( d.span({}, zoneName), - d.span({ className: 'spacer-dot' }), - d.span({}, `${cards.length} ${cards.length === 1 ? 'card' : 'cards' }`)), + d.span({}, `${cards.length} ${cards.length === 1 ? 'card' : 'cards' }`))), items) } diff --git a/public/src/components/lobby.js b/public/src/components/lobby.js index 431b20d..55bfcc7 100644 --- a/public/src/components/lobby.js +++ b/public/src/components/lobby.js @@ -1,8 +1,10 @@ import _ from '../../lib/utils' +import {STRINGS} from '../config' import App from '../app' import data from '../data' import Chat from './chat' import {RBox} from './checkbox' +import {Spaced} from './spacer' let d = React.DOM export default React.createClass({ @@ -13,32 +15,29 @@ export default React.createClass({ App.send('join', 'lobby') }, render() { + document.title = STRINGS.BRANDING.SITE_NAME.join('.') + return d.div({ className: 'container' }, d.div({ className: 'lobby' }, d.header({}, d.h1({ className: 'lobby-header' }, - d.span({}, 'drafts'), - d.span({ className: 'spacer-dot' }), - d.span({}, 'ninja'))), - d.p({}, `${App.state.numUsers} - ${App.state.numUsers === 1 ? 'user' : 'users'} - connected; - ${App.state.numPlayers} - ${App.state.numPlayers === 1 ? 'player' : 'players'} - playing - ${App.state.numActiveGames} - ${App.state.numActiveGames === 1 ? 'game' : 'games'}`), + Spaced(...STRINGS.BRANDING.SITE_NAME))), + + d.p({}, + Spaced( + `${App.state.numUsers} + ${App.state.numUsers === 1 ? 'user' : 'users'} + connected`, + `${App.state.numPlayers} + ${App.state.numPlayers === 1 ? 'player' : 'players'} + playing + ${App.state.numActiveGames} + ${App.state.numActiveGames === 1 ? 'game' : 'games'}`)), d.p({ className: 'error' }, App.err), Create(), Join(), Motd(), - d.div({}, - d.strong({}, 'drafts.ninja'), - ' is a fork of the ', - d.code({}, 'draft'), - ' project by aeosynth. Contributions welcome! ', - d.a({ href: 'https://github.com/arxanas/drafts.ninja' }, - 'https://github.com/arxanas/drafts.ninja'))), + STRINGS.PAGE_SECTIONS.FOOTER), Chat()) } }) @@ -92,10 +91,11 @@ function content() { } function Motd() { - if (App.state.motd) + let motd = STRINGS.PAGE_SECTIONS.MOTD + if (motd) return d.fieldset({ className: 'fieldset' }, d.legend({ className: 'legend' }, 'News'), - d.div({ dangerouslySetInnerHTML: { '__html': App.state.motd } })) + d.div({}, motd)) } function Create() { diff --git a/public/src/components/spacer.js b/public/src/components/spacer.js new file mode 100644 index 0000000..3af9393 --- /dev/null +++ b/public/src/components/spacer.js @@ -0,0 +1,11 @@ +let d = React.DOM + +export function Spaced(...elements) { + let parts = [] + for (let part of elements) { + parts.push(d.span({}, part)) + parts.push(d.span({ className: 'spacer-dot' })) + } + parts.splice(-1, 1) + return parts +} diff --git a/public/src/config.js b/public/src/config.js new file mode 120000 index 0000000..2dc6366 --- /dev/null +++ b/public/src/config.js @@ -0,0 +1 @@ +../../config.client.js \ No newline at end of file diff --git a/public/src/data.js b/public/src/data.js index be9f2f6..49f4245 100644 --- a/public/src/data.js +++ b/public/src/data.js @@ -94,6 +94,7 @@ export default { "Limited Edition Alpha": "LEA" }, other: { + "Eternal Masters": "EMA", "Magic Origins": "ORI", "Modern Masters 2015": "MM2", "Tempest Remastered": "TPR", diff --git a/public/style.css b/public/style.css index 61c7e29..5dd6ac6 100644 --- a/public/style.css +++ b/public/style.css @@ -109,6 +109,10 @@ input[type=button]:disabled:active, button:disabled:active { box-shadow: 0 0 1px 0 black inset; } +.number { + width: 50px; +} + /** Container of inputs that share a border. **/ .connected-container, .connected-column { @@ -159,7 +163,7 @@ input[type=button]:disabled:active, button:disabled:active { .spacer-dot:after { content: "\00b7"; display: inline-block; - width: 25px; + width: 0.75em; text-align: center; } @@ -396,10 +400,6 @@ input[type=button]:disabled:active, button:disabled:active { flex-shrink: 0; } -.num-lands { - width: 50px; -} - .deck-settings tr:first-of-type { text-align: center; } diff --git a/src/game.js b/src/game.js index 390fc6c..d2ce184 100644 --- a/src/game.js +++ b/src/game.js @@ -157,7 +157,6 @@ module.exports = class Game extends Room { } join(sock) { - sock.on('exit', this.farewell.bind(this)) for (var i = 0; i < this.players.length; i++) { var p = this.players[i] if (p.id === sock.id) { @@ -210,11 +209,6 @@ module.exports = class Game extends Room { }) } - farewell(sock) { - sock.h.isConnected = false - this.meta() - } - exit(sock) { if (this.didGameStart) return @@ -307,7 +301,7 @@ module.exports = class Game extends Room { this.meta() } - start([addBots, useTimer]) { + start({addBots, useTimer, timerLength}) { var src = this.cube ? this.cube : this.sets var {players} = this var p @@ -329,8 +323,12 @@ module.exports = class Game extends Room { return } - for (p of players) + timerLength = parseInt(timerLength, 10) + useTimer = !Number.isNaN(timerLength) && (timerLength > 0) && useTimer + for (p of players) { p.useTimer = useTimer + p.timerLength = timerLength + } console.log(`game ${this.id} started with ${this.players.length} players and ${this.seats} seats`) Game.broadcastGameInfo() diff --git a/src/human.js b/src/human.js index 514227b..66bd82d 100644 --- a/src/human.js +++ b/src/human.js @@ -31,6 +31,7 @@ module.exports = class extends EventEmitter { sock.on('autopick', this._autopick.bind(this)) sock.on('pick', this._pick.bind(this)) sock.on('hash', this._hash.bind(this)) + sock.once('exit', this._farewell.bind(this)) var [pack] = this.packs if (pack) @@ -47,6 +48,10 @@ module.exports = class extends EventEmitter { this.hash = hash(deck) this.emit('meta') } + _farewell() { + this.isConnected = false + this.emit('meta') + } _readyToStart(value) { this.isReadyToStart = value this.emit('meta') @@ -70,7 +75,7 @@ module.exports = class extends EventEmitter { return this.pick(0) if (this.useTimer) - this.time = 20 + 5 * pack.length + this.time = this.timerLength + pack.length this.send('pack', pack) } diff --git a/src/make/cards.js b/src/make/cards.js index bcc48df..3c0f50e 100644 --- a/src/make/cards.js +++ b/src/make/cards.js @@ -1,6 +1,20 @@ var fs = require('fs') var _ = require('../_') var raw = require('../../data/AllSets') +raw.EMA = { + name: 'Eternal Masters', + code: 'EMA', + cards: [{"name":"aven riftwatcher","rarity":"common"},{"name":"balance","rarity":"mythic"},{"name":"ballynock cohort","rarity":"common"},{"name":"benevolent bodyguard","rarity":"common"},{"name":"calciderm","rarity":"uncommon"},{"name":"coalition honor guard","rarity":"common"},{"name":"eight-and-a-half-tails","rarity":"rare"},{"name":"elite vanguard","rarity":"common"},{"name":"enlightened tutor","rarity":"rare"},{"name":"faith's fetters","rarity":"uncommon"},{"name":"field of souls","rarity":"uncommon"},{"name":"glimmerpoint stag","rarity":"uncommon"},{"name":"honden of cleansing fire","rarity":"uncommon"},{"name":"humble","rarity":"common"},{"name":"intangible virtue","rarity":"uncommon"},{"name":"jareth, leonine titan","rarity":"rare"},{"name":"karmic guide","rarity":"rare"},{"name":"kor hookmaster","rarity":"common"},{"name":"mesa enchantress","rarity":"uncommon"},{"name":"mistral charger","rarity":"common"},{"name":"monk idealist","rarity":"common"},{"name":"mother of runes","rarity":"rare"},{"name":"pacifism","rarity":"common"},{"name":"raise the alarm","rarity":"common"},{"name":"rally the peasants","rarity":"common"},{"name":"seal of cleansing","rarity":"common"},{"name":"second thoughts","rarity":"common"},{"name":"serra angel","rarity":"uncommon"},{"name":"shelter","rarity":"common"},{"name":"soulcatcher","rarity":"uncommon"},{"name":"squadron hawk","rarity":"common"},{"name":"swords to plowshares","rarity":"uncommon"},{"name":"unexpectedly absent","rarity":"rare"},{"name":"wall of omens","rarity":"uncommon"},{"name":"war priest of thune","rarity":"uncommon"},{"name":"welkin guide","rarity":"common"},{"name":"whitemane lion","rarity":"common"},{"name":"wrath of god","rarity":"rare"},{"name":"arcanis the omnipotent","rarity":"rare"},{"name":"brainstorm","rarity":"uncommon"},{"name":"cephalid sage","rarity":"common"},{"name":"control magic","rarity":"rare"},{"name":"counterspell","rarity":"common"},{"name":"daze","rarity":"uncommon"},{"name":"deep analysis","rarity":"common"},{"name":"diminishing returns","rarity":"rare"},{"name":"dream twist","rarity":"common"},{"name":"fact or fiction","rarity":"uncommon"},{"name":"force of will","rarity":"mythic"},{"name":"future sight","rarity":"rare"},{"name":"gaseous form","rarity":"common"},{"name":"giant tortoise","rarity":"common"},{"name":"glacial wall","rarity":"common"},{"name":"honden of seeing winds","rarity":"uncommon"},{"name":"hydroblast","rarity":"uncommon"},{"name":"inkwell leviathan","rarity":"rare"},{"name":"jace, the mind sculptor","rarity":"mythic"},{"name":"jetting glasskite","rarity":"uncommon"},{"name":"man-o'-war","rarity":"common"},{"name":"memory lapse","rarity":"common"},{"name":"merfolk looter","rarity":"uncommon"},{"name":"mystical tutor","rarity":"rare"},{"name":"oona's grace","rarity":"common"},{"name":"peregrine drake","rarity":"common"},{"name":"phantom monster","rarity":"common"},{"name":"phyrexian ingester","rarity":"uncommon"},{"name":"prodigal sorcerer","rarity":"uncommon"},{"name":"quiet speculation","rarity":"uncommon"},{"name":"screeching skaab","rarity":"common"},{"name":"serendib efreet","rarity":"rare"},{"name":"shoreline ranger","rarity":"common"},{"name":"silent departure","rarity":"common"},{"name":"sprite noble","rarity":"uncommon"},{"name":"stupefying touch","rarity":"common"},{"name":"tidal wave","rarity":"common"},{"name":"warden of evos isle","rarity":"common"},{"name":"wonder","rarity":"uncommon"},{"name":"animate dead","rarity":"uncommon"},{"name":"annihilate","rarity":"uncommon"},{"name":"blightsoil druid","rarity":"common"},{"name":"blood artist","rarity":"uncommon"},{"name":"braids, cabal minion","rarity":"rare"},{"name":"cabal therapy","rarity":"uncommon"},{"name":"carrion feeder","rarity":"common"},{"name":"deadbridge shaman","rarity":"common"},{"name":"duress","rarity":"common"},{"name":"entomb","rarity":"rare"},{"name":"eyeblight's ending","rarity":"common"},{"name":"gravedigger","rarity":"common"},{"name":"havoc demon","rarity":"uncommon"},{"name":"honden of night's reach","rarity":"uncommon"},{"name":"hymn to tourach","rarity":"uncommon"},{"name":"ichorid","rarity":"rare"},{"name":"innocent blood","rarity":"common"},{"name":"lys alana scarblade","rarity":"uncommon"},{"name":"malicious affliction","rarity":"rare"},{"name":"nausea","rarity":"common"},{"name":"necropotence","rarity":"mythic"},{"name":"nekrataal","rarity":"uncommon"},{"name":"night's whisper","rarity":"common"},{"name":"phyrexian gargantua","rarity":"uncommon"},{"name":"phyrexian rager","rarity":"common"},{"name":"plague witch","rarity":"common"},{"name":"prowling pangolin","rarity":"common"},{"name":"sengir autocrat","rarity":"uncommon"},{"name":"sinkhole","rarity":"rare"},{"name":"skulking ghost","rarity":"common"},{"name":"toxic deluge","rarity":"rare"},{"name":"tragic slip","rarity":"common"},{"name":"twisted abomination","rarity":"common"},{"name":"urborg uprising","rarity":"common"},{"name":"vampiric tutor","rarity":"mythic"},{"name":"victimize","rarity":"uncommon"},{"name":"visara the dreadful","rarity":"rare"},{"name":"wake of vultures","rarity":"common"},{"name":"wakedancer","rarity":"common"},{"name":"avarax","rarity":"common"},{"name":"battle squadron","rarity":"uncommon"},{"name":"beetleback chief","rarity":"uncommon"},{"name":"borderland marauder","rarity":"common"},{"name":"burning vengeance","rarity":"uncommon"},{"name":"carbonize","rarity":"common"},{"name":"chain lightning","rarity":"uncommon"},{"name":"crater hellion","rarity":"rare"},{"name":"desperate ravings","rarity":"common"},{"name":"dragon egg","rarity":"common"},{"name":"dualcaster mage","rarity":"rare"},{"name":"faithless looting","rarity":"common"},{"name":"fervent cathar","rarity":"common"},{"name":"firebolt","rarity":"common"},{"name":"flame jab","rarity":"uncommon"},{"name":"gamble","rarity":"rare"},{"name":"ghitu slinger","rarity":"uncommon"},{"name":"honden of infinite rage","rarity":"uncommon"},{"name":"keldon champion","rarity":"uncommon"},{"name":"keldon marauders","rarity":"common"},{"name":"kird ape","rarity":"common"},{"name":"mogg fanatic","rarity":"common"},{"name":"mogg war marshal","rarity":"common"},{"name":"orcish oriflamme","rarity":"common"},{"name":"price of progress","rarity":"uncommon"},{"name":"pyroblast","rarity":"uncommon"},{"name":"pyrokinesis","rarity":"rare"},{"name":"reckless charge","rarity":"common"},{"name":"rorix bladewing","rarity":"rare"},{"name":"seismic stomp","rarity":"common"},{"name":"siege-gang commander","rarity":"rare"},{"name":"sneak attack","rarity":"mythic"},{"name":"stingscourger","rarity":"common"},{"name":"sulfuric vortex","rarity":"rare"},{"name":"tooth and claw","rarity":"uncommon"},{"name":"undying rage","rarity":"common"},{"name":"wildfire emissary","rarity":"common"},{"name":"worldgorger dragon","rarity":"mythic"},{"name":"young pyromancer","rarity":"uncommon"},{"name":"abundant growth","rarity":"common"},{"name":"ancestral mask","rarity":"uncommon"},{"name":"argothian enchantress","rarity":"mythic"},{"name":"brawn","rarity":"uncommon"},{"name":"centaur chieftain","rarity":"uncommon"},{"name":"civic wayfinder","rarity":"common"},{"name":"commune with the gods","rarity":"common"},{"name":"elephant guide","rarity":"common"},{"name":"elvish vanguard","rarity":"common"},{"name":"emperor crocodile","rarity":"common"},{"name":"flinthoof boar","rarity":"uncommon"},{"name":"fog","rarity":"common"},{"name":"gaea's blessing","rarity":"uncommon"},{"name":"green sun's zenith","rarity":"rare"},{"name":"harmonize","rarity":"uncommon"},{"name":"heritage druid","rarity":"rare"},{"name":"honden of life's web","rarity":"uncommon"},{"name":"imperious perfect","rarity":"rare"},{"name":"invigorate","rarity":"uncommon"},{"name":"llanowar elves","rarity":"common"},{"name":"lys alana huntmaster","rarity":"common"},{"name":"natural order","rarity":"mythic"},{"name":"nature's claim","rarity":"common"},{"name":"nimble mongoose","rarity":"common"},{"name":"rancor","rarity":"uncommon"},{"name":"regal force","rarity":"rare"},{"name":"roar of the wurm","rarity":"uncommon"},{"name":"roots","rarity":"common"},{"name":"seal of strength","rarity":"common"},{"name":"sentinel spider","rarity":"common"},{"name":"silvos, rogue elemental","rarity":"rare"},{"name":"sylvan library","rarity":"rare"},{"name":"sylvan might","rarity":"common"},{"name":"thornweald archer","rarity":"common"},{"name":"timberwatch elf","rarity":"uncommon"},{"name":"werebear","rarity":"common"},{"name":"wirewood symbiote","rarity":"uncommon"},{"name":"xantid swarm","rarity":"rare"},{"name":"yavimaya enchantress","rarity":"common"},{"name":"armadillo cloak","rarity":"uncommon"},{"name":"baleful strix","rarity":"rare"},{"name":"bloodbraid elf","rarity":"uncommon"},{"name":"brago, king eternal","rarity":"rare"},{"name":"dack fayden","rarity":"mythic"},{"name":"extract from darkness","rarity":"uncommon"},{"name":"flame-kin zealot","rarity":"uncommon"},{"name":"glare of subdual","rarity":"rare"},{"name":"goblin trenches","rarity":"rare"},{"name":"maelstrom wanderer","rarity":"mythic"},{"name":"shaman of the pack","rarity":"uncommon"},{"name":"shardless agent","rarity":"rare"},{"name":"sphinx of the steel wind","rarity":"mythic"},{"name":"thunderclap wyvern","rarity":"uncommon"},{"name":"trygon predator","rarity":"uncommon"},{"name":"vindicate","rarity":"rare"},{"name":"void","rarity":"rare"},{"name":"wee dragonauts","rarity":"uncommon"},{"name":"zealous persecution","rarity":"uncommon"},{"name":"call the skybreaker","rarity":"rare"},{"name":"deathrite shaman","rarity":"rare"},{"name":"giant solifuge","rarity":"rare"},{"name":"torrent of souls","rarity":"uncommon"},{"name":"ashnod's altar","rarity":"uncommon"},{"name":"chrome mox","rarity":"mythic"},{"name":"duplicant","rarity":"rare"},{"name":"emmessi tome","rarity":"uncommon"},{"name":"goblin charbelcher","rarity":"rare"},{"name":"isochron scepter","rarity":"rare"},{"name":"juggernaut","rarity":"uncommon"},{"name":"mana crypt","rarity":"mythic"},{"name":"millikin","rarity":"uncommon"},{"name":"mindless automaton","rarity":"uncommon"},{"name":"nevinyrral's disk","rarity":"rare"},{"name":"pilgrim's eye","rarity":"common"},{"name":"prismatic lens","rarity":"uncommon"},{"name":"relic of progenitus","rarity":"uncommon"},{"name":"sensei's divining top","rarity":"rare"},{"name":"ticking gnomes","rarity":"uncommon"},{"name":"winter orb","rarity":"rare"},{"name":"worn powerstone","rarity":"uncommon"},{"name":"bloodfell caves","rarity":"common"},{"name":"blossoming sands","rarity":"common"},{"name":"dismal backwater","rarity":"common"},{"name":"jungle hollow","rarity":"common"},{"name":"karakas","rarity":"mythic"},{"name":"maze of ith","rarity":"rare"},{"name":"mishra's factory","rarity":"uncommon"},{"name":"rugged highlands","rarity":"common"},{"name":"scoured barrens","rarity":"common"},{"name":"swiftwater cliffs","rarity":"common"},{"name":"thornwood falls","rarity":"common"},{"name":"tranquil cove","rarity":"common"},{"name":"wasteland","rarity":"rare"},{"name":"wind-scarred crag","rarity":"common"}], + booster: ['common', 'common', 'common', + 'common', 'common', 'common', + 'common', 'common', 'common', + 'common', 'uncommon', 'uncommon', + 'uncommon', 'rare'], +}; +for (let i of raw.EMA.cards) { + i.types = [null]; + i.multiverseid = 0; +} var COLORS = { W: 'White', @@ -16,7 +30,7 @@ var Sets = {} before() var types = ['core', 'expansion', 'commander', 'planechase', 'starter', 'un'] -var codes = ['MMA', 'VMA', 'CNS', 'TPR', 'MM2'] +var codes = ['EMA', 'MMA', 'VMA', 'CNS', 'TPR', 'MM2'] for (var code in raw) { var set = raw[code] if (types.indexOf(set.type) > -1 @@ -214,6 +228,10 @@ function after() { } alias(FRF.special.fetch, 'FRF') + let {EMA} = Sets + let emaNames = raw.EMA.cards.map(x => x.name) + alias(emaNames, 'EMA') + Sets.OGW.common.push('wastes')// wastes are twice as common } @@ -222,8 +240,13 @@ function alias(arr, code) { for (var cardName of arr) { var {sets} = Cards[cardName] var codes = Object.keys(sets) - var last = codes[codes.length - 1] - sets[code] = sets[last] + if (code === 'EMA') { + var first = codes[0] + sets[code] = sets[first] + } else { + var last = codes[codes.length - 1] + sets[code] = sets[last] + } } } @@ -275,13 +298,16 @@ function doCard(rawCard, cards, code, set) { if (rawCard.layout === 'split') name = rawCard.names.join(' // ') + + //separate landsfrom 0cmc cards by setting 0cmc to .2 + var cmcadjusted = rawCard.cmc || 0.2 name = _.ascii(name) if (name in cards) { if (rawCard.layout === 'split') { var card = cards[name] - card.cmc += rawCard.cmc + cmcadjusted = card.cmc + rawCard.cmc if (card.color !== rawCard.color) card.color = 'multicolor' } @@ -292,11 +318,14 @@ function doCard(rawCard, cards, code, set) { var color = !colors ? 'colorless' : colors.length > 1 ? 'multicolor' : colors[0].toLowerCase() + + //set lands to .1 to sort them before nonland 0cmc + if ('Land'.indexOf(rawCard.types) > -1) + cmcadjusted = 0.1 cards[name] = { color, name, - manaCost: rawCard.manaCost, type: rawCard.types[rawCard.types.length - 1], - cmc: rawCard.cmc || 0, + cmc: cmcadjusted, text: rawCard.text || '', manaCost: rawCard.manaCost || '', sets: { diff --git a/src/router.js b/src/router.js index 64a5ccb..9fb2fb2 100644 --- a/src/router.js +++ b/src/router.js @@ -1,4 +1,4 @@ -const {MOTD} = require('../config') +const {MOTD} = require('../config.server') var Game = require('./game') var Room = require('./room') var Sock = require('./sock') diff --git a/src/sock.js b/src/sock.js index d9021d5..634d8a4 100644 --- a/src/sock.js +++ b/src/sock.js @@ -1,4 +1,5 @@ -var {EventEmitter} = require('events') +let {EventEmitter} = require('events') +let {STRINGS} = require('../config.server') // All sockets currently connected to the server. let allSocks = [] @@ -28,7 +29,7 @@ var mixins = { class Sock extends EventEmitter { constructor(ws) { this.ws = ws - var {id='', name='ninja'} = ws.request._query + var {id='', name=STRINGS.BRANDING.DEFAULT_USERNAME} = ws.request._query this.id = id.slice(0, 25) this.name = name.slice(0, 15)