Compare commits
116
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c45572997f | ||
|
|
59217a5fea | ||
|
|
0b94cc4a1f | ||
|
|
cbf66bf5bb | ||
|
|
8b0cd71bb5 | ||
|
|
0e6d75ec9b | ||
|
|
3ca0dcceed | ||
|
|
89ba3ac48e | ||
|
|
ab96166601 | ||
|
|
dac1a8dceb | ||
|
|
ccb2cad516 | ||
|
|
33479051c8 | ||
|
|
b8a2dae9f2 | ||
|
|
2fd1de40da | ||
|
|
f6f736f8be | ||
|
|
20119cf8e4 | ||
|
|
997ff114b4 | ||
|
|
6d62375c46 | ||
|
|
b4cde5a37b | ||
|
|
5f65f77222 | ||
|
|
8b43c53a9e | ||
|
|
3bc7c4cf89 | ||
|
|
c4ba36b60a | ||
|
|
b05e237aee | ||
|
|
7865ba98c3 | ||
|
|
69e4d968b5 | ||
|
|
bd96bc6575 | ||
|
|
90a41a59f0 | ||
|
|
ebdb6158ba | ||
|
|
407e3fca5d | ||
|
|
3d9895a6d5 | ||
|
|
ee6887e803 | ||
|
|
fb48362a59 | ||
|
|
86a547f422 | ||
|
|
0131c8ff3b | ||
|
|
478364418b | ||
|
|
ed96cf0c71 | ||
|
|
5d8251bebb | ||
|
|
69ca6b06b9 | ||
|
|
7377559f7b | ||
|
|
c0472b268d | ||
|
|
13b717836a | ||
|
|
016c61e983 | ||
|
|
d7743285cb | ||
|
|
cc9232d143 | ||
|
|
a1e7fb4fa0 | ||
|
|
9ac25efb0c | ||
|
|
19a3e9846c | ||
|
|
c5e9920473 | ||
|
|
96b5beeb8c | ||
|
|
d0bca0d2ac | ||
|
|
d102834408 | ||
|
|
00ca88500e | ||
|
|
6a2b3805a2 | ||
|
|
eba8c99ac0 | ||
|
|
b82bfea8f1 | ||
|
|
d0a2292094 | ||
|
|
cb962f6175 | ||
|
|
083cc30eb2 | ||
|
|
22009557d2 | ||
|
|
b34ca26d0a | ||
|
|
23b3f1c7e1 | ||
|
|
6b84698802 | ||
|
|
c85fca8357 | ||
|
|
16848772a3 | ||
|
|
c441d75529 | ||
|
|
1bc89c1c89 | ||
|
|
43202e9917 | ||
|
|
05bc1a271d | ||
|
|
da1419d6b6 | ||
|
|
841fac38d3 | ||
|
|
2b8e23b32f | ||
|
|
5eae2bb488 | ||
|
|
6fa18c764b | ||
|
|
c9796a142d | ||
|
|
0718590f56 | ||
|
|
51f566ebf1 | ||
|
|
d2d904be7f | ||
|
|
088994f366 | ||
|
|
184b64a6af | ||
|
|
4aca035709 | ||
|
|
ebf3443e96 | ||
|
|
fd5372b35d | ||
|
|
f464558cd2 | ||
|
|
e7cd3f0549 | ||
|
|
545408a7e7 | ||
|
|
83156c30c7 | ||
|
|
636a8d061c | ||
|
|
2dbda99986 | ||
|
|
7d83552013 | ||
|
|
35bb12de6a | ||
|
|
1d1772e961 | ||
|
|
6787063ac7 | ||
|
|
9e09b8d87c | ||
|
|
2c77972073 | ||
|
|
31f4594928 | ||
|
|
eb9ac15de8 | ||
|
|
e922f3c5cc | ||
|
|
a8d08b93c5 | ||
|
|
64e118a2c8 | ||
|
|
6671541dcb | ||
|
|
962165dc8e | ||
|
|
f9b65dd892 | ||
|
|
979586d9d3 | ||
|
|
c522e60e28 | ||
|
|
f9cd38947d | ||
|
|
99b6cf2861 | ||
|
|
d925a17940 | ||
|
|
a2bbdf4645 | ||
|
|
93c98e16ac | ||
|
|
88e57b1200 | ||
|
|
1e96cf2d6c | ||
|
|
ccaf8e9842 | ||
|
|
e1f8282049 | ||
|
|
d873c4b7f1 | ||
|
|
3529bccf3a |
@@ -1,5 +1,7 @@
|
||||
*.log
|
||||
*.swp
|
||||
.DS_Store
|
||||
.env
|
||||
.sass-cache
|
||||
.tm_properties
|
||||
aws.config
|
||||
|
||||
+18
-44
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Crafting Guide - Gruntfile.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
@@ -17,29 +17,6 @@ EXTERNAL_LIBS = [
|
||||
'./vendor/email.js:emailjs'
|
||||
]
|
||||
|
||||
PUBLISHED_FILES = [
|
||||
'./client/models/base_model.coffee'
|
||||
'./client/models/game/inventory.coffee'
|
||||
'./client/models/game/item.coffee'
|
||||
'./client/models/game/item_slug.coffee'
|
||||
'./client/models/game/mod.coffee'
|
||||
'./client/models/game/mod_version.coffee'
|
||||
'./client/models/game/multiblock.coffee'
|
||||
'./client/models/game/recipe.coffee'
|
||||
'./client/models/game/stack.coffee'
|
||||
'./client/models/game/simple_stack.coffee'
|
||||
'./client/models/parsing/command_parser_version_base.coffee'
|
||||
'./client/models/parsing/mod_parser.coffee'
|
||||
'./client/models/parsing/mod_parser_v1.coffee'
|
||||
'./client/models/parsing/mod_version_parser.coffee'
|
||||
'./client/models/parsing/mod_version_parser_v1.coffee'
|
||||
'./client/models/parsing/versioned_parser_base.coffee'
|
||||
'./client/models/site/tutorial.coffee'
|
||||
'./common/constants.coffee'
|
||||
'./common/underscore.coffee'
|
||||
'./index.coffee'
|
||||
]
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = (grunt)->
|
||||
@@ -54,30 +31,28 @@ module.exports = (grunt)->
|
||||
mode: 'gzip'
|
||||
files: [
|
||||
{expand: true, cwd:'./build/static', src:'**/*.css', dest:'./dist/'}
|
||||
{expand: true, cwd:'./build/static', src:'**/*.html', dest:'./dist/'}
|
||||
{expand: true, cwd:'./build/static', src:['**/*.html', '!**/index.html'], dest:'./dist/'}
|
||||
{expand: true, cwd:'./build/static', src:'**/*.ugly.js', dest:'./dist/', ext: '.js'}
|
||||
{expand: true, cwd:'./build/static', src:'**/*.json', dest:'./dist/'}
|
||||
{expand: true, cwd:'./build/static', src:'**/*.ttf', dest:'./dist/'}
|
||||
{expand: true, cwd:'./build/static', src:'**/*.txt', dest:'./dist/'}
|
||||
]
|
||||
|
||||
coffee:
|
||||
npm_package:
|
||||
options: {
|
||||
bare: true
|
||||
}
|
||||
files: [
|
||||
{expand:true, cwd:'./src', src:PUBLISHED_FILES, dest:'./dist', ext:'.js'}
|
||||
]
|
||||
|
||||
copy:
|
||||
assets_build:
|
||||
files: [
|
||||
{expand:true, cwd:'./assets/', src:'**/*', dest:'./build/static'}
|
||||
]
|
||||
env_build:
|
||||
files: [
|
||||
{expand:true, cwd:'.', src:'.env', dest:'./build'}
|
||||
]
|
||||
build_to_dist:
|
||||
files: [
|
||||
{expand:true, cwd:'./build/static/', src:['**/*', '!**/*.map', '!**/*.js'], dest:'./dist'}
|
||||
expand: true
|
||||
cwd: './build/static/'
|
||||
src: ['**/*', '!**/*.map', '!**/*.js', '!index.html']
|
||||
dest: './dist'
|
||||
]
|
||||
common_source:
|
||||
files: [
|
||||
@@ -123,9 +98,9 @@ module.exports = (grunt)->
|
||||
|
||||
mochaTest:
|
||||
options:
|
||||
bail: true
|
||||
bail: false
|
||||
color: true
|
||||
reporter: 'dot'
|
||||
reporter: 'list'
|
||||
require: [
|
||||
'coffee-script/register'
|
||||
'./src/test_helper.coffee'
|
||||
@@ -135,6 +110,8 @@ module.exports = (grunt)->
|
||||
|
||||
sass:
|
||||
all:
|
||||
options:
|
||||
sourcemap: 'none'
|
||||
files:
|
||||
'./build/static/main.css': [ './build/imports.scss' ]
|
||||
|
||||
@@ -187,7 +164,7 @@ module.exports = (grunt)->
|
||||
['jade', 'build:copy', 'build:css', 'browserify:external', 'browserify:internal']
|
||||
|
||||
grunt.registerTask 'build:copy', 'helper task for build',
|
||||
['copy:assets_build', 'copy:common_source', 'copy:server_source']
|
||||
['copy:assets_build', 'copy:env_build', 'copy:common_source', 'copy:server_source']
|
||||
|
||||
grunt.registerTask 'build:css', 'helper task for build',
|
||||
['sass_globbing', 'sass']
|
||||
@@ -207,14 +184,11 @@ module.exports = (grunt)->
|
||||
grunt.registerTask 'dist', 'build the project to be run from Amazon S3',
|
||||
['build', 'copy:build_to_dist', 'uglify', 'compress']
|
||||
|
||||
grunt.registerTask 'prepublish', 'build the project to be published to NPM as shared code',
|
||||
['clean', 'coffee']
|
||||
|
||||
grunt.registerTask 'publish', 'publish the project to NPM',
|
||||
['prepublish', 'script:publish', 'clean', 'build']
|
||||
|
||||
grunt.registerTask 'start', 'build the project and start a local HTTP server',
|
||||
['build', 'script:start']
|
||||
['script:start']
|
||||
|
||||
grunt.registerTask 'test', 'run unit tests',
|
||||
['mochaTest']
|
||||
@@ -225,7 +199,7 @@ module.exports = (grunt)->
|
||||
grunt.registerTask 'upload:staging', 'upload the project the staging Amazon S3 environment',
|
||||
['dist', 'script:s3_upload:staging']
|
||||
|
||||
grunt.registerTask 'useLocalDeps', 'use local dependencies instead of NPM dependencies',
|
||||
grunt.registerTask 'use-local-deps', 'use local dependencies instead of NPM dependencies',
|
||||
['script:use_local_deps']
|
||||
|
||||
# Code Tasks #######################################################################################################
|
||||
@@ -268,7 +242,7 @@ module.exports = (grunt)->
|
||||
|
||||
grunt.registerTask 'script:clear', "clear the current terminal buffer", ->
|
||||
done = this.async()
|
||||
grunt.util.spawn cmd:'clear', opts:{stdio:'inherit'}, (error)-> done(error)
|
||||
grunt.util.spawn cmd:'./scripts/clear_buffer', opts:{stdio:'inherit'}, (error)-> done(error)
|
||||
|
||||
grunt.registerTask 'script:deploy:prod', "deploy code by copying to the production branch", ->
|
||||
done = this.async()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
google-site-verification: google077275160ac09f2f.html
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 267 B |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 723 B |
Binary file not shown.
|
After Width: | Height: | Size: 300 B |
Generated
+2917
File diff suppressed because it is too large
Load Diff
+34
-34
@@ -1,50 +1,50 @@
|
||||
{
|
||||
"name": "crafting-guide",
|
||||
"version": "3.2.2",
|
||||
"version": "3.2.6",
|
||||
"description": "A guide for building anything in Minecraft or its many mods",
|
||||
"main": "./dist/index.js",
|
||||
"repository": "http://github.com/andrewminer/crafting-guide",
|
||||
"scripts": {
|
||||
"build": "grunt build",
|
||||
"prepublish": "grunt prepublish",
|
||||
"start": "grunt start",
|
||||
"test": "grunt test"
|
||||
},
|
||||
"author": "Andrew Miner",
|
||||
"license": "none",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"chai": "^1.10.0",
|
||||
"coffee-script": "^1.9.3",
|
||||
"coffeeify": "^1.1.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-contrib-clean": "^0.6.0",
|
||||
"grunt-contrib-coffee": "^1.0.0",
|
||||
"grunt-contrib-compress": "^0.14.0",
|
||||
"grunt-contrib-copy": "^0.8.2",
|
||||
"grunt-contrib-jade": "^0.15.0",
|
||||
"grunt-contrib-sass": "^0.9.2",
|
||||
"grunt-contrib-uglify": "^0.11.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-mocha-test": "^0.12.7",
|
||||
"grunt-sass-globbing": "^1.4.0",
|
||||
"mocha": "^2.4.5",
|
||||
"sass": "^0.5.0",
|
||||
"sinon": "^1.14.1",
|
||||
"sinon-chai": "^2.7.0"
|
||||
"chai": "3.5.0",
|
||||
"coffee-script": "1.12.5",
|
||||
"coffeeify": "2.1.0",
|
||||
"grunt": "1.0.1",
|
||||
"grunt-contrib-clean": "1.1.0",
|
||||
"grunt-contrib-coffee": "1.0.0",
|
||||
"grunt-contrib-compress": "1.4.1",
|
||||
"grunt-contrib-copy": "1.0.0",
|
||||
"grunt-contrib-jade": "1.0.0",
|
||||
"grunt-contrib-sass": "1.0.0",
|
||||
"grunt-contrib-uglify": "2.3.0",
|
||||
"grunt-contrib-watch": "1.0.0",
|
||||
"grunt-mocha-test": "0.13.2",
|
||||
"grunt-sass-globbing": "1.5.1",
|
||||
"mocha": "3.3.0",
|
||||
"sass": "0.5.0",
|
||||
"sinon": "2.2.0",
|
||||
"sinon-chai": "2.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"backbone": "^1.2.3",
|
||||
"body-parser": "^1.13.3",
|
||||
"client-sessions": "^0.7.0",
|
||||
"cookie-parser": "^1.3.5",
|
||||
"crafting-guide-common": "^1.5.1",
|
||||
"express": "^4.13.3",
|
||||
"express-session": "^1.11.3",
|
||||
"jade": "^1.11.0",
|
||||
"jquery": "^2.1.4",
|
||||
"marked": "^0.3.5",
|
||||
"underscore": "^1.8.3",
|
||||
"underscore.inflections": "^0.2.1",
|
||||
"when": "^3.7.3"
|
||||
"backbone": "1.3.3",
|
||||
"body-parser": "1.17.1",
|
||||
"client-sessions": "0.8.0",
|
||||
"cookie-parser": "1.4.3",
|
||||
"crafting-guide-common": ">=5.0.0",
|
||||
"dotenv": "4.0.0",
|
||||
"express": "4.15.2",
|
||||
"express-session": "1.15.2",
|
||||
"jade": "1.11.0",
|
||||
"jquery": "3.2.1",
|
||||
"marked": "0.3.6",
|
||||
"underscore": "1.8.3",
|
||||
"underscore.inflections": "0.2.1",
|
||||
"when": "3.7.8"
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
clear && printf '\e[3J'
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
grunt watch &
|
||||
|
||||
pushd './build/' &>/dev/null
|
||||
clear
|
||||
nodemon ./server.coffee $*
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ActiveLayerIndex</key>
|
||||
<integer>0</integer>
|
||||
<key>ApplicationVersion</key>
|
||||
<array>
|
||||
<string>com.omnigroup.OmniGraffle.MacAppStore</string>
|
||||
<string>139.18</string>
|
||||
</array>
|
||||
<key>AutoAdjust</key>
|
||||
<false/>
|
||||
<key>BackgroundGraphic</key>
|
||||
<dict>
|
||||
<key>Bounds</key>
|
||||
<string>{{0, 0}, {320, 320}}</string>
|
||||
<key>Class</key>
|
||||
<string>SolidGraphic</string>
|
||||
<key>ID</key>
|
||||
<integer>2</integer>
|
||||
<key>Style</key>
|
||||
<dict>
|
||||
<key>shadow</key>
|
||||
<dict>
|
||||
<key>Draws</key>
|
||||
<string>NO</string>
|
||||
</dict>
|
||||
<key>stroke</key>
|
||||
<dict>
|
||||
<key>Draws</key>
|
||||
<string>NO</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>BaseZoom</key>
|
||||
<integer>0</integer>
|
||||
<key>CanvasOrigin</key>
|
||||
<string>{0, 0}</string>
|
||||
<key>CanvasSize</key>
|
||||
<string>{320, 320}</string>
|
||||
<key>ColumnAlign</key>
|
||||
<integer>1</integer>
|
||||
<key>ColumnSpacing</key>
|
||||
<real>36</real>
|
||||
<key>CreationDate</key>
|
||||
<string>2016-04-23 05:39:56 +0000</string>
|
||||
<key>Creator</key>
|
||||
<string>Andrew Miner</string>
|
||||
<key>DisplayScale</key>
|
||||
<string>1 pt = 1 px</string>
|
||||
<key>GraphDocumentVersion</key>
|
||||
<integer>8</integer>
|
||||
<key>GraphicsList</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>AllowConnections</key>
|
||||
<string>NO</string>
|
||||
<key>AllowLabelDrop</key>
|
||||
<false/>
|
||||
<key>AllowToConnect</key>
|
||||
<false/>
|
||||
<key>Class</key>
|
||||
<string>LineGraphic</string>
|
||||
<key>ID</key>
|
||||
<integer>5</integer>
|
||||
<key>Points</key>
|
||||
<array>
|
||||
<string>{160, 480}</string>
|
||||
<string>{480, 160}</string>
|
||||
</array>
|
||||
<key>Style</key>
|
||||
<dict>
|
||||
<key>stroke</key>
|
||||
<dict>
|
||||
<key>Cap</key>
|
||||
<integer>0</integer>
|
||||
<key>Color</key>
|
||||
<dict>
|
||||
<key>a</key>
|
||||
<string>0.2</string>
|
||||
<key>b</key>
|
||||
<string>0</string>
|
||||
<key>g</key>
|
||||
<string>1</string>
|
||||
<key>r</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
<key>HeadArrow</key>
|
||||
<string>0</string>
|
||||
<key>Legacy</key>
|
||||
<true/>
|
||||
<key>TailArrow</key>
|
||||
<string>0</string>
|
||||
<key>Width</key>
|
||||
<real>120</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>AllowConnections</key>
|
||||
<string>NO</string>
|
||||
<key>AllowLabelDrop</key>
|
||||
<false/>
|
||||
<key>AllowToConnect</key>
|
||||
<false/>
|
||||
<key>Class</key>
|
||||
<string>LineGraphic</string>
|
||||
<key>ID</key>
|
||||
<integer>4</integer>
|
||||
<key>Points</key>
|
||||
<array>
|
||||
<string>{-160, 160}</string>
|
||||
<string>{160, -160}</string>
|
||||
</array>
|
||||
<key>Style</key>
|
||||
<dict>
|
||||
<key>stroke</key>
|
||||
<dict>
|
||||
<key>Cap</key>
|
||||
<integer>0</integer>
|
||||
<key>Color</key>
|
||||
<dict>
|
||||
<key>a</key>
|
||||
<string>0.2</string>
|
||||
<key>b</key>
|
||||
<string>0</string>
|
||||
<key>g</key>
|
||||
<string>1</string>
|
||||
<key>r</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
<key>HeadArrow</key>
|
||||
<string>0</string>
|
||||
<key>Legacy</key>
|
||||
<true/>
|
||||
<key>TailArrow</key>
|
||||
<string>0</string>
|
||||
<key>Width</key>
|
||||
<real>120</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>AllowConnections</key>
|
||||
<string>NO</string>
|
||||
<key>AllowLabelDrop</key>
|
||||
<false/>
|
||||
<key>AllowToConnect</key>
|
||||
<false/>
|
||||
<key>Class</key>
|
||||
<string>LineGraphic</string>
|
||||
<key>ID</key>
|
||||
<integer>3</integer>
|
||||
<key>Points</key>
|
||||
<array>
|
||||
<string>{0, 320}</string>
|
||||
<string>{320, 0}</string>
|
||||
</array>
|
||||
<key>Style</key>
|
||||
<dict>
|
||||
<key>stroke</key>
|
||||
<dict>
|
||||
<key>Cap</key>
|
||||
<integer>0</integer>
|
||||
<key>Color</key>
|
||||
<dict>
|
||||
<key>a</key>
|
||||
<string>0.2</string>
|
||||
<key>b</key>
|
||||
<string>0</string>
|
||||
<key>g</key>
|
||||
<string>1</string>
|
||||
<key>r</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
<key>HeadArrow</key>
|
||||
<string>0</string>
|
||||
<key>Legacy</key>
|
||||
<true/>
|
||||
<key>TailArrow</key>
|
||||
<string>0</string>
|
||||
<key>Width</key>
|
||||
<real>120</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GridInfo</key>
|
||||
<dict>
|
||||
<key>GridSpacing</key>
|
||||
<real>10</real>
|
||||
<key>MajorGridSpacing</key>
|
||||
<integer>10</integer>
|
||||
<key>ShowsGrid</key>
|
||||
<string>YES</string>
|
||||
<key>SnapsToGrid</key>
|
||||
<string>YES</string>
|
||||
</dict>
|
||||
<key>GuidesLocked</key>
|
||||
<string>NO</string>
|
||||
<key>GuidesVisible</key>
|
||||
<string>YES</string>
|
||||
<key>HPages</key>
|
||||
<integer>1</integer>
|
||||
<key>ImageCounter</key>
|
||||
<integer>1</integer>
|
||||
<key>KeepToScale</key>
|
||||
<false/>
|
||||
<key>Layers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Lock</key>
|
||||
<string>NO</string>
|
||||
<key>Name</key>
|
||||
<string>Layer 1</string>
|
||||
<key>Print</key>
|
||||
<string>YES</string>
|
||||
<key>View</key>
|
||||
<string>YES</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LayoutInfo</key>
|
||||
<dict>
|
||||
<key>Animate</key>
|
||||
<string>NO</string>
|
||||
<key>circoMinDist</key>
|
||||
<real>18</real>
|
||||
<key>circoSeparation</key>
|
||||
<real>0.0</real>
|
||||
<key>layoutEngine</key>
|
||||
<string>dot</string>
|
||||
<key>neatoSeparation</key>
|
||||
<real>0.0</real>
|
||||
<key>twopiSeparation</key>
|
||||
<real>0.0</real>
|
||||
</dict>
|
||||
<key>LinksVisible</key>
|
||||
<string>NO</string>
|
||||
<key>MagnetsVisible</key>
|
||||
<string>NO</string>
|
||||
<key>MasterSheets</key>
|
||||
<array/>
|
||||
<key>ModificationDate</key>
|
||||
<string>2016-04-27 03:11:38 +0000</string>
|
||||
<key>Modifier</key>
|
||||
<string>Andrew Miner</string>
|
||||
<key>NotesVisible</key>
|
||||
<string>NO</string>
|
||||
<key>Orientation</key>
|
||||
<integer>2</integer>
|
||||
<key>OriginVisible</key>
|
||||
<string>NO</string>
|
||||
<key>PageBreaks</key>
|
||||
<string>NO</string>
|
||||
<key>PrintInfo</key>
|
||||
<dict>
|
||||
<key>NSBottomMargin</key>
|
||||
<array>
|
||||
<string>float</string>
|
||||
<string>41</string>
|
||||
</array>
|
||||
<key>NSHorizonalPagination</key>
|
||||
<array>
|
||||
<string>coded</string>
|
||||
<string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG</string>
|
||||
</array>
|
||||
<key>NSLeftMargin</key>
|
||||
<array>
|
||||
<string>float</string>
|
||||
<string>18</string>
|
||||
</array>
|
||||
<key>NSPaperSize</key>
|
||||
<array>
|
||||
<string>size</string>
|
||||
<string>{611.99998474121094, 792}</string>
|
||||
</array>
|
||||
<key>NSPrintReverseOrientation</key>
|
||||
<array>
|
||||
<string>int</string>
|
||||
<string>0</string>
|
||||
</array>
|
||||
<key>NSRightMargin</key>
|
||||
<array>
|
||||
<string>float</string>
|
||||
<string>18</string>
|
||||
</array>
|
||||
<key>NSTopMargin</key>
|
||||
<array>
|
||||
<string>float</string>
|
||||
<string>18</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>PrintOnePage</key>
|
||||
<false/>
|
||||
<key>ReadOnly</key>
|
||||
<string>NO</string>
|
||||
<key>RowAlign</key>
|
||||
<integer>1</integer>
|
||||
<key>RowSpacing</key>
|
||||
<real>36</real>
|
||||
<key>SheetTitle</key>
|
||||
<string>Canvas 1</string>
|
||||
<key>SmartAlignmentGuidesActive</key>
|
||||
<string>YES</string>
|
||||
<key>SmartDistanceGuidesActive</key>
|
||||
<string>YES</string>
|
||||
<key>UniqueID</key>
|
||||
<integer>1</integer>
|
||||
<key>UseEntirePage</key>
|
||||
<false/>
|
||||
<key>VPages</key>
|
||||
<integer>1</integer>
|
||||
<key>WindowInfo</key>
|
||||
<dict>
|
||||
<key>CurrentSheet</key>
|
||||
<integer>0</integer>
|
||||
<key>ExpandedCanvases</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Canvas 1</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>Frame</key>
|
||||
<string>{{215, 0}, {2134, 1417}}</string>
|
||||
<key>ListView</key>
|
||||
<true/>
|
||||
<key>OutlineWidth</key>
|
||||
<integer>142</integer>
|
||||
<key>RightSidebar</key>
|
||||
<false/>
|
||||
<key>ShowRuler</key>
|
||||
<true/>
|
||||
<key>Sidebar</key>
|
||||
<true/>
|
||||
<key>SidebarWidth</key>
|
||||
<integer>120</integer>
|
||||
<key>VisibleRegion</key>
|
||||
<string>{{-840, -477}, {1999, 1275}}</string>
|
||||
<key>Zoom</key>
|
||||
<real>1</real>
|
||||
<key>ZoomValues</key>
|
||||
<array>
|
||||
<array>
|
||||
<string>Canvas 1</string>
|
||||
<real>1</real>
|
||||
<real>1</real>
|
||||
</array>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
+51
-36
@@ -1,78 +1,93 @@
|
||||
#
|
||||
# Crafting Guide - client.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
############################################################################################################
|
||||
|
||||
# Allow Node.js-style `global` in addition to `window`
|
||||
if typeof(global) is 'undefined'
|
||||
if typeof(global) is "undefined"
|
||||
window.global = window
|
||||
|
||||
global._ = require '../common/underscore'
|
||||
global.$ = require 'jquery'
|
||||
global.c = require '../common/constants'
|
||||
global._ = require "../common/underscore"
|
||||
global.$ = require "jquery"
|
||||
global.c = require "../common/constants"
|
||||
global.π = Math.PI
|
||||
global.ε = 0.0001
|
||||
global.w = require 'when'
|
||||
global.w = require "when"
|
||||
|
||||
global.Backbone = Backbone = require 'backbone'
|
||||
Backbone.$ = $
|
||||
|
||||
{Logger} = require 'crafting-guide-common'
|
||||
{Logger} = require("crafting-guide-common").util
|
||||
global.logger = new Logger
|
||||
|
||||
marked = require 'marked'
|
||||
Tracker = require "./tracker"
|
||||
global.tracker = new Tracker
|
||||
|
||||
marked = require "marked"
|
||||
marked.setOptions sanitize:true
|
||||
|
||||
global.Backbone = Backbone = require "backbone"
|
||||
Backbone.$ = $
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
global.hostName = window.location.hostname
|
||||
switch global.hostName
|
||||
when 'prerender.crafting-guide.com'
|
||||
global.env = 'prerender'
|
||||
when "prerender.crafting-guide.com"
|
||||
global.env = "prerender"
|
||||
logger.level = Logger.FATAL
|
||||
apiBaseUrl = 'http://local.crafting-guide.com:8001'
|
||||
when 'local.crafting-guide.com', 'localhost'
|
||||
global.env = 'local'
|
||||
apiBaseUrl = "http://prerender.crafting-guide.com:4347"
|
||||
when "local.crafting-guide.com", "localhost"
|
||||
global.env = "local"
|
||||
logger.level = Logger.DEBUG
|
||||
apiBaseUrl = 'http://local.crafting-guide.com:8001'
|
||||
when 'staging.crafting-guide.com'
|
||||
global.env = 'staging'
|
||||
apiBaseUrl = "http://#{global.hostName}:4347"
|
||||
when "staging.crafting-guide.com"
|
||||
global.env = "staging"
|
||||
logger.level = Logger.VERBOSE
|
||||
apiBaseUrl = 'http://api-staging.crafting-guide.com'
|
||||
when 'crafting-guide.com'
|
||||
global.env = 'production'
|
||||
logger.level = Logger.INFO
|
||||
apiBaseUrl = 'http://api.crafting-guide.com'
|
||||
apiBaseUrl = "http://api-staging.crafting-guide.com"
|
||||
when "crafting-guide.com"
|
||||
global.env = "production"
|
||||
logger.level = Logger.INFO
|
||||
tracker.enabled = true
|
||||
apiBaseUrl = "http://api.crafting-guide.com"
|
||||
else
|
||||
throw new Error "cannot determine the environment of: #{window.location.hostname}"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
Storage = require './storage'
|
||||
Storage = require "./storage"
|
||||
storage = new Storage storage:global.localStorage
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
{CraftingGuideClient} = require 'crafting-guide-common'
|
||||
tracker.trackPageView()
|
||||
|
||||
{CraftingGuideClient} = require("crafting-guide-common").api
|
||||
client = _(new CraftingGuideClient(baseUrl:apiBaseUrl)).extend Backbone.Events
|
||||
client.onStatusChanged = (client, oldStatus, newStatus)->
|
||||
logger.info "Crafting Guide server status changed from #{oldStatus} to #{newStatus}"
|
||||
client.trigger 'change:status', client, oldStatus, newStatus
|
||||
client.trigger 'change', client
|
||||
client.trigger "change:status", client, oldStatus, newStatus
|
||||
client.trigger "change", client
|
||||
|
||||
client.checkStatus()
|
||||
client.startMonitoringStatus()
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
SiteController = require './site/site_controller'
|
||||
global.site = site = new SiteController client:client, storage:storage
|
||||
site.render()
|
||||
site.loadDefaultModPack()
|
||||
site.loadCurrentUser()
|
||||
{http, ModPackStore, ItemDetailStore} = require("crafting-guide-common").api
|
||||
storeOptions = http:http, baseUrl:"#{location.protocol}//#{location.hostname}:#{location.port}"
|
||||
global.stores =
|
||||
modPack: new ModPackStore storeOptions
|
||||
itemDetail: new ItemDetailStore storeOptions
|
||||
|
||||
Backbone.history.start pushState:true
|
||||
logger.info -> "CraftingGuide is ready"
|
||||
########################################################################################################################
|
||||
|
||||
global.stores.modPack.load c.modPacks.default
|
||||
.then (modPack)->
|
||||
SiteController = require "./site/site_controller"
|
||||
global.site = site = new SiteController client:client, storage:storage, modPack:modPack
|
||||
site.render()
|
||||
site.loadCurrentUser()
|
||||
|
||||
Backbone.history.start pushState:true
|
||||
logger.info -> "CraftingGuide is ready"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//-
|
||||
//- Crafting Guide - _google_analytics.jade
|
||||
//-
|
||||
//- Copyright © 2014-2016 by Redwood Labs
|
||||
//- Copyright © 2014-2017 by Redwood Labs
|
||||
//- All rights reserved.
|
||||
//-
|
||||
|
||||
@@ -15,4 +15,6 @@ script.
|
||||
ga('create', 'UA-54970416-2', 'crafting-guide.com');
|
||||
ga('require', 'displayfeatures');
|
||||
ga('require', 'linkid', 'linkid.js');
|
||||
} else {
|
||||
window.ga = function() {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
//-
|
||||
//- Crafting Guide - _google_sitelinks.jade
|
||||
//-
|
||||
//- Copyright © 2014-2017 by Redwood Labs
|
||||
//- All rights reserved.
|
||||
//-
|
||||
|
||||
script(type="application/ld+json").
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "WebSite",
|
||||
"url": "http://crafting-guide.com/",
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": "http://crafting-guide.com?search={search_term}",
|
||||
"query-input": "required name=search_term"
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - base_model.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BaseModel extends Backbone.Model
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
options.logEvents ?= true
|
||||
super attributes, options
|
||||
|
||||
makeGetter = (name)-> return -> @get name
|
||||
makeSetter = (name)-> return (value)-> @set name, value
|
||||
for name, value of attributes
|
||||
continue if name is 'id'
|
||||
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
|
||||
|
||||
@logEvents = options.logEvents or false
|
||||
@state = c.modelState.unloaded
|
||||
|
||||
@loading = null
|
||||
|
||||
Object.defineProperties this,
|
||||
isUnloaded: { get:-> @state is c.modelState.unloaded }
|
||||
isLoading: { get:-> @state is c.modelState.loading }
|
||||
isLoaded: { get:-> @state is c.modelState.loaded }
|
||||
isError: { get:-> @state is c.modelState.error }
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onLoadSucceeded: (text, status, xhr)->
|
||||
try
|
||||
@set @parse text
|
||||
|
||||
@state = c.modelState.loaded
|
||||
@trigger c.event.change, this
|
||||
@trigger c.event.sync, this
|
||||
logger.info => "#{@constructor.name}.#{@cid} loaded successfully"
|
||||
catch e
|
||||
logger.error -> "A parsing error occured: #{e.stack}"
|
||||
@onLoadFailed e.message, 'parsing failed', xhr
|
||||
|
||||
onLoadFailed: (error, status, xhr)->
|
||||
@state = c.modelState.error
|
||||
logger.error => "#{@constructor.name}.#{@cid} failed to load: status:#{status}, message:#{error}"
|
||||
@trigger c.event.error, this, error
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
fetch: (options={})->
|
||||
options.force ?= false
|
||||
return if (@isLoading or @isLoaded) and not options.force
|
||||
|
||||
url = @url()
|
||||
logger.info => "#{@constructor.name}.#{@cid} reading from url: #{url}"
|
||||
|
||||
@state = c.modelState.loading
|
||||
@trigger c.event.request, this
|
||||
@loading = w.promise (resolve, reject)=>
|
||||
$.ajax
|
||||
url: url
|
||||
dataType: 'text'
|
||||
success: (text, status, xhr)=> resolve @onLoadSucceeded text, status, xhr
|
||||
error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr
|
||||
|
||||
@loading.catch (e)-> # do nothing
|
||||
return @loading
|
||||
|
||||
parse: (text)->
|
||||
return JSON.parse text
|
||||
|
||||
sync: (method, model)->
|
||||
throw new Error "#{@constructor.name}.#{@cid} is not permitted to #{method}"
|
||||
|
||||
trigger: (name, model, args...)->
|
||||
if @logEvents
|
||||
argText = ("#{arg}"[0..50] for arg in args).join ", "
|
||||
logger.trace => "#{@constructor.name}.#{@cid} triggered event #{name} with args: #{argText}"
|
||||
super
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name}.#{@cid}"
|
||||
@@ -1,122 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - crafting_node.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingNode
|
||||
|
||||
@::TYPES =
|
||||
INVENTORY: 0
|
||||
ITEM: 1
|
||||
RECIPE: 2
|
||||
|
||||
@::TYPE = null
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
|
||||
@modPack = options.modPack
|
||||
|
||||
@_children = []
|
||||
@_complete = null
|
||||
@_id = _.uniqueId 'node_'
|
||||
@_rotations = 0
|
||||
@_valid = null
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
expand: (queue=[])->
|
||||
return unless @valid
|
||||
return queue if @children.length > 0
|
||||
|
||||
for child in @_createChildren()
|
||||
child.parent = this
|
||||
@_children.push child
|
||||
queue.push child
|
||||
return queue
|
||||
|
||||
acceptVisitor: (visitor)->
|
||||
enter = visitor[@ENTER_METHOD]
|
||||
if enter?
|
||||
enter.call visitor, this
|
||||
else
|
||||
enter = visitor['onEnterOtherNode']
|
||||
if enter? then enter.call visitor, this
|
||||
|
||||
for child in @_children
|
||||
child.acceptVisitor visitor
|
||||
|
||||
leave = visitor[@LEAVE_METHOD]
|
||||
if leave?
|
||||
leave.call visitor, this
|
||||
else
|
||||
leave = visitor['onLeaveOtherNode']
|
||||
if leave? then leave.call visitor, this
|
||||
|
||||
rotateChildren: ->
|
||||
@_children.push @_children.shift()
|
||||
@_rotations += 1
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
children:
|
||||
get: -> return @_children
|
||||
|
||||
complete:
|
||||
get: ->
|
||||
if not @_complete?
|
||||
@_complete = @_checkCompleteness()
|
||||
return @_complete
|
||||
|
||||
completeText:
|
||||
get: -> if @complete then "◼︎" else "◻︎"
|
||||
|
||||
depth:
|
||||
get: ->
|
||||
maxDepth = 1
|
||||
for child in @children
|
||||
maxDepth = Math.max maxDepth, child.depth + 1
|
||||
return maxDepth
|
||||
|
||||
id:
|
||||
get: -> return @_id
|
||||
|
||||
rotations:
|
||||
get: -> return @_rotations
|
||||
|
||||
size:
|
||||
get: ->
|
||||
size = 1
|
||||
for child in @children
|
||||
size += child.size
|
||||
return size
|
||||
|
||||
valid:
|
||||
get: -> @_checkValidity()
|
||||
|
||||
validText:
|
||||
get: -> if @valid then "✓" else "✗"
|
||||
|
||||
|
||||
# Virtual Methods ##############################################################################
|
||||
|
||||
# Subclasses must override this method to create such children as are appropriate for that kind of node. The new
|
||||
# nodes should be appended to the given array.
|
||||
_createChildren: (result)->
|
||||
throw new Error "#{@constructor.name} must override the _createChildren method"
|
||||
|
||||
# Subclasses must override this method to indicate whether the node has at least one "complete" subtree rooted with
|
||||
# itself. Completeness may be defined differently by different subclasses.
|
||||
_checkCompleteness: ->
|
||||
throw new Error "#{@constructor.name} must override the _checkCompletness method"
|
||||
|
||||
# Subclasses must override this method to determine whether the subtree represented by a node should still be
|
||||
# explored for having a useful crafting plan.
|
||||
_checkValidity: ->
|
||||
throw new Error "#{@constructor.name} must override the _checkValidity method"
|
||||
@@ -1,44 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - crafting_node.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
fixtures = require './fixtures.test'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
root = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'crafting_node.coffee', ->
|
||||
|
||||
describe 'acceptVisitor', ->
|
||||
|
||||
it 'can provide a prefix, depth-first traversal', ->
|
||||
root = fixtures.makeTree 'test__iron_ingot'
|
||||
|
||||
visitor =
|
||||
nodes: []
|
||||
onEnterOtherNode: (node)-> visitor.nodes.push node
|
||||
|
||||
root.acceptVisitor visitor
|
||||
|
||||
(v.constructor.name.replace('Node', '') for v in visitor.nodes).should.eql [
|
||||
"Inventory", "Item", "Recipe", "Item", "Item", "Recipe", "Item", "Item", "Recipe", "Item", "Item"
|
||||
]
|
||||
|
||||
it 'can provide a postfix, depth-first traversal', ->
|
||||
root = fixtures.makeTree 'test__iron_ingot'
|
||||
|
||||
visitor =
|
||||
nodes: []
|
||||
onLeaveOtherNode: (node)-> visitor.nodes.push node
|
||||
|
||||
root.acceptVisitor visitor
|
||||
|
||||
(n.constructor.name.replace('Node', '') for n in visitor.nodes).should.eql [
|
||||
"Item", "Item", "Item", "Recipe", "Item", "Recipe", "Item", "Item", "Recipe", "Item", "Inventory"
|
||||
]
|
||||
@@ -1,164 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - crafting_plan.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
SimpleInventory = require './simple_inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingPlan
|
||||
|
||||
constructor: (modPack, want, have, steps)->
|
||||
if not modPack? then throw new Error 'modPack is required'
|
||||
if not want? then throw new Error 'want is required'
|
||||
if not have? then throw new Error 'have is required'
|
||||
if not steps? then throw new Error 'steps is required'
|
||||
|
||||
@_have = have
|
||||
@_made = null
|
||||
@_modPack = modPack
|
||||
@_need = null
|
||||
@_rawScores = {}
|
||||
@_scores = {}
|
||||
@_steps = steps
|
||||
@_want = want
|
||||
|
||||
@_numberSteps()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
computeRequired: ->
|
||||
@_need = new SimpleInventory modPack:@_modPack
|
||||
@_need.addInventory @_want
|
||||
|
||||
@_made = new SimpleInventory modPack:@_modPack
|
||||
@_made.addInventory @_have
|
||||
|
||||
for i in [@_steps.length-1..0] by -1
|
||||
step = @_steps[i]
|
||||
step.multiplier = 0
|
||||
|
||||
for stack in step.recipe.output
|
||||
if not stack?
|
||||
throw new Error 'stack should not be null here'
|
||||
|
||||
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
|
||||
while @_need.quantityOf(qualifiedSlug) > 0
|
||||
@_executeStep step
|
||||
|
||||
@_made.addInventory @_want
|
||||
@_pruneEmptySteps()
|
||||
@_numberSteps()
|
||||
|
||||
hasRawScore: (name)->
|
||||
return @_rawScores[name]?
|
||||
|
||||
getRawScore: (name)->
|
||||
return @_rawScores[name]
|
||||
|
||||
setRawScore: (name, rawScore)->
|
||||
@_rawScores[name] = rawScore
|
||||
|
||||
hasScore: (name)->
|
||||
return @_scores[name]?
|
||||
|
||||
getScore: (name)->
|
||||
return @_scores[name]
|
||||
|
||||
setScore: (name, score)->
|
||||
@_scores[name] = score
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
have:
|
||||
get: -> @_have
|
||||
length:
|
||||
get: -> @steps.length
|
||||
made:
|
||||
get: -> @_made
|
||||
need:
|
||||
get: -> @_need
|
||||
steps:
|
||||
get: -> @_steps
|
||||
want:
|
||||
get: -> @_want
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = ["To Make:"]
|
||||
@_want.each (stack)->
|
||||
result.push " #{stack}"
|
||||
|
||||
result.push "When you already have:"
|
||||
@_have.each (stack)->
|
||||
result.push " #{stack}"
|
||||
|
||||
result.push "Start with:"
|
||||
if @_need?
|
||||
@_need.each (stack)->
|
||||
result.push " #{stack}"
|
||||
|
||||
result.push "Use these recipes:"
|
||||
for step in @_steps
|
||||
result.push " #{step}"
|
||||
|
||||
result.push "To produce:"
|
||||
if @_made?
|
||||
@_made.each (stack)->
|
||||
result.push " #{stack}"
|
||||
|
||||
if _.keys(@_rawScores).length > 0
|
||||
result.push "Scores:"
|
||||
for criteria, score of @_rawScores
|
||||
if @_scores[criteria]?
|
||||
result.push " #{criteria}: #{score} (#{@_scores[criteria]})"
|
||||
else
|
||||
result.push " #{criteria}: #{score}"
|
||||
|
||||
return result.join '\n'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_executeStep: (step)->
|
||||
step.multiplier += 1
|
||||
recipe = step.recipe
|
||||
|
||||
for stack in recipe.input
|
||||
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
|
||||
|
||||
available = @_made.quantityOf qualifiedSlug
|
||||
required = recipe.getQuantityRequired stack.itemSlug
|
||||
consumed = Math.min required, available
|
||||
deficit = required - consumed
|
||||
|
||||
@_made.remove qualifiedSlug, consumed
|
||||
@_need.add qualifiedSlug, deficit
|
||||
|
||||
for stack in recipe.output
|
||||
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
|
||||
|
||||
deficit = @_need.quantityOf qualifiedSlug
|
||||
created = recipe.getQuantityProduced stack.itemSlug
|
||||
replenished = Math.min deficit, created
|
||||
surplus = created - replenished
|
||||
|
||||
@_made.add qualifiedSlug, surplus
|
||||
@_need.remove qualifiedSlug, replenished
|
||||
|
||||
_numberSteps: ->
|
||||
for step, i in @_steps
|
||||
step.number = i + 1
|
||||
|
||||
_pruneEmptySteps: ->
|
||||
index = 0
|
||||
while index < @_steps.length
|
||||
step = @_steps[index]
|
||||
if step.multiplier is 0
|
||||
@_steps.splice index, 1
|
||||
else
|
||||
index++
|
||||
@@ -1,107 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - crafting_plan.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CraftingPlan = require './crafting_plan'
|
||||
fixtures = require './fixtures.test'
|
||||
ItemSlug = require '../game/item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'crafting_plan.coffee', ->
|
||||
|
||||
it 'requires wanted item if gatherable', ->
|
||||
plans = fixtures.makePlans [1, 'test__coal']
|
||||
plans.length.should.equal 1
|
||||
|
||||
plan = plans[0]
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal 'coal'
|
||||
plan.made.unparse().should.equal 'coal'
|
||||
|
||||
it 'can compute a single item with one single step plan', ->
|
||||
plans = fixtures.makePlans [1, 'test__charcoal']
|
||||
plans.length.should.equal 1
|
||||
|
||||
plan = plans[0]
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal 'coal:8.oak_wood'
|
||||
plan.made.unparse().should.equal '8.charcoal'
|
||||
(s.toString() for s in plan.steps).should.eql ['1x 8 test__oak_wood,test__coal>.0. ... .1.>>8 test__charcoal']
|
||||
|
||||
it 'can compute a large quantity of a single item with one single step plan', ->
|
||||
plans = fixtures.makePlans [15, 'test__charcoal']
|
||||
plans.length.should.equal 1
|
||||
|
||||
plan = plans[0]
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal '2.coal:16.oak_wood'
|
||||
plan.made.unparse().should.equal '16.charcoal'
|
||||
(s.toString() for s in plan.steps).should.eql ['2x 8 test__oak_wood,test__coal>.0. ... .1.>>8 test__charcoal']
|
||||
|
||||
it 'can compute a single item with multiple plans', ->
|
||||
plans = fixtures.makePlans [1, 'test__iron_ingot']
|
||||
plans.length.should.equal 2
|
||||
|
||||
plan = plans[0]
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal 'coal:8.iron_ore:8.oak_wood'
|
||||
plan.made.unparse().should.equal '7.charcoal:8.iron_ingot'
|
||||
(s.toString() for s in plan.steps).should.eql [
|
||||
'1x 8 test__oak_wood,test__coal>.0. ... .1.>>8 test__charcoal'
|
||||
'1x 8 test__iron_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
]
|
||||
|
||||
plan = plans[1]
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal 'coal:8.iron_ore'
|
||||
plan.made.unparse().should.equal '8.iron_ingot'
|
||||
(s.toString() for s in plan.steps).should.eql [
|
||||
'1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
]
|
||||
|
||||
it 'can compute multiple items with multiple plans', ->
|
||||
plans = fixtures.makePlans [1, 'test__copper_block'], [1, 'test__iron_sword']
|
||||
plans.length.should.equal 4
|
||||
|
||||
for plan in plans
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.match /16.copper_ore.*8.iron_ore/
|
||||
plan.made.unparse().should.match /copper_block.*:iron_sword/
|
||||
|
||||
plan = plans[0]
|
||||
plan.need.unparse().should.match /3.coal.*9.oak_wood/
|
||||
plan.made.unparse().should.match /7.charcoal/
|
||||
"#{plan.steps[3]}".should.equal \
|
||||
'1x 8 test__iron_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[4]}".should.equal \
|
||||
'2x 8 test__copper_ore,test__coal>.0. ... .1.>test__furnace>8 test__copper_ingot'
|
||||
plan.steps.length.should.equal 7
|
||||
|
||||
plan = plans[1]
|
||||
plan.need.unparse().should.match /3.coal.*:oak_wood/
|
||||
plan.made.unparse().should.not.match /charcoal/
|
||||
"#{plan.steps[2]}".should.equal '1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[3]}".should.equal \
|
||||
'2x 8 test__copper_ore,test__coal>.0. ... .1.>test__furnace>8 test__copper_ingot'
|
||||
plan.steps.length.should.equal 6
|
||||
|
||||
plan = plans[2]
|
||||
plan.need.unparse().should.match /^coal.*9.oak_wood/
|
||||
plan.made.unparse().should.match /5.charcoal/
|
||||
"#{plan.steps[3]}".should.equal \
|
||||
'1x 8 test__iron_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[4]}".should.equal \
|
||||
'2x 8 test__copper_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__copper_ingot'
|
||||
plan.steps.length.should.equal 7
|
||||
|
||||
plan = plans[3]
|
||||
plan.need.unparse().should.match /2.coal.*9.oak_wood/
|
||||
plan.made.unparse().should.match /6.charcoal/
|
||||
"#{plan.steps[3]}".should.equal '1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[4]}".should.equal \
|
||||
'2x 8 test__copper_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__copper_ingot'
|
||||
plan.steps.length.should.equal 7
|
||||
@@ -1,70 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - crafting_step.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
SimpleInventory = require './simple_inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingStep
|
||||
|
||||
constructor: (recipe, modPack, multiplier=0)->
|
||||
if not recipe? then throw new Error 'recipe is required'
|
||||
if not modPack? then throw new Error 'modPack is required'
|
||||
if multiplier < 0 then throw new Error 'multiplier must be at least 1'
|
||||
|
||||
@number = null
|
||||
|
||||
@_inventory = null
|
||||
@_modPack = modPack
|
||||
@_multiplier = multiplier
|
||||
@_recipe = recipe
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addToolsTo: (targetInventory)->
|
||||
for stack in @_recipe.tools
|
||||
targetInventory.add stack.itemSlug, stack.quantity
|
||||
|
||||
completeInto: (targetInventory)->
|
||||
for stack in @_recipe.output
|
||||
quantity = @_recipe.getQuantityProduced stack.itemSlug
|
||||
targetInventory.add stack.itemSlug, quantity * @_multiplier
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
inventory:
|
||||
get: ->
|
||||
if not @_inventory?
|
||||
@_refreshInventory()
|
||||
return @_inventory
|
||||
|
||||
recipe:
|
||||
get: -> @_recipe
|
||||
|
||||
multiplier:
|
||||
get: -> @_multiplier
|
||||
set: (newMultiplier)->
|
||||
@_multiplier = newMultiplier
|
||||
@_refreshInventory() if @_inventory?
|
||||
|
||||
slug:
|
||||
get: -> "#{@multiplier}x #{@_recipe.slug}"
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return @slug
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshInventory: ->
|
||||
@_inventory = new SimpleInventory
|
||||
for stack in @_recipe.input
|
||||
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
|
||||
@_inventory.add qualifiedSlug, @multiplier * @_recipe.getQuantityRequired stack.itemSlug
|
||||
@@ -1,151 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - craftsman.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
GraphBuilder = require './graph_builder'
|
||||
Inventory = require '../game/inventory'
|
||||
PlanBuilder = require './plan_builder'
|
||||
PlanEvaluator = require './plan_evaluator'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Craftsman extends BaseModel
|
||||
|
||||
@::ANALYZE_STEP_INCREMENT = 29
|
||||
|
||||
@::GRAPH_STEP_INCREMENT = 59
|
||||
|
||||
@::PLAN_STEP_INCREMENT = 39
|
||||
|
||||
@::STAGE =
|
||||
WAITING: 'waiting'
|
||||
GRAPHING: 'examining recipes'
|
||||
PLANNING: 'computing plans'
|
||||
ANALYZING: 'analyzing plans'
|
||||
COMPLETE: 'complete'
|
||||
INVALID: 'invalid'
|
||||
|
||||
constructor: (modPack)->
|
||||
if not modPack? then throw new Error 'modPack is required'
|
||||
attributes =
|
||||
paused: false
|
||||
stage: @STAGE.WAITING
|
||||
stageCount: 0
|
||||
super attributes, {}
|
||||
|
||||
@_modPack = modPack
|
||||
|
||||
reset = _.debounce (=> @reset()), 100
|
||||
reevaluatePlans = _.debounce (=> @reevaluatePlans()), 100
|
||||
|
||||
@_have = new Inventory modPack:@_modPack
|
||||
@_have.on c.event.change, reevaluatePlans
|
||||
|
||||
@_want = new Inventory modPack:@_modPack
|
||||
@_want.on c.event.change, reset
|
||||
|
||||
@on c.event.change + ':paused', reset
|
||||
@on c.event.change + ':stage', => logger.info "Craftsman has started #{@stage}..."
|
||||
@on 'scheduleNextWork', => @_scheduleNextWork()
|
||||
@reset()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
reevaluatePlans: ->
|
||||
@_plans = null
|
||||
@_planEvaluator = null
|
||||
@stage = @STAGE.WAITING
|
||||
@stageCount = 0
|
||||
|
||||
@_scheduleNextWork()
|
||||
|
||||
reset: ->
|
||||
@_graphBuilder = null
|
||||
@_planBuilder = null
|
||||
@_planEvaluator = null
|
||||
@_plans = null
|
||||
|
||||
@stage = @STAGE.WAITING
|
||||
@stageCount = 0
|
||||
|
||||
@_scheduleNextWork()
|
||||
|
||||
work: ->
|
||||
return if @_want.isEmpty
|
||||
|
||||
if not @_graphBuilder?
|
||||
@_want.localize()
|
||||
@_have.localize()
|
||||
logger.info -> "Craftsman starting to build #{@_want} from #{@_have}"
|
||||
|
||||
@_graphBuilder = new GraphBuilder modPack:@_modPack, want:@_want, have:@_have
|
||||
@stage = @STAGE.GRAPHING
|
||||
@stageCount = 0
|
||||
else if not @_graphBuilder.complete
|
||||
@_graphBuilder.expandGraph @GRAPH_STEP_INCREMENT
|
||||
@stageCount = @_graphBuilder.stepCount
|
||||
logger.verbose => "Craftsman is expanding graph: #{@stageCount} nodes"
|
||||
else if not @_graphBuilder.rootNode.valid
|
||||
@stage = @STAGE.INVALID
|
||||
@stageCount = 0
|
||||
logger.warning => "Craftsman could not complete a crafting plan."
|
||||
else if not @_planBuilder?
|
||||
logger.debug => "Craftsman finished computing graph:\n#{@_graphBuilder.rootNode}"
|
||||
@_planBuilder = new PlanBuilder @_graphBuilder.rootNode, @_modPack, have:@_have, want:@_want
|
||||
@stage = @STAGE.PLANNING
|
||||
@stageCount = 0
|
||||
else if not @_planBuilder.complete
|
||||
@_planBuilder.producePlans @PLAN_STEP_INCREMENT
|
||||
@stageCount = @_planBuilder.plans.length
|
||||
logger.verbose => "Craftsman is producing plans: #{@_planBuilder.plans.length} plans"
|
||||
else if not @_planEvaluator?
|
||||
logger.verbose => "Craftsman finished producing plans: #{@_planBuilder.plans.length} plans"
|
||||
@_planEvaluator = new PlanEvaluator @_planBuilder.plans
|
||||
@stage = @STAGE.ANALYZING
|
||||
@stageCount = 0
|
||||
else if not @_planEvaluator.complete
|
||||
@_planEvaluator.scorePlans @ANALYZE_STEP_INCREMENT
|
||||
@stageCount = @_planEvaluator.lastScored
|
||||
logger.verbose => "Craftsman is rating plans: #{@stageCount} plans"
|
||||
else
|
||||
@_plans = [
|
||||
@_planEvaluator.findBestPlan PlanEvaluator::CRITERIA.FEWEST_STEPS
|
||||
@_planEvaluator.findBestPlan PlanEvaluator::CRITERIA.LEAST_MATERIALS
|
||||
]
|
||||
@stage = @STAGE.COMPLETE
|
||||
@stageCount = 0
|
||||
|
||||
logger.info => "Craftsman has finished with plans: #{(p.toString() for p in @_plans).join('\n\n')}"
|
||||
|
||||
@trigger c.event.change, this
|
||||
@trigger 'scheduleNextWork'
|
||||
return @complete
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
complete:
|
||||
get: -> @stage in [@STAGE.COMPLETE, @STAGE.INVALID]
|
||||
|
||||
have:
|
||||
get: -> @_have
|
||||
|
||||
plan:
|
||||
get: -> @_plans?[0]
|
||||
|
||||
want:
|
||||
get: -> @_want
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_scheduleNextWork: ->
|
||||
return if @paused
|
||||
return if @want.isEmpty
|
||||
return if @complete
|
||||
|
||||
_.defer => @work()
|
||||
@@ -1,166 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - fixtures.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
GraphBuilder = require './graph_builder'
|
||||
Inventory = require '../game/inventory'
|
||||
ItemSlug = require '../game/item_slug'
|
||||
Mod = require '../game/mod'
|
||||
ModPack = require '../game/mod_pack'
|
||||
ModVersion = require '../game/mod_version'
|
||||
PlanBuilder = require './plan_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
MOD_VERSION_FILE =
|
||||
"""
|
||||
schema: 1
|
||||
|
||||
item: Bed
|
||||
recipe:
|
||||
input: Oak Planks, Wool
|
||||
pattern: ... 000 111
|
||||
|
||||
item: Charcoal
|
||||
recipe:
|
||||
input: 8 Oak Wood, Coal
|
||||
pattern: .0. ... .1.
|
||||
quantity: 8
|
||||
|
||||
item: Crafting Table
|
||||
recipe:
|
||||
input: Oak Planks
|
||||
pattern: .00 .00 ...
|
||||
|
||||
item: Coal
|
||||
gatherable: yes
|
||||
|
||||
item: Cobblestone
|
||||
gatherable: yes
|
||||
|
||||
item: Copper Block
|
||||
recipe:
|
||||
input: Copper Ingot
|
||||
pattern: 000 000 000
|
||||
tools: Crafting Table
|
||||
|
||||
item: Copper Ingot
|
||||
recipe:
|
||||
input: 8 Copper Ore, Coal
|
||||
pattern: .0. ... .1.
|
||||
quantity: 8
|
||||
tools: Furnace
|
||||
recipe:
|
||||
input: 8 Copper Ore, Charcoal
|
||||
pattern: .0. ... .1.
|
||||
quantity: 8
|
||||
tools: Furnace
|
||||
recipe:
|
||||
input: Copper Block
|
||||
pattern: ... .0. ...
|
||||
quantity: 9
|
||||
|
||||
item: Copper Ore
|
||||
gatherable: yes
|
||||
|
||||
item: Furnace
|
||||
recipe:
|
||||
input: Cobblestone
|
||||
pattern: 000 0.0 000
|
||||
tools: Crafting Table
|
||||
|
||||
item: Iron Ore
|
||||
gatherable: yes
|
||||
|
||||
item: Iron Ingot
|
||||
recipe:
|
||||
input: 8 Iron Ore, Charcoal
|
||||
pattern: .0. ... .1.
|
||||
quantity: 8
|
||||
tools: Furnace
|
||||
recipe:
|
||||
input: 8 Iron Ore, Coal
|
||||
pattern: .0. ... .1.
|
||||
quantity: 8
|
||||
tools: Furnace
|
||||
|
||||
item: Iron Sword
|
||||
recipe:
|
||||
input: Iron Ingot, Stick
|
||||
pattern: .0. .0. .1.
|
||||
tools: Crafting Table
|
||||
|
||||
item: Lever
|
||||
recipe:
|
||||
input: Stick, Cobblestone
|
||||
pattern: .0. .1. ...
|
||||
|
||||
item: Oak Planks
|
||||
recipe:
|
||||
input: Oak Wood
|
||||
pattern: ... .0. ...
|
||||
quantity: 4
|
||||
|
||||
item: Oak Wood
|
||||
gatherable: yes
|
||||
|
||||
item: Stick
|
||||
recipe:
|
||||
input: Oak Planks
|
||||
pattern: .0. .0. ...
|
||||
quantity: 4
|
||||
|
||||
item: String
|
||||
gatherable: yes
|
||||
|
||||
item: Wool
|
||||
gatherable: yes
|
||||
recipe:
|
||||
input: String
|
||||
pattern: 00. 00. ...
|
||||
"""
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = fixtures =
|
||||
|
||||
makeGraphBuilder: ->
|
||||
return new GraphBuilder modPack:fixtures.makeModPack(), want:new Inventory
|
||||
|
||||
makeModPack: ->
|
||||
modPack = new ModPack
|
||||
|
||||
mod = new Mod name:'Test', slug:'test'
|
||||
modPack.addMod mod
|
||||
|
||||
modVersion = new ModVersion modSlug:'test', version:'0.0'
|
||||
modVersion.parse MOD_VERSION_FILE
|
||||
mod.addModVersion modVersion
|
||||
|
||||
return modPack
|
||||
|
||||
makePlans: (stacks...)->
|
||||
modPack = fixtures.makeModPack()
|
||||
|
||||
have = new Inventory
|
||||
want = new Inventory
|
||||
for stack in stacks
|
||||
want.add ItemSlug.slugify(stack[1]), stack[0]
|
||||
|
||||
graphBuilder = new GraphBuilder modPack:modPack, want:want
|
||||
graphBuilder.expandGraph()
|
||||
|
||||
planBuilder = new PlanBuilder graphBuilder.rootNode, modPack, want:want, have:have
|
||||
return planBuilder.producePlans()
|
||||
|
||||
makeTree: (itemSlug, quantity=1)->
|
||||
want = new Inventory
|
||||
want.add ItemSlug.slugify itemSlug
|
||||
|
||||
builder = new GraphBuilder modPack:fixtures.makeModPack(), want:want
|
||||
builder.expandGraph()
|
||||
|
||||
return builder.rootNode
|
||||
@@ -1,69 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - graph_builder.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Inventory = require '../game/inventory'
|
||||
InventoryNode = require './inventory_node'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class GraphBuilder
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
if not options.want? then throw new Error 'options.want is required'
|
||||
|
||||
@_maximumGraphSize = c.limits.maximumGraphSize
|
||||
@_modPack = options.modPack
|
||||
@_stepCount = 0
|
||||
@_want = options.want
|
||||
|
||||
@_rootNode = new InventoryNode modPack:@_modPack, inventory:@_want
|
||||
@_queue = [@_rootNode]
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
expandGraph: (steps=null)->
|
||||
maxSteps = if steps? then @_stepCount + steps else Number.MAX_VALUE
|
||||
@_queue ?= []
|
||||
|
||||
while true
|
||||
break if @_queue.length is 0
|
||||
break if @_stepCount >= maxSteps
|
||||
|
||||
node = @_queue.shift()
|
||||
break unless node?
|
||||
|
||||
node.expand @_queue
|
||||
@_stepCount += 1
|
||||
|
||||
reset: ->
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
complete:
|
||||
get: ->
|
||||
return true if @_stepCount > @_maximumGraphSize
|
||||
return false unless @_rootNode?
|
||||
return false unless @_queue?
|
||||
return false unless @_queue.length is 0
|
||||
return true
|
||||
|
||||
rootNode:
|
||||
get: -> @_rootNode
|
||||
|
||||
stepCount:
|
||||
get: -> @_stepCount
|
||||
|
||||
want:
|
||||
get: -> @_want
|
||||
|
||||
# Object Overrides ############################################################################
|
||||
|
||||
toString: (indent='')->
|
||||
"Build Tree\n#{@_rootNode.toString(indent + ' ')}"
|
||||
@@ -1,77 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - graph_builder.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
fixtures = require './fixtures.test'
|
||||
ItemSlug = require '../game/item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
builder = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'GraphBuilder.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
builder = fixtures.makeGraphBuilder()
|
||||
|
||||
describe 'expand', ->
|
||||
|
||||
it 'can work a few steps at a time', ->
|
||||
builder.want.add ItemSlug.slugify 'test__iron_sword'
|
||||
builder.expandGraph 9
|
||||
|
||||
builder.rootNode.depth.should.equal 6
|
||||
builder.rootNode.size.should.equal 13
|
||||
builder.complete.should.be.false
|
||||
|
||||
builder.expandGraph 9
|
||||
|
||||
builder.rootNode.depth.should.equal 8
|
||||
builder.rootNode.size.should.equal 18
|
||||
builder.complete.should.be.true
|
||||
|
||||
it 'works properly with an empty inventory', ->
|
||||
builder.expandGraph 100
|
||||
|
||||
builder.rootNode.depth.should.equal 1
|
||||
builder.rootNode.size.should.equal 1
|
||||
builder.complete.should.be.true
|
||||
|
||||
describe 'can build a tree for', ->
|
||||
|
||||
runSingleItemTreeBuildingTest = (slug, depth, size)->
|
||||
builder.want.add ItemSlug.slugify slug
|
||||
builder.expandGraph 100
|
||||
|
||||
builder.rootNode.depth.should.equal depth
|
||||
builder.rootNode.size.should.equal size
|
||||
builder.complete.should.be.true
|
||||
|
||||
it 'a gatherable item', ->
|
||||
runSingleItemTreeBuildingTest 'test__oak_wood', 2, 2
|
||||
|
||||
it 'a single-step item', ->
|
||||
runSingleItemTreeBuildingTest 'test__crafting_table', 6, 6
|
||||
|
||||
it 'an item with multiple inputs', ->
|
||||
runSingleItemTreeBuildingTest 'test__lever', 8, 9
|
||||
|
||||
it 'an item with multiple recipes', ->
|
||||
runSingleItemTreeBuildingTest 'test__iron_ingot', 6, 11
|
||||
|
||||
it 'an item with multiple inputs and multiple recipes', ->
|
||||
runSingleItemTreeBuildingTest 'test__iron_sword', 8, 18
|
||||
|
||||
it 'an item with one recursive recipe', ->
|
||||
runSingleItemTreeBuildingTest 'test__copper_ingot', 6, 15
|
||||
|
||||
it 'an item which gatherable and craftable', ->
|
||||
runSingleItemTreeBuildingTest 'test__wool', 4, 4
|
||||
|
||||
it 'an item which requires a gatherable and craftable item', ->
|
||||
runSingleItemTreeBuildingTest 'test__bed', 6, 7
|
||||
@@ -1,55 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - inventory_node.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CraftingNode = require './crafting_node'
|
||||
ItemNode = require './item_node'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class InventoryNode extends CraftingNode
|
||||
|
||||
@::ENTER_METHOD = 'onEnterInventoryNode'
|
||||
@::LEAVE_METHOD = 'onLeaveInventoryNode'
|
||||
@::TYPE = CraftingNode::TYPES.INVENTORY
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.inventory? then throw new Error 'options.inventory is required'
|
||||
super options
|
||||
|
||||
@inventory = options.inventory
|
||||
|
||||
# CraftingNode Overrides #######################################################################
|
||||
|
||||
_createChildren: (result=[])->
|
||||
@inventory.each (stack)=>
|
||||
item = @modPack.findItem stack.itemSlug
|
||||
if not item? then throw new Error "Could not find an item for slug: #{stack.itemSlug}"
|
||||
result.push new ItemNode modPack:@modPack, item:item, ignoreGatherable:true
|
||||
return result
|
||||
|
||||
_checkCompleteness: ->
|
||||
for child in @children
|
||||
return false unless child.complete
|
||||
return true
|
||||
|
||||
_checkValidity: ->
|
||||
for child in @children
|
||||
return false unless child.valid
|
||||
return true
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: (options={})->
|
||||
options.indent ?= ''
|
||||
options.recursive ?= true
|
||||
|
||||
parts = ["#{options.indent}#{@completeText} #{@validText} InventoryNode for #{@inventory}"]
|
||||
nextIndent = options.indent + ' '
|
||||
if options.recursive
|
||||
for child in @children
|
||||
parts.push child.toString indent:nextIndent
|
||||
return parts.join '\n'
|
||||
@@ -1,98 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_node.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CraftingNode = require './crafting_node'
|
||||
RecipeNode = require './recipe_node'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemNode extends CraftingNode
|
||||
|
||||
@::ENTER_METHOD = 'onEnterItemNode'
|
||||
@::LEAVE_METHOD = 'onLeaveItemNode'
|
||||
@::TYPE = CraftingNode::TYPES.ITEM
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.item? then throw new Error 'options.item is required'
|
||||
super options
|
||||
|
||||
@item = options.item
|
||||
@_ignoreGatherable = options.ignoreGatherable ?= false
|
||||
@_recipes = null
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getRecipes: ->
|
||||
if not @_recipes?
|
||||
@_recipes = @modPack.findRecipes @item.slug, forCrafting:true, onlyPrimary:true
|
||||
if not @_recipes
|
||||
@_recipes = @modPack.findRecipes @item.slug, forCrafting:true
|
||||
|
||||
return @_recipes or []
|
||||
|
||||
isGatherable: ->
|
||||
if not @_ignoreGatherable
|
||||
return true if @item.isGatherable
|
||||
return true if @getRecipes().length is 0
|
||||
return false
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
gatherable: { get:@prototype.isGatherable }
|
||||
recipes: { get:@prototype.getRecipes }
|
||||
|
||||
# CraftingNode Overrides #######################################################################
|
||||
|
||||
_createChildren: (result=[])->
|
||||
recipes = @getRecipes()
|
||||
return [] if @gatherable
|
||||
|
||||
for recipe in recipes
|
||||
child = new RecipeNode modPack:@modPack, recipe:recipe
|
||||
child.parent = this
|
||||
if child.valid
|
||||
result.push child
|
||||
|
||||
return result
|
||||
|
||||
_checkCompleteness: ->
|
||||
return true if @gatherable
|
||||
return false unless @children?
|
||||
|
||||
for child in @children
|
||||
return true if child.complete
|
||||
return false
|
||||
|
||||
_checkValidity: ->
|
||||
return false if @_isRepeatedItem()
|
||||
return true unless @children.length > 0
|
||||
|
||||
for child in @children
|
||||
return true if child.valid
|
||||
return false
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: (options={})->
|
||||
options.indent ?= ''
|
||||
options.recursive ?= true
|
||||
|
||||
parts = ["#{options.indent}#{@completeText} #{@validText} ItemNode for #{@item.name}"]
|
||||
nextIndent = options.indent + ' '
|
||||
if options.recursive
|
||||
for child in @children
|
||||
parts.push child.toString indent:nextIndent
|
||||
return parts.join '\n'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_isRepeatedItem: ->
|
||||
nextParent = @parent
|
||||
while nextParent?
|
||||
return true if nextParent.item is @item
|
||||
nextParent = nextParent.parent
|
||||
|
||||
return false
|
||||
@@ -1,127 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - plan_builder.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CraftingNode = require './crafting_node'
|
||||
CraftingPlan = require './crafting_plan'
|
||||
CraftingStep = require './crafting_step'
|
||||
Inventory = require '../game/inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class PlanBuilder
|
||||
|
||||
constructor: (rootNode, modPack, options={})->
|
||||
if not rootNode? then throw new Error 'rootNode is required'
|
||||
if not modPack? then throw new Error 'modPack is required'
|
||||
|
||||
@want = options.want
|
||||
@have = options.have
|
||||
|
||||
@_choiceNodes = []
|
||||
@_complete = false
|
||||
@_maxPlanCount = c.limits.maximumPlanCount
|
||||
@_modPack = modPack
|
||||
@_plans = []
|
||||
@_rootNode = rootNode
|
||||
|
||||
@_isolateChoiceNodes()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
producePlans: (maxPlans=null)->
|
||||
maxPlans = if maxPlans then @plans.length + maxPlans else Number.MAX_VALUE
|
||||
|
||||
if @plans.length >= @_maxPlanCount
|
||||
@_complete = true
|
||||
|
||||
while not @complete and (@plans.length < maxPlans)
|
||||
plan = @_captureCurrentPlan()
|
||||
if plan?
|
||||
@plans.push plan
|
||||
|
||||
@_incrementChoiceNodes()
|
||||
|
||||
return @_plans
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
complete:
|
||||
get: -> @_complete
|
||||
|
||||
have:
|
||||
get: -> @_have
|
||||
set: (have)-> @_have = have or new Inventory
|
||||
|
||||
maxPlanCount:
|
||||
get: -> @_maxPlanCount
|
||||
set: (value)-> @_maxPlanCount = value
|
||||
|
||||
plans:
|
||||
get: -> @_plans
|
||||
|
||||
want:
|
||||
get: -> @_want
|
||||
set: (want)-> @_want = want or new Inventory
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_captureCurrentPlan: ->
|
||||
toVisit = [@_rootNode]
|
||||
stepNodes = []
|
||||
|
||||
while toVisit.length > 0
|
||||
node = toVisit.shift()
|
||||
|
||||
if node.TYPE is CraftingNode::TYPES.INVENTORY
|
||||
toVisit.push(c) for c in node.children
|
||||
else if node.TYPE is CraftingNode::TYPES.ITEM
|
||||
toVisit.push node.children[0] if node.children.length > 0
|
||||
else if node.TYPE is CraftingNode::TYPES.RECIPE
|
||||
stepNodes.push node
|
||||
toVisit.push(c) for c in node.children
|
||||
|
||||
steps = []
|
||||
seenRecipes = {}
|
||||
index = stepNodes.length - 1
|
||||
while index >= 0
|
||||
node = stepNodes[index]
|
||||
index -= 1
|
||||
return null unless node.valid and node.complete
|
||||
|
||||
recipeSlug = node.recipe.slug
|
||||
continue if seenRecipes[recipeSlug]?
|
||||
|
||||
seenRecipes[recipeSlug] = true
|
||||
steps.push new CraftingStep node.recipe, @_modPack
|
||||
|
||||
plan = new CraftingPlan @_modPack, @_want, @_have, steps
|
||||
return plan
|
||||
|
||||
_incrementChoiceNodes: ->
|
||||
if @_choiceNodes.length is 0
|
||||
@_complete = true
|
||||
return
|
||||
|
||||
index = @_choiceNodes.length - 1
|
||||
while true
|
||||
if index is -1
|
||||
@_complete = true
|
||||
return
|
||||
|
||||
node = @_choiceNodes[index]
|
||||
node.rotateChildren()
|
||||
|
||||
return unless node.rotations % node.children.length is 0
|
||||
index -= 1
|
||||
|
||||
_isolateChoiceNodes: ->
|
||||
@_rootNode.acceptVisitor
|
||||
onEnterItemNode: (node)=>
|
||||
if node.children.length > 1
|
||||
@_choiceNodes.push node
|
||||
@@ -1,43 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - plan_builder.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
fixtures = require './fixtures.test'
|
||||
PlanBuilder = require './plan_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'plan_builder.coffee', ->
|
||||
|
||||
printPlan = (plan)->
|
||||
return '' unless plan?
|
||||
return ((s.recipe.slug.replace(/^.*>.*>/, '') for s in plan.steps;;)).join ' > '
|
||||
|
||||
it 'generates an empty plan for a gatherable item', ->
|
||||
plans = fixtures.makePlans [1, 'test__oak_wood']
|
||||
|
||||
plans.length.should.equal 1
|
||||
plans[0].length.should.equal 0
|
||||
|
||||
it 'can find a multi-step plan', ->
|
||||
plans = fixtures.makePlans [1, 'test__lever']
|
||||
|
||||
printPlan(plans[0]).should.equal '4 test__oak_planks > 4 test__stick > test__lever'
|
||||
plans.length.should.equal 1
|
||||
|
||||
it 'can find multiple plans', ->
|
||||
plans = fixtures.makePlans [1, 'test__iron_ingot']
|
||||
|
||||
printPlan(plans[0]).should.equal '8 test__charcoal > 8 test__iron_ingot'
|
||||
printPlan(plans[1]).should.equal '8 test__iron_ingot'
|
||||
plans.length.should.equal 2
|
||||
|
||||
it 'ignores invalid plans', ->
|
||||
plans = fixtures.makePlans [1, 'test__copper_block']
|
||||
|
||||
printPlan(plans[0]).should.equal '8 test__copper_ingot > test__copper_block'
|
||||
printPlan(plans[1]).should.equal '8 test__charcoal > 8 test__copper_ingot > test__copper_block'
|
||||
plans.length.should.equal 2
|
||||
@@ -1,98 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - plan_evaluator.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class PlanEvaluator
|
||||
|
||||
@::CRITERIA =
|
||||
FEWEST_STEPS: 'fewest steps'
|
||||
LEAST_MATERIALS: 'least materials'
|
||||
|
||||
constructor: (plans)->
|
||||
if not plans? then throw new Error 'plans is required'
|
||||
|
||||
@_plans = plans
|
||||
@_lastScored = -1
|
||||
@_normalized = false
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
findBestPlan: (mainCriteria)->
|
||||
criteriaList = [mainCriteria].concat (criteria for key, criteria of @CRITERIA when criteria isnt mainCriteria)
|
||||
@_plans.sort (a, b)->
|
||||
for criteria in criteriaList
|
||||
scoreA = a.getScore criteria
|
||||
scoreB = b.getScore criteria
|
||||
|
||||
if scoreA isnt scoreB
|
||||
return if scoreA > scoreB then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
return @_plans[0]
|
||||
|
||||
scorePlans: (count)->
|
||||
count ?= @_plans.length
|
||||
return if @complete
|
||||
|
||||
maxPlanIndex = Math.min @_lastScored + count, @_plans.length - 1
|
||||
for i in [(@_lastScored + 1)..maxPlanIndex]
|
||||
@_plans[i].computeRequired()
|
||||
@_scorePlan @_plans[i]
|
||||
@_lastScored = i
|
||||
|
||||
if @complete then @_normalizeScores()
|
||||
|
||||
return @complete
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
complete:
|
||||
get: -> @_lastScored is @_plans.length - 1
|
||||
|
||||
lastScored:
|
||||
get: -> @_lastScored
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeFewestStepsScore: (plan)->
|
||||
total = 0
|
||||
for step in plan.steps
|
||||
total += step.multiplier
|
||||
plan.setRawScore @CRITERIA.FEWEST_STEPS, total
|
||||
|
||||
_computeLeastMaterialsScore: (plan)->
|
||||
total = 0
|
||||
plan.need.each (stack)->
|
||||
total += stack.quantity
|
||||
plan.setRawScore @CRITERIA.LEAST_MATERIALS, total
|
||||
|
||||
_normalizeScores: ->
|
||||
for key, criteria of @CRITERIA
|
||||
maxScore = 0
|
||||
minScore = Number.MAX_VALUE
|
||||
|
||||
for plan in @_plans
|
||||
continue unless plan.hasRawScore criteria
|
||||
maxScore = Math.max maxScore, plan.getRawScore(criteria)
|
||||
minScore = Math.min minScore, plan.getRawScore(criteria)
|
||||
|
||||
adjustedMaxScore = maxScore - minScore
|
||||
for plan in @_plans
|
||||
continue unless plan.hasRawScore criteria
|
||||
if adjustedMaxScore > 0
|
||||
adjustedRawScore = plan.getRawScore(criteria) - minScore
|
||||
plan.setScore criteria, 1.0 - (1.0 * adjustedRawScore / adjustedMaxScore)
|
||||
else
|
||||
plan.setScore criteria, 1.0
|
||||
|
||||
_scorePlan: (plan)->
|
||||
@_computeFewestStepsScore plan
|
||||
@_computeLeastMaterialsScore plan
|
||||
@@ -1,80 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - plan_evaluator.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CraftingPlan = require './crafting_plan'
|
||||
fixtures = require './fixtures.test'
|
||||
Inventory = require '../game/inventory'
|
||||
PlanEvaluator = require './plan_evaluator'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
criteria = evaluator = planA = planB = planC = plans = wanted = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'plan_evaluator.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
criteria = PlanEvaluator::CRITERIA.FEWEST_STEPS
|
||||
|
||||
describe 'findBestPlan', ->
|
||||
|
||||
it 'can find the shortest of two options', ->
|
||||
evaluator = new PlanEvaluator fixtures.makePlans [1, 'test__copper_block']
|
||||
evaluator.scorePlans()
|
||||
|
||||
bestPlan = evaluator.findBestPlan criteria
|
||||
bestPlan.getScore(criteria).should.equal 1.0
|
||||
|
||||
it 'can find the shortest of many options', ->
|
||||
evaluator = new PlanEvaluator fixtures.makePlans [1, 'test__copper_block'], [1, 'test__iron_sword']
|
||||
evaluator.scorePlans()
|
||||
|
||||
bestPlan = evaluator.findBestPlan criteria
|
||||
bestPlan.getScore(criteria).should.equal 1.0
|
||||
|
||||
it 'can break ties using the non-primary criteria', ->
|
||||
evaluator = new PlanEvaluator fixtures.makePlans [2, 'test__iron_ingot'], [1, 'test__charcoal']
|
||||
evaluator.scorePlans()
|
||||
|
||||
bestPlan = evaluator.findBestPlan criteria
|
||||
bestPlan.getScore(criteria).should.equal 1.0
|
||||
|
||||
(p.getRawScore('fewest steps') for p in evaluator._plans).should.eql [2, 2]
|
||||
(p.getRawScore('least materials') for p in evaluator._plans).should.eql [17, 18]
|
||||
|
||||
describe '_normalizeScores', ->
|
||||
|
||||
beforeEach ->
|
||||
modPack = fixtures.makeModPack()
|
||||
wanted = new Inventory modPack:modPack
|
||||
have = new Inventory modPack:modPack
|
||||
planA = new CraftingPlan modPack, wanted, have, []
|
||||
planB = new CraftingPlan modPack, wanted, have, []
|
||||
planC = new CraftingPlan modPack, wanted, have, []
|
||||
plans = [planA, planB, planC]
|
||||
evaluator = new PlanEvaluator plans
|
||||
criteria = PlanEvaluator::CRITERIA.FEWEST_STEPS
|
||||
|
||||
it 'does not set scores when none have been evaluated', ->
|
||||
evaluator._normalizeScores()
|
||||
(plan.hasRawScore(criteria) for plan in plans).should.eql [false, false, false]
|
||||
|
||||
it 'assigns a 1.0 when all plans equal', ->
|
||||
for plan in plans
|
||||
plan.setRawScore criteria, 5
|
||||
|
||||
evaluator._normalizeScores()
|
||||
(plan.getScore(criteria) for plan in plans).should.eql [1.0, 1.0, 1.0]
|
||||
|
||||
it 'computes correct scores from raw scores', ->
|
||||
planA.setRawScore criteria, 7
|
||||
planB.setRawScore criteria, 8
|
||||
planC.setRawScore criteria, 9
|
||||
|
||||
evaluator._normalizeScores()
|
||||
(plan.getScore(criteria) for plan in plans).should.eql [1.0, 0.5, 0.0]
|
||||
@@ -1,87 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - recipe_node.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CraftingNode = require './crafting_node'
|
||||
Item = require '../game/item'
|
||||
# ItemNode = require './item_node' # don't include here, causes a cycle
|
||||
ItemSlug = require '../game/item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class RecipeNode extends CraftingNode
|
||||
|
||||
@::ENTER_METHOD = 'onEnterRecipeNode'
|
||||
@::LEAVE_METHOD = 'onLeaveRecipeNode'
|
||||
@::TYPE = CraftingNode::TYPES.RECIPE
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.recipe? then throw new Error 'options.recipe is required'
|
||||
super options
|
||||
|
||||
@recipe = options.recipe
|
||||
|
||||
# CraftingNode Overrides #######################################################################
|
||||
|
||||
_createChildren: (result=[])->
|
||||
ItemNode = require './item_node' # include here to avoid a cycle
|
||||
|
||||
for stack in @recipe.input
|
||||
item = @modPack.findItem stack.itemSlug
|
||||
if not item?
|
||||
name = @modPack.findName stack.itemSlug
|
||||
item = new Item name:name, slug:stack.itemSlug, gatherable:true
|
||||
result.push new ItemNode modPack:@modPack, item:item
|
||||
return result
|
||||
|
||||
_checkCompleteness: ->
|
||||
for child in @children
|
||||
return false unless child.complete
|
||||
return true
|
||||
|
||||
_checkValidity: ->
|
||||
return false if @_isRepeatedRecipe()
|
||||
return false if @_requiresToolBeingMade()
|
||||
|
||||
for child in @children
|
||||
return false unless child.valid
|
||||
|
||||
return true
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_isRepeatedRecipe: ->
|
||||
nextParent = @parent
|
||||
while nextParent?
|
||||
return true if nextParent.recipe is @recipe
|
||||
nextParent = nextParent.parent
|
||||
|
||||
return false
|
||||
|
||||
_requiresToolBeingMade: ->
|
||||
for toolStack in @recipe.tools
|
||||
toolSlug = toolStack.itemSlug
|
||||
|
||||
nextParent = @parent
|
||||
while nextParent?
|
||||
if ItemSlug.equal toolSlug, nextParent.item?.slug
|
||||
return true
|
||||
nextParent = nextParent.parent
|
||||
|
||||
return false
|
||||
|
||||
# Object Overrides ############################################################################
|
||||
|
||||
toString: (options={})->
|
||||
options.indent ?= ''
|
||||
options.recursive ?= true
|
||||
|
||||
parts = ["#{options.indent}#{@completeText} #{@validText} RecipeNode for #{@recipe.slug}"]
|
||||
nextIndent = options.indent + ' '
|
||||
if options.recursive
|
||||
for child in @children
|
||||
parts.push child.toString indent:nextIndent
|
||||
return parts.join '\n'
|
||||
@@ -1,212 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - simple_inventory.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
ItemSlug = require '../game/item_slug'
|
||||
SimpleStack = require './simple_stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class SimpleInventory
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
@clear()
|
||||
|
||||
if options.modPack?
|
||||
@modPack = options.modPack
|
||||
|
||||
if options.clone?
|
||||
@addInventory options.clone
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@Delimiters =
|
||||
Item: '.'
|
||||
Stack: ':'
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (itemSlug, quantity=1)->
|
||||
return this unless quantity > 0
|
||||
|
||||
@_add itemSlug, quantity
|
||||
return this
|
||||
|
||||
addInventory: (inventory)->
|
||||
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
|
||||
return this
|
||||
|
||||
clear: (options={})->
|
||||
@_stacks = {}
|
||||
@_itemSlugs = []
|
||||
|
||||
clone: ->
|
||||
inventory = new Inventory
|
||||
inventory.addInventory this
|
||||
return inventory
|
||||
|
||||
each: (callback)->
|
||||
for itemSlug in @_itemSlugs
|
||||
callback @_stacks[itemSlug]
|
||||
|
||||
getSlugs: ->
|
||||
return @_itemSlugs[..]
|
||||
|
||||
hasAtLeast: (itemSlug, quantity=1)->
|
||||
if quantity is 0 then return true
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
return false unless stack?
|
||||
return stack.quantity >= quantity
|
||||
|
||||
localize: ->
|
||||
if not @modPack? then throw new Error 'localize requires @modPack'
|
||||
|
||||
changed = false
|
||||
newSlugs = []
|
||||
newStacks = []
|
||||
for itemSlug in @_itemSlugs
|
||||
stack = @_stacks[itemSlug]
|
||||
continue unless stack?
|
||||
|
||||
qualifiedSlug = if itemSlug.isQualified then itemSlug else null
|
||||
if not qualifiedSlug?
|
||||
qualifiedSlug = @modPack.findItem(itemSlug)?.slug
|
||||
changed = qualifiedSlug?
|
||||
|
||||
if qualifiedSlug?
|
||||
newSlugs.push qualifiedSlug
|
||||
newStacks.push new Stack itemSlug:qualifiedSlug, quantity:stack.quantity
|
||||
else
|
||||
newSlugs.push itemSlug
|
||||
newStacks.push stack
|
||||
|
||||
if changed
|
||||
@_itemSlugs = newSlugs
|
||||
@_stacks = {}
|
||||
for stack in newStacks
|
||||
@_stacks[stack.itemSlug] = stack
|
||||
|
||||
@_sort()
|
||||
|
||||
pop: ->
|
||||
itemSlug = @_itemSlugs.pop()
|
||||
return null unless itemSlug?
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
delete @_stacks[itemSlug]
|
||||
|
||||
return stack
|
||||
|
||||
quantityOf: (itemSlug)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return stack.quantity if stack
|
||||
return 0
|
||||
|
||||
remove: (itemSlug, quantity=null)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return this unless stack?
|
||||
|
||||
quantity ?= stack.quantity
|
||||
return this unless quantity > 0
|
||||
|
||||
if stack.quantity < quantity
|
||||
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{itemSlug} in this inventory"
|
||||
|
||||
stack.quantity -= quantity
|
||||
if stack.quantity is 0
|
||||
for currentItemSlug, index in @_itemSlugs
|
||||
if ItemSlug.equal itemSlug, currentItemSlug
|
||||
@_itemSlugs.splice index, 1
|
||||
break
|
||||
|
||||
return this
|
||||
|
||||
# Parsing Methods ##############################################################################
|
||||
|
||||
parse: (data)->
|
||||
return this if not data? or data.length is 0
|
||||
|
||||
stacks = data.split Inventory.Delimiters.Stack
|
||||
for stackText in stacks
|
||||
stackParts = stackText.split Inventory.Delimiters.Item
|
||||
if stackParts.length is 2
|
||||
quantity = parseInt stackParts[0], 10
|
||||
itemSlug = ItemSlug.slugify stackParts[1]
|
||||
else if stackParts.length is 1
|
||||
quantity = 1
|
||||
itemSlug = ItemSlug.slugify stackParts[0]
|
||||
else
|
||||
throw new Error "expected #{stackText} to have 0 or 1 parts"
|
||||
|
||||
if itemSlug.qualified.length > 0
|
||||
@add itemSlug, quantity
|
||||
|
||||
return this
|
||||
|
||||
unparse: (options={})->
|
||||
parts = []
|
||||
@each (stack)=>
|
||||
slugText = stack.itemSlug.item
|
||||
if @modPack?
|
||||
item = @modPack.findItem ItemSlug.slugify slugText
|
||||
if item? and item.slug.qualified isnt stack.itemSlug.qualified
|
||||
slugText = stack.itemSlug.qualified
|
||||
|
||||
if stack.quantity is 1
|
||||
parts.push slugText
|
||||
else
|
||||
parts.push "#{stack.quantity}#{SimpleInventory.Delimiters.Item}#{slugText}"
|
||||
|
||||
return parts.join SimpleInventory.Delimiters.Stack
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getIsEmpty: ->
|
||||
return @_itemSlugs.length is 0
|
||||
|
||||
getTotalQuantity: ->
|
||||
total = 0
|
||||
@each (stack)->
|
||||
total += stack.quantity
|
||||
return total
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isEmpty: { get:@prototype.getIsEmpty }
|
||||
totalQuantity: { get:@prototype.getTotalQuantity }
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") {items: ["]
|
||||
|
||||
needsDelimiter = false
|
||||
@each (stack)->
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push stack.toString()
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push '}'
|
||||
return result.join ''
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_add: (itemSlug, quantity=1)->
|
||||
return unless itemSlug?
|
||||
return unless quantity > 0
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
if not stack?
|
||||
stack = new SimpleStack itemSlug:itemSlug, quantity:quantity
|
||||
@_stacks[itemSlug] = stack
|
||||
@_itemSlugs.push itemSlug
|
||||
@_sort()
|
||||
else
|
||||
stack.quantity += quantity
|
||||
|
||||
_sort: ->
|
||||
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
|
||||
@@ -1,33 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - simple_stack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
ItemSlug = require '../game/item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Stack
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
|
||||
attributes.quantity ?= 1
|
||||
|
||||
@itemSlug = attributes.itemSlug
|
||||
@quantity = attributes.quantity
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@compare: (a, b)->
|
||||
if a? and not b? then return -1
|
||||
if not a? and b? then return +1
|
||||
if a.quantity isnt b.quantity
|
||||
return if a.quantity > b.quantity then -1 else +1
|
||||
return ItemSlug.compare a.itemSlug, b.itemSlug
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@quantity} #{@itemSlug}"
|
||||
@@ -1,36 +0,0 @@
|
||||
###
|
||||
# Crafting Guide - event_recorder.coffee
|
||||
#
|
||||
# Copyright (c) 2014-2015 by Redwood Labs
|
||||
# All rights reserved.
|
||||
###
|
||||
|
||||
util = require 'util'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class EventRecorder
|
||||
|
||||
constructor: (model)->
|
||||
if not model? then throw new Error 'model is required'
|
||||
|
||||
@model = model
|
||||
@events = []
|
||||
|
||||
@model.on 'all', (event, model, args...)=>
|
||||
logger.verbose -> "#{model?.constructor?.name}(#{model?.cid}) emitted #{event}
|
||||
with args: #{util.inspect(args)}"
|
||||
@events.push id:model?.cid, event:event, args:args
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
reset: ->
|
||||
@events = []
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getNames: ->
|
||||
return (e.event for e in @events)
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
names: {get:@prototype.getNames}
|
||||
@@ -1,227 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - inventory.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
ItemSlug = require './item_slug'
|
||||
Stack = require './stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Inventory extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
attributes.modPack ?= null
|
||||
@clear()
|
||||
|
||||
if options.clone?
|
||||
@addInventory options.clone
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@Delimiters =
|
||||
Item: '.'
|
||||
Stack: ':'
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (itemSlug, quantity=1)->
|
||||
return this unless quantity > 0
|
||||
|
||||
@_add itemSlug, quantity
|
||||
@trigger c.event.add, this, itemSlug, quantity
|
||||
@trigger c.event.change, this
|
||||
return this
|
||||
|
||||
addInventory: (inventory)->
|
||||
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
|
||||
|
||||
@trigger c.event.change, this
|
||||
return this
|
||||
|
||||
clear: (options={})->
|
||||
@_stacks = {}
|
||||
@_itemSlugs = []
|
||||
|
||||
@trigger c.event.change, this
|
||||
|
||||
clone: ->
|
||||
inventory = new Inventory
|
||||
inventory.addInventory this
|
||||
return inventory
|
||||
|
||||
each: (callback)->
|
||||
for itemSlug in @_itemSlugs
|
||||
stack = @_stacks[itemSlug]
|
||||
continue unless stack?
|
||||
callback stack
|
||||
|
||||
getSlugs: ->
|
||||
return @_itemSlugs[..]
|
||||
|
||||
hasAtLeast: (itemSlug, quantity=1)->
|
||||
if quantity is 0 then return true
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
return false unless stack?
|
||||
return stack.quantity >= quantity
|
||||
|
||||
localize: ->
|
||||
if not @modPack? then throw new Error 'localize requires @modPack'
|
||||
|
||||
changed = false
|
||||
newSlugs = []
|
||||
newStacks = []
|
||||
for itemSlug in @_itemSlugs
|
||||
stack = @_stacks[itemSlug]
|
||||
continue unless stack?
|
||||
|
||||
qualifiedSlug = if itemSlug.isQualified then itemSlug else null
|
||||
if not qualifiedSlug?
|
||||
qualifiedSlug = @modPack.findItem(itemSlug)?.slug
|
||||
changed = qualifiedSlug?
|
||||
|
||||
if qualifiedSlug?
|
||||
newSlugs.push qualifiedSlug
|
||||
newStacks.push new Stack itemSlug:qualifiedSlug, quantity:stack.quantity
|
||||
else
|
||||
newSlugs.push itemSlug
|
||||
newStacks.push stack
|
||||
|
||||
if changed
|
||||
for itemSlug, stack of @_stacks
|
||||
@stopListening stack
|
||||
|
||||
@_itemSlugs = newSlugs
|
||||
@_stacks = {}
|
||||
for stack in newStacks
|
||||
@_stacks[stack.itemSlug] = stack
|
||||
@listenTo stack, c.event.change, => @trigger c.event.change, this
|
||||
|
||||
@_sort()
|
||||
@trigger c.event.change, this
|
||||
|
||||
pop: ->
|
||||
itemSlug = @_itemSlugs.pop()
|
||||
return null unless itemSlug?
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
delete @_stacks[itemSlug]
|
||||
|
||||
@trigger c.event.remove, this, stack.itemSlug, stack.quantity
|
||||
@trigger c.event.change, this
|
||||
return stack
|
||||
|
||||
quantityOf: (itemSlug)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return stack.quantity if stack
|
||||
return 0
|
||||
|
||||
remove: (itemSlug, quantity=null)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return this unless stack?
|
||||
|
||||
quantity ?= stack.quantity
|
||||
return this unless quantity > 0
|
||||
|
||||
if stack.quantity < quantity
|
||||
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{itemSlug} in this inventory"
|
||||
|
||||
stack.quantity -= quantity
|
||||
if stack.quantity is 0
|
||||
@stopListening stack
|
||||
delete @_stacks[itemSlug]
|
||||
@_itemSlugs = (s for s in @_itemSlugs when not ItemSlug.equal(s, itemSlug))
|
||||
|
||||
@trigger c.event.remove, this, itemSlug, quantity
|
||||
@trigger c.event.change, this
|
||||
return this
|
||||
|
||||
# Parsing Methods ##############################################################################
|
||||
|
||||
parse: (data)->
|
||||
return this if not data? or data.length is 0
|
||||
|
||||
stacks = data.split Inventory.Delimiters.Stack
|
||||
for stackText in stacks
|
||||
stackParts = stackText.split Inventory.Delimiters.Item
|
||||
if stackParts.length is 2
|
||||
quantity = parseInt stackParts[0], 10
|
||||
itemSlug = ItemSlug.slugify stackParts[1]
|
||||
else if stackParts.length is 1
|
||||
quantity = 1
|
||||
itemSlug = ItemSlug.slugify stackParts[0]
|
||||
else
|
||||
throw new Error "expected #{stackText} to have 0 or 1 parts"
|
||||
|
||||
if itemSlug.qualified.length > 0
|
||||
@add itemSlug, quantity
|
||||
|
||||
return this
|
||||
|
||||
unparse: (options={})->
|
||||
parts = []
|
||||
@each (stack)=>
|
||||
slugText = stack.itemSlug.item
|
||||
if @modPack?
|
||||
item = @modPack.findItem ItemSlug.slugify slugText
|
||||
if item? and item.slug.qualified isnt stack.itemSlug.qualified
|
||||
slugText = stack.itemSlug.qualified
|
||||
|
||||
if stack.quantity is 1
|
||||
parts.push slugText
|
||||
else
|
||||
parts.push "#{stack.quantity}#{Inventory.Delimiters.Item}#{slugText}"
|
||||
|
||||
return parts.join Inventory.Delimiters.Stack
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isEmpty:
|
||||
get: -> @_itemSlugs.length is 0
|
||||
|
||||
totalQuantity:
|
||||
get: ->
|
||||
total = 0
|
||||
@each (stack)->
|
||||
total += stack.quantity
|
||||
return total
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") {items: ["]
|
||||
|
||||
needsDelimiter = false
|
||||
@each (stack)->
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push stack.toString()
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push '}'
|
||||
return result.join ''
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_add: (itemSlug, quantity=1)->
|
||||
return unless itemSlug?
|
||||
return unless quantity > 0
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
if not stack?
|
||||
stack = new Stack itemSlug:itemSlug, quantity:quantity
|
||||
@listenTo stack, c.event.change, => @trigger c.event.change, this
|
||||
@_stacks[itemSlug] = stack
|
||||
@_itemSlugs.push itemSlug
|
||||
@_sort()
|
||||
else
|
||||
stack.quantity += quantity
|
||||
|
||||
_sort: ->
|
||||
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
|
||||
@@ -1,213 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - inventory.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
EventRecorder = require '../event_recorder'
|
||||
Inventory = require './inventory'
|
||||
Item = require './item'
|
||||
ItemSlug = require './item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
inventory = modPack = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'inventory.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
inventory = new Inventory {}, silent:false
|
||||
inventory.add ItemSlug.slugify('wool'), 4
|
||||
inventory.add ItemSlug.slugify('string'), 20
|
||||
inventory.add ItemSlug.slugify('boat')
|
||||
|
||||
describe 'add', ->
|
||||
|
||||
it 'can add to an empty inventory', ->
|
||||
inventory.add ItemSlug.slugify('iron_ingot'), 4
|
||||
stack = inventory._stacks['iron_ingot']
|
||||
stack.constructor.name.should.equal 'Stack'
|
||||
stack.itemSlug.qualified.should.equal 'iron_ingot'
|
||||
stack.quantity.should.equal 4
|
||||
|
||||
it 'can augment quantity of existing items', ->
|
||||
inventory.add ItemSlug.slugify('wool'), 2
|
||||
inventory.unparse().should.equal 'boat:20.string:6.wool'
|
||||
|
||||
it 'can add zero quantity', ->
|
||||
inventory.add ItemSlug.slugify('wool'), 0
|
||||
inventory.unparse().should.equal 'boat:20.string:4.wool'
|
||||
|
||||
it 'emits the proper events', ->
|
||||
events = new EventRecorder inventory
|
||||
inventory.add ItemSlug.slugify('iron_ingot'), 10
|
||||
events.names.should.eql [c.event.add, c.event.change]
|
||||
|
||||
describe 'addInventory', ->
|
||||
|
||||
it 'can add to an empty inventory', ->
|
||||
newInventory = new Inventory
|
||||
newInventory.addInventory inventory
|
||||
newInventory.unparse().should.equal 'boat:20.string:4.wool'
|
||||
|
||||
it 'can add a mix of new and existing items', ->
|
||||
newInventory = new Inventory
|
||||
newInventory.add ItemSlug.slugify('string'), 2
|
||||
newInventory.addInventory inventory
|
||||
newInventory.unparse().should.equal 'boat:22.string:4.wool'
|
||||
|
||||
describe 'clone', ->
|
||||
|
||||
it 'creates an empty inventory from an empty inventory', ->
|
||||
a = new Inventory
|
||||
b = a.clone()
|
||||
b._itemSlugs.should.eql []
|
||||
|
||||
it 'faithfully copies an existing inventory', ->
|
||||
copy = inventory.clone()
|
||||
copy.unparse().should.equal 'boat:20.string:4.wool'
|
||||
|
||||
describe 'each', ->
|
||||
|
||||
it 'works with an empty inventory', ->
|
||||
inventory = new Inventory
|
||||
result = []
|
||||
inventory.each (item)-> result.push item.name
|
||||
result.should.eql []
|
||||
|
||||
it 'works when items have only been added', ->
|
||||
result = []
|
||||
inventory.each (stack)-> result.push stack.itemSlug.qualified
|
||||
result.should.eql ['boat', 'string', 'wool']
|
||||
|
||||
it 'works when items have been augmented', ->
|
||||
inventory.add ItemSlug.slugify 'iron_ingot'
|
||||
inventory.add ItemSlug.slugify 'boat'
|
||||
inventory.add ItemSlug.slugify('wool'), 2
|
||||
|
||||
result = []
|
||||
inventory.each (stack)-> result.push stack.itemSlug.qualified
|
||||
result.should.eql ['boat', 'iron_ingot', 'string', 'wool']
|
||||
|
||||
describe 'hasAtLeast', ->
|
||||
|
||||
it 'works when the item is completely absent', ->
|
||||
answer = inventory.hasAtLeast 'chicken', 1
|
||||
answer.should.be.false
|
||||
|
||||
it 'always returns true for zero quantity', ->
|
||||
inventory.hasAtLeast('chicken', 0).should.be.true
|
||||
inventory.hasAtLeast('wool', 0).should.be.true
|
||||
|
||||
it 'works for a quantity above 1', ->
|
||||
inventory.hasAtLeast('wool', 3).should.be.true
|
||||
inventory.hasAtLeast('wool', 4).should.be.true
|
||||
inventory.hasAtLeast('wool', 5).should.be.false
|
||||
|
||||
describe 'localize', ->
|
||||
|
||||
before ->
|
||||
modPack =
|
||||
modSlug:
|
||||
wool: 'minecraft'
|
||||
string: 'minecraft'
|
||||
boat: 'minecraft'
|
||||
stone_gear: 'buildcraft'
|
||||
findItem: (slug)->
|
||||
return slug:new ItemSlug @modSlug[slug.item], slug.item
|
||||
|
||||
it 'replaces item slugs with qualified slugs', ->
|
||||
inventory.add ItemSlug.slugify 'stone_gear'
|
||||
inventory.modPack = modPack
|
||||
inventory.localize()
|
||||
|
||||
slugs = []
|
||||
inventory.each (stack)-> slugs.push stack.itemSlug.qualified
|
||||
slugs.should.eql [
|
||||
'minecraft__boat'
|
||||
'buildcraft__stone_gear'
|
||||
'minecraft__string'
|
||||
'minecraft__wool'
|
||||
]
|
||||
|
||||
it 'ignores qualified slugs', ->
|
||||
inventory.add ItemSlug.slugify 'buildcraft__stone_gear'
|
||||
inventory.modPack = modPack
|
||||
inventory.localize()
|
||||
|
||||
slugs = []
|
||||
inventory.each (stack)-> slugs.push stack.itemSlug.qualified
|
||||
slugs.should.eql [
|
||||
'minecraft__boat'
|
||||
'buildcraft__stone_gear'
|
||||
'minecraft__string'
|
||||
'minecraft__wool'
|
||||
]
|
||||
|
||||
describe 'parse', ->
|
||||
|
||||
beforeEach ->
|
||||
inventory = new Inventory {}, silent:false
|
||||
|
||||
it 'ignores an empty string', ->
|
||||
result = inventory.parse ''
|
||||
result.unparse().should.eql ''
|
||||
|
||||
it 'can parse a single item without quantity', ->
|
||||
result = inventory.parse 'wool'
|
||||
result.unparse().should.equal 'wool'
|
||||
|
||||
it 'can parse a single item with quantity', ->
|
||||
result = inventory.parse '4.wool'
|
||||
result.unparse().should.equal '4.wool'
|
||||
|
||||
it 'can parse multiple mixed-type items', ->
|
||||
result = inventory.parse '4.wool:10.string:boat'
|
||||
result.unparse().should.equal 'boat:10.string:4.wool'
|
||||
|
||||
describe 'pop', ->
|
||||
|
||||
it 'returns null for an empty inventory', ->
|
||||
inventory = new Inventory
|
||||
result = inventory.pop()
|
||||
expect(result).to.be.null
|
||||
|
||||
it 'completely removes the last item', ->
|
||||
stack = inventory.pop()
|
||||
stack.itemSlug.qualified.should.equal 'wool'
|
||||
stack.quantity.should.equal 4
|
||||
inventory.unparse().should.equal 'boat:20.string'
|
||||
|
||||
it 'triggers the right events', ->
|
||||
events = new EventRecorder inventory
|
||||
result = inventory.pop()
|
||||
events.names.should.eql [c.event.remove, c.event.change]
|
||||
|
||||
describe 'remove', ->
|
||||
|
||||
it 'does nothing when the item is absent', ->
|
||||
before = inventory.unparse()
|
||||
inventory.remove 'foo'
|
||||
after = inventory.unparse()
|
||||
|
||||
before.should.equal after
|
||||
|
||||
it 'throws when the item has insufficient quantity', ->
|
||||
expect(-> inventory.remove('wool', 10)).to.throw Error,
|
||||
'cannot remove 10: only 4 wool in this inventory'
|
||||
|
||||
it 'removes all items by default', ->
|
||||
inventory.remove 'wool'
|
||||
expect(inventory._stacks.wool).to.be.empty
|
||||
|
||||
it 'removes a quantity above 1', ->
|
||||
inventory.remove 'wool', 3
|
||||
inventory._stacks.wool.quantity.should.equal 1
|
||||
|
||||
it 'emits the proper events', ->
|
||||
events = new EventRecorder inventory
|
||||
inventory.remove 'wool'
|
||||
events.names.should.eql [c.event.change, c.event.remove, c.event.change]
|
||||
@@ -1,90 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
ItemSlug = require './item_slug'
|
||||
Recipe = require './recipe'
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Item extends BaseModel
|
||||
|
||||
@Group = Other:'Other'
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.name? then throw new Error 'attributes.name is required'
|
||||
|
||||
attributes.description ?= null
|
||||
attributes.group ?= Item.Group.Other
|
||||
attributes.ignoreDuringCrafting ?= false
|
||||
attributes.isGatherable ?= false
|
||||
attributes.modVersion ?= null
|
||||
attributes.officialUrl ?= null
|
||||
attributes.slug ?= ItemSlug.slugify attributes.name
|
||||
attributes.videos ?= []
|
||||
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
@on c.event.change + ':modVersion', =>
|
||||
@_isCraftable = null
|
||||
@slug.mod = @modVersion?.modSlug
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
compareTo: (that)->
|
||||
if this.slug isnt that.slug
|
||||
return if this.slug < that.slug then -1 else +1
|
||||
if this.name isnt that.name
|
||||
return if this.name < that.name then -1 else +1
|
||||
return 0
|
||||
|
||||
unparse: ->
|
||||
ItemParser = require '../parsing/item_parser' # to avoid require cycles
|
||||
@_parser ?= new ItemParser model:this
|
||||
return @_parser.unparse()
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getIsCraftable: ->
|
||||
if not @_isCraftable?
|
||||
@_isCraftable = false
|
||||
if @modVersion?
|
||||
@_isCraftable = @modVersion.hasRecipes @slug
|
||||
|
||||
return @_isCraftable
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isCraftable: {get:@prototype.getIsCraftable}
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
ItemParser = require '../parsing/item_parser' # to avoid require cycles
|
||||
@_parser ?= new ItemParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.itemData modSlug:@slug.mod, itemSlug:@slug.item
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
builder = new StringBuilder
|
||||
return builder
|
||||
.push @constructor.name, ' (', @cid, ') { '
|
||||
.push 'name:"', @name, '", '
|
||||
.push 'isCraftable:', @isCraftable, ', '
|
||||
.push 'isGatherable:', @isGatherable, ', '
|
||||
.onlyIf (@group isnt Item.Group.Other), (b)=>
|
||||
b.push 'group:"', @group, '", '
|
||||
.push 'slug:"', @slug, '", '
|
||||
.push '}'
|
||||
.toString()
|
||||
@@ -1,96 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_slug.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemSlug
|
||||
|
||||
constructor: ->
|
||||
@_item = @_mod = null
|
||||
|
||||
if arguments.length is 1
|
||||
parts = _.decomposeSlug arguments[0]
|
||||
@_mod = _.slugify parts[0]
|
||||
@item = _.slugify parts[1]
|
||||
else if arguments.length is 2
|
||||
@_mod = arguments[0]
|
||||
@item = arguments[1]
|
||||
else
|
||||
throw new Error 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@compare: (a, b)->
|
||||
if a.item isnt b.item
|
||||
return if a.item < b.item then -1 else +1
|
||||
if a.mod isnt b.mod
|
||||
return if a.mod < b.mod then -1 else +1
|
||||
return 0
|
||||
|
||||
@equal: (a, b)->
|
||||
return true if not a? and not b?
|
||||
return false unless a? and b?
|
||||
return false unless a.mod is b.mod
|
||||
return false unless a.item is b.item
|
||||
return true
|
||||
|
||||
@slugify: (arg)->
|
||||
return arg if arg?.constructor?.name is 'ItemSlug'
|
||||
|
||||
[modSlug, itemSlug] = _.decomposeSlug arg
|
||||
itemSlug = _.slugify itemSlug
|
||||
|
||||
if modSlug?
|
||||
return new ItemSlug modSlug, itemSlug
|
||||
else
|
||||
return new ItemSlug itemSlug
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
compareTo: (that)->
|
||||
return ItemSlug.compare this, that
|
||||
|
||||
matches: (slug, options={exact:false})->
|
||||
return false unless slug?
|
||||
return false unless typeof(slug.matches) is 'function'
|
||||
|
||||
if slug.isQualified and this.isQualified
|
||||
return slug.qualified is this.qualified
|
||||
else
|
||||
return slug.item is this.item
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isQualified:
|
||||
get: -> @_mod?
|
||||
|
||||
item:
|
||||
get: -> @_item
|
||||
|
||||
set: (newItem)->
|
||||
if not newItem? then throw new Error 'item is required'
|
||||
@_item = newItem
|
||||
@mod = @mod # reset @_qualified
|
||||
|
||||
mod:
|
||||
get: -> @_mod
|
||||
|
||||
set: (newMod)->
|
||||
@_mod = newMod
|
||||
@_qualified = if @_mod? then _.composeSlugs(@_mod, @_item) else @_item
|
||||
|
||||
qualified:
|
||||
get: -> @_qualified
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return @_qualified
|
||||
|
||||
valueOf: ->
|
||||
return @_qualified.valueOf()
|
||||
@@ -1,122 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_slug.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
ItemSlug = require './item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'item_slug.coffee', ->
|
||||
|
||||
describe 'constructor', ->
|
||||
|
||||
it 'can handle one argument', ->
|
||||
slug = new ItemSlug 'alpha'
|
||||
slug.item.should.equal 'alpha'
|
||||
expect(slug.mod).to.be.null
|
||||
slug.qualified.should.equal 'alpha'
|
||||
|
||||
it 'can handle two arguments', ->
|
||||
slug = new ItemSlug 'alpha', 'bravo'
|
||||
slug.mod.should.equal 'alpha'
|
||||
slug.item.should.equal 'bravo'
|
||||
slug.qualified.should.equal 'alpha__bravo'
|
||||
|
||||
it 'throws with zero arguments', ->
|
||||
f = -> new ItemSlug
|
||||
expect(f).to.throw Error, 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
|
||||
|
||||
it 'throws with more arguments', ->
|
||||
f = -> new ItemSlug 'alpha', 'bravo', 'charlie'
|
||||
expect(f).to.throw Error, 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
|
||||
|
||||
describe 'ItemSlug.compare', ->
|
||||
|
||||
it 'sorts by item when both are qualified in the same mod', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'charlie', 'bravo'
|
||||
|
||||
ItemSlug.compare(a, b).should.equal -1
|
||||
ItemSlug.compare(b, a).should.equal +1
|
||||
|
||||
it 'sorts by item when not qualified', ->
|
||||
a = new ItemSlug 'alpha'
|
||||
b = new ItemSlug 'bravo'
|
||||
|
||||
ItemSlug.compare(a, b).should.equal -1
|
||||
ItemSlug.compare(b, a).should.equal +1
|
||||
|
||||
describe 'ItemSlug.equal', ->
|
||||
|
||||
it 'requires both to have the same mod', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'alpha', 'charlie'
|
||||
c = new ItemSlug 'alpha', 'bravo'
|
||||
|
||||
ItemSlug.equal(a, b).should.be.false
|
||||
ItemSlug.equal(a, c).should.be.true
|
||||
|
||||
it 'requires both to have the same item', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'alpha', 'charlie'
|
||||
c = new ItemSlug 'alpha', 'bravo'
|
||||
|
||||
ItemSlug.equal(a, b).should.be.false
|
||||
ItemSlug.equal(a, c).should.be.true
|
||||
|
||||
describe 'ItemSlug.slugify', ->
|
||||
|
||||
it 'can slugify a pure name', ->
|
||||
slug = ItemSlug.slugify 'Alpha Bravo (Charlie)'
|
||||
slug.item.should.equal 'alpha_bravo_charlie'
|
||||
expect(slug.mod).to.be.null
|
||||
|
||||
it 'can slugify a simple item slug', ->
|
||||
slug = ItemSlug.slugify 'alpha_bravo_charlie'
|
||||
slug.item.should.equal 'alpha_bravo_charlie'
|
||||
expect(slug.mod).to.be.null
|
||||
|
||||
it 'can slugify a fully-qualified slug', ->
|
||||
slug = ItemSlug.slugify 'alpha_bravo__charlie_delta'
|
||||
slug.mod.should.equal 'alpha_bravo'
|
||||
slug.item.should.equal 'charlie_delta'
|
||||
|
||||
describe 'matches', ->
|
||||
|
||||
it 'ignores mod when either is unqualified', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'bravo'
|
||||
c = new ItemSlug 'charlie'
|
||||
|
||||
a.matches(b).should.be.true
|
||||
a.matches(c).should.be.false
|
||||
|
||||
it 'observes differences in mod when all are qualified', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'charlie', 'bravo'
|
||||
c = new ItemSlug 'delta', 'echo'
|
||||
d = new ItemSlug 'alpha', 'bravo'
|
||||
|
||||
a.matches(b).should.be.false
|
||||
a.matches(c).should.be.false
|
||||
a.matches(d).should.be.true
|
||||
|
||||
describe 'isQualified', ->
|
||||
|
||||
it 'returns true only when the mod slug is set', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'charlie'
|
||||
a.isQualified.should.be.true
|
||||
b.isQualified.should.be.false
|
||||
|
||||
describe '[]', ->
|
||||
|
||||
it 'allows slugs as a key', ->
|
||||
slug = new ItemSlug 'alpha', 'bravo'
|
||||
data = {}
|
||||
data[slug] = 'foo'
|
||||
data['alpha__bravo'].should.equal 'foo'
|
||||
data[slug].should.equal 'foo'
|
||||
@@ -1,224 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Mod extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.slug? then throw new Error 'attributes.slug is required'
|
||||
|
||||
attributes.author ?= ''
|
||||
attributes.description ?= ''
|
||||
attributes.documentationUrl ?= null
|
||||
attributes.downloadUrl ?= null
|
||||
attributes.homePageUrl ?= null
|
||||
attributes.modPack ?= null
|
||||
attributes.name ?= ''
|
||||
|
||||
super attributes, options
|
||||
|
||||
@_activeModVersion = null
|
||||
@_activeVersion = null
|
||||
@_modVersions = []
|
||||
@_tutorials = []
|
||||
|
||||
@once c.event.sync, => @_verifyActiveModVersion()
|
||||
|
||||
# Class Methods ##################################################################################
|
||||
|
||||
@Version: Version =
|
||||
None: 'none'
|
||||
Latest: 'latest'
|
||||
|
||||
# Public Methods #################################################################################
|
||||
|
||||
compareTo: (that)->
|
||||
thisRequired = this.slug in c.requiredMods
|
||||
thatRequired = that.slug in c.requiredMods
|
||||
|
||||
if thisRequired isnt thatRequired
|
||||
return -1 if thisRequired
|
||||
return +1 if thatRequired
|
||||
else if this.slug isnt that.slug
|
||||
return if this.slug < that.slug then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
activeModVersion:
|
||||
get: -> @_activeModVersion
|
||||
|
||||
activeVersion:
|
||||
get: ->
|
||||
return @_activeVersion
|
||||
|
||||
set: (version)->
|
||||
return if version is @_activeVersion
|
||||
|
||||
version ?= Mod.Version.None
|
||||
if version is Mod.Version.Latest then version = _.last(@_modVersions).version
|
||||
|
||||
if version is Mod.Version.None
|
||||
@_activeVersion = version
|
||||
@_activateModVersion null
|
||||
|
||||
@trigger c.event.change + ':activeVersion', this, @_activeVersion
|
||||
@trigger c.event.change, this
|
||||
else
|
||||
for modVersion in @_modVersions
|
||||
if version is modVersion.version
|
||||
@_activateModVersion modVersion
|
||||
break
|
||||
|
||||
@_activeVersion = version
|
||||
@trigger c.event.change + ':activeVersion', this, @_activeVersion
|
||||
@trigger c.event.change, this
|
||||
|
||||
enabled:
|
||||
get: -> @_activeModVersion?
|
||||
|
||||
modVersions:
|
||||
get: -> @_modVersions[..]
|
||||
|
||||
tutorials:
|
||||
get: -> @getAllTutorials()
|
||||
|
||||
# Item Methods #################################################################################
|
||||
|
||||
eachItem: (callback)->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
effectiveModVersion.eachItem callback
|
||||
|
||||
findItem: (slug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
options.enableAsNeeded ?= false
|
||||
|
||||
if not options.includeDisabled
|
||||
return unless @_activeModVersion?
|
||||
return @_activeModVersion.findItem slug
|
||||
else
|
||||
for modVersion in @_modVersions
|
||||
modVersion.fetch()
|
||||
|
||||
item = modVersion.findItem slug
|
||||
if item?
|
||||
if options.enableAsNeeded then @setActiveVersion modVersion.version
|
||||
return item
|
||||
|
||||
return null
|
||||
|
||||
findItemByName: (name)->
|
||||
return unless @_activeModVersion?
|
||||
@_activeModVersion.findItemByName name
|
||||
|
||||
# ModVersion Methods ###########################################################################
|
||||
|
||||
addModVersion: (modVersion)->
|
||||
return unless modVersion?
|
||||
return if @_modVersions.indexOf(modVersion) isnt -1
|
||||
|
||||
@_modVersions.push modVersion
|
||||
@listenTo modVersion, c.event.change, => @trigger c.event.change, this
|
||||
modVersion.mod = this
|
||||
|
||||
@trigger c.event.add + ':modVersion', modVersion, this
|
||||
@trigger c.event.change + ':version', modVersion, this
|
||||
@trigger c.event.change, this
|
||||
|
||||
if not @activeVersion? then @activeVersion = modVersion.version
|
||||
if modVersion.version is @_activeVersion then @_activateModVersion modVersion
|
||||
return this
|
||||
|
||||
eachModVersion: (callback)->
|
||||
for modVersion in @_modVersions
|
||||
callback modVersion
|
||||
|
||||
getModVersion: (version)->
|
||||
return null if version is Mod.Version.None
|
||||
return @_modVersions[0] if version is Mod.Version.Latest
|
||||
|
||||
for modVersion in @_modVersions
|
||||
return modVersion if modVersion.version is version
|
||||
|
||||
return null
|
||||
|
||||
# Name Methods #################################################################################
|
||||
|
||||
eachName: (callback)->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
effectiveModVersion.eachName callback
|
||||
|
||||
findName: (itemSlug)->
|
||||
return unless @_activeModVersion?
|
||||
@_activeModVersion.findName itemSlug
|
||||
|
||||
# Recipe Methods ###############################################################################
|
||||
|
||||
eachRecipe: (callback)->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
effectiveModVersion.eachRecipe callback
|
||||
|
||||
findRecipes: (itemSlug, result=[], options={})->
|
||||
options.alwaysFromOwningMod ?= false
|
||||
|
||||
if @_activeModVersion?
|
||||
return @_activeModVersion.findRecipes itemSlug, result, options
|
||||
else if options.alwaysFromOwningMod and itemSlug.mod is @slug
|
||||
return @getModVersion(Mod.Version.Latest).findRecipes itemSlug, result, options
|
||||
|
||||
return null
|
||||
|
||||
# Tutorial Methods #############################################################################
|
||||
|
||||
addTutorial: (tutorial)->
|
||||
return unless tutorial?
|
||||
if @getTutorial(tutorial.slug)? then throw new Error "duplicate tutorial: #{tutorial.name}"
|
||||
@_tutorials.push tutorial
|
||||
tutorial.modSlug = @slug
|
||||
|
||||
getAllTutorials: ->
|
||||
return @_tutorials[..]
|
||||
|
||||
getTutorial: (tutorialSlug)->
|
||||
for tutorial in @_tutorials
|
||||
return tutorial if tutorial.slug is tutorialSlug
|
||||
return null
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
ModParser = require '../parsing/mod_parser' # to avoid require cycles
|
||||
@_parser ?= new ModParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.modData modSlug:@slug
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_activateModVersion: (modVersion)->
|
||||
if @_activeModVersion? then @stopListening @_activeModVersion
|
||||
@_activeModVersion = modVersion
|
||||
@trigger c.event.change + ':activeModVersion', this, @_activeModVersion
|
||||
|
||||
logger.verbose => "#{@slug} switched to version #{@_activeVersion}"
|
||||
|
||||
if @_activeModVersion?
|
||||
@listenTo @_activeModVersion, 'all', -> @trigger.apply this, arguments
|
||||
|
||||
_verifyActiveModVersion: ->
|
||||
if (@_activeVersion isnt Version.None) and (not @_activeModVersion?)
|
||||
logger.warning => "#{@slug} no longer has a version #{@_activeVersion}, using latest instead"
|
||||
@activeVersion = Version.Latest
|
||||
@@ -1,30 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Mod = require './mod'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
mod = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'mod.coffee', ->
|
||||
|
||||
beforeEach -> mod = new Mod name:'Test', slug:'test'
|
||||
|
||||
describe 'compareTo', ->
|
||||
|
||||
it 'lists required mods first', ->
|
||||
minecraft = new Mod name:'Minecraft', slug:'minecraft'
|
||||
mod.compareTo(minecraft).should.equal +1
|
||||
minecraft.compareTo(mod).should.equal -1
|
||||
|
||||
it 'sorts by name second', ->
|
||||
buildcraft = new Mod name:'Buildcraft', slug:'buildcraft'
|
||||
mod.compareTo(buildcraft).should.equal +1
|
||||
buildcraft.compareTo(mod).should.equal -1
|
||||
@@ -1,176 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Inventory = require './inventory'
|
||||
ModVersionParser = require '../parsing/mod_version_parser'
|
||||
Recipe = require './recipe'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPack extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
|
||||
@_mods = []
|
||||
@_cache = {}
|
||||
|
||||
@on c.event.change, => @_cache = {}
|
||||
|
||||
# Item Methods #################################################################################
|
||||
|
||||
findItem: (itemSlug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
key = "#{itemSlug}-#{options.includeDisabled}"
|
||||
@_cache.itemBySlug ?= {}
|
||||
item = @_cache.itemBySlug[key]
|
||||
return item if item?
|
||||
|
||||
if itemSlug.isQualified
|
||||
mod = @getMod itemSlug.mod
|
||||
if mod?
|
||||
item = mod.findItem itemSlug, options
|
||||
|
||||
if not item?
|
||||
for mod in @_mods
|
||||
continue unless mod.enabled or options.includeDisabled
|
||||
item = mod.findItem itemSlug, options
|
||||
break if item?
|
||||
|
||||
if item?
|
||||
@_cache.itemBySlug[key] = item
|
||||
|
||||
return item
|
||||
|
||||
findItemByName: (name, options={})->
|
||||
options.enableAsNeeded ?= false
|
||||
options.includeDisabled = true if options.enableAsNeeded
|
||||
|
||||
for mod in @_mods
|
||||
continue unless mod.enabled or options.includeDisabled
|
||||
item = mod.findItemByName name, options
|
||||
return item if item?
|
||||
|
||||
return null
|
||||
|
||||
findItemDisplay: (itemSlug)->
|
||||
if not itemSlug? then throw new Error 'itemSlug is required'
|
||||
|
||||
result = {slug:itemSlug}
|
||||
item = @findItem itemSlug, includeDisabled:true
|
||||
if item?
|
||||
result.itemName = item.name
|
||||
result.itemSlug = item.slug.item
|
||||
result.modSlug = item.slug.mod
|
||||
result.modVersion = item.modVersion.version
|
||||
else
|
||||
result.itemName = @findName itemSlug, includeDisabled:true
|
||||
result.itemSlug = itemSlug.item
|
||||
result.modSlug = @_mods[0].slug
|
||||
result.modVersion = @_mods[0].activeVersion
|
||||
|
||||
craftingUrlInventory = new Inventory modPack:this
|
||||
if item?.multiblock?
|
||||
craftingUrlInventory.addInventory item.multiblock.inventory
|
||||
else
|
||||
craftingUrlInventory.add itemSlug
|
||||
|
||||
result.craftingUrl = c.url.crafting inventoryText:craftingUrlInventory.unparse()
|
||||
result.iconUrl = c.url.itemIcon result
|
||||
result.itemUrl = c.url.item result
|
||||
result.modName = @getMod(result.modSlug).name
|
||||
return result
|
||||
|
||||
qualifySlug: (itemSlug)->
|
||||
return itemSlug if itemSlug.isQualified
|
||||
|
||||
item = @findItem itemSlug
|
||||
return item.slug if item?
|
||||
return itemSlug
|
||||
|
||||
# Mod Methods ##################################################################################
|
||||
|
||||
addMod: (mod)->
|
||||
if not mod? then throw new Error 'mod is required'
|
||||
return if @_mods.indexOf(mod) isnt -1
|
||||
|
||||
mod.modPack = this
|
||||
@_mods.push mod
|
||||
@listenTo mod, c.event.change, (modVersion)=> @_onModVersionLoaded modVersion
|
||||
@trigger c.event.add + ':mod', mod, this
|
||||
|
||||
@_mods.sort (a, b)-> a.compareTo b
|
||||
@trigger c.event.sort + ':mod', this
|
||||
@trigger c.event.change, this
|
||||
|
||||
return this
|
||||
|
||||
eachMod: (callback)->
|
||||
for mod in @_mods
|
||||
callback mod
|
||||
|
||||
getMod: (slug)->
|
||||
for mod in @_mods
|
||||
return mod if mod.slug is slug
|
||||
return null
|
||||
|
||||
getAllMods: ->
|
||||
return @_mods[..]
|
||||
|
||||
# Name Methods #################################################################################
|
||||
|
||||
findName: (slug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
for mod in @_mods
|
||||
continue unless mod.enabled or options.includeDisabled
|
||||
name = mod.findName slug
|
||||
return name if name
|
||||
|
||||
return null
|
||||
|
||||
# Recipe Methods ###############################################################################
|
||||
|
||||
findRecipes: (itemSlug, options={})->
|
||||
options.alwaysFromOwningMod ?= false
|
||||
return null unless itemSlug?
|
||||
|
||||
key = "#{itemSlug}-#{options.alwaysFromOwningMod}"
|
||||
@_cache.recipesBySlug ?= {}
|
||||
result = @_cache.recipesBySlug[key]
|
||||
return result if result?
|
||||
|
||||
result = []
|
||||
for mod in @_mods
|
||||
if not mod.enabled
|
||||
owningMod = itemSlug.isQualified and (itemSlug.mod is mod.slug)
|
||||
continue unless owningMod and options.alwaysFromOwningMod
|
||||
|
||||
mod.findRecipes itemSlug, result, options
|
||||
|
||||
@_cache.recipesBySlug[key] = result
|
||||
return if result.length > 0 then result else null
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "ModPack (#{@cid}) {modVersions:«#{@_mods.length} items»}"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_onModVersionLoaded: (modVersion)->
|
||||
working = true
|
||||
@eachMod (mod)->
|
||||
mod.eachModVersion (modVersion)->
|
||||
working = working and (modVersion.isUnloaded or modVersion.isLoading)
|
||||
|
||||
return if working
|
||||
|
||||
@trigger c.event.change, this
|
||||
@trigger c.event.sync, this
|
||||
@@ -1,103 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require './item'
|
||||
ItemSlug = require './item_slug'
|
||||
Mod = require './mod'
|
||||
ModPack = require './mod_pack'
|
||||
ModVersion = require './mod_version'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
buildcraft = industrialCraft = minecraft = modPack = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'mod_pack.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
minecraft = new Mod slug:'minecraft', name:'Minecraft'
|
||||
minecraft.addModVersion new ModVersion modSlug:minecraft.slug, version:'1.7.10'
|
||||
minecraft.activeModVersion.addItem new Item name:'Wool'
|
||||
minecraft.activeModVersion.addItem new Item name:'Bed', recipes:['']
|
||||
minecraft.activeModVersion.registerName ItemSlug.slugify('iron_chestplate'), 'Iron Chestplate'
|
||||
|
||||
buildcraft = new Mod slug:'buildcraft', name:'Buildcraft'
|
||||
buildcraft.addModVersion new ModVersion modSlug:buildcraft.slug, version:'6.2.6'
|
||||
buildcraft.activeModVersion.addItem new Item name:'Stone Gear', recipes:['']
|
||||
buildcraft.activeModVersion.addItem new Item name:'Wrench', recipes:['']
|
||||
buildcraft.activeVersion = Mod.Version.None
|
||||
|
||||
industrialCraft = new Mod slug:'industrial_craft', name:'Industrial Craft'
|
||||
industrialCraft.addModVersion new ModVersion modSlug:industrialCraft.slug, version:'2.0'
|
||||
industrialCraft.activeModVersion.addItem new Item name:'Resin'
|
||||
industrialCraft.activeModVersion.addItem new Item name:'Rubber'
|
||||
industrialCraft.activeModVersion.addItem new Item name:'Wrench', recipes:['']
|
||||
industrialCraft.activeVersion = Mod.Version.None
|
||||
|
||||
modPack = new ModPack
|
||||
modPack.addMod minecraft
|
||||
modPack.addMod buildcraft
|
||||
modPack.addMod industrialCraft
|
||||
|
||||
describe 'findItem', ->
|
||||
|
||||
it 'can find an item by partial slug', ->
|
||||
item = modPack.findItem ItemSlug.slugify 'wool'
|
||||
item.slug.qualified.should.equal 'minecraft__wool'
|
||||
|
||||
it 'can find an item by full slug', ->
|
||||
item = modPack.findItem ItemSlug.slugify 'minecraft__wool'
|
||||
item.name.should.equal 'Wool'
|
||||
|
||||
it 'can find an ambiguous item by full slug', ->
|
||||
buildcraft.activeVersion = Mod.Version.Latest
|
||||
industrialCraft.activeVersion = Mod.Version.Latest
|
||||
item = modPack.findItem ItemSlug.slugify 'industrial_craft__wrench'
|
||||
item.name.should.equal 'Wrench'
|
||||
item.modVersion.mod.name.should.equal 'Industrial Craft'
|
||||
|
||||
it 'can find an ambiguous item by partial slug', ->
|
||||
buildcraft.activeVersion = Mod.Version.Latest
|
||||
industrialCraft.activeVersion = Mod.Version.Latest
|
||||
item = modPack.findItem ItemSlug.slugify 'wrench'
|
||||
item.name.should.equal 'Wrench'
|
||||
item.modVersion.mod.name.should.equal 'Buildcraft'
|
||||
|
||||
describe 'findItemByName', ->
|
||||
|
||||
it 'finds the requested item', ->
|
||||
item = modPack.findItemByName 'Bed'
|
||||
item.name.should.equal 'Bed'
|
||||
|
||||
it 'ignores disabled mod versions', ->
|
||||
item = modPack.findItemByName 'Stone Gear'
|
||||
expect(item).to.be.null
|
||||
|
||||
describe 'findItemDisplay', ->
|
||||
|
||||
it 'returns all data for a regular Minecraft item', ->
|
||||
display = modPack.findItemDisplay ItemSlug.slugify 'bed'
|
||||
display.iconUrl.should.equal '/data/minecraft/items/bed/icon.png'
|
||||
display.itemUrl.should.equal '/browse/minecraft/bed/'
|
||||
display.itemName.should.equal 'Bed'
|
||||
display.modSlug.should.equal 'minecraft'
|
||||
|
||||
it 'returns all data for an item in an enabled mod', ->
|
||||
buildcraft.activeVersion = '6.2.6'
|
||||
display = modPack.findItemDisplay ItemSlug.slugify 'stone_gear'
|
||||
display.iconUrl.should.equal '/data/buildcraft/items/stone_gear/icon.png'
|
||||
display.itemUrl.should.equal '/browse/buildcraft/stone_gear/'
|
||||
display.itemName.should.equal 'Stone Gear'
|
||||
display.modSlug.should.equal 'buildcraft'
|
||||
|
||||
it 'assumes an unfound item is from Minecraft', ->
|
||||
display = modPack.findItemDisplay ItemSlug.slugify 'iron_chestplate'
|
||||
display.iconUrl.should.equal '/data/minecraft/items/iron_chestplate/icon.png'
|
||||
display.itemUrl.should.equal '/browse/minecraft/iron_chestplate/'
|
||||
display.itemName.should.equal 'Iron Chestplate'
|
||||
display.modSlug.should.equal 'minecraft'
|
||||
@@ -1,196 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_version.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Item = require './item'
|
||||
ItemSlug = require './item_slug'
|
||||
Recipe = require './recipe'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModVersion extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.modSlug? then throw new Error 'attributes.modSlug is required'
|
||||
if not attributes.version? then throw new Error 'attributes.version is required'
|
||||
attributes.mod ?= null
|
||||
super attributes, options
|
||||
|
||||
@_groups = {}
|
||||
@_items = {}
|
||||
@_names = {}
|
||||
@_recipes = {}
|
||||
@_slugs = []
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
compareTo: (that)->
|
||||
if this.mod? and that.mod?
|
||||
return this.mod.compareTo that.mod
|
||||
|
||||
if this.modSlug isnt that.modSlug
|
||||
return if this.modSlug < that.modSlug then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
sort: ->
|
||||
@_slugs.sort (a, b)-> ItemSlug.compare a, b
|
||||
|
||||
# Item Methods #################################################################################
|
||||
|
||||
addItem: (item)->
|
||||
if @findItem(item.slug)? then throw new Error "duplicate item for #{item.name}"
|
||||
|
||||
item.modVersion = this
|
||||
@_items[item.slug.item] = item
|
||||
@_groups[item.group] ?= {}
|
||||
@_groups[item.group][item.slug.item] = item
|
||||
|
||||
@registerName item.slug, item.name
|
||||
return this
|
||||
|
||||
allItemsInGroup: (group)->
|
||||
result = []
|
||||
@eachItemInGroup group, (item)-> result.push item
|
||||
return null if result.length is 0
|
||||
return result
|
||||
|
||||
eachItem: (callback)->
|
||||
for slug in @_slugs
|
||||
item = @findItem slug
|
||||
continue unless item?
|
||||
callback item
|
||||
return this
|
||||
|
||||
eachItemInGroup: (group, callback)->
|
||||
itemMap = @_groups[group]
|
||||
return unless itemMap?
|
||||
|
||||
items = _.values(itemMap).sort (a, b)-> a.compareTo b
|
||||
for item in items
|
||||
callback item
|
||||
|
||||
findItem: (itemSlug)->
|
||||
return @_items[itemSlug.item]
|
||||
|
||||
findItemByName: (name)->
|
||||
for itemSlug, item of @_items
|
||||
return item if item.name is name
|
||||
return null
|
||||
|
||||
# Group Methods ################################################################################
|
||||
|
||||
getAllGroups: ->
|
||||
result = []
|
||||
@eachGroup (group)-> result.push group
|
||||
return result
|
||||
|
||||
eachGroup: (callback)->
|
||||
groupNames = _.keys @_groups
|
||||
groupNames.sort (a, b)->
|
||||
if a is b then return 0
|
||||
if a is Item.Group.Other then return -1
|
||||
if b is Item.Group.Other then return +1
|
||||
return if a < b then -1 else +1
|
||||
|
||||
for groupName in groupNames
|
||||
callback groupName
|
||||
|
||||
# Name Methods #################################################################################
|
||||
|
||||
eachName: (callback)->
|
||||
for slug in @_slugs
|
||||
callback @_names[slug.item], slug
|
||||
return this
|
||||
|
||||
findName: (itemSlug)->
|
||||
return @_names[itemSlug.item]
|
||||
|
||||
registerName: (itemSlug, name)->
|
||||
return if @_names[itemSlug.item]
|
||||
@_names[itemSlug.item] = name
|
||||
@_slugs.push itemSlug
|
||||
return this
|
||||
|
||||
# Recipe Methods ###############################################################################
|
||||
|
||||
addRecipe: (recipe)->
|
||||
return unless recipe?
|
||||
|
||||
recipe.modVersion = this
|
||||
if @_recipes[recipe.slug]? then throw new Error "duplicate recipe: #{recipe.slug}"
|
||||
|
||||
@_recipes[recipe.slug] = recipe
|
||||
return this
|
||||
|
||||
eachRecipe: (callback)->
|
||||
recipes = _.values(@_recipes).sort (a, b)-> Recipe.compareFor a, b
|
||||
for recipe in recipes
|
||||
callback recipe
|
||||
return this
|
||||
|
||||
findRecipes: (itemSlug, result=[], options={})->
|
||||
options.onlyPrimary ?= false
|
||||
options.forCrafting ?= false
|
||||
|
||||
primaryRecipes = []
|
||||
otherRecipes = []
|
||||
|
||||
for recipe in _.values @_recipes
|
||||
continue unless recipe.isConditionSatisfied()
|
||||
continue unless recipe.hasAllTools()
|
||||
continue if options.forCrafting and recipe.ignoreDuringCrafting
|
||||
|
||||
if recipe.itemSlug.matches itemSlug
|
||||
primaryRecipes.push recipe
|
||||
else if recipe.produces itemSlug
|
||||
otherRecipes.push recipe
|
||||
|
||||
for recipe in primaryRecipes
|
||||
result.push recipe
|
||||
|
||||
if not options.onlyPrimary
|
||||
for recipe in otherRecipes
|
||||
result.push recipe
|
||||
|
||||
return result
|
||||
|
||||
findExternalRecipes: ->
|
||||
result = {}
|
||||
for k, recipe of @_recipes
|
||||
continue if recipe.itemSlug.isQualified
|
||||
|
||||
recipeList = result[recipe.itemSlug]
|
||||
if not recipeList then recipeList = result[recipe.itemSlug] = []
|
||||
|
||||
recipeList.push recipe
|
||||
|
||||
return result
|
||||
|
||||
hasRecipes: (itemSlug)->
|
||||
for k, recipe of @_recipes
|
||||
return true if recipe.produces itemSlug
|
||||
return false
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
ModVersionParser = require '../parsing/mod_version_parser' # to avoid require cycles
|
||||
@_parser ?= new ModVersionParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.modVersionData modSlug:@modSlug, modVersion:@version
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "ModVersion (#{@cid}) {
|
||||
modSlug:#{@modSlug}, version:#{@version}, items:«#{@_slugs.length} items»
|
||||
}"
|
||||
@@ -1,101 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_version.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require './item'
|
||||
ItemSlug = require './item_slug'
|
||||
ModVersion = require './mod_version'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
modVersion = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'mod_version.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
modVersion = new ModVersion modSlug:'test', version:'0.0'
|
||||
modVersion.addItem new Item name:'underscore', group:'punctuation'
|
||||
modVersion.addItem new Item name:'bravo', group:'letter'
|
||||
modVersion.addItem new Item name:'alpha', group:'letter'
|
||||
modVersion.addItem new Item name:'one', group:'number'
|
||||
modVersion.addItem new Item name:'two', group:'number'
|
||||
|
||||
describe 'constructor', ->
|
||||
|
||||
it 'requires a mod slug', ->
|
||||
expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.modSlug is required'
|
||||
|
||||
it 'requires a mod version', ->
|
||||
expect(-> new ModVersion modSlug:'test').to.throw Error, 'attributes.version is required'
|
||||
|
||||
describe 'addItem', ->
|
||||
|
||||
it 'refuses to add duplicates', ->
|
||||
modVersion.addItem new Item name:'Wool'
|
||||
expect(-> modVersion.addItem new Item name:'Wool').to.throw Error, 'duplicate item for Wool'
|
||||
|
||||
it 'adds an item indexed by its slug', ->
|
||||
modVersion.addItem new Item name:'Wool'
|
||||
modVersion._items.wool.name.should.equal 'Wool'
|
||||
|
||||
it 'sets the modVersion', ->
|
||||
modVersion.addItem new Item name:'Wool'
|
||||
modVersion._items.wool.modVersion.should.equal modVersion
|
||||
|
||||
describe 'eachGroup', ->
|
||||
|
||||
it 'returns all the groups in order', ->
|
||||
groupNames = []
|
||||
modVersion.eachGroup (groupName)-> groupNames.push groupName
|
||||
groupNames.should.eql ['letter', 'number', 'punctuation']
|
||||
|
||||
describe 'eachItemInGroup', ->
|
||||
|
||||
it 'returns immediately for unknown group', ->
|
||||
slugs = []
|
||||
modVersion.eachItemInGroup 'foobar', (item)-> slugs.push item.slug.qualified
|
||||
slugs.should.eql []
|
||||
|
||||
it 'calls callback for exactly the items in a group in order', ->
|
||||
slugs = []
|
||||
modVersion.eachItemInGroup 'letter', (item)-> slugs.push item.slug.qualified
|
||||
slugs.should.eql ['test__alpha', 'test__bravo']
|
||||
|
||||
slugs = []
|
||||
modVersion.eachItemInGroup 'number', (item)-> slugs.push item.slug.qualified
|
||||
slugs.should.eql ['test__one', 'test__two']
|
||||
|
||||
|
||||
describe 'findItemByName', ->
|
||||
|
||||
it 'locates items by slugified name', ->
|
||||
modVersion.addItem new Item name:'Crafting Table'
|
||||
modVersion.findItemByName('Crafting Table').slug.qualified.should.equal 'test__crafting_table'
|
||||
|
||||
describe 'findRecipes', ->
|
||||
|
||||
beforeEach ->
|
||||
modVersion = new ModVersion modSlug:'test', version:'1.0'
|
||||
modVersion.parse """
|
||||
schema:1
|
||||
|
||||
item: Cake
|
||||
recipe:; input: Milk, Sugar, Egg, Wheat; pattern: 000 121 333; extras: 3 Bucket
|
||||
recipe:; input: Milk, Cocoa Beans, Egg, Wheat; pattern: 000 121 333; extras: 3 Bucket
|
||||
recipe:; input: Cake Slice; pattern: 000 000 000; onlyIf: item Cake Slice
|
||||
item: Bucket
|
||||
recipe:; input: Iron Ingot; pattern: ... 0.0 .0.
|
||||
"""
|
||||
|
||||
it 'finds all recipes which list item as output', ->
|
||||
recipes = modVersion.findRecipes ItemSlug.slugify('test__bucket')
|
||||
(r.output[0].itemSlug.item for r in recipes).sort().should.eql ['bucket', 'cake', 'cake']
|
||||
|
||||
it 'skip recipes whose conditions are not met', ->
|
||||
recipes = modVersion.findRecipes ItemSlug.slugify('test__cake')
|
||||
recipes.length.should.equal 2
|
||||
@@ -1,85 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - multiblock.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Inventory = require './inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Multiblock extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.input? then throw new Error 'attributes.input is required'
|
||||
if not attributes.layers? then throw new Error 'attributes.layers is required'
|
||||
if attributes.layers.length < 1 then throw new Error 'attributes.layers.length must be >= 1'
|
||||
super attributes, options
|
||||
|
||||
@_analyzePattern()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getStackAt: (x, y, z)->
|
||||
value = @_stackCache[y]?[z]?[x]
|
||||
return null if value is undefined
|
||||
return value
|
||||
|
||||
getLayerInventory: (y)->
|
||||
@_layerInventories ?= []
|
||||
result = @_layerInventories[y]
|
||||
if not result? then result = @_layerInventories[y] = new Inventory
|
||||
return result
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
depth:
|
||||
get: -> @_depth
|
||||
|
||||
height:
|
||||
get: -> @_height
|
||||
|
||||
inventory:
|
||||
get: -> @_inventory
|
||||
|
||||
width:
|
||||
get: -> @_width
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_analyzeLayer: (layer, y, stackCacheLayer)->
|
||||
rows = layer.split ' '
|
||||
@_depth = Math.max @_depth, rows.length
|
||||
|
||||
for row in rows
|
||||
@_width = Math.max @_width, row.length
|
||||
stackCacheRow = []
|
||||
stackCacheLayer.push stackCacheRow
|
||||
@_analyzeRow row, stackCacheRow, @getLayerInventory(y)
|
||||
|
||||
_analyzePattern: ->
|
||||
@_depth = @_height = @_width = 0
|
||||
@_inventory = new Inventory
|
||||
@_stackCache = []
|
||||
|
||||
for layer, y in @layers
|
||||
stackCacheLayer = []
|
||||
@_stackCache.push stackCacheLayer
|
||||
@_analyzeLayer layer, y, stackCacheLayer
|
||||
|
||||
@_height = @layers.length
|
||||
|
||||
_analyzeRow: (row, stackCacheRow, layerInventory)->
|
||||
for cell, x in row.split ''
|
||||
index = parseInt cell
|
||||
stack = null
|
||||
|
||||
if not _.isNaN index
|
||||
stack = @input[index]
|
||||
@_inventory.add stack.itemSlug, stack.quantity
|
||||
layerInventory.add stack.itemSlug, stack.quantity
|
||||
|
||||
stackCacheRow.push stack
|
||||
@@ -1,85 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - multiblock.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
ItemSlug = require './item_slug'
|
||||
Mod = require './mod'
|
||||
Multiblock = require './multiblock'
|
||||
Stack = require './stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'multiblock.coffee', ->
|
||||
|
||||
describe 'with a single block type', ->
|
||||
|
||||
beforeEach ->
|
||||
@input = [ new Stack itemSlug:ItemSlug.slugify('cobblestone'), quantity:1 ]
|
||||
|
||||
it 'correctly parses a 1x1x1 cube', ->
|
||||
model = new Multiblock input:@input, layers:['0']
|
||||
model.depth.should.equal 1
|
||||
model.height.should.equal 1
|
||||
model.width.should.equal 1
|
||||
model.getStackAt(0, 0, 0).toString().should.equal '1 cobblestone'
|
||||
|
||||
it 'correctly parses a solid 3x3x3 cube', ->
|
||||
model = new Multiblock input:@input, layers:['000 000 000', '000 000 000', '000 000 000']
|
||||
model.depth.should.equal 3
|
||||
model.height.should.equal 3
|
||||
model.width.should.equal 3
|
||||
|
||||
for x in [0..2]
|
||||
for y in [0..2]
|
||||
for z in [0..2]
|
||||
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
|
||||
|
||||
it 'correctly parses a hollow 3x3x3 cube', ->
|
||||
model = new Multiblock input:@input, layers:['000 000 000', '000 0.0 000', '000 000 000']
|
||||
model.depth.should.equal 3
|
||||
model.height.should.equal 3
|
||||
model.width.should.equal 3
|
||||
|
||||
for x in [0..2]
|
||||
for y in [0..2]
|
||||
for z in [0..2]
|
||||
if x isnt 1 or y isnt 1 or z isnt 1
|
||||
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
|
||||
else
|
||||
expect(model.getStackAt(x, y, z)).to.be.null
|
||||
|
||||
it 'correctly parses a 3x2x3 pyramid', ->
|
||||
model = new Multiblock input:@input, layers:['000 000 000', '.. .0 ..']
|
||||
model.depth.should.equal 3
|
||||
model.height.should.equal 2
|
||||
model.width.should.equal 3
|
||||
|
||||
for y in [0..1]
|
||||
for z in [0..2]
|
||||
for x in [0..2]
|
||||
if y is 1 and (x isnt 1 or z isnt 1)
|
||||
expect(model.getStackAt(x, y, z)).to.be.null
|
||||
else
|
||||
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
|
||||
|
||||
describe 'with multiple block types', ->
|
||||
|
||||
beforeEach ->
|
||||
@input = [
|
||||
new Stack itemSlug:ItemSlug.slugify('cobblestone'), quantity:1
|
||||
new Stack itemSlug:ItemSlug.slugify('stone'), quantity:1
|
||||
new Stack itemSlug:ItemSlug.slugify('oak wood'), quantity:1
|
||||
]
|
||||
|
||||
it 'correctly parses a 1x3x1 column of different types', ->
|
||||
model = new Multiblock input:@input, layers:['0', '1', '2']
|
||||
model.depth.should.equal 1
|
||||
model.height.should.equal 3
|
||||
model.width.should.equal 1
|
||||
|
||||
model.getStackAt(0, 0, 0).toString().should.equal '1 cobblestone'
|
||||
model.getStackAt(0, 1, 0).toString().should.equal '1 stone'
|
||||
model.getStackAt(0, 2, 0).toString().should.equal '1 oak_wood'
|
||||
@@ -1,252 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - recipe.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
ItemSlug = require './item_slug'
|
||||
Stack = require './stack'
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Recipe extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.input? then throw new Error 'attributes.input is required'
|
||||
if not attributes.pattern? then throw new Error 'attributes.pattern is required'
|
||||
|
||||
if attributes.itemSlug? and not attributes.output?
|
||||
attributes.output = [new Stack itemSlug:attributes.itemSlug, quantity:1]
|
||||
else if attributes.output? and not attributes.itemSlug?
|
||||
if attributes.output.length is 0 then throw new Error 'attributes.output cannot be empty'
|
||||
attributes.itemSlug = attributes.output[0].itemSlug
|
||||
else
|
||||
throw new Error 'attributes.itemSlug or attributes.output is required'
|
||||
|
||||
attributes.pattern = @_parsePattern attributes.pattern
|
||||
|
||||
attributes.condition ?= null
|
||||
attributes.ignoreDuringCrafting ?= false
|
||||
attributes.modVersion ?= null
|
||||
attributes.tools ?= []
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
@_computeQuantities attributes.pattern
|
||||
|
||||
@on c.event.change + ':modVersion', => @_slug = null
|
||||
@on c.event.change + ':pattern', => @_patternCache = null
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@compareFor: (a, b, itemSlug)->
|
||||
if itemSlug?
|
||||
aValue = a.itemSlug.matches itemSlug
|
||||
bValue = b.itemSlug.matches itemSlug
|
||||
if aValue isnt bValue
|
||||
return -1 if aValue
|
||||
return +1 if bValue
|
||||
|
||||
aValue = a.getQuantityProduced itemSlug
|
||||
bValue = b.getQuantityProduced itemSlug
|
||||
if aValue isnt bValue
|
||||
return if aValue > bValue then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getStackAtSlot: (patternSlot)->
|
||||
trueIndex = 0:0, 1:1, 2:2, 3:4, 4:5, 5:6, 6:8, 7:9, 8:10
|
||||
patternDigit = @pattern[trueIndex[patternSlot]]
|
||||
return null unless patternDigit?
|
||||
return null unless patternDigit.match /[0-9]/
|
||||
|
||||
stack = @input[parseInt(patternDigit)]
|
||||
return null unless stack?
|
||||
|
||||
return stack
|
||||
|
||||
getQuantityProduced: (itemSlug)->
|
||||
total = 0
|
||||
for stack in @output
|
||||
if stack.itemSlug.matches itemSlug
|
||||
total += stack.quantity
|
||||
|
||||
return total
|
||||
|
||||
getQuantityRequired: (itemSlug)->
|
||||
total = 0
|
||||
for stack, index in @input
|
||||
if ItemSlug.equal stack.itemSlug, itemSlug
|
||||
total += @_quantities[index] * stack.quantity
|
||||
|
||||
return total
|
||||
|
||||
hasAllTools: (modPack)->
|
||||
modPack ?= @modVersion?.mod?.modPack
|
||||
return true unless modPack?
|
||||
|
||||
for stack in @tools
|
||||
return false unless modPack.findItem stack.itemSlug
|
||||
return true
|
||||
|
||||
isConditionSatisfied: (modPack)->
|
||||
return true unless @condition?
|
||||
modPack ?= @modVersion?.mod?.modPack
|
||||
|
||||
result = false
|
||||
if @condition.verb is 'item'
|
||||
if modPack?.findItemByName(@condition.noun)?
|
||||
result = true
|
||||
else if @condition.verb is 'mod'
|
||||
modPack.eachMod (mod)->
|
||||
if mod.name is @condition.noun
|
||||
result = true
|
||||
|
||||
if @condition.inverted then result = not result
|
||||
return result
|
||||
|
||||
isPassThroughFor: (itemSlug)->
|
||||
amountCreated = 0
|
||||
for stack in @output
|
||||
if stack.itemSlug.matches itemSlug
|
||||
amountCreated += stack.quantity
|
||||
|
||||
for stack in @input
|
||||
if stack.itemSlug.matches itemSlug
|
||||
amountCreated -= stack.quantity
|
||||
|
||||
return amountCreated <= 0
|
||||
|
||||
produces: (itemSlug)->
|
||||
if not @_produces?
|
||||
@_produces = {}
|
||||
|
||||
for stack in @output
|
||||
actuallyProduces = not @isPassThroughFor stack.itemSlug
|
||||
@_produces[stack.itemSlug.qualified] = actuallyProduces
|
||||
|
||||
result = @_produces[itemSlug.qualified] or @_produces[itemSlug.item]
|
||||
return result
|
||||
|
||||
requires: (itemSlug)->
|
||||
for stack in @input
|
||||
if stack.itemSlug.matches itemSlug
|
||||
return true
|
||||
return false
|
||||
|
||||
requiresTool: (itemSlug)->
|
||||
for stack in @tools
|
||||
if stack.itemSlug.matches itemSlug
|
||||
return true
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
slug:
|
||||
get: ->
|
||||
if not @_slug?
|
||||
builder = new StringBuilder
|
||||
delimiterNeeded = false
|
||||
for stack in @input
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
|
||||
builder.push '>'
|
||||
builder.push @pattern
|
||||
builder.push '>'
|
||||
for stack in @tools
|
||||
builder.push stack.itemSlug.qualified
|
||||
builder.push '>'
|
||||
|
||||
delimiterNeeded = false
|
||||
for stack in @output
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
|
||||
@_slug = builder.toString()
|
||||
|
||||
return @_slug
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") { name:", @name]
|
||||
|
||||
result.push ", input:["
|
||||
needsDelimiter = false
|
||||
for stack in @input
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push @getQuantityRequired stack.itemSlug
|
||||
result.push ' '
|
||||
result.push stack.itemSlug
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push ", output:["
|
||||
needsDelimiter = false
|
||||
for stack in @output
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push @getQuantityProduced stack.itemSlug
|
||||
result.push ' '
|
||||
result.push stack.itemSlug
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
if @tools.length > 0
|
||||
result.push ", tools:["
|
||||
needsDelimiter = false
|
||||
for stack in @tools
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push stack.toString()
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push '}'
|
||||
return result.join ''
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeQuantities: (pattern)->
|
||||
quantityMap = {}
|
||||
|
||||
for c in pattern.split ''
|
||||
continue if c is '.'
|
||||
continue if c is ' '
|
||||
|
||||
if quantityMap[c]?
|
||||
quantityMap[c] += 1
|
||||
else
|
||||
quantityMap[c] = 1
|
||||
|
||||
@_quantities = []
|
||||
for i in [0...@input.length]
|
||||
@_quantities.push quantityMap["#{i}"]
|
||||
|
||||
_parsePattern: (pattern)->
|
||||
return unless pattern?
|
||||
|
||||
pattern = pattern.replace /\ /g, ''
|
||||
return if pattern.length is 0
|
||||
|
||||
pattern = pattern.replace /[^0-9]/g, '.'
|
||||
|
||||
array = pattern.split ''
|
||||
array = array[0...9]
|
||||
while array.length isnt 9
|
||||
array.push '.'
|
||||
|
||||
pattern = array.join ''
|
||||
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
|
||||
return pattern
|
||||
@@ -1,90 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - recipe.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require './item'
|
||||
ItemSlug = require './item_slug'
|
||||
Recipe = require './recipe'
|
||||
Stack = require './stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
input = output = pattern = recipe = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'recipe.coffee', ->
|
||||
|
||||
describe 'constructor', ->
|
||||
|
||||
beforeEach ->
|
||||
input = [
|
||||
new Stack(itemSlug:new ItemSlug('iron_gear')),
|
||||
new Stack(itemSlug:new ItemSlug('gold_ingot'), quantity:4)
|
||||
]
|
||||
pattern = '.1. 101 .1.'
|
||||
|
||||
it 'requires input', ->
|
||||
expect(-> new Recipe slug:'gold_gear', pattern:pattern).to.throw Error, 'attributes.input is required'
|
||||
|
||||
it 'requires a pattern', ->
|
||||
expect(-> new Recipe slug:'gold_gear', input:input).to.throw Error, 'attributes.pattern is required'
|
||||
|
||||
it 'requires either outputs or a slug', ->
|
||||
f = -> new Recipe input:input, pattern:pattern
|
||||
expect(f).to.throw 'attributes.itemSlug or attributes.output is required'
|
||||
|
||||
it 'creates default output', ->
|
||||
recipe = new Recipe itemSlug:ItemSlug.slugify('gold_gear'), input:input, pattern:pattern
|
||||
recipe.output.length.should.equal 1
|
||||
recipe.output[0].itemSlug.qualified.should.equal 'gold_gear'
|
||||
recipe.output[0].quantity.should.equal 1
|
||||
|
||||
it 'assigns a default slug', ->
|
||||
recipe = new Recipe input:input, pattern:pattern, output:[new Stack itemSlug:ItemSlug.slugify('gold_gear')]
|
||||
recipe.itemSlug.qualified.should.equal 'gold_gear'
|
||||
|
||||
describe 'getStackAtSlot', ->
|
||||
|
||||
beforeEach ->
|
||||
input = [
|
||||
new Stack itemSlug:ItemSlug.slugify('iron_gear')
|
||||
new Stack itemSlug:ItemSlug.slugify('gold_ingot'), quantity:4
|
||||
]
|
||||
recipe = new Recipe itemSlug:'gold_gear', input:input, pattern:'.1. 101 .1.'
|
||||
|
||||
it 'returns the proper item for an early slot', ->
|
||||
stack = recipe.getStackAtSlot(1)
|
||||
stack.itemSlug.qualified.should.equal 'gold_ingot'
|
||||
stack.quantity.should.equal 4
|
||||
|
||||
it 'returns the proper item for a late slot', ->
|
||||
stack = recipe.getStackAtSlot(4)
|
||||
stack.itemSlug.qualified.should.equal 'iron_gear'
|
||||
stack.quantity.should.equal 1
|
||||
|
||||
it 'returns null for an invalid slot', ->
|
||||
expect(recipe.getStackAtSlot(12)).to.be.null
|
||||
|
||||
describe '_parsePattern', ->
|
||||
|
||||
beforeEach ->
|
||||
recipe = new Recipe
|
||||
itemSlug: 'oak_wood_planks',
|
||||
input: [new Stack itemSlug:new ItemSlug('oak_wood')],
|
||||
pattern:'... .0. ...'
|
||||
|
||||
it 'normalizes invalid characters', ->
|
||||
recipe._parsePattern('$$0 #() 010').should.equal '..0 ... 010'
|
||||
|
||||
it 'removes extra characters', ->
|
||||
recipe._parsePattern('000 000 000 000').should.equal '000 000 000'
|
||||
|
||||
it 'fills in missing characters', ->
|
||||
recipe._parsePattern('000000').should.equal '000 000 ...'
|
||||
|
||||
it 'fills in spaces', ->
|
||||
recipe._parsePattern('000000000').should.equal '000 000 000'
|
||||
@@ -1,33 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - simple_stack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
ItemSlug = require './item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Stack
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
|
||||
attributes.quantity ?= 1
|
||||
|
||||
@itemSlug = attributes.itemSlug
|
||||
@quantity = attributes.quantity
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@compare: (a, b)->
|
||||
if a? and not b? then return -1
|
||||
if not a? and b? then return +1
|
||||
if a.quantity isnt b.quantity
|
||||
return if a.quantity > b.quantity then -1 else +1
|
||||
return ItemSlug.compare a.itemSlug, b.itemSlug
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@quantity} #{@itemSlug}"
|
||||
@@ -1,23 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - stack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Stack extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
|
||||
attributes.quantity ?= 1
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@quantity} #{@itemSlug}"
|
||||
@@ -1,138 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - command_parser_version_base.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CommandParserVersionBase
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
options.showAllErrors ?= false
|
||||
|
||||
@_model = options.model
|
||||
@_showAllErrors = options.showAllErrors
|
||||
@errors = []
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@COMMAND = /\ *([^:]*):?(.*)/
|
||||
|
||||
@COMMENT = /([^\\]?)#.*/
|
||||
|
||||
@simplify: (text)->
|
||||
text = text.trim()
|
||||
text = text.replace /\ */g , ' '
|
||||
text = text.replace /\n/g, ';'
|
||||
text = text.replace /; */g, ';'
|
||||
text = text.replace /;;*/g, ';'
|
||||
text = text.replace /: /g, ':'
|
||||
return text
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
parse: (text)->
|
||||
@_rawData = {}
|
||||
@_lineNumber = 1
|
||||
@errors = []
|
||||
|
||||
@_lines = text.split '\n'
|
||||
@_lineNumber = 0
|
||||
while @_lineNumber < @_lines.length
|
||||
@_lineNumber += 1
|
||||
commands = @_parseLine @_lines[@_lineNumber - 1]
|
||||
for command in commands
|
||||
@_handleErrors @_execute, command
|
||||
|
||||
@_handleErrors @_buildModel, @_rawData, @_model
|
||||
return @_model
|
||||
|
||||
unparse: ->
|
||||
builder = new StringBuilder context:@_model
|
||||
@_unparseModel builder, @_model
|
||||
return builder.toString()
|
||||
|
||||
# Subclass Methods #############################################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
throw new Error 'Subclasses must override this method'
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
throw new Error 'Subclasses must override this method'
|
||||
|
||||
_command_schema: -> # do nothing
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_execute: (command)->
|
||||
method = this["_command_#{command.name}"]
|
||||
if not method? then throw new Error "Unknown command: #{command.name}"
|
||||
|
||||
@_handleErrors method, command.args
|
||||
|
||||
_parseLine: (line)->
|
||||
line = line.replace CommandParserVersionBase.COMMENT, '$1'
|
||||
line = line.trim()
|
||||
return [] if line.length is 0
|
||||
|
||||
[line, hereDoc] = @_parseHereDoc line
|
||||
|
||||
lineParts = (part.trim() for part in line.split(';'))
|
||||
commands = []
|
||||
for linePart in lineParts
|
||||
continue if linePart.length is 0
|
||||
|
||||
match = CommandParserVersionBase.COMMAND.exec linePart
|
||||
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
|
||||
|
||||
args = []
|
||||
args = (s for s in match[2].split(',') when s.length > 0) if match[2]?
|
||||
args = (s.trim() for s in args)
|
||||
args = (s for s in args when s.length > 0)
|
||||
args.push hereDoc if hereDoc?
|
||||
commands.push name:match[1], args:args
|
||||
|
||||
return commands
|
||||
|
||||
_handleErrors: (callback, args...)->
|
||||
if args.length is 1 and _.isArray(args[0]) then args = args[0]
|
||||
|
||||
try
|
||||
callback.apply this, args
|
||||
catch e
|
||||
e.message = "line #{@_lineNumber}: #{e.message}"
|
||||
if not @_showAllErrors then throw e
|
||||
@errors.push e
|
||||
logger.error -> e.message
|
||||
|
||||
_parseHereDoc: (line)->
|
||||
hereDocIndex = line.indexOf '<<-'
|
||||
return [line, null] unless hereDocIndex isnt -1
|
||||
|
||||
hereDocStopText = line[hereDocIndex+3...line.length]
|
||||
line = line[0...hereDocIndex]
|
||||
|
||||
hereDocLines = []
|
||||
while true
|
||||
@_lineNumber += 1
|
||||
break if @_lineNumber >= @_lines.length
|
||||
|
||||
nextLine = @_lines[@_lineNumber-1]
|
||||
break if nextLine.trim() is hereDocStopText
|
||||
hereDocLines.push nextLine
|
||||
|
||||
shortestIndent = Number.MAX_VALUE
|
||||
for hereDocLine in hereDocLines
|
||||
continue if hereDocLine.trim().length is 0
|
||||
shortestIndent = Math.min hereDocLine.match(/( *).*/)[1].length, shortestIndent
|
||||
|
||||
for i in [0...hereDocLines.length]
|
||||
hereDocLines[i] = hereDocLines[i][shortestIndent..]
|
||||
|
||||
return [line, null] unless hereDocLines.length > 0
|
||||
return [line, hereDocLines.join('\n')]
|
||||
@@ -1,48 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - command_parser_version_base.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
parser = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'command_parser_version_base.coffee', ->
|
||||
|
||||
beforeEach -> parser = new CommandParserVersionBase model:{}
|
||||
|
||||
describe '_parseHereDoc', ->
|
||||
|
||||
it 'returns null for non-heredoc lines', ->
|
||||
result = parser._parseHereDoc 'foobar: baz'
|
||||
expect(result[1]).to.be.null
|
||||
|
||||
it 'identifies the right text for a real heredoc', ->
|
||||
parser._lines = ['command: <<-END', 'alpha', 'bravo', 'charlie', 'END', 'command1: arg2']
|
||||
parser._lineNumber = 1
|
||||
|
||||
result = parser._parseHereDoc parser._lines[0]
|
||||
result[0].should.equal 'command: '
|
||||
result[1].should.equal 'alpha\nbravo\ncharlie'
|
||||
|
||||
it 'identifies an empty heredoc', ->
|
||||
parser._lines = ['command: <<-END', 'END']
|
||||
parser._lineNumber = 1
|
||||
|
||||
result = parser._parseHereDoc parser._lines[0]
|
||||
result[0].should.equal 'command: '
|
||||
expect(result[1]).to.be.null
|
||||
|
||||
it 'trims smallest leading whitespace', ->
|
||||
parser._lines = ['command: <<-END', ' alpha', ' bravo', '', ' charlie', 'END', 'command1: arg2']
|
||||
parser._lineNumber = 1
|
||||
|
||||
result = parser._parseHereDoc parser._lines[0]
|
||||
result[0].should.equal 'command: '
|
||||
result[1].should.equal 'alpha\n bravo\n\ncharlie'
|
||||
@@ -1,19 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
ItemParserV1 = require './item_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new ItemParserV1 options
|
||||
@@ -1,72 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_parser_v1.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemParserV1 extends CommandParserVersionBase
|
||||
|
||||
# CommandParserVersionBase Overrides ###########################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
@_buildItem rawData, model
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
builder.line 'schema: ', 1
|
||||
builder.line()
|
||||
|
||||
@_unparseItem builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_description: (textParts...)->
|
||||
if not @_rawData.description?
|
||||
@_rawData.description = ''
|
||||
else
|
||||
@_rawData.description += '\n'
|
||||
@_rawData.description += textParts.join ', '
|
||||
|
||||
_command_officialUrl: (officialUrl)->
|
||||
if @_rawData.officialUrl? then throw new Error 'duplicate declaration of "officialUrl"'
|
||||
if not officialUrl? or (officialUrl.length is 0) then throw new Error 'officialUrl cannot be empty'
|
||||
@_rawData.officialUrl = officialUrl
|
||||
|
||||
_command_video: (youTubeId, nameParts...)->
|
||||
if not youTubeId?.length then throw new Error 'video declaration requires a YouTubeID'
|
||||
name = nameParts.join ', '
|
||||
if not name?.length then throw new Error 'video declaration requires a name'
|
||||
|
||||
@_rawData.videos ?= []
|
||||
@_rawData.videos.push youTubeId:youTubeId, name:name
|
||||
|
||||
# Object Building Methods ######################################################################
|
||||
|
||||
_buildItem: (rawData, model)->
|
||||
model.description = rawData.description if rawData.description?
|
||||
model.officialUrl = rawData.officialUrl if rawData.officialUrl?
|
||||
model.videos = rawData.videos if rawData.videos?
|
||||
|
||||
# Un-parsing Methods ###########################################################################
|
||||
|
||||
_unparseItem: (builder, model)->
|
||||
if model.officialUrl?
|
||||
builder.line 'officialUrl: ', model.officialUrl
|
||||
builder.line()
|
||||
|
||||
if model.description?
|
||||
if model.description.indexOf('\n') isnt -1
|
||||
builder.line 'description: <<-END'
|
||||
builder.line model.description
|
||||
builder.line 'END'
|
||||
else
|
||||
builder.line 'description: ', model.description
|
||||
builder.line()
|
||||
|
||||
for video in model.videos
|
||||
builder.line 'video: ', video.youTubeId, ', ', video.name
|
||||
builder.line()
|
||||
@@ -1,99 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_parser_v1.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require '../game/item'
|
||||
ItemParserV1 = require './item_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
baseText = item = parser = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'item_parser_v1.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
item = new Item name:'alpha'
|
||||
parser = new ItemParserV1 model:item
|
||||
|
||||
describe 'officialUrl', ->
|
||||
|
||||
it 'may be omitted', ->
|
||||
parser.parse 'schema: 1\nvideo: youtubeid, Video Alpha\ndescription: Bravo, Charlie'
|
||||
expect(item.officialUrl).to.be.null
|
||||
|
||||
it 'is assigned properly when given', ->
|
||||
parser.parse 'schema: 1\nofficialUrl: http://testurl.com'
|
||||
item.officialUrl.should.equal 'http://testurl.com'
|
||||
|
||||
it 'does not allow duplicate declarations', ->
|
||||
func = -> parser.parse 'schema: 1\nofficialUrl: http://testurl.com\nofficialUrl: http://testurl2.com'
|
||||
expect(func).to.throw Error, 'duplicate'
|
||||
|
||||
it 'does not allow an empty value if given', ->
|
||||
func = -> parser.parse 'schema: 1\nofficialUrl:'
|
||||
expect(func).to.throw Error, 'empty'
|
||||
|
||||
describe 'description', ->
|
||||
|
||||
it 'may be omitted', ->
|
||||
parser.parse 'schema: 1\nvideo: youTubeId, Video Alpha\nofficialUrl: http://testurl.com'
|
||||
expect(item.description).to.be.null
|
||||
|
||||
it 'is assigned properly when given', ->
|
||||
parser.parse 'schema: 1\ndescription: Alpha Bravo Charlie'
|
||||
item.description.should.equal 'Alpha Bravo Charlie'
|
||||
|
||||
it 'can be a heredoc', ->
|
||||
parser.parse 'schema: 1\ndescription: <<-END\nAlpha\nBravo\nCharlie\nEND'
|
||||
item.description.should.equal 'Alpha\nBravo\nCharlie'
|
||||
|
||||
it 'concatenates multiple declarations', ->
|
||||
parser.parse 'schema: 1\ndescription: Alpha\ndescription: Bravo'
|
||||
item.description.should.equal 'Alpha\nBravo'
|
||||
|
||||
describe 'video', ->
|
||||
|
||||
it 'may be omitted', ->
|
||||
parser.parse 'schema: 1\nofficialUrl: http://testurl.com\ndescription: Alpha Bravo Charlie'
|
||||
item.videos.should.eql []
|
||||
|
||||
it 'is assigned properly when given', ->
|
||||
parser.parse 'schema: 1\nvideo: youtubeid, Alpha Bravo'
|
||||
item.videos[0].should.eql youTubeId:'youtubeid', name:'Alpha Bravo'
|
||||
item.videos.length.should.equal 1
|
||||
|
||||
it 'may be included multiple times', ->
|
||||
parser.parse 'schema: 1\nvideo: youtubeid1, Alpha\nvideo: youtubeid2, Bravo'
|
||||
item.videos[0].should.eql youTubeId:'youtubeid1', name:'Alpha'
|
||||
item.videos[1].should.eql youTubeId:'youtubeid2', name:'Bravo'
|
||||
item.videos.length.should.equal 2
|
||||
|
||||
it 'requires a YouTubeId and name', ->
|
||||
func = -> parser.parse 'schema: 1\nvideo: alpha'
|
||||
expect(func).to.throw Error, 'requires a name'
|
||||
|
||||
describe 'unparsing', ->
|
||||
|
||||
it 'can round-trip a fully described item', ->
|
||||
text = """
|
||||
schema: 1
|
||||
|
||||
officialUrl: http://testurl.com
|
||||
|
||||
description: <<-END
|
||||
Alpha
|
||||
Bravo
|
||||
END
|
||||
|
||||
video: youtubeid1, Alpha Bravo
|
||||
video: youtubeid2, Charlie Delta
|
||||
|
||||
|
||||
"""
|
||||
parser.parse text
|
||||
parser.unparse().should.equal text
|
||||
@@ -1,19 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
ModParserV1 = require './mod_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new ModParserV1 options
|
||||
@@ -1,94 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_parser_v1.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
Mod = require '../game/mod'
|
||||
ModVersion = require '../game/mod_version'
|
||||
Tutorial = require '../site/tutorial'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModParserV1 extends CommandParserVersionBase
|
||||
|
||||
# CommandParserVersionBase Overrides ###########################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
@_buildMod rawData, model
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
@_unparseMod builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_author: (authorParts...)->
|
||||
if @_rawData.author? then throw new Error 'duplicate declaration of "author"'
|
||||
author = authorParts.join ', '
|
||||
if author.length is 0 then throw new Error '"author" cannot be empty, but may be omitted'
|
||||
|
||||
@_rawData.author = author
|
||||
|
||||
_command_description: (descriptionParts...)->
|
||||
if @_rawData.description? then throw new Error 'duplicate declaration of "description"'
|
||||
description = descriptionParts.join ', '
|
||||
if description.length is 0 then throw new Error '"description" cannot be empty, but may be omitted'
|
||||
|
||||
@_rawData.description = description
|
||||
|
||||
_command_documentationUrl: (documentationUrl)->
|
||||
documentationUrl ?= ''
|
||||
if @_rawData.documentationUrl? then throw new Error 'duplicate declaration of "documentationUrl"'
|
||||
if documentationUrl.length is 0 then throw new Error 'documentationUrl cannot be empty (omit it instead)'
|
||||
@_rawData.documentationUrl = documentationUrl
|
||||
|
||||
_command_downloadUrl: (downloadUrl)->
|
||||
if @_rawData.downloadUrl? then throw new Error 'duplicate declaration of "downloadUrl"'
|
||||
if downloadUrl.length is 0 then throw new Error 'downloadUrl cannot be empty (omit it instead)'
|
||||
@_rawData.downloadUrl = downloadUrl
|
||||
|
||||
_command_homePageUrl: (homePageUrl='')->
|
||||
if @_rawData.homePageUrl? then throw new Error 'duplicate declaration of "homePageUrl"'
|
||||
if homePageUrl.length is 0 then throw new Error 'homePageUrl cannot be empty'
|
||||
|
||||
@_rawData.homePageUrl = homePageUrl
|
||||
|
||||
_command_name: (name)->
|
||||
if @_rawData.name? then throw new Error 'duplicate declaration of "name"'
|
||||
if name.length is 0 then throw new Error '"name" cannot be empty'
|
||||
@_rawData.name = name
|
||||
|
||||
_command_tutorial: (nameParts...)->
|
||||
name = nameParts.join(', ').trim()
|
||||
if name.length is 0 then throw new Error '"name" cannot be empty'
|
||||
@_rawData.tutorialNames ?= []
|
||||
@_rawData.tutorialNames.push name
|
||||
|
||||
_command_version: (version='')->
|
||||
if version.length is 0 then throw new Error 'version cannot be empty'
|
||||
|
||||
@_rawData.versions ?= []
|
||||
@_rawData.versions.push version
|
||||
|
||||
# Object Building Methods ######################################################################
|
||||
|
||||
_buildMod: (rawData, model)->
|
||||
if not rawData.name? then throw new Error 'the "name" declaration is required'
|
||||
if not rawData.homePageUrl? then throw new Error 'the "homePageUrl" declaration is required'
|
||||
if not rawData.versions? then throw new Error 'at least one "version" declaration is required'
|
||||
|
||||
model.author = rawData.author if rawData.author?
|
||||
model.description = rawData.description if rawData.description?
|
||||
model.documentationUrl = rawData.documentationUrl if rawData.documentationUrl?
|
||||
model.downloadUrl = rawData.downloadUrl if rawData.downloadUrl?
|
||||
model.name = rawData.name
|
||||
model.homePageUrl = rawData.homePageUrl
|
||||
|
||||
if rawData.tutorialNames?
|
||||
for tutorialName in rawData.tutorialNames
|
||||
model.addTutorial new Tutorial name:tutorialName
|
||||
|
||||
for version in rawData.versions
|
||||
model.addModVersion new ModVersion modSlug:model.slug, version:version
|
||||
@@ -1,19 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_version_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
ModVersionParserV1 = require './mod_version_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModVersionParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new ModVersionParserV1 options
|
||||
@@ -1,380 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_version_parser_v1.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
Item = require '../game/item'
|
||||
ItemSlug = require '../game/item_slug'
|
||||
ModVersion = require '../game/mod_version'
|
||||
Multiblock = require '../game/multiblock'
|
||||
Recipe = require '../game/recipe'
|
||||
Stack = require '../game/simple_stack'
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModVersionParserV1 extends CommandParserVersionBase
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@INTEGER = /[0-9]+/
|
||||
|
||||
@PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/
|
||||
|
||||
@STACK = /^([0-9]+) +(.*)$/
|
||||
|
||||
# CommandParserVersionBase Overrides ###########################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
@_buildModVersion rawData, model
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
@_unparseModVersion builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_extras: (extraTerms...)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "extras" before "recipe"'
|
||||
if @_recipeData.output.length isnt 1 then throw new Error 'duplicate declaration of "extras"'
|
||||
|
||||
for term in extraTerms
|
||||
@_recipeData.output.push @_parseStack term
|
||||
|
||||
_command_gatherable: (gatherable)->
|
||||
if not @_itemData? then throw new Error 'cannot declare "gatherable" before "item"'
|
||||
if @_itemData.gatherable? then throw new Error 'duplicate declaration of "gatherable"'
|
||||
if not (gatherable in ['yes', 'no']) then throw new Error 'gatherable must be either "yes" or "no"'
|
||||
|
||||
@_itemData.gatherable = (gatherable is 'yes')
|
||||
|
||||
_command_group: (group)->
|
||||
if group.length is 0 then throw new Error 'a group name cannot be empty'
|
||||
@_rawData.group = group
|
||||
|
||||
_command_item: (name='')->
|
||||
if not name.length > 0 then throw new Error 'the item name cannot be empty'
|
||||
|
||||
@_itemData = name:name, line:@_lineNumber, group:@_rawData.group, type:'new'
|
||||
@_rawData.items ?= {}
|
||||
@_rawData.items[name] = @_itemData
|
||||
|
||||
@_recipeData = null
|
||||
|
||||
_command_ignoreDuringCrafting: (value)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "ignoreDuringCrafting" before "recipe"'
|
||||
if @_recipeData.ignoreDuringCrafting? then throw new Error 'duplicate declaration of "ignoreDuringCrafting"'
|
||||
if not (value in ['yes', 'no']) then throw new Error 'ignoreDuringCrafting must be either "yes" or "no"'
|
||||
|
||||
@_recipeData.ignoreDuringCrafting = (value is 'yes')
|
||||
|
||||
_command_input: (stackDescriptions...)->
|
||||
activeData = if @_recipeData? then @_recipeData else @_multiblockData
|
||||
|
||||
if not activeData? then throw new Error 'cannot declare "input" before "recipe" or "multiblock"'
|
||||
if activeData.input.length isnt 0 then throw new Error 'duplicate declaration of "input"'
|
||||
|
||||
for stackDescription in stackDescriptions
|
||||
activeData.input.push @_parseStack stackDescription
|
||||
|
||||
_command_layer: (layerText)->
|
||||
if not @_multiblockData? then throw new Error 'cannot declare "layer" before "multiblock"'
|
||||
if not layerText? then throw new Error 'cannot have an empty layer'
|
||||
if layerText.length is 0 then throw new Error 'cannot have an empty layer'
|
||||
|
||||
@_multiblockData.layers.push layerText
|
||||
|
||||
_command_multiblock: ->
|
||||
if not @_itemData? then throw new Error 'cannot declare "multiblock" before "item"'
|
||||
if @_itemData.multiblockData? then throw new Error 'duplicate declaration of "multiblock"'
|
||||
|
||||
@_recipeData = null
|
||||
@_multiblockData = input:[], layers:[], line:@_lineNumber
|
||||
@_itemData.multiblockData = @_multiblockData
|
||||
|
||||
_command_onlyIf: (condition)->
|
||||
words = condition.split ' '
|
||||
if words.length < 2 then throw new Error 'condition must include a verb followed by a noun'
|
||||
|
||||
inverted = false
|
||||
if words[0] is 'not'
|
||||
inverted = true
|
||||
words.shift()
|
||||
|
||||
verb = words[0]
|
||||
noun = words[1..].join ' '
|
||||
|
||||
if not (verb in ['item', 'mod']) then throw new Error "unknown verb: #{verb}"
|
||||
@_recipeData.condition = verb:verb, noun:noun, inverted:inverted
|
||||
|
||||
_command_pattern: (pattern='')->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "pattern" before "recipe"'
|
||||
if @_recipeData.pattern? then throw new Error 'duplicate declaration of "pattern"'
|
||||
if not ModVersionParserV1.PATTERN.test pattern
|
||||
throw new Error 'a pattern must have 9 digits using 0-9 for items and "." for an empty spot;
|
||||
spaces are optional'
|
||||
|
||||
@_recipeData.pattern = pattern
|
||||
|
||||
_command_quantity: (quantity)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "quantity" before "recipe"'
|
||||
if @_recipeData.quantity? then throw new Error 'duplicate declaration of "quantity"'
|
||||
if not ModVersionParserV1.INTEGER.test(quantity) then throw new Error 'quantity must be an integer'
|
||||
|
||||
@_recipeData.quantity = quantity
|
||||
@_recipeData.output[0].quantity = parseInt quantity
|
||||
|
||||
_command_recipe: ->
|
||||
if not @_itemData? then throw new Error 'cannot delcare "recipe" before "item"'
|
||||
|
||||
@_multiblockData = null
|
||||
@_recipeData = line:@_lineNumber, input:[], output:[{quantity:1, name:@_itemData.name}], tools:[]
|
||||
@_itemData.recipes ?= []
|
||||
@_itemData.recipes.push @_recipeData
|
||||
|
||||
_command_tools: (toolNames...)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "tools" before "recipe"'
|
||||
if @_recipeData.tools.length isnt 0 then throw new Error 'duplicate declaration of "tools"'
|
||||
|
||||
for name in toolNames
|
||||
if name.length is 0 then throw new Error 'tool names cannot be empty'
|
||||
@_recipeData.tools.push name:name, quantity:1
|
||||
|
||||
_command_update: (name='')->
|
||||
if not name.length > 0 then throw new 'the item name cannot be empty'
|
||||
|
||||
@_itemData = name:name, line:@_lineNumber, type:'update'
|
||||
@_recipeData = null
|
||||
|
||||
@_rawData.items ?= {}
|
||||
@_rawData.items[name] = @_itemData
|
||||
|
||||
# Parsing Helpers ##############################################################################
|
||||
|
||||
_parseStack: (stackText)->
|
||||
match = ModVersionParserV1.STACK.exec stackText
|
||||
if match?
|
||||
return quantity:parseInt(match[1]), name:match[2]
|
||||
else
|
||||
return quantity:1, name:stackText
|
||||
|
||||
# Object Creation Methods ######################################################################
|
||||
|
||||
_buildModVersion: (modVersionData, modVersion)->
|
||||
modVersionData.items ?= []
|
||||
|
||||
for itemName, itemData of modVersionData.items
|
||||
@_handleErrors @_buildItem, modVersion, itemData
|
||||
|
||||
modVersion.sort()
|
||||
return modVersion
|
||||
|
||||
_buildItem: (modVersion, itemData)->
|
||||
@_lineNumber = itemData.line
|
||||
itemData.gatherable ?= false
|
||||
itemData.ignoreDuringCrafting ?= false
|
||||
itemData.recipes ?= []
|
||||
|
||||
if itemData.type is 'new'
|
||||
item = new Item
|
||||
name: itemData.name,
|
||||
ignoreDuringCrafting: itemData.ignoreDuringCrafting,
|
||||
isGatherable: itemData.gatherable,
|
||||
group: itemData.group
|
||||
modVersion.addItem item
|
||||
itemData.slug = item.slug
|
||||
else
|
||||
itemData.slug = ItemSlug.slugify itemData.name
|
||||
modVersion.registerName itemData.slug, itemData.name
|
||||
|
||||
if itemData.multiblockData
|
||||
item.multiblock = @_handleErrors @_buildMultiblock, modVersion, item, itemData.multiblockData
|
||||
|
||||
for recipeData in itemData.recipes
|
||||
@_handleErrors @_buildRecipe, modVersion, itemData, recipeData
|
||||
|
||||
return item
|
||||
|
||||
_buildMultiblock: (modVersion, item, multiblockData)->
|
||||
@_lineNumber = multiblockData.line
|
||||
if multiblockData.layers.length is 0 then throw new Error '"multiblock" requires at least one "layer"'
|
||||
if multiblockData.input.length is 0 then throw new Error '"multiblock" requires at least one "input"'
|
||||
|
||||
input = @_buildStackList modVersion, multiblockData.input
|
||||
multiblock = new Multiblock input:input, layers:multiblockData.layers
|
||||
return multiblock
|
||||
|
||||
_buildRecipe: (modVersion, itemData, recipeData)->
|
||||
@_lineNumber = recipeData.line
|
||||
if recipeData.input.length is 0 then throw new Error 'the "input" declaration is required'
|
||||
if not recipeData.pattern? then throw new Error 'the "pattern" declaration is required'
|
||||
|
||||
recipe = new Recipe
|
||||
condition: recipeData.condition
|
||||
ignoreDuringCrafting: recipeData.ignoreDuringCrafting
|
||||
input: @_buildStackList modVersion, recipeData.input, recipeData.pattern
|
||||
output: @_buildStackList modVersion, recipeData.output
|
||||
pattern: recipeData.pattern
|
||||
tools: @_buildStackList modVersion, recipeData.tools
|
||||
|
||||
modVersion.addRecipe recipe
|
||||
return recipe
|
||||
|
||||
_buildStackList: (modVersion, data, pattern=null)->
|
||||
createSlug = (name)=>
|
||||
item = @_rawData.items[name]
|
||||
if item? and item.type isnt 'update'
|
||||
slug = new ItemSlug modVersion.modSlug, _.slugify name
|
||||
else
|
||||
slug = new ItemSlug name
|
||||
modVersion.registerName slug, name
|
||||
return slug
|
||||
|
||||
stacks = []
|
||||
for stackData in data
|
||||
itemSlug = createSlug stackData.name
|
||||
stacks.push new Stack itemSlug:itemSlug, quantity:stackData.quantity
|
||||
|
||||
if pattern?
|
||||
expectedIndexes = _.reduce [0...stacks.length], ((obj, i)-> obj[i] = true; return obj), {}
|
||||
for c in pattern
|
||||
continue if c is '.'
|
||||
continue if c is ' '
|
||||
delete expectedIndexes[c]
|
||||
if not stacks[parseInt(c)]? then throw new Error "there is no item #{c} in this recipe"
|
||||
|
||||
unusedNames = _.map(_.keys(expectedIndexes), ((i)-> data[parseInt(i)].name))
|
||||
if unusedNames.length > 1
|
||||
throw new Error "#{unusedNames.join(', ')} are listed for this recipe, but do not appear in the pattern"
|
||||
else if unusedNames.length is 1
|
||||
throw new Error "#{unusedNames[0]} is listed for this recipe, but does not appear in the pattern"
|
||||
|
||||
return stacks
|
||||
|
||||
# Un-parsing Methods ###########################################################################
|
||||
|
||||
_unparseModVersion: (builder, modVersion)->
|
||||
builder
|
||||
.line 'schema: ', 1
|
||||
.line()
|
||||
|
||||
modVersion.eachGroup (group)=>
|
||||
@_unparseGroup builder, modVersion, group
|
||||
|
||||
externalRecipes = modVersion.findExternalRecipes()
|
||||
keys = _.keys(externalRecipes).sort()
|
||||
for itemSlugText in keys
|
||||
recipeList = externalRecipes[itemSlugText]
|
||||
|
||||
builder
|
||||
.line 'update: ', modVersion.findName ItemSlug.slugify(itemSlugText)
|
||||
.indent()
|
||||
.loop(recipeList, delimiter:'', onEach:(b, r)=> @_unparseRecipe(b, r))
|
||||
.outdent()
|
||||
.line()
|
||||
|
||||
_unparseGroup: (builder, modVersion, group)->
|
||||
if group isnt Item.Group.Other
|
||||
builder
|
||||
.line 'group: ', group
|
||||
.line()
|
||||
.indent()
|
||||
|
||||
modVersion.eachItemInGroup group, (item)=>
|
||||
@_unparseItem builder, modVersion, item
|
||||
builder.line()
|
||||
|
||||
if group isnt Item.Group.Other
|
||||
builder.outdent()
|
||||
|
||||
_unparseItem: (builder, modVersion, item)->
|
||||
recipes = modVersion.findRecipes item.slug, [], onlyPrimary:true
|
||||
|
||||
builder
|
||||
.line 'item: ', item.name
|
||||
.indent()
|
||||
.onlyIf item.isGatherable, => builder.line 'gatherable: yes'
|
||||
.onlyIf item.multiblock?, =>
|
||||
builder
|
||||
.indent()
|
||||
.call => @_unparseMultiblock builder, item.multiblock
|
||||
.outdent()
|
||||
.onlyIf recipes.length > 0, =>
|
||||
builder.loop recipes, delimiter:'', onEach:(b, r)=> @_unparseRecipe(b, r)
|
||||
.outdent()
|
||||
|
||||
_unparseMultiblock: (builder, multiblock)->
|
||||
builder
|
||||
.line 'multiblock:'
|
||||
.indent()
|
||||
.push "input: "
|
||||
.call => @_unparseStackList builder, multiblock.input
|
||||
.push ";\n"
|
||||
.loop(multiblock.layers, delimiter:'', onEach:(builder, layer)=> builder.line "layer: #{layer}")
|
||||
.outdent()
|
||||
|
||||
_unparseRecipe: (builder, recipe)->
|
||||
inputStacks = recipe.input[..]
|
||||
inputStacks.sort (a, b)-> Stack.compare a, b
|
||||
inputNames = []
|
||||
for stack in inputStacks
|
||||
name = builder.context.findName stack.itemSlug
|
||||
if stack.quantity > 1
|
||||
inputNames.push "#{stack.quantity} #{name}"
|
||||
else
|
||||
inputNames.push name
|
||||
|
||||
patternMap = {'.', '.'}
|
||||
for i in [0...recipe.input.length]
|
||||
stack = recipe.input[i]
|
||||
name = builder.context.findName(stack.itemSlug)
|
||||
name = "#{stack.quantity} #{name}" if stack.quantity > 1
|
||||
patternMap["#{i}"] = "#{inputNames.indexOf name}"
|
||||
|
||||
pattern = recipe.pattern or recipe.defaultPattern
|
||||
newPattern = []
|
||||
for c in pattern.split('')
|
||||
newPattern.push patternMap[c]
|
||||
newPattern = newPattern.join ''
|
||||
newPattern = newPattern.replace /(...)(...)(...)/, '$1 $2 $3'
|
||||
|
||||
quantity = recipe.output[0].quantity
|
||||
|
||||
extraOutputs = recipe.output[0...recipe.output.length]
|
||||
extraOutputs.shift()
|
||||
|
||||
builder
|
||||
.line 'recipe:'
|
||||
.indent()
|
||||
.onlyIf recipe.condition?, =>
|
||||
builder.push 'onlyIf: '
|
||||
.onlyIf recipe.condition.inverted, -> builder.push 'not '
|
||||
.line recipe.condition.verb, ' ', recipe.condition.noun
|
||||
.onlyIf extraOutputs.length > 0, =>
|
||||
builder
|
||||
.push 'extras: '
|
||||
.call => @_unparseStackList builder, extraOutputs
|
||||
.line()
|
||||
.onlyIf recipe.ignoreDuringCrafting, => builder.line 'ignoreDuringCrafting: yes'
|
||||
.push 'input: '
|
||||
.loop inputNames
|
||||
.line()
|
||||
.line 'pattern: ', newPattern
|
||||
.onlyIf quantity > 1, => builder.line 'quantity: ', quantity
|
||||
.onlyIf recipe.tools.length > 0, =>
|
||||
builder
|
||||
.push 'tools: '
|
||||
.call => @_unparseStackList builder, recipe.tools
|
||||
.line()
|
||||
.outdent()
|
||||
|
||||
_unparseStackList: (builder, stackList)->
|
||||
if stackList.length is 1 and stackList[0].quantity is 1
|
||||
builder.push builder.context.findName(stackList[0].itemSlug)
|
||||
else
|
||||
builder.loop stackList, onEach:(b, stack)=>
|
||||
builder
|
||||
.onlyIf stack.quantity > 1, => builder.push stack.quantity, ' '
|
||||
.push builder.context.findName stack.itemSlug
|
||||
@@ -1,344 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_version_parser_v1.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
ItemSlug = require '../game/item_slug'
|
||||
ModVersion = require '../game/mod_version'
|
||||
ModVersionParserV1 = require './mod_version_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
baseText = modVersion = parser = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'mod_version_parser_v1.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
modVersion = new ModVersion modSlug:'test', version:'0.0'
|
||||
parser = new ModVersionParserV1 model:modVersion
|
||||
|
||||
describe 'Item', ->
|
||||
|
||||
it 'allows multiple recipes', ->
|
||||
recipes = "item: Charlie;
|
||||
recipe:; input:Alpha; pattern:... .0. ...;
|
||||
recipe:; input:Bravo; pattern:... 0.0 ...;"
|
||||
modVersion = parser.parse recipes
|
||||
recipes = modVersion.findRecipes ItemSlug.slugify 'test__charlie'
|
||||
recipes[0].input[0].itemSlug.qualified.should.equal 'alpha'
|
||||
recipes[1].input[0].itemSlug.qualified.should.equal 'bravo'
|
||||
|
||||
describe 'name', ->
|
||||
|
||||
it 'adds the name when present', ->
|
||||
modVersion = parser.parse 'item: Charlie'
|
||||
modVersion._items.charlie.name.should.equal 'Charlie'
|
||||
|
||||
it 'requires a non-empty name', ->
|
||||
func = -> parser.parse 'item: \n'
|
||||
expect(func).to.throw Error, 'cannot be empty'
|
||||
|
||||
describe 'gatherable', ->
|
||||
|
||||
it 'adds "gatherable" when present', ->
|
||||
modVersion = parser.parse 'item: Alpha Bravo; gatherable: yes'
|
||||
modVersion._items.alpha_bravo.isGatherable.should.be.true
|
||||
|
||||
it 'does not allow a duplicate "gatherable" declaration', ->
|
||||
func = -> parser.parse 'item: Alpha Bravo; gatherable: yes; gatherable: yes'
|
||||
expect(func).to.throw Error, 'duplicate'
|
||||
|
||||
it 'requires "gatherable" to be "yes" or "no"', ->
|
||||
func = -> parser.parse 'item: Alpha Bravo; gatherable: true'
|
||||
expect(func).to.throw Error, 'gatherable must be'
|
||||
|
||||
it 'does not allow "gatherable" before "item"', ->
|
||||
func = -> parser.parse 'gatherable: yes; item: Alpha Bravo; gatherable: yes'
|
||||
expect(func).to.throw Error, '"gatherable" before "item"'
|
||||
|
||||
describe 'Multiblock', ->
|
||||
|
||||
it 'adds a "multiblock" when present', ->
|
||||
modVersion = parser.parse 'item: Alpha; multiblock:; input:Bravo; layer: 0'
|
||||
item = modVersion.findItemByName 'Alpha'
|
||||
item.multiblock.height.should.equal 1
|
||||
|
||||
it 'requires "item" be declared before "multiblock"', ->
|
||||
func = -> parser.parse "multiblock:"
|
||||
expect(func).to.throw Error, '"multiblock" before "item"'
|
||||
|
||||
it 'prohibits multiple "multiblock" commands per item"', ->
|
||||
func = -> parser.parse "item: Alpha; multiblock:; multiblock:"
|
||||
expect(func).to.throw Error, 'duplicate'
|
||||
|
||||
it 'prohibits multiblocks with no inputs', ->
|
||||
func = -> parser.parse "item: Alpha; multiblock:; layer: 000"
|
||||
expect(func).to.throw Error, 'at least one "input"'
|
||||
|
||||
describe 'layer', ->
|
||||
|
||||
it 'allows multiple "layer" commands', ->
|
||||
modVersion = parser.parse 'item: Alpha; multiblock:; input:Bravo, Charlie; layer: 01 10; layer: 10 01'
|
||||
item = modVersion.findItemByName 'Alpha'
|
||||
item.multiblock.depth.should.equal 2
|
||||
item.multiblock.height.should.equal 2
|
||||
item.multiblock.width.should.equal 2
|
||||
|
||||
it 'prohibits empty layers', ->
|
||||
func = -> parser.parse 'item: Alpha; multiblock:; input: Bravo; layer:; layer: 00 00'
|
||||
expect(func).to.throw Error, 'empty layer'
|
||||
|
||||
it 'prohibits multiblocks with no layers', ->
|
||||
func = -> parser.parse "item: Alpha; multiblock:; input: Bravo"
|
||||
expect(func).to.throw Error, 'at least one "layer"'
|
||||
|
||||
describe 'Recipe', ->
|
||||
|
||||
beforeEach -> baseText = 'item: Charlie; '
|
||||
|
||||
describe 'input', ->
|
||||
|
||||
it 'adds "input" when present', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern: ... 010 ...'
|
||||
charlieSlug = ItemSlug.slugify('test__charlie')
|
||||
slugs = (s.itemSlug.item for s in modVersion.findRecipes(charlieSlug)[0].input)
|
||||
slugs.should.eql ['alpha', 'bravo']
|
||||
|
||||
it 'requires an "input" declaration', ->
|
||||
func = -> parser.parse baseText + 'recipe:; pattern: ... .0. ...'
|
||||
expect(func).to.throw Error, 'the "input" declaration is required'
|
||||
|
||||
it 'does not allow a duplicate "input" declaration', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:....0....; input:Bravo'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "input"'
|
||||
|
||||
it 'does not allow "input" before "recipe"', ->
|
||||
func = -> parser.parse baseText + 'input:Alpha, Bravo; recipe:; pattern:....0....'
|
||||
expect(func).to.throw Error, 'cannot declare "input" before "recipe"'
|
||||
|
||||
it 'registers slugs for each input name', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...'
|
||||
(s.item for s in modVersion._slugs).should.eql ['charlie', 'delta', 'echo', 'foxtrot']
|
||||
|
||||
it 'correctly handles recipes which use the same input multiple times', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha; pattern:.0..0....'
|
||||
recipe = _.values(modVersion._recipes)[0]
|
||||
recipe.getQuantityRequired(ItemSlug.slugify('alpha')).should.equal 2
|
||||
|
||||
it 'allows a quantity for each input', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input: 12 Delta, 3 Echo; pattern:... 0.1 ...'
|
||||
recipe = _.values(modVersion._recipes)[0]
|
||||
recipe.input[0].quantity.should.equal 12
|
||||
recipe.input[0].itemSlug.qualified.should.equal 'delta'
|
||||
recipe.input[1].quantity.should.equal 3
|
||||
recipe.input[1].itemSlug.qualified.should.equal 'echo'
|
||||
|
||||
describe 'pattern', ->
|
||||
|
||||
it 'adds "pattern" when present', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
|
||||
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].pattern.should.equal '... .0. .1.'
|
||||
|
||||
it 'requires a "pattern" declaration', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
|
||||
expect(func).to.throw Error, 'the "pattern" declaration is required'
|
||||
|
||||
it 'does not allow a duplicate "pattern" declaration', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:....0..1.; pattern:01.......'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "pattern"'
|
||||
|
||||
it 'requires pattern to be the right length', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:000'
|
||||
expect(func).to.throw Error, 'a pattern must have'
|
||||
|
||||
it 'requires pattern to only use proper characters', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:abc def ghi'
|
||||
expect(func).to.throw Error, 'a pattern must have'
|
||||
|
||||
it 'requires pattern to only refer to existing items', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:... 010 ...'
|
||||
expect(func).to.throw Error, 'there is no item 1 in this recipe'
|
||||
|
||||
it 'requires all items to appear in the pattern', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern: 000 0.0 000'
|
||||
expect(func).to.throw Error, 'Bravo is listed'
|
||||
|
||||
it 'does not allow "pattern" before "recipe"', ->
|
||||
func = -> parser.parse baseText + 'pattern:... .0. ...; recipe:; inputs:Alpha'
|
||||
expect(func).to.throw Error, 'cannot declare "pattern" before "recipe"'
|
||||
|
||||
describe 'quantity', ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = 'item: Charlie; recipe:; input:Alpha; pattern:...0.0...; '
|
||||
|
||||
it 'adds "quantity" when present', ->
|
||||
modVersion = parser.parse baseText + 'quantity: 2'
|
||||
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].output[0].quantity.should.equal 2
|
||||
|
||||
it 'does not allow a duplicate "quantity" declaration', ->
|
||||
func = -> parser.parse baseText + 'quantity:1; quantity:2'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "quantity"'
|
||||
|
||||
it 'requires quantity to be an integer', ->
|
||||
func = -> parser.parse baseText + 'quantity:ten'
|
||||
expect(func).to.throw Error, 'quantity must be an integer'
|
||||
|
||||
it 'assumes a quantity of 1 by default', ->
|
||||
modVersion = parser.parse baseText
|
||||
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].output[0].quantity.should.equal 1
|
||||
|
||||
it 'does not allow "quantity" before recipe', ->
|
||||
func = -> parser.parse 'item:Bravo; quantity:12; recipe:;'
|
||||
expect(func).to.throw Error, 'cannot declare "quantity" before "recipe"'
|
||||
|
||||
describe 'onlyIf', ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = 'item: Charlie; recipe:; input:Alpha; pattern:...0.0...; '
|
||||
|
||||
it 'understands the "item" verb', ->
|
||||
modVersion = parser.parse baseText + 'onlyIf: item Iron Ingot'
|
||||
recipes = []
|
||||
modVersion.eachRecipe (recipe)-> recipes.push recipe
|
||||
recipe = recipes[0]
|
||||
recipe.condition.should.eql verb:'item', noun:'Iron Ingot', inverted:false
|
||||
|
||||
it 'understands the "mod" verb', ->
|
||||
modVersion = parser.parse baseText + 'onlyIf: mod BuildCraft'
|
||||
recipes = []
|
||||
modVersion.eachRecipe (recipe)-> recipes.push recipe
|
||||
recipe = recipes[0]
|
||||
recipe.condition.should.eql verb:'mod', noun:'BuildCraft', inverted:false
|
||||
|
||||
it 'understands inverting verbs', ->
|
||||
modVersion = parser.parse baseText + 'onlyIf: not item Iron Ingot'
|
||||
recipes = []
|
||||
modVersion.eachRecipe (recipe)-> recipes.push recipe
|
||||
recipe = recipes[0]
|
||||
recipe.condition.should.eql verb:'item', noun:'Iron Ingot', inverted:true
|
||||
|
||||
it 'requires at least two words', ->
|
||||
func = -> parser.parse baseText + 'onlyIf: item'
|
||||
expect(func).to.throw Error, 'verb followed by a noun'
|
||||
|
||||
it 'only allows known verbs', ->
|
||||
func = -> parser.parse baseText + 'onlyIf: foo Iron Ingot'
|
||||
expect(func).to.throw Error, 'unknown verb'
|
||||
|
||||
describe 'output', ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = 'item: Delta; item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
|
||||
|
||||
it 'adds a single item as the default output', ->
|
||||
modVersion = parser.parse baseText
|
||||
stack = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].output[0]
|
||||
stack.itemSlug.qualified.should.equal 'test__bravo'
|
||||
stack.quantity.should.equal 1
|
||||
|
||||
it 'can add multiple extras with quantities', ->
|
||||
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
|
||||
output = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].output
|
||||
output[0].itemSlug.qualified.should.equal 'test__bravo'
|
||||
output[0].quantity.should.equal 1
|
||||
output[1].itemSlug.qualified.should.equal 'test__delta'
|
||||
output[1].quantity.should.equal 2
|
||||
output[2].itemSlug.qualified.should.equal 'echo'
|
||||
output[2].quantity.should.equal 4
|
||||
|
||||
it 'does not allow "extras" before "recipe"', ->
|
||||
func = -> parser.parse 'item:Bravo; extras:Charlie'
|
||||
expect(func).to.throw Error, 'cannot declare "extras" before "recipe"'
|
||||
|
||||
it 'registers slugs for each output name', ->
|
||||
modVersion = parser.parse baseText + 'extras:Delta, Echo'
|
||||
(s.qualified for s in modVersion._slugs).should.eql [
|
||||
'test__bravo', 'charlie', 'test__delta', 'echo'
|
||||
]
|
||||
|
||||
it 'does not allow a duplicate "extras" declaration', ->
|
||||
func = -> parser.parse baseText + 'extras:Echo; extras:Delta'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "extras"'
|
||||
|
||||
describe 'tools', ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = 'item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
|
||||
|
||||
it 'can add a single tool', ->
|
||||
modVersion = parser.parse baseText + 'tools: Furnace'
|
||||
modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].tools[0].itemSlug.item.should.equal 'furnace'
|
||||
|
||||
it 'can add multiple tools', ->
|
||||
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
||||
tools = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].tools
|
||||
tools[0].itemSlug.item.should.equal 'crafting_table'
|
||||
tools[1].itemSlug.item.should.equal 'furnace'
|
||||
|
||||
it 'registers slugs for each tool name', ->
|
||||
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
||||
(s.item for s in modVersion._slugs).should.eql ['bravo', 'charlie', 'crafting_table', 'furnace']
|
||||
|
||||
it 'does not allow a duplicate "tools" declaration', ->
|
||||
func = -> parser.parse baseText + 'tools:Crafting Table; tools:Furnace'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "tools"'
|
||||
|
||||
describe "unparsing", ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = """
|
||||
schema: 1
|
||||
|
||||
group: Agriculture
|
||||
|
||||
item: Apple
|
||||
|
||||
item: Baked Potato
|
||||
recipe:
|
||||
input: furnace fuel, Potato
|
||||
pattern: .1. ... .0.
|
||||
tools: Furnace
|
||||
|
||||
item: (filled) Canned Food
|
||||
recipe:
|
||||
input: 4 (Empty) Tin Can, Apple
|
||||
pattern: .1. .0. ...
|
||||
|
||||
item: Pyramid
|
||||
multiblock:
|
||||
input: Cobblestone
|
||||
layer: 000 000 000
|
||||
layer: ... .0. ...
|
||||
|
||||
group: Functional Blocks
|
||||
|
||||
item: Furnace
|
||||
recipe:
|
||||
input: Cobblestone
|
||||
pattern: 000 0.0 000
|
||||
tools: Crafting Table
|
||||
|
||||
update: Iron Ingot
|
||||
recipe:
|
||||
input: furnace fuel, Iron Dust
|
||||
pattern: .1. ... .0.
|
||||
tools: Furnace
|
||||
recipe:
|
||||
onlyIf: item Redstone Furnace
|
||||
input: Iron Ore
|
||||
pattern: ... .0. ...
|
||||
tools: Redstone Furnace
|
||||
"""
|
||||
|
||||
it 'can round-trip a data file', ->
|
||||
text = parser.unparse parser.parse baseText
|
||||
actual = CommandParserVersionBase.simplify text
|
||||
expected = CommandParserVersionBase.simplify baseText
|
||||
|
||||
actual.should.equal expected
|
||||
@@ -1,19 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - tutorial_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
TutorialParserV1 = require './tutorial_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class TutorialParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new TutorialParserV1 options
|
||||
@@ -1,65 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - tutorial_parser_v1.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
Tutorial = require '../site/tutorial'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class TutorialParserV1 extends CommandParserVersionBase
|
||||
|
||||
# CommandParserVersionBase Overrides ###########################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
@_buildTutorial rawData, model
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
@_unparseTutorial builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_content: (contentParts...)->
|
||||
if not @_rawData.currentSection? then throw new Error 'cannot declare "title" before "section"'
|
||||
if @_rawData.currentSection.content? then throw new Error 'duplicate declaration of content'
|
||||
content = contentParts.join(', ').trim()
|
||||
if not content.length > 0 then throw new Error 'content cannot be empty'
|
||||
|
||||
@_rawData.currentSection.content = content
|
||||
|
||||
_command_officialUrl: (officialUrl)->
|
||||
if @_rawData.officialUrl? then throw new Error 'duplicate declaration of "officialUrl"'
|
||||
if officialUrl.length is 0 then throw new Error 'officialUrl cannot be empty'
|
||||
@_rawData.officialUrl = officialUrl
|
||||
|
||||
_command_section: (textParts...)->
|
||||
@_rawData.sections ?= []
|
||||
@_rawData.sections.push @_rawData.currentSection = {}
|
||||
|
||||
_command_title: (titleParts...)->
|
||||
if not @_rawData.currentSection? then throw new Error 'cannot declare "title" before "section"'
|
||||
if @_rawData.currentSection.title? then throw new Error 'duplicate declaration of title'
|
||||
title = titleParts.join(', ').trim()
|
||||
if not title.length > 0 then throw new Error 'title cannot be empty'
|
||||
|
||||
@_rawData.currentSection.title = title
|
||||
|
||||
_command_video: (youTubeId, nameParts...)->
|
||||
if not youTubeId?.length then throw new Error 'video declaration requires a YouTubeID'
|
||||
name = nameParts.join ', '
|
||||
if not name?.length then throw new Error 'video declaration requires a name'
|
||||
|
||||
@_rawData.videos ?= []
|
||||
@_rawData.videos.push youTubeId:youTubeId, name:name
|
||||
|
||||
# Object Building Methods ######################################################################
|
||||
|
||||
_buildTutorial: (rawData, model)->
|
||||
if not rawData.sections? then throw new Error 'the "section" declaration is required'
|
||||
|
||||
model.officialUrl = rawData.officialUrl
|
||||
model.videos = rawData.videos
|
||||
model.sections = rawData.sections
|
||||
@@ -1,51 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - versioned_parser_base.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class VersionedParserBase
|
||||
|
||||
constructor: (options={})->
|
||||
@_parsers = @_createParsers options
|
||||
@_currentSchema = _.chain(@_parsers).keys().last().value()
|
||||
@errors = []
|
||||
|
||||
# Class Members ################################################################################
|
||||
|
||||
@SCHEMA = /schema: *([0-9]+)/
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
parse: (text)->
|
||||
return unless text?
|
||||
|
||||
schema = @_identifySchema text
|
||||
parser = @_parsers[schema]
|
||||
if not parser? then throw new Error "schema version #{schema} is not supported"
|
||||
|
||||
parser.parse text
|
||||
@errors = parser.errors
|
||||
|
||||
return @_model
|
||||
|
||||
unparse: (schema=null)->
|
||||
schema ?= @_currentSchema
|
||||
|
||||
parser = @_parsers["#{schema}"]
|
||||
if not parser? then throw new Error "version #{schema} is not supported"
|
||||
|
||||
return parser.unparse()
|
||||
|
||||
# Overridable Methods ##########################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
throw new Error 'subclasses must override this method'
|
||||
|
||||
_identifySchema: (text)->
|
||||
match = VersionedParserBase.SCHEMA.exec text
|
||||
if not match? then throw new Error 'missing "schema" declaration'
|
||||
return match[1]
|
||||
@@ -1,46 +1,94 @@
|
||||
#
|
||||
# Crafting Guide - craft_page.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Craftsman = require '../crafting/craftsman'
|
||||
Inventory = require '../game/inventory'
|
||||
ModPack = require '../game/mod_pack'
|
||||
{Inventory} = require("crafting-guide-common").models
|
||||
{Observable} = require("crafting-guide-common").util
|
||||
{PlanBuilder} = require("crafting-guide-common").crafting
|
||||
{ResourcesEvaluator} = require("crafting-guide-common").crafting
|
||||
{StepsEvaluator} = require("crafting-guide-common").crafting
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftPage extends BaseModel
|
||||
module.exports = class CraftPage extends Observable
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.modPack then throw new Error 'attributes.modPack is required'
|
||||
attributes.params ?= null
|
||||
attributes.craftsman ?= new Craftsman attributes.modPack
|
||||
super attributes, options
|
||||
constructor: (attributes={})->
|
||||
super
|
||||
|
||||
@modPack.on c.event.change, => @_consumeParams()
|
||||
@on c.event.change + ':params', => @_consumeParams()
|
||||
@craftsman.on c.event.change + ':stage', => @trigger c.event.change, this
|
||||
@craftsman.on c.event.change + ':complete', => @trigger c.event.change, this
|
||||
@muted -> @modPack = attributes.modPack
|
||||
|
||||
@_currentPlan = null
|
||||
@_have = new Inventory
|
||||
@_planBuilder = new PlanBuilder new StepsEvaluator
|
||||
@_want = new Inventory
|
||||
|
||||
@_consumeParams attributes.params
|
||||
|
||||
@_have.on Observable::ANY, this, "_onHaveChanged"
|
||||
@_want.on Observable::ANY, this, "_onWantChanged"
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
currentPlan:
|
||||
get: -> return @_currentPlan
|
||||
set: -> throw new Error "currentPlan cannot be assigned"
|
||||
|
||||
have:
|
||||
get: -> return @_have
|
||||
set: -> throw new "have cannot be assigned"
|
||||
|
||||
isDirty:
|
||||
get: -> return @_isDirty
|
||||
set: -> throw new Error "isDirty cannot be assigned"
|
||||
|
||||
isOutdated:
|
||||
get: -> return @currentPlan? and @isDirty
|
||||
set: -> throw new Error "isOutdated cannot be assigned"
|
||||
|
||||
modPack:
|
||||
get: -> return @_modPack
|
||||
set: (modPack)->
|
||||
if @_modPack? then throw new Error "modPack cannot be reassigned"
|
||||
if not modPack? then throw new Error "modPack is required"
|
||||
@_modPack = modPack
|
||||
|
||||
planBuilder:
|
||||
get: -> return @_planBuilder
|
||||
set: -> throw new Error "planBuilder cannot be assigned"
|
||||
|
||||
want:
|
||||
get: -> return @_want
|
||||
set: -> throw new Error "want cannot be assigned"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
createPlan: ->
|
||||
newPlan = @_planBuilder.createPlan @_want, @_have
|
||||
@triggerPropertyChange "currentPlan", @_currentPlan, newPlan, ->
|
||||
@_currentPlan = newPlan
|
||||
@triggerPropertyChange "isDirty", @_isDirty, false
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_consumeParams: ->
|
||||
return unless @params?
|
||||
_consumeParams: (params)->
|
||||
return unless params?.inventoryText?
|
||||
|
||||
@craftsman.want.clear()
|
||||
if not @params.inventoryText?
|
||||
@params = null
|
||||
else
|
||||
inventory = new Inventory
|
||||
inventory.parse @params.inventoryText
|
||||
newWant = Inventory.fromUrlString params.inventoryText, @modPack
|
||||
@_want.clear()
|
||||
|
||||
inventory.each (stack)=>
|
||||
item = @modPack.findItem stack.itemSlug, enableAsNeeded:true
|
||||
return unless item? and item.isCraftable
|
||||
@craftsman.want.add stack.itemSlug, stack.quantity
|
||||
inventory.remove stack.itemSlug
|
||||
for itemId, stack of newWant.stacks
|
||||
continue unless stack.item.isCraftable
|
||||
@_want.add stack.item, stack.quantity
|
||||
|
||||
if inventory.isEmpty then @params = null
|
||||
_onHaveChanged: ->
|
||||
@triggerPropertyChange "isDirty", @_isDirty, true
|
||||
|
||||
_onWantChanged: ->
|
||||
@triggerPropertyChange "isDirty", @_isDirty, true, ->
|
||||
@_isDirty = true
|
||||
if @want.isEmpty then @triggerPropertyChange "currentPlan", @_currentPlan, null
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#
|
||||
# Crafting Guide - editable_file.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
{BaseModel} = require("crafting-guide-common").deprecated
|
||||
w = require "when"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Crafting Guide - email_client.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#
|
||||
# Crafting Guide - file_cache.coffee
|
||||
#
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
w = require "when"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class FileCache extends Backbone.Events
|
||||
|
||||
@::FILE_TOKEN = '#file='
|
||||
|
||||
constructor: (url)->
|
||||
@_ajax = $.ajax
|
||||
@_files = {}
|
||||
@_loading = w.resolve(true)
|
||||
|
||||
if url? then @loadArchive(url).catch(->)
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getFile: (file)->
|
||||
return null unless @hasFile file
|
||||
return @_files[file]
|
||||
|
||||
hasFile: (file)->
|
||||
return @_files[file]?
|
||||
|
||||
loadArchive: (url)->
|
||||
if not _.isFunction(@_ajax) then throw new Error '$.ajax is required'
|
||||
|
||||
newLoading = w.promise (resolve, reject)=>
|
||||
@_ajax
|
||||
url: url
|
||||
dataType: 'text'
|
||||
success: (text, status, xhr)=> resolve @_onLoadSuccess text, status, xhr
|
||||
error: (xhr, status, error)=> resolve @_onLoadError error, status, xhr
|
||||
|
||||
@_loading = w.join @_loading, newLoading
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
loading:
|
||||
get: -> return @_loading
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_onLoadError: (error, status, xhr)->
|
||||
console.error "Failed to load archive: #{xhr.responseText}"
|
||||
return false
|
||||
|
||||
_onLoadSuccess: (text, status, xhr)->
|
||||
return @_parseArchive text
|
||||
|
||||
_parseArchive: (text)->
|
||||
file = null
|
||||
tokenIndex = nameStartIndex = nameEndIndex = fileStartIndex = fileEndIndex = 0
|
||||
fileCount = 0
|
||||
|
||||
while true
|
||||
tokenIndex = text.indexOf @FILE_TOKEN, fileStartIndex
|
||||
if tokenIndex is -1
|
||||
if file?
|
||||
@_files[file] = text.substring fileStartIndex, text.length
|
||||
fileCount += 1
|
||||
break
|
||||
|
||||
if file?
|
||||
fileEndIndex = tokenIndex
|
||||
@_files[file] = text.substring fileStartIndex, fileEndIndex
|
||||
fileCount += 1
|
||||
|
||||
nameStartIndex = tokenIndex + @FILE_TOKEN.length
|
||||
nameEndIndex = text.indexOf '\n', nameStartIndex
|
||||
if nameEndIndex is -1
|
||||
file = text.substring nameStartIndex, text.length
|
||||
@_files[file] = ''
|
||||
break
|
||||
|
||||
file = text.substring nameStartIndex, nameEndIndex
|
||||
fileStartIndex = nameEndIndex + '\n'.length
|
||||
|
||||
return fileCount > 0
|
||||
@@ -0,0 +1,78 @@
|
||||
#
|
||||
# Crafting Guide - file_cache.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
FileCache = require './file_cache'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'file_cache.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
@cache = new FileCache
|
||||
|
||||
makeTestAjax = (text)->
|
||||
return (options)->
|
||||
options.success text, 'success', {url:options.url}
|
||||
|
||||
it 'can load an archive with a single file', ->
|
||||
@cache._ajax = makeTestAjax '#file=alpha\nbravo charlie delta'
|
||||
@cache.loadArchive 'http://test'
|
||||
.then =>
|
||||
@cache.hasFile('alpha').should.equal true
|
||||
@cache.getFile('alpha').should.equal 'bravo charlie delta'
|
||||
|
||||
it 'can load an archive with multiple files', ->
|
||||
@cache._ajax = makeTestAjax '#file=alpha\nbravo charlie\n#file=delta\necho foxtrot'
|
||||
@cache.loadArchive 'http://test'
|
||||
.then =>
|
||||
@cache.hasFile('alpha').should.equal true
|
||||
@cache.getFile('alpha').should.equal 'bravo charlie\n'
|
||||
@cache.hasFile('delta').should.equal true
|
||||
@cache.getFile('delta').should.equal 'echo foxtrot'
|
||||
|
||||
it 'can load an archive without newlines', ->
|
||||
@cache._ajax = makeTestAjax '#file=alpha\nbravo charlie#file=delta\necho foxtrot'
|
||||
@cache.loadArchive 'http://test'
|
||||
.then =>
|
||||
@cache.hasFile('alpha').should.equal true
|
||||
@cache.getFile('alpha').should.equal 'bravo charlie'
|
||||
@cache.hasFile('delta').should.equal true
|
||||
@cache.getFile('delta').should.equal 'echo foxtrot'
|
||||
|
||||
it 'can cope with an empty file in the middle of an archive', ->
|
||||
@cache._ajax = makeTestAjax '#file=alpha\nbravo charlie\n#file=delta\n#file=echo\nfoxtrot golf'
|
||||
@cache.loadArchive 'http://test'
|
||||
.then =>
|
||||
@cache.hasFile('alpha').should.equal true
|
||||
@cache.getFile('alpha').should.equal 'bravo charlie\n'
|
||||
@cache.hasFile('delta').should.equal true
|
||||
@cache.getFile('delta').should.equal ''
|
||||
@cache.hasFile('echo').should.equal true
|
||||
@cache.getFile('echo').should.equal 'foxtrot golf'
|
||||
|
||||
it 'can cope with an empty file at the end of an archive', ->
|
||||
@cache._ajax = makeTestAjax '#file=alpha\nbravo charlie\n#file=delta'
|
||||
@cache.loadArchive 'http://test'
|
||||
.then =>
|
||||
@cache.hasFile('alpha').should.equal true
|
||||
@cache.getFile('alpha').should.equal 'bravo charlie\n'
|
||||
@cache.hasFile('delta').should.equal true
|
||||
@cache.getFile('delta').should.equal ''
|
||||
|
||||
it 'can load multiple, non-overlapping archives', ->
|
||||
@cache._ajax = makeTestAjax '#file=alpha\nbravo charlie\n#file=delta\necho foxtrot'
|
||||
@cache.loadArchive 'http://test'
|
||||
|
||||
@cache._ajax = makeTestAjax '#file=golf\nhotel india'
|
||||
@cache.loadArchive 'http://test2'
|
||||
.then =>
|
||||
@cache.hasFile('alpha').should.equal true
|
||||
@cache.getFile('alpha').should.equal 'bravo charlie\n'
|
||||
@cache.hasFile('delta').should.equal true
|
||||
@cache.getFile('delta').should.equal 'echo foxtrot'
|
||||
@cache.hasFile('golf').should.equal true
|
||||
@cache.getFile('golf').should.equal 'hotel india'
|
||||
@@ -1,11 +1,11 @@
|
||||
#
|
||||
# Crafting Guide - github_user.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
{BaseModel} = require("crafting-guide-common").deprecated
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#
|
||||
# Crafting Guide - item_display.coffee
|
||||
#
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
_ = require "../../../common/underscore"
|
||||
c = require "../../../common/constants"
|
||||
{Inventory} = require("crafting-guide-common").models
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemDisplay
|
||||
|
||||
constructor: (item)->
|
||||
@item = item
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
craftingUrl:
|
||||
get: ->
|
||||
inventory = new Inventory
|
||||
inventory.add @item
|
||||
c.url.crafting inventoryText:inventory.toUrlString()
|
||||
|
||||
iconUrl:
|
||||
get: -> return c.url.itemIcon this
|
||||
set: -> throw new Error "iconUrl cannot be assigned"
|
||||
|
||||
item:
|
||||
get: -> return @_item
|
||||
set: (item)->
|
||||
if @_item? then throw new Error "item cannot be re-assigned"
|
||||
if not item? then throw new Error "item is required"
|
||||
@_item = item
|
||||
|
||||
itemSlug:
|
||||
get: -> return _.slugify @name
|
||||
set: -> throw new Error "slug cannot be assigned"
|
||||
|
||||
mod:
|
||||
get: -> return @_item.mod
|
||||
set: -> throw new Error "mod cannot be assigned"
|
||||
|
||||
modId:
|
||||
get: -> return @_item.mod.id
|
||||
set: -> throw new Error "modId cannot be assigned"
|
||||
|
||||
modUrl:
|
||||
get: -> return c.url.mod modId:@item.mod.id
|
||||
set: -> throw new Error "modUrl cannot be assigned"
|
||||
|
||||
name:
|
||||
get: -> return @item.displayName
|
||||
set: -> throw new Error "name cannot be assigned"
|
||||
|
||||
url:
|
||||
get: -> return c.url.item this
|
||||
set: -> throw new Error "url cannot be assigned"
|
||||
@@ -1,56 +1,95 @@
|
||||
#
|
||||
# Crafting Guide - item_page.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
{Item} = require("crafting-guide-common").models
|
||||
ItemDisplay = require "./item_display"
|
||||
{Observable} = require("crafting-guide-common").util
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemPage extends BaseModel
|
||||
module.exports = class ItemPage extends Observable
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.modPack? then throw new Error 'attributes.modPack is required'
|
||||
attributes.item ?= null
|
||||
super attributes, options
|
||||
constructor: (item)->
|
||||
if item.constructor isnt Item then throw new Error "item must be an Item instance"
|
||||
@item = item
|
||||
super
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
item:
|
||||
get: -> return @_item
|
||||
set: (item)->
|
||||
if @_item? then throw new Error "item cannot be reassigned"
|
||||
if not item? then throw new Error "item is required"
|
||||
if @_item? then @_item.off Observable::ANY, this
|
||||
@_item = item
|
||||
@_item.on Observable::ANY, this, "_onItemChanged"
|
||||
|
||||
itemDisplay:
|
||||
get: -> return @_itemDisplay ?= new ItemDisplay @item
|
||||
|
||||
mod:
|
||||
get: -> return @_item.mod
|
||||
set: -> throw new Error "mod cannot be assigned"
|
||||
|
||||
modPack:
|
||||
get: -> return @mod.modPack
|
||||
set: -> throw new Error "modPack cannot be assigned"
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
findComponentInItems: ->
|
||||
return @_findRecipesMatching (recipe)=> recipe.requires @item.slug
|
||||
|
||||
findSimilarItems: ->
|
||||
return null unless @item?.modVersion?
|
||||
|
||||
result = []
|
||||
@item.modVersion.eachItemInGroup @item.group, (item)=>
|
||||
result.push item
|
||||
|
||||
return null unless result.length > 0
|
||||
return result
|
||||
return @_findItemsWithMatchingRecipes (recipe)=>
|
||||
recipe.computeQuantityRequired @item
|
||||
|
||||
findRecipes: ->
|
||||
return @modPack.findRecipes @item?.slug, [], alwaysFromOwningMod:true
|
||||
return (recipe for recipeId, recipe of @item.recipes)
|
||||
|
||||
findToolForRecipes: ->
|
||||
return @_findRecipesMatching (recipe)=> recipe.requiresTool @item.slug
|
||||
findSimilarItems: ->
|
||||
group = @mod.itemGroups[@item.groupName]
|
||||
return unless group?
|
||||
|
||||
return null unless group.length > 0
|
||||
return group
|
||||
|
||||
findToolForItems: ->
|
||||
return @_findItemsWithMatchingRecipes (recipe)=>
|
||||
return recipe.tools[@item.id]?
|
||||
|
||||
loadDetails: ->
|
||||
stores.itemDetail.loadDetailFor @item
|
||||
.catch (error)=>
|
||||
if error.message.indexOf("404") is -1
|
||||
logger.error "Could not load details for item #{@item.id}: #{error.message}"
|
||||
else
|
||||
logger.info "Item #{@item.id} has no details file."
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_findRecipesMatching: (isAcceptable)->
|
||||
return null unless @item?
|
||||
|
||||
_findItemsWithMatchingRecipes: (isMatching)->
|
||||
result = {}
|
||||
@modPack.eachMod (mod)=>
|
||||
mod.eachRecipe (recipe)=>
|
||||
if isAcceptable recipe
|
||||
for outputStack in recipe.output
|
||||
outputItem = @modPack.findItem outputStack.itemSlug, includeDisabled:true
|
||||
result[outputItem.slug] = outputItem
|
||||
|
||||
result = _.values result
|
||||
return null unless result.length > 0
|
||||
for modId, mod of @modPack.mods
|
||||
for itemId, item of mod.items
|
||||
for recipeId, recipe of item.recipes
|
||||
if isMatching(recipe)
|
||||
result[item.id] = item
|
||||
|
||||
result = (item for itemId, item of result)
|
||||
result.sort (a, b)->
|
||||
if not a?.displayName? and not b?.displayName? then return 0
|
||||
if not b?.displayName? then return +1
|
||||
if not a?.displayName? then return +1
|
||||
return a.displayName.localeCompare b.displayName
|
||||
|
||||
return result
|
||||
|
||||
_onItemChanged: ->
|
||||
@trigger Observable::CHANGE
|
||||
|
||||
return result.sort (a, b)-> a.compareTo b
|
||||
|
||||
@@ -1,89 +1,104 @@
|
||||
#
|
||||
# Crafting Guide - item_selector.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
ItemSlug = require '../game/item_slug'
|
||||
{Observable} = require("crafting-guide-common").util
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemSelector extends BaseModel
|
||||
module.exports = class ItemSelector extends Observable
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.isAcceptable ?= (item)-> return true # accept everything by default
|
||||
super attributes, options
|
||||
constructor: (modPack, options={})->
|
||||
super
|
||||
|
||||
@_isAcceptable = options.isAcceptable
|
||||
@_isAcceptable = options.isAcceptable or (item)-> return true # accept everything by default
|
||||
@_maxResults = 100
|
||||
@_minHintLength = 3
|
||||
@_modPack = options.modPack
|
||||
@_results = []
|
||||
|
||||
@modPack = modPack
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
hint:
|
||||
get: ->
|
||||
return @_hint
|
||||
get: -> return @_hint
|
||||
set: (hint)->
|
||||
@triggerPropertyChange "hint", @_hint, hint, ->
|
||||
@_hint = hint?.toLowerCase()
|
||||
@_refreshResults()
|
||||
|
||||
set: (newHint)->
|
||||
oldHint = @_hint
|
||||
return if newHint is oldHint
|
||||
|
||||
@_hint = newHint.toLowerCase()
|
||||
|
||||
@trigger c.event.change + ':hint', this, oldHint, newHint
|
||||
@_refreshResults()
|
||||
@trigger c.event.change, this
|
||||
modPack:
|
||||
get: -> return @_modPack
|
||||
set: (modPack)->
|
||||
if not modPack? then throw new Error "modPack is required"
|
||||
if @_modPack? then throw new Error "modPack cannot be reassigned"
|
||||
@_modPack = modPack
|
||||
|
||||
results:
|
||||
get: ->
|
||||
return @_results
|
||||
get: -> return @_results
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_isMatch: (name, itemSlug)->
|
||||
_computeScore: (item)->
|
||||
hintIndex = 0
|
||||
hintLetter = @_hint[hintIndex]
|
||||
name = name.toLowerCase()
|
||||
name = item.displayName.toLowerCase()
|
||||
nextScore = 1
|
||||
totalScore = 0
|
||||
|
||||
for nameIndex in [0...name.length] by 1
|
||||
if name.charAt(nameIndex) is hintLetter
|
||||
totalScore += nextScore
|
||||
nextScore += 1
|
||||
hintIndex += 1
|
||||
if hintIndex is @_hint.length
|
||||
item = @_modPack.findItem itemSlug
|
||||
return false unless item?
|
||||
return @_isAcceptable item
|
||||
hintLetter = @_hint[hintIndex]
|
||||
return 0 unless @_isAcceptable item
|
||||
break
|
||||
|
||||
return false
|
||||
hintLetter = @_hint[hintIndex]
|
||||
else
|
||||
nextScore = 1
|
||||
|
||||
return 0 if hintIndex < @_hint.length
|
||||
return totalScore
|
||||
|
||||
_refreshResults: ->
|
||||
oldResults = @_results
|
||||
newResults = {}
|
||||
scoredItems = []
|
||||
count = 0
|
||||
|
||||
logger.verbose => "Looking for items which match: #{@_hint}"
|
||||
if @_hint.length >= @_minHintLength
|
||||
@_modPack.eachMod (mod)=>
|
||||
for modId, mod of @_modPack.mods
|
||||
return if count >= @_maxResults
|
||||
return unless mod.enabled
|
||||
return unless mod.isEnabled
|
||||
|
||||
mod.eachName (name, itemSlug)=>
|
||||
return if count >= @_maxResults
|
||||
for itemId, item of mod.items
|
||||
score = @_computeScore item
|
||||
if score > 0
|
||||
scoredItems.push score:score, item:item
|
||||
|
||||
if @_isMatch name, itemSlug
|
||||
if itemSlug.isQualified
|
||||
newResults[itemSlug] = itemSlug
|
||||
count += 1
|
||||
scoredItems.sort (a, b)->
|
||||
if a.score isnt b.score
|
||||
return if a.score > b.score then -1 else +1
|
||||
|
||||
newResults = _.values(newResults)
|
||||
newResults.sort ItemSlug.compare
|
||||
nameA = a.item.displayName
|
||||
nameB = b.item.displayName
|
||||
if nameA.length isnt nameB.length
|
||||
return if nameA.length < nameB.length then -1 else +1
|
||||
|
||||
if nameA isnt nameB
|
||||
return if nameA < nameB then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
@_results = []
|
||||
for element in scoredItems
|
||||
@_results.push element.item
|
||||
break if @_results.length >= @_maxResults
|
||||
|
||||
@trigger c.event.change + ':results', this
|
||||
|
||||
@_results = newResults
|
||||
@trigger c.event.change + ':results', this, oldResults, newResults
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Crafting Guide - markdown_image.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
#
|
||||
# Crafting Guide - markdown_image_list.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
{Observable} = require("crafting-guide-common").util
|
||||
MarkdownImage = require './markdown_image'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class MarkdownImageList extends BaseModel
|
||||
module.exports = class MarkdownImageList extends Observable
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
attributes.imageBase ?= ''
|
||||
attributes.markdownText ?= null
|
||||
super attributes, options
|
||||
constructor: (options={})->
|
||||
super
|
||||
|
||||
@client = options.client
|
||||
@_images = {}
|
||||
@_images = []
|
||||
@muted =>
|
||||
@client = options.client
|
||||
@imageBase = options.imageBase
|
||||
@markdownText = options.markdownText
|
||||
|
||||
@on c.event.change + ':imageBase', =>
|
||||
for fileName, image of @_images
|
||||
image.path = @imageBase
|
||||
|
||||
@on c.event.change + ':markdownText', => @_analyzeMarkdownText()
|
||||
@_analyzeMarkdownText()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
@@ -46,22 +42,43 @@ module.exports = class MarkdownImageList extends BaseModel
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getAll: ->
|
||||
fileNames = (fileName for fileName, image of @_images).sort()
|
||||
result = []
|
||||
for fileName in fileNames
|
||||
result.push @_images[fileName]
|
||||
|
||||
return result
|
||||
|
||||
isValid: ->
|
||||
for fileName, image of @_images
|
||||
return false unless image.valid
|
||||
return true
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
all: {get:@prototype.getAll}
|
||||
valid: {get:@prototype.isValid}
|
||||
|
||||
all:
|
||||
get: ->
|
||||
fileNames = (fileName for fileName, image of @_images).sort()
|
||||
result = []
|
||||
for fileName in fileNames
|
||||
result.push @_images[fileName]
|
||||
|
||||
return result
|
||||
|
||||
client:
|
||||
get: -> return @_client
|
||||
set: (client)->
|
||||
if not client? then throw new Error "client is required"
|
||||
@_client = client
|
||||
|
||||
imageBase: ->
|
||||
get: -> return @_imageBase
|
||||
set: (imageBase)->
|
||||
@triggerPropertyChange "imageBase", @_imageBase, imageBase, ->
|
||||
@_imageBase = imageBase
|
||||
for fileName, image of @_images
|
||||
image.path = @imageBase
|
||||
|
||||
markdownText:
|
||||
get: -> return @_markdownText
|
||||
set: (markdownText)->
|
||||
@triggerPropertyChange "markdownText", @_markdownText, markdownText, ->
|
||||
@_markdownText = markdownText
|
||||
@_analyzeMarkdownText()
|
||||
|
||||
valid:
|
||||
get: ->
|
||||
for fileName, image of @_images
|
||||
return false unless image.valid
|
||||
return true
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
@@ -79,8 +96,8 @@ module.exports = class MarkdownImageList extends BaseModel
|
||||
image = @_images[fileName]
|
||||
if not image?
|
||||
image = new MarkdownImage {fileName:fileName, path:@imageBase}, {client:@client}
|
||||
@listenTo image, c.event.change, => @trigger c.event.change, this
|
||||
@trigger c.event.add, this, image
|
||||
image.on Observable::CHANGE, this, "_onImageChanged"
|
||||
@trigger Observable::ADD, image
|
||||
changed = true
|
||||
|
||||
newImages[fileName] = image
|
||||
@@ -92,4 +109,7 @@ module.exports = class MarkdownImageList extends BaseModel
|
||||
|
||||
if changed
|
||||
@_images = newImages
|
||||
@trigger c.event.change, this
|
||||
@trigger Observable::CHANGE
|
||||
|
||||
_onImageChanged: ->
|
||||
@trigger Observable::CHANGE
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Crafting Guide - markdown_image_list.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
@@ -16,7 +16,7 @@ list = null
|
||||
describe 'markdown_image_list.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
list = new MarkdownImageList {}, client:{fetchFile:sinon.stub().returns(w.promise(->))}
|
||||
list = new MarkdownImageList client:{fetchFile:sinon.stub().returns(w.promise(->))}
|
||||
|
||||
describe '_analyzeMarkdownText', ->
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#
|
||||
# Crafting Guide - multiblock_display.coffee
|
||||
#
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
{Inventory} = require("crafting-guide-common").models
|
||||
{Observable} = require("crafting-guide-common").util
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class MultiblockDisplay extends Observable
|
||||
|
||||
constructor: (recipe)->
|
||||
@_layerInventories = []
|
||||
@recipe = recipe
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
recipe:
|
||||
get: -> return @_recipe
|
||||
set: (recipe)->
|
||||
if not recipe? then throw new Error "recipe is required"
|
||||
if @_recipe? then throw new Error "recipe cannot be reassigned"
|
||||
@_recipe = recipe
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getInventory: (z)->
|
||||
if not z?
|
||||
return @_fullInventory ?= @_createFullInventory()
|
||||
else
|
||||
return @_layerInventories[z] ?= @_addLayerInventory z
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_addLayerInventory: (z, inventory=null)->
|
||||
inventory ?= new Inventory
|
||||
for x in [0..@recipe.width]
|
||||
for y in [0..@recipe.height]
|
||||
stack = @recipe.getInputAt x, y, z
|
||||
continue unless stack?
|
||||
inventory.add stack.item, stack.quantity
|
||||
return inventory
|
||||
|
||||
_createFullInventory: (inventory=null)->
|
||||
inventory ?= new Inventory()
|
||||
for z in [0..@recipe.depth]
|
||||
@_addLayerInventory z, inventory
|
||||
return inventory
|
||||
@@ -0,0 +1,134 @@
|
||||
#
|
||||
# Crafting Guide - recipe_display.coffee
|
||||
#
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
_ = require "../../../common/underscore"
|
||||
c = require "../../../common/constants"
|
||||
{Inventory} = require("crafting-guide-common").models
|
||||
{Observable} = require("crafting-guide-common").util
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class RecipeDisplay extends Observable
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.recipe? then throw new Error "options.recipe is required"
|
||||
super
|
||||
|
||||
@_layerInventories = []
|
||||
|
||||
@multiplier = options.multiplier or 1
|
||||
@recipe = options.recipe
|
||||
@showInputInventory = if options.showInputInventory then options.showInputInventory or false
|
||||
@showOutputInventory = if options.showOutputInventory then options.showOutputInventory or false
|
||||
@showOutputSlot = if options.showOutputSlot then options.showOutputSlot or false
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@::RECIPE_TYPE =
|
||||
CRAFTING_TABLE: "crafting_table"
|
||||
MULTIBLOCK: "multiblock"
|
||||
|
||||
@::ALL_RECIPE_TYPES = (value for key, value of @::RECIPE_TYPE)
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
depth:
|
||||
get: -> return @recipe.depth
|
||||
set: -> throw new Error "depth cannot be assigned"
|
||||
|
||||
height:
|
||||
get: -> return @recipe.height
|
||||
set: -> throw new Error "height cannot be assigned"
|
||||
|
||||
multiplier:
|
||||
get: -> return @_multiplier
|
||||
set: (multiplier)->
|
||||
if not _.isNumber(multiplier) then multiplier = 1
|
||||
@triggerPropertyChange "multiplier", @_multiplier, multiplier
|
||||
|
||||
recipe:
|
||||
get: -> return @_recipe
|
||||
set: (recipe)->
|
||||
return if recipe is @_recipe
|
||||
if not recipe? then throw new Error "recipe is required"
|
||||
if @_recipe? then throw new Error "recipe cannot be reassigned"
|
||||
@_recipe = recipe
|
||||
@_computeType()
|
||||
|
||||
showInputInventory:
|
||||
get: -> return @_showInputInventory
|
||||
set: (showInputInventory)->
|
||||
@triggerPropertyChange "showInputInventory", @_showInputInventory, !!showInputInventory
|
||||
|
||||
showOutputInventory:
|
||||
get: -> return @_showOutputInventory
|
||||
set: (showOutputInventory)->
|
||||
@triggerPropertyChange "showOutputInventory", @_showOutputInventory, !!showOutputInventory
|
||||
|
||||
showOutputSlot:
|
||||
get: -> return @_showOutputSlot
|
||||
set: (showOutputSlot)->
|
||||
@triggerPropertyChange "showOutputSlot", @_showOutputSlot, !!showOutputSlot
|
||||
|
||||
tools:
|
||||
get: -> return @recipe.tools
|
||||
set: -> throw new Error "tools cannot be assigned"
|
||||
|
||||
type:
|
||||
get: -> return @_type
|
||||
set: -> throw new Error "type cannot be assigned"
|
||||
|
||||
width:
|
||||
get: -> return @recipe.width
|
||||
set: -> throw new Error "width cannot be assigned"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getInputAt: (x, y, z)->
|
||||
return @recipe.getInputAt x, y, z
|
||||
|
||||
getInventory: (z)->
|
||||
if not z?
|
||||
return @_fullInventory ?= @_createFullInventory()
|
||||
else
|
||||
return @_layerInventories[z] ?= @_addLayerInventory z
|
||||
|
||||
getOutputInventory: ->
|
||||
if not @_outputInventory?
|
||||
@_outputInventory = new Inventory
|
||||
for stack in @recipe.allProducts
|
||||
@_outputInventory.add stack.item, stack.quantity
|
||||
|
||||
return @_outputInventory
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_addLayerInventory: (z, inventory=null)->
|
||||
inventory ?= new Inventory
|
||||
for x in [0..@recipe.width]
|
||||
for y in [0..@recipe.height]
|
||||
stack = @recipe.getInputAt x, y, z
|
||||
continue unless stack?
|
||||
inventory.add stack.item, stack.quantity * @multiplier
|
||||
return inventory
|
||||
|
||||
_computeType: ->
|
||||
if not @recipe?
|
||||
type = null
|
||||
else if @recipe.depth > 1
|
||||
type = @RECIPE_TYPE.MULTIBLOCK
|
||||
else
|
||||
type = @RECIPE_TYPE.CRAFTING_TABLE
|
||||
@triggerPropertyChange "type", @_type, type
|
||||
|
||||
_createFullInventory: (inventory=null)->
|
||||
inventory ?= new Inventory()
|
||||
for z in [0..@recipe.depth]
|
||||
@_addLayerInventory z, inventory
|
||||
return inventory
|
||||
@@ -1,33 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - tutorial.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Tutorial extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.name?.length > 0 then throw new Error "attributes.name cannot be empty"
|
||||
attributes.modSlug ?= null
|
||||
attributes.officialUrl ?= null
|
||||
attributes.sections ?= []
|
||||
attributes.slug ?= _.slugify attributes.name
|
||||
attributes.videos ?= []
|
||||
super attributes, options
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
TutorialParser = require '../parsing/tutorial_parser' # to avoid require cycles
|
||||
@_parser ?= new TutorialParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.tutorialData modSlug:@modSlug, tutorialSlug:@slug
|
||||
@@ -1,11 +1,12 @@
|
||||
#
|
||||
# Crafting Guide - base_controller.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
templates = require './templates'
|
||||
{Observable} = require("crafting-guide-common").util
|
||||
templates = require './templates'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -49,15 +50,26 @@ module.exports = class BaseController extends Backbone.View
|
||||
@off()
|
||||
|
||||
routeLinkClick: (event)->
|
||||
event.preventDefault()
|
||||
href = $(event.target).attr 'href'
|
||||
href ?= $(event.currentTarget).attr 'href'
|
||||
logger.info "Re-routing link to internal navigation: #{href}"
|
||||
$link = $(event.target)
|
||||
if not $link.attr('href')?
|
||||
$link = $(event.currentTarget)
|
||||
|
||||
href = $link.attr 'href'
|
||||
target = $link.attr 'target'
|
||||
|
||||
logger.info -> "Re-routing link to internal navigation: #{href}"
|
||||
|
||||
if href? and href.match /^http/
|
||||
window.location.href = href
|
||||
tracker.trackEvent c.tracking.category.navigate, 'external-link', href
|
||||
.then -> window.location.href = href
|
||||
return false
|
||||
else if target?
|
||||
tracker.trackEvent c.tracking.category.navigate, 'external-link', href
|
||||
return true
|
||||
else
|
||||
tracker.trackEvent c.tracking.category.navigate, 'internal-link', href
|
||||
@router.navigate href, trigger:true
|
||||
return false
|
||||
|
||||
show: ($el)->
|
||||
$el ?= @$el
|
||||
@@ -73,9 +85,6 @@ module.exports = class BaseController extends Backbone.View
|
||||
onDidModelChange: ->
|
||||
@tryRefresh()
|
||||
|
||||
onDidModelSync: ->
|
||||
@tryRefresh()
|
||||
|
||||
onWillRender: -> # do nothing
|
||||
|
||||
onDidRender: -> # do nothing
|
||||
@@ -85,11 +94,12 @@ module.exports = class BaseController extends Backbone.View
|
||||
onDidShow: -> # do nothing
|
||||
|
||||
onWillChangeModel: (oldModel, newModel)->
|
||||
if oldModel?.on?
|
||||
@stopListening oldModel
|
||||
if newModel?.on?
|
||||
@listenTo newModel, 'sync', (e)=> @onDidModelSync e
|
||||
@listenTo newModel, 'change', (e)=> @onDidModelChange e
|
||||
if oldModel?.isObservable
|
||||
oldModel.off target:this
|
||||
|
||||
if newModel?.isObservable
|
||||
newModel.on Observable::ANY, this, "onDidModelChange"
|
||||
|
||||
return newModel
|
||||
|
||||
# Property Methods #############################################################################
|
||||
@@ -102,7 +112,7 @@ module.exports = class BaseController extends Backbone.View
|
||||
|
||||
newModel = @onWillChangeModel @_model, newModel
|
||||
@_model = newModel
|
||||
@tryRefresh()
|
||||
@onDidModelChange()
|
||||
|
||||
rendered:
|
||||
get: -> @_rendered
|
||||
@@ -136,9 +146,13 @@ module.exports = class BaseController extends Backbone.View
|
||||
|
||||
@$el.append $renderedEl.children()
|
||||
@$el.addClass $renderedEl.attr 'class'
|
||||
@$el.data $renderedEl.data()
|
||||
@delegateEvents()
|
||||
|
||||
elementData = $renderedEl.data()
|
||||
elementData.controller = this
|
||||
elementData.model = data
|
||||
@$el.data elementData
|
||||
|
||||
@delegateEvents()
|
||||
@_rendered = true
|
||||
@onDidRender()
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//-
|
||||
//- Crafting Guide - browse_page.jade
|
||||
//-
|
||||
//- Copyright © 2014-2016 by Redwood Labs
|
||||
//- Copyright © 2014-2017 by Redwood Labs
|
||||
//- All rights reserved.
|
||||
//-
|
||||
|
||||
.content.view__browse_page
|
||||
.left
|
||||
.view__adsense
|
||||
include ../common/youtube/youtube.jade
|
||||
|
||||
.right
|
||||
.tile_container
|
||||
section.view__mod_ballot
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// Crafting Guide - browse_page.scss
|
||||
//
|
||||
// Copyright © 2014-2016 by Redwood Labs
|
||||
// Copyright © 2014-2017 by Redwood Labs
|
||||
// All rights reserved.
|
||||
//
|
||||
|
||||
@@ -10,14 +10,37 @@
|
||||
|
||||
.right {
|
||||
.tile_container {
|
||||
display : flex;
|
||||
flex-wrap : wrap;
|
||||
justify-content : space-between;
|
||||
margin-bottom : $size-margin-large;
|
||||
|
||||
display : flex;
|
||||
flex-wrap : wrap;
|
||||
margin-right : -12px;
|
||||
|
||||
.view__mod_tile {
|
||||
max-width : calc(50% - 12px); // $size-margin-large / 2
|
||||
flex : 1 1 calc(50% - 12px); // $size-margin-large / 2
|
||||
margin-right : $size-margin-medium;
|
||||
margin-bottom : $size-margin-large;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include screen-size(tablet) {
|
||||
.view__browse_page {
|
||||
.right {
|
||||
.tile_container {
|
||||
margin-right : 0;
|
||||
|
||||
flex-direction : column;
|
||||
flex-wrap : nowrap;
|
||||
align-items : stretch;
|
||||
|
||||
.view__mod_tile {
|
||||
flex : 0 0 auto;
|
||||
margin-right : 0;
|
||||
width : 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,45 @@
|
||||
#
|
||||
# Crafting Guide - browse_page_controller.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
AdsenseController = require '../common/adsense/adsense_controller'
|
||||
ModTileController = require './mod_tile/mod_tile_controller'
|
||||
PageController = require '../page_controller'
|
||||
{CraftingGuideClient} = require('crafting-guide-common').api
|
||||
ModBallotController = require './mod_ballot/mod_ballot_controller'
|
||||
ModTileController = require './mod_tile/mod_tile_controller'
|
||||
PageController = require '../page_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BrowsePageController extends PageController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modPack then throw new Error 'options.modPack is required'
|
||||
if not options.client then throw new Error 'options.client is required'
|
||||
if not options.imageLoader then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack then throw new Error 'options.modPack is required'
|
||||
|
||||
options.templateName = 'browse_page'
|
||||
super options
|
||||
|
||||
@_client = options.client
|
||||
@_imageLoader = options.imageLoader
|
||||
@_modPack = options.modPack
|
||||
@_tileControllers = []
|
||||
|
||||
@_client.on c.event.change, => @tryRefresh()
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
user:
|
||||
get: -> @_user
|
||||
|
||||
set: (newUser)->
|
||||
@_user = newUser
|
||||
@tryRefresh()
|
||||
|
||||
# PageController Overrides #####################################################################
|
||||
|
||||
getMetaDescription: ->
|
||||
@@ -32,12 +51,17 @@ module.exports = class BrowsePageController extends PageController
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@_adsenseController = @addChild AdsenseController, '.view__adsense', model:'skyscraper'
|
||||
@_modBallotController = @addChild ModBallotController, '.view__mod_ballot',
|
||||
client: @_client
|
||||
imageLoader: @_imageLoader
|
||||
user: @_user
|
||||
|
||||
@$tileContainer = @$('.tile_container')
|
||||
|
||||
refresh: ->
|
||||
@_modBallotController.user = @user
|
||||
@_refreshModTiles()
|
||||
@_refreshBallot()
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
@@ -48,9 +72,15 @@ module.exports = class BrowsePageController extends PageController
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshBallot: ->
|
||||
if @_client.status is CraftingGuideClient.Status.Down
|
||||
@_modBallotController.hide()
|
||||
else
|
||||
@_modBallotController.show()
|
||||
|
||||
_refreshModTiles: ->
|
||||
index = 0
|
||||
mods = @_modPack.getAllMods()
|
||||
mods = (mod for modId, mod of @_modPack.mods)
|
||||
for mod in mods
|
||||
controller = @_tileControllers[index]
|
||||
if not controller
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//-
|
||||
//- Crafting Guide - mod_ballot.jade
|
||||
//-
|
||||
//- Copyright © 2014-2017 by Redwood Labs
|
||||
//- All rights reserved.
|
||||
//-
|
||||
|
||||
.view__mod_ballot
|
||||
.status.initial
|
||||
h2 Are we missing a mod you like?<br>Vote for your favorites!
|
||||
.button.vote-now: .bezel: p Vote Now!
|
||||
|
||||
.status.loading
|
||||
img(src="/images/wait.gif")
|
||||
.status-message
|
||||
|
||||
.status.loaded
|
||||
h2 Are we missing a mod you like?<br>Vote for your favorites!
|
||||
.instructions.
|
||||
You can cast up to 3 votes. Change them any time. You get a vote back when a mod is added.
|
||||
.instructions.remaining.
|
||||
You have <span class="remaining-count">0</span> votes remaining.
|
||||
|
||||
.ballot-list
|
||||
|
||||
.view__suggest_mod_panel
|
||||
|
||||
.status.error
|
||||
.status-message
|
||||
.button.reload: .bezel: p Try Again
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// Crafting Guide - mod_ballot.scss
|
||||
//
|
||||
// Copyright © 2014-2017 by Redwood Labs
|
||||
// All rights reserved.
|
||||
//
|
||||
|
||||
$ballot-move-duration: 0.2s;
|
||||
|
||||
.view__mod_ballot {
|
||||
@include floating-panel;
|
||||
|
||||
display : flex;
|
||||
padding : $size-margin-medium;
|
||||
|
||||
.status {
|
||||
display : flex;
|
||||
flex-direction : column;
|
||||
|
||||
h2 {
|
||||
margin-bottom: $size-margin-large;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
margin-bottom: $size-margin-large;
|
||||
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-medium;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.initial {
|
||||
padding: 0 15%;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
margin-top: $size-margin-large;
|
||||
margin-bottom: $size-margin-medium;
|
||||
}
|
||||
}
|
||||
|
||||
&.loaded {
|
||||
padding: 0 15%;
|
||||
|
||||
& > .instructions {
|
||||
margin-bottom: $size-margin-small;
|
||||
|
||||
font-family: $font-family-text;
|
||||
font-size: $font-size-medium;
|
||||
}
|
||||
|
||||
.remaining {
|
||||
margin-top: $size-margin-medium;
|
||||
font-size: $font-size-large;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ballot-list {
|
||||
position: relative;
|
||||
margin-top: $size-margin-large;
|
||||
|
||||
.view__mod_ballot_line {
|
||||
position: absolute;
|
||||
margin-bottom: $size-margin-small;
|
||||
transition: top $ballot-move-duration ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.view__suggest_mod_panel {
|
||||
margin-top: $size-margin-xlarge;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.button {
|
||||
width: $size-minecraft-block * 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include screen-size(mobile) {
|
||||
.view__mod_ballot {
|
||||
.status {
|
||||
&.loaded {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
#
|
||||
# Crafting Guide - mod_ballot_controller.coffee
|
||||
#
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseController = require '../../base_controller'
|
||||
ModBallotLineController = require './mod_ballot_line/mod_ballot_line_controller'
|
||||
SuggestModPanelController = require './suggest_mod_panel/suggest_mod_panel_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModBallotController extends BaseController
|
||||
|
||||
@::LOAD_ERROR = 'Sorry! Something went wrong loading the mod list. Please try again.'
|
||||
|
||||
@::MAX_VOTES = 3
|
||||
|
||||
@::STATUS =
|
||||
ERROR: 'error'
|
||||
INITIAL: 'initial'
|
||||
LOADED: 'loaded'
|
||||
LOADING: 'loading'
|
||||
LOGGED_OUT: 'logged-out'
|
||||
|
||||
@::VOTE_ERROR = 'Sorry! Something went wrong casting your vote. Please try again.'
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.client then throw new Error 'options.client is required'
|
||||
if not options.imageLoader then throw new Error 'options.imageLoader is required'
|
||||
|
||||
options.model = ballot:null, votes:null
|
||||
options.templateName = 'browse_page/mod_ballot'
|
||||
super options
|
||||
|
||||
@_client = options.client
|
||||
@_imageLoader = options.imageLoader
|
||||
@_lineControllers = {}
|
||||
@_status = @STATUS.INITIAL
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
isVotingAllowed:
|
||||
get: ->
|
||||
voteCount = @model.votes?.length or 0
|
||||
return voteCount < @MAX_VOTES
|
||||
|
||||
user:
|
||||
get: -> @_user
|
||||
|
||||
set: (newUser)->
|
||||
@_user = newUser
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onReloadClicked: ->
|
||||
tracker.trackEvent c.tracking.category.modVote, 'reload'
|
||||
@_reloadData()
|
||||
return false
|
||||
|
||||
onVoteButtonClicked: (controller)->
|
||||
modId = controller.model.ballotLine.modId
|
||||
|
||||
if controller.model.vote?
|
||||
tracker.trackEvent c.tracking.category.modVote, 'cancel-vote', @_findModName modId
|
||||
@_cancelVote modId
|
||||
else
|
||||
tracker.trackEvent c.tracking.category.modVote, 'cast-vote', @_findModName modId
|
||||
@_castVote modId
|
||||
|
||||
onVoteNowClicked: ->
|
||||
tracker.trackEvent c.tracking.category.modVote, 'vote-now'
|
||||
|
||||
if not @user?
|
||||
global.site.login()
|
||||
else
|
||||
@_reloadData()
|
||||
|
||||
return false
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@_suggestPanelController = @addChild SuggestModPanelController, '.view__suggest_mod_panel'
|
||||
|
||||
@$ballotList = @$('.ballot-list')
|
||||
@$remainingCount = @$('.remaining-count')
|
||||
@$remainingMessage = @$('.remaining')
|
||||
@$statusMessage = @$('.status-message')
|
||||
@$statusSections = @$('.status')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@_refreshLines()
|
||||
@_refreshRemaining()
|
||||
@_refreshStatus()
|
||||
super
|
||||
|
||||
# Backbone.View Methods ########################################################################
|
||||
|
||||
events: ->
|
||||
_.extend super,
|
||||
'click .button.reload': 'onReloadClicked'
|
||||
'click .button.vote-now': 'onVoteNowClicked'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_cancelVote: (modId)->
|
||||
controller = @_lineControllers[modId]
|
||||
vote = @_findVote modId
|
||||
|
||||
controller.isWaiting = true
|
||||
controller.tryRefresh()
|
||||
|
||||
@_client.cancelVote modVoteId:vote.id
|
||||
.then =>
|
||||
@model.votes = _(@model.votes).without vote
|
||||
controller.model.vote = null
|
||||
controller.model.ballotLine.voteCount -= 1
|
||||
.catch =>
|
||||
@_changeStatus @STATUS.ERROR, @VOTE_ERROR
|
||||
.finally =>
|
||||
controller.isWaiting = false
|
||||
@tryRefresh()
|
||||
|
||||
_castVote: (modId)->
|
||||
controller = @_lineControllers[modId]
|
||||
vote = @_findVote modId
|
||||
|
||||
controller.isWaiting = true
|
||||
controller.tryRefresh()
|
||||
|
||||
@_client.castVote modId:modId, userId:@user.id
|
||||
.then (response)=>
|
||||
vote = response.json
|
||||
@model.votes.push vote
|
||||
controller.model.vote = vote
|
||||
controller.model.ballotLine.voteCount += 1
|
||||
.catch (error)=>
|
||||
logger.error -> "vote could not be cast: #{error}"
|
||||
@_changeStatus @STATUS.ERROR, @VOTE_ERROR
|
||||
.finally =>
|
||||
controller.isWaiting = false
|
||||
@tryRefresh()
|
||||
|
||||
_computeLines: ->
|
||||
return [] unless @model.ballot?.lines?
|
||||
|
||||
@model.ballot.lines.sort (a, b)->
|
||||
if a.voteCount != b.voteCount
|
||||
return if a.voteCount > b.voteCount then -1 else +1
|
||||
if a.name != b.name
|
||||
return if a.name < b.name then -1 else +1
|
||||
return 0
|
||||
|
||||
return @model.ballot.lines
|
||||
|
||||
_changeStatus: (status, message='')->
|
||||
@_status = status
|
||||
@_message = message
|
||||
|
||||
_findVote: (modId)->
|
||||
return null unless @model.votes?
|
||||
|
||||
for vote in @model.votes
|
||||
return vote if vote.modId is modId
|
||||
return null
|
||||
|
||||
_findModName: (modId)->
|
||||
return '' unless @model.ballot?
|
||||
|
||||
for line in @model.ballot.lines
|
||||
return line.name if line.modId is modId
|
||||
return ''
|
||||
|
||||
_refreshLines: ->
|
||||
extraControllers = _.clone @_lineControllers
|
||||
lines = @_computeLines()
|
||||
|
||||
for line, index in lines
|
||||
controller = @_lineControllers[line.modId]
|
||||
delete extraControllers[line.modId]
|
||||
|
||||
model = {ballotLine:line, vote:@_findVote(line.modId)}
|
||||
if not controller?
|
||||
controller = new ModBallotLineController
|
||||
imageLoader: @_imageLoader
|
||||
model: model
|
||||
onVoteButtonClicked: (controller)=> @onVoteButtonClicked controller
|
||||
controller.render()
|
||||
@$ballotList.append controller.$el
|
||||
@_lineControllers[line.modId] = controller
|
||||
else
|
||||
controller.model = model
|
||||
|
||||
controller.isVotingAllowed = @isVotingAllowed
|
||||
|
||||
for modId, controller of extraControllers
|
||||
delete @_lineControllers[modId]
|
||||
controller.remove()
|
||||
|
||||
_.delay (=> @_refreshLineLayout()), 100
|
||||
|
||||
_refreshLineLayout: ->
|
||||
lines = @_computeLines()
|
||||
offset = @$ballotList.offset()
|
||||
|
||||
for line in lines
|
||||
controller = @_lineControllers[line.modId]
|
||||
continue unless controller?
|
||||
|
||||
if controller.$el.height() is 0
|
||||
# keep trying until the elements have been created and laid out by the browser
|
||||
_.delay (=> @_refreshLineLayout()), 100
|
||||
return
|
||||
|
||||
controller.$el.offset offset
|
||||
offset.top += controller.$el.outerHeight(true)
|
||||
|
||||
@$ballotList.height offset.top - @$ballotList.offset().top
|
||||
|
||||
_refreshRemaining: ->
|
||||
if @model.votes?
|
||||
@$remainingCount.html @MAX_VOTES - @model.votes.length
|
||||
@show @$remainingMessage
|
||||
else
|
||||
@hide @$remainingMessage
|
||||
|
||||
_refreshStatus: ->
|
||||
@hide @$('.status')
|
||||
@show @$(".status.#{@_status}")
|
||||
@$statusMessage.html @_message
|
||||
|
||||
_reloadBallot: ->
|
||||
return @_loadingBallot if @_loadingBallot?
|
||||
|
||||
@model.ballot = null
|
||||
@_changeStatus @STATUS.LOADING, "Loading ballot data..."
|
||||
@tryRefresh()
|
||||
|
||||
@_loadingBallot = @_client.getModBallot()
|
||||
.then (response)=>
|
||||
@model.ballot = response.json
|
||||
if not @user? or @model.votes? then @_changeStatus @STATUS.LOADED
|
||||
@tryRefresh()
|
||||
.catch (error)=>
|
||||
@_changeStatus @STATUS.ERROR, @LOAD_ERROR
|
||||
.finally =>
|
||||
@_loadingBallot = null
|
||||
|
||||
_reloadData: ->
|
||||
@_reloadVotes()
|
||||
@_reloadBallot()
|
||||
|
||||
_reloadVotes: ->
|
||||
return @_loadingVotes if @_loadingVotes?
|
||||
return unless @user?
|
||||
|
||||
@model.votes = null
|
||||
@_changeStatus @STATUS.LOADING, "Loading ballot data..."
|
||||
@tryRefresh()
|
||||
|
||||
@_loadingVotes = @_client.getModVotes()
|
||||
.then (response)=>
|
||||
@model.votes = response.json
|
||||
if @model.ballot? then @_changeStatus @STATUS.LOADED
|
||||
@tryRefresh()
|
||||
.catch (error)=>
|
||||
@_changeStatus @STATUS.ERROR, @LOAD_ERROR
|
||||
.finally =>
|
||||
@_loadingVotes = null
|
||||
@@ -0,0 +1,14 @@
|
||||
//-
|
||||
//- Crafting Guide - mod_ballot_line.jade
|
||||
//-
|
||||
//- Copyright © 2014-2017 by Redwood Labs
|
||||
//- All rights reserved.
|
||||
//-
|
||||
|
||||
.view__mod_ballot_line
|
||||
.vote-count
|
||||
img.icon(src='/images/unknown.png')
|
||||
.name
|
||||
.button.vote: .bezel: p ✓
|
||||
.wait
|
||||
img(src='/images/wait-small.gif')
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Crafting Guide - mod_ballot_line.scss
|
||||
//
|
||||
// Copyright © 2014-2017 by Redwood Labs
|
||||
// All rights reserved.
|
||||
//
|
||||
|
||||
.view__mod_ballot_line {
|
||||
width: 100%;
|
||||
|
||||
display : flex;
|
||||
align-items : center;
|
||||
|
||||
.vote-count {
|
||||
flex : 0 0 $size-minecraft-block;
|
||||
margin-right : $size-margin-medium;
|
||||
|
||||
font-family : $font-family-text;
|
||||
font-size : $font-size-large;
|
||||
text-align : right;
|
||||
}
|
||||
|
||||
& > img {
|
||||
margin-right : $size-margin-medium;
|
||||
|
||||
flex : 0 0 auto;
|
||||
height : $size-minecraft-block;
|
||||
width : $size-minecraft-block;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-right : $size-margin-medium;
|
||||
|
||||
flex : 1 1 100%;
|
||||
font-family : $font-family-text;
|
||||
font-size : $font-size-large;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex : 0 0 auto;
|
||||
width : $size-minecraft-block * 1.5;
|
||||
}
|
||||
|
||||
.wait {
|
||||
flex : 0 0 auto;
|
||||
height : $size-button-height;
|
||||
width : $size-minecraft-block * 1.5;
|
||||
|
||||
display : flex;
|
||||
align-items : center;
|
||||
justify-content : center;
|
||||
}
|
||||
}
|
||||
|
||||
@include screen-size(mobile) {
|
||||
.view__mod_ballot_line {
|
||||
.name {
|
||||
overflow: hidden;
|
||||
font-size: $font-size-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
#
|
||||
# Crafting Guide - mod_ballot_line_controller.coffee
|
||||
#
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseController = require '../../../base_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModBallotLineController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader then throw new Error 'options.imageLoader is required'
|
||||
options.templateName = 'browse_page/mod_ballot/mod_ballot_line'
|
||||
super options
|
||||
|
||||
@_imageLoader = options.imageLoader
|
||||
@_onVoteButtonClicked = options.onVoteButtonClicked or (controller)-> # do nothing
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onButtonClicked: ->
|
||||
return if @$button.hasClass 'disabled'
|
||||
|
||||
@_onVoteButtonClicked this
|
||||
return false
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
isVotingAllowed:
|
||||
get: -> @_isVotingAllowed
|
||||
|
||||
set: (isVotingAllowed)->
|
||||
@_isVotingAllowed = !! isVotingAllowed
|
||||
@tryRefresh()
|
||||
|
||||
isWaiting:
|
||||
get: -> @_isWaiting
|
||||
|
||||
set: (isWaiting)->
|
||||
@_isWaiting = !! isWaiting
|
||||
@tryRefresh()
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$button = @$('.button')
|
||||
@$image = @$('img.icon')
|
||||
@$name = @$('.name')
|
||||
@$voteCount = @$('.vote-count')
|
||||
@$wait = @$('.wait')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@$name.html @model.ballotLine?.name or ''
|
||||
@$voteCount.html @model.ballotLine?.voteCount or ''
|
||||
@_refreshIcon()
|
||||
@_refreshVoteButton()
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click .button': 'onButtonClicked'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshIcon: ->
|
||||
if @model.ballotLine?
|
||||
modSlug = _.slugify(@model.ballotLine.name)
|
||||
url = c.url.modIcon modSlug:modSlug
|
||||
@_imageLoader.load url, @$image
|
||||
else
|
||||
@$image.attr 'src', '/images/unknown.png'
|
||||
|
||||
_refreshVoteButton: ->
|
||||
if @isWaiting
|
||||
@$wait.removeClass 'hidden'
|
||||
@$button.addClass 'hidden'
|
||||
else
|
||||
@$wait.addClass 'hidden'
|
||||
@$button.removeClass 'hidden'
|
||||
|
||||
if @model.vote?
|
||||
@$button.addClass 'active'
|
||||
else
|
||||
@$button.removeClass 'active'
|
||||
|
||||
if @isVotingAllowed
|
||||
@$button.removeClass 'disabled'
|
||||
else
|
||||
@$button.addClass 'disabled'
|
||||
@@ -0,0 +1,25 @@
|
||||
//-
|
||||
//- Crafting Guide - suggest_mod_panel.jade
|
||||
//-
|
||||
//- Copyright © 2014-2017 by Redwood Labs
|
||||
//- All rights reserved.
|
||||
//-
|
||||
|
||||
.view__suggest_mod_panel
|
||||
.initial
|
||||
.instructions Still don't see the mod you want?<br>Tell us about it!
|
||||
|
||||
.entry-form
|
||||
input.name(placeholder="enter the mod name")
|
||||
input.url(placeholder="enter the website")
|
||||
.buttons
|
||||
.button: .bezel: p send
|
||||
|
||||
.sending
|
||||
.instructions Sending...
|
||||
|
||||
.error
|
||||
.instructions Something went wrong! Please try again.
|
||||
|
||||
.success
|
||||
.instructions Thanks! We'll add that one to the list.
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// Crafting Guide - suggest_mod_panel.scss
|
||||
//
|
||||
// Copyright © 2014-2017 by Redwood Labs
|
||||
// All rights reserved.
|
||||
//
|
||||
|
||||
.view__suggest_mod_panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.instructions {
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-xlarge;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.initial {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.instructions {
|
||||
margin-bottom: $size-margin-large;
|
||||
}
|
||||
|
||||
.entry-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
input {
|
||||
margin-bottom: $size-margin-medium;
|
||||
|
||||
font-family: $font-family-input;
|
||||
font-size: $font-size-large;
|
||||
|
||||
padding: $size-margin-xsmall $size-margin-small;
|
||||
|
||||
&.invalid {
|
||||
border-color: $color-error;
|
||||
color: $color-error;
|
||||
}
|
||||
|
||||
&.valid {
|
||||
border-color: $color-valid;
|
||||
color: $color-valid;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: $size-margin-large;
|
||||
|
||||
.button {
|
||||
flex: 0 0 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error, .sending, .success {
|
||||
margin: $size-margin-xlarge 0;
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
#
|
||||
# Crafting Guide - suggest_mod_panel_controller.coffee
|
||||
#
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseController = require '../../../base_controller'
|
||||
EmailClient = require '../../../../models/site/email_client'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class SuggestModPanelController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
options.templateName = 'browse_page/mod_ballot/suggest_mod_panel'
|
||||
super options
|
||||
|
||||
@_emailClient = new EmailClient
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
isFormComplete:
|
||||
get: ->
|
||||
return false unless @name.length > 0
|
||||
return false unless @url.length > 0
|
||||
return true
|
||||
|
||||
name:
|
||||
get: ->
|
||||
return '' unless @rendered
|
||||
return @$nameField.val()
|
||||
|
||||
url:
|
||||
get: ->
|
||||
return '' unless @rendered
|
||||
return @$urlField.val()
|
||||
|
||||
user:
|
||||
get: -> @_user
|
||||
set: (user)-> @_user = user
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onButtonClicked: (event)->
|
||||
return false unless @isFormComplete
|
||||
|
||||
tracker.trackEvent c.tracking.category.feedback, 'suggest-mod', @name
|
||||
@_sendRequest()
|
||||
return false
|
||||
|
||||
onNameChanged: (event)->
|
||||
@refresh()
|
||||
return false
|
||||
|
||||
onUrlChanged: (event)->
|
||||
@refresh()
|
||||
return false
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$button = @$('.button')
|
||||
@$errorPanel = @$('.error')
|
||||
@$initialPanel = @$('.initial')
|
||||
@$nameField = @$('input.name')
|
||||
@$sendingPanel = @$('.sending')
|
||||
@$successPanel = @$('.success')
|
||||
@$urlField = @$('input.url')
|
||||
|
||||
@hide @$errorPanel
|
||||
@hide @$successPanel
|
||||
@hide @$sendingPanel
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@_refreshButtonState()
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'input .name': 'onNameChanged'
|
||||
'input .url': 'onUrlChanged'
|
||||
'click .button': 'onButtonClicked'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshButtonState: ->
|
||||
if @isFormComplete
|
||||
@$button.removeClass 'disabled'
|
||||
else
|
||||
@$button.addClass 'disabled'
|
||||
|
||||
_sendRequest: ->
|
||||
showPanel = ($selectedPanel)=>
|
||||
@hide $panel for $panel in [@$initialPanel, @$sendingPanel, @$successPanel, @$errorPanel]
|
||||
@show $selectedPanel
|
||||
|
||||
data =
|
||||
name: @name
|
||||
url: @url
|
||||
user: @user?.name
|
||||
email: @user?.email
|
||||
|
||||
showPanel @$sendingPanel
|
||||
w(@_emailClient.send 'mod-suggestion', name:@name, url:@url)
|
||||
.then =>
|
||||
showPanel @$successPanel
|
||||
@$urlField.val ""
|
||||
@$nameField.val ""
|
||||
@refresh()
|
||||
.catch => showPanel @$errorPanel
|
||||
.delay c.duration.slow * 2
|
||||
.then => showPanel @$initialPanel
|
||||
@@ -1,14 +1,17 @@
|
||||
//-
|
||||
//- Crafting Guide - mod_tile.jade
|
||||
//-
|
||||
//- Copyright © 2014-2016 by Redwood Labs
|
||||
//- Copyright © 2014-2017 by Redwood Labs
|
||||
//- All rights reserved.
|
||||
//-
|
||||
|
||||
.view__mod_tile
|
||||
a
|
||||
.left
|
||||
img
|
||||
.right
|
||||
p.title
|
||||
p.description
|
||||
.tile
|
||||
a
|
||||
.left
|
||||
img
|
||||
.right
|
||||
p.title
|
||||
p.description
|
||||
|
||||
.view__mod_version_selector
|
||||
@@ -1,54 +1,60 @@
|
||||
//
|
||||
// Crafting Guide - mod_tile.scss
|
||||
//
|
||||
// Copyright © 2014-2016 by Redwood Labs
|
||||
// Copyright © 2014-2017 by Redwood Labs
|
||||
// All rights reserved.
|
||||
//
|
||||
|
||||
.view__mod_tile {
|
||||
min-width : $size-minecraft-block * 9;
|
||||
display: flex;
|
||||
|
||||
display : flex;
|
||||
justify-content : stretch;
|
||||
align-items : stretch;
|
||||
|
||||
a {
|
||||
max-width: 100%;
|
||||
.tile {
|
||||
flex: 1 1 auto;
|
||||
|
||||
@include floating-panel;
|
||||
border : $size-border-medium solid $color-background-panel;
|
||||
color : $color-text;
|
||||
text-decoration : none;
|
||||
min-width : $size-minecraft-block * 9;
|
||||
|
||||
display : flex;
|
||||
padding : $size-margin-medium;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > .left {
|
||||
flex: 0 0 $size-minecraft-block * 4;
|
||||
margin-right: $size-margin-medium;
|
||||
border : $size-border-medium solid $color-background-panel;
|
||||
|
||||
img {
|
||||
width: $size-minecraft-block * 4;
|
||||
max-height: $size-minecraft-block * 4;
|
||||
}
|
||||
}
|
||||
a {
|
||||
flex: 1 1 auto;
|
||||
color : $color-text;
|
||||
text-decoration : none;
|
||||
|
||||
& > .right {
|
||||
flex : 1 1 100%;
|
||||
display : flex;
|
||||
padding : $size-margin-medium;
|
||||
|
||||
overflow : hidden;
|
||||
& > .left {
|
||||
flex: 0 0 $size-minecraft-block * 4;
|
||||
margin-right: $size-margin-medium;
|
||||
|
||||
.title {
|
||||
margin-bottom : $size-margin-medium;
|
||||
|
||||
font-family : $font-family-header;
|
||||
font-size : $font-size-xlarge;
|
||||
img {
|
||||
width: $size-minecraft-block * 4;
|
||||
max-height: $size-minecraft-block * 4;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-family : $font-family-text;
|
||||
font-size : $font-size-medium;
|
||||
line-height : $font-size-medium * 1.5;
|
||||
& > .right {
|
||||
flex : 1 1 100%;
|
||||
|
||||
overflow : hidden;
|
||||
|
||||
.title {
|
||||
max-width: 0;
|
||||
margin-bottom : $size-margin-medium;
|
||||
|
||||
font-family : $font-family-header;
|
||||
font-size : $font-size-xlarge;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-family : $font-family-text;
|
||||
font-size : $font-size-medium;
|
||||
line-height : $font-size-medium * 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,13 +62,18 @@
|
||||
background: $color-blue-faint;
|
||||
border-color: $color-link-normal;
|
||||
|
||||
& > .right {
|
||||
& a > .right {
|
||||
.title {
|
||||
color: $color-link-normal;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view__mod_version_selector {
|
||||
margin: 0;
|
||||
padding: $size-margin-small $size-margin-medium;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@@ -86,3 +97,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include screen-size(mobile) {
|
||||
.view__mod_tile {
|
||||
min-width: 0;
|
||||
|
||||
a {
|
||||
& > .left {
|
||||
flex: 0 0 $size-minecraft-block * 2;
|
||||
|
||||
img {
|
||||
width: $size-minecraft-block * 2;
|
||||
max-height: $size-minecraft-block * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
#
|
||||
# Crafting Guide - mod_tile_controller.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseController = require '../../base_controller'
|
||||
BaseController = require '../../base_controller'
|
||||
ModVersionSelectorController = require '../../common/mod_version_selector/mod_version_selector_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModTileController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
options.tagName = 'section'
|
||||
options.templateName = 'browse_page/mod_tile'
|
||||
super options
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@modVersionSelector = @addChild ModVersionSelectorController, '.view__mod_version_selector', model:@model
|
||||
|
||||
@$link = @$('a')
|
||||
@$logoImage = @$('img')
|
||||
@$title = @$('p.title')
|
||||
@@ -25,12 +29,12 @@ module.exports = class ModTileController extends BaseController
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@$link.attr 'href', c.url.mod modSlug:@model.slug
|
||||
@$logoImage.attr 'src', c.url.modIcon modSlug:@model.slug
|
||||
@$title.text @model.name
|
||||
@$link.attr 'href', c.url.mod modId:@model.id
|
||||
@$logoImage.attr 'src', c.url.modIcon modId:@model.id
|
||||
@$title.text @model.displayName
|
||||
@$description.text @model.description
|
||||
|
||||
if @model.enabled
|
||||
if @model.isEnabled
|
||||
@$el.removeClass 'disabled'
|
||||
else
|
||||
@$el.addClass 'disabled'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//-
|
||||
//- Crafting Guide - adsense.jade
|
||||
//-
|
||||
//- Copyright © 2014-2016 by Redwood Labs
|
||||
//- Copyright © 2014-2017 by Redwood Labs
|
||||
//- All rights reserved.
|
||||
//-
|
||||
|
||||
|
||||
@@ -1,25 +1,69 @@
|
||||
//
|
||||
// Crafting Guide - adsense.scss
|
||||
//
|
||||
// Copyright © 2014-2016 by Redwood Labs
|
||||
// Copyright © 2014-2017 by Redwood Labs
|
||||
// All rights reserved.
|
||||
//
|
||||
|
||||
.view__adsense {
|
||||
.skyscraper {
|
||||
height : $size-skyscraper-height;
|
||||
width : $size-skyscraper-width;
|
||||
position : relative;
|
||||
left : 50%;
|
||||
margin : $size-margin-large 0;
|
||||
position : relative;
|
||||
transform : translate(-50%);
|
||||
|
||||
border : 0.1em solid $color-gray-faint;
|
||||
margin : $size-margin-large 0;
|
||||
ins {
|
||||
position: absolute; top: 0; right: 0; bottom: 0; left: 0;
|
||||
}
|
||||
|
||||
&.large-mobile-banner {
|
||||
height : $size-ad-large-mobile-banner-height;
|
||||
width : $size-ad-large-mobile-banner-width;
|
||||
|
||||
&.placeholder:before {
|
||||
font-size : $font-size-large;
|
||||
}
|
||||
}
|
||||
|
||||
&.leaderboard {
|
||||
height : $size-ad-leaderboard-height;
|
||||
width : $size-ad-leaderboard-width;
|
||||
|
||||
&.placeholder:before {
|
||||
font-size : $font-size-xxlarge;
|
||||
}
|
||||
}
|
||||
|
||||
&.skyscraper {
|
||||
height : $size-ad-skyscraper-height;
|
||||
width : $size-ad-skyscraper-width;
|
||||
|
||||
&.placeholder:before {
|
||||
transform : rotate(90deg);
|
||||
font-size : $font-size-xxlarge;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top : 0;
|
||||
}
|
||||
}
|
||||
|
||||
ins {
|
||||
position: absolute; top: 0; right: 0; bottom: 0; left: 0;
|
||||
&.placeholder {
|
||||
border : $size-border-small solid $color-gray-faint;
|
||||
|
||||
display : flex;
|
||||
align-items : center;
|
||||
justify-content : center;
|
||||
|
||||
&:before {
|
||||
color : $color-gray-faint;
|
||||
content : "GOOGLE ADSENSE";
|
||||
font-family : $font-family-control;
|
||||
letter-spacing : $size-margin-small;
|
||||
white-space : nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view__mod_tile + .view__adsense {
|
||||
margin-top : 0;
|
||||
}
|
||||
|
||||
@@ -1,67 +1,134 @@
|
||||
#
|
||||
# Crafting Guide - adsense_controller.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# Copyright © 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseController = require '../../base_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class AdsenseController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
options.templateName = 'common/adsense'
|
||||
super options
|
||||
module.exports = class AdsenseController
|
||||
|
||||
constructor: ->
|
||||
@_adsEnabled = global.env in c.productionEnvs
|
||||
@_adCount = 0
|
||||
@_adsPlaced = 0
|
||||
@_waiting = false
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
fillAdPositions: ->
|
||||
if @_adCount > 0
|
||||
return unless @$sidebar and @$mainBody
|
||||
return unless @_adCount < @_computeMaxAds()
|
||||
return unless @_adCount < c.adsense.slotIds.length
|
||||
adType = @_computeAdType()
|
||||
maxAds = c.adsense[adType].slotIds.length - @_adsPlaced
|
||||
return if maxAds is 0
|
||||
|
||||
$adContainer = $('<div class="skyscraper"></div>')
|
||||
@$el.append $adContainer
|
||||
if adType is 'skyscraper'
|
||||
placedNewAd = @_createSkyscraperPositions()
|
||||
else
|
||||
placedNewAd = @_createBannerPositions adType
|
||||
|
||||
if @_adsEnabled
|
||||
$ad = $('<ins class="adsbygoogle"></ins>')
|
||||
$ad.attr 'data-ad-client', c.adsense.clientId
|
||||
$ad.attr 'data-ad-slot', c.adsense.slotIds[@_adCount]
|
||||
$adContainer.append $ad
|
||||
@_loadAds()
|
||||
if @_adsPlaced < c.adsense[adType].slotIds.length
|
||||
duration = if placedNewAd then 0 else c.adsense.readinessCheckInterval
|
||||
_.delay (=> @fillAdPositions()), duration
|
||||
else
|
||||
logger.verbose => "All #{adType} ad positions filled"
|
||||
|
||||
@_adCount += 1
|
||||
_.defer => @fillAdPositions()
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$mainBody = $('.page > .content > .right')
|
||||
@$sidebar = $('.page > .content > .left')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
reset: ->
|
||||
@_adsPlaced = 0
|
||||
@_waitForPageReadiness()
|
||||
super
|
||||
|
||||
# Private Methods #############################################################################
|
||||
|
||||
_computeMaxAds: ->
|
||||
return null unless @$sidebar
|
||||
result = Math.floor @$sidebar.height() / (c.adsense.skyscraper.height + c.adsense.skyscraper.margin)
|
||||
_computeAdType: ->
|
||||
return c.adsense.adTypeMap[c.screen.type.compute()]
|
||||
|
||||
_computeAdCount: (adType)->
|
||||
result = c.adsense[adType].slotIds.length
|
||||
if c.screen.type.compute() is c.screen.type.desktop
|
||||
$sidebar = $('.page > .content > .left')
|
||||
return 0 unless $sidebar?.length
|
||||
|
||||
available = $sidebar.height() + c.adsense.skyscraper.margin
|
||||
available = Math.floor available / (c.adsense.skyscraper.height + c.adsense.skyscraper.margin)
|
||||
result = Math.min available, result
|
||||
|
||||
return result
|
||||
|
||||
_loadAds: ->
|
||||
_createSkyscraperPositions: ->
|
||||
$sidebar = $('.page > .content > .left')
|
||||
remaining = @_computeAdCount 'skyscraper'
|
||||
remaining -= @_adsPlaced
|
||||
placedNewAd = false
|
||||
|
||||
while remaining > 0
|
||||
$el = $('<div class="view__adsense"></div>')
|
||||
$el.addClass c.adsense.skyscraper.cssClass
|
||||
|
||||
if @_adsEnabled
|
||||
$ad = $('<ins class="adsbygoogle"></ins>')
|
||||
$ad.attr 'data-ad-client', c.adsense.clientId
|
||||
$ad.attr 'data-ad-slot', c.adsense.skyscraper.slotIds[@_adsPlaced]
|
||||
$el.append $ad
|
||||
else
|
||||
$el.addClass 'placeholder'
|
||||
|
||||
$sidebar.append $el
|
||||
placedNewAd = true
|
||||
@_adsPlaced += 1
|
||||
remaining -= 1
|
||||
logger.info => "Placed skyscraper ad ##{@_adsPlaced}"
|
||||
@_loadAd()
|
||||
|
||||
return placedNewAd
|
||||
|
||||
_createBannerPositions: (adType)->
|
||||
$pageContent = $('.page > .content > .right')
|
||||
remaining = @_computeAdCount adType
|
||||
remaining -= @_adsPlaced
|
||||
sectionIndex = 0
|
||||
placedNewAd = false
|
||||
|
||||
for sectionEl in $pageContent.find('section')
|
||||
break unless remaining > 0
|
||||
|
||||
$section = $(sectionEl)
|
||||
continue unless $section.is ':visible'
|
||||
|
||||
sectionIndex += 1
|
||||
continue if sectionIndex % 2 is 0
|
||||
continue if $section.next().hasClass 'view__adsense'
|
||||
|
||||
$el = $('<div class="view__adsense"></div>')
|
||||
$el.addClass c.adsense[adType].cssClass
|
||||
|
||||
if @_adsEnabled
|
||||
$ad = $('<ins class="adsbygoogle"></ins>')
|
||||
$ad.attr 'data-ad-client', c.adsense.clientId
|
||||
$ad.attr 'data-ad-slot', c.adsense[adType].slotIds[@_adsPlaced]
|
||||
$el.append $ad
|
||||
else
|
||||
$el.addClass 'placeholder'
|
||||
|
||||
$section.after $el
|
||||
placedNewAd = true
|
||||
@_adsPlaced += 1
|
||||
remaining -= 1
|
||||
logger.info => "Placed #{adType} ad ##{@_adsPlaced}"
|
||||
@_loadAd()
|
||||
|
||||
return placedNewAd
|
||||
|
||||
_isTooClose: ($priorEl, $el)->
|
||||
return false if c.screen.type.compute() is c.screen.type.desktop
|
||||
return false if not $priorEl?
|
||||
|
||||
priorBottom = $priorEl.position().top + $priorEl.height()
|
||||
currentTop = $el.position().top
|
||||
return priorBottom + c.adsense.minimumDistance > currentTop
|
||||
|
||||
_loadAd: ->
|
||||
return unless @_adsEnabled
|
||||
|
||||
logger.info "Loading #{@_adCount} Google AdSense ads"
|
||||
logger.info "Loading Google AdSense ads"
|
||||
try
|
||||
global.adsbygoogle ||= []
|
||||
global.adsbygoogle.push {}
|
||||
@@ -70,11 +137,12 @@ module.exports = class AdsenseController extends BaseController
|
||||
|
||||
_waitForPageReadiness: ->
|
||||
return if @_waiting
|
||||
adCount = @_computeAdCount @_computeAdType()
|
||||
|
||||
if @$sidebar.height() < c.adsense[@model].height / 2
|
||||
logger.verbose "Adsense is waiting for room to insert ads"
|
||||
if adCount is 0
|
||||
logger.trace "Adsense is waiting for room to insert ads"
|
||||
@_waiting = true
|
||||
_.delay (=> @_waiting = false; @_waitForPageReadiness()), c.duration.normal
|
||||
_.delay (=> @_waiting = false; @_waitForPageReadiness()), c.adsense.readinessCheckInterval
|
||||
else
|
||||
logger.verbose "Adsense is ready to fill ads"
|
||||
@fillAdPositions()
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//-
|
||||
//- Crafting Guide - amazon.jade
|
||||
//-
|
||||
//- Copyright © 2014-2017 by Redwood Labs
|
||||
//- All rights reserved.
|
||||
//-
|
||||
|
||||
#amzn_assoc_ad_div_adunit0_0
|
||||
script(src="//z-na.amazon-adsystem.com/widgets/onejs?MarketPlace=US&adInstanceId=357573bb-98d8-4294-8cdf-4361dbf11cee")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user