diff --git a/src/scripts/models/crafting_plan.coffee b/src/scripts/models/crafting_plan.coffee index 41cbd0d56..eafd955b8 100644 --- a/src/scripts/models/crafting_plan.coffee +++ b/src/scripts/models/crafting_plan.coffee @@ -90,7 +90,7 @@ module.exports = class CraftingPlan extends BaseModel return "#{@constructor.name} { have:#{@have}, want:#{@want}, - need:#{@_need}, + need:#{@need}, result:#{@result}, steps:#{@steps} }" @@ -98,11 +98,13 @@ module.exports = class CraftingPlan extends BaseModel # Private Methods ############################################################################## _addStep: (recipe)-> - logger.verbose -> "adding step: #{recipe.item.qualifiedSlug}" - @steps[recipe.item.qualifiedSlug] = recipe:recipe + logger.verbose -> "adding step: #{recipe.slug}" + @steps[recipe.slug] = recipe:recipe _chooseRecipe: (item)-> - return item.getPrimaryRecipe() + recipes = @modPack.findRecipes item.qualifiedSlug + return null unless recipes? + return recipes[0] _findSteps: (slug)-> item = @modPack.findItem slug diff --git a/src/scripts/models/item.coffee b/src/scripts/models/item.coffee index 4480c3ace..55db52aec 100644 --- a/src/scripts/models/item.coffee +++ b/src/scripts/models/item.coffee @@ -9,6 +9,7 @@ BaseCollection = require './base_collection' BaseModel = require './base_model' {Event} = require '../constants' Recipe = require './recipe' +StringBuilder = require './string_builder' ######################################################################################################################## @@ -27,27 +28,14 @@ module.exports = class Item extends BaseModel options.logEvents ?= false super attributes, options - @_recipes = [] - Object.defineProperties this, - isCraftable: { get:-> @_recipes.length > 0 } - qualifiedSlug: { get:@getQualifiedSlug } - primaryRecipe: { get:@getPrimaryRecipe } + isCraftable: {get:@getIsCraftable} + qualifiedSlug: {get:@getQualifiedSlug} @on Event.change + ':modVersion', => @_qualifiedSlug = null # Public Methods ############################################################################### - addRecipe: (recipe)-> - [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 - compareTo: (that)-> if this.slug isnt that.slug return if this.slug < that.slug then -1 else +1 @@ -57,6 +45,10 @@ module.exports = class Item extends BaseModel # Property Methods ############################################################################# + getIsCraftable: -> + return false unless @modVersion? + return @modVersion.hasRecipes @qualifiedSlug + getQualifiedSlug: -> return @slug if not @modVersion? @@ -65,25 +57,18 @@ module.exports = class Item extends BaseModel return @_qualifiedSlug - getPrimaryRecipe: -> - return @_recipes[0] - # Object Overrides ############################################################################# toString: -> - result = [] - result.push @constructor.name - result.push ' ('; result.push @cid; result.push ') { ' - result.push 'name:"'; result.push @name; result.push '", ' - result.push 'isGatherable:'; result.push @isGatherable - - if _.slugify(@name) isnt @slug - result.push ', slug:'; result.push @slug - - if @_recipes.length > 0 - result.push ', recipes:«' - result.push @_recipes.length - result.push ' items»' - - result.push '}' - return result.join '' + builder = new StringBuilder + return builder + .push @constructor.name, ' (', @cid, ') { ' + .push 'name:"', @name, '", ' + .push 'isCraftable:', @isCraftable, ', ' + .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 '}' + .toString() diff --git a/src/scripts/models/mod_pack.coffee b/src/scripts/models/mod_pack.coffee index 96c7b2601..f78fa0d42 100644 --- a/src/scripts/models/mod_pack.coffee +++ b/src/scripts/models/mod_pack.coffee @@ -91,7 +91,8 @@ module.exports = class ModPack extends BaseModel for mod in @_mods continue unless mod.enabled mod.findRecipes slug, result - return result + + return if result.length > 0 then result else null isGatherable: (slug)-> item = @findItem slug diff --git a/src/scripts/models/mod_version.coffee b/src/scripts/models/mod_version.coffee index 4d13d1652..7d91ca634 100644 --- a/src/scripts/models/mod_version.coffee +++ b/src/scripts/models/mod_version.coffee @@ -22,30 +22,14 @@ module.exports = class ModVersion extends BaseModel attributes.mod ?= null super attributes, options - @_groups = {} - @_items = {} - @_names = {} - @_slugs = [] + @_groups = {} + @_items = {} + @_names = {} + @_recipes = {} + @_slugs = [] # Public 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 - - item.modVersion = this - @registerSlug item.slug, item.name - return this - - allItemsInGroup: (group)-> - result = [] - @eachItemInGroup group, (item)-> result.push item - return null if result.length is 0 - return result - compareTo: (that)-> if this.mod? and that.mod? return this.mod.compareTo that.mod @@ -55,16 +39,24 @@ module.exports = class ModVersion extends BaseModel return 0 - eachGroup: (callback)-> - groupNames = _.keys @_groups - groupNames.sort (a, b)-> - if a is b then return 0 - if a is Item.Group.Other then return -1 - if b is Item.Group.Other then return +1 - return if a < b then -1 else +1 + # Item Methods ################################################################################# - for groupName in groupNames - callback groupName + 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 + + item.modVersion = this + @registerName item.slug, item.name + return this + + allItemsInGroup: (group)-> + result = [] + @eachItemInGroup group, (item)-> result.push item + return null if result.length is 0 + return result eachItem: (callback)-> for slug in @_slugs @@ -80,30 +72,38 @@ module.exports = class ModVersion extends BaseModel for slug in _.keys(group).sort() callback group[slug] + findItem: (itemSlug)-> + return @_items[itemSlug] + + findItemByName: (name)-> + for itemSlug, item of @_items + return item if item.name is name + return null + + # Group Methods ################################################################################ + + eachGroup: (callback)-> + groupNames = _.keys @_groups + groupNames.sort (a, b)-> + if a is b then return 0 + if a is Item.Group.Other then return -1 + if b is Item.Group.Other then return +1 + return if a < b then -1 else +1 + + for groupName in groupNames + callback groupName + + # Name Methods ################################################################################# + eachName: (callback)-> for slug in @_slugs callback @_names[slug], slug return this - findItem: (slug)-> - return @_items[slug] - - findItemByName: (name)-> - return @findItem _.slugify name - findName: (slug)-> return @_names[slug] - findRecipes: (itemSlug, result=[])-> - for slug in @_slugs - item = @_items[slug] - continue unless item? - - item.eachRecipe (recipe)-> - result.push recipe if recipe.doesProduce itemSlug - return result - - registerSlug: (slug, name)-> + registerName: (slug, name)-> hasSlug = @_names[slug]? @_names[slug] = name @@ -114,6 +114,32 @@ module.exports = class ModVersion extends BaseModel return this + # Recipe Methods ############################################################################### + + addRecipe: (recipe)-> + recipe.modVersion = this + + for stack in recipe.output + recipeList = @_recipes[stack.slug] + if not recipeList? + @_recipes[stack.slug] = recipeList = [] + recipeList.push recipe + + return this + + findRecipes: (itemSlug, result=[])-> + recipeList = @_recipes[itemSlug] + if recipeList? + for recipe in recipeList + result.push recipe + + return result + + hasRecipes: (itemSlug)-> + recipeList = @_recipes[itemSlug] + return true if recipeList? and recipeList.length > 0 + return false + # Backbone.Model Overrides ##################################################################### parse: (text)-> diff --git a/src/scripts/models/parser_versions/mod_version_parser_v1.coffee b/src/scripts/models/parser_versions/mod_version_parser_v1.coffee index d47b51f6f..3cd7f22db 100644 --- a/src/scripts/models/parser_versions/mod_version_parser_v1.coffee +++ b/src/scripts/models/parser_versions/mod_version_parser_v1.coffee @@ -135,7 +135,7 @@ 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)=> + qualifySlug = (name, slug)=> if @_rawData.items[name]? return _.composeSlugs modVersion.modSlug, slug else @@ -148,8 +148,8 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase inputStacks = [] for name in recipeData.input slug = _.slugify name - modVersion.registerSlug slug, name - slug = localizeSlug name, slug + modVersion.registerName slug, name + slug = qualifySlug name, slug inputStacks.push new Stack slug:slug, quantity:0 for c in recipeData.pattern @@ -168,26 +168,25 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase 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 + modVersion.registerName slug, extraData.name + slug = qualifySlug 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 + modVersion.registerName slug, name + slug = qualifySlug name, slug toolStacks.push new Stack slug:slug, quantity:1 attributes = - input: inputStacks - name: item.name - pattern: recipeData.pattern - output: outputStacks - tools: toolStacks + input: inputStacks + output: outputStacks + pattern: recipeData.pattern + tools: toolStacks recipe = new Recipe attributes - item.addRecipe recipe + modVersion.addRecipe recipe return recipe # Un-parsing Methods ########################################################################### diff --git a/src/scripts/models/recipe.coffee b/src/scripts/models/recipe.coffee index 74b3a70f1..7f93bdca1 100644 --- a/src/scripts/models/recipe.coffee +++ b/src/scripts/models/recipe.coffee @@ -13,23 +13,23 @@ Stack = require './stack' module.exports = class Recipe extends BaseModel constructor: (attributes={}, options={})-> - if attributes.item? - attributes.name = attributes.item.name - 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' if not attributes.pattern? then throw new Error 'attributes.pattern is required' - attributes.item ?= null - attributes.output ?= [new Stack slug:_.slugify(attributes.name), quantity:1] - attributes.pattern = @_parsePattern attributes.pattern - attributes.tools ?= [] - options.logEvents ?= false - super attributes, options + 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.output.length is 0 then throw new Error 'attributes.output cannot be empty' + attributes.slug = attributes.output[0].slug + else + throw new Error 'attributes.slug or attributes.output is required' - Object.defineProperties this, - slug: {get:@getSlug} + attributes.pattern = @_parsePattern attributes.pattern + + attributes.modVersion ?= null + attributes.tools ?= [] + options.logEvents ?= false + super attributes, options # Public Methods ############################################################################### @@ -44,17 +44,6 @@ module.exports = class Recipe extends BaseModel return stack.slug - doesProduce: (itemSlug)-> - for stack in @output - return true if stack.slug is itemSlug - return false - - # Property Methods ############################################################################# - - getSlug: -> - return @item.qualifiedSlug if @item? - return @output[0].slug - # Object Overrides ############################################################################# toString: -> diff --git a/test/crafting_plan.test.coffee b/test/crafting_plan.test.coffee index 5afe29cff..9fd899f93 100644 --- a/test/crafting_plan.test.coffee +++ b/test/crafting_plan.test.coffee @@ -43,6 +43,7 @@ describe 'crafting_plan.coffee', -> it 'can craft a single step recipe', -> plan.want.add 'oak_plank' plan.craft() + logger.debug "plan: #{plan}" plan.need.toList().should.eql ['oak_log'] plan.result.toList().should.eql [[4, 'minecraft__oak_plank']] diff --git a/test/mod_pack.test.coffee b/test/mod_pack.test.coffee index aa469fb3a..8cec5be88 100644 --- a/test/mod_pack.test.coffee +++ b/test/mod_pack.test.coffee @@ -23,7 +23,7 @@ describe 'mod_pack.coffee', -> 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' + minecraft.activeModVersion.registerName 'iron_chestplate', 'Iron Chestplate' buildcraft = new Mod slug:'buildcraft', name:'Buildcraft' buildcraft.addModVersion new ModVersion modSlug:buildcraft.slug, version:'6.2.6' diff --git a/test/parser_versions/mod_version_parser_v1.test.coffee b/test/parser_versions/mod_version_parser_v1.test.coffee index c0317a529..ca522094e 100644 --- a/test/parser_versions/mod_version_parser_v1.test.coffee +++ b/test/parser_versions/mod_version_parser_v1.test.coffee @@ -27,7 +27,8 @@ describe 'mod_version_parser_v1.coffee', -> recipe:; input:Alpha; pattern:... .0. ...; recipe:; input:Bravo; pattern:... 0.0 ...;" modVersion = parser.parse recipes - recipes = modVersion._items.charlie._recipes + recipes = modVersion.findRecipes 'test__charlie' + logger.debug "recipes: #{util.inspect(recipes)}" recipes[0].input[0].slug.should.equal 'alpha' recipes[1].input[0].slug.should.equal 'bravo' @@ -67,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 = (s.slug for s in modVersion.findRecipes('test__charlie')[0].input) slugs.should.eql ['alpha', 'bravo', 'test__charlie'] it 'requires an "input" declaration', -> @@ -90,7 +91,7 @@ describe 'mod_version_parser_v1.coffee', -> it 'adds "pattern" when present', -> modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.' - modVersion._items.charlie._recipes[0].pattern.should.equal '... .0. .1.' + modVersion.findRecipes('test__charlie')[0].pattern.should.equal '... .0. .1.' it 'requires a "pattern" declaration', -> func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo' @@ -118,7 +119,7 @@ describe 'mod_version_parser_v1.coffee', -> it 'computes the input stack sizes from the pattern', -> modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern:111 .0. 2.2' - recipe = modVersion._items.charlie._recipes[0] + recipe = modVersion.findRecipes('test__charlie')[0] recipe.input[0].quantity.should.equal 1 recipe.input[1].quantity.should.equal 3 recipe.input[2].quantity.should.equal 2 @@ -134,7 +135,7 @@ describe 'mod_version_parser_v1.coffee', -> it 'adds "quantity" when present', -> modVersion = parser.parse baseText + 'quantity: 2' - modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 2 + modVersion.findRecipes('test__charlie')[0].output[0].quantity.should.equal 2 it 'does not allow a duplicate "quantity" declaration', -> func = -> parser.parse baseText + 'quantity:1; quantity:2' @@ -146,7 +147,7 @@ describe 'mod_version_parser_v1.coffee', -> it 'assumes a quantity of 1 by default', -> modVersion = parser.parse baseText - modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 1 + modVersion.findRecipes('test__charlie')[0].output[0].quantity.should.equal 1 it 'does not allow "quantity" before recipe', -> func = -> parser.parse 'item:Bravo; quantity:12; recipe:;' @@ -159,13 +160,13 @@ describe 'mod_version_parser_v1.coffee', -> it 'adds a single item as the default output', -> modVersion = parser.parse baseText - stack = modVersion._items.bravo._recipes[0].output[0] + stack = modVersion.findRecipes('test__bravo')[0].output[0] 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 = modVersion.findRecipes('test__bravo')[0].output output[0].slug.should.equal 'test__bravo' output[0].quantity.should.equal 1 output[1].slug.should.equal 'test__delta' @@ -192,11 +193,11 @@ describe 'mod_version_parser_v1.coffee', -> it 'can add a single tool', -> modVersion = parser.parse baseText + 'tools: Furnace' - modVersion._items.bravo._recipes[0].tools[0].slug.should.equal 'furnace' + modVersion.findRecipes('test__bravo')[0].tools[0].slug.should.equal 'furnace' it 'can add multiple tools', -> modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace' - tools = modVersion._items.bravo._recipes[0].tools + tools = modVersion.findRecipes('test__bravo')[0].tools tools[0].slug.should.equal 'crafting_table' tools[1].slug.should.equal 'furnace' diff --git a/test/recipe.test.coffee b/test/recipe.test.coffee index cd8b48177..4cf2409cf 100644 --- a/test/recipe.test.coffee +++ b/test/recipe.test.coffee @@ -23,36 +23,31 @@ describe 'recipe.coffee', -> input = [ new Stack(slug:'iron_gear'), new Stack(slug:'gold_ingot', quantity:4) ] pattern = '.1. 101 .1.' - it 'requires a name', -> - expect(-> new Recipe input:input, pattern:pattern).to.throw Error, 'attributes.name is required' - it 'requires input', -> - expect(-> new Recipe name:'Gold Gear', pattern:pattern).to.throw Error, 'attributes.input is required' + expect(-> new Recipe slug:'gold_gear', pattern:pattern).to.throw Error, 'attributes.input is required' it 'requires a pattern', -> - expect(-> new Recipe name:'Gold Gear', input:input).to.throw Error, 'attributes.pattern is required' + expect(-> new Recipe slug:'gold_gear', input:input).to.throw Error, 'attributes.pattern is required' - it 'allows an item to provide required attributes', -> - 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'] + it 'requires either outputs or a slug', -> + f = -> new Recipe input:input, pattern:pattern + expect(f).to.throw 'attributes.slug or attributes.output is required' it 'creates default output', -> - recipe = new Recipe name:'Gold Gear', input:input, pattern:pattern + recipe = new Recipe slug:'gold_gear', input:input, pattern:pattern recipe.output.length.should.equal 1 recipe.output[0].slug.should.equal 'gold_gear' recipe.output[0].quantity.should.equal 1 it 'assigns a default slug', -> - recipe = new Recipe name:'Gold Gear', input:input, pattern:pattern + recipe = new Recipe input:input, pattern:pattern, output:[new Stack slug:'gold_gear'] recipe.slug.should.equal 'gold_gear' describe 'getItemSlugAt', -> beforeEach -> input = [ new Stack(slug:'iron_gear'), new Stack(slug:'gold_ingot', quantity:4) ] - recipe = new Recipe name:'Gold Gear', input:input, pattern:'.1. 101 .1.' + recipe = new Recipe slug:'gold_gear', input:input, pattern:'.1. 101 .1.' it 'returns the proper item for an early slot', -> recipe.getItemSlugAt(1).should.equal 'gold_ingot' @@ -63,26 +58,10 @@ describe 'recipe.coffee', -> it 'returns null for an invalid slot', -> expect(recipe.getItemSlugAt(12)).to.be.null - describe 'doesProduce', -> - - beforeEach -> - input = [ new Stack(slug:'empty_cell'), new Stack(slug:'water_bucket') ] - output = [ new Stack(slug:'water_cell'), new Stack(slug:'bucket') ] - recipe = new Recipe name:'Water Cell', input:input, output:output, pattern:'... .0. .1.' - - it 'returns true when asked for the primary output', -> - recipe.doesProduce('water_cell').should.be.true - - it 'returns true when asked for a secondary output', -> - recipe.doesProduce('bucket').should.be.true - - it 'return false when asked for a non-output', -> - recipe.doesProduce('cake').should.be.false - describe '_parsePattern', -> beforeEach -> - recipe = new Recipe name:'Oak Wood Planks', input:[new Stack slug:'oak_wood'], pattern:'... .0. ...' + recipe = new Recipe slug:'oak_wood_planks', input:[new Stack slug:'oak_wood'], pattern:'... .0. ...' it 'normalizes invalid characters', -> recipe._parsePattern('$$0 #() 010').should.equal '..0 ... 010'