Massive refactoring to clean up models

* Tweak style of quantity label in output box to be more visible
* Move data files for all mods under a directory for the specific
  version of the mod
* Add a `silent` property to the base model to prevent models which
  shouldn't be observed from emitting events
* Change the term "itemSlug" to just "slug" across the board
* Change all models so that they don't require their parent in their
  constructors and so that they don't automatically add themselves to
  their parents lists
* Remove a bunch of unnecessary event triggering
* Tighten up access to various lists of children across all models
* Refactor the guts of the V2 parser into an abstract base class so
  it can be used for other parsers in the future
* Remove a number of unused methods
This commit is contained in:
Andrew Miner
2015-01-17 11:49:07 -08:00
parent bb959264ee
commit 83868e2760
34 changed files with 530 additions and 519 deletions
+3 -8
View File
@@ -47,16 +47,11 @@ All rights reserved.
} }
.quantity { .quantity {
position: absolute; right: 0; bottom: 0; position: absolute; right: 0.1em; bottom: 0.1em;
color: black; color: white;
font-family: PressStart; font-family: PressStart;
font-size: $font-size-large; font-size: $font-size-large;
} text-shadow: -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000;
.quantity + .quantity {
color: white;
right: 0.2em; bottom: 0.2em;
z-index: 1
} }
} }
+1 -1
View File
@@ -52,7 +52,7 @@ ModelState.loaded = 'loaded'
ModelState.failed = 'failed' ModelState.failed = 'failed'
exports.Url = Url = {} exports.Url = Url = {}
Url.itemIcon = _.template "/data/<%= modSlug %>/<%= modVersion %>/images/<%= itemSlug %>.png" Url.itemIcon = _.template "/data/<%= modSlug %>/<%= modVersion %>/images/<%= slug %>.png"
Url.item = _.template "/item/<%= encodeURIComponent(itemName) %>" Url.item = _.template "/item/<%= encodeURIComponent(itemName) %>"
Url.mod = _.template "/data/<%= modSlug %>/mod.cg" Url.mod = _.template "/data/<%= modSlug %>/mod.cg"
Url.modVersion = _.template "/data/<%= modSlug %>/<%= modVersion %>/mod-version.cg" Url.modVersion = _.template "/data/<%= modSlug %>/<%= modVersion %>/mod-version.cg"
@@ -74,6 +74,7 @@ module.exports = class BaseController extends Backbone.View
return unless @onWillChangeModel @_model, newModel return unless @onWillChangeModel @_model, newModel
@_model = newModel @_model = newModel
@_model.silent = false
@_tryRefresh() @_tryRefresh()
# Backbone.View Overrides ###################################################################### # Backbone.View Overrides ######################################################################
@@ -25,6 +25,8 @@ module.exports = class CraftingTableController extends BaseController
@imageLoader = options.imageLoader @imageLoader = options.imageLoader
@modPack = options.modPack @modPack = options.modPack
@model.plan.silent = false
# Event Methods ################################################################################ # Event Methods ################################################################################
onNextClicked: -> onNextClicked: ->
@@ -79,7 +81,7 @@ module.exports = class CraftingTableController extends BaseController
outputStack = @model.output outputStack = @model.output
if outputStack? if outputStack?
display = @modPack.findItemDisplay outputStack.itemSlug display = @modPack.findItemDisplay outputStack.slug
@$outputLink.attr 'href', display.itemUrl @$outputLink.attr 'href', display.itemUrl
@$outputLink.attr 'title', display.itemName @$outputLink.attr 'title', display.itemName
@$outputImg.attr 'alt', display.itemName @$outputImg.attr 'alt', display.itemName
@@ -32,8 +32,9 @@ module.exports = class ModPackController extends BaseController
onWillRender: -> onWillRender: ->
for attributes in DefaultModVersions for attributes in DefaultModVersions
modVersion = new ModVersion _.extend attributes, modPack:@model modVersion = new ModVersion attributes
modVersion.fetch() modVersion.fetch()
@model.addModVersion modVersion
onDidRender: -> onDidRender: ->
@$table = @$('table') @$table = @$('table')
@@ -47,13 +48,14 @@ module.exports = class ModPackController extends BaseController
return return
index = 0 index = 0
while index < Math.min @_controllers.length, @model.modVersions.length modVersions = @model.getModVersions()
while index < Math.min @_controllers.length, modVersions.length
controller = @_controllers[index] controller = @_controllers[index]
controller.model = @model.modVersions[index] controller.model = modVersions[index]
index++ index++
while @_controllers.length < @model.modVersions.length while @_controllers.length < modVersions.length
controller = new ModVersionController model:@model.modVersions[index] controller = new ModVersionController model:modVersions[index]
controller.render() controller.render()
@_controllers.push controller @_controllers.push controller
controller.$el.hide duration:0 controller.$el.hide duration:0
@@ -61,7 +63,7 @@ module.exports = class ModPackController extends BaseController
controller.$el.slideDown duration:Duration.normal controller.$el.slideDown duration:Duration.normal
index++ index++
while @_controllers.length > @model.modVersions.length while @_controllers.length > modVersions.length
controller = @_controllers.pop() controller = @_controllers.pop()
controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove() controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove()
@@ -31,7 +31,7 @@ module.exports = class StackController extends BaseController
super super
refresh: -> refresh: ->
display = @modPack.findItemDisplay @model.itemSlug display = @modPack.findItemDisplay @model.slug
@$nameField.html display.itemName @$nameField.html display.itemName
@$quantityField.html @model.quantity @$quantityField.html @model.quantity
+1
View File
@@ -6,6 +6,7 @@ All rights reserved.
### ###
require './underscore_mixins' require './underscore_mixins'
require './polyfill'
views = require './views' views = require './views'
FeedbackController = require './controllers/feedback_controller' FeedbackController = require './controllers/feedback_controller'
+10
View File
@@ -0,0 +1,10 @@
###
Crafting Guide - base_collection.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
########################################################################################################################
module.exports = class BaseCollection extends Backbone.Collection
+5 -3
View File
@@ -13,7 +13,7 @@ All rights reserved.
module.exports = class BaseModel extends Backbone.Model module.exports = class BaseModel extends Backbone.Model
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
options.allowedSyncMethods = [] options.silent ?= true
super attributes, options super attributes, options
makeGetter = (name)-> return -> @get name makeGetter = (name)-> return -> @get name
@@ -22,9 +22,9 @@ module.exports = class BaseModel extends Backbone.Model
continue if name is 'id' continue if name is 'id'
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name) Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
@allowedSyncMethods = options.allowedSyncMethods @silent = options.silent
@state = ModelState.unloaded
@state = ModelState.unloaded
@on 'request', => @state = ModelState.loading @on 'request', => @state = ModelState.loading
@on 'sync', => @state = ModelState.loaded @on 'sync', => @state = ModelState.loaded
@on 'error', => @state = ModelState.error @on 'error', => @state = ModelState.error
@@ -61,6 +61,7 @@ module.exports = class BaseModel extends Backbone.Model
success: (text, status, xhr)=> resolve @onLoadSucceeded text, status, xhr success: (text, status, xhr)=> resolve @onLoadSucceeded text, status, xhr
error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr
@loading.catch -> # do nothing. prevents unhandled promise warnings
return @loading return @loading
parse: (text)-> parse: (text)->
@@ -70,6 +71,7 @@ module.exports = class BaseModel extends Backbone.Model
throw new Error "#{@constructor.name} (#{@cid}) is not permitted to #{method}" throw new Error "#{@constructor.name} (#{@cid}) is not permitted to #{method}"
trigger: (name)-> trigger: (name)->
return if @silent
logger.trace "#{@constructor.name}.#{@cid} triggered a \"#{name}\" event" logger.trace "#{@constructor.name}.#{@cid} triggered a \"#{name}\" event"
super super
@@ -0,0 +1,89 @@
###
Crafting Guide - command_parser_base.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
########################################################################################################################
module.exports = class CommandParserBase
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
options.showAllErrors ?= false
@_model = options.model
@_showAllErrors = options.showAllErrors
# Class Methods ################################################################################
@COMMAND = /\ *([^:]*):?(.*)/
@COMMENT = /([^\\]?)#.*/
# Public Methods ###############################################################################
parse: (text)->
@_rawData = {}
@_lineNumber = 1
lines = text.split '\n'
for i in [0...lines.length]
@_lineNumber = i + 1
commands = @_parseLine lines[i]
for command in commands
@_handleErrors @_execute, command
@_handleErrors @_buildModel, @_rawData, @_model
return @_model
unparse: ->
builder = new StringBuilder context:@model
@_unparseModel builder, model
return builder.toString()
# Subclass Methods #############################################################################
_buildModel: (rawData, model)->
throw new Error 'Subclasses must override this method'
_unparseModel: (builder, model)->
throw new Error 'Subclasses must override this method'
# Private Methods ##############################################################################
_execute: (command)->
method = this["_command_#{command.name}"]
if not method? then throw new Error "Unknown command: #{command.name}"
@_handleErrors method, command.args
_parseLine: (line)->
line = line.replace CommandParserBase.COMMENT, '$1'
line = line.trim()
return [] if line.length is 0
lineParts = (part.trim() for part in line.split(';'))
commands = []
for linePart in lineParts
continue if linePart.length is 0
match = CommandParserBase.COMMAND.exec linePart
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
args = []
args = (s.trim() for s in match[2].split(',')) if match[2]?
commands.push name:match[1], args:args
return commands
_handleErrors: (callback, args...)->
if args.length is 1 and _.isArray(args[0]) then args = args[0]
try
callback.apply this, args
catch e
e.message = "line #{@_lineNumber}: #{e.message}"
if not @_showAllErrors then throw e
logger.error e.message
+7 -6
View File
@@ -18,16 +18,17 @@ module.exports = class CraftingGrid extends BaseModel
attributes.recipe ?= null attributes.recipe ?= null
super attributes, options super attributes, options
Object.defineProperty @prototype, 'slotCount', get:-> CraftingGrid.SLOT_COUNT Object.defineProperties this,
'slotCount': { get:-> CraftingGrid.SLOT_COUNT }
# Public Methods ############################################################################### # Public Methods ###############################################################################
getItemDisplayAt: (index)-> getItemDisplayAt: (slot)->
if index >= @slotCount then throw new Error "index (#{index}) must be less than #{@slotCount}" if slot >= @slotCount then throw new Error "slot (#{slot}) must be less than #{@slotCount}"
return null unless @recipe? return null unless @recipe?
itemSlug = @recipe.getItemSlugAt index slug = @recipe.getItemSlugAt slot
return null unless itemSlug? return null unless slug?
itemDisplay = @modPack.findItemDisplay itemSlug itemDisplay = @modPack.findItemDisplay slug
return itemDisplay return itemDisplay
+39 -32
View File
@@ -6,6 +6,7 @@ All rights reserved.
### ###
BaseModel = require './base_model' BaseModel = require './base_model'
{Event} = require '../constants'
Inventory = require './inventory' Inventory = require './inventory'
######################################################################################################################## ########################################################################################################################
@@ -17,44 +18,50 @@ module.exports = class CraftingPlan extends BaseModel
attributes.includingTools ?= false attributes.includingTools ?= false
super attributes, options super attributes, options
@have = new Inventory @have = new Inventory
@want = new Inventory @want = new Inventory
@need = new Inventory @need = new Inventory
@result = new Inventory @result = new Inventory
@storage = options.storage
@have.on 'change', => @craft() @clear silent:true
@want.on 'change', => @craft()
@modPack.on 'change', => @craft()
@on 'change:includingTools', => @craft() @have.on Event.change, => @craft()
@want.on Event.change, => @craft()
@modPack.on Event.add, => @craft()
@clear() @on Event.change + ':includingTools', => @craft()
# Public Methods ############################################################################### # Public Methods ###############################################################################
clear: -> clear: (options={})->
options.silent ?= false
@steps = [] @steps = []
@need.clear() @need.clear()
@result.clear() @result.clear()
@trigger 'change', this @trigger 'change', this unless options.silent
return this return this
craft: -> craft: ->
@clear() @clear silent:true
@need.silent = @result.silent = true
@result.addInventory @have @result.addInventory @have
@steps = {} @steps = {}
@want.each (stack)=> @want.each (stack)=>
@_findSteps stack.itemSlug @_findSteps stack.slug
@need.add stack.itemSlug, stack.quantity @need.add stack.slug, stack.quantity
@steps = _.values @steps @steps = _.values @steps
@_resolveNeeds() @_resolveNeeds()
@_removeExtraSteps() @_removeExtraSteps()
@result.addInventory @want @result.addInventory @want
@need.silent = @result.silent = false
@need.trigger 'change', @need
@result.trigger 'change', @result
@trigger 'change', this @trigger 'change', this
# Event Methods ################################################################################ # Event Methods ################################################################################
@@ -82,10 +89,10 @@ module.exports = class CraftingPlan extends BaseModel
@steps[recipe.slug] = recipe:recipe @steps[recipe.slug] = recipe:recipe
_chooseRecipe: (item)-> _chooseRecipe: (item)->
return item.recipes[0] return item.getPrimaryRecipe()
_findSteps: (itemSlug)-> _findSteps: (slug)->
item = @modPack.findItem itemSlug item = @modPack.findItem slug
return unless item? return unless item?
return unless item.isCraftable return unless item.isCraftable
return if item.isGatherable return if item.isGatherable
@@ -94,16 +101,16 @@ module.exports = class CraftingPlan extends BaseModel
if @includingTools if @includingTools
for toolStack in recipe.tools for toolStack in recipe.tools
if not @_hasStep toolStack.itemSlug if not @_hasStep toolStack.slug
@_findSteps toolStack.itemSlug @_findSteps toolStack.slug
for inputStack in recipe.input for inputStack in recipe.input
@_findSteps inputStack.itemSlug @_findSteps inputStack.slug
@_addStep recipe @_addStep recipe
_hasStep: (itemSlug)-> _hasStep: (slug)->
return @steps[itemSlug]? return @steps[slug]?
_removeExtraSteps: -> _removeExtraSteps: ->
result = (step for step in @steps when step.multiplier > 0) result = (step for step in @steps when step.multiplier > 0)
@@ -118,25 +125,25 @@ module.exports = class CraftingPlan extends BaseModel
if @includingTools if @includingTools
for stack in recipe.tools for stack in recipe.tools
slug = stack.itemSlug slug = stack.slug
available = @result.quantityOf(slug) + @need.quantityOf(slug) available = @result.quantityOf(slug) + @need.quantityOf(slug)
needed = Math.max 0, stack.quantity - available needed = Math.max 0, stack.quantity - available
@need.add stack.itemSlug, needed @need.add stack.slug, needed
@result.add stack.itemSlug, needed @result.add stack.slug, needed
for stack in recipe.input for stack in recipe.input
needed = step.multiplier * stack.quantity needed = step.multiplier * stack.quantity
consumed = Math.min needed, @result.quantityOf(stack.itemSlug) consumed = Math.min needed, @result.quantityOf(stack.slug)
remaining = needed - consumed remaining = needed - consumed
@result.remove stack.itemSlug, consumed @result.remove stack.slug, consumed
@need.add stack.itemSlug, remaining @need.add stack.slug, remaining
for stack in recipe.output for stack in recipe.output
created = stack.quantity * step.multiplier created = stack.quantity * step.multiplier
consumed = Math.min created, @need.quantityOf stack.itemSlug consumed = Math.min created, @need.quantityOf stack.slug
remaining = created - consumed remaining = created - consumed
@result.add stack.itemSlug, remaining @result.add stack.slug, remaining
@need.remove stack.itemSlug, consumed @need.remove stack.slug, consumed
+1 -1
View File
@@ -81,7 +81,7 @@ module.exports = class CraftingTable extends BaseModel
getToolNames: -> getToolNames: ->
recipe = @plan.steps[@_step]?.recipe recipe = @plan.steps[@_step]?.recipe
return '' unless recipe? return '' unless recipe?
toolSlugs = (stack.itemSlug for stack in recipe.tools) toolSlugs = (stack.slug for stack in recipe.tools)
toolNames = (@modPack.findName(slug) for slug in toolSlugs).join ', ' toolNames = (@modPack.findName(slug) for slug in toolSlugs).join ', '
return toolNames return toolNames
+42 -39
View File
@@ -15,78 +15,80 @@ module.exports = class Inventory extends BaseModel
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
super attributes, options super attributes, options
@clear() @clear silent:true
Object.defineProperty @prototype, 'isEmpty', get:-> @_slugs.length is 0 Object.defineProperties this,
isEmpty: { get:-> @_slugs.length is 0 }
# Public Methods ############################################################################### # Public Methods ###############################################################################
add: (itemSlug, quantity=1)-> add: (slug, quantity=1)->
@_add itemSlug, quantity @_add slug, quantity
@trigger Event.add, this, itemSlug, quantity @trigger Event.add, this, slug, quantity
@trigger Event.change, this @trigger Event.change, this
return this return this
addInventory: (inventory)-> addInventory: (inventory)->
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity @silent = true
inventory.each (stack)=> @_add stack.slug, stack.quantity
@silent = false
@trigger Event.change, this @trigger Event.change, this
return this return this
clear: -> clear: (options={})->
options.silent ?= false
@_stacks = {} @_stacks = {}
@_slugs = [] @_slugs = []
@trigger 'change', self
@trigger Event.change, this unless options.silent
clone: -> clone: ->
inventory = new Inventory inventory = new Inventory
@each (stack)-> inventory.add stack.itemSlug, stack.quantity inventory.addInventory this
return inventory return inventory
each: (onStack)-> each: (callback)->
for itemSlug in @_slugs for slug in @_slugs
stack = @_stacks[itemSlug] callback @_stacks[slug]
onStack stack
getStack: (itemSlug)-> hasAtLeast: (slug, quantity=1)->
return @_stacks[itemSlug]
hasAtLeast: (itemSlug, quantity=1)->
if quantity is 0 then return true if quantity is 0 then return true
stack = @_stacks[itemSlug] stack = @_stacks[slug]
return false unless stack? return false unless stack?
return stack.quantity >= quantity return stack.quantity >= quantity
pop: -> pop: ->
itemSlug = @_slugs.pop() slug = @_slugs.pop()
return null unless itemSlug? return null unless slug?
stack = @_stacks[itemSlug] stack = @_stacks[slug]
delete @_stacks[itemSlug] delete @_stacks[slug]
@trigger Event.remove, this, stack.itemSlug, stack.quantity @trigger Event.remove, this, stack.slug, stack.quantity
@trigger Event.change, this @trigger Event.change, this
return stack return stack
quantityOf: (itemSlug)-> quantityOf: (slug)->
stack = @_stacks[itemSlug] stack = @_stacks[slug]
return 0 unless stack? return 0 unless stack?
return stack.quantity return stack.quantity
remove: (itemSlug, quantity=1)-> remove: (slug, quantity=1)->
return if quantity is 0 return if quantity is 0
stack = @_stacks[itemSlug] stack = @_stacks[slug]
if not stack? then throw new Error "cannot remove #{itemSlug} since it is not in this inventory" if not stack? then throw new Error "cannot remove #{slug} since it is not in this inventory"
if stack.quantity < quantity if stack.quantity < quantity
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{itemSlug} in this inventory" throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{slug} in this inventory"
stack.quantity -= quantity stack.quantity -= quantity
if stack.quantity is 0 if stack.quantity is 0
delete @_stacks[itemSlug] delete @_stacks[slug]
@_slugs = _(@_slugs).without itemSlug @_slugs = _(@_slugs).without slug
@trigger Event.remove, this, itemSlug, quantity @trigger Event.remove, this, slug, quantity
@trigger Event.change, this @trigger Event.change, this
return this return this
@@ -94,9 +96,9 @@ module.exports = class Inventory extends BaseModel
result = [] result = []
@each (stack)-> @each (stack)->
if stack.quantity > 1 if stack.quantity > 1
result.push [stack.quantity, stack.itemSlug] result.push [stack.quantity, stack.slug]
else else
result.push stack.itemSlug result.push stack.slug
return result return result
# Object Overrides ############################################################################# # Object Overrides #############################################################################
@@ -116,14 +118,15 @@ module.exports = class Inventory extends BaseModel
# Private Methods ############################################################################## # Private Methods ##############################################################################
_add: (itemSlug, quantity=1)-> _add: (slug, quantity=1)->
return unless slug?
return if quantity is 0 return if quantity is 0
stack = @_stacks[itemSlug] stack = @_stacks[slug]
if not stack? if not stack?
stack = new Stack itemSlug:itemSlug, quantity:quantity stack = new Stack slug:slug, quantity:quantity
@_stacks[itemSlug] = stack @_stacks[slug] = stack
@_slugs.push itemSlug @_slugs.push slug
@_slugs.sort() @_slugs.sort()
else else
stack.quantity += quantity stack.quantity += quantity
+19 -17
View File
@@ -5,33 +5,38 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
BaseModel = require './base_model' BaseCollection = require './base_collection'
BaseModel = require './base_model'
{Event} = require '../constants'
Recipe = require './recipe'
######################################################################################################################## ########################################################################################################################
module.exports = class Item extends BaseModel module.exports = class Item extends BaseModel
@DEFAULT_STACK_SIZE = 64
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
if not attributes.name? then throw new Error 'attributes.name is required' if not attributes.name? then throw new Error 'attributes.name is required'
if not attributes.modVersion? then throw new Error 'attributes.modVersion is required'
attributes.isGatherable ?= false attributes.isGatherable ?= false
attributes.recipes ?= []
attributes.slug ?= _.slugify attributes.name attributes.slug ?= _.slugify attributes.name
attributes.stackSize ?= Item.DEFAULT_STACK_SIZE
super attributes, options super attributes, options
@modVersion.addItem this @_recipes = []
Object.defineProperties this,
Object.defineProperty @prototype, 'isCraftable', get:-> @recipes.length > 0 'isCraftable': { get:-> @_recipes.length > 0 }
# Public Methods ############################################################################### # Public Methods ###############################################################################
addRecipe: (recipe)-> addRecipe: (recipe)->
if recipe.item isnt this then throw new Error "cannot add a recipe which isn't associated with this item" if recipe.slug isnt @slug then throw new Error "cannot add a recipe for #{recipe.slug} to #{@slug}"
@recipes.push recipe @_recipes.push recipe
eachRecipe: (callback)->
for recipe in @_recipes
callback recipe
getPrimaryRecipe: ->
return @_recipes[0]
compareTo: (that)-> compareTo: (that)->
if this.slug isnt that.slug if this.slug isnt that.slug
@@ -52,13 +57,10 @@ module.exports = class Item extends BaseModel
if _.slugify(@name) isnt @slug if _.slugify(@name) isnt @slug
result.push ', slug:'; result.push @slug result.push ', slug:'; result.push @slug
if @stackSize isnt Item.DEFAULT_STACK_SIZE
result.push ', stackSize:'; result.push @stackSize
if @recipes.length > 0 if @recipes.length > 0
result.push ', recipes:' result.push ', recipes:«'
result.push @recipes.length result.push @_recipes.length
result.push ' items' result.push ' items»'
result.push '}' result.push '}'
return result.join '' return result.join ''
+2 -2
View File
@@ -41,7 +41,7 @@ module.exports = class ItemPage extends BaseModel
_updateLocation: -> _updateLocation: ->
list = @plan.want.toList() list = @plan.want.toList()
if list.length is 1 if list.length is 1
itemSlug = if _.isArray(list[0]) then list[0][1] else list[0] slug = if _.isArray(list[0]) then list[0][1] else list[0]
router.navigate "/item/#{itemSlug}" router.navigate "/item/#{slug}"
else else
router.navigate "/" router.navigate "/"
+2 -5
View File
@@ -6,6 +6,7 @@ All rights reserved.
### ###
BaseModel = require './base_model' BaseModel = require './base_model'
{Url} = require '../constants'
######################################################################################################################## ########################################################################################################################
@@ -20,9 +21,5 @@ module.exports = class Mod extends BaseModel
parse: (response)-> parse: (response)->
sync: (method, model, options={})->
if method isnt 'read' then throw new Error "Mod data can only be read, not #{method}d"
super method, model, options
url: -> url: ->
return "/data/#{@slug}/mod.cg" return Url.mod modSlug:@slug
+33 -39
View File
@@ -16,35 +16,18 @@ ModVersionParser = require './mod_version_parser'
module.exports = class ModPack extends BaseModel module.exports = class ModPack extends BaseModel
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
attributes.modVersions ?= []
super attributes, options super attributes, options
@_modVersions = []
# Public Methods ############################################################################### # Public Methods ###############################################################################
addModVersion: (modVersion)-> findItem: (slug, options={})->
if modVersion.modPack isnt this then throw new Error "the mod version must be associated with this mod pack"
return if @modVersions.indexOf(modVersion) isnt -1
@modVersions.push modVersion
@trigger Event.add, modVersion, this
@modVersions.sort (a, b)-> a.compareTo b
@trigger Event.sort, this
@trigger Event.change, this
return this
enableModsForItem: (name)->
for modVersion in @modVersions
continue if modVersion.enabled
if modVersion.hasRecipe name
modVersion.enabled = true
findItem: (itemSlug, options={})->
options.includeDisabled ?= false options.includeDisabled ?= false
for modVersion in @modVersions for modVersion in @_modVersions
continue unless modVersion.enabled or options.includeDisabled continue unless modVersion.enabled or options.includeDisabled
item = modVersion.items[itemSlug] item = modVersion.findItem slug
return item if item? return item if item?
return null return null
@@ -53,9 +36,9 @@ module.exports = class ModPack extends BaseModel
options.includeDisabled ?= false options.includeDisabled ?= false
slug = _.slugify name slug = _.slugify name
for modVersion in @modVersions for modVersion in @_modVersions
continue unless modVersion.enabled or options.includeDisabled continue unless modVersion.enabled or options.includeDisabled
item = modVersion.items[slug] item = modVersion.findItem slug
return item if item? return item if item?
return null return null
@@ -63,12 +46,12 @@ module.exports = class ModPack extends BaseModel
findName: (slug, options={})-> findName: (slug, options={})->
options.includeDisabled ?= false options.includeDisabled ?= false
for modVersion in @modVersions for modVersion in @_modVersions
continue unless modVersion.enabled or options.includeDisabled continue unless modVersion.enabled or options.includeDisabled
name = modVersion.findName slug name = modVersion.findName slug
return name if name return name if name
return slug return null
findItemDisplay: (slug)-> findItemDisplay: (slug)->
result = {} result = {}
@@ -76,39 +59,50 @@ module.exports = class ModPack extends BaseModel
if item? if item?
result.modSlug = item.modVersion.slug result.modSlug = item.modVersion.slug
result.modVersion = item.modVersion.version result.modVersion = item.modVersion.version
result.itemSlug = item.slug result.slug = item.slug
result.itemName = item.name result.itemName = item.name
else else
result.modSlug = _.slugify DefaultModVersions[0].name result.modSlug = _.slugify DefaultModVersions[0].name
result.modVersion = DefaultModVersions[0].version result.modVersion = DefaultModVersions[0].version
result.itemSlug = slug result.slug = slug
result.itemName = @findName slug, includeDisabled:true result.itemName = @findName slug, includeDisabled:true
result.iconUrl = Url.itemIcon result result.iconUrl = Url.itemIcon result
result.itemUrl = Url.item result result.itemUrl = Url.item result
return result return result
hasRecipe: (name, options={})->
options.includeDisabled ?= false
for modVersion in @modVersions
continue unless modVersion.enabled or options.includeDisabled
return true if modVersion.hasRecipe name
return false
isValidName: (name, options={})-> isValidName: (name, options={})->
options.includeDisabled ?= false options.includeDisabled ?= false
slug = _.slugify name slug = _.slugify name
for modVersion in @modVersions for modVersion in @_modVersions
continue unless modVersion.enabled or options.includeDisabled continue unless modVersion.enabled or options.includeDisabled
name = modVersion.findName slug name = modVersion.findName slug
return true if name return true if name
return false return false
# Property Methods #############################################################################
addModVersion: (modVersion)->
return if @_modVersions.indexOf(modVersion) isnt -1
@_modVersions.push modVersion
@trigger Event.add, modVersion, this
@_modVersions.sort (a, b)-> a.compareTo b
@trigger Event.change, this
return this
eachModVersion: (callback)->
for modVersion in @_modVersions
callback modVersion
getModVersions: ->
return @_modVersions[..]
# Object Overrides ############################################################################# # Object Overrides #############################################################################
toString: -> toString: ->
return "ModPack (#{@cid}) {modVersions:#{@modVersions.length} items}" return "ModPack (#{@cid}) {modVersions:#{@_modVersions.length} items}"
+50 -28
View File
@@ -5,40 +5,38 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
BaseModel = require './base_model' BaseCollection = require './base_collection'
{Event} = require '../constants' BaseModel = require './base_model'
{RequiredMods} = require '../constants' {Event} = require '../constants'
{Url} = require '../constants' Item = require './item'
{RequiredMods} = require '../constants'
{Url} = require '../constants'
######################################################################################################################## ########################################################################################################################
module.exports = class ModVersion extends BaseModel module.exports = class ModVersion extends BaseModel
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
if not attributes.modPack? then throw new Error 'attributes.modPack is required' if not attributes.name? then throw new Error 'attributes.name is required'
if _.isEmpty(attributes.name) then throw new Error 'attributes.name cannot be empty' if not attributes.version? then throw new Error 'attributes.version is required'
if _.isEmpty(attributes.version) then throw new Error 'attributes.version cannot be empty'
attributes.description ?= '' attributes.description ?= ''
attributes.enabled ?= true attributes.enabled ?= true
attributes.items ?= {}
attributes.names ?= {}
attributes.slug ?= _.slugify attributes.name attributes.slug ?= _.slugify attributes.name
super attributes, options super attributes, options
@modPack.addModVersion this @_items = {}
@_names = {}
@_slugs = []
# Public Methods ############################################################################### # Public Methods ###############################################################################
addItem: (item)-> addItem: (item)->
if item.modVersion isnt this then throw new Error "cannot add item not associated with this mod version" if @_items[item.slug]? then throw new Error "duplicate item for #{item.name}"
if @items[item.slug]? then throw new Error "duplicate item for #{item.name}"
@items[item.slug] = item
@names[item.slug] = item.name
@trigger Event.add, item, this
@trigger Event.change, this
@_items[item.slug] = item
item.modVersion = this
@registerSlug item.slug, item.name
return this return this
compareTo: (that)-> compareTo: (that)->
@@ -56,26 +54,49 @@ module.exports = class ModVersion extends BaseModel
else else
return if this.name < that.name then -1 else +1 return if this.name < that.name then -1 else +1
findName: (slug)-> eachItem: (callback)->
return @names[slug] for slug in @_slugs
callback @_items[slug], slug
return this
hasRecipe: (name)-> eachName: (callback)->
item = @findItemByName name for slug in @_slugs
return false unless item? callback @_names[slug], slug
return item.recipes.length > 0 return this
findItem: (slug)->
return @_items[slug]
findItemByName: (name)->
return @findItem _.slugify name
findName: (slug)->
return @_names[slug]
registerSlug: (slug, name)-> registerSlug: (slug, name)->
@names[slug] = name hasSlug = @_names[slug]?
@trigger Event.change + ':names', this, @names @_names[slug] = name
@trigger Event.change, this
if not hasSlug
@_slugs.push slug
@_slugs.sort()
@_slugs = _.uniq @_slugs, true
return this return this
# Backbone.Model Overrides ##################################################################### # Backbone.Model Overrides #####################################################################
parse: (text)-> parse: (text)->
currentSilent = @silent
@silent = true
ModVersionParser = require './mod_version_parser' # to avoid require cycles ModVersionParser = require './mod_version_parser' # to avoid require cycles
@_parser ?= new ModVersionParser modVersion:this @_parser ?= new ModVersionParser model:this
@_parser.parse text @_parser.parse text
@silent = currentSilent
@trigger Event.change, this
return null # prevent calling `set` return null # prevent calling `set`
url: -> url: ->
@@ -88,4 +109,5 @@ module.exports = class ModVersion extends BaseModel
enabled:#{@enabled}, enabled:#{@enabled},
name:#{@name}, name:#{@name},
version:#{@version}, version:#{@version},
items:#{_.keys(@items).length} items}" items:#{_.keys(@_items).length} items
}"
+4 -8
View File
@@ -16,10 +16,10 @@ module.exports = class ModVersionParser
@CURRENT_VERSION = '2' @CURRENT_VERSION = '2'
constructor: (options={})-> constructor: (options={})->
if not options.modVersion? then throw new Error 'options.modVersion is required' if not options.model? then throw new Error 'options.model is required'
options.showAllErrors ?= false options.showAllErrors ?= false
@_modVersion = options.modVersion @_model = options.model
@_parsers = @_parsers =
'1': new ModVersionParserV1 options '1': new ModVersionParserV1 options
'2': new ModVersionParserV2 options '2': new ModVersionParserV2 options
@@ -34,13 +34,9 @@ module.exports = class ModVersionParser
parser = @_parsers['2'] parser = @_parsers['2']
if not parser? then throw new Error "cannot parse version #{data.dataVersion} mod descriptions" if not parser? then throw new Error "cannot parse version #{data.dataVersion} mod descriptions"
parser.parse data
oldLevel = logger.level return @_model
logger.level = Logger.WARNING
result = parser.parse data
logger.level = oldLevel
return @_modVersion
unparse: (dataVersion=ModVersionParser.CURRENT_VERSION)-> unparse: (dataVersion=ModVersionParser.CURRENT_VERSION)->
if not modVersion? then throw new Error 'modVersion is required' if not modVersion? then throw new Error 'modVersion is required'
+7 -7
View File
@@ -59,19 +59,19 @@ module.exports = class NameFinder
names = [] names = []
nameMap = {} nameMap = {}
for modVersion in @modPack.modVersions @modPack.eachModVersion (modVersion)=>
continue unless modVersion.enabled or @includeDisabledMods return unless modVersion.enabled or @includeDisabledMods
for slug, name of modVersion.names modVersion.eachName (name, slug)=>
continue if nameMap[name] return if nameMap[name]
item = modVersion.items[slug] item = modVersion.findItem slug
if not @includeGatherable if not @includeGatherable
continue unless item? and (not item.isGatherable) return unless item? and (not item.isGatherable)
scanName = "#{modVersion.name} : #{name}" scanName = "#{modVersion.name} : #{name}"
if nameHint? if nameHint?
continue unless @_isMatch scanName.toLowerCase(), nameHint return unless @_isMatch scanName.toLowerCase(), nameHint
nameMap[name] = name nameMap[name] = name
names.push value:name, label:scanName, modVersion:modVersion names.push value:name, label:scanName, modVersion:modVersion
@@ -15,8 +15,8 @@ Stack = require '../stack'
module.exports = class ModVersionParserV1 module.exports = class ModVersionParserV1
constructor: (options={})-> constructor: (options={})->
if not options.modVersion? then throw new Error 'options.modVersion is required' if not options.model? then throw new Error 'options.model is required'
@_modVersion = options.modVersion @_model = options.model
@_errorLocation = 'the header information' @_errorLocation = 'the header information'
parse: (data)-> parse: (data)->
@@ -27,25 +27,48 @@ module.exports = class ModVersionParserV1
# Private Methods ############################################################################## # Private Methods ##############################################################################
_computeDefaultPattern: (input)->
itemCount = input.length
slotCount = _.reduce input, ((total, stack)-> total + stack.quantity), 0
return '... .0. ...' if itemCount is 1 and slotCount is 1
return '00. 00. ...' if itemCount is 1 and slotCount is 4
return '000 000 000' if itemCount is 1 and slotCount is 9
result = ['.', '.', '.', '.', '.', '.', '.', '.', '.']
indexes = [4, 7, 1, 3, 5, 6, 8, 0, 2]
for i in [0...input.length]
stack = input[i]
for j in [0...stack.quantity]
index = indexes.shift()
result[index] = "#{i}"
pattern = result.join ''
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
return pattern
_findOrCreateItem: (name)-> _findOrCreateItem: (name)->
item = @_modVersion.findItemByName name item = @_model.findItemByName name
if not item? if not item?
item = new Item modVersion:@_modVersion, name:name item = new Item name:name
@_modVersion.registerSlug item.slug, item.name @_model.addItem item
return item return item
# Parsing Methods ##############################################################################
_parseModVersion: (data)-> _parseModVersion: (data)->
if not data? then throw new Error 'mod description data is missing' if not data? then throw new Error 'mod description data is missing'
if not data.name? then throw new Error 'name is required' if not data.name? then throw new Error 'name is required'
if not data.version? then throw new Error 'version is required' if not data.version? then throw new Error 'version is required'
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array' if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
if data.name isnt @_modVersion.name if data.name isnt @_model.name
throw new Error "the data is for #{data.name}, not #{@_modVersion.name} as expected" throw new Error "the data is for #{data.name}, not #{@_model.name} as expected"
if data.version isnt @_modVersion.version if data.version isnt @_model.version
throw new Error "the data is for version #{data.version}, not #{@_modVersion.version} as expected" throw new Error "the data is for version #{data.version}, not #{@_model.version} as expected"
@_modVersion.description = data.description or '' @_model.description = data.description or ''
@_parseRawMaterials data.raw_materials @_parseRawMaterials data.raw_materials
for index in [0...data.recipes.length] for index in [0...data.recipes.length]
@@ -54,7 +77,7 @@ module.exports = class ModVersionParserV1
recipe = @_parseRecipe recipeData recipe = @_parseRecipe recipeData
recipe._originalIndex = index recipe._originalIndex = index
return @_modVersion return @_model
_parseRawMaterials: (data)-> _parseRawMaterials: (data)->
return unless data? and data.length > 0 return unless data? and data.length > 0
@@ -84,7 +107,7 @@ module.exports = class ModVersionParserV1
output: @_parseStackList(data.output, field:'output', canBeEmpty:false) output: @_parseStackList(data.output, field:'output', canBeEmpty:false)
input: @_parseStackList(data.input, field:'input', canBeEmpty:true) input: @_parseStackList(data.input, field:'input', canBeEmpty:true)
tools: @_parseStackList(data.tools, field:'tools', canBeEmpty:true) tools: @_parseStackList(data.tools, field:'tools', canBeEmpty:true)
attributes.pattern = data.pattern if data.pattern? attributes.pattern = data.pattern or @_computeDefaultPattern attributes.input
recipe = new Recipe attributes recipe = new Recipe attributes
return recipe return recipe
@@ -102,9 +125,9 @@ module.exports = class ModVersionParserV1
name = data[1] name = data[1]
slug = _.slugify name slug = _.slugify name
@_modVersion.registerSlug slug, name @_model.registerSlug slug, name
return new Stack itemSlug:slug, quantity:data[0] return new Stack slug:slug, quantity:data[0]
_parseStackList: (data, options={})-> _parseStackList: (data, options={})->
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field" if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
@@ -191,9 +214,9 @@ module.exports = class ModVersionParserV1
else if stackList.length is 1 else if stackList.length is 1
stack = stackList[0] stack = stackList[0]
if stack.quantity is 1 if stack.quantity is 1
result.push '"' + @_modVersion.findName(stack.itemSlug) + '"' result.push '"' + @_model.findName(stack.slug) + '"'
else else
result.push '[[' + stack.quantity + ', "' + @_modVersion.findName(stack.itemSlug) + '"]]' result.push '[[' + stack.quantity + ', "' + @_model.findName(stack.slug) + '"]]'
else else
result.push '[' result.push '['
@@ -202,17 +225,17 @@ module.exports = class ModVersionParserV1
stacks.sort (a, b)-> stacks.sort (a, b)->
if a.quantity isnt b.quantity if a.quantity isnt b.quantity
return if a.quantity > b.quantity then -1 else +1 return if a.quantity > b.quantity then -1 else +1
if a.itemSlug isnt b.itemSlug if a.slug isnt b.slug
return if a.itemSlug < b.itemSlug then -1 else +1 return if a.slug < b.slug then -1 else +1
return 0 return 0
firstItem = true firstItem = true
for stack in stacks for stack in stacks
result.push ', ' if not firstItem result.push ', ' if not firstItem
if stack.quantity is 1 if stack.quantity is 1
result.push '"' + @_modVersion.findName(stack.itemSlug) + '"' result.push '"' + @_model.findName(stack.slug) + '"'
else else
result.push '[' + stack.quantity + ', "' + @_modVersion.findName(stack.itemSlug) + '"]' result.push '[' + stack.quantity + ', "' + @_model.findName(stack.slug) + '"]'
firstItem = false firstItem = false
result.push ']' result.push ']'
@@ -5,97 +5,38 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
Item = require '../item' CommandParserBase = require '../command_parser_base'
ModVersion = require '../mod_version' Item = require '../item'
Recipe = require '../recipe' ModVersion = require '../mod_version'
Stack = require '../stack' Recipe = require '../recipe'
StringBuilder = require '../string_builder' Stack = require '../stack'
StringBuilder = require '../string_builder'
######################################################################################################################## ########################################################################################################################
module.exports = class ModVersionParserV2 module.exports = class ModVersionParserV2 extends CommandParserBase
constructor: (options={})->
if not options.modVersion? then throw new Error 'options.modVersion is required'
options.showAllErrors ?= false
@_modVersion = options.modVersion
@_showAllErrors = options.showAllErrors
# Class Methods ################################################################################ # Class Methods ################################################################################
@COMMAND = /\ *([^:]*):?(.*)/
@COMMENT = /([^\\]?)#.*/
@INTEGER = /[0-9]+/ @INTEGER = /[0-9]+/
@PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/ @PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/
@STACK = /^([0-9]+) +(.*)$/ @STACK = /^([0-9]+) +(.*)$/
# Public Methods ############################################################################### # CommandParserBase Overrides ##################################################################
parse: (text)-> _buildModel: (rawData, model)->
@_modVersionData = {} @_buildModVersion rawData, model
@_lineNumber = 1
lines = text.split '\n' _unparseModel: (builder, model)->
for i in [0...lines.length] @_unparseModVersion builder, model
@_lineNumber = i + 1
commands = @_parseLine lines[i]
for command in commands
@_handleErrors @_execute, command
return @_handleErrors @_buildModVersion, @_modVersionData, @_modVersion
unparse: ->
builder = new StringBuilder context:@_modVersion
@_unparseModVersion builder
return builder.toString()
# Private Methods ##############################################################################
_execute: (command)->
method = this["_command_#{command.name}"]
if not method? then throw new Error "Unknown command: #{command.name}"
@_handleErrors method, command.args
_parseLine: (line)->
line = line.replace ModVersionParserV2.COMMENT, '$1'
line = line.trim()
return [] if line.length is 0
lineParts = (part.trim() for part in line.split(';'))
commands = []
for linePart in lineParts
continue if linePart.length is 0
match = ModVersionParserV2.COMMAND.exec linePart
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
args = []
args = (s.trim() for s in match[2].split(',')) if match[2]?
commands.push name:match[1], args:args
return commands
_handleErrors: (callback, args...)->
if args.length is 1 and _.isArray(args[0]) then args = args[0]
try
callback.apply this, args
catch e
e.message = "line #{@_lineNumber}: #{e.message}"
if not @_showAllErrors then throw e
logger.error e.message
# Command Methods ############################################################################## # Command Methods ##############################################################################
_command_description: (descriptionParts...)-> _command_description: (descriptionParts...)->
if @_modVersionData.description? then throw new Error 'duplicate declaration of "description"' if @_rawData.description? then throw new Error 'duplicate declaration of "description"'
@_modVersionData.description = descriptionParts.join ', ' @_rawData.description = descriptionParts.join ', '
_command_extras: (extraTerms...)-> _command_extras: (extraTerms...)->
if not @_recipeData? then throw new Error 'cannot declare "extras" before "recipe"' if not @_recipeData? then throw new Error 'cannot declare "extras" before "recipe"'
@@ -120,16 +61,16 @@ module.exports = class ModVersionParserV2
if not name.length > 0 then throw new Error 'the item name cannot be empty' if not name.length > 0 then throw new Error 'the item name cannot be empty'
@_itemData = name:name, line:@_lineNumber @_itemData = name:name, line:@_lineNumber
@_modVersionData.items ?= [] @_rawData.items ?= []
@_modVersionData.items.push @_itemData @_rawData.items.push @_itemData
@_recipeData = null @_recipeData = null
_command_name: (name='')-> _command_name: (name='')->
if @_modVersionData.name? then throw new Error 'duplicate declaration of "name"' if @_rawData.name? then throw new Error 'duplicate declaration of "name"'
if not name.length > 0 then throw new Error 'the mod name cannot be empty' if not name.length > 0 then throw new Error 'the mod name cannot be empty'
@_modVersionData.name = name @_rawData.name = name
_command_input: (inputNames...)-> _command_input: (inputNames...)->
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"' if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
@@ -175,10 +116,10 @@ module.exports = class ModVersionParserV2
@_recipeData.tools.push name @_recipeData.tools.push name
_command_version: (version='')-> _command_version: (version='')->
if @_modVersionData.version? then throw new Error 'duplicate declaration of "version"' if @_rawData.version? then throw new Error 'duplicate declaration of "version"'
if version.length is 0 then throw new Error 'version cannot be empty' if version.length is 0 then throw new Error 'version cannot be empty'
@_modVersionData.version = version @_rawData.version = version
# Object Creation Methods ###################################################################### # Object Creation Methods ######################################################################
@@ -208,7 +149,8 @@ module.exports = class ModVersionParserV2
itemData.gatherable ?= false itemData.gatherable ?= false
itemData.recipes ?= [] itemData.recipes ?= []
item = new Item modVersion:modVersion, name:itemData.name, isGatherable:itemData.gatherable item = new Item name:itemData.name, isGatherable:itemData.gatherable
modVersion.addItem item
for recipeData in itemData.recipes for recipeData in itemData.recipes
@_handleErrors @_buildRecipe, modVersion, item, recipeData @_handleErrors @_buildRecipe, modVersion, item, recipeData
@@ -228,7 +170,7 @@ module.exports = class ModVersionParserV2
for name in recipeData.input for name in recipeData.input
slug = _.slugify name slug = _.slugify name
modVersion.registerSlug slug, name modVersion.registerSlug slug, name
inputStacks.push new Stack itemSlug:slug, quantity:0 inputStacks.push new Stack slug:slug, quantity:0
for c in recipeData.pattern for c in recipeData.pattern
continue if c is '.' continue if c is '.'
@@ -240,42 +182,43 @@ module.exports = class ModVersionParserV2
for i in [0...inputStacks.length] for i in [0...inputStacks.length]
stack = inputStacks[i] stack = inputStacks[i]
if stack.quantity is 0 if stack.quantity is 0
name = modVersion.findName stack.itemSlug name = modVersion.findName stack.slug
throw new Error "#{name} is an input for this recipe, but it is not in the pattern" throw new Error "#{name} is an input for this recipe, but it is not in the pattern"
outputStacks = [ new Stack itemSlug:item.slug, quantity:recipeData.quantity ] outputStacks = [ new Stack slug:item.slug, quantity:recipeData.quantity ]
for extraData in recipeData.extras for extraData in recipeData.extras
slug = _.slugify extraData.name slug = _.slugify extraData.name
modVersion.registerSlug slug, extraData.name modVersion.registerSlug slug, extraData.name
outputStacks.push new Stack itemSlug:slug, quantity:extraData.quantity outputStacks.push new Stack slug:slug, quantity:extraData.quantity
toolStacks = [] toolStacks = []
for name in recipeData.tools for name in recipeData.tools
slug = _.slugify name slug = _.slugify name
modVersion.registerSlug slug, name modVersion.registerSlug slug, name
toolStacks.push new Stack itemSlug:slug, quantity:1 toolStacks.push new Stack slug:slug, quantity:1
attributes = attributes =
input: inputStacks input: inputStacks
item: item name: item.name
pattern: recipeData.pattern pattern: recipeData.pattern
output: outputStacks output: outputStacks
tools: toolStacks tools: toolStacks
recipe = new Recipe attributes recipe = new Recipe attributes
item.addRecipe recipe
return recipe return recipe
# Un-parsing Methods ########################################################################### # Un-parsing Methods ###########################################################################
_unparseModVersion: (builder)-> _unparseModVersion: (builder, modVersion)->
itemList = _.values @modVersion.items itemList = _.values modVersion.items
itemList.sort (a, b)-> a.compareTo b itemList.sort (a, b)-> a.compareTo b
builder builder
.line 'schema: ', 2 .line 'schema: ', 2
.line 'name: ', @modVersion.name .line 'name: ', modVersion.name
.line 'version: ', @modVersion.version .line 'version: ', modVersion.version
.onlyIf @modVersion.description?, => builder.line 'description: ', @modVersion.description .onlyIf modVersion.description?, => builder.line 'description: ', modVersion.description
.line() .line()
.onlyIf itemList.length > 0, => .onlyIf itemList.length > 0, =>
builder.loop itemList, delimiter:'\n', onEach:(b, i)=> @_unparseItem(b, i) builder.loop itemList, delimiter:'\n', onEach:(b, i)=> @_unparseItem(b, i)
@@ -291,13 +234,13 @@ module.exports = class ModVersionParserV2
.outdent() .outdent()
_unparseRecipe: (builder, recipe)-> _unparseRecipe: (builder, recipe)->
inputNames = (builder.context.findName(stack.itemSlug) for stack in recipe.input) inputNames = (builder.context.findName(stack.slug) for stack in recipe.input)
inputNames.sort() inputNames.sort()
patternMap = {'.', '.'} patternMap = {'.', '.'}
for i in [0...recipe.input.length] for i in [0...recipe.input.length]
stack = recipe.input[i] stack = recipe.input[i]
patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.itemSlug)}" patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.slug)}"
pattern = recipe.pattern or recipe.defaultPattern pattern = recipe.pattern or recipe.defaultPattern
newPattern = [] newPattern = []
@@ -333,9 +276,9 @@ module.exports = class ModVersionParserV2
_unparseStackList: (builder, stackList)-> _unparseStackList: (builder, stackList)->
if stackList.length is 1 and stackList[0].quantity is 1 if stackList.length is 1 and stackList[0].quantity is 1
builder.push builder.context.findName(stackList[0].itemSlug) builder.push builder.context.findName(stackList[0].slug)
else else
builder.loop stackList, onEach:(b, stack)=> builder.loop stackList, onEach:(b, stack)=>
builder builder
.onlyIf stack.quantity > 1, => builder.push stack.quantity, ' ' .onlyIf stack.quantity > 1, => builder.push stack.quantity, ' '
.push builder.context.findName stack.itemSlug .push builder.context.findName stack.slug
+14 -49
View File
@@ -13,50 +13,36 @@ Stack = require './stack'
module.exports = class Recipe extends BaseModel module.exports = class Recipe extends BaseModel
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
if not attributes.input? then throw new Error 'attributes.input is required' if attributes.item?
if not attributes.item? then throw new Error 'attributes.item is required' attributes.name = attributes.item.name
attributes.slug = attributes.item.slug
attributes.output ?= [new Stack slug:item.slug, quantity:1]
attributes.output ?= [new Stack itemSlug:attributes.item.slug] if not attributes.name? then throw new Error 'attributes.name is required'
if not attributes.input? then throw new Error 'attributes.input is required'
if not attributes.pattern? then throw new Error 'attributes.pattern is required'
attributes.item ?= null
attributes.output ?= [new Stack slug:_.slugify(attributes.name), quantity:1]
attributes.pattern = @_parsePattern attributes.pattern attributes.pattern = @_parsePattern attributes.pattern
attributes.slug ?= attributes.output[0].slug
attributes.tools ?= [] attributes.tools ?= []
super attributes, options super attributes, options
@item.addRecipe this
Object.defineProperties this,
'defaultPattern': { get: -> @_computeDefaultPattern() }
'name': { get: -> @item.name }
'slug': { get: -> @item.slug }
# Public Methods ############################################################################### # Public Methods ###############################################################################
getItemSlugAt: (index)-> getItemSlugAt: (patternSlot)->
pattern = if @pattern? then @pattern else @_computeDefaultPattern() pattern = if @pattern? then @pattern else @_computeDefaultPattern()
trueIndex = 0:0, 1:1, 2:2, 3:4, 4:5, 5:6, 6:8, 7:9, 8:10 trueIndex = 0:0, 1:1, 2:2, 3:4, 4:5, 5:6, 6:8, 7:9, 8:10
patternDigit = pattern[trueIndex[index]] patternDigit = pattern[trueIndex[patternSlot]]
return null unless patternDigit? return null unless patternDigit?
return null unless patternDigit.match /[0-9]/ return null unless patternDigit.match /[0-9]/
stack = @input[parseInt(patternDigit)] stack = @input[parseInt(patternDigit)]
return null unless stack? return null unless stack?
return stack.itemSlug return stack.slug
make: (inventory, missing)->
for stack in @input
itemSlug = stack.itemSlug
needed = stack.quantity
while needed > 0
if inventory.hasAtLeast itemSlug
inventory.remove itemSlug
else
missing.add itemSlug
for stack in @output
inventory.add stack.itemSlug, stack.quantity
return this
# Object Overrides ############################################################################# # Object Overrides #############################################################################
@@ -109,24 +95,3 @@ module.exports = class Recipe extends BaseModel
pattern = array.join '' pattern = array.join ''
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3' pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
return pattern return pattern
_computeDefaultPattern: ->
itemCount = @input.length
slotCount = _.reduce @input, ((total, stack)-> total + stack.quantity), 0
return '... .0. ...' if itemCount is 1 and slotCount is 1
return '00. 00. ...' if itemCount is 1 and slotCount is 4
return '000 000 000' if itemCount is 1 and slotCount is 9
result = ['.', '.', '.', '.', '.', '.', '.', '.', '.']
indexes = [4, 7, 1, 3, 5, 6, 8, 0, 2]
for i in [0...@input.length]
stack = @input[i]
for j in [0...stack.quantity]
index = indexes.shift()
result[index] = "#{i}"
pattern = result.join ''
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
return pattern
+6 -18
View File
@@ -5,30 +5,18 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
BaseModel = require './base_model'
######################################################################################################################## ########################################################################################################################
module.exports = class Stack module.exports = class Stack extends BaseModel
constructor: (attributes={})-> constructor: (attributes={})->
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required' if not attributes.slug? then throw new Error 'attributes.slug is required'
attributes.quantity ?= 1 attributes.quantity ?= 1
super attributes
@itemSlug = attributes.itemSlug
@quantity = attributes.quantity
# Public Methods ###############################################################################
canMerge: (stack)->
return @itemSlug is stack.itemSlug
merge: (stack)->
if not @canMerge stack
throw new Error "this stack of #{@itemSlug} cannot merge a stack of #{@stack.itemSlug}"
@quantity += stack.quantity
return this
# Object Overrides ############################################################################# # Object Overrides #############################################################################
toString: -> toString: ->
return "#{@quantity} #{@itemSlug}" return "#{@quantity} #{@slug}"
+13
View File
@@ -0,0 +1,13 @@
###
Crafting Table - polyfill.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
########################################################################################################################
if not Array.prototype.clear?
Object.defineProperty Array.prototype, 'clear', value:->
@splice 0, @length
return this
-1
View File
@@ -18,7 +18,6 @@
.output .output
a: img a: img
p.quantity p.quantity
p.quantity
p.multiplier p.multiplier
.next .next
.problem .problem
+7 -3
View File
@@ -5,8 +5,9 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
ModPack = require '../src/scripts/models/mod_pack'
CraftingPlan = require '../src/scripts/models/crafting_plan' CraftingPlan = require '../src/scripts/models/crafting_plan'
ModVersion = require '../src/scripts/models/mod_version'
ModPack = require '../src/scripts/models/mod_pack'
######################################################################################################################## ########################################################################################################################
@@ -17,8 +18,8 @@ modPack = plan = null
describe 'CraftingPlan', -> describe 'CraftingPlan', ->
beforeEach -> beforeEach ->
modPack = new ModPack modVersion = new ModVersion name:'Minecraft', version:'1.7.10'
modPack.loadModVersionData """ modVersion.parse """
schema:2; name:Minecraft; version:1.7.10 schema:2; name:Minecraft; version:1.7.10
item:Oak Plank; recipe:; input:Oak Log; pattern:... .0. ...; quantity:4 item:Oak Plank; recipe:; input:Oak Log; pattern:... .0. ...; quantity:4
@@ -28,6 +29,9 @@ describe 'CraftingPlan', ->
item:Iron Ingot; recipe:; input:Iron Ore, furnace fuel; pattern:.0. ... .1.; tools:Furnace item:Iron Ingot; recipe:; input:Iron Ore, furnace fuel; pattern:.0. ... .1.; tools:Furnace
item:Iron Sword; recipe:; input:Iron Ingot, Stick; pattern:.0. .0. .1.; tools:Crafting Table item:Iron Sword; recipe:; input:Iron Ingot, Stick; pattern:.0. .0. .1.; tools:Crafting Table
""" """
modPack = new ModPack
modPack.addModVersion modVersion
plan = new CraftingPlan modPack:modPack, includingTools:false plan = new CraftingPlan modPack:modPack, includingTools:false
describe 'craft', -> describe 'craft', ->
+5 -5
View File
@@ -19,7 +19,7 @@ inventory = null
describe 'Inventory', -> describe 'Inventory', ->
beforeEach -> beforeEach ->
inventory = new Inventory inventory = new Inventory {}, silent:false
inventory.add 'wool', 4 inventory.add 'wool', 4
inventory.add 'string', 20 inventory.add 'string', 20
inventory.add 'boat' inventory.add 'boat'
@@ -30,7 +30,7 @@ describe 'Inventory', ->
inventory.add 'iron_ingot', 4 inventory.add 'iron_ingot', 4
stack = inventory._stacks['iron_ingot'] stack = inventory._stacks['iron_ingot']
stack.constructor.name.should.equal 'Stack' stack.constructor.name.should.equal 'Stack'
stack.itemSlug.should.equal 'iron_ingot' stack.slug.should.equal 'iron_ingot'
stack.quantity.should.equal 4 stack.quantity.should.equal 4
it 'can augment quantity of existing items', -> it 'can augment quantity of existing items', ->
@@ -80,7 +80,7 @@ describe 'Inventory', ->
it 'works when items have only been added', -> it 'works when items have only been added', ->
result = [] result = []
inventory.each (stack)-> result.push stack.itemSlug inventory.each (stack)-> result.push stack.slug
result.should.eql ['boat', 'string', 'wool'] result.should.eql ['boat', 'string', 'wool']
it 'works when items have been augmented', -> it 'works when items have been augmented', ->
@@ -89,7 +89,7 @@ describe 'Inventory', ->
inventory.add 'wool', 2 inventory.add 'wool', 2
result = [] result = []
inventory.each (stack)-> result.push stack.itemSlug inventory.each (stack)-> result.push stack.slug
result.should.eql ['boat', 'iron_ingot', 'string', 'wool'] result.should.eql ['boat', 'iron_ingot', 'string', 'wool']
describe 'hasAtLeast', -> describe 'hasAtLeast', ->
@@ -116,7 +116,7 @@ describe 'Inventory', ->
it 'completely removes the last item', -> it 'completely removes the last item', ->
stack = inventory.pop() stack = inventory.pop()
stack.itemSlug.should.equal 'wool' stack.slug.should.equal 'wool'
stack.quantity.should.equal 4 stack.quantity.should.equal 4
inventory.toList().should.eql ['boat', [20, 'string']] inventory.toList().should.eql ['boat', [20, 'string']]
+14 -52
View File
@@ -19,43 +19,22 @@ describe 'ModPack', ->
beforeEach -> beforeEach ->
minecraft = new ModVersion name:'Minecraft', version:'1.7.10', enabled:true minecraft = new ModVersion name:'Minecraft', version:'1.7.10', enabled:true
new Item modVersion:minecraft, name:'Wool' minecraft.addItem new Item name:'Wool'
new Item modVersion:minecraft, name:'Bed', recipes:[''] minecraft.addItem new Item name:'Bed', recipes:['']
minecraft.registerSlug 'iron_chestplate', 'Iron Chestplate' minecraft.registerSlug 'iron_chestplate', 'Iron Chestplate'
buildcraft = new ModVersion name:'Buildcraft', version:'4.0', enabled:false buildcraft = new ModVersion name:'Buildcraft', version:'6.2.6', enabled:false
new Item modVersion:buildcraft, name:'Stone Gear', recipes:[''] buildcraft.addItem new Item name:'Stone Gear', recipes:['']
new Item modVersion:buildcraft, name:'Bed', recipes:[''] buildcraft.addItem new Item name:'Bed', recipes:['']
industrialCraft = new ModVersion name:'Industrial Craft', version:'2.0', enabled:false industrialCraft = new ModVersion name:'Industrial Craft', version:'2.0', enabled:false
new Item modVersion:industrialCraft, name:'Resin' industrialCraft.addItem new Item name:'Resin'
new Item modVersion:industrialCraft, name:'Rubber', recipes:[''] industrialCraft.addItem new Item name:'Rubber'
modPack = new ModPack modVersions:[minecraft, buildcraft, industrialCraft] modPack = new ModPack
modPack.addModVersion minecraft
describe 'enableModsForItem', -> modPack.addModVersion buildcraft
modPack.addModVersion industrialCraft
it 'it ignores already-enabled mod versions', ->
buildcraft.enabled = true
modPack.enableModsForItem 'Stone Gear'
minecraft.enabled.should.be.true
buildcraft.enabled.should.be.true
industrialCraft.enabled.should.be.false
it 'it ignores mod versions not containing the item', ->
modPack.enableModsForItem 'Stone Gear'
minecraft.enabled.should.be.true
buildcraft.enabled.should.be.true
industrialCraft.enabled.should.be.false
it 'enables disabled mod versions with the item', ->
modPack.enableModsForItem 'Rubber'
minecraft.enabled.should.be.true
buildcraft.enabled.should.be.false
industrialCraft.enabled.should.be.true
describe 'findItemByName', -> describe 'findItemByName', ->
@@ -75,7 +54,7 @@ describe 'ModPack', ->
it 'returns all data for a regular Minecraft item', -> it 'returns all data for a regular Minecraft item', ->
display = modPack.findItemDisplay 'bed' display = modPack.findItemDisplay 'bed'
display.iconUrl.should.equal '/data/minecraft/images/bed.png' display.iconUrl.should.equal '/data/minecraft/1.7.10/images/bed.png'
display.itemUrl.should.equal '/item/Bed' display.itemUrl.should.equal '/item/Bed'
display.itemName.should.equal 'Bed' display.itemName.should.equal 'Bed'
display.modSlug.should.equal 'minecraft' display.modSlug.should.equal 'minecraft'
@@ -83,7 +62,7 @@ describe 'ModPack', ->
it 'returns all data for an item in an enabled mod', -> it 'returns all data for an item in an enabled mod', ->
buildcraft.enabled = true buildcraft.enabled = true
display = modPack.findItemDisplay 'stone_gear' display = modPack.findItemDisplay 'stone_gear'
display.iconUrl.should.equal '/data/buildcraft/images/stone_gear.png' display.iconUrl.should.equal '/data/buildcraft/6.2.6/images/stone_gear.png'
display.itemUrl.should.equal '/item/Stone%20Gear' display.itemUrl.should.equal '/item/Stone%20Gear'
display.itemName.should.equal 'Stone Gear' display.itemName.should.equal 'Stone Gear'
display.modSlug.should.equal 'buildcraft' display.modSlug.should.equal 'buildcraft'
@@ -94,24 +73,7 @@ describe 'ModPack', ->
it 'assumes an unfound item is from Minecraft', -> it 'assumes an unfound item is from Minecraft', ->
display = modPack.findItemDisplay 'iron_chestplate' display = modPack.findItemDisplay 'iron_chestplate'
display.iconUrl.should.equal '/data/minecraft/images/iron_chestplate.png' display.iconUrl.should.equal '/data/minecraft/1.7.10/images/iron_chestplate.png'
display.itemUrl.should.equal '/item/Iron%20Chestplate' display.itemUrl.should.equal '/item/Iron%20Chestplate'
display.itemName.should.equal 'Iron Chestplate' display.itemName.should.equal 'Iron Chestplate'
display.modSlug.should.equal 'minecraft' display.modSlug.should.equal 'minecraft'
describe 'hasRecipe', ->
it 'returns true when the item is present and has recipes', ->
modPack.hasRecipe('Bed').should.be.true
it 'returns false when the item is not present', ->
modPack.hasRecipe('Iron Sword').should.be.false
it 'returns false when the item does not have recipes', ->
modPack.hasRecipe('Wool').should.be.false
it 'ignores disabled mod versions', ->
modPack.hasRecipe('Stone Gear').should.be.false
it 'includes disabled mod versions when include disabled is requested', ->
modPack.hasRecipe('Stone Gear', includeDisabled:true).should.be.true
+13 -23
View File
@@ -21,25 +21,29 @@ describe 'ModVersion', ->
describe 'constructor', -> describe 'constructor', ->
it 'requires a mod name', -> it 'requires a mod name', ->
expect(-> new ModVersion version:'0.0').to.throw Error, 'name cannot be empty' expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.name is required'
it 'requires a mod version', -> it 'requires a mod version', ->
expect(-> new ModVersion name:'Test').to.throw Error, 'version cannot be empty' expect(-> new ModVersion name:'Test').to.throw Error, 'attributes.version is required'
it 'supplies default values', -> it 'supplies default values', ->
modVersion.description.should.equal '' modVersion.description.should.equal ''
modVersion.items.should.eql {}
modVersion.enabled.should.be.true modVersion.enabled.should.be.true
modVersion.slug.should.equal 'test'
describe 'addItem', -> describe 'addItem', ->
it 'refuses to add duplicates', -> it 'refuses to add duplicates', ->
new Item modVersion:modVersion, name:'Wool' modVersion.addItem new Item name:'Wool'
expect(-> new Item modVersion:modVersion, name:'Wool').to.throw Error, 'duplicate item for Wool' expect(-> modVersion.addItem new Item name:'Wool').to.throw Error, 'duplicate item for Wool'
it 'adds an item indexes by its slug', -> it 'adds an item indexed by its slug', ->
new Item modVersion:modVersion, name:'Wool' modVersion.addItem new Item name:'Wool'
modVersion.items.wool.name.should.equal 'Wool' modVersion._items.wool.name.should.equal 'Wool'
it 'sets the modVersion', ->
modVersion.addItem new Item name:'Wool'
modVersion._items.wool.modVersion.should.equal modVersion
describe 'compareTo', -> describe 'compareTo', ->
@@ -56,19 +60,5 @@ describe 'ModVersion', ->
describe 'findItemByName', -> describe 'findItemByName', ->
it 'locates items by slugified name', -> it 'locates items by slugified name', ->
new Item modVersion:modVersion, name:'Crafting Table' modVersion.addItem new Item name:'Crafting Table'
modVersion.findItemByName('Crafting Table').slug.should.equal 'crafting_table' modVersion.findItemByName('Crafting Table').slug.should.equal 'crafting_table'
describe 'hasRecipe', ->
it 'returns false for an unknown item', ->
new Item modVersion:modVersion, name:'Oak Wood Planks', recipes:['foo']
modVersion.hasRecipe('Pineapple Upside-Down Cake').should.be.false
it 'returns false for a un-craftable item', ->
new Item modVersion:modVersion, name:'Wool'
modVersion.hasRecipe('Wool').should.be.false
it 'returns true for a craftable item', ->
new Item modVersion:modVersion, name:'Oak Wood Planks', recipes:['foo']
modVersion.hasRecipe('Oak Wood Planks').should.be.true
@@ -19,7 +19,7 @@ describe "ModVersionParserV1", ->
beforeEach -> beforeEach ->
modVersion = new ModVersion name:'Test', version:'0.0' modVersion = new ModVersion name:'Test', version:'0.0'
parser = new ModVersionParserV1 modVersion:modVersion parser = new ModVersionParserV1 model:modVersion
describe '_parseModVersion', -> describe '_parseModVersion', ->
@@ -51,8 +51,7 @@ describe "ModVersionParserV1", ->
modVersion = parser._parseModVersion data modVersion = parser._parseModVersion data
modVersion.name.should.equal 'Test' modVersion.name.should.equal 'Test'
modVersion.version.should.equal '0.0' modVersion.version.should.equal '0.0'
slugs = (slug for slug, item of modVersion.items).sort() modVersion._slugs.should.eql ['bed', 'crafting_table', 'planks', 'sugar', 'sugar_cane', 'wool']
slugs.should.eql ['bed', 'sugar']
describe '_parseRawMaterials', -> describe '_parseRawMaterials', ->
@@ -66,17 +65,17 @@ describe "ModVersionParserV1", ->
it 'adds items marked as gatherable', -> it 'adds items marked as gatherable', ->
parser._parseRawMaterials ['Wool'] parser._parseRawMaterials ['Wool']
modVersion.items['wool'].isGatherable.should.be.true modVersion._items['wool'].isGatherable.should.be.true
it 'marks an existing item as gatherable', -> it 'marks an existing item as gatherable', ->
item = new Item modVersion:modVersion, name:'Wool' modVersion.addItem new Item name:'Wool'
modVersion.items['wool'].isGatherable.should.be.false modVersion._items['wool'].isGatherable.should.be.false
parser._parseRawMaterials ['Wool'] parser._parseRawMaterials ['Wool']
modVersion.items['wool'].isGatherable.should.be.true modVersion._items['wool'].isGatherable.should.be.true
it 'registers the names of the items', -> it 'registers the names of the items', ->
parser._parseRawMaterials ['Wool'] parser._parseRawMaterials ['Wool']
modVersion.names['wool'].should.equal 'Wool' modVersion._names['wool'].should.equal 'Wool'
describe '_parseRecipe', -> describe '_parseRecipe', ->
@@ -95,15 +94,15 @@ describe "ModVersionParserV1", ->
input: [[3, 'planks'], [3, 'wool']] input: [[3, 'planks'], [3, 'wool']]
tools: 'crafting table' tools: 'crafting table'
recipe = parser._parseRecipe data recipe = parser._parseRecipe data
(stack.itemSlug for stack in recipe.output).should.eql ['bed'] (stack.slug for stack in recipe.output).should.eql ['bed']
(stack.itemSlug for stack in recipe.input).sort().should.eql ['planks', 'wool'] (stack.slug for stack in recipe.input).sort().should.eql ['planks', 'wool']
(stack.itemSlug for stack in recipe.tools).should.eql ['crafting_table'] (stack.slug for stack in recipe.tools).should.eql ['crafting_table']
it 'can parse a recipe without tools', -> it 'can parse a recipe without tools', ->
recipe = parser._parseRecipe {output:'sugar', input:'sugar cane'} recipe = parser._parseRecipe {output:'sugar', input:'sugar cane'}
(stack.itemSlug for stack in recipe.output).should.eql ['sugar'] (stack.slug for stack in recipe.output).should.eql ['sugar']
(stack.itemSlug for stack in recipe.input).sort().should.eql ['sugar_cane'] (stack.slug for stack in recipe.input).sort().should.eql ['sugar_cane']
(stack.itemSlug for stack in recipe.tools).should.eql [] (stack.slug for stack in recipe.tools).should.eql []
it 'registers all names', -> it 'registers all names', ->
data = data =
@@ -111,7 +110,7 @@ describe "ModVersionParserV1", ->
input: [[3, 'Oak Wood Planks'], [3, 'Wool']] input: [[3, 'Oak Wood Planks'], [3, 'Wool']]
tools: 'Crafting Table' tools: 'Crafting Table'
parser._parseRecipe data parser._parseRecipe data
_.keys(modVersion.names).sort().should.eql ['bed', 'crafting_table', 'oak_wood_planks', 'wool'] modVersion._slugs.should.eql ['bed', 'crafting_table', 'oak_wood_planks', 'wool']
describe '_parseStack', -> describe '_parseStack', ->
@@ -123,11 +122,11 @@ describe "ModVersionParserV1", ->
it 'can fill in a missing number', -> it 'can fill in a missing number', ->
stack = parser._parseStack 'boat' stack = parser._parseStack 'boat'
stack.itemSlug.should.equal 'boat' stack.slug.should.equal 'boat'
stack.quantity.should.equal 1 stack.quantity.should.equal 1
stack2 = parser._parseStack ['boat'] stack2 = parser._parseStack ['boat']
stack2.itemSlug.should.equal 'boat' stack2.slug.should.equal 'boat'
stack2.quantity.should.equal 1 stack2.quantity.should.equal 1
it 'requires the data to start with a number', -> it 'requires the data to start with a number', ->
@@ -144,7 +143,7 @@ describe "ModVersionParserV1", ->
it 'can promote a single item to a list', -> it 'can promote a single item to a list', ->
list = parser._parseStackList 'boat' list = parser._parseStackList 'boat'
(stack.itemSlug for stack in list).should.eql ['boat'] (stack.slug for stack in list).should.eql ['boat']
it 'can require a list to be non-empty', -> it 'can require a list to be non-empty', ->
parser._errorLocation = 'boat' parser._errorLocation = 'boat'
@@ -157,4 +156,4 @@ describe "ModVersionParserV1", ->
it 'can parse a non-empty list', -> it 'can parse a non-empty list', ->
list = parser._parseStackList [[3, 'plank'], [3, 'wool']] list = parser._parseStackList [[3, 'plank'], [3, 'wool']]
(stack.itemSlug for stack in list).sort().should.eql ['plank', 'wool'] (stack.slug for stack in list).sort().should.eql ['plank', 'wool']
@@ -18,7 +18,7 @@ describe 'ModVersionParserV2', ->
beforeEach -> beforeEach ->
modVersion = new ModVersion name:'Test', version:'0.0' modVersion = new ModVersion name:'Test', version:'0.0'
parser = new ModVersionParserV2 modVersion:modVersion parser = new ModVersionParserV2 model:modVersion
describe 'Item', -> describe 'Item', ->
@@ -29,15 +29,15 @@ describe 'ModVersionParserV2', ->
recipe:; input:Alpha; pattern:... .0. ...; recipe:; input:Alpha; pattern:... .0. ...;
recipe:; input:Bravo; pattern:... 0.0 ...;" recipe:; input:Bravo; pattern:... 0.0 ...;"
modVersion = parser.parse baseText + recipes modVersion = parser.parse baseText + recipes
recipes = modVersion.items.charlie.recipes recipes = modVersion._items.charlie._recipes
recipes[0].input[0].itemSlug.should.equal 'alpha' recipes[0].input[0].slug.should.equal 'alpha'
recipes[1].input[0].itemSlug.should.equal 'bravo' recipes[1].input[0].slug.should.equal 'bravo'
describe 'name', -> describe 'name', ->
it 'adds the name when present', -> it 'adds the name when present', ->
modVersion = parser.parse baseText + 'item: Charlie' modVersion = parser.parse baseText + 'item: Charlie'
modVersion.items.charlie.name.should.equal 'Charlie' modVersion._items.charlie.name.should.equal 'Charlie'
it 'requires a non-empty name', -> it 'requires a non-empty name', ->
func = -> parser.parse baseText + 'item: \n' func = -> parser.parse baseText + 'item: \n'
@@ -47,7 +47,7 @@ describe 'ModVersionParserV2', ->
it 'adds "gatherable" when present', -> it 'adds "gatherable" when present', ->
modVersion = parser.parse baseText + 'item: Alpha Bravo; gatherable: yes' modVersion = parser.parse baseText + 'item: Alpha Bravo; gatherable: yes'
modVersion.items.alpha_bravo.isGatherable.should.be.true modVersion._items.alpha_bravo.isGatherable.should.be.true
it 'does not allow a duplicate "gatherable" declaration', -> it 'does not allow a duplicate "gatherable" declaration', ->
func = -> parser.parse baseText + 'item: Alpha Bravo; gatherable: yes; gatherable: yes' func = -> parser.parse baseText + 'item: Alpha Bravo; gatherable: yes; gatherable: yes'
@@ -67,7 +67,7 @@ describe 'ModVersionParserV2', ->
modVersion = parser.parse 'item: Alpha; version:0.0; name:Test' modVersion = parser.parse 'item: Alpha; version:0.0; name:Test'
modVersion.name.should.equal 'Test' modVersion.name.should.equal 'Test'
modVersion.version.should.equal '0.0' modVersion.version.should.equal '0.0'
modVersion.items.alpha.name.should.equal 'Alpha' modVersion._items.alpha.name.should.equal 'Alpha'
it 'does not allow duplicate item declarations', -> it 'does not allow duplicate item declarations', ->
func = -> parser.parse 'version:0.0; name:Test; item:Charlie; item:Charlie' func = -> parser.parse 'version:0.0; name:Test; item:Charlie; item:Charlie'
@@ -75,7 +75,7 @@ describe 'ModVersionParserV2', ->
it 'allows multiple items', -> it 'allows multiple items', ->
modVersion = parser.parse 'name:Test; version:0.0; item:Bravo; item:Charlie' modVersion = parser.parse 'name:Test; version:0.0; item:Bravo; item:Charlie'
_.keys(modVersion.items).sort().should.eql ['bravo', 'charlie'] _.keys(modVersion._items).sort().should.eql ['bravo', 'charlie']
describe 'name', -> describe 'name', ->
@@ -123,7 +123,7 @@ describe 'ModVersionParserV2', ->
it 'adds "input" when present', -> it 'adds "input" when present', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern: ... 012 ...' modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern: ... 012 ...'
slugs = (s.itemSlug for s in modVersion.items.charlie.recipes[0].input) slugs = (s.slug for s in modVersion._items.charlie._recipes[0].input)
slugs.should.eql ['alpha', 'bravo', 'charlie'] slugs.should.eql ['alpha', 'bravo', 'charlie']
it 'requires an "input" declaration', -> it 'requires an "input" declaration', ->
@@ -140,13 +140,13 @@ describe 'ModVersionParserV2', ->
it 'registers slugs for each input name', -> it 'registers slugs for each input name', ->
modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...' modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...'
_.keys(modVersion.names).sort().should.eql ['charlie', 'delta', 'echo', 'foxtrot'] modVersion._slugs.should.eql ['charlie', 'delta', 'echo', 'foxtrot']
describe 'pattern', -> describe 'pattern', ->
it 'adds "pattern" when present', -> it 'adds "pattern" when present', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.' modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
modVersion.items.charlie.recipes[0].pattern.should.equal '... .0. .1.' modVersion._items.charlie._recipes[0].pattern.should.equal '... .0. .1.'
it 'requires a "pattern" declaration', -> it 'requires a "pattern" declaration', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo' func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
@@ -174,7 +174,7 @@ describe 'ModVersionParserV2', ->
it 'computes the input stack sizes from the pattern', -> it 'computes the input stack sizes from the pattern', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern:111 .0. 2.2' modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern:111 .0. 2.2'
recipe = modVersion.items.charlie.recipes[0] recipe = modVersion._items.charlie._recipes[0]
recipe.input[0].quantity.should.equal 1 recipe.input[0].quantity.should.equal 1
recipe.input[1].quantity.should.equal 3 recipe.input[1].quantity.should.equal 3
recipe.input[2].quantity.should.equal 2 recipe.input[2].quantity.should.equal 2
@@ -190,7 +190,7 @@ describe 'ModVersionParserV2', ->
it 'adds "quantity" when present', -> it 'adds "quantity" when present', ->
modVersion = parser.parse baseText + 'quantity: 2' modVersion = parser.parse baseText + 'quantity: 2'
modVersion.items.charlie.recipes[0].output[0].quantity.should.equal 2 modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 2
it 'does not allow a duplicate "quantity" declaration', -> it 'does not allow a duplicate "quantity" declaration', ->
func = -> parser.parse baseText + 'quantity:1; quantity:2' func = -> parser.parse baseText + 'quantity:1; quantity:2'
@@ -202,7 +202,7 @@ describe 'ModVersionParserV2', ->
it 'assumes a quantity of 1 by default', -> it 'assumes a quantity of 1 by default', ->
modVersion = parser.parse baseText modVersion = parser.parse baseText
modVersion.items.charlie.recipes[0].output[0].quantity.should.equal 1 modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 1
it 'does not allow "quantity" before recipe', -> it 'does not allow "quantity" before recipe', ->
func = -> parser.parse 'name:Alpha; version:1; item:Bravo; quantity:12; recipe:;' func = -> parser.parse 'name:Alpha; version:1; item:Bravo; quantity:12; recipe:;'
@@ -215,18 +215,18 @@ describe 'ModVersionParserV2', ->
it 'adds a single item as the default output', -> it 'adds a single item as the default output', ->
modVersion = parser.parse baseText modVersion = parser.parse baseText
stack = modVersion.items.bravo.recipes[0].output[0] stack = modVersion._items.bravo._recipes[0].output[0]
stack.itemSlug.should.equal 'bravo' stack.slug.should.equal 'bravo'
stack.quantity.should.equal 1 stack.quantity.should.equal 1
it 'can add multiple extras with quantities', -> it 'can add multiple extras with quantities', ->
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo' modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
output = modVersion.items.bravo.recipes[0].output output = modVersion._items.bravo._recipes[0].output
output[0].itemSlug.should.equal 'bravo' output[0].slug.should.equal 'bravo'
output[0].quantity.should.equal 1 output[0].quantity.should.equal 1
output[1].itemSlug.should.equal 'delta' output[1].slug.should.equal 'delta'
output[1].quantity.should.equal 2 output[1].quantity.should.equal 2
output[2].itemSlug.should.equal 'echo' output[2].slug.should.equal 'echo'
output[2].quantity.should.equal 4 output[2].quantity.should.equal 4
it 'does not allow "extras" before "recipe"', -> it 'does not allow "extras" before "recipe"', ->
@@ -235,7 +235,7 @@ describe 'ModVersionParserV2', ->
it 'registers slugs for each output name', -> it 'registers slugs for each output name', ->
modVersion = parser.parse baseText + 'extras:Delta, Echo' modVersion = parser.parse baseText + 'extras:Delta, Echo'
_.keys(modVersion.names).sort().should.eql ['bravo', 'charlie', 'delta', 'echo'] modVersion._slugs.should.eql ['bravo', 'charlie', 'delta', 'echo']
it 'does not allow a duplicate "extras" declaration', -> it 'does not allow a duplicate "extras" declaration', ->
func = -> parser.parse baseText + 'extras:Echo; extras:Delta' func = -> parser.parse baseText + 'extras:Echo; extras:Delta'
@@ -248,17 +248,17 @@ describe 'ModVersionParserV2', ->
it 'can add a single tool', -> it 'can add a single tool', ->
modVersion = parser.parse baseText + 'tools: Furnace' modVersion = parser.parse baseText + 'tools: Furnace'
modVersion.items.bravo.recipes[0].tools[0].itemSlug.should.equal 'furnace' modVersion._items.bravo._recipes[0].tools[0].slug.should.equal 'furnace'
it 'can add multiple tools', -> it 'can add multiple tools', ->
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace' modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
tools = modVersion.items.bravo.recipes[0].tools tools = modVersion._items.bravo._recipes[0].tools
tools[0].itemSlug.should.equal 'crafting_table' tools[0].slug.should.equal 'crafting_table'
tools[1].itemSlug.should.equal 'furnace' tools[1].slug.should.equal 'furnace'
it 'registers slugs for each tool name', -> it 'registers slugs for each tool name', ->
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace' modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
_.keys(modVersion.names).sort().should.eql ['bravo', 'charlie', 'crafting_table', 'furnace'] modVersion._slugs.should.eql ['bravo', 'charlie', 'crafting_table', 'furnace']
it 'does not allow a duplicate "tools" declaration', -> it 'does not allow a duplicate "tools" declaration', ->
func = -> parser.parse baseText + 'tools:Crafting Table; tools:Furnace' func = -> parser.parse baseText + 'tools:Crafting Table; tools:Furnace'
+1
View File
@@ -21,6 +21,7 @@ global.util = require 'util'
Logger = require '../src/scripts/logger' Logger = require '../src/scripts/logger'
global.logger = new Logger level:Logger.TRACE global.logger = new Logger level:Logger.TRACE
require '../src/scripts/polyfill'
require '../src/scripts/underscore_mixins' require '../src/scripts/underscore_mixins'
# Test Registry ######################################################################################################## # Test Registry ########################################################################################################