Convert codebase to using ItemSlug

* Flesh out the formerly incomplete ItemSlug class
* Convert all uses of text item slugs to use ItemSlug
* Move the ItemSlug class under models where it properly belongs
* Avoid the use of `slug` as a local variable or item attribute
  wherever possible to avoid confusion between various kinds of slugs
* Merge the InventoryParser class into the Inventory class as it is
  unlikely to need to support multiple versions
* Simplify the ModPack/Mod/ModVersion interfaces by replacing the use
  of various "helper" methods with equivalent use of more generic
  methods
* Update the various methods of ModPack/Mod/ModVersion to be able to
  work with qualified or unqualified ItemSlugs
* Remove the unused BaseCollection class
* Add tests for ItemSlug and update all other tests to reflect
  all the other changes
This commit is contained in:
Andrew Miner
2015-02-18 10:30:53 -08:00
parent 100ce1bc70
commit e88821d449
33 changed files with 624 additions and 535 deletions
+2 -2
View File
@@ -55,8 +55,8 @@ Text.title = 'Crafting Guide for Minecraft | The Ultimate Step-by-Step Tutorial
exports.Url = Url = {}
Url.crafting = _.template "/crafting/<%= inventoryText %>"
Url.itemIcon = _.template "/data/<%= modSlug %>/<%= modVersion %>/images/<%= slug %>.png"
Url.item = _.template "/mod/<%= modSlug %>/<%= slug %>"
Url.itemIcon = _.template "/data/<%= modSlug %>/<%= modVersion %>/images/<%= itemSlug %>.png"
Url.item = _.template "/mod/<%= modSlug %>/<%= itemSlug %>"
Url.mod = _.template "/mod/<%= modSlug %>"
Url.modData = _.template "/data/<%= modSlug %>/mod.cg"
Url.modVersion = _.template "/data/<%= modSlug %>/<%= modVersion %>/mod-version.cg"
@@ -64,8 +64,8 @@ module.exports = class CraftingGridController extends BaseController
if slot >= @_slotCount then throw new Error "slot (#{slot}) must be less than #{@_slotCount}"
return null unless @model?
slug = @model.getItemSlugAt slot
return null unless slug?
itemSlug = @model.getItemSlugAt slot
return null unless itemSlug?
itemDisplay = @_modPack.findItemDisplay slug
itemDisplay = @_modPack.findItemDisplay itemSlug
return itemDisplay
@@ -10,7 +10,6 @@ CraftingTableController = require './crafting_table_controller'
{Event} = require '../constants'
ImageLoader = require './image_loader'
InventoryController = require './inventory_controller'
InventoryParser = require '../models/inventory_parser'
CraftingPage = require '../models/crafting_page'
ModPackController = require './mod_pack_controller'
NameFinder = require '../models/name_finder'
@@ -32,7 +31,6 @@ module.exports = class CraftingPageController extends BaseController
super options
@_imageLoader = options.imageLoader
@_parser = new InventoryParser options.modPack
@_storage = options.storage
# Event Methods ################################################################################
@@ -8,7 +8,6 @@ All rights reserved.
BaseController = require './base_controller'
{Duration} = require '../constants'
ImageLoader = require './image_loader'
InventoryParser = require '../models/inventory_parser'
MinimalRecipeController = require './minimal_recipe_controller'
########################################################################################################################
@@ -34,7 +33,6 @@ module.exports = class CraftingTableController extends BaseController
@model.stepIndex -= 1
onReportProblem: ->
parser = new InventoryParser @_modPack
itemList = parser.unparse @model.plan.want
toolsMessage = if @model.plan.includingTools then '(including tools)' else ''
message = "When I was on step #{@model.stepIndex + 1} of making:
@@ -68,7 +68,7 @@ module.exports = class FullRecipeController extends BaseController
result = []
if @model?
for stack in @model.tools
item = @modPack.findItem stack.slug
item = @modPack.findItem stack.itemSlug
result.push item.name if item?
return result
@@ -78,7 +78,7 @@ module.exports = class FullRecipeController extends BaseController
if @model?
for stack in @model.input
inputs.add stack.slug, stack.quantity
inputs.add stack.itemSlug, stack.quantity
_refreshOutputs: ->
@@ -87,4 +87,4 @@ module.exports = class FullRecipeController extends BaseController
if @model?
for stack in @model.output
outputs.add stack.slug, stack.quantity
outputs.add stack.itemSlug, stack.quantity
@@ -196,4 +196,4 @@ module.exports = class InventoryController extends BaseController
controller.$el.fadeOut duration:Duration.fast, complete:-> @remove()
_removeStack: (stack)->
@model.remove stack.slug, stack.quantity
@model.remove stack.itemSlug, stack.quantity
@@ -7,8 +7,9 @@ All rights reserved.
BaseController = require './base_controller'
{Duration} = require '../constants'
{Key} = require '../constants'
ImageLoader = require './image_loader'
ItemSlug = require '../models/item_slug'
{Key} = require '../constants'
NameFinder = require '../models/name_finder'
StackController = require './stack_controller'
@@ -38,10 +39,10 @@ module.exports = class InventoryTableController extends BaseController
# Event Methods ################################################################################
onAddButtonClicked: ->
name = @$nameField.val()
return unless @modPack.isValidName name
name = @modpack.findItemByName @$nameField.val()
return unless item?
@model.add _.slugify(name), parseInt(@$quantityField.val())
@model.add item.slug, parseInt(@$quantityField.val())
@$nameField.val ''
@$quantityField.val '1'
@@ -160,7 +161,7 @@ module.exports = class InventoryTableController extends BaseController
# Private Methods ##############################################################################
_removeStack: (stack)->
@model.remove stack.slug, stack.quantity
@model.remove stack.itemSlug, stack.quantity
_updateNameAutocomplete: ->
onChanged = => @onNameFieldChanged()
@@ -48,7 +48,7 @@ module.exports = class MinimalRecipeController extends BaseController
if @model?
outputStack = @model.output[0]
if outputStack?
display = @_modPack.findItemDisplay outputStack.slug
display = @_modPack.findItemDisplay outputStack.itemSlug
@$outputLink.attr 'href', display.itemUrl
@$outputLink.attr 'title', display.itemName
@$outputImg.attr 'alt', display.itemName
@@ -72,6 +72,6 @@ module.exports = class MinimalRecipeController extends BaseController
result = []
if @model?
for stack in @model.tools
item = @_modPack.findItem stack.slug
item = @_modPack.findItem stack.itemSlug
result.push item.name if item?
return result
@@ -49,7 +49,6 @@ module.exports = class StackController extends BaseController
@$nameLink.html display.itemName
@$nameLink.attr 'href', display.itemUrl
@$quantityField.html @model.quantity
# @$removeButton.css opacity:(if @editable then 1 else 0)
@$action.css display:(if @editable then 'table-cell' else 'none')
+7 -6
View File
@@ -12,6 +12,7 @@ CraftingPageController = require './controllers/crafting_page_controller'
{Event} = require './constants'
HeaderController = require './controllers/header_controller'
ItemPageController = require './controllers/item_page_controller'
ItemSlug = require './models/item_slug'
Mod = require './models/mod'
ModPack = require './models/mod_pack'
ModPageController = require './controllers/mod_page_controller'
@@ -42,8 +43,8 @@ module.exports = class CraftingGuideRouter extends Backbone.Router
makeResponder = (m)-> return ->
m.activeModVersion.fetch() if m.activeModVersion?
for slug in DefaultMods
mod = new Mod slug:slug
for modSlug in DefaultMods
mod = new Mod slug:modSlug
mod.on Event.change + ':activeModVersion', makeResponder mod
@storage.register "mod:#{mod.slug}", mod, 'activeVersion'
mod.fetch()
@@ -75,13 +76,13 @@ module.exports = class CraftingGuideRouter extends Backbone.Router
@_setPage 'item', controller
modItem: (modSlug, itemSlug)->
slug = _.composeSlugs modSlug, itemSlug
slug = new ItemSlug modSlug, itemSlug
controller = new ItemPageController _.extend {itemSlug:slug}, @_defaultOptions
@_setPage 'item', controller
mod: (slug)->
mod: (modSlug)->
controller = new ModPageController _.extend {}, @_defaultOptions
controller.model = @modPack.getMod slug
controller.model = @modPack.getMod modSlug
@_setPage 'mod', controller
root: ->
@@ -89,7 +90,7 @@ module.exports = class CraftingGuideRouter extends Backbone.Router
text = ''
if params.recipeName?
if params.count?
if params.count? and params.count > 1
text = "#{params.count}.#{_.slugify(params.recipeName)}"
else
text = _.slugify params.recipeName
-10
View File
@@ -1,10 +0,0 @@
###
Crafting Guide - base_collection.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
########################################################################################################################
module.exports = class BaseCollection extends Backbone.Collection
+6 -7
View File
@@ -9,7 +9,7 @@ BaseModel = require './base_model'
CraftingPlan = require './crafting_plan'
CraftingTable = require './crafting_table'
{Event} = require '../constants'
InventoryParser = require './inventory_parser'
Inventory = require './inventory'
ModPack = require './mod_pack'
########################################################################################################################
@@ -23,8 +23,6 @@ module.exports = class CraftingPage extends BaseModel
attributes.table ?= new CraftingTable plan:attributes.plan
super attributes, options
@_parser = new InventoryParser
@modPack.on Event.change, => @_consumeParams()
@on Event.change + ':params', => @_consumeParams()
@@ -37,12 +35,13 @@ module.exports = class CraftingPage extends BaseModel
if not @params.inventoryText?
@params = null
else
inventory = @_parser.parse @params.inventoryText
inventory = new Inventory
inventory.parse @params.inventoryText
inventory.each (stack)=>
item = @modPack.findItem stack.slug, enableAsNeeded:true
item = @modPack.findItem stack.itemSlug, enableAsNeeded:true
return unless item? and item.isCraftable
@plan.want.add stack.slug, stack.quantity
inventory.remove stack.slug
@plan.want.add stack.itemSlug, stack.quantity
inventory.remove stack.itemSlug
if inventory.isEmpty then @params = null
+43 -43
View File
@@ -18,10 +18,10 @@ module.exports = class CraftingPlan extends BaseModel
attributes.includingTools ?= false
super attributes, options
@have = new Inventory
@want = new Inventory
@need = new Inventory
@result = new Inventory
@have = new Inventory modPack:@modPack
@want = new Inventory modPack:@modPack
@need = new Inventory modPack:@modPack
@result = new Inventory modPack:@modPack
recraft = _.debounce (=> @craft()), 100
for inventory in [@have, @want]
@@ -46,17 +46,17 @@ module.exports = class CraftingPlan extends BaseModel
logger.info => "crafting #{@want}#{toolsMessage} starting with #{@have}"
@clear()
@have.localizeTo @modPack
@want.localizeTo @modPack
@have.localize()
@want.localize()
@result.addInventory @have
@steps = {}
@_reservedSteps = {}
@want.each (stack)=>
@_findSteps stack.slug
item = @modPack.findItem stack.slug
@need.add item.qualifiedSlug, stack.quantity
@_findSteps stack.itemSlug
item = @modPack.findItem stack.itemSlug
@need.add item.slug, stack.quantity
@_reservedSteps = null
@steps = _.values @steps
@@ -72,11 +72,11 @@ module.exports = class CraftingPlan extends BaseModel
removeUncraftableItems: ->
toRemove = []
@want.each (stack)=>
item = @modPack.findItem stack.slug
if not item? then toRemove.push stack.slug
item = @modPack.findItem stack.itemSlug
if not item? then toRemove.push stack.itemSlug
for slug in toRemove
@want.remove slug
for itemSlug in toRemove
@want.remove itemSlug
# Event Methods ################################################################################
@@ -98,16 +98,16 @@ module.exports = class CraftingPlan extends BaseModel
# Private Methods ##############################################################################
_addStep: (recipe)->
logger.verbose -> "adding step: #{recipe.slug}"
@steps[recipe.slug] = recipe:recipe
logger.verbose -> "adding step: #{recipe.itemSlug}"
@steps[recipe.itemSlug] = recipe:recipe
_chooseRecipe: (item)->
recipes = @modPack.findRecipes item.qualifiedSlug
recipes = @modPack.findRecipes item.slug
return null unless recipes?
return recipes[0]
_findSteps: (slug)->
item = @modPack.findItem slug
_findSteps: (itemSlug)->
item = @modPack.findItem itemSlug
return unless item?
return unless item.isCraftable
return if item.isGatherable
@@ -116,26 +116,26 @@ module.exports = class CraftingPlan extends BaseModel
if @includingTools
for toolStack in recipe.tools
if not @_hasStep toolStack.slug
@_findSteps toolStack.slug
if not @_hasStep toolStack.itemSlug
@_findSteps toolStack.itemSlug
return if @_hasStep item.qualifiedSlug
@_reservedSteps[item.qualifiedSlug] = recipe
return if @_hasStep item.slug
@_reservedSteps[item.slug] = recipe
for inputStack in recipe.input
@_findSteps inputStack.slug
@_findSteps inputStack.itemSlug
@_addStep recipe
_hasStep: (slug)->
return true if @steps[slug]?
return true if @_reservedSteps[slug]?
_hasStep: (itemSlug)->
return true if @steps[itemSlug]?
return true if @_reservedSteps[itemSlug]?
return false
_qualifyItemSlug: (slug)->
item = @modPack.findItem slug
return item.qualifiedSlug if item?
return slug
_qualifyItemSlug: (itemSlug)->
item = @modPack.findItem itemSlug
return item.slug if item?
return itemSlug
_removeExtraSteps: ->
result = (step for step in @steps when step.multiplier > 0)
@@ -146,31 +146,31 @@ module.exports = class CraftingPlan extends BaseModel
step = @steps[i]
recipe = step.recipe
step.multiplier = Math.ceil(@need.quantityOf(recipe.slug) / recipe.output[0].quantity)
step.multiplier = Math.ceil(@need.quantityOf(recipe.itemSlug) / recipe.output[0].quantity)
if @includingTools
for stack in recipe.tools
slug = @_qualifyItemSlug stack.slug
available = @result.quantityOf(slug) + @need.quantityOf(slug)
itemSlug = @_qualifyItemSlug stack.itemSlug
available = @result.quantityOf(itemSlug) + @need.quantityOf(itemSlug)
needed = Math.max 0, stack.quantity - available
@need.add slug, needed
@result.add slug, needed
@need.add itemSlug, needed
@result.add itemSlug, needed
for stack in recipe.input
slug = @_qualifyItemSlug stack.slug
itemSlug = @_qualifyItemSlug stack.itemSlug
needed = step.multiplier * stack.quantity
consumed = Math.min needed, @result.quantityOf slug
consumed = Math.min needed, @result.quantityOf itemSlug
remaining = needed - consumed
@result.remove slug, consumed
@need.add slug, remaining
@result.remove itemSlug, consumed
@need.add itemSlug, remaining
for stack in recipe.output
slug = @_qualifyItemSlug stack.slug
itemSlug = @_qualifyItemSlug stack.itemSlug
created = stack.quantity * step.multiplier
consumed = Math.min created, @need.quantityOf slug
consumed = Math.min created, @need.quantityOf itemSlug
remaining = created - consumed
@result.add slug, remaining
@need.remove slug, consumed
@result.add itemSlug, remaining
@need.remove itemSlug, consumed
+87 -60
View File
@@ -7,6 +7,7 @@ All rights reserved.
BaseModel = require './base_model'
{Event} = require '../constants'
ItemSlug = require './item_slug'
{RequiredMods} = require '../constants'
Stack = require './stack'
@@ -16,28 +17,35 @@ module.exports = class Inventory extends BaseModel
constructor: (attributes={}, options={})->
super attributes, options
attributes.modPack ?= null
@clear()
Object.defineProperties this,
isEmpty: { get:-> @_slugs.length is 0 }
isEmpty: { get:-> @_itemSlugs.length is 0 }
# Class Methods ################################################################################
@Delimiters =
Item: '.'
Stack: ':'
# Public Methods ###############################################################################
add: (slug, quantity=1)->
@_add slug, quantity
@trigger Event.add, this, slug, quantity
add: (itemSlug, quantity=1)->
@_add itemSlug, quantity
@trigger Event.add, this, itemSlug, quantity
@trigger Event.change, this
return this
addInventory: (inventory)->
inventory.each (stack)=> @_add stack.slug, stack.quantity
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
@trigger Event.change, this
return this
clear: (options={})->
@_stacks = {}
@_slugs = []
@_itemSlugs = []
@trigger Event.change, this
@@ -47,78 +55,110 @@ module.exports = class Inventory extends BaseModel
return inventory
each: (callback)->
for slug in @_slugs
callback @_stacks[slug]
for itemSlug in @_itemSlugs
callback @_stacks[itemSlug]
getSlugs: ->
return @_slugs[..]
return @_itemSlugs[..]
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
localizeTo: (modPack)->
localize: ->
if not @modPack? then throw new Error 'localize requires @modPack'
newSlugs = []
for slug in @_slugs
stack = @_stacks[slug]
qualifiedSlug = modPack.findItem(slug)?.qualifiedSlug
for itemSlug in @_itemSlugs
stack = @_stacks[itemSlug]
qualifiedSlug = @modPack.findItem(itemSlug)?.slug
if qualifiedSlug?
delete @_stacks[slug]
delete @_stacks[itemSlug]
newSlugs.push qualifiedSlug
@_stacks[qualifiedSlug] = stack
stack.slug = qualifiedSlug
stack.itemSlug = qualifiedSlug
else
throw new Error "could not find an item for: #{slug}"
newSlugs.push itemSlug
@_slugs = newSlugs
@_itemSlugs = newSlugs
@_sort()
pop: ->
slug = @_slugs.pop()
return null unless slug?
itemSlug = @_itemSlugs.pop()
return null unless itemSlug?
stack = @_stacks[slug]
delete @_stacks[slug]
stack = @_stacks[itemSlug]
delete @_stacks[itemSlug]
@trigger Event.remove, this, stack.slug, 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=null)->
remove: (itemSlug, quantity=null)->
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"
quantity ?= stack.quantity
if stack.quantity < quantity
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{slug} 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]
@_itemSlugs = _(@_itemSlugs).without itemSlug
@trigger Event.remove, this, slug, quantity
@trigger Event.remove, this, itemSlug, quantity
@trigger Event.change, this
return this
toList: ->
result = []
@each (stack)->
if stack.quantity > 1
result.push [stack.quantity, stack.slug]
# Parsing Methods ##############################################################################
parse: (data)->
return this if not data? or data.length is 0
stacks = data.split Inventory.Delimiters.Stack
for stackText in stacks
stackParts = stackText.split Inventory.Delimiters.Item
if stackParts.length is 2
quantity = parseInt stackParts[0], 10
itemSlug = ItemSlug.slugify stackParts[1]
else if stackParts.length is 1
quantity = 1
itemSlug = ItemSlug.slugify stackParts[0]
else
result.push stack.slug
return result
throw new Error "expected #{stackText} to have 0 or 1 parts"
if itemSlug.qualified.length > 0
@add itemSlug, quantity
return this
unparse: ->
parts = []
@each (stack)=>
slugText = stack.itemSlug.qualified
if @modPack?
item = @modPack.findItem stack.itemSlug.item
if item?
slugText = if item.slug isnt stack.itemSlug then item.slug.qualified
if stack.quantity is 1
parts.push slugText
else
parts.push "#{stack.quantity}#{Inventory.Delimiters.Item}#{slugText}"
return parts.join Inventory.Delimiters.Stack
# Object Overrides #############################################################################
@@ -137,31 +177,18 @@ module.exports = class Inventory extends BaseModel
# Private Methods ##############################################################################
_add: (slug, quantity=1)->
return unless slug?
_add: (itemSlug, quantity=1)->
return unless itemSlug?
return if quantity is 0
stack = @_stacks[slug]
stack = @_stacks[itemSlug]
if not stack?
stack = new Stack slug:slug, quantity:quantity
@_stacks[slug] = stack
@_slugs.push slug
stack = new Stack itemSlug:itemSlug, quantity:quantity
@_stacks[itemSlug] = stack
@_itemSlugs.push itemSlug
@_sort()
else
stack.quantity += quantity
_sort: ->
@_slugs.sort (a, b)->
[modSlugA, itemSlugA] = _.decomposeSlug a
[modSlugB, itemSlugB] = _.decomposeSlug b
isRequiredA = modSlugA in RequiredMods
isRequiredB = modSlugB in RequiredMods
if isRequiredA isnt isRequiredB
return -1 if isRequiredA
return +1 if isRequiredB
else if modSlugA isnt modSlugB
return if modSlugA < modSlugB then -1 else +1
else if itemSlugA isnt itemSlugB
return if itemSlugA < itemSlugB then -1 else +1
return 0
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
@@ -1,62 +0,0 @@
###
Crafting Guide - inventory_parser.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
Inventory = require './inventory'
Item = require './item'
StringBuilder = require './string_builder'
########################################################################################################################
module.exports = class InventoryParser
constructor: (modPack=null)->
@modPack = modPack
# Class Methods ################################################################################
@STACK_DELIMITER = ':'
@ITEM_DELIMITER = '.'
# Public Methods ###############################################################################
parse: (data, inventory=null)->
inventory ?= new Inventory
return inventory if not data? or data.length is 0
stacks = data.split InventoryParser.STACK_DELIMITER
for stackText in stacks
stackParts = stackText.split InventoryParser.ITEM_DELIMITER
if stackParts.length is 2
quantity = parseInt stackParts[0]
slug = stackParts[1]
else if stackParts.length is 1
quantity = 1
slug = stackParts[0]
else
throw new Error "expected #{stackText} to have 0 or 1 parts"
if slug.length > 0
inventory.add slug, quantity
return inventory
unparse: (inventory)->
if not @modPack? then throw new Error 'this.modPack is needed to unparse'
parts = []
inventory.each (stack)=>
[modSlug, itemSlug] = _.decomposeSlug stack.slug
item = @modPack.findItem itemSlug
slug = if item.qualifiedSlug is stack.slug then item.slug else item.qualifiedSlug
if stack.quantity is 1
parts.push slug
else
parts.push "#{stack.quantity}#{InventoryParser.ITEM_DELIMITER}#{slug}"
return parts.join InventoryParser.STACK_DELIMITER
+7 -17
View File
@@ -5,9 +5,9 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseCollection = require './base_collection'
BaseModel = require './base_model'
{Event} = require '../constants'
ItemSlug = require './item_slug'
Recipe = require './recipe'
StringBuilder = require './string_builder'
@@ -23,16 +23,12 @@ module.exports = class Item extends BaseModel
attributes.group ?= Item.Group.Other
attributes.isGatherable ?= false
attributes.modVersion ?= null
attributes.slug ?= _.slugify attributes.name
attributes.slug ?= ItemSlug.slugify attributes.name
options.logEvents ?= false
super attributes, options
Object.defineProperties this,
isCraftable: {get:@getIsCraftable}
qualifiedSlug: {get:@getQualifiedSlug}
@on Event.change + ':modVersion', => @_qualifiedSlug = null
@on Event.change + ':modVersion', => @slug.mod = @modVersion?.modSlug
# Public Methods ###############################################################################
@@ -47,15 +43,10 @@ module.exports = class Item extends BaseModel
getIsCraftable: ->
return false unless @modVersion?
return @modVersion.hasRecipes @qualifiedSlug
return @modVersion.hasRecipes @slug
getQualifiedSlug: ->
return @slug if not @modVersion?
if not @_qualifiedSlug?
@_qualifiedSlug = _.composeSlugs @modVersion.modSlug, @slug
return @_qualifiedSlug
Object.defineProperties @prototype,
isCraftable: {get:@prototype.getIsCraftable}
# Object Overrides #############################################################################
@@ -68,7 +59,6 @@ module.exports = class Item extends BaseModel
.push 'isGatherable:', @isGatherable, ', '
.onlyIf (@group isnt Item.Group.Other), (b)=>
b.push 'group:"', @group, '", '
.onlyIf (_.slugify(@name) isnt @slug), (b)=>
b.push 'slug:"', @slug, '", '
.push 'slug:"', @slug, '", '
.push '}'
.toString()
+3 -6
View File
@@ -31,14 +31,11 @@ module.exports = class ItemPage extends BaseModel
findComponentInItems: ->
return null unless @item?
slugs = [@item.slug, @item.qualifiedSlug]
result = {}
@modPack.eachMod (mod)->
mod.eachItem (item)->
item.eachRecipe (recipe)->
for stack in recipe.input
if stack.slug in slugs
result[item.qualifiedSlug] = item
mod.eachRecipe (recipe)->
if recipe.produces @item.slug
result[item.slug] = item
result = _.values(result).sort (a, b)-> a.compareTo b
return null unless result.length > 0
@@ -5,12 +5,15 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
{RequiredMods} = require '../constants'
########################################################################################################################
module.exports = class ItemSlug
constructor: ->
@_item = @_mod = null
if arguments.length is 1
@item = arguments[0]
else if arguments.length is 2
@@ -21,10 +24,14 @@ module.exports = class ItemSlug
# Class Methods ################################################################################
DELIMITER = '__'
@compare: (a, b)->
if a.isQualified isnt b.isQualified
aIsRequired = a.mod in RequiredMods
bIsRequired = b.mod in RequiredMods
if aIsRequired isnt bIsRequired
return -1 if aIsRequired
return +1 if bIsRequired
else if a.isQualified isnt b.isQualified
return -1 if a.isQualified
return +1 if b.isQualified
else if a.mod isnt b.mod
@@ -34,18 +41,36 @@ module.exports = class ItemSlug
return 0
@equals: (a, b)->
@equal: (a, b)->
return false unless a.mod is b.mod
return false unless a.item is b.item
return true
@slugify: (arg)->
return arg if arg?.constructor?.name is 'ItemSlug'
[modSlug, itemSlug] = _.decomposeSlug arg
itemSlug = _.slugify itemSlug
if modSlug?
return new ItemSlug modSlug, itemSlug
else
return new ItemSlug itemSlug
# Public Methods ###############################################################################
matches: (slug, options={exact:false})->
return false unless slug?.constructor?.name is 'ItemSlug'
if slug.isQualified and this.isQualified
return slug.qualified is this.qualified
else
return slug.item is this.item
# Property Methods #############################################################################
Object.defineProperties @prototype,
isQualified: { get:@prototype.isQualified }
mod: { get:@prototype.getMod, set:@prototype.setMod }
item: { get:@prototype.getItem, set:@prototype.setItem }
qualified: { get:@prototype.getQualified set:@prototype.setQualified }
getIsQualified: ->
return @_mod?
getItem: ->
return @_item
@@ -53,17 +78,24 @@ module.exports = class ItemSlug
setItem: (item)->
if not item? then throw new Error 'item is required'
@_item = item
@mod = mod
@mod = @mod # reset @_qualified
getMod: ->
return @_mod
setMod: (mod)->
@_qualified = if mod? then "#{@mod}#{ItemSlug.DELIMITER}#{@item}" else @item
@_mod = mod
@_qualified = if @_mod? then _.composeSlugs(@_mod, @_item) else @_item
isQualified: ->
return @_mod?
getQualified: ->
return @_qualified
Object.defineProperties @prototype,
isQualified: { get:@prototype.getIsQualified }
mod: { get:@prototype.getMod, set:@prototype.setMod }
item: { get:@prototype.getItem, set:@prototype.setItem }
qualified: { get:@prototype.getQualified }
# Object Overrides #############################################################################
+5 -5
View File
@@ -87,13 +87,13 @@ module.exports = class Mod extends BaseModel
return unless @_activeModVersion?
@_activeModVersion.findItemByName name
findName: (slug)->
findName: (itemSlug)->
return unless @_activeModVersion?
@_activeModVersion.findName slug
@_activeModVersion.findName itemSlug
findRecipes: (slug, result=[])->
findRecipes: (itemSlug, result=[])->
return result unless @_activeModVersion?
@_activeModVersion.findRecipes slug, result
@_activeModVersion.findRecipes itemSlug, result
# Property Methods #############################################################################
@@ -123,6 +123,7 @@ module.exports = class Mod extends BaseModel
for modVersion in @_modVersions
return modVersion if modVersion.version is version
return null
getActiveVersion: ->
@@ -150,7 +151,6 @@ module.exports = class Mod extends BaseModel
@trigger Event.change + ':activeVersion', this, @_activeVersion
@trigger Event.change, this
# Backbone.View Overrides ######################################################################
parse: (text)->
+20 -34
View File
@@ -22,12 +22,11 @@ module.exports = class ModPack extends BaseModel
# Public Methods ###############################################################################
findItem: (slug, options={})->
findItem: (itemSlug, options={})->
options.includeDisabled ?= false
[modSlug, itemSlug] = _.decomposeSlug slug
if modSlug?
mod = @getMod modSlug
if itemSlug.isQualified
mod = @getMod itemSlug.mod
if mod?
item = mod.findItem itemSlug, options
return item if item?
@@ -50,63 +49,50 @@ module.exports = class ModPack extends BaseModel
return null
findItemDisplay: (slug)->
if not slug? then return null
findItemDisplay: (itemSlug)->
if not itemSlug? then return null
result = {}
item = @findItem slug, includeDisabled:true
item = @findItem itemSlug, includeDisabled:true
if item?
result.modSlug = item.modVersion.modSlug
result.modSlug = item.slug.mod
result.modVersion = item.modVersion.version
result.slug = item.slug
result.itemSlug = item.slug.item
result.itemName = item.name
else
result.modSlug = @_mods[0].slug
result.modVersion = @_mods[0].activeVersion
result.slug = slug
result.itemName = @findName slug, includeDisabled:true
result.itemSlug = itemSlug.item
result.itemName = @findName itemSlug, includeDisabled:true
result.craftingUrl = Url.crafting inventoryText:slug
result.craftingUrl = Url.crafting inventoryText:itemSlug.item
result.iconUrl = Url.itemIcon result
result.itemUrl = Url.item result
return result
findName: (slug)->
findName: (slug, options={})->
options.includeDisabled ?= false
for mod in @_mods
continue unless mod.enabled
continue unless mod.enabled or options.includeDisabled
name = mod.findName slug
return name if name
return null
findRecipes: (slug, result=[])->
[modSlug, itemSlug] = _.decomposeSlug slug
if modSlug?
mod = @getMod modSlug
findRecipes: (itemSlug, result=[])->
if itemSlug.isQualified
mod = @getMod itemSlug.mod
if mod?
mod.findRecipes slug, result
mod.findRecipes itemSlug, result
return result if result.length > 0
for mod in @_mods
continue unless mod.enabled
mod.findRecipes slug, result
mod.findRecipes itemSlug, result
return if result.length > 0 then result else null
isGatherable: (slug)->
item = @findItem slug
return true if not item?
return true if item.isGatherable
return false if item.isCraftable
return true
isValidName: (name)->
slug = _.slugify name
existingName = @findName slug
return name is existingName
# Property Methods #############################################################################
addMod: (mod)->
+39 -38
View File
@@ -5,10 +5,10 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseCollection = require './base_collection'
BaseModel = require './base_model'
{Event} = require '../constants'
Item = require './item'
ItemSlug = require './item_slug'
{RequiredMods} = require '../constants'
{Url} = require '../constants'
@@ -39,16 +39,19 @@ module.exports = class ModVersion extends BaseModel
return 0
sort: ->
@_slugs.sort (a, b)-> ItemSlug.compare a, b
# Item Methods #################################################################################
addItem: (item)->
if @_items[item.slug]? then throw new Error "duplicate item for #{item.name}"
@_items[item.slug] = item
@_groups[item.group] ?= {}
@_groups[item.group][item.slug] = item
if @findItem(item.slug)? then throw new Error "duplicate item for #{item.name}"
item.modVersion = this
@_items[item.slug.item] = item
@_groups[item.group] ?= {}
@_groups[item.group][item.slug.item] = item
@registerName item.slug, item.name
return this
@@ -60,20 +63,21 @@ module.exports = class ModVersion extends BaseModel
eachItem: (callback)->
for slug in @_slugs
item = @_items[slug]
item = @findItem slug
continue unless item?
callback @_items[slug], slug
callback item
return this
eachItemInGroup: (group, callback)->
group = @_groups[group]
return unless group?
itemMap = @_groups[group]
return unless itemMap?
for slug in _.keys(group).sort()
callback group[slug]
items = _.values(itemMap).sort (a, b)-> a.compareTo b
for item in items
callback item
findItem: (itemSlug)->
return @_items[itemSlug]
return @_items[itemSlug.item]
findItemByName: (name)->
for itemSlug, item of @_items
@@ -97,22 +101,16 @@ module.exports = class ModVersion extends BaseModel
eachName: (callback)->
for slug in @_slugs
callback @_names[slug], slug
callback @_names[slug.item]
return this
findName: (slug)->
[modSlug, itemSlug] = _.decomposeSlug slug
return @_names[itemSlug]
registerName: (slug, name)->
hasSlug = @_names[slug]?
@_names[slug] = name
if not hasSlug
@_slugs.push slug
@_slugs.sort()
@_slugs = _.uniq @_slugs, true
findName: (itemSlug)->
return @_names[itemSlug.item]
registerName: (itemSlug, name)->
return if @_names[itemSlug.item]
@_names[itemSlug.item] = name
@_slugs.push itemSlug
return this
# Recipe Methods ###############################################################################
@@ -121,15 +119,15 @@ module.exports = class ModVersion extends BaseModel
recipe.modVersion = this
for stack in recipe.output
recipeList = @_recipes[stack.slug]
recipeList = @_recipes[stack.itemSlug.item]
if not recipeList?
@_recipes[stack.slug] = recipeList = []
recipeList = @_recipes[stack.itemSlug.item] = []
recipeList.push recipe
return this
findRecipes: (slug, result=[])->
recipeList = @_recipes[slug]
findRecipes: (itemSlug, result=[])->
recipeList = @_recipes[itemSlug.item]
if recipeList?
for recipe in recipeList
result.push recipe
@@ -138,16 +136,19 @@ module.exports = class ModVersion extends BaseModel
findExternalRecipes: ->
result = {}
for slug, recipeList of @_recipes
[modSlug, itemSlug] = _.decomposeSlug slug
logger.debug "checking: #{modSlug}, #{itemSlug}"
continue if @_items[itemSlug]?
logger.debug "external: #{recipeList}"
result[itemSlug] = recipeList[..]
for itemSlug in @_slugs
continue if itemSlug.isQualified
recipes = @_recipes[itemSlug.item]
continue unless recipes? and recipes.length > 0
resultList = result[itemSlug] = []
for recipe in recipes
resultList.push recipe
return result
hasRecipes: (itemSlug)->
recipeList = @_recipes[itemSlug]
recipeList = @_recipes[itemSlug.item]
return true if recipeList? and recipeList.length > 0
return false
@@ -167,5 +168,5 @@ module.exports = class ModVersion extends BaseModel
toString: ->
return "ModVersion (#{@cid}) {
modSlug:#{@modSlug}, version:#{@version}, items:#{_.keys(@_items).length} items
modSlug:#{@modSlug}, version:#{@version}, items:«#{@_slugs.length} items»
}"
@@ -7,6 +7,7 @@ All rights reserved.
CommandParserVersionBase = require './command_parser_version_base'
Item = require '../item'
ItemSlug = require '../item_slug'
ModVersion = require '../mod_version'
Recipe = require '../recipe'
Stack = require '../stack'
@@ -124,6 +125,7 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
for itemName, itemData of modVersionData.items
@_handleErrors @_buildItem, modVersion, itemData
modVersion.sort()
return modVersion
_buildItem: (modVersion, itemData)->
@@ -134,6 +136,10 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
if itemData.type is 'new'
item = new Item name:itemData.name, isGatherable:itemData.gatherable, group:itemData.group
modVersion.addItem item
itemData.slug = item.slug
else
itemData.slug = ItemSlug.slugify itemData.name
modVersion.registerName itemData.slug, itemData.name
for recipeData in itemData.recipes
@_handleErrors @_buildRecipe, modVersion, itemData, recipeData
@@ -145,14 +151,14 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
if not recipeData.input? then throw new Error 'the "input" declaration is required'
if not recipeData.pattern? then throw new Error 'the "pattern" declaration is required'
itemSlug = _.slugify itemData.name
modVersion.registerName itemSlug, itemData.name
if itemData.type is 'new' then itemSlug = _.composeSlugs modVersion.modSlug, itemSlug
qualifySlug = (name, slug)=>
return slug unless itemData.type is 'new'
return slug unless @_rawData.items[name]?
return _.composeSlugs modVersion.modSlug, slug
createSlug = (name)=>
item = @_rawData.items[name]
if item?
slug = new ItemSlug modVersion.modSlug, _.slugify name
else
slug = new ItemSlug _.slugify name
modVersion.registerName slug, name
return slug
recipeData.quantity ?= 1
recipeData.extras ?= []
@@ -160,10 +166,8 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
inputStacks = []
for name in recipeData.input
slug = _.slugify name
modVersion.registerName slug, name
slug = qualifySlug name, slug
inputStacks.push new Stack slug:slug, quantity:0
inputSlug = createSlug name
inputStacks.push new Stack itemSlug:inputSlug, quantity:0
for c in recipeData.pattern
continue if c is '.'
@@ -175,23 +179,18 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
for i in [0...inputStacks.length]
stack = inputStacks[i]
if stack.quantity is 0
name = modVersion.findName stack.slug
name = modVersion.findName stack.itemSlug
throw new Error "#{name} is an input for this recipe, but it is not in the pattern"
outputStacks = [ new Stack slug:itemSlug, quantity:recipeData.quantity ]
outputStacks = [ new Stack itemSlug:itemData.slug, quantity:recipeData.quantity ]
for extraData in recipeData.extras
slug = _.slugify extraData.name
modVersion.registerName slug, extraData.name
slug = qualifySlug extraData.name, slug
outputStacks.push new Stack slug:slug, quantity:extraData.quantity
outputSlug = createSlug extraData.name
outputStacks.push new Stack itemSlug:outputSlug, quantity:extraData.quantity
toolStacks = []
for name in recipeData.tools
slug = _.slugify name
modVersion.registerName slug, name
slug = qualifySlug name, slug
toolStacks.push new Stack slug:slug, quantity:1
toolSlug = createSlug name
toolStacks.push new Stack itemSlug:toolSlug, quantity:1
recipe = new Recipe
input: inputStacks
@@ -200,7 +199,6 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
tools: toolStacks
modVersion.addRecipe recipe
logger.debug "adding recipe: #{recipe}"
return recipe
# Un-parsing Methods ###########################################################################
@@ -213,9 +211,9 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
modVersion.eachGroup (group)=>
@_unparseGroup builder, modVersion, group
for itemSlug, recipeList of modVersion.findExternalRecipes()
for itemSlugText, recipeList of modVersion.findExternalRecipes()
builder
.line 'update: ', modVersion.findName itemSlug
.line 'update: ', modVersion.findName ItemSlug.slugify(itemSlugText)
.indent()
.loop(recipeList, delimiter:'\n\n', onEach:(b, r)=> @_unparseRecipe(b, r))
.outdent()
@@ -235,7 +233,7 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
builder.outdent()
_unparseItem: (builder, modVersion, item)->
recipes = modVersion.findRecipes item.qualifiedSlug
recipes = modVersion.findRecipes item.slug
builder
.line 'item: ', item.name
@@ -246,13 +244,13 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
.outdent()
_unparseRecipe: (builder, recipe)->
inputNames = (builder.context.findName(stack.slug) for stack in recipe.input)
inputNames = (builder.context.findName(stack.itemSlug) for stack in recipe.input)
inputNames.sort()
patternMap = {'.', '.'}
for i in [0...recipe.input.length]
stack = recipe.input[i]
patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.slug)}"
patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.itemSlug)}"
pattern = recipe.pattern or recipe.defaultPattern
newPattern = []
@@ -288,9 +286,9 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
_unparseStackList: (builder, stackList)->
if stackList.length is 1 and stackList[0].quantity is 1
builder.push builder.context.findName(stackList[0].slug)
builder.push builder.context.findName(stackList[0].itemSlug)
else
builder.loop stackList, onEach:(b, stack)=>
builder
.onlyIf stack.quantity > 1, => builder.push stack.quantity, ' '
.push builder.context.findName stack.slug
.push builder.context.findName stack.itemSlug
+13 -6
View File
@@ -16,13 +16,13 @@ module.exports = class Recipe extends BaseModel
if not attributes.input? then throw new Error 'attributes.input is required'
if not attributes.pattern? then throw new Error 'attributes.pattern is required'
if attributes.slug? and not attributes.output?
attributes.output = [new Stack slug:attributes.slug, quantity:1]
else if attributes.output? and not attributes.slug?
if attributes.itemSlug? and not attributes.output?
attributes.output = [new Stack itemSlug:attributes.itemSlug, quantity:1]
else if attributes.output? and not attributes.itemSlug?
if attributes.output.length is 0 then throw new Error 'attributes.output cannot be empty'
attributes.slug = attributes.output[0].slug
attributes.itemSlug = attributes.output[0].itemSlug
else
throw new Error 'attributes.slug or attributes.output is required'
throw new Error 'attributes.itemSlug or attributes.output is required'
attributes.pattern = @_parsePattern attributes.pattern
@@ -42,7 +42,14 @@ module.exports = class Recipe extends BaseModel
stack = @input[parseInt(patternDigit)]
return null unless stack?
return stack.slug
return stack.itemSlug
produces: (itemSlug)->
for stack in @output
if stack.itemSlug.matches itemSlug
return true
return false
# Object Overrides #############################################################################
+2 -2
View File
@@ -12,7 +12,7 @@ BaseModel = require './base_model'
module.exports = class Stack extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.slug? then throw new Error 'attributes.slug is required'
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
attributes.quantity ?= 1
options.logEvents ?= false
super attributes, options
@@ -20,4 +20,4 @@ module.exports = class Stack extends BaseModel
# Object Overrides #############################################################################
toString: ->
return "#{@quantity} #{@slug}"
return "#{@quantity} #{@itemSlug}"