Author SHA1 Message Date
Andrew Miner 9ddbd5cda7 Add remaining commands 2016-04-15 21:22:10 -07:00
Andrew Miner 31f6e12844 wip 2016-04-13 22:31:11 -07:00
Andrew Miner 14278c81df Add first stage of a new single-file parser
This first stage consists of the general framework for the parser as
a whole, a lexical analyzers to converts text into commands, and the
semantic anlyzer to convert commands into a complete data structure
representing the cumulative effect of running the commands.
2016-04-10 13:37:17 -07:00
116 changed files with 2122 additions and 2306 deletions
+2 -7
View File
@@ -54,7 +54,7 @@ module.exports = (grunt)->
mode: 'gzip'
files: [
{expand: true, cwd:'./build/static', src:'**/*.css', dest:'./dist/'}
{expand: true, cwd:'./build/static', src:['**/*.html', '!**/index.html'], dest:'./dist/'}
{expand: true, cwd:'./build/static', src:'**/*.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/'}
@@ -77,10 +77,7 @@ module.exports = (grunt)->
]
build_to_dist:
files: [
expand: true
cwd: './build/static/'
src: ['**/*', '!**/*.map', '!**/*.js', '!index.html']
dest: './dist'
{expand:true, cwd:'./build/static/', src:['**/*', '!**/*.map', '!**/*.js'], dest:'./dist'}
]
common_source:
files: [
@@ -138,8 +135,6 @@ module.exports = (grunt)->
sass:
all:
options:
sourcemap: 'none'
files:
'./build/static/main.css': [ './build/imports.scss' ]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "crafting-guide",
"version": "3.2.4",
"version": "3.2.2",
"description": "A guide for building anything in Minecraft or its many mods",
"main": "./dist/index.js",
"repository": "http://github.com/andrewminer/crafting-guide",
Binary file not shown.
@@ -1,353 +0,0 @@
<?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>
@@ -1,18 +0,0 @@
//-
//- Crafting Guide - _google_sitelinks.jade
//-
//- Copyright © 2014-2016 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"
}
}
-11
View File
@@ -1,11 +0,0 @@
script.
! function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
p = /^http:/.test(d.location) ? 'http' : 'https';
if (!d.getElementById(id)) {
js = d.createElement(s);
js.id = id;
js.src = p + '://platform.twitter.com/widgets.js';
fjs.parentNode.insertBefore(js, fjs);
}
}(document, 'script', 'twitter-wjs');
+3 -44
View File
@@ -9,9 +9,6 @@
module.exports = class BaseModel extends Backbone.Model
@_loadingQueue = []
@_isDraining = false
constructor: (attributes={}, options={})->
options.logEvents ?= true
super attributes, options
@@ -22,11 +19,11 @@ module.exports = class BaseModel extends Backbone.Model
continue if name is 'id'
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
@fileCache = options.fileCache or null
@loading = null
@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 }
@@ -63,24 +60,13 @@ module.exports = class BaseModel extends Backbone.Model
@state = c.modelState.loading
@trigger c.event.request, this
loadFromServer = =>
w.promise (resolve, reject)=>
@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
if @fileCache?
@loading = @fileCache.loading.then =>
if @fileCache.hasFile url
return @_addToLoadingQueue @fileCache.getFile(url), 'success', {url:url}
else
@loading = loadFromServer()
else
@loading = loadFromServer()
@loading.catch (e)-> # do nothing
return @loading
@@ -100,30 +86,3 @@ module.exports = class BaseModel extends Backbone.Model
toString: ->
return "#{@constructor.name}.#{@cid}"
# Private Methods ##############################################################################
_addToLoadingQueue: (text, status, xhr)->
deferred = w.defer()
BaseModel._loadingQueue.push resolve:deferred.resolve, func:(=> @onLoadSucceeded text, status, xhr)
@_drainLoadingQueue()
return deferred.promise
_drainLoadingQueue: ->
return if @_isDraining
@_isDraining = true
drainDelay = 50
drain = =>
toLoad = BaseModel._loadingQueue.shift()
if not toLoad?
@_isDraining = false
else
toLoad.func()
toLoad.resolve(true)
_.delay drain, drainDelay
_.delay drain, drainDelay
@@ -24,7 +24,6 @@ module.exports = class CraftingPlan
@_rawScores = {}
@_scores = {}
@_steps = steps
@_tools = null
@_want = want
@_numberSteps()
@@ -38,32 +37,19 @@ module.exports = class CraftingPlan
@_made = new SimpleInventory modPack:@_modPack
@_made.addInventory @_have
@_tools = new SimpleInventory modPack:@_modPack
for i in [@_steps.length-1..0] by -1
step = @_steps[i]
step.multiplier = 0
recipe = step.recipe
for stack in step.recipe.output
continue if recipe.isPassThroughFor stack.itemSlug
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
if not stack?
throw new Error 'stack should not be null here'
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
while @_need.quantityOf(qualifiedSlug) > 0
@_executeStep step
if step.multiplier > 0
for stack in step.recipe.output
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
continue unless recipe.isPassThroughFor stack.itemSlug
continue if @_made.hasAtLeast qualifiedSlug, 1
continue if @_need.hasAtLeast qualifiedSlug, 1
continue if @_tools.hasAtLeast qualifiedSlug, 1
@_need.add qualifiedSlug, 1
@_tools.add qualifiedSlug, 1
@_made.addInventory @_want
@_made.addInventory @_tools
@_pruneEmptySteps()
@_numberSteps()
@@ -63,22 +63,6 @@ describe 'crafting_plan.coffee', ->
'1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
]
it 'uses the correct amount of passthrough items', ->
plans = fixtures.makePlans [10, 'test__split_oak_wood'], [10, 'test__split_spruce_wood']
plan = plans[2]
plan.computeRequired()
plan.need.unparse().should.equal 'coal:8.iron_ore:6.oak_wood:5.spruce_wood'
plan.made.unparse().should.equal '4.iron_ingot:maul:2.oak_planks:10.split_oak_wood:10.split_spruce_wood:stick'
(s.toString() for s in plan.steps).should.eql [
'1x test__oak_wood>... .0. ...>>4 test__oak_planks'
'1x test__oak_planks>.0. .0. ...>>4 test__stick'
'1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
'1x test__iron_ingot,test__stick>001 001 ..1>test__crafting_table>test__maul'
'5x test__spruce_wood,test__maul>.1. .0. ...>>2 test__split_spruce_wood,test__maul'
'5x test__oak_wood,test__maul>.1. .0. ...>>2 test__split_oak_wood,test__maul'
]
it 'can compute multiple items with multiple plans', ->
plans = fixtures.makePlans [1, 'test__copper_block'], [1, 'test__iron_sword']
plans.length.should.equal 4
@@ -27,11 +27,10 @@ module.exports = class CraftingStep
addToolsTo: (targetInventory)->
for stack in @_recipe.tools
targetInventory.add stack.itemSlug, stack.quantity, insert:true
targetInventory.add stack.itemSlug, stack.quantity
completeInto: (targetInventory)->
for stack in @_recipe.output
continue if @_recipe.isPassThroughFor stack.itemSlug
quantity = @_recipe.getQuantityProduced stack.itemSlug
targetInventory.add stack.itemSlug, quantity * @_multiplier
@@ -68,6 +67,4 @@ module.exports = class CraftingStep
@_inventory = new SimpleInventory
for stack in @_recipe.input
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
required = @_recipe.getQuantityRequired(stack.itemSlug) - @_recipe.getQuantityProduced(stack.itemSlug)
required = if required > 0 then required * @multiplier else 1
@_inventory.add qualifiedSlug, required
@_inventory.add qualifiedSlug, @multiplier * @_recipe.getQuantityRequired stack.itemSlug
+23 -20
View File
@@ -22,43 +22,57 @@ module.exports = class Craftsman extends BaseModel
@::PLAN_STEP_INCREMENT = 39
@::STAGE =
EMPTY: 'empty'
READY: 'ready'
WAITING: 'waiting'
GRAPHING: 'examining recipes'
PLANNING: 'computing plans'
ANALYZING: 'analyzing plans'
COMPLETE: 'complete'
INVALID: 'invalid'
OUTDATED: 'outdated'
constructor: (modPack)->
if not modPack? then throw new Error 'modPack is required'
attributes =
stage: @STAGE.READY
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, => @_resetStage()
@_have.on c.event.change, reevaluatePlans
@_want = new Inventory modPack:@_modPack
@_want.on c.event.change, => @_resetStage()
@_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
@_resetStage()
@stage = @STAGE.WAITING
@stageCount = 0
@_scheduleNextWork()
work: ->
return if @_want.isEmpty
@@ -116,7 +130,7 @@ module.exports = class Craftsman extends BaseModel
Object.defineProperties @prototype,
complete:
get: -> @stage in [@STAGE.COMPLETE, @STAGE.INVALID, @STAGE.OUTDATED]
get: -> @stage in [@STAGE.COMPLETE, @STAGE.INVALID]
have:
get: -> @_have
@@ -129,19 +143,8 @@ module.exports = class Craftsman extends BaseModel
# Private Methods ##############################################################################
_resetStage: ->
@stageCount = 0
if @want.isEmpty
if @stage isnt @STAGE.EMPTY
@stage = @STAGE.EMPTY
@reset()
else if not @_plans?
@stage = @STAGE.READY
else
@stage = @STAGE.OUTDATED
_scheduleNextWork: ->
return if @paused
return if @want.isEmpty
return if @complete
@@ -98,12 +98,6 @@ MOD_VERSION_FILE =
input: Stick, Cobblestone
pattern: .0. .1. ...
item: Maul
recipe:
input: Iron Ingot, Stick
pattern: 001 001 ..1
tools: Crafting Table
item: Oak Planks
recipe:
input: Oak Wood
@@ -113,29 +107,12 @@ MOD_VERSION_FILE =
item: Oak Wood
gatherable: yes
item: Spruce Wood
gatherable: yes
item: Stick
recipe:
input: Oak Planks
pattern: .0. .0. ...
quantity: 4
item: Split Oak Wood
recipe:
extras: Maul
input: Oak Wood, Maul
pattern: .1. .0. ...
quantity: 2
item: Split Spruce Wood
recipe:
extras: Maul
input: Spruce Wood, Maul
pattern: .1. .0. ...
quantity: 2
item: String
gatherable: yes
@@ -44,7 +44,7 @@ module.exports = class SimpleInventory
@_itemSlugs = []
clone: ->
inventory = new SimpleInventory
inventory = new Inventory
inventory.addInventory this
return inventory
@@ -79,7 +79,7 @@ module.exports = class SimpleInventory
if qualifiedSlug?
newSlugs.push qualifiedSlug
newStacks.push new SimpleStack itemSlug:qualifiedSlug, quantity:stack.quantity
newStacks.push new Stack itemSlug:qualifiedSlug, quantity:stack.quantity
else
newSlugs.push itemSlug
newStacks.push stack
@@ -130,9 +130,9 @@ module.exports = class SimpleInventory
parse: (data)->
return this if not data? or data.length is 0
stacks = data.split SimpleInventory.Delimiters.Stack
stacks = data.split Inventory.Delimiters.Stack
for stackText in stacks
stackParts = stackText.split SimpleInventory.Delimiters.Item
stackParts = stackText.split Inventory.Delimiters.Item
if stackParts.length is 2
quantity = parseInt stackParts[0], 10
itemSlug = ItemSlug.slugify stackParts[1]
+3 -3
View File
@@ -1,9 +1,9 @@
#
###
# Crafting Guide - event_recorder.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# Copyright (c) 2014-2015 by Redwood Labs
# All rights reserved.
#
###
util = require 'util'
+3 -19
View File
@@ -29,10 +29,10 @@ module.exports = class Inventory extends BaseModel
# Public Methods ###############################################################################
add: (itemSlug, quantity=1, options={})->
add: (itemSlug, quantity=1)->
return this unless quantity > 0
@_add itemSlug, quantity, options
@_add itemSlug, quantity
@trigger c.event.add, this, itemSlug, quantity
@trigger c.event.change, this
return this
@@ -141,18 +141,6 @@ module.exports = class Inventory extends BaseModel
@trigger c.event.change, this
return this
toDescription: ->
return null if @isEmpty
return null unless @modPack?
item = @modPack.findItem @_itemSlugs[0]
extras = @_itemSlugs.length - 1
result = "#{item.name}"
if extras > 0 then result += " and #{extras} more..."
return result
# Parsing Methods ##############################################################################
parse: (data)->
@@ -221,8 +209,7 @@ module.exports = class Inventory extends BaseModel
# Private Methods ##############################################################################
_add: (itemSlug, quantity=1, options={})->
options.insert ?= false
_add: (itemSlug, quantity=1)->
return unless itemSlug?
return unless quantity > 0
@@ -231,9 +218,6 @@ module.exports = class Inventory extends BaseModel
stack = new Stack itemSlug:itemSlug, quantity:quantity
@listenTo stack, c.event.change, => @trigger c.event.change, this
@_stacks[itemSlug] = stack
if options.insert
@_itemSlugs.unshift itemSlug
else
@_itemSlugs.push itemSlug
@_sort()
else
+2 -12
View File
@@ -29,6 +29,8 @@ module.exports = class Mod extends BaseModel
@_modVersions = []
@_tutorials = []
@once c.event.sync, => @_verifyActiveModVersion()
# Class Methods ##################################################################################
@Version: Version =
@@ -93,12 +95,6 @@ module.exports = class Mod extends BaseModel
# Item Methods #################################################################################
chooseRandomItem: ->
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
return null unless effectiveModVersion?
return effectiveModVersion.chooseRandomItem()
eachItem: (callback)->
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
effectiveModVersion.eachItem callback
@@ -133,7 +129,6 @@ module.exports = class Mod extends BaseModel
@_modVersions.push modVersion
@listenTo modVersion, c.event.change, => @trigger c.event.change, this
modVersion.fileCache = this.fileCache
modVersion.mod = this
@trigger c.event.add + ':modVersion', modVersion, this
@@ -148,9 +143,6 @@ module.exports = class Mod extends BaseModel
for modVersion in @_modVersions
callback modVersion
getAllModVersions: ->
return @_modVersions[..]
getModVersion: (version)->
return null if version is Mod.Version.None
return @_modVersions[0] if version is Mod.Version.Latest
@@ -209,8 +201,6 @@ module.exports = class Mod extends BaseModel
@_parser ?= new ModParser model:this
@_parser.parse text
@_verifyActiveModVersion()
return null # prevent calling `set`
url: ->
+7 -20
View File
@@ -6,10 +6,9 @@
#
BaseModel = require '../base_model'
Mod = require './mod'
Inventory = require './inventory'
ModVersionParser = require '../parsing/mod_version_parser'
Recipe = require './recipe'
SimpleInventory = require '../crafting/simple_inventory'
########################################################################################################################
@@ -25,12 +24,6 @@ module.exports = class ModPack extends BaseModel
# Item Methods #################################################################################
chooseRandomItem: ->
return null unless @_mods.length > 0
modIndex = Math.floor Math.random() * @_mods.length
return @_mods[modIndex].chooseRandomItem()
findItem: (itemSlug, options={})->
options.includeDisabled ?= false
@@ -82,7 +75,7 @@ module.exports = class ModPack extends BaseModel
result.modSlug = @_mods[0].slug
result.modVersion = @_mods[0].activeVersion
craftingUrlInventory = new SimpleInventory modPack:this
craftingUrlInventory = new Inventory modPack:this
if item?.multiblock?
craftingUrlInventory.addInventory item.multiblock.inventory
else
@@ -172,18 +165,12 @@ module.exports = class ModPack extends BaseModel
# Private Methods ##############################################################################
_onModVersionLoaded: (modVersion)->
mods = @getAllMods()
return true unless mods.length > 0
working = true
@eachMod (mod)->
mod.eachModVersion (modVersion)->
working = working and (modVersion.isUnloaded or modVersion.isLoading)
for mod in mods
modVersions = mod.getAllModVersions()
return true unless modVersions.length > 0
continue if mod.activeVersion is Mod.Version.None
activeModVersion = mod.activeModVersion
return true unless activeModVersion?
return true if activeModVersion.isUnloaded
return true if activeModVersion.isLoading
return if working
@trigger c.event.change, this
@trigger c.event.sync, this
@@ -59,10 +59,6 @@ module.exports = class ModVersion extends BaseModel
return null if result.length is 0
return result
chooseRandomItem: ->
itemIndex = Math.floor Math.random() * @_slugs.length
return @_slugs[itemIndex]
eachItem: (callback)->
for slug in @_slugs
item = @findItem slug
+12 -7
View File
@@ -103,7 +103,7 @@ module.exports = class Recipe extends BaseModel
if modPack?.findItemByName(@condition.noun)?
result = true
else if @condition.verb is 'mod'
modPack.eachMod (mod)=>
modPack.eachMod (mod)->
if mod.name is @condition.noun
result = true
@@ -111,7 +111,16 @@ module.exports = class Recipe extends BaseModel
return result
isPassThroughFor: (itemSlug)->
return @getQuantityProduced(itemSlug) is @getQuantityRequired(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?
@@ -212,11 +221,7 @@ module.exports = class Recipe extends BaseModel
_computeQuantities: (pattern)->
quantityMap = {}
index = 0
while index < pattern.length
c = pattern[index]
index += 1
for c in pattern.split ''
continue if c is '.'
continue if c is ' '
@@ -0,0 +1,143 @@
#
# Crafting Guide - lexical_analyzer.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ParserCommand = require './parser_command'
########################################################################################################################
module.exports = class LexicalAnalyzer
constructor: (fileName, rawText)->
if not fileName? then throw new Error 'fileName is required'
if not rawText? then throw new Error 'rawText is required'
@_commands = []
@_fileName = fileName
@_rawText = rawText
@_lines = rawText.split '\n'
@_lineIndex = -1
@_lineNumber = 0
# Class Methods ################################################################################
@::COMMAND = /\ *([^:]*):?(.*)/
@::COMMENT = /([^\\]?)#.*/
@::FILE = /^#FILE +(.*)/
# Public Methods ###############################################################################
next: ->
command = @peek()
@_commands.shift()
return command
peek: ->
while @_commands.length is 0
break if @_lineNumber > @_lines.length
@_parseLine()
return null unless @_commands.length > 0
return @_commands[0]
push: (command)->
@_commands.unshift command
# Property Methods #############################################################################
getIsFinished: ->
return @peek() is null
Object.defineProperties @prototype,
isFinished: { get:@::getIsFinished }
# Private Methods ##############################################################################
_currentLine: ->
return null if @_lineIndex >= @_lines.length
return @_lines[@_lineIndex]
_nextLine: ->
if @_lineIndex < @_lines.length
while true
@_lineIndex += 1
@_lineNumber += 1
break if @_lineIndex >= @_lines.length
line = @_lines[@_lineIndex]
fileMatch = line.match @FILE, '$1'
if fileMatch?
@_fileName = fileMatch[1]
@_lineNumber = 0
else
line = line.replace @COMMENT, '$1'
@_lines[@_lineIndex] = line
break if line.trim().length > 0
return @_currentLine()
_parseHereDoc: ->
line = @_currentLine()
hereDocIndex = line.indexOf '<<-'
return [line, null] unless hereDocIndex isnt -1
hereDocStopText = line[hereDocIndex+3...line.length]
line = line[0...hereDocIndex]
hereDocLines = []
while true
nextLine = @_nextLine()
break unless nextLine?
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')]
_parseLine: ->
line = @_nextLine()
return unless line?
startingLineNumber = @_lineNumber
[line, lineHereDoc] = @_parseHereDoc()
lineParts = (part.trim() for part in line.split(';'))
for linePart, index in lineParts
continue if linePart.length is 0
hereDoc = lineHereDoc if index is lineParts.length - 1
match = @COMMAND.exec linePart
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
argText = if match[2] then match[2].trim() else ''
args = (arg.trim() for arg in argText.split(','))
args = (arg for arg in args when arg.length > 0)
if hereDoc?
if argText.length > 0 then argText += ', '
argText += hereDoc
args.push hereDoc
@_commands.push new ParserCommand
argText: argText
args: args
fileName: @_fileName
hereDoc: hereDoc
lineNumber: startingLineNumber
name: match[1]
@@ -0,0 +1,121 @@
#
# Crafting Guide - lexical_analyzer.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
LexicalAnalyzer = require './lexical_analyzer'
########################################################################################################################
lexer = null
createLexer = (text)->
return new LexicalAnalyzer 'file.cg', text
########################################################################################################################
describe 'lexical_analyzer.coffee', ->
describe 'a document with', ->
it 'no content returns null immediately', ->
lexer = createLexer ''
expect(lexer.next()).to.be.null
it 'only whitespace returns null immediately', ->
lexer = createLexer '\n\n \n \n\n'
expect(lexer.next()).to.be.null
it 'a single command returns only that command', ->
lexer = createLexer 'alpha: bravo'
lexer.next().name.should.equal 'alpha'
expect(lexer.next()).to.be.null
it 'multiple commands on a single line returns all commands', ->
lexer = createLexer 'alpha: bravo; charlie: delta; echo: foxtrot'
lexer.next().name.should.equal 'alpha'
lexer.next().name.should.equal 'charlie'
lexer.next().name.should.equal 'echo'
expect(lexer.next()).to.be.null
it 'multiple commands on separate lines returns all commands', ->
lexer = createLexer 'alpha: bravo \n charlie: delta \n echo: foxtrot'
lexer.next().name.should.equal 'alpha'
lexer.next().name.should.equal 'charlie'
lexer.next().name.should.equal 'echo'
expect(lexer.next()).to.be.null
describe 'a command with', ->
it 'only a name has no arguments', ->
command = createLexer('alpha:').next()
command.argText.should.equal ''
command.args.should.eql []
it 'one argument has only that argument', ->
command = createLexer('alpha: bravo').next()
command.argText.should.equal 'bravo'
command.args.should.eql ['bravo']
it 'multiple arguments has all of them', ->
command = createLexer('alpha: bravo, charlie, delta').next()
command.argText.should.equal 'bravo, charlie, delta'
command.args.should.eql ['bravo', 'charlie', 'delta']
it 'only a heredoc has it recorded in the right places', ->
command = createLexer('alpha: <<-END\nbravo charlie\nEND').next()
command.hereDoc.should.equal 'bravo charlie'
command.args.should.eql ['bravo charlie']
command.argText.should.equal 'bravo charlie'
it 'multiple args and a hereDoc is built correctly', ->
command = createLexer('alpha: bravo, charlie <<-END\ndelta echo\nfoxtrot\nEND').next()
command.hereDoc.should.equal 'delta echo\nfoxtrot'
command.argText.should.equal 'bravo, charlie, delta echo\nfoxtrot'
command.args.should.eql ['bravo', 'charlie', 'delta echo\nfoxtrot']
describe 'file name is assigned correctly when', ->
it 'no file markers are given', ->
lexer = createLexer 'alpha: bravo\ncharlie: delta'
lexer.next().fileName.should.equal 'file.cg'
lexer.next().fileName.should.equal 'file.cg'
it 'commands appear before the first file marker', ->
lexer = createLexer 'alpha: bravo\n#FILE file2.cg\ncharlie: delta'
lexer.next().fileName.should.equal 'file.cg'
lexer.next().fileName.should.equal 'file2.cg'
it 'multiple file markers are used', ->
lexer = createLexer '#FILE file1.cg\na: b\n#FILE file2.cg\nc: d\n#FILE file3.cg\ne: f'
lexer.next().fileName.should.equal 'file1.cg'
lexer.next().fileName.should.equal 'file2.cg'
lexer.next().fileName.should.equal 'file3.cg'
describe 'line numbers are assigned correctly when', ->
it 'commands are listed one after another', ->
lexer = createLexer 'alpha: bravo\ncharlie: delta\necho: foxtrot'
lexer.next().lineNumber.should.equal 1
lexer.next().lineNumber.should.equal 2
lexer.next().lineNumber.should.equal 3
it 'commands are separated by blank lines and comments', ->
lexer = createLexer '# foo\n\nalpha: bravo\n\ncharlie: delta\n\n# bar\n\necho: foxtrot'
lexer.next().lineNumber.should.equal 3
lexer.next().lineNumber.should.equal 5
lexer.next().lineNumber.should.equal 9
it 'commands include hereDocs', ->
lexer = createLexer 'alpha: <<-END\nbravo charlie\nEND\ndelta: \n\necho: '
lexer.next().lineNumber.should.equal 1
lexer.next().lineNumber.should.equal 4
lexer.next().lineNumber.should.equal 6
it 'file markers are used', ->
lexer = createLexer '#FILE file1.cg\nalpha: \n#FILE file2.cg\n\nbravo: \n\n#FILE file3.cg\n\n\ncharlie: '
lexer.next().lineNumber.should.equal 1
lexer.next().lineNumber.should.equal 2
lexer.next().lineNumber.should.equal 3
+112
View File
@@ -0,0 +1,112 @@
#
# Crafting Guide - parser.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
LexicalAnalyzer = require './lexical_analyzer'
ModPack = require '../game/mod_pack'
ParserData = require './parser_data'
########################################################################################################################
module.exports = class Parser
constructor: (modPack, fileName, rawText)->
@_extensions = []
@_fileName = fileName
@_lexer = new LexicalAnalyzer fileName, rawText
@_modPack = new ModPack
@_data = new ParserData
modPackData = @_data.create {}, 'modPack'
modPackData.instance = @_modPack
@_loadSchema()
# Public Methods ###############################################################################
next: ->
if not @_lexer.isFinished
@_processNextCommand()
else if not @_data.isValidated
@_validateNextEntry()
else if not @_data.isBuilt
@_buildNextEntry()
else if not @_data.isAssembled
@_assembleNextEntry()
# Property Methods #############################################################################
getIsFinished: ->
return @_lexer.isFinished and @_data.isValidated and @_data.isBuilt and @_data.isAssembled
getData: ->
return @_data
getModPack: ->
return @_modPack
Object.defineProperties @prototype,
data: { get:@::getData }
isFinished: { get:@::getIsFinished }
modPack: { get:@::getModPack }
# Private Methods ##############################################################################
_assembleNextEntry: ->
entry = @_data.popNextToAssemble()
return unless entry?
for extensions in @_extensions
continue unless extension.canAssemble entry
extension.assemble entry
return
_buildNextEntry: ->
entry = @_data.popNextToBuild()
return unless entry?
for extensions in @_extensions
continue unless extension.canBuild entry
extension.build entry
return
_loadSchema: ->
command = @_lexer.next()
if command.name isnt 'schema' then throw new Error "#{@_fileName} must start with the \"schema\" command"
schema = command.argText
if schema is '1'
@_use new require './parser_extensions/current_pe_v1'
@_use new require './parser_extensions/item_pe_v1'
@_use new require './parser_extensions/mod_pe_v1'
@_use new require './parser_extensions/mod_version_pe_v1'
@_use new require './parser_extensions/recipe_pe_v1'
else
throw new Error "unknown schema version: #{schema}"
_processNextCommand: ->
command = @_lexer.next()
return unless command?
for extension in @_extensions
continue unless extension.canExecute command
extension.execute command
return
@_data.addError command, "unknown command: #{command.name}"
_use: (parserExtension)->
parserExtension.state = @_state
@_extensions.push parserExtension
_validateNextEntry: ->
entry = @_data.popNextToValidate()
return unless entry?
for extensions in @_extensions
continue unless extension.canValidate entry
extension.validate entry
return
@@ -0,0 +1,33 @@
#
# Crafting Guide - parser_command.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
module.exports = class ParserCommand
constructor: (options={})->
if not options.name? then throw 'options.name is required'
if not options.fileName? then throw new 'options.fileName is required'
if not options.lineNumber? then throw 'options.lineNumber is required'
{@name, @fileName, @lineNumber, @argText, @args, @hereDoc} = options
# Property Methods #############################################################################
getLocation: ->
return "#{@fileName}:#{@lineNumber}"
Object.defineProperties @prototype,
location: { get:@::getLocation }
# Object Overrides #############################################################################
toString: ->
result = "#{@fileName}:#{@lineNumber} #{@name}: #{@argText}"
if @hereDoc?
result += " <<- END\n#{@hereDoc}\nEND"
return result
@@ -0,0 +1,134 @@
#
# Crafting Guide - parser_data.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
module.exports = class ParserData
constructor: ->
@clear()
@_toAssemble = []
@_toBuild = []
@_toValidate = []
# Public Methods ###############################################################################
addError: (command, message)->
if not command? then throw new Error 'command is required'
message ?= 'is not valid'
@_errors.push command:command, message:message
create: (command, type, id=null)->
id ?= _.uniqueId "#{type}-"
@clearCurrent type
typeData = @_findOrCreateType type
itemData = @_current = typeData['current'] = typeData[id] = id:id, command:command, type:type
@_toAssemble.push itemData
@_toBuild.push itemData
@_toValidate.push itemData
return itemData
clear: ->
@_current = null
@_data =
mod: {}
modVersion: {}
tutorial: {}
itemGroup: {}
item: {}
multiblock: {}
recipe: {}
@_errors = []
clearCurrent: (type)->
switch type
when 'mod'
delete @_data.mod.current
delete @_data.modVersion.current
delete @_data.tutorial.current
delete @_data.itemGroup.current
delete @_data.item.current
delete @_data.multiblock.current
delete @_data.recipe.current
when 'modVersion'
delete @_data.modVersion.current
delete @_data.tutorial.current
delete @_data.itemGroup.current
delete @_data.item.current
delete @_data.multiblock.current
delete @_data.recipe.current
when 'tutorial'
delete @_data.tutorial.current
when 'itemGroup'
delete @_data.itemGroup.current
delete @_data.item.current
delete @_data.multiblock.current
delete @_data.recipe.current
when 'item'
delete @_data.item.current
delete @_data.multiblock.current
delete @_data.recipe.current
when 'multiblock'
delete @_data.multiblock.current
when 'recipe'
delete @_data.recipe.current
get: (type, id)->
typeData = @_findOrCreateType type
itemData = typeData[id] or null
return itemData
getCurrent: (type)->
return @_current unless type?
typeData = @_findOrCreateType type
current = typeData['current'] or null
return current
popNextToAssemble: ->
return @_toAssemble.pop() or null
popNextToBuild: ->
return @_toBuild.pop() or null
popNextToValidate: ->
return @_toValidate.pop() or null
# Property Methods #############################################################################
getErrors: ->
return @_errors[..]
getIsAssembled: ->
return @_toAssemble.length > 0
getIsBuilt: ->
return @_toBuild.length > 0
getIsValidated: ->
return @_toValidate.length > 0
Object.defineProperties @prototype,
errors: { get:@::getErrors }
isAssembled: { get:@::getIsAssembled }
isBuilt: { get:@::getIsBuilt }
isValidated: { get:@::getIsValidated }
# Private Methods ##############################################################################
_findOrCreateType: (type)->
typeData = @_data[type]
if not typeData?
typeData = @_data[type] = {}
typeData.type = type
return typeData
@@ -0,0 +1,55 @@
#
# Crafting Guide - parser_data.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ParserCommand = require './parser_command'
ParserData = require './parser_data'
########################################################################################################################
command = state = null
########################################################################################################################
describe 'parser_data.coffee', ->
beforeEach ->
state = new ParserData
command = new ParserCommand name:'alpha', fileName:'file.cg', lineNumber:1
describe 'create', ->
it 'returns an object with the given id', ->
alpha = state.create command, 'alpha', 0
alpha.id.should.equal 0
alpha.command.location.should.equal 'file.cg:1'
it 'returns an object with a newly created id when none is given', ->
alpha = state.create command, 'alpha'
alpha.id.should.not.be.null
alpha.id.should.match /^alpha-[0-9]+$/
alpha.command.location.should.equal 'file.cg:1'
describe 'get', ->
it 'returns a previously created item', ->
alpha1 = state.create command, 'alpha', 0
alpha2 = state.get 'alpha', 0
alpha1.should.equal alpha2
it 'returns null when no such item exists', ->
alpha = state.create command, 'alpha', 0
expect(state.get 'alpha', 1).to.be.null
expect(state.get 'bravo', 0).to.be.null
describe 'getCurrent', ->
it 'returns the new item each time one is created', ->
alpha1 = state.create command, 'alpha', 0
state.getCurrent('alpha').id.should.equal 0
alpha2 = state.create command, 'alpha', 1
state.getCurrent('alpha').id.should.equal 1
@@ -0,0 +1,165 @@
#
# Crafting Guide - parser_extension.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
module.exports = class ParserExtension
constructor: (data, type, commandFunctions)->
@data = data
@_commandFunctions = commandFunctions or {}
@_type = type or null
# Public Methods ###############################################################################
canAssemble: (entry)->
return entry.type is @_type
canBuild: (entry)->
return entry.type is @_type
canExecute: (command)->
_.isFunction @_commandFunctions[command.name]
canValidate: (entry)->
return entry.type is @_type
execute: (command)->
if not @_data? then throw new Error '@data must be defined before executing commands'
func = @_commandFunctions[command.name]
if not func then throw new Error "#{@constructor.name} cannot handle command: #{command.name}"
func.call this, command
return this
# Error Checking Helpers #######################################################################
alreadyExists: (command, type, id)->
obj = @data.get(type, id)
if obj?
@data.addError command, "a #{type} called #{id} has already been declared"
return true
return false
duplicateField: (command, obj, field)->
if obj[field]?
@data.addError command, "duplicate #{field}"
return true
return false
incompatibleField: (command, obj, field, otherCommandName)->
otherCommandName ?= field
if obj[field]?
@data.addError "#{command.name} cannot be used with #{otherCommandName}"
return true
return false
missingCurrent: (command, type)->
obj = @data.getCurrent type
if not obj?
@data.addError command, "you must declare a #{type} before using #{command.name}"
return true
return false
missingArgs: (command)->
if not command.args or command.args.length is 0
@data.addError command, "#{command.name} requires at least one item"
return true
return false
missingArgText: (command)->
if command.argText.length is 0
@data.addError command, "#{command.name} cannot be empty"
return true
return false
invalidVerb: (command, verb)->
if not verb in ['mod', 'item']
@data.addError command, "#{command.name} only applies to either `mod` or `item`"
return true
return false
tooFewArguments: (command, minimum)->
if command.args.length < minimum
@data.addError command, "#{command.name} requires at least #{minimum} arguments"
return true
return false
# Parsing Helpers ##############################################################################
parseBoolean: (command, text)->
return null unless text?
switch text.toLowerCase()
when "yes" then return true
when "no" then return false
@data.addError command, "#{command.name} requires \"yes\" or \"no\""
return null
parseInt: (command, text)->
return null unless text?
value = Number.parseInt text
if isNaN value
@addError command, "#{command.name} requires a number"
return null
return value
# Property Methods #############################################################################
getData: ->
return @_data
setData: (data)->
if not data? then throw new Error 'data cannot be undefined'
@_data = data
Object.defineProperties @prototype,
data: { get:@::getData, set:@::setData }
# Overridable Methods ##########################################################################
assmeble: (entry)->
throw new Error "#{@constructor.name} must override the `assemble` method"
build: (entry)->
throw new Error "#{@constructor.name} must override the `build` method"
validate: (entry)->
throw new Error "#{@constructor.name} must override the `validate` method"
# Private Methods ##############################################################################
_findBuildMethod: (entry)->
methodName = "_build_#{entry.type}"
method = this[methodName]
return null unless _.isFunction method
return method
_findCommandMethod: (command)->
methodName = "_command_#{command.name}"
method = this[methodName]
return null unless _.isFunction method
return method
_findValidateMethod: (command)->
methodName = "_validate_#{command.name}"
method = this[methodName]
return null unless _.isFunction method
return method
@@ -0,0 +1,50 @@
#
# Crafting Guide - current_pe_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ParserExtension = require '../parser_extension'
########################################################################################################################
module.exports = class CurrentParserExtensionV1 extends ParserExtension
constructor: (data)->
super data, null,
description: @_command_description
name: @_command_name
officialUrl: @_command_officialUrl
video: @_command_video
# Command Methods ##############################################################################
_command_description: (command)->
current = @data.getCurrent()
return if @duplicateField command, current, 'description'
return if @missingArgText command
current.description = command.argText
_command_name: (command)->
current = @data.getCurrent()
return if @duplicateField command, current, 'name'
return if @missingArgText command
current.name = command.argText
_command_officialUrl: (command)->
current = @data.getCurrent()
return if @duplicateField command, current, 'officialUrl'
return if @missingArgText command
current.officialUrl = command.argText
_command_video: (command)->
current = @data.getCurrent()
return if @missingArgs command
return if @tooFewArguments command, 2
current.videos ?= []
current.videos.push youTubeId:command.args[0], caption:command.args[1..].join(', ')
@@ -0,0 +1,59 @@
#
# Crafting Guide - current_pe_v1.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CurrentParserExtensionV1 = require './current_pe_v1'
ParserData = require '../parser_data'
########################################################################################################################
parser = state = null
########################################################################################################################
describe 'current_pe_v1.coffee', ->
beforeEach ->
state = new ParserData
parser = new CurrentParserExtensionV1 state
state.create {}, 'alpha'
describe 'description', ->
it 'assigns to the current object', ->
parser.execute name:'description', argText:'bravo'
state.getCurrent().description.should.equal 'bravo'
state.errors.should.eql []
describe 'name', ->
it 'assigns to the current object', ->
parser.execute name:'name', argText:'bravo'
state.getCurrent().name.should.equal 'bravo'
state.errors.should.eql []
describe 'officialUrl', ->
it 'assigns to the current object', ->
parser.execute name:'officialUrl', argText:'bravo'
state.getCurrent().officialUrl.should.equal 'bravo'
state.errors.should.eql []
describe 'video', ->
it 'assigns to the current object', ->
parser.execute name:'video', args:['alpha', 'bravo']
videos = state.getCurrent().videos
videos[0].youTubeId.should.equal 'alpha'
videos[0].caption.should.equal 'bravo'
state.errors.should.eql []
it 'can assign multiple videos', ->
parser.execute name:'video', args:['alpha', 'bravo']
parser.execute name:'video', args:['charlie', 'delta']
state.getCurrent().videos.length.should.equal 2
state.errors.should.eql []
@@ -0,0 +1,67 @@
#
# Crafting Guide - item_pe_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ParserExtension = require '../parser_extension'
########################################################################################################################
module.exports = class ItemParserExtensionV1 extends ParserExtension
constructor: (data)->
super data, 'item',
gatherable: @_command_gatherable
item: @_command_item
update: @_command_update
# ParserExtensions Overrides ###################################################################
assmeble: (entry)->
throw new Error "#{@constructor.name} must override the `assemble` method"
build: (entry)->
entry.instance = new
throw new Error "#{@constructor.name} must override the `build` method"
validate: (entry)-> # do nothing
# Command Methods ##############################################################################
_command_gatherable: (command)->
return if @missingArgText command
return if @missingCurrent command, 'item'
item = @data.getCurrent 'item'
return if @duplicateField command, item, 'gatherable'
gatherable = @parseBoolean command, command.argText
return unless gatherable?
item.gatherable = gatherable
_command_item: (command)->
return if @missingArgText command
return if @missingCurrent command, 'modVersion'
return if @alreadyExists command, 'item', command.argText
item = @data.create command, 'item', command.argText
item.name = command.argText
item.isUpdate = false
group = @data.getCurrent 'itemGroup'
if group? then item.group = group
item.modVersion = @data.getCurrent 'modVersion'
_command_update: (command)->
return if @missingArgText command
return if @missingCurrent command, 'modVersion'
return if @alreadyExists command, 'item', command.argText
item = @data.create command, 'item', command.argText
item.name = command.argText
item.isUpdate = true
item.modVersion = @data.getCurrent 'modVersion'
@@ -0,0 +1,54 @@
#
# Crafting Guide - item_pe_v1.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ItemParserExtensionV1 = require './item_pe_v1'
ParserData = require '../parser_data'
########################################################################################################################
parser = data = null
########################################################################################################################
describe 'item_pe_v1.coffee', ->
beforeEach ->
data = new ParserData
parser = new ItemParserExtensionV1 data
data.create {}, 'modVersion', 0
describe 'gatherable', ->
it 'assigns to the current item', ->
data.create {}, 'item', 1
parser.execute name:'gatherable', argText:'yes'
data.getCurrent('item').gatherable.should.be.true
data.errors.should.eql []
describe 'item', ->
it 'creates a new item', ->
data.create {}, 'itemGroup', 2
parser.execute name:'item', argText:'alpha'
item = data.getCurrent 'item'
item.id.should.equal 'alpha'
item.isUpdate.should.equal false
item.name.should.equal 'alpha'
item.modVersion.id.should.equal 0
item.group.id.should.equal 2
data.getErrors().should.eql []
describe 'update', ->
it 'creates an item update', ->
parser.execute name:'update', argText:'alpha'
item = data.getCurrent 'item'
item.isUpdate.should.equal true
data.getErrors().should.eql []
@@ -0,0 +1,62 @@
#
# Crafting Guide - mod_pe_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ParserExtension = require '../parser_extension'
########################################################################################################################
module.exports = class ModParserExtensionV1 extends ParserExtension
constructor: (data)->
super data, 'mod',
author: @_command_author
documentationUrl: @_command_documentationUrl
downloadUrl: @_command_downloadUrl
homePageUrl: @_command_homePageUrl
mod: @_command_mod
# Command Methods ##############################################################################
_command_author: (command)->
return if @missingCurrent command, 'mod'
mod = @data.getCurrent 'mod'
return if @duplicateField command, mod, 'author'
return if @missingArgText command
mod.author = command.argText
_command_documentationUrl: (command)->
current = @data.getCurrent()
return if @duplicateField command, current, 'documentationUrl'
return if @missingArgText command
current.documentationUrl = command.argText
_command_downloadUrl: (command)->
return if @missingCurrent command, 'mod'
mod = @data.getCurrent 'mod'
return if @duplicateField command, mod, 'downloadUrl'
return if @missingArgText command
mod.downloadUrl = command.argText
_command_homePageUrl: (command)->
return if @missingCurrent command, 'mod'
mod = @data.getCurrent 'mod'
return if @duplicateField command, mod, 'homePageUrl'
return if @missingArgText command
mod.homePageUrl = command.argText
_command_mod: (command)->
return if @missingArgText command
return if @alreadyExists command, 'mod', command.argText
@data.create command, 'mod', command.argText
@@ -0,0 +1,58 @@
#
# Crafting Guide - mod_pe_v1.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ModParserExtensionV1 = require './mod_pe_v1'
ParserData = require '../parser_data'
########################################################################################################################
parser = data = null
########################################################################################################################
describe 'mod_pe_v1.coffee', ->
beforeEach ->
data = new ParserData
parser = new ModParserExtensionV1 data
data.create {}, 'mod', 0
describe 'author', ->
it 'assigns to the current mod', ->
parser.execute name:'author', argText:'alpha'
data.getCurrent('mod').author.should.equal 'alpha'
data.errors.should.eql []
describe 'documentationUrl', ->
it 'assigns to the current mod', ->
parser.execute name:'documentationUrl', argText:'alpha'
data.getCurrent('mod').documentationUrl.should.equal 'alpha'
data.errors.should.eql []
describe 'downloadUrl', ->
it 'assigns to the current mod', ->
parser.execute name:'downloadUrl', argText:'alpha'
data.getCurrent('mod').downloadUrl.should.equal 'alpha'
data.errors.should.eql []
describe 'homePageUrl', ->
it 'assigns to the current mod', ->
parser.execute name:'homePageUrl', argText:'alpha'
data.getCurrent('mod').homePageUrl.should.equal 'alpha'
data.errors.should.eql []
describe 'mod', ->
it 'creates a new mod', ->
parser.execute name:'mod', argText:'alpha'
mod = data.getCurrent 'mod'
mod.id.should.equal 'alpha'
@@ -0,0 +1,33 @@
#
# Crafting Guide - mod_version_pe_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ParserExtension = require '../parser_extension'
########################################################################################################################
module.exports = class ModVersionParserExtensionV1 extends ParserExtension
constructor: (data)->
super data, 'modVersion',
group: @_command_group
version: @_command_version
# Command Methods ##############################################################################
_command_group: (command)->
return if @missingArgText command
return if @missingCurrent command, 'modVersion'
group = @data.create command, 'itemGroup', command.argText
group.modVersion = @data.getCurrent 'modVersion'
_command_version: (command)->
return if @missingArgText command
return if @missingCurrent command, 'mod'
modVersion = @data.create command, 'modVersion', command.argText
modVersion.mod = @data.getCurrent 'mod'
@@ -0,0 +1,41 @@
#
# Crafting Guide - mod_version_pe_v1.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ModVersionParserExtensionV1 = require './mod_version_pe_v1'
ParserData = require '../parser_data'
########################################################################################################################
parser = data = null
########################################################################################################################
describe 'mod_version_pe_v1.coffee', ->
beforeEach ->
data = new ParserData
parser = new ModVersionParserExtensionV1 data
data.create {}, 'mod', 0
describe 'group', ->
it 'creates a new item group', ->
data.create {}, 'modVersion', 1
parser.execute name:'group', argText:'alpha'
itemGroup = data.getCurrent('itemGroup')
itemGroup.id.should.equal 'alpha'
itemGroup.modVersion.id.should.equal 1
describe 'version', ->
it 'creates a new mod version', ->
parser.execute name:'version', argText:'alpha'
modVersion = data.getCurrent 'modVersion'
modVersion.id.should.equal 'alpha'
modVersion.mod.id.should.equal 0
@@ -0,0 +1,38 @@
#
# Crafting Guide - multiblock_pe_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ParserExtension = require '../parser_extension'
########################################################################################################################
module.exports = class MultiblockParserExtensionV1 extends ParserExtension
constructor: (data)->
super data, 'multiblock',
layer: @_command_layer
multiblock: @_command_multiblock
# Command Methods ##############################################################################
_command_layer: (command)->
return if @missingCurrent command, 'multiblock'
return if @missingArgText command
multiblock = @data.getCurrent 'multiblock'
multiblock.layers ?= []
multiblock.layers.push command.argText
_command_multiblock: (command)->
return if @missingCurrent command, 'item'
item = @data.getCurrent 'item'
return if @duplicateField command, item, 'multiblock'
return if @incompatibleField command, item, 'recipe'
return if @incompatibleField command, item, 'gatherable'
multiblock = @data.create command, 'multiblock'
multiblock.item = item
@@ -0,0 +1,48 @@
#
# Crafting Guide - multiblock_pe_v1.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
MultiblockParserExtensionV1 = require './multiblock_pe_v1'
ParserData = require '../parser_data'
########################################################################################################################
parser = data = null
########################################################################################################################
describe 'multiblock_pe_v1.coffee', ->
beforeEach ->
data = new ParserData
parser = new MultiblockParserExtensionV1 data
data.create {}, 'item', 1
describe 'layer', ->
it 'creates a new layer in the current multiblock', ->
data.create {}, 'multiblock', 2
parser.execute name:'layer', argText:'alpha'
multiblock = data.getCurrent 'multiblock'
multiblock.layers.length.should.equal 1
multiblock.layers[0].should.equal 'alpha'
it 'can create multiple layers', ->
data.create {}, 'multiblock', 2
parser.execute name:'layer', argText:'alpha'
parser.execute name:'layer', argText:'bravo'
multiblock = data.getCurrent 'multiblock'
multiblock.layers.should.eql ['alpha', 'bravo']
describe 'multiblock', ->
it 'creates a new multiblock', ->
parser.execute name:'multiblock'
multiblock = data.getCurrent 'multiblock'
multiblock.item.id.should.equal 1
@@ -0,0 +1,143 @@
#
# Crafting Guide - recipe_pe_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ParserExtension = require '../parser_extension'
########################################################################################################################
module.exports = class RecipeParserExtensionV1 extends ParserExtension
constructor: (data)->
super data, 'recipe',
extras: @_command_extras
ignoreDuringCrafting: @_command_ignoreDuringCrafting
input: @_command_input
onlyIf: @_command_onlyIf
pattern: @_command_pattern
recipe: @_command_recipe
quantity: @_command_quantity
tools: @_command_tools
# Class Methods ################################################################################
@::PATTERN = /[0-9. ]+/
@::STACK = /^([0-9]+) +(.+)$/
# Error Checking Helpers #######################################################################
invalidPattern: (command)->
match = command.argText.match @PATTERN
if not match?
@data.addError command, "invalid pattern: \"#{command.argText}\""
return true
return false
# Command Methods ##############################################################################
_command_extras: (command)->
return if @missingCurrent command, 'recipe'
return if @missingArgs command
recipe = @data.getCurrent 'recipe'
return if @duplicateField command, 'extras', recipe
recipe.extras = command.args
_command_ignoreDuringCrafting: (command)->
return if @missingArgText command
return if @missingCurrent command, 'recipe'
recipe = @data.getCurrent 'recipe'
return if @duplicateField command, recipe, 'ignoreDuringCrafting'
ignoreDuringCrafting = @parseBoolean command, command.argText
return unless ignoreDuringCrafting?
recipe.ignoreDuringCrafting = ignoreDuringCrafting
_command_input: (command)->
return if @missingCurrent command, 'recipe'
return if @missingArgs command
recipe = @data.getCurrent 'recipe'
multiblock = @data.getCurrent 'multiblock'
target = if multiblock? then multiblock else recipe
return if @duplicateField command, 'input', target
target.input = (@_parseStack(arg) for arg in command.args)
_command_onlyIf: (command)->
return if @missingCurrent command, 'recipe'
return if @missingArgText command
recipe = @data.getCurrent 'recipe'
return if @duplicateField command, recipe, 'condition'
words = command.argText.split ' '
inverted = false
if words[0] is 'not'
inverted = true
words.shift()
verb = words[0]
noun = words[1..].join ' '
return if @invalidVerb command, verb
recipe.condition = verb:verb, noun:noun, inverted:inverted
_command_pattern: (command)->
return if @missingCurrent command, 'recipe'
return if @missingArgText command
return if @invalidPattern command
recipe = @data.getCurrent 'recipe'
return if @duplicateField command, 'pattern', recipe
recipe.pattern = command.argText
_command_quantity: (command)->
return if @missingCurrent command, 'recipe'
return if @missingArgText command
value = @parseInt command, command.argText
return unless value?
recipe = @data.getCurrent 'recipe'
return if @duplicateField command, 'quantity', recipe
recipe.quantity = value
_command_recipe: (command)->
return if @missingCurrent command, 'item'
recipe = @data.create command, 'recipe'
recipe.item = @data.getCurrent 'item'
_command_tools: (command)->
return if @missingCurrent command, 'recipe'
return if @missingArgs command
recipe = @data.getCurrent 'recipe'
return if @duplicateField command, 'tools', recipe
recipe.tools = (@_parseStack(arg) for arg in command.args)
# Custom Parsers ###############################################################################
_parseStack: (text)->
match = text.match @STACK
return name:text, quantity:1 unless match?
quantity = parseFloat match[1]
if isNaN quantity then quantity = 1
name = match[2]
return name:name, quantity:quantity
@@ -0,0 +1,125 @@
#
# Crafting Guide - recipe_pe_v1.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
RecipeParserExtensionV1 = require './recipe_pe_v1'
ParserData = require '../parser_data'
########################################################################################################################
parser = data = null
########################################################################################################################
describe 'recipe_pe_v1.coffee', ->
beforeEach ->
data = new ParserData
parser = new RecipeParserExtensionV1 data
data.create {}, 'item', 0
describe 'extras', ->
it 'assigns to the current recipe', ->
data.create {}, 'recipe'
parser.execute name:'extras', args:['bravo']
recipe = data.getCurrent 'recipe'
recipe.extras.should.eql ['bravo']
data.errors.should.eql []
describe 'ignoreDuringCrafting', ->
it 'assigns to the current recipe', ->
data.create {}, 'recipe'
parser.execute name:'ignoreDuringCrafting', argText:'yes'
recipe = data.getCurrent 'recipe'
recipe.ignoreDuringCrafting.should.equal true
data.errors.should.eql []
describe 'input', ->
it 'correctly reads a single item', ->
data.create {}, 'recipe'
parser.execute name:'input', args:['bravo']
recipe = data.getCurrent 'recipe'
recipe.input.should.eql [name:'bravo', quantity:1]
data.errors.should.eql []
it 'correctly reads multiple items', ->
data.create {}, 'recipe'
parser.execute name:'input', args:['bravo', 'charlie']
recipe = data.getCurrent 'recipe'
recipe.input.should.eql [{name:'bravo', quantity:1}, {name:'charlie', quantity:1}]
data.errors.should.eql []
it 'correctly reads a stack', ->
data.create {}, 'recipe'
parser.execute name:'input', args:['10 bravo']
recipe = data.getCurrent 'recipe'
recipe.input.should.eql [name:'bravo', quantity:10]
data.errors.should.eql []
describe 'onlyIf', ->
it 'assigns to the current recipe', ->
data.create {}, 'recipe'
parser.execute name:'onlyIf', argText:'not mod Alpha'
recipe = data.getCurrent 'recipe'
recipe.condition.should.eql verb:'mod', noun:'Alpha', inverted:true
data.errors.should.eql []
describe 'pattern', ->
it 'assigns to the current recipe', ->
data.create {}, 'recipe'
parser.execute name:'pattern', argText:'00. 00. ...'
recipe = data.getCurrent 'recipe'
recipe.pattern.should.equal '00. 00. ...'
data.errors.should.eql []
it 'rejects an invalid pattern', ->
data.create {}, 'recipe'
parser.execute name:'pattern', argText:'alpha'
recipe = data.getCurrent 'recipe'
expect(recipe.pattern).to.be.undefined
(e.message for e in data.errors).should.eql ['invalid pattern: "alpha"']
describe 'quantity', ->
it 'assigns to the current recipe', ->
data.create {}, 'recipe'
parser.execute name:'quantity', argText:'42'
recipe = data.getCurrent 'recipe'
recipe.quantity.should.equal 42
data.errors.should.eql []
describe 'recipe', ->
it 'creates a new recipe', ->
parser.execute name:'recipe'
recipe = data.getCurrent 'recipe'
recipe.item.id.should.equal 0
data.errors.should.eql []
describe 'tools', ->
it 'assigns to the current recipe', ->
data.create {}, 'recipe'
parser.execute name:'tools', args:['bravo']
recipe = data.getCurrent 'recipe'
recipe.tools.should.eql [name:'bravo', quantity:1]
data.errors.should.eql []
@@ -0,0 +1,51 @@
#
# Crafting Guide - tutorial_pe_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ParserExtension = require '../parser_extension'
########################################################################################################################
module.exports = class TutorialParserExtensionV1 extends ParserExtension
constructor: (data)->
super data, 'tutorial',
content: @_command_content
section: @_command_section
title: @_command_title
tutorial: @_command_tutorial
# Command Methods ##############################################################################
_command_content: (command)->
return if @missingCurrent command, 'tutorial'
return if @missingArgText command
section = @data.getCurrent 'section'
section.content = command.argText
_command_section: (command)->
return if @missingCurrent command, 'tutorial'
section = @data.create command, 'section'
section.tutorial = @data.getCurrent 'tutorial'
_command_title: (command)->
return if @missingCurrent command, 'section'
return if @missingArgText command
section = @data.getCurrent 'section'
return if @duplicateField command, section, 'title'
section.title = command.argText
_command_tutorial: (command)->
return if @missingCurrent command, 'modVersion'
return if @missingArgText command
tutorial = @data.create command, 'tutorial', command.argText
tutorial.name = command.argText
tutorial.modVersion = @data.getCurrent 'modVersion'
@@ -0,0 +1,63 @@
#
# Crafting Guide - tutorial_pe_v1.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
TutorialParserExtensionV1 = require './tutorial_pe_v1'
ParserData = require '../parser_data'
########################################################################################################################
parser = data = null
########################################################################################################################
describe 'tutorial_pe_v1.coffee', ->
beforeEach ->
data = new ParserData
parser = new TutorialParserExtensionV1 data
data.create {}, 'modVersion', 1
describe 'content', ->
it 'assigns content to the current section', ->
data.create {}, 'tutorial', 2
data.create {}, 'section', 3
parser.execute name:'content', argText:'alpha'
data.errors.should.eql []
section = data.getCurrent 'section'
section.content.should.equal 'alpha'
describe 'section', ->
it 'creates a section in the current tutorial', ->
data.create {}, 'tutorial', 2
parser.execute name:'section'
data.errors.should.eql []
section = data.getCurrent 'section'
section.tutorial.id.should.equal 2
describe 'title', ->
it 'assigns a title to the current tutorial', ->
data.create {}, 'tutorial', 2
data.create {}, 'section', 3
parser.execute name:'title', argText:'alpha'
data.errors.should.eql []
section = data.getCurrent 'section'
section.title.should.equal 'alpha'
describe 'tutorial', ->
it 'creates a new tutorial', ->
parser.execute name:'tutorial', argText:'alpha'
data.errors.should.eql []
tutorial = data.getCurrent 'tutorial'
tutorial.name.should.equal 'alpha'
-1
View File
@@ -40,7 +40,6 @@ module.exports = class CraftPage extends BaseModel
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
-86
View File
@@ -1,86 +0,0 @@
#
# Crafting Guide - file_cache.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
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
@@ -1,72 +0,0 @@
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
View File
@@ -47,7 +47,6 @@ module.exports = class ItemPage extends BaseModel
mod.eachRecipe (recipe)=>
if isAcceptable recipe
for outputStack in recipe.output
continue if recipe.isPassThroughFor outputStack.itemSlug
outputItem = @modPack.findItem outputStack.itemSlug, includeDisabled:true
result[outputItem.slug] = outputItem
@@ -7,6 +7,7 @@
.content.view__browse_page
.left
.view__adsense
.right
.tile_container
+2 -20
View File
@@ -12,30 +12,12 @@
.tile_container {
display : flex;
flex-wrap : wrap;
justify-content : space-between;
.view__mod_tile {
flex : 1 1 calc(50% - 12px); // $size-margin-large / 2
margin-right : $size-margin-medium;
max-width : calc(50% - 12px); // $size-margin-large / 2
margin-bottom : $size-margin-large;
}
}
}
}
@include screen-size(tablet) {
.view__browse_page {
.right {
.tile_container {
flex-direction: column;
flex-wrap: nowrap;
align-items: stretch;
.view__mod_tile {
flex: 0 0 auto;
margin-right: 0;
width: 100%;
}
}
}
}
}
@@ -5,6 +5,7 @@
# All rights reserved.
#
AdsenseController = require '../common/adsense/adsense_controller'
ModTileController = require './mod_tile/mod_tile_controller'
PageController = require '../page_controller'
@@ -31,6 +32,8 @@ module.exports = class BrowsePageController extends PageController
# BaseController Overrides #####################################################################
onDidRender: ->
@_adsenseController = @addChild AdsenseController, '.view__adsense', model:'skyscraper'
@$tileContainer = @$('.tile_container')
refresh: ->
@@ -8,12 +8,14 @@
.view__mod_tile {
min-width : $size-minecraft-block * 9;
display: flex;
display : flex;
justify-content : stretch;
align-items : stretch;
a {
@include floating-panel;
flex: 1 1 auto;
max-width: 100%;
@include floating-panel;
border : $size-border-medium solid $color-background-panel;
color : $color-text;
text-decoration : none;
@@ -37,7 +39,6 @@
overflow : hidden;
.title {
max-width: 0;
margin-bottom : $size-margin-medium;
font-family : $font-family-header;
@@ -85,20 +86,3 @@
}
}
}
@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;
}
}
}
}
}
@@ -12,7 +12,6 @@ BaseController = require '../../base_controller'
module.exports = class ModTileController extends BaseController
constructor: (options={})->
options.tagName = 'section'
options.templateName = 'browse_page/mod_tile'
super options
+7 -51
View File
@@ -6,64 +6,20 @@
//
.view__adsense {
left : 50%;
margin : $size-margin-large 0;
.skyscraper {
height : $size-skyscraper-height;
width : $size-skyscraper-width;
position : relative;
transform : translate(-50%);
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;
}
border : 0.1em solid $color-gray-faint;
margin : $size-margin-large 0;
&:first-child {
margin-top : 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;
ins {
position: absolute; top: 0; right: 0; bottom: 0; left: 0;
}
}
}
.view__mod_tile + .view__adsense {
margin-top : 0;
}
@@ -5,132 +5,63 @@
# All rights reserved.
#
BaseController = require '../../base_controller'
########################################################################################################################
module.exports = class AdsenseController
module.exports = class AdsenseController extends BaseController
constructor: (options={})->
options.templateName = 'common/adsense'
super options
constructor: ->
@_adsEnabled = global.env in c.productionEnvs
@_adsPlaced = 0
@_adCount = 0
@_waiting = false
# Public Methods ###############################################################################
fillAdPositions: ->
adType = @_computeAdType()
maxAds = c.adsense[adType].slotIds.length - @_adsPlaced
return if maxAds is 0
if @_adCount > 0
return unless @$sidebar and @$mainBody
return unless @_adCount < @_computeMaxAds()
return unless @_adCount < c.adsense.slotIds.length
logger.verbose => "Looking to place #{maxAds} more #{adType} ads..."
$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()
reset: ->
@_adsPlaced = 0
# BaseController Overrides #####################################################################
onDidRender: ->
@$mainBody = $('.page > .content > .right')
@$sidebar = $('.page > .content > .left')
super
refresh: ->
@_waitForPageReadiness()
super
# Private Methods #############################################################################
_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
_computeMaxAds: ->
return null unless @$sidebar
result = Math.floor @$sidebar.height() / (c.adsense.skyscraper.height + c.adsense.skyscraper.margin)
return result
_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: ->
_loadAds: ->
return unless @_adsEnabled
logger.info "Loading Google AdSense ads"
logger.info "Loading #{@_adCount} Google AdSense ads"
try
global.adsbygoogle ||= []
global.adsbygoogle.push {}
@@ -139,12 +70,11 @@ module.exports = class AdsenseController
_waitForPageReadiness: ->
return if @_waiting
adCount = @_computeAdCount @_computeAdType()
if adCount is 0
if @$sidebar.height() < c.adsense[@model].height / 2
logger.verbose "Adsense is waiting for room to insert ads"
@_waiting = true
_.delay (=> @_waiting = false; @_waitForPageReadiness()), c.adsense.readinessCheckInterval
_.delay (=> @_waiting = false; @_waitForPageReadiness()), c.duration.normal
else
logger.verbose "Adsense is ready to fill ads"
@fillAdPositions()
@@ -9,5 +9,5 @@
.item_container
.empty_placeholder nothing
.buttons
.button.item-selector: .bezel: p add<span class="full"> an item</span>...
.button.item-selector: .bezel: p add an item...
.button.clear: .bezel: p clear
@@ -38,24 +38,12 @@
margin-top: $size-margin-large;
.item-selector {
flex : 80%;
flex : 85%;
margin-right : $size-margin-medium;
@include screen-size(mobile) {
flex: 50%;
.full {
display: none;
}
}
}
.clear {
flex : 20%;
@include screen-size(mobile) {
flex: 50%;
}
flex : 15%;
}
}
}
@@ -73,12 +61,8 @@
.quantity {
padding-right : $size-margin-medium;
input {
width : 3em;
width: 3em;
font-size : $font-size-xlarge;
@include screen-size(mobile) {
width : 2em;
}
}
}
@@ -17,23 +17,3 @@
}
}
}
@include screen-size(tablet) {
.view__item_group {
.panel {
.view__item_tile {
width: calc(50% - 8px); // $size-margin-xsmall * 2
}
}
}
}
@include screen-size(mobile) {
.view__item_group {
.panel {
.view__item_tile {
width: 100%;
}
}
}
}
@@ -10,7 +10,7 @@
overflow: visible;
display: flex;
align-items: flex-start;
align-items: center;
img {
height: $size-minecraft-block;
@@ -22,19 +22,9 @@
font-family: $font-family-text;
font-size: $font-size-medium;
line-height: $size-minecraft-block;
overflow: hidden;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
@include screen-size(mobile) {
.view__item_tile {
a {
p {
white-space: normal;
}
}
}
}
@@ -48,6 +48,7 @@
}
&.selected {
flex : 0 0 auto;
background : $color-active-hover;
.name {
@@ -9,6 +9,6 @@
.view__item_selector_popup
.search
img(src='/images/search.png')
input(placeholder='enter an item name')
input(placeholder='enter the name of an item')
img.close(src='/images/close.png')
.results
@@ -9,7 +9,6 @@
}
.view__item_selector_popup {
max-width: $size-minecraft-block * 20;
height : 66%;
background : $color-background-panel;
@@ -36,7 +35,6 @@
input {
flex: 1 1 100%;
min-width: $font-size-large * 10;
background: none;
border: 0;
@@ -52,15 +50,8 @@
.results {
flex: 1 1 100%;
position: relative;
overflow-x: hidden;
overflow-y: scroll;
}
}
@include screen-size(mobile) {
.view__item_selector_popup {
position: fixed; top: 0; left: 0; height: 100%; width: 100%;
}
}
@@ -71,10 +71,10 @@ module.exports = class ItemSelectorController extends BaseController
when c.key.enter
@_chooseSelected()
return false
when c.key.downArrow
when c.key.down
@_selectNext()
return false
when c.key.upArrow
when c.key.up
@_selectPrevious()
return false
@@ -82,7 +82,6 @@ module.exports = class ItemSelectorController extends BaseController
@model.hint = @$hintField.val()
@tryRefresh()
@onResultSelected @_elementControllers[0]
return false
onPopupClicked: (event)->
return false
@@ -91,7 +90,6 @@ module.exports = class ItemSelectorController extends BaseController
@_session.resolve controller.model.slug
@_session = null
@_close()
return false
onResultSelected: (controller)->
for c in @_elementControllers
@@ -100,8 +98,6 @@ module.exports = class ItemSelectorController extends BaseController
if controller?
controller.selected = true
return false
onScreenClicked: (event)->
@_close()
return false
@@ -173,8 +169,6 @@ module.exports = class ItemSelectorController extends BaseController
@_elementControllers.pop().remove()
_selectNext: ->
return unless @_elementControllers.length > 0
for i in [0...(@_elementControllers.length - 1)] by 1
controller = @_elementControllers[i]
nextController = @_elementControllers[i + 1]
@@ -185,13 +179,7 @@ module.exports = class ItemSelectorController extends BaseController
@_showElement nextController.$el
return
controller = @_elementControllers[0]
controller.selected = true
@_showElement controller.$el
_selectPrevious: ->
return unless @_elementControllers.length > 0
for i in [1...@_elementControllers.length] by 1
previousController = @_elementControllers[i - 1]
controller = @_elementControllers[i]
@@ -202,10 +190,6 @@ module.exports = class ItemSelectorController extends BaseController
@_showElement previousController.$el
return
controller = @_elementControllers[@_elementControllers.length - 1]
controller.selected = true
@_showElement controller.$el
_showElement: ($el)->
top = $el.position().top + @$resultsContainer.scrollTop()
@$resultsContainer.animate {scrollTop:top}, c.duration.snap
@$resultsContainer.css 'scrollTop', top
@@ -128,15 +128,3 @@
}
}
}
@include screen-size(mobile) {
.view__markdown_section {
.creating {
padding-bottom: $size-margin-xlarge;
}
.footer {
display: none;
}
}
}
@@ -60,15 +60,16 @@ module.exports = class MarkdownSectionController extends BaseController
# Event Methods ################################################################################
onCancelClicked: (event)->
event.preventDefault()
@model = @_originalModel
@resetToDefaultState()
@_imageListController.reset()
@_updatePreview()
return false
onEditClicked: (event)->
return false unless @editable
event.preventDefault()
return unless @editable
@_originalModel = @model
@state = @State.waiting
@@ -80,22 +81,22 @@ module.exports = class MarkdownSectionController extends BaseController
@state = @State.appologizing
w(true).delay(@_confirmDuration).then => @resetToDefaultState()
return false
onQuestionClicked: (event)->
event.preventDefault()
@_enterFeedback ''
return false
onPreviewClicked: (event)->
event.preventDefault()
@_updatePreview()
@state = @State.previewing
return false
onReturnClicked: (event)->
event.preventDefault()
@state = @State.editing
return false
onSaveClicked: (event)->
event.preventDefault()
@state = @State.waiting
@_endEditing()
.then =>
@@ -108,11 +109,9 @@ module.exports = class MarkdownSectionController extends BaseController
.then =>
@state = @State.viewing
return false
onTextChanged: (event)->
event.preventDefault()
@model = @$textarea.val()
return false
# Property Methods #############################################################################
+2 -6
View File
@@ -9,16 +9,12 @@
display: flex;
.input {
flex: 0 0 auto;
flex: 1 1 50%;
align-items : center;
display : flex;
flex-direction : column;
.view__crafting_grid {
flex: 0 0 auto;
}
.tool {
margin-top : $size-margin-small;
@@ -28,7 +24,7 @@
}
.output {
flex: 1 1 100%;
flex: 1 1 50%;
margin-left: $size-margin-medium;
display : flex;
+1 -3
View File
@@ -6,9 +6,7 @@
//-
.view__stack
.quantity
input
.hover
.quantity: input
.icon: img(src='')
.name: a
.action.first(style="display: none")
-43
View File
@@ -21,7 +21,6 @@
.quantity {
padding-right : $size-margin-medium;
position : relative;
text-align : right;
input {
@@ -33,38 +32,6 @@
font-size : $font-size-large;
text-align : right;
}
.hover {
$backgroundColor: fade-out($color-black, 0.25);
display: none;
position: absolute; left: 50%; top: -($font-size-large * 2);
transform: translateX(-50%);
background: $backgroundColor;
color: $color-white;
font-family: $font-family-control;
font-size: $font-size-medium;
padding: $size-margin-small;
z-index: $layer-dialog;
&:after {
position: absolute; top: 100%; left: calc(50% - 9px); // $font-size-large / 2
content: "";
width: 0;
height: 0;
border-style: solid;
border-width: $size-margin-medium $size-margin-medium 0 $size-margin-medium;
border-color: $backgroundColor transparent transparent transparent;
}
}
&:hover {
.hover.enabled {
display: block;
}
}
}
.icon {
@@ -117,13 +84,3 @@
}
}
}
@include screen-size(mobile) {
.view__stack.editable {
.quantity {
input {
width : 1.5em;
}
}
}
}
@@ -19,7 +19,6 @@ Events:
module.exports = class StackController extends BaseController
@::MAX_QUANTITY = 9999
@::STACK_SIZE = 64
constructor: (options={})->
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
@@ -108,7 +107,6 @@ module.exports = class StackController extends BaseController
@$image = @$('.icon img')
@$nameLink = @$('.name a')
@$quantityField = @$('.quantity input')
@$quantityHover = @$('.quantity .hover')
@$secondAction = @$('.action.second')
@$secondButton = @$('.action.second .button')
super
@@ -126,7 +124,6 @@ module.exports = class StackController extends BaseController
@$quantityField.val quantityText
@_refreshButtons()
@_refreshHover()
if @_editable
@$el.addClass 'editable'
@@ -172,20 +169,6 @@ module.exports = class StackController extends BaseController
when 'remove' then $label.text '-'
when 'up' then $label.text '⬆︎'
_refreshHover: ->
if @model.quantity > @STACK_SIZE
remainder = @model.quantity % @STACK_SIZE
stacks = (@model.quantity - remainder) / @STACK_SIZE
text = "#{stacks}x#{@STACK_SIZE}"
text += "+#{remainder}" if remainder > 0
@$quantityHover.text text
@$quantityHover.addClass 'enabled'
else
@$quantityHover.text ''
@$quantityHover.removeClass 'enabled'
_updateButtonType: ($button, type)->
for currentType in ['check', 'down', 'remove', 'up']
$button.removeClass currentType
@@ -1,18 +0,0 @@
//-
//- Crafting Guide - twitter.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__twitter
.left
img(src="/images/twitter.png")
.right
.instructions Follow us for updates!
a(
href="https://twitter.com/Crafting_Guide"
class="twitter-follow-button"
data-show-count="false"
data-size="large"
) Follow @Crafting_Guide
@@ -1,62 +0,0 @@
//
// Crafting Guide - news_page.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__twitter {
display: flex;
& > .left {
flex: 1 1 50%;
display: flex;
align-items: center;
justify-content: center;
img {
height: $size-minecraft-block * 4;
width: $size-minecraft-block * 4;
}
}
& > .right {
flex: 1 1 50%;
display: flex;
flex-direction: column;
align-items: center;
.instructions {
flex: 1 1 50%;
margin-bottom: $size-margin-medium;
margin-top: $size-margin-medium;
font-family: $font-family-header;
font-size: $font-size-xlarge;
text-align: center;
}
iframe {
flex: 1 1 50%;
}
}
}
@include screen-size(mobile) {
.view__twitter {
& > .left {
img {
height: $size-minecraft-block * 3;
width: $size-minecraft-block * 3;
}
}
& > .right {
.instructions {
font-size: $font-size-xlarge;
}
}
}
}
+1 -1
View File
@@ -6,5 +6,5 @@
//-
.view__video
iframe(src="", frameborder="0", allowfullscreen="true")
iframe(width="356", height="267", src="", frameborder="0", allowfullscreen="true")
.caption: p
-4
View File
@@ -11,10 +11,6 @@
align-items: center;
justify-content: center;
iframe {
max-width: 100%;
}
.caption {
margin-top: $size-margin-small;
+17 -9
View File
@@ -5,8 +5,16 @@
//- All rights reserved.
//-
- var quarryHref = '/craft/quarry'
- var quarry
- var quantumSuitHref = '/craft/quantumsuit_bodyarmor:quantumsuit_boots:quantumsuit_helmet:quantumsuit_leggings'
- var ic2Href = '/browse/industrial_craft_2'
- var solarPanelHref = '/craft/solar_panel_vi'
- var solarFluxHref = '/browse/solar_flux'
.content.view__craft_page
.left
.view__adsense
.right
section.instructions
@@ -18,19 +26,19 @@
p.
Not sure what you'd like to make? Try one of these:
.items
a(href="/" data-slug="potion_of_strength_ii_1_30")
a(href="/craft/potion_of_strength_ii_1_30")
img(src="/data/minecraft/items/potion_of_strength_ii_1_30/icon.png")
a(href="/" data-slug="quarry")
a(href="/craft/quarry")
img(src="/data/buildcraft/items/quarry/icon.png")
a(href="/" data-slug="quantumsuit_bodyarmor:quantumsuit_boots:quantumsuit_helmet:quantumsuit_leggings")
a(href="/craft/quantumsuit_bodyarmor:quantumsuit_boots:quantumsuit_helmet:quantumsuit_leggings")
img(src="/data/ic2_classic/items/quantumsuit_helmet/icon.png")
a(href="/" data-slug="solar_panel_vi")
a(href="/craft/solar_panel_vi")
img(src="/data/solar_flux/items/solar_panel_vi/icon.png")
a(href="/" data-slug="crystal_chest")
a(href="/craft/crystal_chest")
img(src="/data/iron_chests/items/crystal_chest/icon.png")
a(href="/" data-slug="digital_miner")
a(href="/craft/digital_miner")
img(src="/data/mekanism/items/digital_miner/icon.png")
a(href="/" data-slug="2.tesseract")
a(href="/craft/2.tesseract")
img(src="/data/thermal_expansion/items/tesseract/icon.png")
section.want
@@ -43,6 +51,8 @@
.panel
.view__inventory.large.editable
section.view__craftsman_working
section.need
h2 Need to Gather
.panel
@@ -51,5 +61,3 @@
section.steps
h2 Steps
.panel
section.view__craftsman_working
@@ -49,14 +49,4 @@
}
}
}
&.outdated {
section.need .panel {
background: $color-white url('/images/panel-background-outdated.png') repeat;
}
section.steps .panel {
background: $color-white url('/images/panel-background-outdated.png') repeat;
}
}
}
@@ -5,21 +5,19 @@
# All rights reserved.
#
AdsenseController = require '../common/adsense/adsense_controller'
BaseController = require '../base_controller'
CraftPage = require '../../models/site/craft_page'
Craftsman = require '../../models/crafting/craftsman'
CraftsmanWorkingController = require './craftsman_working/craftsman_working_controller'
InventoryController = require '../common/inventory/inventory_controller'
PageController = require '../page_controller'
SimpleInventory = require '../../models/crafting/simple_inventory'
StepController = require './step/step_controller'
########################################################################################################################
module.exports = class CraftPageController extends PageController
@::SCROLL_BUFFER = 8 # px
constructor: (options={})->
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
if not options.modPack? then throw new Error 'options.modPack is required'
@@ -35,9 +33,10 @@ module.exports = class CraftPageController extends PageController
@_router = options.router
@_storage = options.storage
# Event Methods ################################################################################
# c.event Methods ################################################################################
onHaveInventoryChanged: ->
logger.warning => "storing have: #{@model.craftsman.have.unparse()}"
@_storage.store 'crafting-plan:have', @model.craftsman.have.unparse()
onMoveNeedToHave: (itemSlug)->
@@ -51,39 +50,27 @@ module.exports = class CraftPageController extends PageController
@model.craftsman.want.remove itemSlug
@onWantInventoryChanged()
onSampleClicked: (event)->
$el = $(event.target)
while $el.length > 0
inventoryText = $el.attr 'data-slug'
break if inventoryText
$el = $el.parent()
if inventoryText?
sampleInventory = new SimpleInventory
sampleInventory.parse inventoryText
@model.craftsman.want.addInventory sampleInventory
@_scrollTo @$workingSection
return false
onWantInventoryChanged: ->
text = @model.craftsman.want.unparse()
url = c.url.crafting inventoryText:text
@router.navigate url
if @model.craftsman.want.isEmpty
@model.craftsman.have.clear()
# PageController Overrides #####################################################################
getMetaDescription: ->
return c.text.craftDescription()
getTitle: ->
description = @model.craftsman.want.toDescription()
return null unless description?
return "Crafting Plan for #{description}"
return 'Craft'
# BaseController Overrides #####################################################################
onDidRender: ->
@_adsenseController = @addChild AdsenseController, '.view__adsense', model:'skyscraper'
@_wantInventoryController = @addChild InventoryController, '.want .view__inventory',
firstButtonType: 'remove'
imageLoader: @_imageLoader
@@ -102,6 +89,7 @@ module.exports = class CraftPageController extends PageController
router: @_router
@_haveInventoryController.on c.event.button.first, (controller, itemSlug)=>
@onRemoveFromHaveInventory itemSlug
@_haveInventoryController.on c.event.change, (c)=> @onHaveInventoryChanged()
@_needInventoryController = @addChild InventoryController, '.need .view__inventory',
editable: false
@@ -114,7 +102,6 @@ module.exports = class CraftPageController extends PageController
@_workingSectionController = @addChild CraftsmanWorkingController, '.view__craftsman_working',
model: @model.craftsman
@_workingSectionController.on c.event.click, => @_scrollTo @$workingSection
@$haveSection = @$('section.have')
@$instructionsSection = @$('section.instructions')
@@ -125,37 +112,25 @@ module.exports = class CraftPageController extends PageController
@$toolsSection = @$('section.tools')
@$workingSection = @$('.view__craftsman_working')
if c.screen.type.compute() is c.screen.type.mobile
@$('.view__inventory.large').removeClass 'large'
super
onWillRender: ->
@model.craftsman.have.clear()
@model.craftsman.have.parse @_storage.load('crafting-plan:have')
@model.craftsman.have.on c.event.change, => @onHaveInventoryChanged()
@model.craftsman.want.on c.event.change, => @refresh()
@model.craftsman.on c.event.change + ":stage", =>
return unless @model.craftsman.stage is Craftsman::STAGE.COMPLETE
@_scrollTo @$needSection
super
refresh: ->
@_needInventoryController.model = @model.craftsman.plan?.need
@_refreshOutdated()
@_refreshSectionVisibility()
@_refreshSteps()
@_adsenseController.fillAdPositions()
super
# Backbone.View Overrides ########################################################################
events: ->
return _.extend super,
'click .instructions a': 'onSampleClicked'
'click .instructions a': 'routeLinkClick'
# Private Methods ################################################################################
@@ -163,7 +138,7 @@ module.exports = class CraftPageController extends PageController
controller.model.addToolsTo @model.craftsman.want
_completeStep: (controller)->
controller.markComplete @model.craftsman.have
controller.model.completeInto @model.craftsman.have
_isAddingToolsPossible: (controller)->
tools = controller.model.recipe.tools
@@ -185,12 +160,6 @@ module.exports = class CraftPageController extends PageController
return true
_refreshOutdated: ->
if @model.craftsman.stage is Craftsman::STAGE.OUTDATED
@$el.addClass 'outdated'
else
@$el.removeClass 'outdated'
_refreshSectionVisibility: ->
return unless @_rendered
@@ -203,13 +172,11 @@ module.exports = class CraftPageController extends PageController
if @model.craftsman.want.isEmpty
visibleSections.push el for el in [@$instructionsSection, @$wantSection]
else if @model.craftsman.stage is Craftsman::STAGE.INVALID
visibleSections.push el for el in [@$wantSection, @$haveSection, @$workingSection]
visibleSections.push el for el in [@$wantSection, @$workingSection]
else if not @model.craftsman.complete
visibleSections.push el for el in [@$wantSection, @$haveSection, @$workingSection]
visibleSections.push el for el in [@$wantSection, @$workingSection]
else
visibleSections.push el for el in [@$haveSection, @$wantSection, @$workingSection]
if @model.craftsman.plan?
visibleSections.push el for el in [@$needSection, @$stepsSection]
visibleSections.push el for el in [@$haveSection, @$needSection, @$stepsSection, @$wantSection]
@hide $el for $el in allSections
@show $el for $el in visibleSections
@@ -242,28 +209,3 @@ module.exports = class CraftPageController extends PageController
while @_stepControllers.length > index
@_stepControllers.pop().remove()
_scrollTo: ($targetEl)->
@_$scrollTarget = $targetEl
if @_scrollTimer?
clearTimeout @_scrollTimer
@_scrollTimer = setTimeout(=>
$el = @_$scrollTarget
@_scrollTimer = null
minScrollPosition = $el.offset().top + $el.height() - $(window).height() - @SCROLL_BUFFER
maxScrollPosition = $el.offset().top + @SCROLL_BUFFER
scrollPosition = $(window).scrollTop()
scrollTarget = scrollPosition
if scrollPosition > maxScrollPosition
scrollTarget = maxScrollPosition
else if scrollPosition < minScrollPosition
scrollTarget = minScrollPosition
else
return
$('html, body').animate {scrollTop:scrollTarget}, c.duration.normal
, c.duration.slow)
@@ -5,16 +5,11 @@
//- All rights reserved.
//-
.view__craftsman_working
.panel
.view__item_group.section
.content
.waiting
img(src='/images/wait.gif')
.message
p
.count
p
.button: .bezel: p Calculate
.outdated
.message Your crafting plan is out of date!
.button: .bezel: p Recalculate
@@ -6,78 +6,32 @@ All rights reserved.
*/
.view__craftsman_working {
.panel {
.content {
background: $color-white;
box-shadow: none;
align-items : center;
display : flex;
flex-direction : column;
padding : $size-margin-xlarge;
.waiting {
margin-bottom : $size-margin-xlarge;
margin-bottom: $size-margin-xlarge;
}
.message {
margin-bottom : $size-margin-medium;
margin-bottom: $size-margin-medium;
p {
font-family : $font-family-header;
font-size : $font-size-large;
text-align : center;
font-family: $font-family-header;
font-size: $font-size-large;
}
}
.count {
p {
font-family : $font-family-header;
font-size : $font-size-medium;
text-align : center;
}
}
.button {
margin-top : $size-margin-large;
min-width: $size-minecraft-block * 6;
}
}
.outdated {
position: fixed; top: 0; left: 0; right: 0;
background : fade-out($color-white, 0.15) url('/images/panel-background-outdated.png');
border-bottom : $size-border-medium solid $color-black;
display : flex;
flex-direction : column;
align-items : center;
.message {
margin-top : $size-margin-medium;
font-family : $font-family-header;
font-size : $font-size-xlarge;
text-align : center;
}
.button {
width : $size-minecraft-block * 6;
margin : $size-margin-medium;
}
}
}
@include screen-size(not-mobile) {
.view__craftsman_working {
.panel {
.button {
margin-top : $size-margin-large;
height : $size-minecraft-block * 2;
width : $size-minecraft-block * 10;
p {
font-size : $font-size-xxlarge;
transform : translateY(-25%);
}
font-family: $font-family-header;
font-size: $font-size-medium;
}
}
}
@@ -12,94 +12,48 @@ Craftsman = require '../../../models/crafting/craftsman'
module.exports = class CraftsmanWorkingController extends BaseController
@::HIDE_TIMER_DURATION = 2000
constructor: (options={})->
if not options.model then throw new Error 'options.model is required'
options.templateName = 'craft_page/craftsman_working'
super options
@_hideTimer = null
# Event Methods ################################################################################
onButtonClicked: ->
@trigger c.event.click
@model.reset()
@model.work()
return false
@model.on c.event.change, => @_refreshStatusText()
# BaseController Methods #######################################################################
onDidModelChange: ->
@refresh()
onDidRender: ->
@$button = @$('.button')
@$count = @$('.count p')
@$message = @$('.message p')
@$outdated = @$('.outdated')
@$count = @$('.count p')
@$waiting = @$('.waiting')
@_controls = [@$button, @$count, @$message, @$outdated, @$waiting]
super
refresh: ->
return unless @$message? and @$count?
button = count = message = outdated = waiting = null
switch @model.stage
when Craftsman::STAGE.READY
message = 'Ready to compute crafting plan!'
count = 'Click "Calculate" to continue.'
button = true
when Craftsman::STAGE.GRAPHING
message = 'Researching recipes...'
count = "Found #{@model.stageCount} recipes so far..."
watiing = true
when Craftsman::STAGE.PLANNING
message = 'Figuring out possible crafting plans...'
count = "Found #{@model.stageCount} possibilities so far..."
watiing = true
when Craftsman::STAGE.ANALYZING
message = 'Looking for the best plan...'
count = "Finished checking #{@model.stageCount} so far..."
watiing = true
when Craftsman::STAGE.COMPLETE
message = 'Crafting plan is complete.'
when Craftsman::STAGE.INVALID
message = 'Couldn\'t make a crafting plan!'
count = 'Please report this problem using the Feedback box.'
when Craftsman::STAGE.OUTDATED
message = 'Your crafting plan is out of date!'
count = 'Click "Calculate" to re-compute.'
button = true
outdated = true
@hide(control) for control in @_controls
if button?
@show @$button
if count?
@show @$count
@$count.html count
if message?
@show @$message
@$message.html message
if outdated?
@show @$outdated
if waiting?
@show @$waiting
@_refreshStatusText()
super
# Backbone.View Overrides ######################################################################
# Private Methods ##############################################################################
events: ->
return _.extend super,
'click .button': 'onButtonClicked'
_refreshStatusText: ->
return unless @$message? and @$count?
@show @$waiting
switch @model.stage
when Craftsman::STAGE.WAITING
@$message.html 'Preparing crafting calculation...'
@$count.html ''
when Craftsman::STAGE.GRAPHING
@$message.html 'Researching recipes...'
@$count.html "Found #{@model.stageCount} recipes so far..."
when Craftsman::STAGE.PLANNING
@$message.html 'Figuring out possible crafting plans...'
@$count.html "Found #{@model.stageCount} possibilities so far..."
when Craftsman::STAGE.ANALYZING
@$message.html 'Looking for the best plan...'
@$count.html "Finished checking #{@model.stageCount} so far..."
when Craftsman::STAGE.COMPLETE
@$message.html 'All done!'
@$count.html ''
when Craftsman::STAGE.INVALID
@$message.html 'Couldn\'t make a crafting plan'
@$count.html ''
@hide @waiting
+2 -2
View File
@@ -11,5 +11,5 @@
.view__inventory
.view__recipe
.buttons
a.button.tool: .bezel: p <span class="full">show plan for </span>tools
.button.complete: .bezel: p complete<span class="full"> step</span>
.button.complete: .bezel: p complete step
.button.tool: .bezel: p make this tool
-50
View File
@@ -31,10 +31,6 @@
.view__inventory {
flex: 50%;
margin-left: $size-margin-large;
.buttons {
display: none;
}
}
.view__recipe {
@@ -56,51 +52,5 @@
margin-left: $size-margin-medium;
}
}
a {
border: 0;
.bezel {
border-top : 3px solid $color-button-highlight;
border-left : 3px solid $color-button-highlight;
border-bottom : 6px solid $color-button-shadow;
border-right : 3px solid $color-button-shadow;
}
}
}
&.complete {
opacity: 0.25;
h3 {
text-decoration: line-through;
}
}
}
@include screen-size(mobile) {
.view__step {
.main_content {
flex-direction: column-reverse;
.view__inventory {
flex: 0 0 auto;
margin: $size-margin-large $size-margin-large 0 $size-margin-large;
}
.view__recipe {
flex: 0 0 auto;
}
}
.buttons {
opacity: 1.0;
.button {
.full {
display: none;
}
}
}
}
}
@@ -8,7 +8,6 @@
BaseController = require '../../base_controller'
InventoryController = require '../../common/inventory/inventory_controller'
RecipeController = require '../../common/recipe/recipe_controller'
SimpleInventory = require '../../../models/crafting/simple_inventory'
########################################################################################################################
@@ -30,19 +29,16 @@ module.exports = class StepController extends BaseController
@onComplete = options.onComplete or (controller)-> # do nothing
@onAddTools = options.onAddTools or (controller)-> # do nothing
# Public Methods ###############################################################################
markComplete: (targetInventory)->
@$el.addClass 'complete'
@$completeButton.addClass 'disabled'
@model.completeInto targetInventory
# Event Methods ################################################################################
onCompleteButtonClicked: (event)->
return if @$completeButton.hasClass 'disabled'
@onComplete this
onToolButtonClicked: (event)->
return if @$toolButton.hasClass 'disabled'
@onAddTools this
# BaseController Overrides #####################################################################
onDidRender: ->
@@ -67,9 +63,6 @@ module.exports = class StepController extends BaseController
onWillChangeModel: (oldModel, newModel)->
if not newModel? then throw new Error 'model cannot be null'
if @rendered
@$el.removeClass 'complete'
@$completeButton.removeClass 'disabled'
return super
refresh: ->
@@ -90,19 +83,11 @@ module.exports = class StepController extends BaseController
events: ->
return _.extend super,
'click .button.complete': 'onCompleteButtonClicked'
'click .button.tool': 'onToolButtonClicked'
# Private Methods ##############################################################################
_refreshToolButton: ->
inventory = new SimpleInventory {}, modPack:@_modPack
for toolStack in @model.recipe.tools
inventory.add toolStack.itemSlug, toolStack.quantity
inventory.localize()
inventoryText = inventory.unparse()
@$toolButton.attr 'href', "/craft/#{inventoryText}"
@$toolButton.attr 'target', inventoryText
if @canAddTools this
@$toolButton.removeClass 'disabled'
else
+5 -7
View File
@@ -15,10 +15,6 @@
display: flex;
@include screen-size(mobile) {
display: none;
}
.form-content {
flex: 1 1 100%;
@@ -77,11 +73,13 @@
background: $color-gray-light;
border: $size-border-medium solid $color-black;
border-left: 0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
p {
position: absolute; top: 50%; left: 50%;
transform: translateX(-50%) rotate(-90deg) translateX(10%);
transform: rotate(-90deg);
font-family: $font-family-header;
font-size: $label-size;
-19
View File
@@ -19,20 +19,12 @@
> .content {
display: flex;
@include screen-size(mobile) {
flex-direction: column;
}
color: $color-text-light;
.left, .center, .right {
flex: 1 1 33%;
margin: $size-margin-large $size-margin-large $size-margin-large 0;
@include screen-size(mobile) {
margin-left: $size-margin-large;
}
display: flex;
flex-direction: column;
@@ -46,7 +38,6 @@
.action {
flex: 1 1 auto;
align-self: center;
margin-top: $size-margin-large;
margin-bottom: $size-margin-large;
display: flex;
@@ -64,13 +55,3 @@
height: $size-minecraft-block;
}
}
@include screen-size(mobile) {
.view__footer {
> .content {
.left, .center, .right {
flex: 0 0 auto;
}
}
}
}
-2
View File
@@ -21,6 +21,4 @@
.button.search: .bezel: p Search
.button.login: .bezel: p Login
.breadcrumb-bar
.breadcrumbs
.extra-nav
+14 -79
View File
@@ -23,22 +23,24 @@
img {
position : relative;
height : $size-minecraft-block * 1.5;
width : $size-minecraft-block * 1.5;
top : -$size-minecraft-block * 0.5;
left : -$size-minecraft-block * 0.5;
height : $size-minecraft-block * 2.0;
width : $size-minecraft-block * 2.0;
}
}
h1 {
flex: 1 1 auto;
white-space: nowrap;
}
.addthis_sharing_toolbox {
flex : 0 0 auto;
min-height : 37px; // actual size of addthis component
min-height : 37px;
min-width : 144px;
margin-right : $size-margin-small;
&.placeholder {
border : 1px solid orange;
text-align : center;
@@ -60,38 +62,24 @@
align-items : center;
display : flex;
flex-wrap : wrap;
justify-content : center;
justify-content : space-around;
padding : $size-margin-medium $size-margin-medium;
padding-top : 0;
.button {
flex-grow : 1;
flex-shrink : 1;
min-width : 3 * $size-minecraft-block;
flex : 1 1 20%;
min-width : 4 * $size-minecraft-block;
max-width : 5 * $size-minecraft-block;
margin-top : $size-margin-medium;
margin-right : $size-margin-small;
&:last-child {
margin-right: 0;
margin-top: $size-margin-medium;
}
}
}
.breadcrumb-bar {
background: $color-screen;
padding: 0 ($size-margin-medium + $size-margin-large);
font-family: $font-family-control;
font-size: $font-size-medium;
line-height: $font-size-medium + 2 * $size-margin-small;
display: flex;
.breadcrumbs {
flex: 1 1 75%;
background: $color-screen;
font-family: $font-family-control;
font-size: $font-size-medium;
text-indent: -$size-margin-large;
padding: $size-margin-small $size-margin-medium;
&:before {
content: "";
@@ -106,57 +94,4 @@
margin: 0 $size-margin-medium;
}
}
.extra-nav {
flex: 1 1 25%;
align-self: flex-end;
text-align: right;
}
}
}
@include screen-size(desktop) {
.view__header {
.top-row {
a {
img {
top : -$size-minecraft-block * 0.5;
left : -$size-minecraft-block * 0.5;
height : $size-minecraft-block * 2.0;
width : $size-minecraft-block * 2.0;
}
}
}
}
}
@include screen-size(tablet) {
.view__header {
.top-row {
h1 {
font-size : $font-size-xlarge;
}
}
}
}
@include screen-size(mobile) {
.view__header {
.top-row {
display : none;
}
.divider {
.button.login {
display: none;
}
}
.breadcrumb-bar {
.extra-nav {
display: none;
}
}
}
}
@@ -7,7 +7,6 @@
BaseController = require '../base_controller'
ItemSelectorController = require '../common/item_selector/item_selector_controller'
UrlParams = require '../url_params'
########################################################################################################################
@@ -44,11 +43,9 @@ module.exports = class HeaderController extends BaseController
@router.navigate '/news', trigger:true
return false
onSearch: (event, hint='')->
@_selector.launch hint
onSearch: ->
@_selector.launch()
.then (itemSlug)=>
return unless itemSlug?
itemDisplay = @_modPack.findItemDisplay itemSlug
@router.navigate itemDisplay.itemUrl, trigger:true
return false
@@ -65,11 +62,8 @@ module.exports = class HeaderController extends BaseController
@$('.addthis_sharing_toolbox').addClass 'placeholder'
@$breadcrumbs = @$('.breadcrumbs')
@$extraNav = @$('.extra-nav')
@$loginButtonLabel = @$('.button.login p')
@$title = $('title')
@_checkForSearchRequest()
super
onWillChangeModel: (oldModel, newModel)->
@@ -81,8 +75,6 @@ module.exports = class HeaderController extends BaseController
@$loginButtonLabel.text if global.site.user? then "Logout" else "Login"
@_refreshBreadcrumbs()
@_refreshExtraNav()
@_refreshSelectedNavButton()
@_refreshTitle()
super
@@ -95,16 +87,10 @@ module.exports = class HeaderController extends BaseController
'click .login': 'onLogin'
'click .news': 'onNews'
'click .search': 'onSearch'
'click .breadcrumb-bar a': 'routeLinkClick'
'click .breadcrumbs a': 'routeLinkClick'
# Private Methods ##############################################################################
_checkForSearchRequest: ->
params = new UrlParams search:{type:'string'}
return unless params.search?
_.delay (=> @onSearch {}, params.search), c.duration.slow
_refreshBreadcrumbs: ->
breadcrumbs = @model?.controller?.getBreadcrumbs()
breadcrumbs ?= []
@@ -120,23 +106,6 @@ module.exports = class HeaderController extends BaseController
else
@hide @$breadcrumbs
_refreshExtraNav: ->
extraNavContent = @model?.controller?.getExtraNav()
if extraNavContent?
@$extraNav.empty()
@$extraNav.append extraNavContent
@show @$extraNav
else
@hide @$extraNav
_refreshSelectedNavButton: ->
return unless @model?.page?
@$('.button').removeClass 'active'
@$(".button.#{@model.page}").addClass 'active'
_refreshTitle: ->
title = @model?.controller?.getTitle()
if title
+2 -4
View File
@@ -12,7 +12,7 @@ html
link(href="/images/favicon.png" rel="icon" type="image/png")
link(href="/main.css" rel="stylesheet" type="text/css" )
meta(charset="UTF-8")
meta(name="viewport" content="user-scalable=no, width=device-width")
meta(name="viewport" content="width=480")
body
.view__feedback
@@ -21,7 +21,7 @@ html
.page
.view__header
.content.hidden
.content(style="display: none")
.content-loading
img(src="/images/wait.gif")
@@ -32,6 +32,4 @@ html
include ../includes/_google_adsense.jade
include ../includes/_google_analytics.jade
include ../includes/_google_sitelinks.jade
include ../includes/_twitter_follow.jade
+3 -6
View File
@@ -36,22 +36,19 @@ html {
}
& > *:first-child {
flex: 1 1 100%;
flex: 0 0 50%;
box-shadow : $size-margin-small $size-margin-small $size-margin-large $color-shadow;
}
}
.page {
margin : $size-margin-large 0;
max-width : $size-page-width;
width : 100%;
@include screen-size(desktop) {
margin : $size-margin-large 0;
box-shadow : $size-margin-small $size-margin-small $size-margin-large $color-shadow;
}
background : $color-background-page;
box-shadow : $size-margin-small $size-margin-small $size-margin-large $color-shadow;
display : flex;
flex-direction : column;
+2 -1
View File
@@ -7,6 +7,7 @@
.content.view__item_page
.left
.view__adsense
.right
.about
@@ -16,7 +17,7 @@
.title
a.sourceMod
.button-row
.button.craftingPlan: .bezel: p View <span class="full">Crafting </span>Plan
.button.craftingPlan: .bezel: p View Crafting Plan
.right
p Official Link:
a.officialLink project page
+1 -48
View File
@@ -48,7 +48,7 @@
.button {
flex: 1 1 100%;
max-width: $size-minecraft-block * 10;
max-width: 66%;
}
}
}
@@ -58,50 +58,3 @@
}
}
}
@include screen-size(mobile) {
.view__item_page {
.about {
flex-direction: column;
& > .left {
margin: 0 $size-margin-medium;
display: flex;
flex-direction: column;
align-items: center;
img {
}
}
& > .center {
flex: 0 0 auto;
margin: 0 $size-margin-medium;
.sourceMod, .title {
text-align: center;
}
a.sourceMod {
}
.button-row {
flex: 0 0 auto;
margin: $size-margin-large 0;
.button {
max-width: $size-minecraft-block * 8;
.full {
display: none;
}
}
}
}
& > .right {
margin: 0 $size-margin-medium;
}
}
}
}
@@ -5,6 +5,7 @@
# All rights reserved.
#
AdsenseController = require '../common/adsense/adsense_controller'
EditableFile = require '../../models/site/editable_file'
Item = require '../../models/game/item'
ItemGroupController = require '../common/item_group/item_group_controller'
@@ -41,7 +42,6 @@ module.exports = class ItemPageController extends PageController
@_itemSlug = options.itemSlug
@_modPack = options.modPack
@_router = options.router
@_triggerEditing = options.login
@_modPack.on c.event.change, =>
@tryRefresh()
@@ -68,13 +68,6 @@ module.exports = class ItemPageController extends PageController
$("<b>#{display.itemName}</b>")
]
getExtraNav: ->
itemSlug = @_modPack.chooseRandomItem()
return null unless itemSlug?
itemDisplay = @_modPack.findItemDisplay itemSlug
return $("<a href='#{itemDisplay.itemUrl}'>Random Item</a>")
getMetaDescription: ->
return null unless @_itemSlug?
display = @_modPack.findItemDisplay @_itemSlug
@@ -90,11 +83,9 @@ module.exports = class ItemPageController extends PageController
# BaseController Overrides #####################################################################
onDidModelChange: ->
@trigger c.event.change
super
onDidRender: ->
@_adsenseController = @addChild AdsenseController, '.view__adsense', model:'skyscraper', router: @_router
options = imageLoader:@_imageLoader, modPack:@_modPack, router:@_router, show:false
@_multiblockController = @addChild MultiblockViewerController, '.view__multiblock_viewer', options
@_similarItemsController = @addChild ItemGroupController, '.view__item_group.similar', options
@@ -111,7 +102,6 @@ module.exports = class ItemPageController extends PageController
enterFeedback: @_enterFeedback
@$aboutImage = @$('.about img')
@$craftingPlanButton = @$('.button.craftingPlan')
@$name = @$('.about .title')
@$officialLink = @$('.about a.officialLink')
@$sourceModLink = @$('.about a.sourceMod')
@@ -145,15 +135,6 @@ module.exports = class ItemPageController extends PageController
else
@hide @$aboutLinks
if @model.item.isCraftable
@show @$craftingPlanButton
else
@hide @$craftingPlanButton
if @_triggerEditing
@_triggerEditing = false
@_descriptionController.onEditClicked {}
@show()
else
@hide()
@@ -336,7 +317,7 @@ module.exports = class ItemPageController extends PageController
item = @_modPack.findItem @_itemSlug, includeDisabled:true
if item?
if not ItemSlug.equal item.slug, @_itemSlug
@_router.navigate c.url.item(modSlug:item.slug.mod, itemSlug:item.slug.item), trigger:true
router.navigate c.url.item(modSlug:item.slug.mod, itemSlug:item.slug.item), trigger:true
return
@model.item = item
@@ -75,20 +75,3 @@
}
}
}
@include screen-size(mobile) {
.view__multiblock_viewer {
flex-direction: column-reverse;
.left {
flex: 0 0 auto;
border: 0;
}
.right {
flex: 0 0 auto;
margin-bottom: $size-margin-large;
}
}
}
@@ -8,7 +8,7 @@
.view__recipe_detail {
display : flex;
.input, .output {
.input, .output{
flex : 0 1 33%;
h3 {
@@ -20,13 +20,8 @@
}
}
.input {
order: 10;
}
.pattern {
flex : 0 1 33%;
order: 20;
display : flex;
flex-direction : column;
@@ -41,48 +36,9 @@
}
}
.output {
order: 30;
}
&:not(:first-child) {
border-top : 1px solid $color-gray-light;
margin-top : $size-margin-medium;
padding-top : $size-margin-large;
}
}
@include screen-size(mobile) {
.view__recipe_detail {
flex-direction: column;
> :not(:last-child) {
margin-bottom: $size-margin-large;
}
.input, .output {
flex: 0 0 auto;
h3 {
}
.view__inventory {
}
}
.input {
order: 21;
}
.pattern {
flex: 0 0 auto;
.tool {
}
}
&:not(:first-child) {
}
}
}
@@ -1,11 +0,0 @@
//-
//- Crafting Guide - mod_editor_page.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.content.view__mod_editor_page
.left
.right
@@ -1,9 +0,0 @@
//
// Crafting Guide - mod_editor_page.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__mod_editor_page {
}
@@ -1,46 +0,0 @@
#
# Crafting Guide - mod_page_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
PageController = require '../page_controller'
########################################################################################################################
module.exports = class ModPageController extends PageController
constructor: (options={})->
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 = 'mod_editor_page'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
# Event Methods ################################################################################
# Property Methods #############################################################################
# PageController Overrides #####################################################################
getBreadcrumbs: ->
getTitle: ->
# BaseController Overrides #####################################################################
onDidRender: ->
super
refresh: ->
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
# Private Methods ##############################################################################
+3 -5
View File
@@ -1,5 +1,5 @@
//-
//- Crafting Guide - mod_page.jade
//- Crafting Guide - item_page.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
@@ -7,6 +7,7 @@
.content.view__mod_page
.left
.view__adsense
.right
.about
@@ -16,11 +17,8 @@
.title
.author
.description
.modpack
span.checkbox
span.label I have this mod in my modpack
.version
span I'm using version:
span in my mod pack
select
.warning.
<b>PLEASE NOTE:</b> By selecting "no", crafting plans, search, and other features will ignore items
+2 -77
View File
@@ -1,5 +1,5 @@
//
// Crafting Guide - mod_page.scss
// Crafting Guide - item_page.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
@@ -49,44 +49,8 @@
line-height: $font-size-medium * 1.25;
}
.modpack {
margin-top : $size-margin-large;
cursor : pointer;
font-family : $font-family-control;
font-size : $font-size-medium;
display : flex;
.checkbox {
display : inline-block;
height : $font-size-medium;
margin-right : $size-margin-small;
position : relative;
width : $font-size-medium;
background : $color-white;
border : $size-border-small solid $color-black;
}
.label {
color: $color-gray-medium;
}
&.enabled {
.checkbox:before {
position: absolute; top: 0; left: 1.5px; // adjust for glyph position
content: "";
}
.label {
color: $color-black;
}
}
}
.version {
margin-top: $size-margin-medium;
margin-top: $size-margin-large;
font-family: $font-family-control;
font-size: $font-size-medium;
@@ -128,42 +92,3 @@
}
}
}
@include screen-size(mobile) {
.view__mod_page {
.about {
flex-direction: column;
& > * {
margin-left: $size-margin-medium;
margin-right: $size-margin-medium;
}
.left {
margin-bottom: $size-margin-large;
margin-left: $size-margin-medium;
margin-right: $size-margin-medium;
display : flex;
flex-direction : column;
align-items : center;
}
.center {
flex: 0 0 auto;
margin-bottom: $size-margin-xlarge;
margin-left: $size-margin-medium;
margin-right: $size-margin-medium;
.author, .title {
text-align: center;
}
}
.right {
margin-left: $size-margin-medium;
margin-right: $size-margin-medium;
}
}
}
}
@@ -5,6 +5,7 @@
# All rights reserved.
#
AdsenseController = require '../common/adsense/adsense_controller'
BaseController = require '../base_controller'
Item = require '../../models/game/item'
ItemGroupController = require '../common/item_group/item_group_controller'
@@ -28,14 +29,6 @@ module.exports = class ModPageController extends BaseController
# Event Methods ################################################################################
onToggleEnabled: ->
if @model.activeVersion is Mod.Version.None
@model.activeVersion = Mod.Version.Latest
else
@model.activeVersion = Mod.Version.None
return false
onVersionChanged: ->
@model.activeVersion = @$versionSelector.val()
return false
@@ -65,6 +58,8 @@ module.exports = class ModPageController extends BaseController
# BaseController Overrides #####################################################################
onDidRender: ->
@_adsenseController = @addChild AdsenseController, '.view__adsense', model: 'skyscraper'
@$author = @$('.about .author')
@$description = @$('.about .description')
@$documentationLink = @$('.about .documentation')
@@ -72,18 +67,15 @@ module.exports = class ModPageController extends BaseController
@$homePageLink = @$('.about .homePage')
@$itemGroups = @$(".itemGroups")
@$logo = @$('.about img')
@$modpackSection = @$('.modpack')
@$title = @$('.about .title')
@$tutorialsContainer = @$('section.tutorials .panel')
@$tutorialsSection = @$('section.tutorials')
@$versionSection = @$('.version')
@$versionSelector = @$('.version select')
@$warning = @$('.warning')
super
refresh: ->
@_refreshAboutBlock()
@_refreshEnabled()
@_refreshItemGroups()
@_refreshTutorials()
@_refreshVersionSelector()
@@ -94,7 +86,6 @@ module.exports = class ModPageController extends BaseController
events: ->
return _.extend super,
'change .version select': 'onVersionChanged'
'click .modpack': 'onToggleEnabled'
# Private Methods ##############################################################################
@@ -116,19 +107,6 @@ module.exports = class ModPageController extends BaseController
@$logo.attr 'src', ''
@$title.text ''
_refreshEnabled: ->
if @model.slug in c.requiredMods
@hide @$modpackSection
else
@show @$modpackSection
if @model.activeVersion is Mod.Version.None
@$modpackSection.removeClass 'enabled'
@show @$warning
else
@$modpackSection.addClass 'enabled'
@hide @$warning
_refreshItemGroups: ->
@_groupControllers ?= []
groupIndex = 0
@@ -146,7 +124,6 @@ module.exports = class ModPageController extends BaseController
router: @_router
title: if group is Item.Group.Other then 'Items' else group
_.defer =>
@_groupControllers.push controller
@$itemGroups.append controller.$el
controller.render()
@@ -186,16 +163,19 @@ module.exports = class ModPageController extends BaseController
_refreshVersionSelector: ->
modVersions = @model.modVersions
if (@model.activeVersion is Mod.Version.None) or modVersions.length < 2
@hide @$versionSection
else
@show @$versionSection
selectedModVersion = @effectiveModVersion
@$versionSelector.empty()
$option = $("<option value=\"none\">no</option>")
if not @model.enabled
@$warning.css display:''
selectedModVersion = null
$option.attr 'selected', true
else
@$warning.css display:'none'
@$versionSelector.append $option
for modVersion in @model.modVersions
$option = $("<option value=\"#{modVersion.version}\">#{modVersion.version}</option>")
if modVersion is selectedModVersion
+52 -78
View File
@@ -7,86 +7,9 @@
.content.view__news_page
.left
.view__adsense
.right
include ../common/twitter/twitter.jade
section
h3 2016-04-30
.panel
p.
Big news... no, huge news... no, tiny news! Crafting Guide is now fully functional on any size
screen from a 30" desktop monitor all the way down to a 4" phone. Just grab your favorite mobile
device and give it a try!
section
h3 2016-04-24
.panel
p.
I've been knocking off a few more feature requests over the weekend. First, I've made the entire
site respond to being on a smaller screen (tablet-sized for now, phone-sized will be next).
Hopefully, it should just feel pretty natural on whatever screen size, and you won't notice any big
differences from one to the other.
p.
The second small change is that whenever you're looking at a recipe which calls for a <i>lot</i> of
some item, you can hover over the quantity to see what it would be in stacks + the remainder. For
example, 127 translates to: 1x64+63. That should make it easier to think about large builds.
section
h3 2016-04-23
.panel
p.
I've spent some time working on the crafting page today, and I think you'll find it a lot easier to
adjust your plans now. The big difference is that the site no longer automatically calculate.
Instead, you have to deliberately press a button when you're ready for a new plan. This allows you
to check out an existing plan, mark certain steps complete, adjust your inventory, and only when
you're ready, click the button for a new plan. Not a huge change, but I think you'll find yourself
adjusting a not-quite-right plan a lot more often now!
section
h3 2016-04-21
.panel
p.
The most highly-requested mod currently on my list is <a href="/browse/tinkers_construct">Tinker's
Construct</a>, and I'm pleased to announce that it is now available on Crafting Guide! In case you
aren't familiar with it, it adds the ability to create tools by crafting together individual parts
made from different materials, and adds a complex smelting mechanic which allows you to create a
variety of useful metal alloys. This smelting has the side-effect of producing two ingots for every
block of ore as well, so you can very rapidly get to ore-doubling without having to have already got
a complex power system up and running.
p.
There is <i>one</i> thing missing. I didn't include any recipes for the actual tools themselves.
The problem, of course, is that you can mix and match parts made from any of the supported
materials, and that leads to thousands upon thousands of possible tools: way more than would ever be
practical to include in Crafting Guide in the obvious way other items are included.
p.
The plan, then, is to create a feature just for Tinker's Construct which allows you to select a tool
to create, and then pick and choose the material types for each part. You'll then be able to make a
crafting plan for those parts. If that sounds like something you'd use, head over to <a
href="https://github.com/andrewminer/crafting-guide/issues/214">GitHub</a> and add your vote!
section
h3 2016-04-16
.panel
p.
I've been working on adding a new mod, and I ran across a bug which has been in the system for a
while now, but which will cause serious problems with a lot of the recipes in the new mod. So, I put
the mod aside for a bit to fix it.
p.
The bug was that when you are making something like an <a
href="/browse/forge_multipart/oak_wood_cover">Oak Wood Cover</a> the <a
href="/browse/forge_multipart/stone_saw">saw</a> wouldn't be part of the crafting plan. This isn't
such a big deal for most things, but for this new mod, it would have been a major problem.
section
h3 2016-04-14
.panel
p.
In between adding new features to the website, I've taken a little break to add support for
<a href="/browse/projectred">ProjectRed</a>. It takes up the mantle of the retired RedPower mod
and adds a lot of what that one used to provide: logic gates, cables, pneumatic tubes, and lots and
lots of new lights and lamps.
section
h3 2016-04-05
.panel
@@ -156,3 +79,54 @@
p.
With many thanks to <a href="https://github.com/pkmnfrk">Mike Caron</a>, I'm pleased to announce
support for <a href="/browse/storage_drawers">Storage Drawers</a>!
section
h3 2015-12-13
.panel
p.
I'm super excited to announce that I've added full multi-block support for Crafting Guide! For
example, check out a <a href="/browse/big_reactors/5_core_passive_reactor">5-Core Reactor</a>
from Big Reactors, or the <a href="/browse/railcraft/steel_tank_5x5x5">5x5x5 Steel Tank</a> from
Railcraft. I'm really pleased with how it turned out!
section
h3 2015-11-17
.panel
p.
And today, it's <a href="/browse/extra_cells">Extra Cells</a>!
section
h3 2015-11-16
.panel
p.
It's a small one, but also highly requested... Here's <a
href="/browse/computercraft">ComputerCraft</a>!
section
h3 2015-11-14
.panel
p.
One more small one over the weekend... <a href="/browse/advanced_solar_panels">Advanced Solar
Panels</a> has been added to crafting guide!
section
h3 2015-11-14
.panel
p.
For a long while now, <a href="/browse/galacticraft">Galacticraft</a> has been the most
requested mod, and it's finally here! Enjoy!
section
h3 2015-11-10
.panel
p.
Remember me? They guy who runs the site. Yeah, I'm back, and with big news! The site now has
a completely new crafting algorithm. Instead of just taking the first crafting plan it comes up
with, it tries to compute every possible plan, rates them, and then picks the best one. This
also clears up a lot of bugs related to the old algorithm. The one drawback is that some items
can take a bit longer to compute than with the old way, but I think you'll find it's worth it!
p.
On a personal note, I think it's clear that I don't have nearly as much time to dedicate to the
site as I used to. That's mostly because I started a new job which keeps me away from working
on the site so much. I hope to be able to get into a more regular pace now things my personal
life has settled down again.

Some files were not shown because too many files have changed in this diff Show More