Refactor website with new design & structure

This commit is contained in:
Andrew Miner
2016-04-03 19:30:15 -07:00
parent ec7e8c883d
commit d69585facd
406 changed files with 7411 additions and 37859 deletions
+227
View File
@@ -0,0 +1,227 @@
#
# Crafting Guide - inventory.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
ItemSlug = require './item_slug'
Stack = require './stack'
########################################################################################################################
module.exports = class Inventory extends BaseModel
constructor: (attributes={}, options={})->
super attributes, options
attributes.modPack ?= null
@clear()
if options.clone?
@addInventory options.clone
# Class Methods ################################################################################
@Delimiters =
Item: '.'
Stack: ':'
# Public Methods ###############################################################################
add: (itemSlug, quantity=1)->
return this unless quantity > 0
@_add itemSlug, quantity
@trigger c.event.add, this, itemSlug, quantity
@trigger c.event.change, this
return this
addInventory: (inventory)->
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
@trigger c.event.change, this
return this
clear: (options={})->
@_stacks = {}
@_itemSlugs = []
@trigger c.event.change, this
clone: ->
inventory = new Inventory
inventory.addInventory this
return inventory
each: (callback)->
for itemSlug in @_itemSlugs
stack = @_stacks[itemSlug]
continue unless stack?
callback stack
getSlugs: ->
return @_itemSlugs[..]
hasAtLeast: (itemSlug, quantity=1)->
if quantity is 0 then return true
stack = @_stacks[itemSlug]
return false unless stack?
return stack.quantity >= quantity
localize: ->
if not @modPack? then throw new Error 'localize requires @modPack'
changed = false
newSlugs = []
newStacks = []
for itemSlug in @_itemSlugs
stack = @_stacks[itemSlug]
continue unless stack?
qualifiedSlug = if itemSlug.isQualified then itemSlug else null
if not qualifiedSlug?
qualifiedSlug = @modPack.findItem(itemSlug)?.slug
changed = qualifiedSlug?
if qualifiedSlug?
newSlugs.push qualifiedSlug
newStacks.push new Stack itemSlug:qualifiedSlug, quantity:stack.quantity
else
newSlugs.push itemSlug
newStacks.push stack
if changed
for itemSlug, stack of @_stacks
@stopListening stack
@_itemSlugs = newSlugs
@_stacks = {}
for stack in newStacks
@_stacks[stack.itemSlug] = stack
@listenTo stack, c.event.change, => @trigger c.event.change, this
@_sort()
@trigger c.event.change, this
pop: ->
itemSlug = @_itemSlugs.pop()
return null unless itemSlug?
stack = @_stacks[itemSlug]
delete @_stacks[itemSlug]
@trigger c.event.remove, this, stack.itemSlug, stack.quantity
@trigger c.event.change, this
return stack
quantityOf: (itemSlug)->
stack = @_stacks[itemSlug]
return stack.quantity if stack
return 0
remove: (itemSlug, quantity=null)->
stack = @_stacks[itemSlug]
return this unless stack?
quantity ?= stack.quantity
return this unless quantity > 0
if stack.quantity < quantity
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{itemSlug} in this inventory"
stack.quantity -= quantity
if stack.quantity is 0
@stopListening stack
delete @_stacks[itemSlug]
@_itemSlugs = (s for s in @_itemSlugs when not ItemSlug.equal(s, itemSlug))
@trigger c.event.remove, this, itemSlug, quantity
@trigger c.event.change, this
return this
# 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
throw new Error "expected #{stackText} to have 0 or 1 parts"
if itemSlug.qualified.length > 0
@add itemSlug, quantity
return this
unparse: (options={})->
parts = []
@each (stack)=>
slugText = stack.itemSlug.item
if @modPack?
item = @modPack.findItem ItemSlug.slugify slugText
if item? and item.slug.qualified isnt stack.itemSlug.qualified
slugText = stack.itemSlug.qualified
if stack.quantity is 1
parts.push slugText
else
parts.push "#{stack.quantity}#{Inventory.Delimiters.Item}#{slugText}"
return parts.join Inventory.Delimiters.Stack
# Property Methods #############################################################################
Object.defineProperties @prototype,
isEmpty:
get: -> @_itemSlugs.length is 0
totalQuantity:
get: ->
total = 0
@each (stack)->
total += stack.quantity
return total
# Object Overrides #############################################################################
toString: ->
result = [@constructor.name, " (", @cid, ") {items: ["]
needsDelimiter = false
@each (stack)->
if needsDelimiter then result.push ', '
result.push stack.toString()
needsDelimiter = true
result.push ']'
result.push '}'
return result.join ''
# Private Methods ##############################################################################
_add: (itemSlug, quantity=1)->
return unless itemSlug?
return unless quantity > 0
stack = @_stacks[itemSlug]
if not stack?
stack = new Stack itemSlug:itemSlug, quantity:quantity
@listenTo stack, c.event.change, => @trigger c.event.change, this
@_stacks[itemSlug] = stack
@_itemSlugs.push itemSlug
@_sort()
else
stack.quantity += quantity
_sort: ->
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
@@ -0,0 +1,213 @@
#
# Crafting Guide - inventory.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
EventRecorder = require '../event_recorder'
Inventory = require './inventory'
Item = require './item'
ItemSlug = require './item_slug'
########################################################################################################################
inventory = modPack = null
########################################################################################################################
describe 'inventory.coffee', ->
beforeEach ->
inventory = new Inventory {}, silent:false
inventory.add ItemSlug.slugify('wool'), 4
inventory.add ItemSlug.slugify('string'), 20
inventory.add ItemSlug.slugify('boat')
describe 'add', ->
it 'can add to an empty inventory', ->
inventory.add ItemSlug.slugify('iron_ingot'), 4
stack = inventory._stacks['iron_ingot']
stack.constructor.name.should.equal 'Stack'
stack.itemSlug.qualified.should.equal 'iron_ingot'
stack.quantity.should.equal 4
it 'can augment quantity of existing items', ->
inventory.add ItemSlug.slugify('wool'), 2
inventory.unparse().should.equal 'boat:20.string:6.wool'
it 'can add zero quantity', ->
inventory.add ItemSlug.slugify('wool'), 0
inventory.unparse().should.equal 'boat:20.string:4.wool'
it 'emits the proper events', ->
events = new EventRecorder inventory
inventory.add ItemSlug.slugify('iron_ingot'), 10
events.names.should.eql [c.event.add, c.event.change]
describe 'addInventory', ->
it 'can add to an empty inventory', ->
newInventory = new Inventory
newInventory.addInventory inventory
newInventory.unparse().should.equal 'boat:20.string:4.wool'
it 'can add a mix of new and existing items', ->
newInventory = new Inventory
newInventory.add ItemSlug.slugify('string'), 2
newInventory.addInventory inventory
newInventory.unparse().should.equal 'boat:22.string:4.wool'
describe 'clone', ->
it 'creates an empty inventory from an empty inventory', ->
a = new Inventory
b = a.clone()
b._itemSlugs.should.eql []
it 'faithfully copies an existing inventory', ->
copy = inventory.clone()
copy.unparse().should.equal 'boat:20.string:4.wool'
describe 'each', ->
it 'works with an empty inventory', ->
inventory = new Inventory
result = []
inventory.each (item)-> result.push item.name
result.should.eql []
it 'works when items have only been added', ->
result = []
inventory.each (stack)-> result.push stack.itemSlug.qualified
result.should.eql ['boat', 'string', 'wool']
it 'works when items have been augmented', ->
inventory.add ItemSlug.slugify 'iron_ingot'
inventory.add ItemSlug.slugify 'boat'
inventory.add ItemSlug.slugify('wool'), 2
result = []
inventory.each (stack)-> result.push stack.itemSlug.qualified
result.should.eql ['boat', 'iron_ingot', 'string', 'wool']
describe 'hasAtLeast', ->
it 'works when the item is completely absent', ->
answer = inventory.hasAtLeast 'chicken', 1
answer.should.be.false
it 'always returns true for zero quantity', ->
inventory.hasAtLeast('chicken', 0).should.be.true
inventory.hasAtLeast('wool', 0).should.be.true
it 'works for a quantity above 1', ->
inventory.hasAtLeast('wool', 3).should.be.true
inventory.hasAtLeast('wool', 4).should.be.true
inventory.hasAtLeast('wool', 5).should.be.false
describe 'localize', ->
before ->
modPack =
modSlug:
wool: 'minecraft'
string: 'minecraft'
boat: 'minecraft'
stone_gear: 'buildcraft'
findItem: (slug)->
return slug:new ItemSlug @modSlug[slug.item], slug.item
it 'replaces item slugs with qualified slugs', ->
inventory.add ItemSlug.slugify 'stone_gear'
inventory.modPack = modPack
inventory.localize()
slugs = []
inventory.each (stack)-> slugs.push stack.itemSlug.qualified
slugs.should.eql [
'minecraft__boat'
'buildcraft__stone_gear'
'minecraft__string'
'minecraft__wool'
]
it 'ignores qualified slugs', ->
inventory.add ItemSlug.slugify 'buildcraft__stone_gear'
inventory.modPack = modPack
inventory.localize()
slugs = []
inventory.each (stack)-> slugs.push stack.itemSlug.qualified
slugs.should.eql [
'minecraft__boat'
'buildcraft__stone_gear'
'minecraft__string'
'minecraft__wool'
]
describe 'parse', ->
beforeEach ->
inventory = new Inventory {}, silent:false
it 'ignores an empty string', ->
result = inventory.parse ''
result.unparse().should.eql ''
it 'can parse a single item without quantity', ->
result = inventory.parse 'wool'
result.unparse().should.equal 'wool'
it 'can parse a single item with quantity', ->
result = inventory.parse '4.wool'
result.unparse().should.equal '4.wool'
it 'can parse multiple mixed-type items', ->
result = inventory.parse '4.wool:10.string:boat'
result.unparse().should.equal 'boat:10.string:4.wool'
describe 'pop', ->
it 'returns null for an empty inventory', ->
inventory = new Inventory
result = inventory.pop()
expect(result).to.be.null
it 'completely removes the last item', ->
stack = inventory.pop()
stack.itemSlug.qualified.should.equal 'wool'
stack.quantity.should.equal 4
inventory.unparse().should.equal 'boat:20.string'
it 'triggers the right events', ->
events = new EventRecorder inventory
result = inventory.pop()
events.names.should.eql [c.event.remove, c.event.change]
describe 'remove', ->
it 'does nothing when the item is absent', ->
before = inventory.unparse()
inventory.remove 'foo'
after = inventory.unparse()
before.should.equal after
it 'throws when the item has insufficient quantity', ->
expect(-> inventory.remove('wool', 10)).to.throw Error,
'cannot remove 10: only 4 wool in this inventory'
it 'removes all items by default', ->
inventory.remove 'wool'
expect(inventory._stacks.wool).to.be.empty
it 'removes a quantity above 1', ->
inventory.remove 'wool', 3
inventory._stacks.wool.quantity.should.equal 1
it 'emits the proper events', ->
events = new EventRecorder inventory
inventory.remove 'wool'
events.names.should.eql [c.event.change, c.event.remove, c.event.change]
+90
View File
@@ -0,0 +1,90 @@
#
# Crafting Guide - item.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
ItemSlug = require './item_slug'
Recipe = require './recipe'
{StringBuilder} = require 'crafting-guide-common'
########################################################################################################################
module.exports = class Item extends BaseModel
@Group = Other:'Other'
constructor: (attributes={}, options={})->
if not attributes.name? then throw new Error 'attributes.name is required'
attributes.description ?= null
attributes.group ?= Item.Group.Other
attributes.ignoreDuringCrafting ?= false
attributes.isGatherable ?= false
attributes.modVersion ?= null
attributes.officialUrl ?= null
attributes.slug ?= ItemSlug.slugify attributes.name
attributes.videos ?= []
options.logEvents ?= false
super attributes, options
@on c.event.change + ':modVersion', =>
@_isCraftable = null
@slug.mod = @modVersion?.modSlug
# Public Methods ###############################################################################
compareTo: (that)->
if this.slug isnt that.slug
return if this.slug < that.slug then -1 else +1
if this.name isnt that.name
return if this.name < that.name then -1 else +1
return 0
unparse: ->
ItemParser = require '../parsing/item_parser' # to avoid require cycles
@_parser ?= new ItemParser model:this
return @_parser.unparse()
# Property Methods #############################################################################
getIsCraftable: ->
if not @_isCraftable?
@_isCraftable = false
if @modVersion?
@_isCraftable = @modVersion.hasRecipes @slug
return @_isCraftable
Object.defineProperties @prototype,
isCraftable: {get:@prototype.getIsCraftable}
# Backbone.Model Overrides #####################################################################
parse: (text)->
ItemParser = require '../parsing/item_parser' # to avoid require cycles
@_parser ?= new ItemParser model:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return c.url.itemData modSlug:@slug.mod, itemSlug:@slug.item
# Object Overrides #############################################################################
toString: ->
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, '", '
.push 'slug:"', @slug, '", '
.push '}'
.toString()
+96
View File
@@ -0,0 +1,96 @@
#
# Crafting Guide - item_slug.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
module.exports = class ItemSlug
constructor: ->
@_item = @_mod = null
if arguments.length is 1
parts = _.decomposeSlug arguments[0]
@_mod = _.slugify parts[0]
@item = _.slugify parts[1]
else if arguments.length is 2
@_mod = arguments[0]
@item = arguments[1]
else
throw new Error 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
# Class Methods ################################################################################
@compare: (a, b)->
if a.item isnt b.item
return if a.item < b.item then -1 else +1
if a.mod isnt b.mod
return if a.mod < b.mod then -1 else +1
return 0
@equal: (a, b)->
return true if not a? and not b?
return false unless a? and 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 ###############################################################################
compareTo: (that)->
return ItemSlug.compare this, that
matches: (slug, options={exact:false})->
return false unless slug?
return false unless typeof(slug.matches) is 'function'
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: -> @_mod?
item:
get: -> @_item
set: (newItem)->
if not newItem? then throw new Error 'item is required'
@_item = newItem
@mod = @mod # reset @_qualified
mod:
get: -> @_mod
set: (newMod)->
@_mod = newMod
@_qualified = if @_mod? then _.composeSlugs(@_mod, @_item) else @_item
qualified:
get: -> @_qualified
# Object Overrides #############################################################################
toString: ->
return @_qualified
valueOf: ->
return @_qualified.valueOf()
@@ -0,0 +1,122 @@
#
# Crafting Guide - item_slug.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ItemSlug = require './item_slug'
########################################################################################################################
describe 'item_slug.coffee', ->
describe 'constructor', ->
it 'can handle one argument', ->
slug = new ItemSlug 'alpha'
slug.item.should.equal 'alpha'
expect(slug.mod).to.be.null
slug.qualified.should.equal 'alpha'
it 'can handle two arguments', ->
slug = new ItemSlug 'alpha', 'bravo'
slug.mod.should.equal 'alpha'
slug.item.should.equal 'bravo'
slug.qualified.should.equal 'alpha__bravo'
it 'throws with zero arguments', ->
f = -> new ItemSlug
expect(f).to.throw Error, 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
it 'throws with more arguments', ->
f = -> new ItemSlug 'alpha', 'bravo', 'charlie'
expect(f).to.throw Error, 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
describe 'ItemSlug.compare', ->
it 'sorts by item when both are qualified in the same mod', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'charlie', 'bravo'
ItemSlug.compare(a, b).should.equal -1
ItemSlug.compare(b, a).should.equal +1
it 'sorts by item when not qualified', ->
a = new ItemSlug 'alpha'
b = new ItemSlug 'bravo'
ItemSlug.compare(a, b).should.equal -1
ItemSlug.compare(b, a).should.equal +1
describe 'ItemSlug.equal', ->
it 'requires both to have the same mod', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'alpha', 'charlie'
c = new ItemSlug 'alpha', 'bravo'
ItemSlug.equal(a, b).should.be.false
ItemSlug.equal(a, c).should.be.true
it 'requires both to have the same item', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'alpha', 'charlie'
c = new ItemSlug 'alpha', 'bravo'
ItemSlug.equal(a, b).should.be.false
ItemSlug.equal(a, c).should.be.true
describe 'ItemSlug.slugify', ->
it 'can slugify a pure name', ->
slug = ItemSlug.slugify 'Alpha Bravo (Charlie)'
slug.item.should.equal 'alpha_bravo_charlie'
expect(slug.mod).to.be.null
it 'can slugify a simple item slug', ->
slug = ItemSlug.slugify 'alpha_bravo_charlie'
slug.item.should.equal 'alpha_bravo_charlie'
expect(slug.mod).to.be.null
it 'can slugify a fully-qualified slug', ->
slug = ItemSlug.slugify 'alpha_bravo__charlie_delta'
slug.mod.should.equal 'alpha_bravo'
slug.item.should.equal 'charlie_delta'
describe 'matches', ->
it 'ignores mod when either is unqualified', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'bravo'
c = new ItemSlug 'charlie'
a.matches(b).should.be.true
a.matches(c).should.be.false
it 'observes differences in mod when all are qualified', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'charlie', 'bravo'
c = new ItemSlug 'delta', 'echo'
d = new ItemSlug 'alpha', 'bravo'
a.matches(b).should.be.false
a.matches(c).should.be.false
a.matches(d).should.be.true
describe 'isQualified', ->
it 'returns true only when the mod slug is set', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'charlie'
a.isQualified.should.be.true
b.isQualified.should.be.false
describe '[]', ->
it 'allows slugs as a key', ->
slug = new ItemSlug 'alpha', 'bravo'
data = {}
data[slug] = 'foo'
data['alpha__bravo'].should.equal 'foo'
data[slug].should.equal 'foo'
+224
View File
@@ -0,0 +1,224 @@
#
# Crafting Guide - mod.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
########################################################################################################################
module.exports = class Mod extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.slug? then throw new Error 'attributes.slug is required'
attributes.author ?= ''
attributes.description ?= ''
attributes.documentationUrl ?= null
attributes.downloadUrl ?= null
attributes.homePageUrl ?= null
attributes.modPack ?= null
attributes.name ?= ''
super attributes, options
@_activeModVersion = null
@_activeVersion = null
@_modVersions = []
@_tutorials = []
@once c.event.sync, => @_verifyActiveModVersion()
# Class Methods ##################################################################################
@Version: Version =
None: 'none'
Latest: 'latest'
# Public Methods #################################################################################
compareTo: (that)->
thisRequired = this.slug in c.requiredMods
thatRequired = that.slug in c.requiredMods
if thisRequired isnt thatRequired
return -1 if thisRequired
return +1 if thatRequired
else if this.slug isnt that.slug
return if this.slug < that.slug then -1 else +1
return 0
# Property Methods #############################################################################
Object.defineProperties @prototype,
activeModVersion:
get: -> @_activeModVersion
activeVersion:
get: ->
return @_activeVersion
set: (version)->
return if version is @_activeVersion
version ?= Mod.Version.None
if version is Mod.Version.Latest then version = _.last(@_modVersions).version
if version is Mod.Version.None
@_activeVersion = version
@_activateModVersion null
@trigger c.event.change + ':activeVersion', this, @_activeVersion
@trigger c.event.change, this
else
for modVersion in @_modVersions
if version is modVersion.version
@_activateModVersion modVersion
break
@_activeVersion = version
@trigger c.event.change + ':activeVersion', this, @_activeVersion
@trigger c.event.change, this
enabled:
get: -> @_activeModVersion?
modVersions:
get: -> @_modVersions[..]
tutorials:
get: -> @getAllTutorials()
# Item Methods #################################################################################
eachItem: (callback)->
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
effectiveModVersion.eachItem callback
findItem: (slug, options={})->
options.includeDisabled ?= false
options.enableAsNeeded ?= false
if not options.includeDisabled
return unless @_activeModVersion?
return @_activeModVersion.findItem slug
else
for modVersion in @_modVersions
modVersion.fetch()
item = modVersion.findItem slug
if item?
if options.enableAsNeeded then @setActiveVersion modVersion.version
return item
return null
findItemByName: (name)->
return unless @_activeModVersion?
@_activeModVersion.findItemByName name
# ModVersion Methods ###########################################################################
addModVersion: (modVersion)->
return unless modVersion?
return if @_modVersions.indexOf(modVersion) isnt -1
@_modVersions.push modVersion
@listenTo modVersion, c.event.change, => @trigger c.event.change, this
modVersion.mod = this
@trigger c.event.add + ':modVersion', modVersion, this
@trigger c.event.change + ':version', modVersion, this
@trigger c.event.change, this
if not @activeVersion? then @activeVersion = modVersion.version
if modVersion.version is @_activeVersion then @_activateModVersion modVersion
return this
eachModVersion: (callback)->
for modVersion in @_modVersions
callback modVersion
getModVersion: (version)->
return null if version is Mod.Version.None
return @_modVersions[0] if version is Mod.Version.Latest
for modVersion in @_modVersions
return modVersion if modVersion.version is version
return null
# Name Methods #################################################################################
eachName: (callback)->
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
effectiveModVersion.eachName callback
findName: (itemSlug)->
return unless @_activeModVersion?
@_activeModVersion.findName itemSlug
# Recipe Methods ###############################################################################
eachRecipe: (callback)->
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
effectiveModVersion.eachRecipe callback
findRecipes: (itemSlug, result=[], options={})->
options.alwaysFromOwningMod ?= false
if @_activeModVersion?
return @_activeModVersion.findRecipes itemSlug, result, options
else if options.alwaysFromOwningMod and itemSlug.mod is @slug
return @getModVersion(Mod.Version.Latest).findRecipes itemSlug, result, options
return null
# Tutorial Methods #############################################################################
addTutorial: (tutorial)->
return unless tutorial?
if @getTutorial(tutorial.slug)? then throw new Error "duplicate tutorial: #{tutorial.name}"
@_tutorials.push tutorial
tutorial.modSlug = @slug
getAllTutorials: ->
return @_tutorials[..]
getTutorial: (tutorialSlug)->
for tutorial in @_tutorials
return tutorial if tutorial.slug is tutorialSlug
return null
# Backbone.Model Overrides #####################################################################
parse: (text)->
ModParser = require '../parsing/mod_parser' # to avoid require cycles
@_parser ?= new ModParser model:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return c.url.modData modSlug:@slug
# Private Methods ##############################################################################
_activateModVersion: (modVersion)->
if @_activeModVersion? then @stopListening @_activeModVersion
@_activeModVersion = modVersion
@trigger c.event.change + ':activeModVersion', this, @_activeModVersion
logger.verbose => "#{@slug} switched to version #{@_activeVersion}"
if @_activeModVersion?
@listenTo @_activeModVersion, 'all', -> @trigger.apply this, arguments
_verifyActiveModVersion: ->
if (@_activeVersion isnt Version.None) and (not @_activeModVersion?)
logger.warning => "#{@slug} no longer has a version #{@_activeVersion}, using latest instead"
@activeVersion = Version.Latest
+30
View File
@@ -0,0 +1,30 @@
#
# Crafting Guide - mod.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Mod = require './mod'
########################################################################################################################
mod = null
########################################################################################################################
describe 'mod.coffee', ->
beforeEach -> mod = new Mod name:'Test', slug:'test'
describe 'compareTo', ->
it 'lists required mods first', ->
minecraft = new Mod name:'Minecraft', slug:'minecraft'
mod.compareTo(minecraft).should.equal +1
minecraft.compareTo(mod).should.equal -1
it 'sorts by name second', ->
buildcraft = new Mod name:'Buildcraft', slug:'buildcraft'
mod.compareTo(buildcraft).should.equal +1
buildcraft.compareTo(mod).should.equal -1
+176
View File
@@ -0,0 +1,176 @@
#
# Crafting Guide - mod_pack.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
Inventory = require './inventory'
ModVersionParser = require '../parsing/mod_version_parser'
Recipe = require './recipe'
########################################################################################################################
module.exports = class ModPack extends BaseModel
constructor: (attributes={}, options={})->
super attributes, options
@_mods = []
@_cache = {}
@on c.event.change, => @_cache = {}
# Item Methods #################################################################################
findItem: (itemSlug, options={})->
options.includeDisabled ?= false
key = "#{itemSlug}-#{options.includeDisabled}"
@_cache.itemBySlug ?= {}
item = @_cache.itemBySlug[key]
return item if item?
if itemSlug.isQualified
mod = @getMod itemSlug.mod
if mod?
item = mod.findItem itemSlug, options
if not item?
for mod in @_mods
continue unless mod.enabled or options.includeDisabled
item = mod.findItem itemSlug, options
break if item?
if item?
@_cache.itemBySlug[key] = item
return item
findItemByName: (name, options={})->
options.enableAsNeeded ?= false
options.includeDisabled = true if options.enableAsNeeded
for mod in @_mods
continue unless mod.enabled or options.includeDisabled
item = mod.findItemByName name, options
return item if item?
return null
findItemDisplay: (itemSlug)->
if not itemSlug? then throw new Error 'itemSlug is required'
result = {slug:itemSlug}
item = @findItem itemSlug, includeDisabled:true
if item?
result.itemName = item.name
result.itemSlug = item.slug.item
result.modSlug = item.slug.mod
result.modVersion = item.modVersion.version
else
result.itemName = @findName itemSlug, includeDisabled:true
result.itemSlug = itemSlug.item
result.modSlug = @_mods[0].slug
result.modVersion = @_mods[0].activeVersion
craftingUrlInventory = new Inventory modPack:this
if item?.multiblock?
craftingUrlInventory.addInventory item.multiblock.inventory
else
craftingUrlInventory.add itemSlug
result.craftingUrl = c.url.crafting inventoryText:craftingUrlInventory.unparse()
result.iconUrl = c.url.itemIcon result
result.itemUrl = c.url.item result
result.modName = @getMod(result.modSlug).name
return result
qualifySlug: (itemSlug)->
return itemSlug if itemSlug.isQualified
item = @findItem itemSlug
return item.slug if item?
return itemSlug
# Mod Methods ##################################################################################
addMod: (mod)->
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, c.event.change, (modVersion)=> @_onModVersionLoaded modVersion
@trigger c.event.add + ':mod', mod, this
@_mods.sort (a, b)-> a.compareTo b
@trigger c.event.sort + ':mod', this
@trigger c.event.change, this
return this
eachMod: (callback)->
for mod in @_mods
callback mod
getMod: (slug)->
for mod in @_mods
return mod if mod.slug is slug
return null
getAllMods: ->
return @_mods[..]
# Name Methods #################################################################################
findName: (slug, options={})->
options.includeDisabled ?= false
for mod in @_mods
continue unless mod.enabled or options.includeDisabled
name = mod.findName slug
return name if name
return null
# Recipe Methods ###############################################################################
findRecipes: (itemSlug, options={})->
options.alwaysFromOwningMod ?= false
return null unless itemSlug?
key = "#{itemSlug}-#{options.alwaysFromOwningMod}"
@_cache.recipesBySlug ?= {}
result = @_cache.recipesBySlug[key]
return result if result?
result = []
for mod in @_mods
if not mod.enabled
owningMod = itemSlug.isQualified and (itemSlug.mod is mod.slug)
continue unless owningMod and options.alwaysFromOwningMod
mod.findRecipes itemSlug, result, options
@_cache.recipesBySlug[key] = result
return if result.length > 0 then result else null
# Object Overrides #############################################################################
toString: ->
return "ModPack (#{@cid}) {modVersions:«#{@_mods.length} items»}"
# Private Methods ##############################################################################
_onModVersionLoaded: (modVersion)->
working = true
@eachMod (mod)->
mod.eachModVersion (modVersion)->
working = working and (modVersion.isUnloaded or modVersion.isLoading)
return if working
@trigger c.event.change, this
@trigger c.event.sync, this
+103
View File
@@ -0,0 +1,103 @@
#
# Crafting Guide - mod_pack.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Item = require './item'
ItemSlug = require './item_slug'
Mod = require './mod'
ModPack = require './mod_pack'
ModVersion = require './mod_version'
########################################################################################################################
buildcraft = industrialCraft = minecraft = modPack = null
########################################################################################################################
describe 'mod_pack.coffee', ->
beforeEach ->
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.registerName ItemSlug.slugify('iron_chestplate'), 'Iron Chestplate'
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:'Wrench', recipes:['']
buildcraft.activeVersion = Mod.Version.None
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
modPack.addMod minecraft
modPack.addMod buildcraft
modPack.addMod industrialCraft
describe 'findItem', ->
it 'can find an item by partial slug', ->
item = modPack.findItem ItemSlug.slugify 'wool'
item.slug.qualified.should.equal 'minecraft__wool'
it 'can find an item by full slug', ->
item = modPack.findItem ItemSlug.slugify '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 ItemSlug.slugify '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 ItemSlug.slugify 'wrench'
item.name.should.equal 'Wrench'
item.modVersion.mod.name.should.equal 'Buildcraft'
describe 'findItemByName', ->
it 'finds the requested item', ->
item = modPack.findItemByName 'Bed'
item.name.should.equal 'Bed'
it 'ignores disabled mod versions', ->
item = modPack.findItemByName 'Stone Gear'
expect(item).to.be.null
describe 'findItemDisplay', ->
it 'returns all data for a regular Minecraft item', ->
display = modPack.findItemDisplay ItemSlug.slugify 'bed'
display.iconUrl.should.equal '/data/minecraft/items/bed/icon.png'
display.itemUrl.should.equal '/browse/minecraft/bed/'
display.itemName.should.equal 'Bed'
display.modSlug.should.equal 'minecraft'
it 'returns all data for an item in an enabled mod', ->
buildcraft.activeVersion = '6.2.6'
display = modPack.findItemDisplay ItemSlug.slugify 'stone_gear'
display.iconUrl.should.equal '/data/buildcraft/items/stone_gear/icon.png'
display.itemUrl.should.equal '/browse/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 ItemSlug.slugify 'iron_chestplate'
display.iconUrl.should.equal '/data/minecraft/items/iron_chestplate/icon.png'
display.itemUrl.should.equal '/browse/minecraft/iron_chestplate/'
display.itemName.should.equal 'Iron Chestplate'
display.modSlug.should.equal 'minecraft'
+196
View File
@@ -0,0 +1,196 @@
#
# Crafting Guide - mod_version.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
Item = require './item'
ItemSlug = require './item_slug'
Recipe = require './recipe'
########################################################################################################################
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 = {}
@_items = {}
@_names = {}
@_recipes = {}
@_slugs = []
# Public Methods ###############################################################################
compareTo: (that)->
if this.mod? and that.mod?
return this.mod.compareTo that.mod
if this.modSlug isnt that.modSlug
return if this.modSlug < that.modSlug then -1 else +1
return 0
sort: ->
@_slugs.sort (a, b)-> ItemSlug.compare a, b
# Item Methods #################################################################################
addItem: (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
allItemsInGroup: (group)->
result = []
@eachItemInGroup group, (item)-> result.push item
return null if result.length is 0
return result
eachItem: (callback)->
for slug in @_slugs
item = @findItem slug
continue unless item?
callback item
return this
eachItemInGroup: (group, callback)->
itemMap = @_groups[group]
return unless itemMap?
items = _.values(itemMap).sort (a, b)-> a.compareTo b
for item in items
callback item
findItem: (itemSlug)->
return @_items[itemSlug.item]
findItemByName: (name)->
for itemSlug, item of @_items
return item if item.name is name
return null
# Group Methods ################################################################################
getAllGroups: ->
result = []
@eachGroup (group)-> result.push group
return result
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.item], slug
return this
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 ###############################################################################
addRecipe: (recipe)->
return unless recipe?
recipe.modVersion = this
if @_recipes[recipe.slug]? then throw new Error "duplicate recipe: #{recipe.slug}"
@_recipes[recipe.slug] = recipe
return this
eachRecipe: (callback)->
recipes = _.values(@_recipes).sort (a, b)-> Recipe.compareFor a, b
for recipe in recipes
callback recipe
return this
findRecipes: (itemSlug, result=[], options={})->
options.onlyPrimary ?= false
options.forCrafting ?= false
primaryRecipes = []
otherRecipes = []
for recipe in _.values @_recipes
continue unless recipe.isConditionSatisfied()
continue unless recipe.hasAllTools()
continue if options.forCrafting and recipe.ignoreDuringCrafting
if recipe.itemSlug.matches itemSlug
primaryRecipes.push recipe
else if recipe.produces itemSlug
otherRecipes.push recipe
for recipe in primaryRecipes
result.push recipe
if not options.onlyPrimary
for recipe in otherRecipes
result.push recipe
return result
findExternalRecipes: ->
result = {}
for k, recipe of @_recipes
continue if recipe.itemSlug.isQualified
recipeList = result[recipe.itemSlug]
if not recipeList then recipeList = result[recipe.itemSlug] = []
recipeList.push recipe
return result
hasRecipes: (itemSlug)->
for k, recipe of @_recipes
return true if recipe.produces itemSlug
return false
# Backbone.Model Overrides #####################################################################
parse: (text)->
ModVersionParser = require '../parsing/mod_version_parser' # to avoid require cycles
@_parser ?= new ModVersionParser model:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return c.url.modVersionData modSlug:@modSlug, modVersion:@version
# Object Overrides #############################################################################
toString: ->
return "ModVersion (#{@cid}) {
modSlug:#{@modSlug}, version:#{@version}, items:«#{@_slugs.length} items»
}"
@@ -0,0 +1,101 @@
#
# Crafting Guide - mod_version.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Item = require './item'
ItemSlug = require './item_slug'
ModVersion = require './mod_version'
########################################################################################################################
modVersion = null
########################################################################################################################
describe 'mod_version.coffee', ->
beforeEach ->
modVersion = new ModVersion modSlug:'test', version:'0.0'
modVersion.addItem new Item name:'underscore', group:'punctuation'
modVersion.addItem new Item name:'bravo', group:'letter'
modVersion.addItem new Item name:'alpha', group:'letter'
modVersion.addItem new Item name:'one', group:'number'
modVersion.addItem new Item name:'two', group:'number'
describe 'constructor', ->
it 'requires a mod slug', ->
expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.modSlug is required'
it 'requires a mod version', ->
expect(-> new ModVersion modSlug:'test').to.throw Error, 'attributes.version is required'
describe 'addItem', ->
it 'refuses to add duplicates', ->
modVersion.addItem new Item name:'Wool'
expect(-> modVersion.addItem new Item name:'Wool').to.throw Error, 'duplicate item for Wool'
it 'adds an item indexed by its slug', ->
modVersion.addItem new Item name:'Wool'
modVersion._items.wool.name.should.equal 'Wool'
it 'sets the modVersion', ->
modVersion.addItem new Item name:'Wool'
modVersion._items.wool.modVersion.should.equal modVersion
describe 'eachGroup', ->
it 'returns all the groups in order', ->
groupNames = []
modVersion.eachGroup (groupName)-> groupNames.push groupName
groupNames.should.eql ['letter', 'number', 'punctuation']
describe 'eachItemInGroup', ->
it 'returns immediately for unknown group', ->
slugs = []
modVersion.eachItemInGroup 'foobar', (item)-> slugs.push item.slug.qualified
slugs.should.eql []
it 'calls callback for exactly the items in a group in order', ->
slugs = []
modVersion.eachItemInGroup 'letter', (item)-> slugs.push item.slug.qualified
slugs.should.eql ['test__alpha', 'test__bravo']
slugs = []
modVersion.eachItemInGroup 'number', (item)-> slugs.push item.slug.qualified
slugs.should.eql ['test__one', 'test__two']
describe 'findItemByName', ->
it 'locates items by slugified name', ->
modVersion.addItem new Item name:'Crafting Table'
modVersion.findItemByName('Crafting Table').slug.qualified.should.equal 'test__crafting_table'
describe 'findRecipes', ->
beforeEach ->
modVersion = new ModVersion modSlug:'test', version:'1.0'
modVersion.parse """
schema:1
item: Cake
recipe:; input: Milk, Sugar, Egg, Wheat; pattern: 000 121 333; extras: 3 Bucket
recipe:; input: Milk, Cocoa Beans, Egg, Wheat; pattern: 000 121 333; extras: 3 Bucket
recipe:; input: Cake Slice; pattern: 000 000 000; onlyIf: item Cake Slice
item: Bucket
recipe:; input: Iron Ingot; pattern: ... 0.0 .0.
"""
it 'finds all recipes which list item as output', ->
recipes = modVersion.findRecipes ItemSlug.slugify('test__bucket')
(r.output[0].itemSlug.item for r in recipes).sort().should.eql ['bucket', 'cake', 'cake']
it 'skip recipes whose conditions are not met', ->
recipes = modVersion.findRecipes ItemSlug.slugify('test__cake')
recipes.length.should.equal 2
+85
View File
@@ -0,0 +1,85 @@
#
# Crafting Guide - multiblock.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
Inventory = require './inventory'
########################################################################################################################
module.exports = class Multiblock extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.input? then throw new Error 'attributes.input is required'
if not attributes.layers? then throw new Error 'attributes.layers is required'
if attributes.layers.length < 1 then throw new Error 'attributes.layers.length must be >= 1'
super attributes, options
@_analyzePattern()
# Public Methods ###############################################################################
getStackAt: (x, y, z)->
value = @_stackCache[y]?[z]?[x]
return null if value is undefined
return value
getLayerInventory: (y)->
@_layerInventories ?= []
result = @_layerInventories[y]
if not result? then result = @_layerInventories[y] = new Inventory
return result
# Property Methods #############################################################################
Object.defineProperties @prototype,
depth:
get: -> @_depth
height:
get: -> @_height
inventory:
get: -> @_inventory
width:
get: -> @_width
# Private Methods ##############################################################################
_analyzeLayer: (layer, y, stackCacheLayer)->
rows = layer.split ' '
@_depth = Math.max @_depth, rows.length
for row in rows
@_width = Math.max @_width, row.length
stackCacheRow = []
stackCacheLayer.push stackCacheRow
@_analyzeRow row, stackCacheRow, @getLayerInventory(y)
_analyzePattern: ->
@_depth = @_height = @_width = 0
@_inventory = new Inventory
@_stackCache = []
for layer, y in @layers
stackCacheLayer = []
@_stackCache.push stackCacheLayer
@_analyzeLayer layer, y, stackCacheLayer
@_height = @layers.length
_analyzeRow: (row, stackCacheRow, layerInventory)->
for cell, x in row.split ''
index = parseInt cell
stack = null
if not _.isNaN index
stack = @input[index]
@_inventory.add stack.itemSlug, stack.quantity
layerInventory.add stack.itemSlug, stack.quantity
stackCacheRow.push stack
@@ -0,0 +1,85 @@
#
# Crafting Guide - multiblock.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ItemSlug = require './item_slug'
Mod = require './mod'
Multiblock = require './multiblock'
Stack = require './stack'
########################################################################################################################
describe 'multiblock.coffee', ->
describe 'with a single block type', ->
beforeEach ->
@input = [ new Stack itemSlug:ItemSlug.slugify('cobblestone'), quantity:1 ]
it 'correctly parses a 1x1x1 cube', ->
model = new Multiblock input:@input, layers:['0']
model.depth.should.equal 1
model.height.should.equal 1
model.width.should.equal 1
model.getStackAt(0, 0, 0).toString().should.equal '1 cobblestone'
it 'correctly parses a solid 3x3x3 cube', ->
model = new Multiblock input:@input, layers:['000 000 000', '000 000 000', '000 000 000']
model.depth.should.equal 3
model.height.should.equal 3
model.width.should.equal 3
for x in [0..2]
for y in [0..2]
for z in [0..2]
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
it 'correctly parses a hollow 3x3x3 cube', ->
model = new Multiblock input:@input, layers:['000 000 000', '000 0.0 000', '000 000 000']
model.depth.should.equal 3
model.height.should.equal 3
model.width.should.equal 3
for x in [0..2]
for y in [0..2]
for z in [0..2]
if x isnt 1 or y isnt 1 or z isnt 1
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
else
expect(model.getStackAt(x, y, z)).to.be.null
it 'correctly parses a 3x2x3 pyramid', ->
model = new Multiblock input:@input, layers:['000 000 000', '.. .0 ..']
model.depth.should.equal 3
model.height.should.equal 2
model.width.should.equal 3
for y in [0..1]
for z in [0..2]
for x in [0..2]
if y is 1 and (x isnt 1 or z isnt 1)
expect(model.getStackAt(x, y, z)).to.be.null
else
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
describe 'with multiple block types', ->
beforeEach ->
@input = [
new Stack itemSlug:ItemSlug.slugify('cobblestone'), quantity:1
new Stack itemSlug:ItemSlug.slugify('stone'), quantity:1
new Stack itemSlug:ItemSlug.slugify('oak wood'), quantity:1
]
it 'correctly parses a 1x3x1 column of different types', ->
model = new Multiblock input:@input, layers:['0', '1', '2']
model.depth.should.equal 1
model.height.should.equal 3
model.width.should.equal 1
model.getStackAt(0, 0, 0).toString().should.equal '1 cobblestone'
model.getStackAt(0, 1, 0).toString().should.equal '1 stone'
model.getStackAt(0, 2, 0).toString().should.equal '1 oak_wood'
+252
View File
@@ -0,0 +1,252 @@
#
# Crafting Guide - recipe.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
ItemSlug = require './item_slug'
Stack = require './stack'
{StringBuilder} = require 'crafting-guide-common'
########################################################################################################################
module.exports = class Recipe extends BaseModel
constructor: (attributes={}, options={})->
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.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.itemSlug = attributes.output[0].itemSlug
else
throw new Error 'attributes.itemSlug or attributes.output is required'
attributes.pattern = @_parsePattern attributes.pattern
attributes.condition ?= null
attributes.ignoreDuringCrafting ?= false
attributes.modVersion ?= null
attributes.tools ?= []
options.logEvents ?= false
super attributes, options
@_computeQuantities attributes.pattern
@on c.event.change + ':modVersion', => @_slug = null
@on c.event.change + ':pattern', => @_patternCache = null
# Class Methods ################################################################################
@compareFor: (a, b, itemSlug)->
if itemSlug?
aValue = a.itemSlug.matches itemSlug
bValue = b.itemSlug.matches itemSlug
if aValue isnt bValue
return -1 if aValue
return +1 if bValue
aValue = a.getQuantityProduced itemSlug
bValue = b.getQuantityProduced itemSlug
if aValue isnt bValue
return if aValue > bValue then -1 else +1
return 0
# Public Methods ###############################################################################
getStackAtSlot: (patternSlot)->
trueIndex = 0:0, 1:1, 2:2, 3:4, 4:5, 5:6, 6:8, 7:9, 8:10
patternDigit = @pattern[trueIndex[patternSlot]]
return null unless patternDigit?
return null unless patternDigit.match /[0-9]/
stack = @input[parseInt(patternDigit)]
return null unless stack?
return stack
getQuantityProduced: (itemSlug)->
total = 0
for stack in @output
if stack.itemSlug.matches itemSlug
total += stack.quantity
return total
getQuantityRequired: (itemSlug)->
total = 0
for stack, index in @input
if ItemSlug.equal stack.itemSlug, itemSlug
total += @_quantities[index] * stack.quantity
return total
hasAllTools: (modPack)->
modPack ?= @modVersion?.mod?.modPack
return true unless modPack?
for stack in @tools
return false unless modPack.findItem stack.itemSlug
return true
isConditionSatisfied: (modPack)->
return true unless @condition?
modPack ?= @modVersion?.mod?.modPack
result = false
if @condition.verb is 'item'
if modPack?.findItemByName(@condition.noun)?
result = true
else if @condition.verb is 'mod'
modPack.eachMod (mod)->
if mod.name is @condition.noun
result = true
if @condition.inverted then result = not result
return result
isPassThroughFor: (itemSlug)->
amountCreated = 0
for stack in @output
if stack.itemSlug.matches itemSlug
amountCreated += stack.quantity
for stack in @input
if stack.itemSlug.matches itemSlug
amountCreated -= stack.quantity
return amountCreated <= 0
produces: (itemSlug)->
if not @_produces?
@_produces = {}
for stack in @output
actuallyProduces = not @isPassThroughFor stack.itemSlug
@_produces[stack.itemSlug.qualified] = actuallyProduces
result = @_produces[itemSlug.qualified] or @_produces[itemSlug.item]
return result
requires: (itemSlug)->
for stack in @input
if stack.itemSlug.matches itemSlug
return true
return false
requiresTool: (itemSlug)->
for stack in @tools
if stack.itemSlug.matches itemSlug
return true
# Property Methods #############################################################################
Object.defineProperties @prototype,
slug:
get: ->
if not @_slug?
builder = new StringBuilder
delimiterNeeded = false
for stack in @input
if delimiterNeeded then builder.push ','
delimiterNeeded = true
if stack.quantity > 1 then builder.push stack.quantity, ' '
builder.push stack.itemSlug.qualified
builder.push '>'
builder.push @pattern
builder.push '>'
for stack in @tools
builder.push stack.itemSlug.qualified
builder.push '>'
delimiterNeeded = false
for stack in @output
if delimiterNeeded then builder.push ','
delimiterNeeded = true
if stack.quantity > 1 then builder.push stack.quantity, ' '
builder.push stack.itemSlug.qualified
@_slug = builder.toString()
return @_slug
# Object Overrides #############################################################################
toString: ->
result = [@constructor.name, " (", @cid, ") { name:", @name]
result.push ", input:["
needsDelimiter = false
for stack in @input
if needsDelimiter then result.push ', '
result.push @getQuantityRequired stack.itemSlug
result.push ' '
result.push stack.itemSlug
needsDelimiter = true
result.push ']'
result.push ", output:["
needsDelimiter = false
for stack in @output
if needsDelimiter then result.push ', '
result.push @getQuantityProduced stack.itemSlug
result.push ' '
result.push stack.itemSlug
needsDelimiter = true
result.push ']'
if @tools.length > 0
result.push ", tools:["
needsDelimiter = false
for stack in @tools
if needsDelimiter then result.push ', '
result.push stack.toString()
needsDelimiter = true
result.push ']'
result.push '}'
return result.join ''
# Private Methods ##############################################################################
_computeQuantities: (pattern)->
quantityMap = {}
for c in pattern.split ''
continue if c is '.'
continue if c is ' '
if quantityMap[c]?
quantityMap[c] += 1
else
quantityMap[c] = 1
@_quantities = []
for i in [0...@input.length]
@_quantities.push quantityMap["#{i}"]
_parsePattern: (pattern)->
return unless pattern?
pattern = pattern.replace /\ /g, ''
return if pattern.length is 0
pattern = pattern.replace /[^0-9]/g, '.'
array = pattern.split ''
array = array[0...9]
while array.length isnt 9
array.push '.'
pattern = array.join ''
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
return pattern
+90
View File
@@ -0,0 +1,90 @@
#
# Crafting Guide - recipe.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Item = require './item'
ItemSlug = require './item_slug'
Recipe = require './recipe'
Stack = require './stack'
########################################################################################################################
input = output = pattern = recipe = null
########################################################################################################################
describe 'recipe.coffee', ->
describe 'constructor', ->
beforeEach ->
input = [
new Stack(itemSlug:new ItemSlug('iron_gear')),
new Stack(itemSlug:new ItemSlug('gold_ingot'), quantity:4)
]
pattern = '.1. 101 .1.'
it 'requires input', ->
expect(-> new Recipe slug:'gold_gear', pattern:pattern).to.throw Error, 'attributes.input is required'
it 'requires a pattern', ->
expect(-> new Recipe slug:'gold_gear', input:input).to.throw Error, 'attributes.pattern is required'
it 'requires either outputs or a slug', ->
f = -> new Recipe input:input, pattern:pattern
expect(f).to.throw 'attributes.itemSlug or attributes.output is required'
it 'creates default output', ->
recipe = new Recipe itemSlug:ItemSlug.slugify('gold_gear'), input:input, pattern:pattern
recipe.output.length.should.equal 1
recipe.output[0].itemSlug.qualified.should.equal 'gold_gear'
recipe.output[0].quantity.should.equal 1
it 'assigns a default slug', ->
recipe = new Recipe input:input, pattern:pattern, output:[new Stack itemSlug:ItemSlug.slugify('gold_gear')]
recipe.itemSlug.qualified.should.equal 'gold_gear'
describe 'getStackAtSlot', ->
beforeEach ->
input = [
new Stack itemSlug:ItemSlug.slugify('iron_gear')
new Stack itemSlug:ItemSlug.slugify('gold_ingot'), quantity:4
]
recipe = new Recipe itemSlug:'gold_gear', input:input, pattern:'.1. 101 .1.'
it 'returns the proper item for an early slot', ->
stack = recipe.getStackAtSlot(1)
stack.itemSlug.qualified.should.equal 'gold_ingot'
stack.quantity.should.equal 4
it 'returns the proper item for a late slot', ->
stack = recipe.getStackAtSlot(4)
stack.itemSlug.qualified.should.equal 'iron_gear'
stack.quantity.should.equal 1
it 'returns null for an invalid slot', ->
expect(recipe.getStackAtSlot(12)).to.be.null
describe '_parsePattern', ->
beforeEach ->
recipe = new Recipe
itemSlug: 'oak_wood_planks',
input: [new Stack itemSlug:new ItemSlug('oak_wood')],
pattern:'... .0. ...'
it 'normalizes invalid characters', ->
recipe._parsePattern('$$0 #() 010').should.equal '..0 ... 010'
it 'removes extra characters', ->
recipe._parsePattern('000 000 000 000').should.equal '000 000 000'
it 'fills in missing characters', ->
recipe._parsePattern('000000').should.equal '000 000 ...'
it 'fills in spaces', ->
recipe._parsePattern('000000000').should.equal '000 000 000'
@@ -0,0 +1,33 @@
#
# Crafting Guide - simple_stack.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ItemSlug = require './item_slug'
########################################################################################################################
module.exports = class Stack
constructor: (attributes={}, options={})->
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
attributes.quantity ?= 1
@itemSlug = attributes.itemSlug
@quantity = attributes.quantity
# Class Methods ################################################################################
@compare: (a, b)->
if a? and not b? then return -1
if not a? and b? then return +1
if a.quantity isnt b.quantity
return if a.quantity > b.quantity then -1 else +1
return ItemSlug.compare a.itemSlug, b.itemSlug
# Object Overrides #############################################################################
toString: ->
return "#{@quantity} #{@itemSlug}"
+23
View File
@@ -0,0 +1,23 @@
#
# Crafting Guide - stack.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
########################################################################################################################
module.exports = class Stack extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
attributes.quantity ?= 1
options.logEvents ?= false
super attributes, options
# Object Overrides #############################################################################
toString: ->
return "#{@quantity} #{@itemSlug}"