diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..212566614 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore index c08d2e9df..2b939b13b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,24 @@ -node_modules/* -desktop/app/node_modules/* -.tmp -.sass-cache +# Build artifacts +/app/data/packages.js +/app/localization/locales/en/index.json +/dist +node_modules + +# Development artifacts +*.log +*.log.txt +dump.rdb .env +.env_for_alpha .foreman -.vagrant/ .idea/ -.DS_Store -/app/data/packages.js -/matchmaker/*.log -/server/*.log -.env_for_alpha -/public -/dist .node-version -.env_for_dev -dump.rdb -cli/config/*.temp -cli/config -.vscode -scripts/codex/images -npm-debug.log -scripts/**/*.log.txt -desktop/node_modules -desktop/src -scratch.txt -Procfile -/scratch +.sass-cache .steamcache -/config/production_local.json -/scripts/aws-utility/node_modules -/scripts/codex/node_modules -/cli/codes -/app/localization/locales/en/index.json -/scripts/localization/localization_output/ -/scripts/localization/node_modules/ -/cli/*Snapshots/ -yarn-error.log -.pgdata +.tmp +.vagrant/ +.vscode +/.pgdata + +# OSX-specific artifacts +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index c49ef3531..2c0b3db5b 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,127 @@ -# OPEN DUELYST -🚧 WIP 🚧 +# Duelyst -## Requirements +This is the source code for Duelyst, a digital collectible card game and turn-based strategy hybrid developed by Counterplay Games and released in 2016. + +### Client Architecture + +The game client is a Backbone.js + Marionette application which runs in the browser. Code can be found in `app/`, and configuration can be found in `app/common/config.js`. + +### Server Architecture + +Duelyst's backend primarily consists of four CoffeeScript services: + +API Server: + +- The API server is an Express.js app which handles routes for game clients. +- The service stores user and game data in Postgres and Redis. +- The service listens on port 3000 by default, and it serves the browser client on the default route. +- Code can be found in `server/api.coffee`, and configuration can be found in `config/`. + +Game Server: + +- The Game server is a Socket.IO WebSocket server which handles multiplayer games. +- The service enqueues tasks in Redis to be picked up by the workers. +- The service listens on port 8000 by default. +- Code can be found in `server/game.coffee`, and configuration can be found in `config/`. + +Single Player (SP) Server: + +- The SP server is a Socket.IO WebSocket server which handles single-player games. +- The service enqueues tasks in Redis to be picked up by the workers. +- The service listens on port 8000 by default. +- Code can be found in `server/single_player.coffee`, and configuration can be found in `config/`. + +Worker: + +- The worker uses Kue to poll Redis-backed queues for tasks like game creation and matchmaking. +- Some matchmaking tasks also use Postgres, for server healthchecks and retrieving bot users. +- Code can be found in `worker/worker.coffee`, and configuration can be found in `config/`. +- A Kue GUI is available at `http://localhost:3000` via `docker compose up worker-ui`). + +#### Other Dependencies + +Postgres: + +- Client code can be found in `server/lib/data_access/knex.coffee` and `server/knexfile.js`, and configuration can be found in `config/` +- Migrations can be run via `docker compose up migrate` +- An admin UI is available at `http://localhost:8080` via `docker compose up adminer` + +Redis: + +- Client code can be found in `server/redis/r-client.coffee`, and configuration can be found in `config/` + +Consul: + +- Not required in single-server deployments, but was historically used for service discovery, matchmaking, and spectating +- Client code can be found in `server/lib/consul.coffee`, and configuration can be found in `config/` + +## Setting up a development environment + +#### Requirements - [Docker](https://www.docker.com/products/docker-desktop/) -- [Node.js](https://nodejs.org/en/download/) +- [Node.js](https://nodejs.org/en/download/) with NPM -## Setup +#### Building the code -- `npm install -g yarn` -- `yarn install --dev` -- `yarn build` to build client -- `yarn watch` to build client continuously -- Modify `development.json` with: - - Firebase Realtime endpoint and secret - - Redis connection string - - Postgres connection string +Install NPM tools: +``` +npm install -g typescript yarn gulpjs/gulp-cli +``` -## Quick Start +Compile TypeScript dependencies: +``` +cd packages/chroma-js +tsc +``` -- `docker compose up -d` to start all services locally +Build the game: +``` +yarn install --dev +yarn build +``` + +The above build command builds the game and its required resources for the first time, which will take a few minutes. Use it when making changes to resources like cards, codex data, cosmetics, etc. + +Once the initial build is done, you can save time by rebuilding only the app (takes about 50 seconds): +``` +yarn build:app +``` + +Or rebuild only the HTML/CSS and localization files (takes about 5 seconds): +``` +yarn build:web +``` + +When working in the `server` or `worker` directories, no rebuilds are needed. See below for instructions to test changes in Docker instead. + +#### Starting a test environment in Docker + +- Modify `development.json` with: + - Firebase Realtime endpoint and secret + - Redis connection string + - Postgres connection string +- Optionally enable debug logging for sockets by prepending `yarn $1` with `DEBUG=*` in `docker/start` +- Use `docker compose up` to start required services locally, or start individual services: + - `redis` + - `db` (Postgres) + - `adminer` (Postgres admin UI) + - `api` (HTTP server) + - `game` (Multi-player server) + - `sp` (Single-player server) + - `worker` (Game workers) + - `worker-ui` (Game worker UI) + - `migrate` (Postgres migrations) +- Run database migrations + - Use `npm install -g knex` to install the Postgres client + - In another terminal window, use `NODE_ENV=development yarn migrate:latest` to run database migrations + - On Windows: `$env:NODE_ENV = 'development'; yarn migrate:latest` + - Only need to run this once (unless you change Postgres schema) - Open http://localhost:3000 in a browser to load the game client -## Scripts +#### Starting individual components - `yarn api` to start api server - `yarn game` to start game server - `yarn sp` to start single player game server -- `yarn worker` to start worker -- `NODE_ENV=development yarn migrate:latest` to run database migrations +- `yarn worker` to start worker \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 191bb6b80..cc10e4af2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,9 +1,8 @@ version: "3.9" x-base-service: &base - image: node:latest + image: node:lts environment: - DEBUG: "*" REDIS_IP: redis POSTGRES_CONNECTION: "pg://duelyst:duelyst@db/duelyst" depends_on: @@ -33,6 +32,8 @@ services: - ./.pgdata:/var/lib/postgresql/data adminer: image: adminer + profiles: + - donotstart restart: always ports: - 8080:8080 @@ -45,10 +46,18 @@ services: game: <<: *base + profiles: + - donotstart ports: - 8000:8000 command: docker/start game + sp: + <<: *base + ports: + - 8000:8000 + command: docker/start sp + worker: <<: *base ports: @@ -57,12 +66,16 @@ services: worker-ui: <<: *base + profiles: + - donotstart ports: - 3001:3000 command: docker/start worker-ui migrate: - image: node:latest + image: node:lts + profiles: + - donotstart environment: DEBUG: "*" NODE_ENV: development diff --git a/docker/start b/docker/start index 9a3cab0ee..e15b8ba01 100644 --- a/docker/start +++ b/docker/start @@ -1,5 +1,5 @@ #!/usr/bin/env bash - yarn install --dev -# NODE_ENV=development yarn migrate:latest -DEBUG=* yarn $1 + +# To enable socket debug logs, prepend this command with DEBUG=* +yarn $1 \ No newline at end of file diff --git a/gulp/clean.js b/gulp/clean.js index 0a799f6e1..ac56e24a5 100644 --- a/gulp/clean.js +++ b/gulp/clean.js @@ -9,7 +9,25 @@ export function all() { 'desktop/src', 'desktop/node_modules' ]) - return del('dist') +} + +// Cleans out app code only +export function app() { + return del('dist/src/duelyst.js') +} + +// Cleans out HTML/CSS only +export function web() { + return del([ + 'dist/src/duelyst.css', + 'dist/src/index.html', + 'dist/src/vendor.js' + ]) +} + +// Cleans out localization files only +export function locales() { + return del('dist/src/resources/locales') } // Cleans out desktop specific parts output folders diff --git a/gulpfile.babel.js b/gulpfile.babel.js index be43feed9..137e0280f 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -29,7 +29,7 @@ import * as git from './gulp/git' import * as docker from './gulp/docker' import * as shop from './gulp/shop' import * as localization from './gulp/localization' -import * as cdn from './gulp/cdn' +//import * as cdn from './gulp/cdn' import {opts,config,env,version,production,staging,development} from './gulp/shared' gutil.log(`${gutil.colors.red(`GULP :: env: ${env} :: version: ${version}`)}`) @@ -38,6 +38,9 @@ gutil.log(`${gutil.colors.yellow(`GULP :: minification = ${opts.minify}`)}`) // Define main tasks gulp.task('clean:all', clean.all) +gulp.task('clean:app', clean.app) +gulp.task('clean:web', clean.web) +gulp.task('clean:locales', clean.locales) gulp.task('clean:desktop', clean.desktop) gulp.task('clean:git', clean.git) gulp.task('css', css) @@ -49,7 +52,7 @@ gulp.task('vendor', vendor) gulp.task('rsx:imagemin', rsx.imageMin) gulp.task('rsx:imagemin:lossy', rsx.imageMinLossy) gulp.task('rsx:copy', rsx.copy) -gulp.task('rsx:copy:cdn', rsx.copyCdn) +//gulp.task('rsx:copy:cdn', rsx.copyCdn) gulp.task('rsx:copy:all', rsx.copyAll) gulp.task('rsx:packages', rsx.packages) gulp.task('rsx', gulp.series(rsx.packages,rsx.copy)) @@ -69,8 +72,8 @@ gulp.task('docker:push', docker.push) gulp.task('shop:paypal:buttons:SYNC:danger', shop.syncPaypalButtons) gulp.task('shop:paypal:buttons:add', shop.addPaypalButtons) gulp.task('localization:copy', localization.copy) -gulp.task('cdn:purgeAll', cdn.purgeAll) -gulp.task('cdn:purgeLocalization', cdn.purgeLocalization) +//gulp.task('cdn:purgeAll', cdn.purgeAll) +//gulp.task('cdn:purgeLocalization', cdn.purgeLocalization) // Define git helper tasks (master,staging,production) const branches = ['master', 'staging', 'production'] @@ -162,23 +165,36 @@ gulp.task('build', gulp.series( 'clean:all', 'source', 'rsx:copy', - 'rsx:copy:cdn', + //'rsx:copy:cdn', 'autowatch' )) +gulp.task('build:app', gulp.series( + 'clean:app', + 'js' +)) +gulp.task('build:web', gulp.series( + 'clean:web', + 'clean:locales', + 'html', + 'css', + 'vendor', + 'localization:copy' +)) + // register standalone page gulp.task('source:register', gulp.series(gulp.parallel('vendor','css','html:register'), 'localization:copy','rsx:packages', 'js:register')) gulp.task('build:register', gulp.series( 'clean:all', 'source:register', 'rsx:copy', - 'rsx:copy:cdn', + //'rsx:copy:cdn', 'autowatch' )) gulp.task('default', gulp.series('build')) // Release Builds (CI ready tasks) const ciTargets = ['staging','production'] -const cdnUrl = config.get('cdn') +//const cdnUrl = config.get('cdn') function validateConfig(cb) { // Ensure running build:release:${target} matches running config environemnt @@ -199,7 +215,9 @@ function validateConfigForDesktop(cb) { } cb() } -function overideCdnUrl(cb) { + +/* +function overrideCdnUrl(cb) { // We override the CDN url here // to prevent the CSS task from using for desktop app config.set('cdn', '') @@ -216,6 +234,7 @@ function versionedCdnUrl(cb) { config.set('cdn', cdnUrl) cb() } +*/ gulp.task('build:release', gulp.series( validateConfig, @@ -229,14 +248,14 @@ gulp.task('build:release', gulp.series( )) gulp.task('upload:release', gulp.series( 'rsx:copy', - 'rsx:copy:cdn', + //'rsx:copy:cdn', 'upload:main', 'upload:audio' )) gulp.task('build:release:versioned', gulp.series( validateConfig, 'clean:all', - versionedCdnUrl, + //versionedCdnUrl, 'source', 'source:register', 'rsx:build_urls', @@ -246,16 +265,16 @@ gulp.task('build:release:versioned', gulp.series( )) gulp.task('upload:release:versioned', gulp.series( 'rsx:copy', - 'rsx:copy:cdn', + //'rsx:copy:cdn', 'upload:main:versioned', 'upload:audio:versioned' )) gulp.task('desktop:build', gulp.series( validateConfigForDesktop, 'clean:all', - overideCdnUrl, + //overrideCdnUrl, 'source', - restoreCdnUrl, + //restoreCdnUrl, 'rsx:codex_urls', 'rsx:copy', 'desktop:setup', @@ -267,9 +286,9 @@ gulp.task('desktop:build', gulp.series( gulp.task('desktop:build:steam', gulp.series( validateConfigForDesktop, 'clean:all', - overideCdnUrl, + //overrideCdnUrl, 'source', - restoreCdnUrl, + //restoreCdnUrl, 'rsx:codex_urls', 'rsx:copy', 'desktop:setup', diff --git a/package.json b/package.json index 59260c216..728de3740 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "imagemin-zopfli": "4.2.0", "inquirer": "0.10.0", "istanbul": "^0.3.17", - "j2j": "./packages/node-j2j", + "j2j": "0.1.2", "mailchimp": "1.1.0", "minimist": "^1.2.0", "mocha": "^2.4.5", @@ -183,25 +183,28 @@ "sp": "node ./bin/single_player", "worker": "node ./bin/worker", "worker-ui": "node ./bin/workerui", - "migrate:rollback": "cd ./server && knex migrate:rollback", - "migrate:latest": "cd ./server && knex migrate:latest", - "build": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js build --watch=false", + + "build:app": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js build:app --watch=false", + "build:desktop": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js desktop:build", "build:release": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js build:release", "build:steam": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js desktop:build:steam", - "build:desktop": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js desktop:build", + "build:web": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js build:web --watch=false", + "build": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js build --watch=false", + "deploy:staging": "npm run push:master && npm run push:staging", + "lint": "xo", + "migrate:latest": "cd ./server && knex migrate:latest", + "migrate:rollback": "cd ./server && knex migrate:rollback", "minify": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js build --watch=false --minify=true", - "watch": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js build", + "packages": "gulp rsx:packages && gulp rsx:copy", "push:master": "gulp git:master:patch", "push:staging": "gulp git:staging", - "packages": "gulp rsx:packages && gulp rsx:copy", "rsx:copy": "gulp rsx:copy", - "deploy:staging": "npm run push:master && npm run push:staging", - "lint": "xo", - "test": "mocha", "test:all": "mocha -t 10000", - "test:sdk": "mocha test/unit/sdk --recursive -t 5000", + "test:data": "mocha test/unit/data_access --recursive -t 15000", "test:misc": "mocha test/unit/misc --recursive -t 5000", - "test:data": "mocha test/unit/data_access --recursive -t 15000" + "test:sdk": "mocha test/unit/sdk --recursive -t 5000", + "test": "mocha", + "watch": "node --max_old_space_size=2048 --stack-size=100000 node_modules/gulp/bin/gulp.js build" }, "xo": { "esnext": true, diff --git a/test/unit/data_access/paypal.js b/test/unit/data_access/paypal.js index 80e15f079..e1c1858c0 100644 --- a/test/unit/data_access/paypal.js +++ b/test/unit/data_access/paypal.js @@ -1,3 +1,5 @@ +/* PayPal unit tests are currently disabled. + var path = require('path') require('app-module-path').addPath(path.join(__dirname, '../../../')) require('coffee-script/register') @@ -193,3 +195,4 @@ describe("shop module", function() { }) }) +*/ \ No newline at end of file diff --git a/test/unit/data_access/shop.js b/test/unit/data_access/shop.js index 6acd78ff3..96abc3d89 100644 --- a/test/unit/data_access/shop.js +++ b/test/unit/data_access/shop.js @@ -1,3 +1,5 @@ +/* Shop unit tests are currently disabled. + var path = require('path') require('app-module-path').addPath(path.join(__dirname, '../../../')) require('coffee-script/register') @@ -809,3 +811,4 @@ describe("shop module", function() { }) }); +*/ \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 766cb5f84..cfb26030d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8437,8 +8437,10 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" -j2j@./packages/node-j2j: - version "0.2.0" +j2j@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/j2j/-/j2j-0.1.2.tgz#20ece0ef06d905ee2ce32cb3468da363c2938968" + integrity sha512-qfVEQYpdAR3dhSGaISJqv9JKhiLhrO0Tx4w5U9kdo5uHiRHRWTrY7g6Rba0rb3wng5LmblVZpMJCJpW8ZXBv6Q== dependencies: cardinal "^0.4.4" chalk "^0.5.1"