Complete refactoring to move Recipes into Items

* Changed "version" to "dataVersion" in data format
* Changed the "modName" and "modVersion" fields on the ModVersion
  class to remove the "mod" prefix and changed the data format to
  match
* Change the Stack class to use the itemSlug instead of the item
  itself
* Update the ModVersion and ModPack classes to include methods for
  recording a correspondence between slugs and names. Update code
  which displays items from stacks to use these methods to determine
  display names.
* Add a reference from Recipe back to the item it creates
This commit is contained in:
Andrew Miner
2015-01-03 20:08:11 -08:00
parent 392c410df8
commit 0d25ac723d
24 changed files with 260 additions and 285 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"mod_name": "Applied Energetics",
"mod_version": "rv-12b",
"dataVersion": 1,
"name": "Applied Energetics",
"version": "rv-12b",
"description": "Crafting recipes from Applied Energetics, by AlgorithmX2",
"recipes": [
{
+3 -3
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"mod_name": "BuildCraft",
"mod_version": "3.0",
"dataVersion": 1,
"name": "BuildCraft",
"version": "3.0",
"description": "Crafting recipes from BuildCraft, by SpaceToad",
"recipes": [
{
+3 -3
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"mod_name": "GraviSuite",
"mod_version": "unknown",
"dataVersion": 1,
"name": "GraviSuite",
"version": "1.0",
"description": "Crafting recipes from GraviSuite, by flotschi301",
"recipes": [
{
+3 -3
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"mod_name": "Industrial Craft",
"mod_version": "2.0",
"dataVersion": 1,
"name": "Industrial Craft",
"version": "2.0",
"description": "Crafting recipes from IndustrialCraft, by Alblaka",
"recipes": [
{
+3 -3
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"mod_name": "Minecraft",
"mod_version": "1.7.10",
"dataVersion": 1,
"name": "Minecraft",
"version": "1.7.10",
"description": "Crafting recipes from vanilla Minecraft",
"raw_materials": [
"Coal",
+3 -3
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"mod_name": "MoreBlocks",
"mod_version": "1.0",
"dataVersion": 1,
"name": "MoreBlocks",
"version": "1.0",
"description": "Crafting recipes from More Blocks Mod, by MCE626",
"recipes": [
{
+3 -3
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"mod_name": "Thermal Expansion",
"mod_version": "2.x",
"dataVersion": 1,
"name": "Thermal Expansion",
"version": "2.x",
"description": "Crafting Recipes from Thermal Expansion, by King Lemming and the CoFH team",
"recipes": [
{
@@ -100,12 +100,15 @@ module.exports = class CraftingTableController extends BaseController
@$resultList.empty()
if @model.plan?
@model.plan.need.each (item)=>
@$needList.append "<li><span class='quantity'>#{item.quantity}</span> #{item.name}</li>"
@model.plan.make.each (item)=>
@$makeList.append "<li><span class='quantity'>#{item.quantity}</span> #{item.name}</li>"
@model.plan.result.each (item)=>
@$resultList.append "<li><span class='quantity'>#{item.quantity}</span> #{item.name}</li>"
@model.plan.need.each (stack)=>
name = @model.modPack.findName stack.itemSlug
@$needList.append "<li><span class='quantity'>#{stack.quantity}</span> #{name}</li>"
@model.plan.make.each (stack)=>
name = @model.modPack.findName stack.itemSlug
@$makeList.append "<li><span class='quantity'>#{stack.quantity}</span> #{name}</li>"
@model.plan.result.each (stack)=>
name = @model.modPack.findName stack.itemSlug
@$resultList.append "<li><span class='quantity'>#{stack.quantity}</span> #{name}</li>"
super
@@ -33,14 +33,14 @@ module.exports = class ModVersionController extends BaseController
super
refresh: ->
if @model.modName in RequiredMods
if @model.name in RequiredMods
@$enabled.attr 'checked', 'checked'
@$enabled.attr 'disabled', 'disabled'
else
@$enabled.removeAttr 'disabled'
if @model.enabled then @$enabled.attr('checked', 'checked') else @$enabled.removeAttr('checked')
@$name.html "#{@model.modName} (#{@model.modVersion})"
@$name.html "#{@model.name} (#{@model.version})"
@$description.html "#{@model.description}"
# Backbone.View Overrides ######################################################################
+20 -22
View File
@@ -32,13 +32,12 @@ module.exports = class CraftingPlan
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 item, quantity
@_expected.add item.slug, quantity
@_pending = @_expected.clone()
while not @_pending.isEmpty
@@ -55,8 +54,8 @@ module.exports = class CraftingPlan
_processPending: ->
targetStack = @_pending.pop()
targetItem = targetStack.item
logger.verbose "processing item: #{targetItem}, craftable? #{targetItem.isCraftable}"
targetItem = @modPack.findItem targetStack.itemSlug
logger.verbose "processing item: #{targetStack}"
return unless targetItem?
return if (not targetItem.isCraftable) or targetItem.isGatherable
@@ -64,11 +63,12 @@ module.exports = class CraftingPlan
logger.verbose "recipe: #{recipe}"
if @includingTools
for toolItem in recipe.tools
totalExpected = @result.quantityOf(toolItem.slug) + @_expected.quantityOf(toolItem.slug)
for toolStack in recipe.tools
slug = toolStack.itemSlug
totalExpected = @result.quantityOf(slug) + @_expected.quantityOf(slug)
if totalExpected < 1
@_pending.add toolItem
@_expected.add toolItem
@_pending.add slug
@_expected.add slug
while @_totalQuantityOf(targetItem.slug) < @_expected.quantityOf(targetItem.slug)
@steps.push recipe
@@ -80,28 +80,26 @@ module.exports = class CraftingPlan
@_processOutputStack stack
_processInputStack: (stack)->
slug = stack.item.slug
quantityAvailable = @result.quantityOf slug
quantityAvailable = @result.quantityOf stack.itemSlug
quantityUsed = Math.min quantityAvailable, stack.quantity
quantityNeeded = stack.quantity - quantityUsed
logger.verbose "processing input:#{stack.name},
logger.trace "processing input:#{stack.itemSlug},
a:#{quantityAvailable}, u:#{quantityUsed}, n:#{quantityNeeded}"
@result.remove slug, quantityUsed
@_pending.add stack.item, quantityNeeded
@need.add stack.item, quantityNeeded
@result.remove stack.itemSlug, quantityUsed
@_pending.add stack.itemSlug, quantityNeeded
@need.add stack.itemSlug, quantityNeeded
_processOutputStack: (stack)->
slug = stack.item.slug
quantityMissing = @need.quantityOf slug
quantityMissing = @need.quantityOf stack.itemSlug
quantityUsed = Math.min quantityMissing, stack.quantity
quantityLeft = stack.quantity - quantityUsed
logger.verbose "processing output:#{stack.name},
logger.trace "processing output:#{stack.itemSlug},
m:#{quantityMissing}, u:#{quantityUsed}, l:#{quantityLeft}"
@make.add stack.item, stack.quantity
@need.remove slug, quantityUsed
@result.add stack.item, quantityLeft
@make.add stack.itemSlug, stack.quantity
@need.remove stack.itemSlug, quantityUsed
@result.add stack.itemSlug, quantityLeft
_totalQuantityOf: (slug)->
return @result.quantityOf(slug) - @need.quantityOf(slug)
_totalQuantityOf: (itemSlug)->
return @result.quantityOf(itemSlug) - @need.quantityOf(itemSlug)
+30 -30
View File
@@ -21,24 +21,24 @@ module.exports = class Inventory extends BaseModel
# Public Methods ###############################################################################
add: (item, quantity=1)->
add: (itemSlug, quantity=1)->
return if quantity is 0
stack = @_stacks[item.slug]
stack = @_stacks[itemSlug]
if not stack?
stack = new Stack item:item, quantity:quantity
@_stacks[item.slug] = stack
@_slugs.push item.slug
stack = new Stack itemSlug:itemSlug, quantity:quantity
@_stacks[itemSlug] = stack
@_slugs.push itemSlug
@_slugs.sort()
else
stack.quantity += quantity
@trigger Event.add, this, item, quantity
@trigger Event.add, this, itemSlug, quantity
@trigger Event.change, this
return this
addInventory: (inventory)->
inventory.each (stack)=> @add stack.item, stack.quantity
inventory.each (stack)=> @add stack.itemSlug, stack.quantity
return this
clear: ->
@@ -47,51 +47,51 @@ module.exports = class Inventory extends BaseModel
clone: ->
inventory = new Inventory
@each (stack)-> inventory.add stack.item, stack.quantity
@each (stack)-> inventory.add stack.itemSlug, stack.quantity
return inventory
each: (onItem)->
for slug in @_slugs
stack = @_stacks[slug]
onItem stack
each: (onStack)->
for itemSlug in @_slugs
stack = @_stacks[itemSlug]
onStack stack
hasAtLeast: (slug, quantity=1)->
hasAtLeast: (itemSlug, quantity=1)->
if quantity is 0 then return true
stack = @_stacks[slug]
stack = @_stacks[itemSlug]
return false unless stack?
return stack.quantity >= quantity
pop: ->
slug = @_slugs.pop()
return null unless slug?
itemSlug = @_slugs.pop()
return null unless itemSlug?
stack = @_stacks[slug]
delete @_stacks[slug]
stack = @_stacks[itemSlug]
delete @_stacks[itemSlug]
@trigger Event.remove, this, stack.item, stack.quantity
@trigger Event.remove, this, stack.itemSlug, stack.quantity
@trigger Event.change, this
return stack
quantityOf: (slug)->
stack = @_stacks[slug]
quantityOf: (itemSlug)->
stack = @_stacks[itemSlug]
return 0 unless stack?
return stack.quantity
remove: (slug, quantity=1)->
remove: (itemSlug, quantity=1)->
return if quantity is 0
stack = @_stacks[slug]
if not stack? then throw new Error "cannot remove #{slug} since it is not in this inventory"
stack = @_stacks[itemSlug]
if not stack? then throw new Error "cannot remove #{itemSlug} 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"
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{itemSlug} in this inventory"
stack.quantity -= quantity
if stack.quantity is 0
delete @_stacks[slug]
@_slugs = _(@_slugs).without slug
delete @_stacks[itemSlug]
@_slugs = _(@_slugs).without itemSlug
@trigger Event.remove, this, slug, quantity
@trigger Event.remove, this, itemSlug, quantity
@trigger Event.change, this
return this
@@ -99,9 +99,9 @@ module.exports = class Inventory extends BaseModel
result = []
@each (stack)->
if stack.quantity > 1
result.push [stack.quantity, stack.item.slug]
result.push [stack.quantity, stack.itemSlug]
else
result.push stack.item.slug
result.push stack.itemSlug
return result
# Object Overrides #############################################################################
+1 -1
View File
@@ -25,6 +25,6 @@ module.exports = class InventoryParser
quantity = if match? then parseInt(match[1]) else 1
if name.length > 0
inventory.add new Item(name:name), quantity
inventory.add _.slugify(name), quantity
return inventory
+2 -2
View File
@@ -27,8 +27,8 @@ module.exports = class Item extends BaseModel
# Public Methods ###############################################################################
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}"
slug = recipe.output[0].itemSlug
if slug isnt @slug then throw new Error "invalid recipe for #{@slug} because it makes a #{slug}"
@recipes.push recipe
+20
View File
@@ -29,6 +29,16 @@ module.exports = class ModPack extends BaseModel
if modVersion.hasRecipe name
modVersion.enabled = true
findItem: (itemSlug, options={})->
options.includeDisabled ?= false
for modVersion in @modVersions
continue unless modVersion.enabled or options.includeDisabled
item = modVersion.items[itemSlug]
return item if item?
return null
findItemByName: (name, options={})->
options.includeDisabled ?= false
@@ -39,6 +49,16 @@ module.exports = class ModPack extends BaseModel
return null
findName: (slug, options={})->
options.includeDisabled ?= false
for modVersion in @modVersions
continue unless modVersion.enabled or options.includeDisabled
name = modVersion.findName slug
return name if name
return slug
gatherRecipeNames: (options={})->
options.includeDisabled ?= false
+18 -11
View File
@@ -13,12 +13,13 @@ BaseModel = require './base_model'
module.exports = class ModVersion extends BaseModel
constructor: (attributes={}, options={})->
if _.isEmpty(attributes.modName) then throw new Error 'modName cannot be empty'
if _.isEmpty(attributes.modVersion) then throw new Error 'modVersion cannot be empty'
if _.isEmpty(attributes.name) then throw new Error 'name cannot be empty'
if _.isEmpty(attributes.version) then throw new Error 'version cannot be empty'
attributes.description ?= ''
attributes.items ?= {}
attributes.enabled ?= attributes.modName in RequiredMods
attributes.enabled ?= attributes.name in RequiredMods
attributes.names ?= {}
super attributes, options
# Public Methods ###############################################################################
@@ -29,29 +30,32 @@ module.exports = class ModVersion extends BaseModel
return this
compareTo: (that)->
if this.modName is that.modName then return 0
if this.name is that.name then return 0
thisRequired = this.modName in RequiredMods
thatRequired = that.modName in RequiredMods
thisRequired = this.name in RequiredMods
thatRequired = that.name in RequiredMods
if thisRequired and thatRequired
return if this.modName < that.modName then -1 else +1
return if this.name < that.name 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
return if this.name < that.name then -1 else +1
findItemByName: (name)->
slug = _.slugify name
return @items[slug]
findName: (slug)->
return @names[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})"
result[item.slug] = value:item.name, label:"#{item.name} (from #{@name} #{@version})"
return result
@@ -60,11 +64,14 @@ module.exports = class ModVersion extends BaseModel
return false unless item?
return item.recipes.length > 0
registerSlug: (slug, name)->
@names[slug] = name
# Object Overrides #############################################################################
toString: ->
return "ModVersion (#{@cid}) {
enabled:#{@enabled},
modName:#{@modName},
modVersion:#{@modVersion},
name:#{@name},
version:#{@version},
items:#{_.keys(@items).length} items}"
+40 -64
View File
@@ -23,18 +23,18 @@ module.exports = class ModVersionParser
parse: (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.dataVersion? then throw new Error 'dataVersion is required'
parser = @_parsers["#{data.version}"]
if not parser? then throw new Error "cannot parse version #{data.version} mod descriptions"
parser = @_parsers["#{data.dataVersion}"]
if not parser? then throw new Error "cannot parse version #{data.dataVersion} mod descriptions"
return parser.parse data
unparse: (modVersion, version=ModVersionParser.CURRENT_VERSION)->
unparse: (modVersion, dataVersion=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"
parser = @_parsers["#{dataVersion}"]
if not parser? then throw new Error "version #{dataVersion} is not supported"
return parser.unparse modVersion
@@ -49,7 +49,10 @@ module.exports.V1 = class V1
return @_parseModVersion data
unparse: (modVersion)->
return @_unparseModVersion modVersion
@modVersion = modVersion
text = @_unparseModVersion modVersion
@modVersion = null
return text
# Private Methods ##############################################################################
@@ -58,27 +61,16 @@ module.exports.V1 = class V1
if not item?
item = new Item name:name
@modVersion.addItem item
@modVersion.registerSlug item.slug, item.name
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]
result = []
for name in data
item = @_findOrCreateItem name
result.push item
return result
_parseModVersion: (data)->
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.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 = modVersion = new ModVersion name:data.name, version:data.version
modVersion.description = data.description or ''
@_parseRawMaterials data.raw_materials
@@ -86,12 +78,13 @@ module.exports.V1 = class V1
for index in [0...data.recipes.length]
@_errorLocation = "recipe #{index + 1}"
recipeData = data.recipes[index]
@_parseRecipe recipeData
recipe = @_parseRecipe recipeData
recipe._originalIndex = index
@modVersion = null
return modVersion
_parseRawMaterials: (data, options={})->
_parseRawMaterials: (data)->
return unless data? and data.length > 0
results = []
@@ -100,21 +93,25 @@ module.exports.V1 = class V1
item.isGatherable = true
results.push item
_parseRecipe: (data, options={})->
_parseRecipe: (data)->
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
data.output = if _.isArray(data.output) then data.output else [data.output]
names = (e for e in _.flatten(data.output) when _.isString(e))
if names.length is 0 then throw new Error "#{@_errorLocation} has an empty output list"
item = @_findOrCreateItem names[0]
@_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'
output = @_parseStackList data.output, field:'output', canBeEmpty:false
input = @_parseStackList data.input, field:'input', canBeEmpty:true
tools = @_parseStackList data.tools, field:'tools', canBeEmpty:true
recipe = new Recipe input:input, output:output, tools:tools
recipe = new Recipe item:item, input:input, output:output, tools:tools
item.addRecipe recipe
return recipe
@@ -129,8 +126,11 @@ 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"
item = @_findOrCreateItem data[1]
return new Stack item:item, quantity:data[0]
name = data[1]
slug = _.slugify name
@modVersion.registerSlug slug, name
return new Stack itemSlug:slug, quantity:data[0]
_parseStackList: (data, options={})->
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
@@ -146,25 +146,14 @@ module.exports.V1 = class V1
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'
result.push ' "mod_name": "' + modVersion.name + '",\n'
result.push ' "mod_version": "' + modVersion.version + '",\n'
if modVersion.description.length > 0
result.push ' "description": "' + modVersion.description + '",\n'
@@ -209,7 +198,7 @@ module.exports.V1 = class V1
if recipe.tools.length > 0
result.push ',\n'
result.push ' "tools": '
@_unparseItemList recipe.tools, result
@_unparseStackList recipe.tools, result
result.push '\n'
return result
@@ -222,9 +211,9 @@ module.exports.V1 = class V1
else if stackList.length is 1
stack = stackList[0]
if stack.quantity is 1
result.push '"' + stack.name + '"'
result.push '"' + @modVersion.findName(stack.itemSlug) + '"'
else
result.push '[[' + stack.quantity + ', "' + stack.name + '"]]'
result.push '[[' + stack.quantity + ', "' + @modVersion.findName(stack.itemSlug) + '"]]'
else
result.push '['
@@ -233,32 +222,19 @@ module.exports.V1 = class V1
stacks.sort (a, b)->
if a.quantity isnt b.quantity
return if a.quantity > b.quantity then -1 else +1
if a.item.name isnt b.item.name
return if a.item.name < b.item.name then -1 else +1
if a.itemSlug isnt b.itemSlug
return if a.itemSlug < b.itemSlug then -1 else +1
return 0
firstItem = true
for stack in stacks
result.push ', ' if not firstItem
if stack.quantity is 1
result.push '"' + stack.name + '"'
result.push '"' + @modVersion.findName(stack.itemSlug) + '"'
else
result.push '[' + stack.quantity + ', "' + stack.name + '"]'
result.push '[' + stack.quantity + ', "' + @modVersion.findName(stack.itemSlug) + '"]'
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
+8 -7
View File
@@ -12,6 +12,7 @@ BaseModel = require './base_model'
module.exports = class Recipe extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.item? then throw new Error 'attributes.item is required'
if not attributes.input? then throw new Error 'attributes.input is required'
if not attributes.output? then throw new Error 'attributes.output is required'
@@ -19,22 +20,22 @@ module.exports = class Recipe extends BaseModel
attributes.tools ?= []
super attributes, options
Object.defineProperty @prototype, 'name', get:-> @output[0].item.name
Object.defineProperty @prototype, 'name', get:-> @item.name
# Public Methods ###############################################################################
make: (inventory, missing)->
for stack in @input
item = stack.item
needed = stack.quantity
itemSlug = stack.itemSlug
needed = stack.quantity
while needed > 0
if inventory.hasAtLeast item.slug
inventory.remove item.slug
if inventory.hasAtLeast itemSlug
inventory.remove itemSlug
else
missing.add item.slug
missing.add itemSlug
for stack in @output
inventory.add stack.item, stack.quantity
inventory.add stack.itemSlug, stack.quantity
return this
+5 -20
View File
@@ -10,40 +10,25 @@ All rights reserved.
module.exports = class Stack
constructor: (attributes={})->
if not attributes.item? then throw new Error 'item is required'
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
attributes.quantity ?= 1
@item = attributes.item
@itemSlug = attributes.itemSlug
@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
return @itemSlug is stack.itemSlug
merge: (stack)->
if not @canMerge stack
throw new Error "this stack of #{@item.name} cannot merge a stack of #{@stack.name}"
throw new Error "this stack of #{@itemSlug} cannot merge a stack of #{@stack.itemSlug}"
@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}"
return "#{@quantity} #{@itemSlug}"