Qualify all item slugs to the mod which adds them #53

* Change default item URL to fall under /mod/<mod slug>/<item slug>
  but kept the old URL pattern around with the old behavior (i.e.,
  find the first item with that slug)
* Update Item to have a `qualifiedSlug` property which combines the
  item's own slug with its Mod's slug.  Updated a lot of code all
  over to use this in preference to the plain slug.
* Add a `localizeTo` method to Inventory to allow non-qualified slugs
  to be converted into qualified slugs within the inventory's stacks
* Update ModPack to work with either qualified or unqualified slugs
  for relevant methods
* Fix model state changes to happen before related events are
  triggered
* Add a debounce to the CraftingPlan object when triggering a recraft
  due to changes in related objects (esp. its inventories)
* Update the Inventory's sort to keep items sorted by mod before name
  with the Minecraft items sorted to the top
* Fix `ModPack.findItemByName` to actually recursively use the Mod's
  method instead of slugifying the given name and looking for the
  slug (since this won't necessarily work anymore).
* Ensure each Mod is assigned a reference to its containing ModPack
* Fix NameFinder to only consider an item non-gatherable if it is
  also craftable
* Update ModVersionParserV1 to assign qualified slugs to recipes
  for any items which are in the same mod (leaving any other slugs
  unqualified)
* Add Underscore mixins for composing and decomposing slugs
* Update old tests for the use of qualified slugs, and some new tests
  for the new methods added
This commit is contained in:
Andrew Miner
2015-02-10 17:56:02 -08:00
parent 246dbcafa9
commit 380a78ef61
28 changed files with 278 additions and 101 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ 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 "/item/<%= slug %>"
Url.item = _.template "/mod/<%= modSlug %>/<%= slug %>"
Url.mod = _.template "/mod/<%= modSlug %>"
Url.modData = _.template "/data/<%= modSlug %>/mod.cg"
Url.modVersion = _.template "/data/<%= modSlug %>/<%= modVersion %>/mod-version.cg"
@@ -68,8 +68,8 @@ module.exports = class FullRecipeController extends BaseController
result = []
if @model?
for stack in @model.tools
name = @modPack.findName stack.slug
result.push name if name?
item = @modPack.findItem stack.slug
result.push item.name if item?
return result
_refreshInputs: ->
@@ -28,7 +28,7 @@ module.exports = class ItemController extends BaseController
super
refresh: ->
display = @_modPack.findItemDisplay @model.slug
display = @_modPack.findItemDisplay @model.qualifiedSlug
@$icon.attr 'src', display.iconUrl
@$name.html display.itemName
@@ -57,7 +57,7 @@ module.exports = class ItemPageController extends BaseController
$('title').html if @model.item? then "#{@model.item.name} | #{Text.title}" else Text.title
@_resolveItemSlug()
display = @_modPack.findItemDisplay @model.item?.slug
display = @_modPack.findItemDisplay @model.item?.qualifiedSlug
if display?
@$craftingPlanLink.attr href:display.craftingUrl
@$craftingPlanLink.fadeIn duration:Duration.normal
@@ -143,6 +143,4 @@ module.exports = class ItemPageController extends BaseController
@$similarContainer.fadeOut duration:Duration.normal
_resolveItemSlug: ->
oldItem = @model.item
@model.item = @_modPack.findItem @_itemSlug, includeDisabled:true
newItem = @model.item
@@ -72,6 +72,6 @@ module.exports = class MinimalRecipeController extends BaseController
result = []
if @model?
for stack in @model.tools
name = @_modPack.findName stack.slug
result.push name if name?
item = @_modPack.findItem stack.slug
result.push item.name if item?
return result
+9 -3
View File
@@ -61,6 +61,7 @@ module.exports = class CraftingGuideRouter extends Backbone.Router
'item/:itemSlug': 'item'
'crafting/(:text)': 'crafting'
'mod/:modSlug': 'mod'
'mod/:modSlug/:itemSlug': 'modItem'
# Route Methods ################################################################################
@@ -69,7 +70,12 @@ module.exports = class CraftingGuideRouter extends Backbone.Router
controller.model.params = inventoryText:text
@_setPage 'crafting', controller
item: (slug)->
item: (itemSlug)->
controller = new ItemPageController _.extend {itemSlug:itemSlug}, @_defaultOptions
@_setPage 'item', controller
modItem: (modSlug, itemSlug)->
slug = _.composeSlugs modSlug, itemSlug
controller = new ItemPageController _.extend {itemSlug:slug}, @_defaultOptions
@_setPage 'item', controller
@@ -84,9 +90,9 @@ module.exports = class CraftingGuideRouter extends Backbone.Router
text = ''
if params.recipeName?
if params.count?
text = "#{params.count}.#{params.recipeName}"
text = "#{params.count}.#{_.slugify(params.recipeName)}"
else
text = "#{params.recipeName}"
text = _.slugify params.recipeName
@navigate Url.crafting(inventoryText:text), trigger:true
+1 -1
View File
@@ -24,7 +24,7 @@ global.logger = new Logger
switch window.location.hostname
when 'localhost'
global.env = 'development'
logger.level = Logger.INFO
logger.level = Logger.DEBUG
when 'new.crafting-guide.com'
global.env = 'staging'
logger.level = Logger.VERBOSE
+4 -4
View File
@@ -25,10 +25,6 @@ module.exports = class BaseModel extends Backbone.Model
@logEvents = options.logEvents or false
@state = ModelState.unloaded
@on 'request', => @state = ModelState.loading
@on 'sync', => @state = ModelState.loaded
@on 'error', => @state = ModelState.error
@loading = null
Object.defineProperties this,
@@ -42,6 +38,8 @@ module.exports = class BaseModel extends Backbone.Model
onLoadSucceeded: (text, status, xhr)->
try
@set @parse text
@state = ModelState.loaded
@trigger Event.change, this
@trigger Event.sync, this
logger.info => "#{@constructor.name}.#{@cid} loaded successfully"
@@ -50,6 +48,7 @@ module.exports = class BaseModel extends Backbone.Model
@onLoadFailed e.message, 'parsing failed', xhr
onLoadFailed: (error, status, xhr)->
@state = ModelState.error
logger.error => "#{@constructor.name}.#{@cid} failed to load: status:#{status}, message:#{error}"
@trigger Event.error, this, error
@@ -62,6 +61,7 @@ module.exports = class BaseModel extends Backbone.Model
url = @url()
logger.info => "#{@constructor.name}.#{@cid} reading from url: #{url}"
@state = ModelState.loading
@trigger Event.request, this
@loading = w.promise (resolve, reject)=>
$.ajax
+2 -2
View File
@@ -14,7 +14,7 @@ ModPack = require './mod_pack'
########################################################################################################################
module.exports = class extends BaseModel
module.exports = class CraftingPage extends BaseModel
constructor: (attributes={}, options={})->
attributes.modPack ?= new ModPack
@@ -40,7 +40,7 @@ module.exports = class extends BaseModel
inventory = @_parser.parse @params.inventoryText
inventory.each (stack)=>
item = @modPack.findItemByName stack.slug, enableAsNeeded:true
item = @modPack.findItem stack.slug, enableAsNeeded:true
return unless item? and item.isCraftable
@plan.want.add stack.slug, stack.quantity
inventory.remove stack.slug
+35 -23
View File
@@ -23,14 +23,14 @@ module.exports = class CraftingPlan extends BaseModel
@need = new Inventory
@result = new Inventory
recraft = _.debounce (=> @craft()), 100
for inventory in [@have, @want]
inventory.on 'change', recraft
@on Event.change + ':includingTools', recraft
@clear()
@have.on Event.change, => @craft()
@want.on Event.change, => @craft()
@modPack.on Event.change, => @craft()
@on Event.change + ':includingTools', => @craft()
# Public Methods ###############################################################################
clear: (options={})->
@@ -42,7 +42,12 @@ module.exports = class CraftingPlan extends BaseModel
return this
craft: ->
toolsMessage = if @includingTools then ' (including tools)' else ''
logger.info => "crafting #{@want}#{toolsMessage} starting with #{@have}"
@clear()
@have.localizeTo @modPack
@want.localizeTo @modPack
@result.addInventory @have
@@ -50,12 +55,14 @@ module.exports = class CraftingPlan extends BaseModel
@_reservedSteps = {}
@want.each (stack)=>
@_findSteps stack.slug
@need.add stack.slug, stack.quantity
item = @modPack.findItem stack.slug
@need.add item.qualifiedSlug, stack.quantity
@_reservedSteps = null
@steps = _.values @steps
@_resolveNeeds()
@_removeExtraSteps()
@result.addInventory @want
@need.trigger 'change', @need
@@ -91,14 +98,13 @@ 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.item.qualifiedSlug}"
@steps[recipe.item.qualifiedSlug] = recipe:recipe
_chooseRecipe: (item)->
return item.getPrimaryRecipe()
_findSteps: (slug)->
logger.debug -> "finding steps for #{slug}"
item = @modPack.findItem slug
return unless item?
return unless item.isCraftable
@@ -111,9 +117,8 @@ module.exports = class CraftingPlan extends BaseModel
if not @_hasStep toolStack.slug
@_findSteps toolStack.slug
return if @_hasStep item.slug
logger.debug -> "reserving: #{item.slug}"
@_reservedSteps[item.slug] = recipe
return if @_hasStep item.qualifiedSlug
@_reservedSteps[item.qualifiedSlug] = recipe
for inputStack in recipe.input
@_findSteps inputStack.slug
@@ -125,6 +130,11 @@ module.exports = class CraftingPlan extends BaseModel
return true if @_reservedSteps[slug]?
return false
_qualifyItemSlug: (slug)->
item = @modPack.findItem slug
return item.qualifiedSlug if item?
return slug
_removeExtraSteps: ->
result = (step for step in @steps when step.multiplier > 0)
@steps = result
@@ -134,29 +144,31 @@ module.exports = class CraftingPlan extends BaseModel
step = @steps[i]
recipe = step.recipe
step.multiplier = Math.ceil(@need.quantityOf(recipe.slug) / step.recipe.output[0].quantity)
step.multiplier = Math.ceil(@need.quantityOf(recipe.slug) / recipe.output[0].quantity)
if @includingTools
for stack in recipe.tools
slug = stack.slug
slug = @_qualifyItemSlug stack.slug
available = @result.quantityOf(slug) + @need.quantityOf(slug)
needed = Math.max 0, stack.quantity - available
@need.add stack.slug, needed
@result.add stack.slug, needed
@need.add slug, needed
@result.add slug, needed
for stack in recipe.input
slug = @_qualifyItemSlug stack.slug
needed = step.multiplier * stack.quantity
consumed = Math.min needed, @result.quantityOf(stack.slug)
consumed = Math.min needed, @result.quantityOf slug
remaining = needed - consumed
@result.remove stack.slug, consumed
@need.add stack.slug, remaining
@result.remove slug, consumed
@need.add slug, remaining
for stack in recipe.output
slug = @_qualifyItemSlug stack.slug
created = stack.quantity * step.multiplier
consumed = Math.min created, @need.quantityOf stack.slug
consumed = Math.min created, @need.quantityOf slug
remaining = created - consumed
@result.add stack.slug, remaining
@need.remove stack.slug, consumed
@result.add slug, remaining
@need.remove slug, consumed
+34 -1
View File
@@ -7,6 +7,7 @@ All rights reserved.
BaseModel = require './base_model'
{Event} = require '../constants'
{RequiredMods} = require '../constants'
Stack = require './stack'
########################################################################################################################
@@ -59,6 +60,22 @@ module.exports = class Inventory extends BaseModel
return false unless stack?
return stack.quantity >= quantity
localizeTo: (modPack)->
newSlugs = []
for slug in @_slugs
stack = @_stacks[slug]
qualifiedSlug = modPack.findItem(slug)?.qualifiedSlug
if qualifiedSlug?
delete @_stacks[slug]
newSlugs.push qualifiedSlug
@_stacks[qualifiedSlug] = stack
stack.slug = qualifiedSlug
else
throw new Error "could not find an item for: #{slug}"
@_slugs = newSlugs
@_sort()
pop: ->
slug = @_slugs.pop()
return null unless slug?
@@ -129,6 +146,22 @@ module.exports = class Inventory extends BaseModel
stack = new Stack slug:slug, quantity:quantity
@_stacks[slug] = stack
@_slugs.push slug
@_slugs.sort()
@_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
+2 -2
View File
@@ -33,10 +33,10 @@ module.exports = class InventoryParser
stackParts = stackText.split InventoryParser.ITEM_DELIMITER
if stackParts.length is 2
quantity = parseInt stackParts[0]
slug = _.slugify stackParts[1]
slug = stackParts[1]
else if stackParts.length is 1
quantity = 1
slug = _.slugify stackParts[0]
slug = stackParts[0]
else
throw new Error "expected #{stackText} to have 0 or 1 parts"
+22 -4
View File
@@ -21,28 +21,33 @@ module.exports = class Item extends BaseModel
attributes.group ?= Item.Group.Other
attributes.isGatherable ?= false
attributes.modVersion ?= null
attributes.slug ?= _.slugify attributes.name
options.logEvents ?= false
super attributes, options
@_recipes = []
Object.defineProperties this,
isCraftable: { get:-> @_recipes.length > 0 }
qualifiedSlug: { get:@getQualifiedSlug }
primaryRecipe: { get:@getPrimaryRecipe }
@on Event.change + ':modVersion', => @_qualifiedSlug = null
# Public Methods ###############################################################################
addRecipe: (recipe)->
if recipe.slug isnt @slug then throw new Error "cannot add a recipe for #{recipe.slug} to #{@slug}"
[modSlug, itemSlug] = _.decomposeSlug recipe.slug
if itemSlug isnt @slug then throw new Error "cannot add a recipe for #{recipe.slug} to #{@slug}"
recipe.item = this
@_recipes.push recipe
eachRecipe: (callback)->
for recipe in @_recipes
callback recipe
getPrimaryRecipe: ->
return @_recipes[0]
compareTo: (that)->
if this.slug isnt that.slug
return if this.slug < that.slug then -1 else +1
@@ -50,6 +55,19 @@ module.exports = class Item extends BaseModel
return if this.name < that.name then -1 else +1
return 0
# Property Methods #############################################################################
getQualifiedSlug: ->
return @slug if not @modVersion?
if not @_qualifiedSlug?
@_qualifiedSlug = _.composeSlugs @modVersion.modSlug, @slug
return @_qualifiedSlug
getPrimaryRecipe: ->
return @_recipes[0]
# Object Overrides #############################################################################
toString: ->
+4 -4
View File
@@ -31,14 +31,14 @@ module.exports = class ItemPage extends BaseModel
findComponentInItems: ->
return null unless @item?
itemSlug = @item.slug
itemSlug = @item.qualifiedSlug
result = {}
@modPack.eachMod (mod)->
mod.eachItem (item)->
item.eachRecipe (recipe)->
for stack in recipe.input
if stack.slug is itemSlug
result[item.slug] = item
result[item.qualifiedSlug] = item
result = _.values(result).sort (a, b)-> a.compareTo b
return null unless result.length > 0
@@ -56,7 +56,7 @@ module.exports = class ItemPage extends BaseModel
return result
findRecipes: ->
result = @modPack.findRecipes @item?.slug
result = @modPack.findRecipes @item?.qualifiedSlug
return null unless result.length > 0
return result
@@ -66,7 +66,7 @@ module.exports = class ItemPage extends BaseModel
@_plan.clear()
if @item?
@_plan.want.add @item.slug
@_plan.want.add @item.qualifiedSlug
@_plan.craft()
if @_plan.steps.length > 0
+1
View File
@@ -21,6 +21,7 @@ module.exports = class Mod extends BaseModel
attributes.documentationUrl ?= null
attributes.downloadUrl ?= null
attributes.homePageUrl ?= null
attributes.modPack ?= null
attributes.name ?= ''
super attributes, options
+20 -8
View File
@@ -25,9 +25,16 @@ module.exports = class ModPack extends BaseModel
findItem: (slug, options={})->
options.includeDisabled ?= false
[modSlug, itemSlug] = _.decomposeSlug slug
if modSlug?
mod = @getMod modSlug
if mod?
item = mod.findItem itemSlug, options
return item if item?
for mod in @_mods
continue unless mod.enabled or options.includeDisabled
item = mod.findItem slug, options
item = mod.findItem itemSlug, options
return item if item?
return null
@@ -36,10 +43,9 @@ module.exports = class ModPack extends BaseModel
options.enableAsNeeded ?= false
options.includeDisabled = true if options.enableAsNeeded
slug = _.slugify name
for mod in @_mods
continue unless mod.enabled or options.includeDisabled
item = mod.findItem slug, options
item = mod.findItemByName name, options
return item if item?
return null
@@ -74,6 +80,14 @@ module.exports = class ModPack extends BaseModel
return null
findRecipes: (slug, result=[])->
[modSlug, itemSlug] = _.decomposeSlug slug
if modSlug?
mod = @getMod modSlug
if mod?
mod.findRecipes slug, result
return result if result.length > 0
for mod in @_mods
continue unless mod.enabled
mod.findRecipes slug, result
@@ -88,12 +102,9 @@ module.exports = class ModPack extends BaseModel
isValidName: (name)->
slug = _.slugify name
for mod in @_mods
continue unless mod.enabled
name = mod.findName slug
return true if name
existingName = @findName slug
return false
return name is existingName
# Property Methods #############################################################################
@@ -101,6 +112,7 @@ module.exports = class ModPack extends BaseModel
if not mod? then throw new Error 'mod is required'
return if @_mods.indexOf(mod) isnt -1
mod.modPack = this
@_mods.push mod
@listenTo mod, Event.change, => @trigger Event.change, this
@trigger Event.add + ':mod', mod, this
+1
View File
@@ -19,6 +19,7 @@ module.exports = class ModVersion extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.modSlug? then throw new Error 'attributes.modSlug is required'
if not attributes.version? then throw new Error 'attributes.version is required'
attributes.mod ?= null
super attributes, options
@_groups = {}
+1 -1
View File
@@ -67,7 +67,7 @@ module.exports = class NameFinder
item = mod.findItem slug
if not @includeGatherable
return unless item? and (not item.isGatherable)
return unless item? and item.isCraftable
scanName = "#{mod.name} : #{name}"
if nameHint?
@@ -61,8 +61,8 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
if not name.length > 0 then throw new Error 'the item name cannot be empty'
@_itemData = name:name, line:@_lineNumber, group:@_rawData.group
@_rawData.items ?= []
@_rawData.items.push @_itemData
@_rawData.items ?= {}
@_rawData.items[name] = @_itemData
@_recipeData = null
@@ -112,7 +112,7 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
_buildModVersion: (modVersionData, modVersion)->
modVersionData.items ?= []
for itemData in modVersionData.items
for itemName, itemData of modVersionData.items
@_handleErrors @_buildItem, modVersion, itemData
return modVersion
@@ -135,6 +135,12 @@ 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'
localizeSlug = (name, slug)=>
if @_rawData.items[name]?
return _.composeSlugs modVersion.modSlug, slug
else
return slug
recipeData.quantity ?= 1
recipeData.extras ?= []
recipeData.tools ?= []
@@ -143,6 +149,7 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
for name in recipeData.input
slug = _.slugify name
modVersion.registerSlug slug, name
slug = localizeSlug name, slug
inputStacks.push new Stack slug:slug, quantity:0
for c in recipeData.pattern
@@ -158,16 +165,18 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
name = modVersion.findName stack.slug
throw new Error "#{name} is an input for this recipe, but it is not in the pattern"
outputStacks = [ new Stack slug:item.slug, quantity:recipeData.quantity ]
outputStacks = [ new Stack slug:item.qualifiedSlug, quantity:recipeData.quantity ]
for extraData in recipeData.extras
slug = _.slugify extraData.name
modVersion.registerSlug slug, extraData.name
slug = localizeSlug extraData.name, slug
outputStacks.push new Stack slug:slug, quantity:extraData.quantity
toolStacks = []
for name in recipeData.tools
slug = _.slugify name
modVersion.registerSlug slug, name
slug = localizeSlug name, slug
toolStacks.push new Stack slug:slug, quantity:1
attributes =
+10 -3
View File
@@ -15,8 +15,7 @@ module.exports = class Recipe extends BaseModel
constructor: (attributes={}, options={})->
if attributes.item?
attributes.name = attributes.item.name
attributes.slug = attributes.item.slug
attributes.output ?= [new Stack slug:attributes.item.slug, quantity:1]
attributes.output ?= [new Stack slug:attributes.item.qualifiedSlug, quantity:1]
if not attributes.name? then throw new Error 'attributes.name is required'
if not attributes.input? then throw new Error 'attributes.input is required'
@@ -25,11 +24,13 @@ module.exports = class Recipe extends BaseModel
attributes.item ?= null
attributes.output ?= [new Stack slug:_.slugify(attributes.name), quantity:1]
attributes.pattern = @_parsePattern attributes.pattern
attributes.slug ?= attributes.output[0].slug
attributes.tools ?= []
options.logEvents ?= false
super attributes, options
Object.defineProperties this,
slug: {get:@getSlug}
# Public Methods ###############################################################################
getItemSlugAt: (patternSlot)->
@@ -48,6 +49,12 @@ module.exports = class Recipe extends BaseModel
return true if stack.slug is itemSlug
return false
# Property Methods #############################################################################
getSlug: ->
return @item.qualifiedSlug if @item?
return @output[0].slug
# Object Overrides #############################################################################
toString: ->
+12
View File
@@ -16,3 +16,15 @@ _.mixin
result = result.replace /^_/, ''
result = result.replace /_$/, ''
return result
composeSlugs: (part1, part2)->
return "#{part1}__#{part2}"
decomposeSlug: (slug)->
return [null, null] unless slug?
parts = slug.split '__'
if parts.length is 1
parts = [ null, parts[0] ]
return parts
+14 -6
View File
@@ -44,25 +44,29 @@ describe 'crafting_plan.coffee', ->
plan.want.add 'oak_plank'
plan.craft()
plan.need.toList().should.eql ['oak_log']
plan.result.toList().should.eql [[4, 'oak_plank']]
plan.result.toList().should.eql [[4, 'minecraft__oak_plank']]
it 'can craft a multi-step recipe', ->
plan.want.add 'crafting_table'
plan.craft()
plan.need.toList().should.eql ['oak_log']
plan.result.toList().should.eql ['crafting_table']
plan.result.toList().should.eql ['minecraft__crafting_table']
it 'can craft a multi-step recipe using tools', ->
plan.want.add 'furnace'
plan.craft()
plan.need.toList().should.eql [[8, 'cobblestone']]
plan.result.toList().should.eql ['furnace']
plan.result.toList().should.eql ['minecraft__furnace']
it 'can craft a multi-step recipe re-using tools', ->
plan.want.add 'iron_sword'
plan.craft()
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']]
plan.result.toList().should.eql [
'minecraft__iron_sword',
[2, 'minecraft__oak_plank'],
[3, 'minecraft__stick']
]
describe 'with building tools', ->
@@ -71,7 +75,7 @@ describe 'crafting_plan.coffee', ->
plan.want.add 'furnace'
plan.craft()
plan.need.toList().should.eql [[8, 'cobblestone'], 'oak_log']
plan.result.toList().should.eql ['crafting_table', 'furnace']
plan.result.toList().should.eql ['minecraft__crafting_table', 'minecraft__furnace']
it 'can craft a multi-step recipe re-using tools', ->
plan.includingTools = true
@@ -82,5 +86,9 @@ describe 'crafting_plan.coffee', ->
[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']
'minecraft__crafting_table',
'minecraft__furnace',
'minecraft__iron_sword',
[2, 'minecraft__oak_plank'],
[3, 'minecraft__stick']
]
+34 -1
View File
@@ -12,7 +12,7 @@ Item = require '../src/scripts/models/item'
########################################################################################################################
inventory = null
inventory = modPack = null
########################################################################################################################
@@ -107,6 +107,39 @@ describe 'inventory.coffee', ->
inventory.hasAtLeast('wool', 4).should.be.true
inventory.hasAtLeast('wool', 5).should.be.false
describe 'localizeTo', ->
before ->
modPack =
map:
wool: 'minecraft__wool'
string: 'minecraft__string'
boat: 'minecraft__boat'
stone_gear: 'buildcraft__stone_gear'
findItem: (slug)->
[modSlug, itemSlug] = _.decomposeSlug slug
return slug:itemSlug, qualifiedSlug:@map[itemSlug]
it 'replaces item slugs with qualified slugs', ->
inventory.add 'stone_gear'
inventory.localizeTo modPack
inventory.toList().should.eql [
'minecraft__boat',
[20, 'minecraft__string'],
[4, 'minecraft__wool'],
'buildcraft__stone_gear'
]
it 'ignores qualified slugs', ->
inventory.add 'buildcraft__stone_gear'
inventory.localizeTo modPack
inventory.toList().should.eql [
'minecraft__boat',
[20, 'minecraft__string'],
[4, 'minecraft__wool'],
'buildcraft__stone_gear'
]
describe 'pop', ->
it 'returns null for an empty inventory', ->
+3 -3
View File
@@ -24,7 +24,7 @@ describe 'inventory_parser.coffee', ->
result.toList().should.eql []
it 'can parse a single item without quantity', ->
result = parser.parse 'Wool'
result = parser.parse 'wool'
result.toList().should.eql ['wool']
it 'can parse a single item with quantity', ->
@@ -32,11 +32,11 @@ describe 'inventory_parser.coffee', ->
result.toList().should.eql [[4, 'wool']]
it 'can parse multiple mixed-type items', ->
result = parser.parse '4.Wool:10.String:Boat'
result = parser.parse '4.wool:10.string:boat'
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
result = parser.parse '4.wool', inventory
result.toList().should.eql [[8, 'string'], [4, 'wool']]
+32 -7
View File
@@ -19,22 +19,23 @@ buildcraft = industrialCraft = minecraft = modPack = null
describe 'mod_pack.coffee', ->
beforeEach ->
minecraft = new Mod slug:'minecraft'
minecraft = new Mod slug:'minecraft', name:'Minecraft'
minecraft.addModVersion new ModVersion modSlug:minecraft.slug, version:'1.7.10'
minecraft.activeModVersion.addItem new Item name:'Wool'
minecraft.activeModVersion.addItem new Item name:'Bed', recipes:['']
minecraft.activeModVersion.registerSlug 'iron_chestplate', 'Iron Chestplate'
buildcraft = new Mod slug:'buildcraft'
buildcraft = new Mod slug:'buildcraft', name:'Buildcraft'
buildcraft.addModVersion new ModVersion modSlug:buildcraft.slug, version:'6.2.6'
buildcraft.activeModVersion.addItem new Item name:'Stone Gear', recipes:['']
buildcraft.activeModVersion.addItem new Item name:'Bed', recipes:['']
buildcraft.activeModVersion.addItem new Item name:'Wrench', recipes:['']
buildcraft.activeVersion = Mod.Version.None
industrialCraft = new Mod slug:'industrial_craft'
industrialCraft = new Mod slug:'industrial_craft', name:'Industrial Craft'
industrialCraft.addModVersion new ModVersion modSlug:industrialCraft.slug, version:'2.0'
industrialCraft.activeModVersion.addItem new Item name:'Resin'
industrialCraft.activeModVersion.addItem new Item name:'Rubber'
industrialCraft.activeModVersion.addItem new Item name:'Wrench', recipes:['']
industrialCraft.activeVersion = Mod.Version.None
modPack = new ModPack
@@ -42,6 +43,30 @@ describe 'mod_pack.coffee', ->
modPack.addMod buildcraft
modPack.addMod industrialCraft
describe 'findItem', ->
it 'can find an item by partial slug', ->
item = modPack.findItem 'wool'
item.qualifiedSlug.should.equal 'minecraft__wool'
it 'can find an item by full slug', ->
item = modPack.findItem 'minecraft__wool'
item.name.should.equal 'Wool'
it 'can find an ambiguous item by full slug', ->
buildcraft.activeVersion = Mod.Version.Latest
industrialCraft.activeVersion = Mod.Version.Latest
item = modPack.findItem 'industrial_craft__wrench'
item.name.should.equal 'Wrench'
item.modVersion.mod.name.should.equal 'Industrial Craft'
it 'can find an ambiguous item by partial slug', ->
buildcraft.activeVersion = Mod.Version.Latest
industrialCraft.activeVersion = Mod.Version.Latest
item = modPack.findItem 'wrench'
item.name.should.equal 'Wrench'
item.modVersion.mod.name.should.equal 'Buildcraft'
describe 'findItemByName', ->
it 'finds the requested item', ->
@@ -57,7 +82,7 @@ describe 'mod_pack.coffee', ->
it 'returns all data for a regular Minecraft item', ->
display = modPack.findItemDisplay 'bed'
display.iconUrl.should.equal '/data/minecraft/1.7.10/images/bed.png'
display.itemUrl.should.equal '/item/bed'
display.itemUrl.should.equal '/mod/minecraft/bed'
display.itemName.should.equal 'Bed'
display.modSlug.should.equal 'minecraft'
@@ -65,13 +90,13 @@ describe 'mod_pack.coffee', ->
buildcraft.activeVersion = '6.2.6'
display = modPack.findItemDisplay 'stone_gear'
display.iconUrl.should.equal '/data/buildcraft/6.2.6/images/stone_gear.png'
display.itemUrl.should.equal '/item/stone_gear'
display.itemUrl.should.equal '/mod/buildcraft/stone_gear'
display.itemName.should.equal 'Stone Gear'
display.modSlug.should.equal 'buildcraft'
it 'assumes an unfound item is from Minecraft', ->
display = modPack.findItemDisplay 'iron_chestplate'
display.iconUrl.should.equal '/data/minecraft/1.7.10/images/iron_chestplate.png'
display.itemUrl.should.equal '/item/iron_chestplate'
display.itemUrl.should.equal '/mod/minecraft/iron_chestplate'
display.itemName.should.equal 'Iron Chestplate'
display.modSlug.should.equal 'minecraft'
+2 -2
View File
@@ -91,5 +91,5 @@ describe 'mod_version.coffee', ->
"""
it 'finds all recipes which list item as output', ->
recipes = modVersion.findRecipes 'bucket'
(r.output[0].slug for r in recipes).sort().should.eql ['bucket', 'cake', 'cake']
recipes = modVersion.findRecipes 'test__bucket'
(r.output[0].slug for r in recipes).sort().should.eql ['test__bucket', 'test__cake', 'test__cake']
@@ -68,7 +68,7 @@ describe 'mod_version_parser_v1.coffee', ->
it 'adds "input" when present', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern: ... 012 ...'
slugs = (s.slug for s in modVersion._items.charlie._recipes[0].input)
slugs.should.eql ['alpha', 'bravo', 'charlie']
slugs.should.eql ['alpha', 'bravo', 'test__charlie']
it 'requires an "input" declaration', ->
func = -> parser.parse baseText + 'recipe:; pattern: ... .0. ...'
@@ -155,20 +155,20 @@ describe 'mod_version_parser_v1.coffee', ->
describe 'output', ->
beforeEach ->
baseText = 'item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
baseText = 'item: Delta; item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
it 'adds a single item as the default output', ->
modVersion = parser.parse baseText
stack = modVersion._items.bravo._recipes[0].output[0]
stack.slug.should.equal 'bravo'
stack.slug.should.equal 'test__bravo'
stack.quantity.should.equal 1
it 'can add multiple extras with quantities', ->
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
output = modVersion._items.bravo._recipes[0].output
output[0].slug.should.equal 'bravo'
output[0].slug.should.equal 'test__bravo'
output[0].quantity.should.equal 1
output[1].slug.should.equal 'delta'
output[1].slug.should.equal 'test__delta'
output[1].quantity.should.equal 2
output[2].slug.should.equal 'echo'
output[2].quantity.should.equal 4
+3 -1
View File
@@ -5,6 +5,7 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
Item = require '../src/scripts/models/item'
Recipe = require '../src/scripts/models/recipe'
Stack = require '../src/scripts/models/stack'
@@ -32,7 +33,8 @@ describe 'recipe.coffee', ->
expect(-> new Recipe name:'Gold Gear', input:input).to.throw Error, 'attributes.pattern is required'
it 'allows an item to provide required attributes', ->
recipe = new Recipe item:{name:'Gold Gear', slug:'gold_gear'}, input:input, pattern:pattern
item = new Item name:'Gold Gear'
recipe = new Recipe item:item, input:input, pattern:pattern
recipe.name.should.equal 'Gold Gear'
(o.slug for o in recipe.output).should.eql ['gold_gear']