This commit is contained in:
Andrew Miner
2015-01-02 23:03:06 -08:00
parent e389c01468
commit b642c51486
23 changed files with 848 additions and 438 deletions
+6 -4
View File
@@ -4,7 +4,9 @@ global._ = require 'underscore'
global.Backbone = require 'backbone'
fs = require 'fs'
RecipeBookParser = require '../src/scripts/models/recipe_book_parser'
ModVersionParser = require '../src/scripts/models/mod_version_parser'
require '../src/scripts/underscore_mixins'
########################################################################################################################
@@ -33,9 +35,9 @@ else
text = fs.readFileSync sourceFileName, 'UTF-8'
data = JSON.parse text
parser = new RecipeBookParser
recipeBook = parser.parse data
text = parser.unparse recipeBook
parser = new ModVersionParser
modVersion = parser.parse data
text = parser.unparse modVersion
if targetFileName is '-'
console.log text
+4
View File
@@ -764,6 +764,10 @@
"output": [[16, "Light Gray Stained Glass Pane"]],
"input": [[6, "Light Gray Stained Glass"]],
"tools": "Crafting Table"
}, {
"output": [[8, "Light Gray Stained Glass"]],
"input": [[8, "Glass"], "Light Gray Dye"],
"tools": "Crafting Table"
}, {
"output": "Light Gray Wool",
"input": ["Light Gray Dye", "Wool"]
@@ -30,7 +30,7 @@ module.exports = class CraftingTableController extends BaseController
onModPackChanged: ->
return unless @rendered
@model.modPack.enableBooksForRecipe @$nameField.val()
@model.modPack.enableModsForItem @$nameField.val()
@_updateNameAutocomplete()
@_craft()
@@ -134,7 +134,7 @@ module.exports = class CraftingTableController extends BaseController
onChanged = => @onNameFieldChanged()
@$nameField.autocomplete
source: @model.modPack.gatherNames()
source: @model.modPack.gatherRecipeNames()
delay: 0
minLength: 0
change: onChanged
@@ -15,7 +15,7 @@ module.exports = class ModPackController extends BaseController
constructor: (options={})->
if not options.model? then throw new Error "options.model is required"
@_bookControllers = []
@_modVersionControllers = []
options.templateName = 'mod_pack'
super options
@@ -23,8 +23,8 @@ module.exports = class ModPackController extends BaseController
# BaseController Overrides #####################################################################
onWillRender: ->
if @model.books.length is 0
@model.loadAllBooks DefaultBookUrls
if @model.modVersions.length is 0
@model.loadAllModVersions DefaultBookUrls
onDidRender: ->
@$table = @$('table')
@@ -34,12 +34,12 @@ module.exports = class ModPackController extends BaseController
@$('table tr:not(:last-child)').remove()
return unless @model?
@_bookControllers = []
for i in [@model.books.length-1..0] by -1
book = @model.books[i]
controller = new ModVersionController model:book
@_modVersionControllers = []
for i in [@model.modVersions.length-1..0] by -1
modVersion = @model.modVersions[i]
controller = new ModVersionController model:modVersion
controller.render()
@_bookControllers.push controller
@_modVersionControllers.push controller
@$table.prepend controller.$el
super
+6 -4
View File
@@ -1,10 +1,12 @@
###
# Crafting Guide - main.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
Crafting Guide - main.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
require './underscore_mixins'
views = require './views'
Logger = require './logger'
CraftingGuideRouter = require './crafting_guide_router'
+46 -35
View File
@@ -29,11 +29,18 @@ module.exports = class CraftingPlan
return this
craft: (name, quantity=1, have=null)->
logger.trace "craft(#{name}, #{quantity}, #{have})"
item = @modPack.findItemByName name
logger.debug "item: #{item}"
if not item? then throw new Error "cannot find an item named: #{name}"
@clear()
@result.addInventory(have) if have?
@_expected.add name, quantity
@_expected.add item, quantity
@_pending = @_expected.clone()
while not @_pending.isEmpty
@_processPending()
@@ -47,50 +54,54 @@ module.exports = class CraftingPlan
# Private Methods ##############################################################################
_processPending: ->
targetItem = @_pending.pop()
targetStack = @_pending.pop()
targetItem = targetStack.item
logger.verbose "processing item: #{targetItem}, craftable? #{targetItem.isCraftable}"
return unless targetItem?
return if (not targetItem.isCraftable) or targetItem.isGatherable
return if @modPack.isRawMaterial targetItem.name
recipes = @modPack.gatherRecipes targetItem.name
return if not recipes.length > 0
recipe = recipes[0]
recipe = targetItem.recipes[0]
logger.verbose "recipe: #{recipe}"
if @includingTools
for tool in recipe.tools
totalExpected = @result.quantityOf(tool.name) + @_expected.quantityOf(tool.name)
if totalExpected < tool.quantity
@_pending.add tool.name, tool.quantity
@_expected.add tool.name, tool.quantity
for toolItem in recipe.tools
totalExpected = @result.quantityOf(toolItem.slug) + @_expected.quantityOf(toolItem.slug)
if totalExpected < 1
@_pending.add toolItem
@_expected.add toolItem
while @_totalQuantityOf(targetItem.name) < @_expected.quantityOf(targetItem.name)
while @_totalQuantityOf(targetItem.slug) < @_expected.quantityOf(targetItem.slug)
@steps.push recipe
for item in recipe.input
@_processInputItem item
for stack in recipe.input
@_processInputStack stack
for item in recipe.output
@_processOutputItem item
for stack in recipe.output
@_processOutputStack stack
_processInputItem: (item)->
quantityAvailable = @result.quantityOf item.name
quantityUsed = Math.min quantityAvailable, item.quantity
quantityNeeded = item.quantity - quantityUsed
_processInputStack: (stack)->
slug = stack.item.slug
quantityAvailable = @result.quantityOf slug
quantityUsed = Math.min quantityAvailable, stack.quantity
quantityNeeded = stack.quantity - quantityUsed
logger.verbose "processing input:#{stack.name},
a:#{quantityAvailable}, u:#{quantityUsed}, n:#{quantityNeeded}"
@result.remove item.name, quantityUsed
@_pending.add item.name, quantityNeeded
@need.add item.name, quantityNeeded
@result.remove slug, quantityUsed
@_pending.add stack.item, quantityNeeded
@need.add stack.item, quantityNeeded
_processOutputItem: (item)->
quantityMissing = @need.quantityOf item.name
quantityUsed = Math.min quantityMissing, item.quantity
quantityLeft = item.quantity - quantityUsed
_processOutputStack: (stack)->
slug = stack.item.slug
quantityMissing = @need.quantityOf slug
quantityUsed = Math.min quantityMissing, stack.quantity
quantityLeft = stack.quantity - quantityUsed
logger.verbose "processing output:#{stack.name},
m:#{quantityMissing}, u:#{quantityUsed}, l:#{quantityLeft}"
@make.add item.name, item.quantity
@need.remove item.name, quantityUsed
@result.add item.name, quantityLeft
@make.add stack.item, stack.quantity
@need.remove slug, quantityUsed
@result.add stack.item, quantityLeft
_totalQuantityOf: (name)->
return @result.quantityOf(name) - @need.quantityOf(name)
_totalQuantityOf: (slug)->
return @result.quantityOf(slug) - @need.quantityOf(slug)
+6 -6
View File
@@ -1,8 +1,8 @@
###
# Crafting Guide - crafting_table.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
Crafting Guide - crafting_table.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
@@ -30,10 +30,10 @@ module.exports = class CraftingTable extends BaseModel
@plan = null
return
toolPhrase = if @includingTools then ' includingTools' else ''
toolPhrase = if @includingTools then ' including tools' else ''
logger.verbose "calculating build plan for #{@quantity} #{@name}#{toolPhrase} with inventory: #{@have}"
@modPack.enableBooksForRecipe @name
@modPack.enableModsForItem @name
plan = new CraftingPlan @modPack, @includingTools
plan.includingTools = @includingTools
+48 -48
View File
@@ -7,7 +7,7 @@
BaseModel = require './base_model'
{Event} = require '../constants'
Item = require './item'
Stack = require './stack'
########################################################################################################################
@@ -17,91 +17,91 @@ module.exports = class Inventory extends BaseModel
super attributes, options
@clear()
Object.defineProperty this, 'isEmpty', get:-> @_names.length is 0
Object.defineProperty @prototype, 'isEmpty', get:-> @_slugs.length is 0
# Public Methods ###############################################################################
add: (name, quantity=1)->
add: (item, quantity=1)->
return if quantity is 0
item = @_items[name]
if not item?
item = new Item name:name, quantity:quantity
@_items[name] = item
@_names.push name
@_names.sort()
stack = @_stacks[item.slug]
if not stack?
stack = new Stack item:item, quantity:quantity
@_stacks[item.slug] = stack
@_slugs.push item.slug
@_slugs.sort()
else
item.quantity += quantity
stack.quantity += quantity
@trigger Event.add, this, name, quantity
@trigger Event.add, this, item, quantity
@trigger Event.change, this
return this
addInventory: (inventory)->
inventory.each (item)=> @add item.name, item.quantity
inventory.each (stack)=> @add stack.item, stack.quantity
return this
clear: ->
@_items = {}
@_names = []
@_stacks = {}
@_slugs = []
clone: ->
inventory = new Inventory
@each (item)-> inventory.add item.name, item.quantity
@each (stack)-> inventory.add stack.item, stack.quantity
return inventory
each: (onItem)->
for name in @_names
item = @_items[name]
onItem item
for slug in @_slugs
stack = @_stacks[slug]
onItem stack
hasAtLeast: (name, quantity=1)->
hasAtLeast: (slug, quantity=1)->
if quantity is 0 then return true
item = @_items[name]
return false unless item?
return item.quantity >= quantity
stack = @_stacks[slug]
return false unless stack?
return stack.quantity >= quantity
pop: ->
name = @_names.pop()
return null unless name?
slug = @_slugs.pop()
return null unless slug?
item = @_items[name]
delete @_items[name]
stack = @_stacks[slug]
delete @_stacks[slug]
@trigger Event.remove, this, item.name, item.quantity
@trigger Event.remove, this, stack.item, stack.quantity
@trigger Event.change, this
return item
return stack
quantityOf: (name)->
item = @_items[name]
return 0 unless item?
return item.quantity
quantityOf: (slug)->
stack = @_stacks[slug]
return 0 unless stack?
return stack.quantity
remove: (name, quantity=1)->
remove: (slug, quantity=1)->
return if quantity is 0
item = @_items[name]
if not item? then throw new Error "cannot remove #{name} since it is not in this inventory"
if item.quantity < quantity
throw new Error "cannot remove #{quantity} #{name} because there is only #{item.quantity} in this inventory"
stack = @_stacks[slug]
if not stack? then throw new Error "cannot remove #{slug} since it is not in this inventory"
if stack.quantity < quantity
throw new Error "cannot remove #{quantity} #{slug} because there is only #{stack.quantity} in this inventory"
item.quantity -= quantity
if item.quantity is 0
delete @_items[name]
@_names = _(@_names).without name
stack.quantity -= quantity
if stack.quantity is 0
delete @_stacks[slug]
@_slugs = _(@_slugs).without slug
@trigger Event.remove, this, name, quantity
@trigger Event.remove, this, slug, quantity
@trigger Event.change, this
return this
toList: ->
result = []
@each (item)->
if item.quantity > 1
result.push [item.quantity, item.name]
@each (stack)->
if stack.quantity > 1
result.push [stack.quantity, stack.item.slug]
else
result.push item.name
result.push stack.item.slug
return result
# Object Overrides #############################################################################
@@ -110,9 +110,9 @@ module.exports = class Inventory extends BaseModel
result = [@constructor.name, " (", @cid, ") { items: ["]
needsDelimiter = false
@each (item)->
@each (stack)->
if needsDelimiter then result.push ', '
result.push item.toString()
result.push stack.toString()
needsDelimiter = true
result.push ']'
+6 -5
View File
@@ -1,11 +1,12 @@
###
# Crafting Guide - inventory_parser.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
Crafting Guide - inventory_parser.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
Inventory = require './inventory'
Item = require './item'
########################################################################################################################
@@ -24,6 +25,6 @@ module.exports = class InventoryParser
quantity = if match? then parseInt(match[1]) else 1
if name.length > 0
inventory.add name, quantity
inventory.add new Item(name:name), quantity
return inventory
+32 -22
View File
@@ -14,38 +14,48 @@ module.exports = class Item extends BaseModel
@DEFAULT_STACK_SIZE = 64
constructor: (attributes={}, options={})->
attributes.name ?= ''
attributes.quantity ?= 1
if not attributes.name? then throw new Error 'attributes.name is required'
attributes.isGatherable ?= false
attributes.recipes ?= []
attributes.slug ?= _.slugify attributes.name
attributes.stackSize ?= Item.DEFAULT_STACK_SIZE
super attributes, options
Object.defineProperty this, 'stackQuantity', get:@getStackQuantity
Object.defineProperty @prototype, 'isCraftable', get:-> @recipes.length > 0
# Public Methods ###############################################################################
canMerge: (item)->
return item.name is @name
addRecipe: (recipe)->
output = recipe.output[0].item
if output isnt this then throw new Error "invalid recipe for #{@name} because it makes a #{output.name}"
merge: (item)->
if not @canMerge(item) then throw new Error "cannot merge #{item} into #{this}"
@quantity += item.quantity
@recipes.push recipe
# Property Methods #############################################################################
getStackQuantity: ->
count = 0
extra = @quantity
while extra > @stackSize
extra -= @stackSize
count += 1
return count:count, extra:extra
compareTo: (that)->
if this.name isnt that.name
return if this.name < that.name then -1 else +1
return 0
# Object Overrides #############################################################################
toString: ->
result = "#{@constructor.name} (#{@cid}) { name:\"#{@name}\", quantity:#{@quantity}"
result = []
result.push @constructor.name
result.push ' ('; result.push @cid; result.push ') { '
result.push 'name:"'; result.push @name; result.push '", '
result.push 'isGatherable:'; result.push @isGatherable
if _.slugify(@name) isnt @slug
result.push ', slug:'; result.push @slug
if @stackSize isnt Item.DEFAULT_STACK_SIZE
result += ", stackSize:#{@stackSize}"
result += ' }'
return result
result.push ', stackSize:'; result.push @stackSize
if @recipes.length > 0
result.push ', recipes:'
result.push @recipes.length
result.push ' items'
result.push '}'
return result.join ''
+44 -69
View File
@@ -9,32 +9,43 @@ BaseModel = require './base_model'
{Event} = require '../constants'
ModVersionParser = require './mod_version_parser'
{RequiredMods} = require '../constants'
util = require 'util'
########################################################################################################################
module.exports = class ModPack extends BaseModel
constructor: (attributes={}, options={})->
attributes.books ?= []
attributes.modVersions ?= []
super attributes, options
@_parser = new ModVersionParser
# Public Methods ###############################################################################
enableBooksForRecipe: (name)->
for book in @books
continue if book.enabled
if book.hasRecipe name
book.enabled = true
enableModsForItem: (name)->
for modVersion in @modVersions
continue if modVersion.enabled
if modVersion.hasRecipe name
modVersion.enabled = true
gatherNames: (options={})->
findItemByName: (name, options={})->
options.includeDisabled ?= false
for modVersion in @modVersions
continue unless modVersion.enabled or options.includeDisabled
item = modVersion.findItemByName name
return item if item?
return null
gatherRecipeNames: (options={})->
options.includeDisabled ?= false
nameData = {}
for book in @books
continue unless book.enabled or options.includeDisabled
book.gatherNames nameData
for modVersion in @modVersions
continue unless modVersion.enabled or options.includeDisabled
modVersion.gatherRecipeNames nameData
result = []
names = _.keys(nameData).sort()
@@ -42,76 +53,56 @@ module.exports = class ModPack extends BaseModel
result.push nameData[name]
return result
gatherRecipes: (name, options={})->
options.includeDisabled ?= false
result = []
for book in @books
continue unless book.enabled or options.includeDisabled
book.gatherRecipes name, result
return result
hasRecipe: (name, options={})->
options.includeDisabled ?= false
for book in @books
continue unless book.enabled or options.includeDisabled
return true if book.hasRecipe name
for modVersion in @modVersions
continue unless modVersion.enabled or options.includeDisabled
return true if modVersion.hasRecipe name
return false
isRawMaterial: (name, options={})->
options.includeDisabled ?= false
for book in @books
continue unless book.enabled or options.includeDisabled
return true if book.isRawMaterial name
return false
loadBook: (url)->
loadModVersion: (url)->
w.promise (resolve, reject)=>
@trigger Event.load.started, this, url
$.ajax
url: url
dataType: 'json'
success: (data, status, xhr)=>
resolve @onBookLoaded(url, data, status, xhr)
resolve @onModVersionLoaded(url, data, status, xhr)
error: (xhr, status, error)=>
reject @onBookLoadFailed(url, error, status, xhr)
reject @onModVersionLoadFailed(url, error, status, xhr)
loadBookData: (data)->
book = @_parser.parse data
@books.push book
@_sortBooks()
book.on Event.change, => @trigger Event.change, this
loadModVersionData: (data)->
modVersion = @_parser.parse data
@modVersions.push modVersion
@modVersions.sort (a, b)-> a.compareTo b
modVersion.on Event.change, => @trigger Event.change, this
return book
return modVersion
loadAllBooks: (urlList)->
promises = (@loadBook(url) for url in urlList)
loadAllModVersions: (urlList)->
promises = (@loadModVersion(url) for url in urlList)
return w.settle promises
# Event Methods ################################################################################
onBookLoaded: (url, data, status, xhr)->
onModVersionLoaded: (url, data, status, xhr)->
try
book = @loadBookData data
modVersion = @loadModVersionData data
logger.info "loaded recipe book from #{url}: #{book}"
@trigger Event.load.succeeded, this, book
logger.info "loaded ModVersion from #{url}: #{modVersion}"
@trigger Event.load.succeeded, this, modVersion
@trigger Event.load.finished, this
@trigger Event.change, this
return book
return modVersion
catch e
@onBookLoadFailed url, e, status, xhr
@onModVersionLoadFailed url, e, status, xhr
onBookLoadFailed: (url, error, status, xhr)->
onModVersionLoadFailed: (url, error, status, xhr)->
message = if error.stack? then error.stack else error
logger.error "failed to load recipe book from #{url}: #{message}"
logger.error "failed to load ModVersion from #{url}: #{message}"
@trigger Event.load.failed, this, error.message
@trigger Event.load.finished, this
return error
@@ -119,20 +110,4 @@ module.exports = class ModPack extends BaseModel
# Object Overrides #############################################################################
toString: ->
return "ModPack (#{@cid}) {books:#{@books.length} items}"
_sortBooks:->
@books.sort (a, b)->
if a.modName is b.modName then return 0
aRequired = a.modName in RequiredMods
bRequired = b.modName in RequiredMods
if aRequired and bRequired
return if a.modName < b.modName then -1 else +1
else if aRequired
return -1
else if bRequired
return +1
else
return if a.modName < b.modName then -1 else +1
return "ModPack (#{@cid}) {modVersions:#{@modVersions.length} items}"
+34 -20
View File
@@ -17,34 +17,48 @@ module.exports = class ModVersion extends BaseModel
if _.isEmpty(attributes.modVersion) then throw new Error 'modVersion cannot be empty'
attributes.description ?= ''
attributes.rawMaterials ?= []
attributes.recipes ?= []
attributes.items ?= {}
attributes.enabled ?= attributes.modName in RequiredMods
super attributes, options
# Public Methods ###############################################################################
gatherNames: (result)->
for recipe in @recipes
continue if result[recipe.name]
result[recipe.name] = value:recipe.name, label:"#{recipe.name} (from #{@modName} #{@modVersion})"
addItem: (item)->
if @items[item.slug]? then throw new Error "duplicate item for #{item.slug}"
@items[item.slug] = item
return this
compareTo: (that)->
if this.modName is that.modName then return 0
thisRequired = this.modName in RequiredMods
thatRequired = that.modName in RequiredMods
if thisRequired and thatRequired
return if this.modName < that.modName then -1 else +1
else if thisRequired
return -1
else if thatRequired
return +1
else
return if this.modName < that.modName then -1 else +1
findItemByName: (name)->
slug = _.slugify name
return @items[slug]
gatherRecipeNames: (result={})->
for slug, item of @items
continue if result[item.slug]
continue unless item.isCraftable
result[item.slug] = value:item.name, label:"#{item.name} (from #{@modName} #{@modVersion})"
return result
gatherRecipes: (name, result)->
for recipe in @recipes
if recipe.name is name
result.push recipe
return result
isRawMaterial: (name)->
return name in @rawMaterials
hasRecipe: (name)->
for recipe in @recipes
return true if recipe.name is name
return false
item = @findItemByName name
return false unless item?
return item.recipes.length > 0
# Object Overrides #############################################################################
@@ -53,4 +67,4 @@ module.exports = class ModVersion extends BaseModel
enabled:#{@enabled},
modName:#{@modName},
modVersion:#{@modVersion},
recipes:#{@recipes.length} items}"
items:#{_.keys(@items).length} items}"
+140 -85
View File
@@ -6,8 +6,10 @@ All rights reserved.
###
Item = require './item'
Recipe = require './recipe'
ModVersion = require './mod_version'
Recipe = require './recipe'
Stack = require './stack'
util = require 'util'
########################################################################################################################
@@ -20,21 +22,21 @@ module.exports = class ModVersionParser
'1': new V1
parse: (data)->
if not data? then throw new Error 'recipe book data is missing'
if not data? then throw new Error 'mod description data is missing'
if not data.version? then throw new Error 'version is required'
parser = @_parsers["#{data.version}"]
if not parser? then throw new Error "cannot parse version #{data.version} recipe books"
if not parser? then throw new Error "cannot parse version #{data.version} mod descriptions"
return parser.parse data
unparse: (ModVersion, version=ModVersionParser.CURRENT_VERSION)->
if not ModVersion? then throw new Error 'recipe book is required'
unparse: (modVersion, version=ModVersionParser.CURRENT_VERSION)->
if not modVersion? then throw new Error 'modVersion is required'
parser = @_parsers["#{version}"]
if not parser? then throw new Error "version #{version} is not supported"
return parser.unparse ModVersion
return parser.unparse modVersion
########################################################################################################################
@@ -46,62 +48,77 @@ module.exports.V1 = class V1
parse: (data)->
return @_parseModVersion data
unparse: (ModVersion)->
return @_unparseModVersion ModVersion
unparse: (modVersion)->
return @_unparseModVersion modVersion
# Private Methods ##############################################################################
_parseModVersion: (data)->
if not data? then throw new Error 'recipe book data is missing'
if not data.version? then throw new Error 'version is required'
if not data.mod_name? then throw new Error 'mod_name is required'
if not data.mod_version? then throw new Error 'mod_version is required'
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
book = new ModVersion version:data.version, modName:data.mod_name, modVersion:data.mod_version
book.description = data.description or ''
book.rawMaterials = data.raw_materials or []
for index in [0...data.recipes.length]
@_errorLocation = "recipe #{index + 1}"
recipeData = data.recipes[index]
recipe = @_parseRecipe recipeData
recipe._originalIndex = index
book.recipes.push recipe
return book
_parseRecipe: (data, options={})->
if not data? then throw new Error "recipe data is missing for #{@_errorLocation}"
if not data.output? then throw new Error "#{@_errorLocation} is missing output"
output = @_parseItemList data.output, field:'output', canBeEmpty:false
@_errorLocation = "recipe for #{output[0].name}"
if not data.input? then throw new Error "#{@_errorLocation} is missing input"
data.tools ?= []
input = @_parseItemList data.input, field:'input', canBeEmpty:true
tools = @_parseItemList data.tools, field:'tools', canBeEmpty:true
return new Recipe input:input, output:output, tools:tools
_findOrCreateItem: (name)->
item = @modVersion.findItemByName name
if not item?
item = new Item name:name
@modVersion.addItem item
return item
_parseItemList: (data, options={})->
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
if not _.isArray(data) then data = [data]
if data.length is 0 and not options.canBeEmpty
throw new Error "#{options.field} for #{@_errorLocation} cannot be empty"
result = []
for index in [0...data.length]
itemData = data[index]
result.push @_parseItem itemData, field:options.field, index:index
for name in data
item = @_findOrCreateItem name
result.push item
return result
_parseItem: (data, options={})->
_parseModVersion: (data)->
if not data? then throw new Error 'mod description data is missing'
if not data.version? then throw new Error 'version is required'
if not data.mod_name? then throw new Error 'mod_name is required'
if not data.mod_version? then throw new Error 'mod_version is required'
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
@modVersion = modVersion = new ModVersion modName:data.mod_name, modVersion:data.mod_version
modVersion.description = data.description or ''
@_parseRawMaterials data.raw_materials
for index in [0...data.recipes.length]
@_errorLocation = "recipe #{index + 1}"
recipeData = data.recipes[index]
@_parseRecipe recipeData
@modVersion = null
return modVersion
_parseRawMaterials: (data, options={})->
return unless data? and data.length > 0
results = []
for name in data
item = @_findOrCreateItem name
item.isGatherable = true
results.push item
_parseRecipe: (data, options={})->
if not data? then throw new Error "recipe data is missing for #{@_errorLocation}"
if not data.output? then throw new Error "#{@_errorLocation} is missing output"
output = @_parseStackList data.output, field:'output', canBeEmpty:false
item = output[0].item
@_errorLocation = "recipe for #{item.name}"
if not data.input? then throw new Error "#{@_errorLocation} is missing input"
data.tools ?= []
input = @_parseStackList data.input, field:'input', canBeEmpty:true
tools = @_parseItemList data.tools, field:'tools'
recipe = new Recipe input:input, output:output, tools:tools
item.addRecipe recipe
return recipe
_parseStack: (data, options={})->
errorBase = "#{options.field} element #{options.index} for #{@_errorLocation}"
if not data? then throw new Error "#{errorBase} is missing"
@@ -112,39 +129,64 @@ module.exports.V1 = class V1
if data.length isnt 2 then throw new Error "#{errorBase} must have at least one element"
if not _.isNumber(data[0]) then throw new Error "#{errorBase} must start with a number"
return new Item quantity:data[0], name:data[1]
item = @_findOrCreateItem data[1]
return new Stack item:item, quantity:data[0]
_unparseModVersion: (ModVersion)->
_parseStackList: (data, options={})->
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
if not _.isArray(data) then data = [data]
if data.length is 0 and not options.canBeEmpty
throw new Error "#{options.field} for #{@_errorLocation} cannot be empty"
result = []
for index in [0...data.length]
stackData = data[index]
result.push @_parseStack stackData, field:options.field, index:index
return result
_parseItemList: (data, options={})->
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
if not _.isArray(data) then data = [data]
result = []
for name in data
item = @_findOrCreateItem name
result.push item
return result
# Un-parsing Methods ###########################################################################
_unparseModVersion: (modVersion)->
result = []
result.push '{\n'
result.push ' "version": 1,\n'
result.push ' "mod_name": "' + ModVersion.modName + '",\n'
result.push ' "mod_version": "' + ModVersion.modVersion + '",\n'
if ModVersion.description.length > 0
result.push ' "description": "' + ModVersion.description + '",\n'
result.push ' "mod_name": "' + modVersion.modName + '",\n'
result.push ' "mod_version": "' + modVersion.modVersion + '",\n'
if modVersion.description.length > 0
result.push ' "description": "' + modVersion.description + '",\n'
if ModVersion.rawMaterials.length > 0
rawMaterials = (item.name for slug, item of modVersion.items when item.isGatherable)
rawMaterials.sort()
if rawMaterials.length > 0
result.push ' "raw_materials": [\n'
firstItem = true
materials = ModVersion.rawMaterials.slice()
materials.sort()
for material in materials
for material in rawMaterials
if not firstItem then result.push ',\n'
result.push ' "' + material + '"'
firstItem = false
result.push '\n ],\n'
result.push ' "recipes": [\n'
recipes = ModVersion.recipes.slice()
recipes.sort (a, b)->
if a.name isnt b.name
return if a.name < b.name then -1 else +1
if a._originalIndex isnt b._originalIndex
return if a._originalIndex < b._originalIndex then -1 else +1
return 0
items = (item for slug, item of modVersion.items when item.isCraftable)
items.sort (a, b)-> a.compareTo b
firstItem = true
for recipe in recipes
for item in items
for recipe in item.recipes
result.push if firstItem then ' {\n' else ' }, {\n'
@_unparseRecipe recipe, result
firstItem = false
@@ -157,12 +199,12 @@ module.exports.V1 = class V1
_unparseRecipe: (recipe, result=[])->
result.push ' "output": '
@_unparseItemList recipe.output, result, sort:false
@_unparseStackList recipe.output, result, sort:false
if recipe.input.length > 0
result.push ',\n'
result.push ' "input": '
@_unparseItemList recipe.input, result
@_unparseStackList recipe.input, result
if recipe.tools.length > 0
result.push ',\n'
@@ -172,38 +214,51 @@ module.exports.V1 = class V1
result.push '\n'
return result
_unparseItemList: (itemList, result, options={})->
_unparseStackList: (stackList, result, options={})->
options.sort ?= true
if itemList.length is 0
if stackList.length is 0
result.push '[]'
else if itemList.length is 1
item = itemList[0]
if item.quantity is 1
result.push '"' + item.name + '"'
else if stackList.length is 1
stack = stackList[0]
if stack.quantity is 1
result.push '"' + stack.name + '"'
else
result.push '[[' + item.quantity + ', "' + item.name + '"]]'
result.push '[[' + stack.quantity + ', "' + stack.name + '"]]'
else
result.push '['
items = itemList.slice()
stacks = stackList.slice()
if options.sort
items.sort (a, b)->
stacks.sort (a, b)->
if a.quantity isnt b.quantity
return if a.quantity > b.quantity then -1 else +1
if a.name isnt b.name
return if a.name < b.name then -1 else +1
if a.item.name isnt b.item.name
return if a.item.name < b.item.name then -1 else +1
return 0
firstItem = true
for item in items
for stack in stacks
result.push ', ' if not firstItem
if item.quantity is 1
result.push '"' + item.name + '"'
if stack.quantity is 1
result.push '"' + stack.name + '"'
else
result.push '[' + item.quantity + ', "' + item.name + '"]'
result.push '[' + stack.quantity + ', "' + stack.name + '"]'
firstItem = false
result.push ']'
return result
_unparseItemList: (itemList, result)->
if itemList.length is 1
result.push '"'; result.push itemList[0].name; result.push '"'
else
firstItem = true
for item in itemList
result.push if firstItem then '["' else '", "'
result.push item.name
firstItem = false
result.push '"]'
return result
+19 -16
View File
@@ -12,26 +12,29 @@ BaseModel = require './base_model'
module.exports = class Recipe extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.output? then throw new Error "attributes.output is required"
attributes.input ?= []
if not attributes.input? then throw new Error 'attributes.input is required'
if not attributes.output? then throw new Error 'attributes.output is required'
attributes.pattern ?= (i for i in [0...attributes.input.length]).join('')
attributes.tools ?= []
super attributes, options
Object.defineProperty @prototype, 'name', get:-> @output[0].name
Object.defineProperty @prototype, 'name', get:-> @output[0].item.name
# Public Methods ###############################################################################
make: (inventory, missing)->
for item in @input
needed = item.quantity
for stack in @input
item = stack.item
needed = stack.quantity
while needed > 0
if inventory.hasAtLeast item.name
inventory.remove item.name
if inventory.hasAtLeast item.slug
inventory.remove item.slug
else
missing.add item.name
missing.add item.slug
for item in @output
inventory.add item.name, item.quantity
for stack in @output
inventory.add stack.item, stack.quantity
return this
@@ -42,26 +45,26 @@ module.exports = class Recipe extends BaseModel
result.push ", input:["
needsDelimiter = false
for inputItem in @input
for stack in @input
if needsDelimiter then result.push ', '
result.push inputItem.toString()
result.push stack.toString()
needsDelimiter = true
result.push ']'
result.push ", output:["
needsDelimiter = false
for outputItem in @output
for stack in @output
if needsDelimiter then result.push ', '
result.push outputItem.toString()
result.push stack.toString()
needsDelimiter = true
result.push ']'
if @tools.length > 0
result.push ", tools:["
needsDelimiter = false
for tool in @tools
for stack in @tools
if needsDelimiter then result.push ', '
result.push tool.toString()
result.push stack.toString()
needsDelimiter = true
result.push ']'
+49
View File
@@ -0,0 +1,49 @@
###
Crafting Guide - stack.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
########################################################################################################################
module.exports = class Stack
constructor: (attributes={})->
if not attributes.item? then throw new Error 'item is required'
attributes.quantity ?= 1
@item = attributes.item
@quantity = attributes.quantity
Object.defineProperty @prototype, 'name', get:-> @item?.name
Object.defineProperty @prototype, 'stackQuantity', get:@getStackQuantity
# Public Methods ###############################################################################
canMerge: (stack)->
return @item.slug is stack.item.slug
merge: (stack)->
if not @canMerge stack
throw new Error "this stack of #{@item.name} cannot merge a stack of #{@stack.name}"
@quantity += stack.quantity
return this
# Property Methods #############################################################################
getStackQuantity: ->
count = 0
extra = @quantity
while extra > @item.stackSize
extra -= @item.stackSize
count += 1
return count:count, extra:extra
# Object Overrides #############################################################################
toString: ->
return "#{@quantity} #{@item.name}"
+18
View File
@@ -0,0 +1,18 @@
###
Crafting Guide - underscore.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
_.mixin
slugify: (text)->
return null unless text?
result = text.toLowerCase()
result = result.replace /[^a-zA-Z0-9_]/g, '_'
result = result.replace /_+/, '_'
result = result.replace /_$/, ''
result = result.replace /^_/, ''
return result
+22 -20
View File
@@ -1,8 +1,8 @@
###
# Crafting Guide - crafting_plan.test.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
Crafting Guide - crafting_plan.test.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
ModPack = require '../src/scripts/models/mod_pack'
@@ -10,15 +10,15 @@ CraftingPlan = require '../src/scripts/models/crafting_plan'
########################################################################################################################
catalog = plan = null
modPack = plan = null
########################################################################################################################
describe 'CraftingPlan', ->
beforeEach ->
catalog = new ModPack
catalog.loadBookData {
modPack = new ModPack
modPack.loadModVersionData {
version: 1
mod_name: 'Minecraft'
mod_version: '1.7.10'
@@ -31,8 +31,10 @@ describe 'CraftingPlan', ->
{ input:[[2, 'Iron Ingot'], 'Stick'], tools:'Crafting Table', output:'Iron Sword' }
]
}
logger.debug "modPack: #{modPack}"
logger.debug "modVersion: #{modPack.modVersions[0]}"
plan = new CraftingPlan catalog
plan = new CraftingPlan modPack
describe 'craft', ->
@@ -40,40 +42,40 @@ describe 'CraftingPlan', ->
it 'can craft a single step recipe', ->
plan.craft 'Oak Plank'
plan.need.toList().should.eql ['Oak Log']
plan.result.toList().should.eql [[4, 'Oak Plank']]
plan.need.toList().should.eql ['oak_log']
plan.result.toList().should.eql [[4, 'oak_plank']]
it 'can craft a multi-step recipe', ->
plan.craft 'Crafting Table'
plan.need.toList().should.eql ['Oak Log']
plan.result.toList().should.eql ['Crafting Table']
plan.need.toList().should.eql ['oak_log']
plan.result.toList().should.eql ['crafting_table']
it 'can craft a multi-step recipe using tools', ->
plan.craft 'Furnace'
plan.need.toList().should.eql [[8, 'Cobblestone']]
plan.result.toList().should.eql ['Furnace']
plan.need.toList().should.eql [[8, 'cobblestone']]
plan.result.toList().should.eql ['furnace']
it 'can craft a multi-step recipe re-using tools', ->
plan.craft 'Iron Sword'
plan.need.toList().should.eql [[2, 'Iron Ore'], 'Oak Log', [2, '{furnace fuel}']]
plan.result.toList().should.eql ['Iron Sword', [2, 'Oak Plank'], [3, 'Stick']]
plan.need.toList().should.eql [[2, 'furnace_fuel'], [2, 'iron_ore'], 'oak_log']
plan.result.toList().should.eql ['iron_sword', [2, 'oak_plank'], [3, 'stick']]
describe 'with building tools', ->
it 'can craft a multi-step recipe using tools', ->
plan.includingTools = true
plan.craft 'Furnace'
plan.need.toList().should.eql [[8, 'Cobblestone'], 'Oak Log']
plan.result.toList().should.eql ['Crafting Table', 'Furnace']
plan.need.toList().should.eql [[8, 'cobblestone'], 'oak_log']
plan.result.toList().should.eql ['crafting_table', 'furnace']
it 'can craft a multi-step recipe re-using tools', ->
plan.includingTools = true
plan.craft 'Iron Sword'
plan.need.toList().should.eql [
[8, 'Cobblestone'], [2, 'Iron Ore'], [2, 'Oak Log'], [2, '{furnace fuel}'],
[8, 'cobblestone'], [2, 'furnace_fuel'], [2, 'iron_ore'], [2, 'oak_log']
]
plan.result.toList().should.eql [
'Crafting Table', 'Furnace', 'Iron Sword', [2, 'Oak Plank'], [3, 'Stick']
'crafting_table', 'furnace', 'iron_sword', [2, 'oak_plank'], [3, 'stick']
]
describe 'using existing inventory', ->
+29 -29
View File
@@ -20,30 +20,30 @@ describe 'Inventory', ->
beforeEach ->
inventory = new Inventory
inventory.add 'wool', 4
inventory.add 'string', 20
inventory.add 'boat'
inventory.add new Item(name:'Wool'), 4
inventory.add new Item(name:'String'), 20
inventory.add new Item(name:'Boat')
describe 'add', ->
it 'can add to an empty inventory', ->
inventory.add 'iron ingot', 4
item = inventory._items['iron ingot']
item.constructor.name.should.equal 'Item'
item.name.should.equal 'iron ingot'
item.quantity.should.equal 4
inventory.add new Item(name:'Iron Ingot'), 4
stack = inventory._stacks['iron_ingot']
stack.constructor.name.should.equal 'Stack'
stack.name.should.equal 'Iron Ingot'
stack.quantity.should.equal 4
it 'can augment quantity of existing items', ->
inventory.add 'wool', 2
inventory.add new Item(name:'Wool'), 2
inventory.toList().should.eql ['boat', [20, 'string'], [6, 'wool']]
it 'can add zero quantity', ->
inventory.add 'wool', 0
inventory.add new Item(name:'Wool'), 0
inventory.toList().should.eql ['boat', [20, 'string'], [4, 'wool']]
it 'emits the proper events', ->
events = new EventRecorder inventory
inventory.add 'iron ingot', 10
inventory.add new Item(name:'Iron Ingot'), 10
events.names.should.eql [Event.add, Event.change]
describe 'addInventory', ->
@@ -51,11 +51,11 @@ describe 'Inventory', ->
it 'can add to an empty inventory', ->
newInventory = new Inventory
newInventory.addInventory inventory
newInventory._names.should.eql ['boat', 'string', 'wool']
newInventory._slugs.should.eql ['boat', 'string', 'wool']
it 'can add a mix of new and existing items', ->
newInventory = new Inventory
newInventory.add 'string', 2
newInventory.add new Item(name:'String'), 2
newInventory.addInventory inventory
newInventory.toList().should.eql ['boat', [22, 'string'], [4, 'wool']]
@@ -64,12 +64,12 @@ describe 'Inventory', ->
it 'creates an empty inventory from an empty inventory', ->
a = new Inventory
b = a.clone()
b._names.should.eql []
b._slugs.should.eql []
it 'faithfully copies an existing inventory', ->
copy = inventory.clone()
copy._names.should.eql ['boat', 'string', 'wool']
(item.quantity for name, item of copy._items).should.eql [1, 20, 4]
logger.debug "copy.toList(): #{copy.toList()}"
copy.toList().should.eql ['boat', [20, 'string'], [4, 'wool']]
describe 'each', ->
@@ -81,17 +81,17 @@ describe 'Inventory', ->
it 'works when items have only been added', ->
result = []
inventory.each (item)-> result.push item.name
result.should.eql ['boat', 'string', 'wool']
inventory.each (stack)-> result.push stack.name
result.should.eql ['Boat', 'String', 'Wool']
it 'works when items have been augmented', ->
inventory.add 'iron ingot'
inventory.add 'boat'
inventory.add 'wool', 2
inventory.add new Item name:'Iron Ingot'
inventory.add new Item name:'Boat'
inventory.add new Item(name:'Wool'), 2
result = []
inventory.each (item)-> result.push item.name
result.should.eql ['boat', 'iron ingot', 'string', 'wool']
inventory.each (stack)-> result.push stack.name
result.should.eql ['Boat', 'Iron Ingot', 'String', 'Wool']
describe 'hasAtLeast', ->
@@ -101,7 +101,7 @@ describe 'Inventory', ->
it 'always returns true for zero quantity', ->
inventory.hasAtLeast('chicken', 0).should.be.true
inventory.hasAtLeast('wool', 0).should.be.true
inventory.hasAtLeast('Wool', 0).should.be.true
it 'works for a quantity above 1', ->
inventory.hasAtLeast('wool', 3).should.be.true
@@ -116,9 +116,9 @@ describe 'Inventory', ->
expect(result).to.be.null
it 'completely removes the last item', ->
result = inventory.pop()
result.name.should.equal 'wool'
result.quantity.should.equal 4
stack = inventory.pop()
stack.name.should.equal 'Wool'
stack.quantity.should.equal 4
inventory.toList().should.eql ['boat', [20, 'string']]
it 'triggers the right events', ->
@@ -138,11 +138,11 @@ describe 'Inventory', ->
it 'removes a single item by default', ->
inventory.remove 'wool'
inventory._items.wool.quantity.should.equal 3
inventory._stacks.wool.quantity.should.equal 3
it 'removes a quantity above 1', ->
inventory.remove 'wool', 3
inventory._items.wool.quantity.should.equal 1
inventory._stacks.wool.quantity.should.equal 1
it 'emits the proper events', ->
events = new EventRecorder inventory
+8 -7
View File
@@ -7,6 +7,7 @@
Inventory = require '../src/scripts/models/inventory'
InventoryParser = require '../src/scripts/models/inventory_parser'
Item = require '../src/scripts/models/item'
########################################################################################################################
@@ -20,22 +21,22 @@ describe 'InventoryParser', ->
it 'returns an empty Inventory for an empty string', ->
result = parser.parse ''
result._names.length.should.equal 0
result.toList().should.eql []
it 'can parse a single item without quantity', ->
result = parser.parse 'wool'
result._items.wool.quantity.should.equal 1
result = parser.parse 'Wool'
result.toList().should.eql ['wool']
it 'can parse a single item with quantity', ->
result = parser.parse '4 wool'
result._items.wool.quantity.should.equal 4
result.toList().should.eql [[4, 'wool']]
it 'can parse multiple mixed-type items', ->
result = parser.parse '4 wool\n10 string\nboat\n\n'
result = parser.parse '4 Wool\n10 String\nBoat\n\n'
result.toList().should.eql ['boat', [10, 'string'], [4, 'wool']]
it 're-uses the given inventory object', ->
inventory = new Inventory
inventory.add 'string', 8
result = parser.parse '4 wool', inventory
inventory.add new Item(name:'String'), 8
result = parser.parse '4 Wool', inventory
result.toList().should.eql [[8, 'string'], [4, 'wool']]
+115
View File
@@ -0,0 +1,115 @@
###
Crafting Guide - mod_pack.test.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
Item = require '../src/scripts/models/item'
ModPack = require '../src/scripts/models/mod_pack'
ModVersion = require '../src/scripts/models/mod_version'
########################################################################################################################
buildcraft = industrialCraft = minecraft = modPack = null
########################################################################################################################
describe 'ModPack', ->
beforeEach ->
minecraft = new ModVersion modName:'Minecraft', modVersion:'1.7.10'
minecraft.addItem new Item name:'Wool'
minecraft.addItem new Item name:'Bed', recipes:['']
buildcraft = new ModVersion modName:'Buildcraft', modVersion:'4.0'
buildcraft.addItem new Item name:'Stone Gear', recipes:['']
buildcraft.addItem new Item name:'Bed', recipes:['']
industrialCraft = new ModVersion modName:'Industrial Craft', modVersion:'2.0'
industrialCraft.addItem new Item name:'Resin'
industrialCraft.addItem new Item name:'Rubber', recipes:['']
modPack = new ModPack modVersions:[minecraft, buildcraft, industrialCraft]
describe 'enableModsForItem', ->
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', ->
it 'finds the requested item', ->
item = modPack.findItemByName 'Bed'
item.name.should.equal 'Bed'
it 'ignores disabled mod versions', ->
item = modPack.findItemByName 'Stone Gear'
expect(item).to.be.null
it "doesn't ignore mod versions when include disabled is requested", ->
item = modPack.findItemByName 'Stone Gear', includeDisabled:true
item.name.should.equal 'Stone Gear'
describe 'gatherRecipeNames', ->
it 'finds all registered item names', ->
buildcraft.enabled = true
industrialCraft.enabled = true
(i.value for i in modPack.gatherRecipeNames()).sort().should.eql ['Bed', 'Rubber', 'Stone Gear']
it 'ignores duplicate item names', ->
buildcraft.enabled = true
names = modPack.gatherRecipeNames()
bedName = (e for e in names when e.value is 'Bed')[0]
bedName.should.eql value:'Bed', label:'Bed (from Minecraft 1.7.10)'
it 'alphabetizes the item names', ->
buildcraft.enabled = true
industrialCraft.enabled = true
(i.value for i in modPack.gatherRecipeNames()).should.eql ['Bed', 'Rubber', 'Stone Gear']
it 'ignores non-craftable items', ->
(n.value for n in modPack.gatherRecipeNames()).sort().should.eql ['Bed']
it 'ignores disabled mod versions', ->
(n.value for n in modPack.gatherRecipeNames()).should.not.include 'Stone Gear'
it "doesn't ignore disabled mod versions when include disabled is requested", ->
(n.value for n in modPack.gatherRecipeNames(includeDisabled:true)).should.include 'Stone Gear'
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
+95
View File
@@ -0,0 +1,95 @@
###
Crafting Guide - mod_version.test.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
Item = require '../src/scripts/models/item'
ModVersion = require '../src/scripts/models/mod_version'
########################################################################################################################
modVersion = null
########################################################################################################################
describe 'ModVersion', ->
beforeEach -> modVersion = new ModVersion modName:'Test', modVersion:'0.0'
describe 'constructor', ->
it 'requires a mod name', ->
expect(-> new ModVersion modVersion:'0.0').to.throw Error, 'modName cannot be empty'
it 'requires a mod version', ->
expect(-> new ModVersion modName:'Test').to.throw Error, 'modVersion cannot be empty'
it 'supplies default values', ->
modVersion.description.should.equal ''
modVersion.items.should.eql {}
modVersion.enabled.should.be.false
describe 'addItem', ->
it 'refuses to add duplicates', ->
modVersion.addItem new Item name:'Wool'
expect(-> modVersion.addItem new Item name:'Wool').to.throw Error, 'duplicate item for wool'
it 'adds an item indexes by its slug', ->
modVersion.addItem new Item name:'Wool'
modVersion.items.wool.name.should.equal 'Wool'
describe 'compareTo', ->
it 'lists required mods first', ->
minecraft = new ModVersion modName:'Minecraft', modVersion:'1.7.10'
modVersion.compareTo(minecraft).should.equal +1
minecraft.compareTo(modVersion).should.equal -1
it 'sorts by name second', ->
buildcraft = new ModVersion modName:'Buildcraft', modVersion:'3.0'
modVersion.compareTo(buildcraft).should.equal +1
buildcraft.compareTo(modVersion).should.equal -1
describe 'findItemByName', ->
it 'locates items by slugified name', ->
modVersion.addItem new Item name:'Crafting Table'
modVersion.findItemByName('Crafting Table').slug.should.equal 'crafting_table'
describe 'gatherRecipeNames', ->
it 'skips names already found', ->
modVersion.addItem new Item name:'Wool'
modVersion.addItem new Item name:'Oak Wood Planks', recipes:['foo']
names = modVersion.gatherRecipeNames {wool:true}
names.wool.should.be.true
names.oak_wood_planks.value.should.equal 'Oak Wood Planks'
it 'only includes craftable items', ->
modVersion.addItem new Item name:'Wool'
modVersion.addItem new Item name:'Oak Wood Planks', recipes:['foo']
names = modVersion.gatherRecipeNames()
_.keys(names).should.eql ['oak_wood_planks']
it 'computes the proper value and label', ->
modVersion.addItem new Item name:'Oak Wood Planks', recipes:['foo']
names = modVersion.gatherRecipeNames()
names.oak_wood_planks.value.should.equal 'Oak Wood Planks'
names.oak_wood_planks.label.should.equal 'Oak Wood Planks (from Test 0.0)'
describe 'hasRecipe', ->
it 'returns false for an unknown item', ->
modVersion.addItem new Item name:'Oak Wood Planks', recipes:['foo']
modVersion.hasRecipe('Pineapple Upside-Down Cake').should.be.false
it 'returns false for a un-craftable item', ->
modVersion.addItem new Item name:'Wool'
modVersion.hasRecipe('Wool').should.be.false
it 'returns true for a craftable item', ->
modVersion.addItem new Item name:'Oak Wood Planks', recipes:['foo']
modVersion.hasRecipe('Oak Wood Planks').should.be.true
+100 -51
View File
@@ -1,11 +1,13 @@
###
# Crafting Guide - v1.test.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
Crafting Guide - v1.test.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
RecipeBookParser = require '../src/scripts/models/mod_version_parser'
Item = require '../src/scripts/models/item'
ModVersion = require '../src/scripts/models/mod_version'
ModVersionParser = require '../src/scripts/models/mod_version_parser'
########################################################################################################################
@@ -13,48 +15,91 @@ parser = null
########################################################################################################################
describe 'RecipeBookParser', ->
describe 'ModVersionParser', ->
describe "V1", ->
before -> parser = new RecipeBookParser.V1
before -> parser = new ModVersionParser.V1
describe '_parseItemList', ->
beforeEach -> parser.modVersion = new ModVersion modName:'Test', modVersion:'0.0'
it 'can parse an empty list', ->
parser._parseItemList []
_.keys(parser.modVersion.items).should.eql []
it 'can add new items', ->
parser._parseItemList ['Crafting Table', 'Furnace']
_.keys(parser.modVersion.items).should.eql ['crafting_table', 'furnace']
it 'can find existing items', ->
parser.modVersion.addItem new Item name:'Furnace'
parser._parseItemList ['Crafting Table', 'Furnace']
_.keys(parser.modVersion.items).should.eql ['furnace', 'crafting_table']
describe '_parseModVersion', ->
it 'requires a mod_name', ->
data = version:1, mod_version:'1.0', recipes:[]
data = version:1, mod_version:'1.0', items:[]
expect(-> parser._parseModVersion data).to.throw Error, 'mod_name is required'
it 'requires a mod_version', ->
data = version:1, mod_name:'Empty', recipes:[]
data = version:1, mod_name:'Empty', items:[]
expect(-> parser._parseModVersion data).to.throw Error, 'mod_version is required'
it 'can parse an empty recipe book', ->
it 'can parse an empty modVersion', ->
data =
version: 1
mod_name: 'Empty'
mod_version: '1.0'
recipes: []
book = parser._parseModVersion data
book.modName.should.equal 'Empty'
book.modVersion.should.equal '1.0'
modVersion = parser._parseModVersion data
modVersion.modName.should.equal 'Empty'
modVersion.modVersion.should.equal '1.0'
it 'can parse a non-empty recipe book', ->
it 'can parse a non-empty mod version', ->
data =
version: 1
mod_name: 'Minecraft'
mod_version: '1.7.10'
recipes: [
{ input:'sugar cane', output:'sugar' }
{ input:[[3, 'wool'], [3, 'planks']], tools:'crafting table', output:'bed' }
{ input:'Sugar Cane', output:'Sugar' }
{ input:[[3, 'Wool'], [3, 'Planks']], tools:'Crafting Table', output:'Bed' }
]
book = parser._parseModVersion data
book.modName.should.equal 'Minecraft'
book.modVersion.should.equal '1.7.10'
(r.name for r in book.recipes).sort().should.eql ['bed', 'sugar']
modVersion = parser._parseModVersion data
modVersion.modName.should.equal 'Minecraft'
modVersion.modVersion.should.equal '1.7.10'
slugs = (slug for slug, item of modVersion.items).sort()
slugs.should.eql ['bed', 'crafting_table', 'planks', 'sugar', 'sugar_cane', 'wool']
describe '_parseRawMaterials', ->
beforeEach -> parser.modVersion = new ModVersion modName:'Test', modVersion:'0.0'
it 'skips the section when missing', ->
parser._parseRawMaterials null
_.keys(parser.modVersion._items).length.should.equal 0
it 'skips the section when empty', ->
parser._parseRawMaterials []
_.keys(parser.modVersion._items).length.should.equal 0
it 'adds items marked as gatherable', ->
parser._parseRawMaterials ['Wool']
parser.modVersion.items['wool'].isGatherable.should.be.true
it 'marks an existing item as gatherable', ->
parser.modVersion.addItem new Item name:'Wool'
parser.modVersion.items['wool'].isGatherable.should.be.false
parser._parseRawMaterials ['Wool']
parser.modVersion.items['wool'].isGatherable.should.be.true
describe '_parseRecipe', ->
beforeEach -> parser.modVersion = new ModVersion modName:'Test', modVersion:'0.0'
it 'requires output to be defined', ->
parser._errorLocation = 'boat'
expect(-> parser._parseRecipe input:'wool').to.throw Error, 'boat is missing output'
@@ -68,58 +113,62 @@ describe 'RecipeBookParser', ->
input: [[3, 'planks'], [3, 'wool']]
tools: 'crafting table'
recipe = parser._parseRecipe data
(i.name for i in recipe.output).should.eql ['bed']
(i.name for i in recipe.input).sort().should.eql ['planks', 'wool']
(i.name for i in recipe.tools).should.eql ['crafting table']
(stack.name for stack in recipe.output).should.eql ['bed']
(stack.name for stack in recipe.input).sort().should.eql ['planks', 'wool']
(item.name for item in recipe.tools).should.eql ['crafting table']
it 'can parse a recipe without tools', ->
recipe = parser._parseRecipe output:'sugar', input:'sugar cane'
(i.name for i in recipe.output).should.eql ['sugar']
(i.name for i in recipe.input).sort().should.eql ['sugar cane']
(i.name for i in recipe.tools).should.eql []
(stack.name for stack in recipe.output).should.eql ['sugar']
(stack.name for stack in recipe.input).sort().should.eql ['sugar cane']
(stack.name for stack in recipe.tools).should.eql []
describe '_parseItemList', ->
describe '_parseStack', ->
it 'can promote a single item to a list', ->
list = parser._parseItemList 'boat'
(i.name for i in list).should.eql ['boat']
it 'can require a list to be non-empty', ->
parser._errorLocation = 'boat'
options = field:'output', canBeEmpty:false
expect(-> parser._parseItemList [], options).to.throw Error, 'output for boat cannot be empty'
it 'can allow an empty list', ->
list = parser._parseItemList [], canBeEmpty:true
list.length.should.equal 0
it 'can parse a non-empty list', ->
list = parser._parseItemList [[3, 'plank'], [3, 'wool']]
(i.name for i in list).sort().should.eql ['plank', 'wool']
describe '_parseItem', ->
beforeEach -> parser.modVersion = new ModVersion modName:'Test', modVersion:'0.0'
it 'requires the array to have at least one element', ->
parser._errorLocation = 'boat'
options = index:1, field:'output'
expect(-> parser._parseItem([], options)).to.throw Error,
expect(-> parser._parseStack([], options)).to.throw Error,
"output element 1 for boat must have at least one element"
it 'can fill in a missing number', ->
item = parser._parseItem 'boat'
item = parser._parseStack 'boat'
item.name.should.equal 'boat'
item.quantity.should.equal 1
item2 = parser._parseItem ['boat']
item2 = parser._parseStack ['boat']
item2.name.should.equal 'boat'
item2.quantity.should.equal 1
it 'requires the data to start with a number', ->
parser._errorLocation = 'boat'
options = index:1, field:'output'
expect(-> parser._parseItem(['2', 'book'], options)).to.throw Error,
expect(-> parser._parseStack(['2', 'wool'], options)).to.throw Error,
"output element 1 for boat must start with a number"
it 'can parse a basic item', ->
item = parser._parseItem [2, 'book']
item.constructor.name.should.equal 'Item'
stack = parser._parseStack [2, 'wool']
stack.constructor.name.should.equal 'Stack'
describe '_parseStackList', ->
beforeEach -> parser.modVersion = new ModVersion modName:'Test', modVersion:'0.0'
it 'can promote a single item to a list', ->
list = parser._parseStackList 'boat'
(i.name for i in list).should.eql ['boat']
it 'can require a list to be non-empty', ->
parser._errorLocation = 'boat'
options = field:'output', canBeEmpty:false
expect(-> parser._parseStackList [], options).to.throw Error, 'output for boat cannot be empty'
it 'can allow an empty list', ->
list = parser._parseStackList [], canBeEmpty:true
list.length.should.equal 0
it 'can parse a non-empty list', ->
list = parser._parseStackList [[3, 'plank'], [3, 'wool']]
(i.name for i in list).sort().should.eql ['plank', 'wool']
+4
View File
@@ -20,6 +20,8 @@ global.should = chai.should()
Logger = require '../src/scripts/logger'
global.logger = new Logger level:Logger.TRACE
require '../src/scripts/underscore_mixins'
# Test Registry ########################################################################################################
mocha.setup 'bdd'
@@ -27,6 +29,8 @@ mocha.setup 'bdd'
require './crafting_plan.test'
require './inventory.test'
require './inventory_parser.test'
require './mod_pack.test'
require './mod_version.test'
require './mod_version_parser.test'
mocha.checkLeaks()