wip
This commit is contained in:
+1
-1
@@ -130,7 +130,7 @@ module.exports = (grunt)->
|
||||
|
||||
mochaTest:
|
||||
options:
|
||||
bail: true
|
||||
bail: false
|
||||
color: true
|
||||
reporter: 'list'
|
||||
require: [
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#
|
||||
# Crafting Guide - base_model.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BaseModel extends Backbone.Model
|
||||
|
||||
@_loadingQueue = []
|
||||
@_isDraining = false
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
options.logEvents ?= true
|
||||
super attributes, options
|
||||
|
||||
makeGetter = (name)-> return -> @get name
|
||||
makeSetter = (name)-> return (value)-> @set name, value
|
||||
for name, value of attributes
|
||||
continue if name is 'id'
|
||||
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
|
||||
|
||||
@fileCache = options.fileCache or null
|
||||
@loading = null
|
||||
@logEvents = options.logEvents or false
|
||||
@state = c.modelState.unloaded
|
||||
|
||||
Object.defineProperties this,
|
||||
isUnloaded: { get:-> @state is c.modelState.unloaded }
|
||||
isLoading: { get:-> @state is c.modelState.loading }
|
||||
isLoaded: { get:-> @state is c.modelState.loaded }
|
||||
isError: { get:-> @state is c.modelState.error }
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onLoadSucceeded: (text, status, xhr)->
|
||||
try
|
||||
@set @parse text
|
||||
|
||||
@state = c.modelState.loaded
|
||||
@trigger c.event.change, this
|
||||
@trigger c.event.sync, this
|
||||
logger.info => "#{@constructor.name}.#{@cid} loaded successfully"
|
||||
catch e
|
||||
logger.error -> "A parsing error occured: #{e.stack}"
|
||||
@onLoadFailed e.message, 'parsing failed', xhr
|
||||
|
||||
onLoadFailed: (error, status, xhr)->
|
||||
@state = c.modelState.error
|
||||
logger.error => "#{@constructor.name}.#{@cid} failed to load: status:#{status}, message:#{error}"
|
||||
@trigger c.event.error, this, error
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
fetch: (options={})->
|
||||
options.force ?= false
|
||||
return if (@isLoading or @isLoaded) and not options.force
|
||||
|
||||
url = @url()
|
||||
logger.info => "#{@constructor.name}.#{@cid} reading from url: #{url}"
|
||||
|
||||
@state = c.modelState.loading
|
||||
@trigger c.event.request, this
|
||||
|
||||
loadFromServer = =>
|
||||
w.promise (resolve, reject)=>
|
||||
$.ajax
|
||||
url: url
|
||||
dataType: 'text'
|
||||
success: (text, status, xhr)=> resolve @onLoadSucceeded text, status, xhr
|
||||
error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr
|
||||
|
||||
if @fileCache?
|
||||
@loading = @fileCache.loading.then =>
|
||||
if @fileCache.hasFile url
|
||||
return @_addToLoadingQueue @fileCache.getFile(url), 'success', {url:url}
|
||||
else
|
||||
@loading = loadFromServer()
|
||||
else
|
||||
@loading = loadFromServer()
|
||||
|
||||
@loading.catch (e)-> # do nothing
|
||||
return @loading
|
||||
|
||||
parse: (text)->
|
||||
return JSON.parse text
|
||||
|
||||
sync: (method, model)->
|
||||
throw new Error "#{@constructor.name}.#{@cid} is not permitted to #{method}"
|
||||
|
||||
trigger: (name, model, args...)->
|
||||
if @logEvents
|
||||
argText = ("#{arg}"[0..50] for arg in args).join ", "
|
||||
logger.trace => "#{@constructor.name}.#{@cid} triggered event #{name} with args: #{argText}"
|
||||
super
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name}.#{@cid}"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_addToLoadingQueue: (text, status, xhr)->
|
||||
deferred = w.defer()
|
||||
|
||||
BaseModel._loadingQueue.push resolve:deferred.resolve, func:(=> @onLoadSucceeded text, status, xhr)
|
||||
@_drainLoadingQueue()
|
||||
return deferred.promise
|
||||
|
||||
_drainLoadingQueue: ->
|
||||
return if @_isDraining
|
||||
@_isDraining = true
|
||||
|
||||
drainDelay = 50
|
||||
drain = =>
|
||||
toLoad = BaseModel._loadingQueue.shift()
|
||||
if not toLoad?
|
||||
@_isDraining = false
|
||||
else
|
||||
toLoad.func()
|
||||
toLoad.resolve(true)
|
||||
|
||||
_.delay drain, drainDelay
|
||||
|
||||
_.delay drain, drainDelay
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# Crafting Guide - converter.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require "../models/game/item"
|
||||
Mod = require "../models/game/mod"
|
||||
ModPack = require "../models/game/mod_pack"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Converter
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
convert: (id, displayName, oldModPack)->
|
||||
modSlugToIdMap = {}
|
||||
itemSlugToIdMap = {}
|
||||
|
||||
newModPack = new ModPack id:id, displayName:displayName
|
||||
oldModPack.eachMod (oldMod)=>
|
||||
newMod = new Mod id:_.uniqueId("mod-"), displayName:oldMod.name, modPack:newModPack
|
||||
modSlugToIdMap[oldMod.slug.toString()] = newMod.id
|
||||
|
||||
oldMod.eachItem (oldItem)=>
|
||||
newItem = new Item id:_.uniqueId("item-"), displayName:oldItem.name, mod:newMod
|
||||
itemSlugToIdMap[oldItem.slug.toString()] = newItem.id
|
||||
|
||||
if oldItem.isGatherable?
|
||||
newItem.isGatherable = oldItem.isGatherable
|
||||
|
||||
oldModPack.eachMod (oldMod)=>
|
||||
newMod = newModPack.mods[modSlugToIdMap[oldMod.slug.toString()]]
|
||||
|
||||
|
||||
return newModPack
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_convertItem: (oldItem, newMod)->
|
||||
|
||||
_convertMod: (oldMod, newModPack)->
|
||||
return newMod
|
||||
@@ -0,0 +1,36 @@
|
||||
#
|
||||
# Crafting Guide - event_recorder.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
util = require 'util'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class EventRecorder
|
||||
|
||||
constructor: (model)->
|
||||
if not model? then throw new Error 'model is required'
|
||||
|
||||
@model = model
|
||||
@events = []
|
||||
|
||||
@model.on 'all', (event, model, args...)=>
|
||||
logger.verbose -> "#{model?.constructor?.name}(#{model?.cid}) emitted #{event}
|
||||
with args: #{util.inspect(args)}"
|
||||
@events.push id:model?.cid, event:event, args:args
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
reset: ->
|
||||
@events = []
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getNames: ->
|
||||
return (e.event for e in @events)
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
names: {get:@prototype.getNames}
|
||||
@@ -0,0 +1,243 @@
|
||||
#
|
||||
# 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, options={})->
|
||||
return this unless quantity > 0
|
||||
|
||||
@_add itemSlug, quantity, options
|
||||
@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
|
||||
|
||||
toDescription: ->
|
||||
return null if @isEmpty
|
||||
return null unless @modPack?
|
||||
|
||||
item = @modPack.findItem @_itemSlugs[0]
|
||||
extras = @_itemSlugs.length - 1
|
||||
|
||||
result = "#{item.name}"
|
||||
if extras > 0 then result += " and #{extras} more..."
|
||||
|
||||
return result
|
||||
|
||||
# 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, options={})->
|
||||
options.insert ?= false
|
||||
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
|
||||
if options.insert
|
||||
@_itemSlugs.unshift itemSlug
|
||||
else
|
||||
@_itemSlugs.push itemSlug
|
||||
@_sort()
|
||||
else
|
||||
stack.quantity += quantity
|
||||
|
||||
_sort: ->
|
||||
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
|
||||
@@ -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()
|
||||
@@ -0,0 +1,234 @@
|
||||
#
|
||||
# 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 = []
|
||||
|
||||
# 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 #################################################################################
|
||||
|
||||
chooseRandomItem: ->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
return null unless effectiveModVersion?
|
||||
|
||||
return effectiveModVersion.chooseRandomItem()
|
||||
|
||||
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.fileCache = this.fileCache
|
||||
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
|
||||
|
||||
getAllModVersions: ->
|
||||
return @_modVersions[..]
|
||||
|
||||
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
|
||||
|
||||
@_verifyActiveModVersion()
|
||||
|
||||
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
|
||||
@@ -0,0 +1,202 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Mod = require './mod'
|
||||
ModVersionParser = require '../parsing/mod_version_parser'
|
||||
Recipe = require './recipe'
|
||||
SimpleInventory = require '../crafting/simple_inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPack extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
|
||||
@_mods = []
|
||||
@_cache = {}
|
||||
|
||||
@on c.event.change, => @_cache = {}
|
||||
|
||||
# Item Methods #################################################################################
|
||||
|
||||
chooseRandomItem: ->
|
||||
return null unless @_mods.length > 0
|
||||
|
||||
modIndex = Math.floor Math.random() * @_mods.length
|
||||
return @_mods[modIndex].chooseRandomItem()
|
||||
|
||||
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 SimpleInventory 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[..]
|
||||
|
||||
removeMod: (mod)->
|
||||
index = @_mods.indexOf mod
|
||||
return unless index >= 0
|
||||
|
||||
@_mods.splice index, 1
|
||||
|
||||
@trigger c.event.remove, this, mod.slug
|
||||
@trigger c.event.change, this
|
||||
|
||||
# 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)->
|
||||
mods = @getAllMods()
|
||||
return true unless mods.length > 0
|
||||
|
||||
for mod in mods
|
||||
if mod.isError
|
||||
@removeMod mod
|
||||
continue
|
||||
|
||||
modVersions = mod.getAllModVersions()
|
||||
return true unless modVersions.length > 0
|
||||
continue if mod.activeVersion is Mod.Version.None
|
||||
|
||||
activeModVersion = mod.activeModVersion
|
||||
return true unless activeModVersion?
|
||||
return true if activeModVersion.isUnloaded
|
||||
return true if activeModVersion.isLoading
|
||||
|
||||
@trigger c.event.change, this
|
||||
@trigger c.event.sync, this
|
||||
@@ -0,0 +1,247 @@
|
||||
#
|
||||
# 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)->
|
||||
return @getQuantityProduced(itemSlug) is @getQuantityRequired(itemSlug)
|
||||
|
||||
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 = {}
|
||||
|
||||
index = 0
|
||||
while index < pattern.length
|
||||
c = pattern[index]
|
||||
index += 1
|
||||
|
||||
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
|
||||
@@ -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}"
|
||||
@@ -0,0 +1,33 @@
|
||||
#
|
||||
# Crafting Guide - tutorial.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Tutorial extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.name?.length > 0 then throw new Error "attributes.name cannot be empty"
|
||||
attributes.modSlug ?= null
|
||||
attributes.officialUrl ?= null
|
||||
attributes.sections ?= []
|
||||
attributes.slug ?= _.slugify attributes.name
|
||||
attributes.videos ?= []
|
||||
super attributes, options
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
TutorialParser = require '../parsing/tutorial_parser' # to avoid require cycles
|
||||
@_parser ?= new TutorialParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.tutorialData modSlug:@modSlug, tutorialSlug:@slug
|
||||
@@ -5,7 +5,7 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Inventory = require "./inventory"
|
||||
Inventory = require "../game/inventory"
|
||||
{StringBuilder} = require "crafting-guide-common"
|
||||
|
||||
########################################################################################################################
|
||||
@@ -20,8 +20,8 @@ module.exports = class CraftingPlan
|
||||
@steps = attributes.steps
|
||||
@want = attributes.want
|
||||
|
||||
@_consolidateSteps()
|
||||
@_computeResources()
|
||||
@_consolidateSteps()
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
CraftingPlan = require './crafting_plan'
|
||||
CraftingPlanStep = require './crafting_plan_step'
|
||||
Inventory = require './inventory'
|
||||
fixtures = require './fixtures'
|
||||
Inventory = require '../game/inventory'
|
||||
fixtures = require '../fixtures'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = class Evaluation
|
||||
@recipe = attributes.recipe if attributes.recipe?
|
||||
@baseScore = attributes.baseScore
|
||||
|
||||
@_baseEvaluations = []
|
||||
@_id = _.uniqueId "evaluation-"
|
||||
@_includedTools = {}
|
||||
@_toolScore = null
|
||||
|
||||
@@ -23,10 +23,6 @@ module.exports = class Evaluation
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
baseEvaluations:
|
||||
get: -> return @_baseEvaluations
|
||||
set: -> throw new Error "baseEvaluations cannot be assigned"
|
||||
|
||||
baseScore:
|
||||
get: -> return @_baseScore
|
||||
set: (baseScore)->
|
||||
@@ -70,13 +66,15 @@ module.exports = class Evaluation
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addBaseEvaluation: (evaluation)->
|
||||
@_baseEvaluations.push evaluation
|
||||
|
||||
addIncludedTool: (item)->
|
||||
return if @_includedTools[item.id]?
|
||||
@_includedTools[item.id] = item
|
||||
@_toolScore = null
|
||||
|
||||
addIncludedToolsFrom: (evaluation)->
|
||||
for id, toolItem of evaluation.includedTools
|
||||
@addIncludedTool toolItem
|
||||
|
||||
computeTotalScore: (quantity=1)->
|
||||
if @item?
|
||||
multiplier = quantity
|
||||
@@ -86,18 +84,13 @@ module.exports = class Evaluation
|
||||
return @baseScore * multiplier + @toolScore
|
||||
|
||||
isToolIncluded: (item)->
|
||||
return true if @_includedTools[item.id]?
|
||||
|
||||
for baseEvaluation in @_baseEvaluations
|
||||
return true if baseEvaluation.isToolIncluded item
|
||||
|
||||
return false
|
||||
return @_includedTools[item.id]?
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
obj = if @item? then @item else @recipe
|
||||
return "#{@evaluator}=>#{obj}@#{@baseScore}"
|
||||
return "#{@evaluator.constructor.name}:#{obj}@#{@baseScore}<#{@_id}>"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ module.exports = class Evaluator
|
||||
|
||||
recipeEvaluation = @_findBestRecipeEvaluationFor item
|
||||
if recipeEvaluation?
|
||||
evaluation.addBaseEvaluation recipeEvaluation
|
||||
evaluation.baseScore = recipeEvaluation.baseScore
|
||||
evaluation.addIncludedToolsFrom recipeEvaluation
|
||||
else
|
||||
@_computeGatherableItemScore item, evaluation
|
||||
|
||||
@@ -49,12 +49,13 @@ module.exports = class Evaluator
|
||||
evaluation = @_evaluations[recipe.id] = new Evaluation evaluator:this, recipe:recipe
|
||||
@_computeRecipeScore recipe, evaluation
|
||||
|
||||
for id, item of recipe.inputs
|
||||
inputEvaluation = @evaluateItem item
|
||||
evaluation.addIncludedToolsFrom inputEvaluation
|
||||
|
||||
for id, toolItem of recipe.tools
|
||||
evaluation.addIncludedTool toolItem
|
||||
|
||||
for id, toolItem of @evaluateItem(toolItem).includedTools
|
||||
evaluation.addIncludedTool toolItem
|
||||
|
||||
evaluation.addIncludedToolsFrom @evaluateItem toolItem
|
||||
|
||||
return evaluation
|
||||
|
||||
@@ -79,20 +80,19 @@ module.exports = class Evaluator
|
||||
|
||||
# Overrideable Methods #########################################################################
|
||||
|
||||
_computeRecipeScore: (recipe, evaluation)->
|
||||
throw new Error "#{@constructor.name} must override _computeRecipeScore"
|
||||
|
||||
_computeGatherableItemScore: (item, evaluation)->
|
||||
throw new Error "#{@constructor.name} must override _computeGatherableItemScore"
|
||||
|
||||
_computeRecipeScore: (recipe, evaluation)->
|
||||
throw new Error "#{@constructor.name} must override _computeRecipeScore"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_findBestRecipeEvaluationFor: (item)->
|
||||
return null if item.isGatherable
|
||||
result = null
|
||||
|
||||
for recipeMap in [item.recipes, item.recipesAsExtra]
|
||||
for id, recipe of recipeMap
|
||||
for id, recipe of item.recipes
|
||||
evaluation = @evaluateRecipe recipe
|
||||
continue unless evaluation?.baseScore?
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - inventory.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Stack = require './stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Inventory
|
||||
|
||||
constructor: (inventory=null)->
|
||||
@_id = _.uniqueId "inventory-"
|
||||
@_stacks = {}
|
||||
|
||||
if inventory? then @merge inventory
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
isEmpty:
|
||||
get: -> (id for id, stack of @_stacks).length is 0
|
||||
|
||||
stacks:
|
||||
get: -> return @_stacks
|
||||
set: -> throw new Error "stacks cannot be replaced"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (item, quantity)->
|
||||
return unless item?
|
||||
return if quantity is 0
|
||||
|
||||
existingStack = @_stacks[item.id]
|
||||
if existingStack?
|
||||
if existingStack.quantity + quantity < 0 then throw new Error "cannot have a negative quantity"
|
||||
existingStack.quantity += quantity
|
||||
else
|
||||
if quantity < 0 then throw new Error "cannot have a negative quantity"
|
||||
@_stacks[item.id] = new Stack item:item, quantity:quantity
|
||||
|
||||
if @_stacks[item.id].quantity is 0
|
||||
delete @_stacks[item.id]
|
||||
|
||||
clear: ->
|
||||
@_stacks = {}
|
||||
|
||||
contains: (item)->
|
||||
return @_stacks[item.id]?
|
||||
|
||||
getQuantity: (item)->
|
||||
existingStack = @_stacks[item.id]
|
||||
return 0 unless existingStack?
|
||||
return existingStack.quantity
|
||||
|
||||
merge: (inventory)->
|
||||
for id, stack of inventory.stacks
|
||||
@add stack.item, stack.quantity
|
||||
|
||||
remove: (item, quantity)->
|
||||
@add item, -1 * quantity
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: (options={})->
|
||||
options.full ?= false
|
||||
|
||||
if options.full
|
||||
result = []
|
||||
needsDelimiter = false
|
||||
|
||||
stackList = (stack for itemId, stack of @_stacks)
|
||||
stackList.sort (a, b)->
|
||||
if a.item.displayName isnt b.item.displayName
|
||||
return if a.item.displayName < b.item.displayName then -1 else +1
|
||||
return 0
|
||||
|
||||
for stack in stackList
|
||||
if needsDelimiter then result.push ", "
|
||||
needsDelimiter = true
|
||||
|
||||
result.push stack.quantity
|
||||
result.push " "
|
||||
result.push stack.item.displayName
|
||||
return result.join ""
|
||||
else
|
||||
return "Inventory<#{@_id}>@#{(id for id, item of @_stacks).length}"
|
||||
@@ -1,93 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Item
|
||||
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@displayName = attributes.displayName
|
||||
@isGatherable = attributes.isGatherable
|
||||
@mod = attributes.mod
|
||||
|
||||
@_hasPrimaryRecipe = false
|
||||
@_recipesAsPrimary = {}
|
||||
@_recipesAsExtra = {}
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
displayName:
|
||||
get: -> return @_displayName
|
||||
set: (displayName)->
|
||||
if not displayName? then throw new Error "displayName is required"
|
||||
@_displayName = displayName
|
||||
|
||||
firstRecipe:
|
||||
get: -> return recipe for id, recipe of @recipes
|
||||
set: -> throw new Error "firstRecipe cannot be assigned"
|
||||
|
||||
id:
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
return if @_id is id
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
isGatherable:
|
||||
get: ->
|
||||
return true if @_isGatherable is true
|
||||
return false if (id for id, recipe of @_recipesAsPrimary).length > 0
|
||||
return false if (id for id, recipe of @_recipesAsExtra).length > 0
|
||||
return true
|
||||
set: (isGatherable)->
|
||||
@_isGatherable = null unless isGatherable?
|
||||
@_isGatherable = !!isGatherable
|
||||
|
||||
mod:
|
||||
get: -> return @_mod
|
||||
set: (mod)->
|
||||
if not mod? then throw new Error "mod is required"
|
||||
if @_mod is mod then return
|
||||
if @_mod? then throw new Error "mod cannot be reassigned"
|
||||
@_mod = mod
|
||||
@_mod.addItem this
|
||||
|
||||
modPack:
|
||||
get: -> return @_mod.modPack
|
||||
set: -> throw new Error "modPack cannot be replaced"
|
||||
|
||||
recipes:
|
||||
get: -> return if @_hasPrimaryRecipe then @_recipesAsPrimary else @_recipesAsExtra
|
||||
set: -> throw new Error "recipes cannot be assigned"
|
||||
|
||||
recipesAsPrimary:
|
||||
get: -> return @_recipesAsPrimary
|
||||
set: -> throw new Error "recipes cannot be assigned"
|
||||
|
||||
recipesAsExtra:
|
||||
get: -> return @_recipesAsExtra
|
||||
set: -> throw new Error "recipesAsExtra cannot be assigned"
|
||||
|
||||
# Public Recipes ###############################################################################
|
||||
|
||||
addRecipe: (recipe)->
|
||||
if recipe.output.item is this
|
||||
@_recipesAsPrimary[recipe.id] = recipe
|
||||
@_hasPrimaryRecipe = true
|
||||
else if recipe.extras[this.id] is this
|
||||
@_recipesAsExtra[recipe.id] = recipe
|
||||
else
|
||||
throw new Error "recipe<#{recipe.id}> does not produce this item<#{@id}>"
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "Item:#{@displayName}<#{@id}>"
|
||||
@@ -1,62 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Mod
|
||||
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@displayName = attributes.displayName
|
||||
@modPack = attributes.modPack
|
||||
|
||||
@_items = {}
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
displayName:
|
||||
get: -> return @_displayName
|
||||
set: (displayName)->
|
||||
if not displayName? then throw new Error "displayName is required"
|
||||
return if @_displayName is displayName
|
||||
@_displayName = displayName
|
||||
|
||||
id:
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
return if @_id is id
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
items:
|
||||
get: -> return @_items
|
||||
set: -> throw new Error "items cannot be replaced"
|
||||
|
||||
modPack:
|
||||
get: -> return @_modPack
|
||||
set: (modPack)->
|
||||
if not modPack? then throw new Error "modPack is required"
|
||||
if @_modPack is modPack then return
|
||||
if @_modPack? then throw new Error "modPack cannot be reassigned"
|
||||
@_modPack = modPack
|
||||
@_modPack.addMod this
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addItem: (item)->
|
||||
if not item? then return
|
||||
if @_items[item.id] is item then return
|
||||
@_items[item.id] = item
|
||||
item.mod = this
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "Mod:#{@displayName}<#{@id}>"
|
||||
@@ -1,52 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPack
|
||||
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@displayName = attributes.displayName
|
||||
|
||||
@_mods = {}
|
||||
@_oreDict = {}
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
displayName:
|
||||
get: -> return @_displayName
|
||||
set: (displayName)->
|
||||
if not displayName? then throw new Error "displayName is required"
|
||||
if @_displayName is displayName then return
|
||||
@_displayName = displayName
|
||||
|
||||
id:
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
if @_id is id then return
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
|
||||
mods:
|
||||
get: -> return @_mods
|
||||
set: -> throw new Error "mods cannot be replaced"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addMod: (mod)->
|
||||
if not mod? then return
|
||||
if @_mods[mod.id] is mod then return
|
||||
@_mods[mod.id] = mod
|
||||
mod.modPack = this
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "ModPack:#{@displayName}<#{@id}>"
|
||||
@@ -5,14 +5,14 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
fixtures = require './fixtures'
|
||||
Inventory = require './inventory'
|
||||
fixtures = require '../fixtures'
|
||||
Inventory = require '../game/inventory'
|
||||
PlanBuilder = require './plan_builder'
|
||||
ResourcesEvaluator = require './resources_evaluator'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe.only "PlanBuilder", ->
|
||||
describe "PlanBuilder", ->
|
||||
|
||||
beforeEach ->
|
||||
@planner = new PlanBuilder new ResourcesEvaluator
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - recipe.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Recipe
|
||||
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@height = attributes.height
|
||||
@output = attributes.output
|
||||
@width = attributes.width
|
||||
|
||||
@_extras = {}
|
||||
@_inputs = {}
|
||||
@_inputGrid = []
|
||||
@_tools = {}
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
allProducts: # an array of Stacks starting the the primary output of this recipe
|
||||
get: -> return [].concat @output, (stack for id, stack of @extras)
|
||||
set: -> throw new Error "allProducts cannot be assigned"
|
||||
|
||||
extras: # a hash of item id to Stack of all the non-primary outputs of this recipe
|
||||
get: -> return @_extras
|
||||
set: -> throw new Error "extras cannot be replaced"
|
||||
|
||||
id: # a string which uniquely identifies this recipe
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
if @_id is id then return
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
height: # an integer specifying the number of rows needed by this recipe
|
||||
get: -> return @_height
|
||||
set: (height)->
|
||||
height = parseInt "#{height}"
|
||||
height = if Number.isNaN(height) then 0 else Math.max(0, height)
|
||||
@_height = height
|
||||
|
||||
inputs: # a hash of item id to Item containing all the inputs to this recipe
|
||||
get: -> return @_inputs
|
||||
set: -> throw new Error "inputs cannot be replaced"
|
||||
|
||||
needsTools: # a boolean indicating whether this recipe requires a tool
|
||||
get: -> return (id for id, toolItem of @_tools).length > 0
|
||||
|
||||
output: # a Stack specifying the primary output of this recipe
|
||||
get: -> return @_output
|
||||
set: (output)->
|
||||
if not output? then throw new Error "output is required"
|
||||
if @_output is output then return
|
||||
if @_output? then throw new Error "output cannot be reassigned"
|
||||
@_output = output
|
||||
@_output.item.addRecipe this
|
||||
|
||||
modPack: # the ModPack to which this recipe belongs
|
||||
get: -> return @_output.modPack
|
||||
set: -> throw new Error "modPack cannot be replaced"
|
||||
|
||||
tools: # a hash of item id to Item of all the tools required for this recipe
|
||||
get: -> return @_tools
|
||||
set: -> throw new Error "tools cannot be assigned"
|
||||
|
||||
width: # an integer specifying the number of columns needed by this recipe
|
||||
get: -> return @_width
|
||||
set: (width)->
|
||||
width = parseInt "#{width}"
|
||||
width = if Number.isNaN(width) then 0 else Math.max(0, width)
|
||||
@_width = width
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addExtra: (stack)->
|
||||
return unless stack
|
||||
@_extras[stack.item.id] = stack
|
||||
|
||||
addTool: (item)->
|
||||
return unless item
|
||||
@_tools[item.id] = item
|
||||
|
||||
computeQuantityRequired: (item)->
|
||||
result = 0
|
||||
|
||||
for row in [0...@height]
|
||||
for col in [0...@width]
|
||||
stack = @_inputGrid[row]?[col]
|
||||
continue unless stack?
|
||||
continue unless stack.item.id is item.id
|
||||
result += stack.quantity
|
||||
|
||||
return result
|
||||
|
||||
computeQuantityProduced: (item)->
|
||||
result = 0
|
||||
|
||||
if @_output.item.id is item.id
|
||||
result += @_output.quantity
|
||||
|
||||
for itemId, stack of @_extras
|
||||
continue unless itemId is item.id
|
||||
result += stack.quantity
|
||||
|
||||
return result
|
||||
|
||||
getInputAt: (row, col)->
|
||||
return @_inputGrid[row]?[col]
|
||||
|
||||
setInputAt: (row, col, stack)->
|
||||
@_height = Math.max @_height, row + 1
|
||||
@_width = Math.max @_width, col + 1
|
||||
|
||||
@_inputGrid[row] ?= []
|
||||
@_inputGrid[row][col] = stack
|
||||
@_inputs[stack.item.id] = stack.item
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: (options={})->
|
||||
options.full ?= false
|
||||
|
||||
if options.full
|
||||
b = new StringBuilder
|
||||
b.loop (item for id, item of @inputs), delimiter:" + ", onEach:(b, item)=>
|
||||
b.push @computeQuantityRequired(item), " ", item.displayName
|
||||
b.push " ="
|
||||
b.onlyIf @needsTools, (b)=>
|
||||
b.push "("
|
||||
b.loop (toolItem for id, toolItem of @tools), onEach:(b, toolItem)-> b.push toolItem.displayName
|
||||
b.push ")"
|
||||
b.push "=> "
|
||||
b.loop @allProducts, delimiter:" + ", onEach:(b, stack)=>
|
||||
b.push stack.quantity, " ", stack.item.displayName
|
||||
|
||||
return b.toString()
|
||||
else
|
||||
return "Recipe:#{@output}<#{@id}>"
|
||||
@@ -16,18 +16,17 @@ module.exports = class ResourcesEvaluator extends Evaluator
|
||||
_computeRecipeScore: (recipe, evaluation)->
|
||||
evaluation.baseScore = 0
|
||||
|
||||
for row in [0...recipe.height]
|
||||
for col in [0...recipe.width]
|
||||
stack = recipe.getInputAt row, col
|
||||
for x in [0...recipe.width]
|
||||
for y in [0...recipe.height]
|
||||
for z in [0...recipe.depth]
|
||||
stack = recipe.getInputAt x, y, z
|
||||
continue unless stack?
|
||||
|
||||
inputEvaluation = @evaluateItem stack.item
|
||||
if inputEvaluation?.baseScore?
|
||||
evaluation.baseScore += inputEvaluation.baseScore * stack.quantity
|
||||
evaluation.addBaseEvaluation inputEvaluation
|
||||
else
|
||||
evaluation.baseScore = null
|
||||
logger.outdent()
|
||||
return
|
||||
|
||||
for id, extraStack of recipe.extras
|
||||
@@ -35,10 +34,6 @@ module.exports = class ResourcesEvaluator extends Evaluator
|
||||
continue unless extraEvaluation?.baseScore?
|
||||
evaluation.baseScore -= extraStack.quantity * extraEvaluation.baseScore
|
||||
|
||||
for id, toolItem of recipe.tools
|
||||
toolEvaluation = @evaluateItem toolItem
|
||||
evaluation.addBaseEvaluation toolEvaluation
|
||||
|
||||
evaluation.baseScore = evaluation.baseScore / recipe.output.quantity
|
||||
|
||||
_computeGatherableItemScore: (item, evaluation)->
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
|
||||
ResourcesEvaluator = require './resources_evaluator'
|
||||
fixtures = require './fixtures'
|
||||
fixtures = require '../fixtures'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -123,3 +123,7 @@ describe "ResourcesEvaluator", ->
|
||||
# 1 oak wood ==> 4 oak planks (0.25)
|
||||
# 4 planks ==> 1 crafting table (1)
|
||||
@evaluation.computeTotalScore().should.equal 18
|
||||
|
||||
describe 'evaluating a recipe which is only ever made as an extra', ->
|
||||
|
||||
it 'should still be able to come up with an evaluation'
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - stack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Stack
|
||||
|
||||
constructor: (attributes={})->
|
||||
@item = attributes.item
|
||||
@quantity = attributes.quantity
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
item:
|
||||
get: -> return @_item
|
||||
set: (item)->
|
||||
if not item? then throw new Error "item is required"
|
||||
if @_item is item then return
|
||||
if @_item? then throw new Error "item cannot be reassigned"
|
||||
@_item = item
|
||||
|
||||
modPack:
|
||||
get: -> return @_item.modPack
|
||||
set: -> throw new Error "modPack cannot be replaced"
|
||||
|
||||
quantity:
|
||||
get: -> return @_quantity
|
||||
set: (quantity)->
|
||||
quantity = parseInt "#{quantity}"
|
||||
quantity = if Number.isNaN(quantity) then 0 else Math.max(0, quantity)
|
||||
@_quantity = quantity
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "Stack:#{@item}×#{@quantity}"
|
||||
@@ -22,7 +22,6 @@ module.exports = class StepsEvaluator extends Evaluator
|
||||
evaluation.score = null
|
||||
return
|
||||
|
||||
evaluation.addBaseEvaluation inputEvaluation
|
||||
evaluation.score = Math.min evaluation.score, inputEvaluation.score + 1
|
||||
|
||||
for id, item of recipe.tools
|
||||
@@ -33,7 +32,6 @@ module.exports = class StepsEvaluator extends Evaluator
|
||||
evaluation.score = null
|
||||
return
|
||||
|
||||
evaluation.addBaseEvaluation evaluation
|
||||
evaluation.addIncludedTool item
|
||||
evaluation.score += toolEvaluation.score
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require "./item"
|
||||
Mod = require "./mod"
|
||||
ModPack = require "./mod_pack"
|
||||
Recipe = require "./recipe"
|
||||
Stack = require "./stack"
|
||||
Item = require "./game/item"
|
||||
Mod = require "./game/mod"
|
||||
ModPack = require "./game/mod_pack"
|
||||
Recipe = require "./game/recipe"
|
||||
Stack = require "./game/stack"
|
||||
|
||||
# Instance Creation Fixtures ###########################################################################################
|
||||
|
||||
@@ -43,7 +43,7 @@ exports.createStack = createStack = (attributes={})->
|
||||
# Item Configuration Fixtures ##########################################################################################
|
||||
|
||||
exports.configureBucket = configureBucket = (mod)->
|
||||
bucket = mod.items["bucket"]
|
||||
bucket = mod.modPack.findItem "bucket"
|
||||
if not bucket?
|
||||
ironIngot = configureIronIngot mod
|
||||
bucket = createItem mod:mod, id:"bucket", displayName:"Bucket"
|
||||
@@ -52,13 +52,13 @@ exports.configureBucket = configureBucket = (mod)->
|
||||
recipe = createRecipe output:createStack item:bucket
|
||||
recipe.setInputAt 0, 0, createStack item:ironIngot
|
||||
recipe.setInputAt 1, 1, createStack item:ironIngot
|
||||
recipe.setInputAt 0, 2, createStack item:ironIngot
|
||||
recipe.setInputAt 2, 0, createStack item:ironIngot
|
||||
recipe.addTool craftingTable
|
||||
|
||||
return bucket
|
||||
|
||||
exports.configureCake = configureCake = (mod)->
|
||||
cake = mod.items["cake"]
|
||||
cake = mod.modPack.findItem "cake"
|
||||
if not cake?
|
||||
bucket = configureBucket mod
|
||||
cake = createItem mod:mod, id:"cake", displayName:"Cake"
|
||||
@@ -70,13 +70,13 @@ exports.configureCake = configureCake = (mod)->
|
||||
|
||||
recipe = createRecipe output:createStack item:cake
|
||||
recipe.setInputAt 0, 0, createStack item:milkBucket
|
||||
recipe.setInputAt 0, 1, createStack item:milkBucket
|
||||
recipe.setInputAt 0, 2, createStack item:milkBucket
|
||||
recipe.setInputAt 1, 0, createStack item:sugar
|
||||
recipe.setInputAt 1, 0, createStack item:milkBucket
|
||||
recipe.setInputAt 2, 0, createStack item:milkBucket
|
||||
recipe.setInputAt 0, 1, createStack item:sugar
|
||||
recipe.setInputAt 1, 1, createStack item:egg
|
||||
recipe.setInputAt 1, 2, createStack item:sugar
|
||||
recipe.setInputAt 2, 0, createStack item:wheat
|
||||
recipe.setInputAt 2, 1, createStack item:wheat
|
||||
recipe.setInputAt 2, 1, createStack item:sugar
|
||||
recipe.setInputAt 0, 2, createStack item:wheat
|
||||
recipe.setInputAt 1, 2, createStack item:wheat
|
||||
recipe.setInputAt 2, 2, createStack item:wheat
|
||||
recipe.addTool craftingTable
|
||||
recipe.addExtra createStack item:bucket, quantity:3
|
||||
@@ -84,39 +84,39 @@ exports.configureCake = configureCake = (mod)->
|
||||
return cake
|
||||
|
||||
exports.configureCoal = configureCoal = (mod)->
|
||||
coal = mod.items["coal"]
|
||||
coal = mod.modPack.findItem "coal"
|
||||
if not coal?
|
||||
coal = createItem mod:mod, id:"coal", displayName:"Coal", isGatherable:true
|
||||
coal = createItem mod:mod, id:"coal", displayName:"Coal"
|
||||
return coal
|
||||
|
||||
exports.configureCobblestone = configureCobblestone = (mod)->
|
||||
cobblestone = mod.items["cobblestone"]
|
||||
cobblestone = mod.modPack.findItem "cobblestone"
|
||||
if not cobblestone?
|
||||
cobblestone = createItem mod:mod, displayName:"Cobblestone", isGatherable:true
|
||||
cobblestone = createItem mod:mod, displayName:"Cobblestone"
|
||||
return cobblestone
|
||||
|
||||
exports.configureCraftingTable = configureCraftingTable = (mod)->
|
||||
craftingTable = mod.items["crafting_table"]
|
||||
craftingTable = mod.modPack.findItem "crafting_table"
|
||||
if not craftingTable?
|
||||
craftingTable = createItem mod:mod, id:"crafting_table", displayName:"Crafting Table"
|
||||
oakPlanks = configureOakPlank mod
|
||||
|
||||
recipe = createRecipe output:createStack item:craftingTable
|
||||
recipe.setInputAt 0, 0, createStack item:oakPlanks
|
||||
recipe.setInputAt 0, 1, createStack item:oakPlanks
|
||||
recipe.setInputAt 1, 0, createStack item:oakPlanks
|
||||
recipe.setInputAt 0, 1, createStack item:oakPlanks
|
||||
recipe.setInputAt 1, 1, createStack item:oakPlanks
|
||||
|
||||
return craftingTable
|
||||
|
||||
exports.configureEgg = configureEgg = (mod)->
|
||||
egg = mod.items["egg"]
|
||||
egg = mod.modPack.findItem "egg"
|
||||
if not egg?
|
||||
egg = createItem mod:mod, id:"egg", displayName:"Egg", isGatherable:true
|
||||
egg = createItem mod:mod, id:"egg", displayName:"Egg"
|
||||
return egg
|
||||
|
||||
exports.configureFurnace = configureFurnace = (mod)->
|
||||
furnace = mod.items["furnace"]
|
||||
furnace = mod.modPack.findItem "furnace"
|
||||
if not furnace?
|
||||
cobblestone = configureCobblestone mod
|
||||
craftingTable = configureCraftingTable mod
|
||||
@@ -124,19 +124,19 @@ exports.configureFurnace = configureFurnace = (mod)->
|
||||
|
||||
recipe = createRecipe output:createStack item:furnace
|
||||
recipe.setInputAt 0, 0, createStack item:cobblestone
|
||||
recipe.setInputAt 0, 1, createStack item:cobblestone
|
||||
recipe.setInputAt 0, 2, createStack item:cobblestone
|
||||
recipe.setInputAt 1, 0, createStack item:cobblestone
|
||||
recipe.setInputAt 1, 2, createStack item:cobblestone
|
||||
recipe.setInputAt 2, 0, createStack item:cobblestone
|
||||
recipe.setInputAt 0, 1, createStack item:cobblestone
|
||||
recipe.setInputAt 2, 1, createStack item:cobblestone
|
||||
recipe.setInputAt 0, 2, createStack item:cobblestone
|
||||
recipe.setInputAt 1, 2, createStack item:cobblestone
|
||||
recipe.setInputAt 2, 2, createStack item:cobblestone
|
||||
recipe.addTool craftingTable
|
||||
|
||||
return furnace
|
||||
|
||||
exports.configureIronIngot = configureIronIngot = (mod)->
|
||||
ironIngot = mod.items["iron_ingot"]
|
||||
ironIngot = mod.modPack.findItem "iron_ingot"
|
||||
if not ironIngot?
|
||||
coal = configureCoal mod
|
||||
furnace = configureFurnace mod
|
||||
@@ -144,23 +144,23 @@ exports.configureIronIngot = configureIronIngot = (mod)->
|
||||
ironOre = configureIronOre mod
|
||||
|
||||
recipe = createRecipe output:createStack item:ironIngot, quantity:8
|
||||
recipe.setInputAt 0, 1, createStack item:ironOre, quantity:8
|
||||
recipe.setInputAt 2, 1, createStack item:coal
|
||||
recipe.setInputAt 1, 0, createStack item:ironOre, quantity:8
|
||||
recipe.setInputAt 1, 2, createStack item:coal
|
||||
recipe.addTool furnace
|
||||
|
||||
return ironIngot
|
||||
|
||||
exports.configureIronBlock = configureIronBlock = (mod)->
|
||||
ironBlock = mod.items["iron_block"]
|
||||
ironBlock = mod.modPack.findItem "iron_block"
|
||||
if not ironBlock?
|
||||
craftingTable = configureCraftingTable mod
|
||||
ironBlock = createItem mod:mod, id:"iron_block", displayName:"Iron Block"
|
||||
ironIngot = configureIronIngot mod
|
||||
|
||||
recipe = createRecipe output:createStack item:ironBlock
|
||||
for row in [0..2]
|
||||
for col in [0..2]
|
||||
recipe.setInputAt row, col, createStack item:ironIngot
|
||||
for x in [0..2]
|
||||
for y in [0..2]
|
||||
recipe.setInputAt x, y, createStack item:ironIngot
|
||||
recipe.addTool craftingTable
|
||||
|
||||
recipe = createRecipe output:createStack item:ironIngot, quantity:9
|
||||
@@ -169,7 +169,7 @@ exports.configureIronBlock = configureIronBlock = (mod)->
|
||||
return ironBlock
|
||||
|
||||
exports.configureIronSword = configureIronSword = (mod)->
|
||||
ironSword = mod.items["iron_sword"]
|
||||
ironSword = mod.modPack.findItem "iron_sword"
|
||||
if not ironSword?
|
||||
craftingTable = configureCraftingTable mod
|
||||
ironIngot = configureIronIngot mod
|
||||
@@ -177,15 +177,15 @@ exports.configureIronSword = configureIronSword = (mod)->
|
||||
stick = configureStick mod
|
||||
|
||||
recipe = createRecipe output:createStack item:ironSword
|
||||
recipe.setInputAt 0, 1, createStack item:ironIngot
|
||||
recipe.setInputAt 1, 0, createStack item:ironIngot
|
||||
recipe.setInputAt 1, 1, createStack item:ironIngot
|
||||
recipe.setInputAt 2, 1, createStack item:stick
|
||||
recipe.setInputAt 1, 2, createStack item:stick
|
||||
recipe.addTool craftingTable
|
||||
|
||||
return ironSword
|
||||
|
||||
exports.configureIronShovel = configureIronShovel = (mod)->
|
||||
ironShovel = mod.items["iron_shovel"]
|
||||
ironShovel = mod.modPack.findItem "iron_shovel"
|
||||
if not ironShovel?
|
||||
craftingTable = configureCraftingTable mod
|
||||
ironIngot = configureIronIngot mod
|
||||
@@ -193,40 +193,40 @@ exports.configureIronShovel = configureIronShovel = (mod)->
|
||||
stick = configureStick mod
|
||||
|
||||
recipe = createRecipe output:createStack item:ironShovel
|
||||
recipe.setInputAt 0, 1, createStack item:ironIngot
|
||||
recipe.setInputAt 1, 0, createStack item:ironIngot
|
||||
recipe.setInputAt 1, 1, createStack item:stick
|
||||
recipe.setInputAt 2, 1, createStack item:stick
|
||||
recipe.setInputAt 1, 2, createStack item:stick
|
||||
recipe.addTool craftingTable
|
||||
|
||||
return ironShovel
|
||||
|
||||
exports.configureIronOre = configureIronOre = (mod)->
|
||||
ironOre = mod.items["iron_ore"]
|
||||
ironOre = mod.modPack.findItem "iron_ore"
|
||||
if not ironOre?
|
||||
ironOre = createItem mod:mod, id:"iron_ore", displayName:"Iron Ore", isGatherable:true
|
||||
ironOre = createItem mod:mod, id:"iron_ore", displayName:"Iron Ore"
|
||||
return ironOre
|
||||
|
||||
exports.configureMilk = configureMilk = (mod)->
|
||||
milk = mod.items["milk"]
|
||||
milk = mod.modPack.findItem "milk"
|
||||
if not milk?
|
||||
milk = createItem mod:mod, id:"milk", displayName:"Milk", isGatherable:true
|
||||
milk = createItem mod:mod, id:"milk", displayName:"Milk"
|
||||
return milk
|
||||
|
||||
exports.configureMilkBucket = configureMilkBucket = (mod)->
|
||||
milkBucket = mod.items["milk_bucket"]
|
||||
milkBucket = mod.modPack.findItem "milk_bucket"
|
||||
if not milkBucket?
|
||||
bucket = configureBucket mod
|
||||
milk = configureMilk mod
|
||||
milkBucket = createItem mod:mod, id:"milkBucket", displayName:"Milk Bucket"
|
||||
|
||||
recipe = createRecipe output:createStack item:milkBucket
|
||||
recipe.setInputAt 0, 1, createStack item:milk
|
||||
recipe.setInputAt 1, 0, createStack item:milk
|
||||
recipe.setInputAt 1, 1, createStack item:bucket
|
||||
|
||||
return milkBucket
|
||||
|
||||
exports.configureOakPlank = configureOakPlank = (mod)->
|
||||
oakPlanks = mod.items["oak_planks"]
|
||||
oakPlanks = mod.modPack.findItem "oak_planks"
|
||||
if not oakPlanks?
|
||||
oakPlanks = createItem mod:mod, id:"oak_planks", displayName:"Oak Planks"
|
||||
oakWood = configureOakWood mod
|
||||
@@ -237,31 +237,37 @@ exports.configureOakPlank = configureOakPlank = (mod)->
|
||||
return oakPlanks
|
||||
|
||||
exports.configureOakWood = configureOakWood = (mod)->
|
||||
oakWood = mod.items["oak_wood"]
|
||||
oakWood = mod.modPack.findItem "oak_wood"
|
||||
if not oakWood?
|
||||
oakWood = createItem mod:mod, id:"oak_wood", displayName:"Oak Wood", isGatherable:true
|
||||
oakWood = createItem mod:mod, id:"oak_wood", displayName:"Oak Wood"
|
||||
return oakWood
|
||||
|
||||
exports.configureObsidian = configureObsidian = (mod)->
|
||||
obsidian = mod.modPack.findItem "obsidian"
|
||||
if not obsidian?
|
||||
obsidian = createItem mod:mod, id:"obsidian", displayName:"Obsidian"
|
||||
return obsidian
|
||||
|
||||
exports.configureRedstoneDust = configureRedstoneDust = (mod)->
|
||||
redstoneDust = mod.items["redstone_dust"]
|
||||
redstoneDust = mod.modPack.findItem "redstone_dust"
|
||||
if not redstoneDust?
|
||||
redstoneDust = createItem mod:mod, id:"redstone_dust", displayName:"Redstone Dust", isGatherable:true
|
||||
redstoneDust = createItem mod:mod, id:"redstone_dust", displayName:"Redstone Dust"
|
||||
return redstoneDust
|
||||
|
||||
exports.configureStick = configureStick = (mod)->
|
||||
stick = mod.items["stick"]
|
||||
stick = mod.modPack.findItem "stick"
|
||||
if not stick?
|
||||
oakPlanks = configureOakPlank mod
|
||||
stick = createItem mod:mod, id:"stick", displayName:"Stick"
|
||||
|
||||
recipe = createRecipe output:createStack item:stick, quantity:4
|
||||
recipe.setInputAt 0, 0, createStack item:oakPlanks
|
||||
recipe.setInputAt 1, 0, createStack item:oakPlanks
|
||||
recipe.setInputAt 0, 1, createStack item:oakPlanks
|
||||
|
||||
return stick
|
||||
|
||||
exports.configureSugar = configureSugar = (mod)->
|
||||
sugar = mod.items["sugar"]
|
||||
sugar = mod.modPack.findItem "sugar"
|
||||
if not sugar?
|
||||
sugar = createItem mod:mod, id:"sugar", displayName:"Sugar"
|
||||
sugarCane = configureSugarCane mod
|
||||
@@ -272,7 +278,7 @@ exports.configureSugar = configureSugar = (mod)->
|
||||
return sugar
|
||||
|
||||
exports.configureSaw = configureSaw = (mod)->
|
||||
saw = mod.items["saw"]
|
||||
saw = mod.modPack.findItem "saw"
|
||||
if not saw?
|
||||
craftingTable = configureCraftingTable mod
|
||||
ironBlock = configureIronBlock mod
|
||||
@@ -284,13 +290,13 @@ exports.configureSaw = configureSaw = (mod)->
|
||||
|
||||
recipe = createRecipe output:createStack item:saw
|
||||
recipe.setInputAt 0, 0, createStack item:oakPlank
|
||||
recipe.setInputAt 0, 1, createStack item:ironIngot
|
||||
recipe.setInputAt 0, 2, createStack item:oakPlank
|
||||
recipe.setInputAt 1, 0, createStack item:oakPlank
|
||||
recipe.setInputAt 1, 1, createStack item:ironBlock
|
||||
recipe.setInputAt 1, 2, createStack item:oakPlank
|
||||
recipe.setInputAt 1, 0, createStack item:ironIngot
|
||||
recipe.setInputAt 2, 0, createStack item:oakPlank
|
||||
recipe.setInputAt 2, 1, createStack item:redstoneDust
|
||||
recipe.setInputAt 0, 1, createStack item:oakPlank
|
||||
recipe.setInputAt 1, 1, createStack item:ironBlock
|
||||
recipe.setInputAt 2, 1, createStack item:oakPlank
|
||||
recipe.setInputAt 0, 2, createStack item:oakPlank
|
||||
recipe.setInputAt 1, 2, createStack item:redstoneDust
|
||||
recipe.setInputAt 2, 2, createStack item:oakPlank
|
||||
recipe.addTool craftingTable
|
||||
|
||||
@@ -301,13 +307,13 @@ exports.configureSaw = configureSaw = (mod)->
|
||||
return saw
|
||||
|
||||
exports.configureSugarCane = configureSugarCane = (mod)->
|
||||
sugarCane = mod.items["sugar_cane"]
|
||||
sugarCane = mod.modPack.findItem "sugar_cane"
|
||||
if not sugarCane?
|
||||
sugarCane = createItem mod:mod, id:"sugar_cane", displayName:"Sugar Cane", isGatherable:true
|
||||
sugarCane = createItem mod:mod, id:"sugar_cane", displayName:"Sugar Cane"
|
||||
return sugarCane
|
||||
|
||||
exports.configureWheat = configureWheat = (mod)->
|
||||
wheat = mod.items["wheat"]
|
||||
wheat = mod.modPack.findItem "wheat"
|
||||
if not wheat?
|
||||
wheat = createItem mod:mod, id:"wheat", displayName:"Wheat", isGatherable:true
|
||||
wheat = createItem mod:mod, id:"wheat", displayName:"Wheat"
|
||||
return wheat
|
||||
@@ -5,239 +5,86 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
ItemSlug = require './item_slug'
|
||||
Stack = require './stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Inventory extends BaseModel
|
||||
module.exports = class Inventory
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
attributes.modPack ?= null
|
||||
@clear()
|
||||
constructor: (inventory=null)->
|
||||
@_id = _.uniqueId "inventory-"
|
||||
@_stacks = {}
|
||||
|
||||
if options.clone?
|
||||
@addInventory options.clone
|
||||
if inventory? then @merge inventory
|
||||
|
||||
# Class Methods ################################################################################
|
||||
# Properties ###################################################################################
|
||||
|
||||
@Delimiters =
|
||||
Item: '.'
|
||||
Stack: ':'
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
isEmpty:
|
||||
get: -> (id for id, stack of @_stacks).length is 0
|
||||
|
||||
stacks:
|
||||
get: -> return @_stacks
|
||||
set: -> throw new Error "stacks cannot be replaced"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (itemSlug, quantity=1, options={})->
|
||||
return this unless quantity > 0
|
||||
add: (item, quantity)->
|
||||
return unless item?
|
||||
return if quantity is 0
|
||||
|
||||
@_add itemSlug, quantity, options
|
||||
@trigger c.event.add, this, itemSlug, quantity
|
||||
@trigger c.event.change, this
|
||||
return this
|
||||
existingStack = @_stacks[item.id]
|
||||
if existingStack?
|
||||
if existingStack.quantity + quantity < 0 then throw new Error "cannot have a negative quantity"
|
||||
existingStack.quantity += quantity
|
||||
else
|
||||
if quantity < 0 then throw new Error "cannot have a negative quantity"
|
||||
@_stacks[item.id] = new Stack item:item, quantity:quantity
|
||||
|
||||
addInventory: (inventory)->
|
||||
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
|
||||
if @_stacks[item.id].quantity is 0
|
||||
delete @_stacks[item.id]
|
||||
|
||||
@trigger c.event.change, this
|
||||
return this
|
||||
|
||||
clear: (options={})->
|
||||
clear: ->
|
||||
@_stacks = {}
|
||||
@_itemSlugs = []
|
||||
|
||||
@trigger c.event.change, this
|
||||
contains: (item)->
|
||||
return @_stacks[item.id]?
|
||||
|
||||
clone: ->
|
||||
inventory = new Inventory
|
||||
inventory.addInventory this
|
||||
return inventory
|
||||
getQuantity: (item)->
|
||||
existingStack = @_stacks[item.id]
|
||||
return 0 unless existingStack?
|
||||
return existingStack.quantity
|
||||
|
||||
each: (callback)->
|
||||
for itemSlug in @_itemSlugs
|
||||
stack = @_stacks[itemSlug]
|
||||
continue unless stack?
|
||||
callback stack
|
||||
merge: (inventory)->
|
||||
for id, stack of inventory.stacks
|
||||
@add stack.item, stack.quantity
|
||||
|
||||
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
|
||||
|
||||
toDescription: ->
|
||||
return null if @isEmpty
|
||||
return null unless @modPack?
|
||||
|
||||
item = @modPack.findItem @_itemSlugs[0]
|
||||
extras = @_itemSlugs.length - 1
|
||||
|
||||
result = "#{item.name}"
|
||||
if extras > 0 then result += " and #{extras} more..."
|
||||
|
||||
return result
|
||||
|
||||
# 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
|
||||
remove: (item, quantity)->
|
||||
@add item, -1 * quantity
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") {items: ["]
|
||||
toString: (options={})->
|
||||
options.full ?= false
|
||||
|
||||
if options.full
|
||||
result = []
|
||||
needsDelimiter = false
|
||||
@each (stack)->
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push stack.toString()
|
||||
|
||||
stackList = (stack for itemId, stack of @_stacks)
|
||||
stackList.sort (a, b)->
|
||||
if a.item.displayName isnt b.item.displayName
|
||||
return if a.item.displayName < b.item.displayName then -1 else +1
|
||||
return 0
|
||||
|
||||
for stack in stackList
|
||||
if needsDelimiter then result.push ", "
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push '}'
|
||||
return result.join ''
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_add: (itemSlug, quantity=1, options={})->
|
||||
options.insert ?= false
|
||||
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
|
||||
if options.insert
|
||||
@_itemSlugs.unshift itemSlug
|
||||
result.push stack.quantity
|
||||
result.push " "
|
||||
result.push stack.item.displayName
|
||||
return result.join ""
|
||||
else
|
||||
@_itemSlugs.push itemSlug
|
||||
@_sort()
|
||||
else
|
||||
stack.quantity += quantity
|
||||
|
||||
_sort: ->
|
||||
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
|
||||
return "Inventory<#{@_id}>@#{(id for id, item of @_stacks).length}"
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
#
|
||||
# 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]
|
||||
@@ -5,86 +5,92 @@
|
||||
# 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
|
||||
module.exports = class Item
|
||||
|
||||
@Group = Other:'Other'
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@displayName = attributes.displayName
|
||||
@isGatherable = attributes.isGatherable
|
||||
@mod = attributes.mod
|
||||
|
||||
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()
|
||||
@_hasPrimaryRecipe = false
|
||||
@_recipesAsPrimary = {}
|
||||
@_recipesAsExtra = {}
|
||||
|
||||
# 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 #####################################################################
|
||||
displayName: # a string containing the user-facing name of this item
|
||||
get: -> return @_displayName
|
||||
set: (displayName)->
|
||||
if not displayName? then throw new Error "displayName is required"
|
||||
@_displayName = displayName
|
||||
|
||||
parse: (text)->
|
||||
ItemParser = require '../parsing/item_parser' # to avoid require cycles
|
||||
@_parser ?= new ItemParser model:this
|
||||
@_parser.parse text
|
||||
firstRecipe: # the first Recipe returned by iterating the `recipes` property
|
||||
get: ->
|
||||
recipeList = (recipe for id, recipe of @recipes)
|
||||
return null unless recipeList.length > 0
|
||||
return recipeList[0]
|
||||
set: -> throw new Error "firstRecipe cannot be assigned"
|
||||
|
||||
return null # prevent calling `set`
|
||||
id: # a string containing a unique identifier for this item
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
return if @_id is id
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
url: ->
|
||||
return c.url.itemData modSlug:@slug.mod, itemSlug:@slug.item
|
||||
isGatherable: # whether this item can be gathered directly without needing to be crafted
|
||||
get: ->
|
||||
return true if @_isGatherable is true
|
||||
return false if (id for id, recipe of @_recipesAsPrimary).length > 0
|
||||
return false if (id for id, recipe of @_recipesAsExtra).length > 0
|
||||
return true
|
||||
set: (isGatherable)->
|
||||
@_isGatherable = null unless isGatherable?
|
||||
@_isGatherable = !!isGatherable
|
||||
|
||||
mod: # the Mod which adds this item to the game
|
||||
get: -> return @_mod
|
||||
set: (mod)->
|
||||
if not mod? then throw new Error "mod is required"
|
||||
if @_mod is mod then return
|
||||
if @_mod? then throw new Error "mod cannot be reassigned"
|
||||
@_mod = mod
|
||||
@_mod.addItem this
|
||||
|
||||
modPack: # the ModPack containing this item
|
||||
get: -> return @_mod.modPack
|
||||
set: -> throw new Error "modPack cannot be assigned"
|
||||
|
||||
recipes: # a hash of recipeId to Recipe containing `recipesAsPrimary` if not empty or else `recipesAsExtra`
|
||||
get: -> return if @_hasPrimaryRecipe then @_recipesAsPrimary else @_recipesAsExtra
|
||||
set: -> throw new Error "recipes cannot be assigned"
|
||||
|
||||
recipesAsPrimary: # a hash of recipeId to Recipe where this item is the primary output
|
||||
get: -> return @_recipesAsPrimary
|
||||
set: -> throw new Error "recipes cannot be assigned"
|
||||
|
||||
recipesAsExtra: # a hash of recipeId to Recipe where this item is an extra output
|
||||
get: -> return @_recipesAsExtra
|
||||
set: -> throw new Error "recipesAsExtra cannot be assigned"
|
||||
|
||||
# Public Recipes ###############################################################################
|
||||
|
||||
addRecipe: (recipe)->
|
||||
if recipe.output.item is this
|
||||
@_recipesAsPrimary[recipe.id] = recipe
|
||||
@_hasPrimaryRecipe = true
|
||||
else if recipe.extras[this.id]?.item is this
|
||||
@_recipesAsExtra[recipe.id] = recipe
|
||||
else
|
||||
throw new Error "recipe<#{recipe.id}> does not produce this item<#{@id}>"
|
||||
|
||||
# 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()
|
||||
return "Item:#{@displayName}<#{@id}>"
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
#
|
||||
# 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'
|
||||
@@ -5,230 +5,58 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Mod extends BaseModel
|
||||
module.exports = class Mod
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.slug? then throw new Error 'attributes.slug is required'
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@displayName = attributes.displayName
|
||||
@modPack = attributes.modPack
|
||||
|
||||
attributes.author ?= ''
|
||||
attributes.description ?= ''
|
||||
attributes.documentationUrl ?= null
|
||||
attributes.downloadUrl ?= null
|
||||
attributes.homePageUrl ?= null
|
||||
attributes.modPack ?= null
|
||||
attributes.name ?= ''
|
||||
@_items = {}
|
||||
|
||||
super attributes, options
|
||||
|
||||
@_activeModVersion = null
|
||||
@_activeVersion = null
|
||||
@_modVersions = []
|
||||
@_tutorials = []
|
||||
|
||||
# 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 #############################################################################
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
activeModVersion:
|
||||
get: -> @_activeModVersion
|
||||
displayName:
|
||||
get: -> return @_displayName
|
||||
set: (displayName)->
|
||||
if not displayName? then throw new Error "displayName is required"
|
||||
return if @_displayName is displayName
|
||||
@_displayName = displayName
|
||||
|
||||
activeVersion:
|
||||
get: ->
|
||||
return @_activeVersion
|
||||
id:
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
return if @_id is id
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
set: (version)->
|
||||
return if version is @_activeVersion
|
||||
items:
|
||||
get: -> return @_items
|
||||
set: -> throw new Error "items cannot be replaced"
|
||||
|
||||
version ?= Mod.Version.None
|
||||
if version is Mod.Version.Latest then version = _.last(@_modVersions).version
|
||||
modPack:
|
||||
get: -> return @_modPack
|
||||
set: (modPack)->
|
||||
if not modPack? then throw new Error "modPack is required"
|
||||
if @_modPack is modPack then return
|
||||
if @_modPack? then throw new Error "modPack cannot be reassigned"
|
||||
@_modPack = modPack
|
||||
@_modPack.addMod this
|
||||
|
||||
if version is Mod.Version.None
|
||||
@_activeVersion = version
|
||||
@_activateModVersion null
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
@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
|
||||
addItem: (item)->
|
||||
if not item? then return
|
||||
if @_items[item.id] is item then return
|
||||
@_items[item.id] = item
|
||||
item.mod = this
|
||||
|
||||
@_activeVersion = version
|
||||
@trigger c.event.change + ':activeVersion', this, @_activeVersion
|
||||
@trigger c.event.change, this
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
enabled:
|
||||
get: -> @_activeModVersion?
|
||||
|
||||
modVersions:
|
||||
get: -> @_modVersions[..]
|
||||
|
||||
tutorials:
|
||||
get: -> @getAllTutorials()
|
||||
|
||||
# Item Methods #################################################################################
|
||||
|
||||
chooseRandomItem: ->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
return null unless effectiveModVersion?
|
||||
|
||||
return effectiveModVersion.chooseRandomItem()
|
||||
|
||||
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.fileCache = this.fileCache
|
||||
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
|
||||
|
||||
getAllModVersions: ->
|
||||
return @_modVersions[..]
|
||||
|
||||
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
|
||||
|
||||
@_verifyActiveModVersion()
|
||||
|
||||
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
|
||||
toString: ->
|
||||
return "Mod:#{@displayName}<#{@id}>"
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
@@ -5,198 +5,55 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Mod = require './mod'
|
||||
ModVersionParser = require '../parsing/mod_version_parser'
|
||||
Recipe = require './recipe'
|
||||
SimpleInventory = require '../crafting/simple_inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPack extends BaseModel
|
||||
module.exports = class ModPack
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@displayName = attributes.displayName
|
||||
|
||||
@_mods = []
|
||||
@_cache = {}
|
||||
@_mods = {}
|
||||
|
||||
@on c.event.change, => @_cache = {}
|
||||
# Property Methods #############################################################################
|
||||
|
||||
# Item Methods #################################################################################
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
chooseRandomItem: ->
|
||||
return null unless @_mods.length > 0
|
||||
displayName: # a string containing the user-displayable name of this ModPack
|
||||
get: -> return @_displayName
|
||||
set: (displayName)->
|
||||
if not displayName? then throw new Error "displayName is required"
|
||||
if @_displayName is displayName then return
|
||||
@_displayName = displayName
|
||||
|
||||
modIndex = Math.floor Math.random() * @_mods.length
|
||||
return @_mods[modIndex].chooseRandomItem()
|
||||
id: # a string which uniquely identifies this ModPack
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
if @_id is id then return
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
findItem: (itemSlug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
mods: # a hash of mod id to Mod containing all the mods which are part of this ModPack
|
||||
get: -> return @_mods
|
||||
set: -> throw new Error "mods cannot be replaced"
|
||||
|
||||
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 SimpleInventory 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 ##################################################################################
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addMod: (mod)->
|
||||
if not mod? then throw new Error 'mod is required'
|
||||
return if @_mods.indexOf(mod) isnt -1
|
||||
|
||||
if not mod? then return
|
||||
if @_mods[mod.id] is mod then return
|
||||
@_mods[mod.id] = mod
|
||||
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[..]
|
||||
|
||||
removeMod: (mod)->
|
||||
index = @_mods.indexOf mod
|
||||
return unless index >= 0
|
||||
|
||||
@_mods.splice index, 1
|
||||
|
||||
@trigger c.event.remove, this, mod.slug
|
||||
@trigger c.event.change, this
|
||||
|
||||
# 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
|
||||
findItem: (itemId)->
|
||||
for modId, mod of @mods
|
||||
item = mod.items[itemId]
|
||||
return item if item?
|
||||
|
||||
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)->
|
||||
mods = @getAllMods()
|
||||
return true unless mods.length > 0
|
||||
|
||||
for mod in mods
|
||||
if mod.isError
|
||||
@removeMod mod
|
||||
continue
|
||||
|
||||
modVersions = mod.getAllModVersions()
|
||||
return true unless modVersions.length > 0
|
||||
continue if mod.activeVersion is Mod.Version.None
|
||||
|
||||
activeModVersion = mod.activeModVersion
|
||||
return true unless activeModVersion?
|
||||
return true if activeModVersion.isUnloaded
|
||||
return true if activeModVersion.isLoading
|
||||
|
||||
@trigger c.event.change, this
|
||||
@trigger c.event.sync, this
|
||||
return "ModPack:#{@displayName}<#{@id}>"
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#
|
||||
# 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'
|
||||
@@ -1,106 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
recipe:; input: Copper Ingot; pattern: ... 0.0 .0.; extras: Copper Nugget
|
||||
"""
|
||||
|
||||
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', 'bucket']
|
||||
|
||||
it 'skip recipes whose conditions are not met', ->
|
||||
recipes = modVersion.findRecipes ItemSlug.slugify('test__cake')
|
||||
recipes.length.should.equal 2
|
||||
|
||||
it 'finds recipes for items which are only ever extras', ->
|
||||
recipes = modVersion.findRecipes ItemSlug.slugify('test__copper_nugget')
|
||||
recipes.length.should.equal 1
|
||||
@@ -1,85 +0,0 @@
|
||||
#
|
||||
# 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'
|
||||
@@ -5,243 +5,170 @@
|
||||
# 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
|
||||
module.exports = class Recipe
|
||||
|
||||
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'
|
||||
constructor: (attributes={})->
|
||||
@depth = attributes.depth
|
||||
@height = attributes.height
|
||||
@id = attributes.id
|
||||
@output = attributes.output
|
||||
@width = attributes.width
|
||||
|
||||
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'
|
||||
@_extras = {}
|
||||
@_inputs = {}
|
||||
@_inputGrid = []
|
||||
@_tools = {}
|
||||
|
||||
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)->
|
||||
return @getQuantityProduced(itemSlug) is @getQuantityRequired(itemSlug)
|
||||
|
||||
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 #############################################################################
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
slug:
|
||||
get: ->
|
||||
if not @_slug?
|
||||
builder = new StringBuilder
|
||||
delimiterNeeded = false
|
||||
for stack in @input
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
allProducts: # an array of Stacks starting the the primary output of this recipe
|
||||
get: -> return [].concat @output, (stack for id, stack of @extras)
|
||||
set: -> throw new Error "allProducts cannot be assigned"
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
depth: # an integer specifying the number of layers to this recipe
|
||||
get: -> return @_depth
|
||||
set: (depth)->
|
||||
depth = parseInt "#{depth}"
|
||||
depth = if Number.isNaN(depth) then 0 else Math.max(0, depth)
|
||||
@_depth = depth
|
||||
|
||||
builder.push '>'
|
||||
builder.push @pattern
|
||||
builder.push '>'
|
||||
for stack in @tools
|
||||
builder.push stack.itemSlug.qualified
|
||||
builder.push '>'
|
||||
extras: # a hash of item id to Stack of all the non-primary outputs of this recipe
|
||||
get: -> return @_extras
|
||||
set: -> throw new Error "extras cannot be replaced"
|
||||
|
||||
delimiterNeeded = false
|
||||
for stack in @output
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
id: # a string which uniquely identifies this recipe
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
if @_id is id then return
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
height: # an integer specifying the number of rows needed by this recipe
|
||||
get: -> return @_height
|
||||
set: (height)->
|
||||
height = parseInt "#{height}"
|
||||
height = if Number.isNaN(height) then 0 else Math.max(0, height)
|
||||
@_height = height
|
||||
|
||||
@_slug = builder.toString()
|
||||
inputs: # a hash of item id to Item containing all the inputs to this recipe
|
||||
get: -> return @_inputs
|
||||
set: -> throw new Error "inputs cannot be replaced"
|
||||
|
||||
return @_slug
|
||||
needsTools: # a boolean indicating whether this recipe requires a tool
|
||||
get: -> return (id for id, toolItem of @_tools).length > 0
|
||||
|
||||
output: # a Stack specifying the primary output of this recipe
|
||||
get: -> return @_output
|
||||
set: (output)->
|
||||
if not output? then throw new Error "output is required"
|
||||
if @_output is output then return
|
||||
if @_output? then throw new Error "output cannot be reassigned"
|
||||
@_output = output
|
||||
@_output.item.addRecipe this
|
||||
|
||||
modPack: # the ModPack to which this recipe belongs
|
||||
get: -> return @_output.modPack
|
||||
set: -> throw new Error "modPack cannot be replaced"
|
||||
|
||||
tools: # a hash of item id to Item of all the tools required for this recipe
|
||||
get: -> return @_tools
|
||||
set: -> throw new Error "tools cannot be assigned"
|
||||
|
||||
width: # an integer specifying the number of columns needed by this recipe
|
||||
get: -> return @_width
|
||||
set: (width)->
|
||||
width = parseInt "#{width}"
|
||||
width = if Number.isNaN(width) then 0 else Math.max(0, width)
|
||||
@_width = width
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addExtra: (stack)->
|
||||
return unless stack
|
||||
return if @_extras[stack.item.id] is stack
|
||||
@_extras[stack.item.id] = stack
|
||||
stack.item.addRecipe this
|
||||
|
||||
addTool: (item)->
|
||||
return unless item
|
||||
@_tools[item.id] = item
|
||||
|
||||
computeQuantityRequired: (item)->
|
||||
result = 0
|
||||
|
||||
for x in [0...@width]
|
||||
for y in [0...@height]
|
||||
for z in [0...@depth]
|
||||
stack = @getInputAt x, y, z
|
||||
continue unless stack?
|
||||
continue unless stack.item.id is item.id
|
||||
result += stack.quantity
|
||||
|
||||
return result
|
||||
|
||||
computeQuantityProduced: (item)->
|
||||
result = 0
|
||||
|
||||
if @_output.item.id is item.id
|
||||
result += @_output.quantity
|
||||
|
||||
for itemId, stack of @_extras
|
||||
continue unless itemId is item.id
|
||||
result += stack.quantity
|
||||
|
||||
return result
|
||||
|
||||
getInputAt: ->
|
||||
[x, y, z] = [0, 0, 0]
|
||||
if arguments.length is 3
|
||||
[x, y, z] = arguments
|
||||
else
|
||||
[x, y] = arguments
|
||||
|
||||
return @_inputGrid[x]?[y]?[z] or null
|
||||
|
||||
setInputAt: ->
|
||||
[x, y, z, stack] = [0, 0, 0, null]
|
||||
if arguments.length is 4
|
||||
[x, y, z, stack] = arguments
|
||||
else
|
||||
[x, y, stack] = arguments
|
||||
|
||||
@_depth = Math.max @_depth, z + 1
|
||||
@_height = Math.max @_height, y + 1
|
||||
@_width = Math.max @_width, x + 1
|
||||
|
||||
@_inputGrid[x] ?= []
|
||||
@_inputGrid[x][y] ?= []
|
||||
@_inputGrid[x][y][z] = stack
|
||||
|
||||
if stack?.item? then @_inputs[stack.item.id] = stack.item
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") { name:", @name]
|
||||
toString: (options={})->
|
||||
options.full ?= false
|
||||
|
||||
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 ']'
|
||||
if options.full
|
||||
b = new StringBuilder
|
||||
b.loop (item for id, item of @inputs), delimiter:" + ", onEach:(b, item)=>
|
||||
b.push @computeQuantityRequired(item), " ", item.displayName
|
||||
b.push " ="
|
||||
b.onlyIf @needsTools, (b)=>
|
||||
b.push "("
|
||||
b.loop (toolItem for id, toolItem of @tools), onEach:(b, toolItem)-> b.push toolItem.displayName
|
||||
b.push ")"
|
||||
b.push "=> "
|
||||
b.loop @allProducts, delimiter:" + ", onEach:(b, stack)=>
|
||||
b.push stack.quantity, " ", stack.item.displayName
|
||||
|
||||
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 = {}
|
||||
|
||||
index = 0
|
||||
while index < pattern.length
|
||||
c = pattern[index]
|
||||
index += 1
|
||||
|
||||
continue if c is '.'
|
||||
continue if c is ' '
|
||||
|
||||
if quantityMap[c]?
|
||||
quantityMap[c] += 1
|
||||
return b.toString()
|
||||
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
|
||||
return "Recipe:#{@output}<#{@id}>"
|
||||
|
||||
@@ -5,86 +5,68 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require './item'
|
||||
ItemSlug = require './item_slug'
|
||||
Recipe = require './recipe'
|
||||
Stack = require './stack'
|
||||
fixtures = require "../fixtures"
|
||||
Item = require "./item"
|
||||
Recipe = require "./recipe"
|
||||
Stack = require "./stack"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
input = output = pattern = recipe = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'recipe.coffee', ->
|
||||
|
||||
describe 'constructor', ->
|
||||
describe "Recipe", ->
|
||||
|
||||
beforeEach ->
|
||||
input = [
|
||||
new Stack(itemSlug:new ItemSlug('iron_gear')),
|
||||
new Stack(itemSlug:new ItemSlug('gold_ingot'), quantity:4)
|
||||
]
|
||||
pattern = '.1. 101 .1.'
|
||||
@mod = fixtures.createMod()
|
||||
@stick = fixtures.configureStick @mod
|
||||
@ironIngot = fixtures.configureIronIngot @mod
|
||||
@obsidian = fixtures.configureObsidian @mod
|
||||
|
||||
it 'requires input', ->
|
||||
expect(-> new Recipe slug:'gold_gear', pattern:pattern).to.throw Error, 'attributes.input is required'
|
||||
@ironSword = new Item id:"iron_sword", displayName:"Iron Sword", mod:@mod
|
||||
@obsidianBox = new Item id:"obsidian_box", displayName:"Obsidian Box", mod:@mod
|
||||
|
||||
it 'requires a pattern', ->
|
||||
expect(-> new Recipe slug:'gold_gear', input:input).to.throw Error, 'attributes.pattern is required'
|
||||
describe "getting & setting inputs", ->
|
||||
|
||||
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', ->
|
||||
describe "for 2D recipes", ->
|
||||
|
||||
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.'
|
||||
@recipe = new Recipe id:"test1", output:new Stack item:@ironSword
|
||||
@recipe.setInputAt 1, 0, new Stack item:@ironIngot
|
||||
@recipe.setInputAt 1, 1, new Stack item:@ironIngot
|
||||
@recipe.setInputAt 1, 2, new Stack item:@stick
|
||||
|
||||
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 assigned values as expected", ->
|
||||
expect(@recipe.getInputAt(0, 0)).to.equal null
|
||||
@recipe.getInputAt(1, 0).item.displayName.should.equal "Iron Ingot"
|
||||
@recipe.getInputAt(1, 1).item.displayName.should.equal "Iron Ingot"
|
||||
@recipe.getInputAt(1, 2).item.displayName.should.equal "Stick"
|
||||
expect(@recipe.getInputAt(2, 2)).to.equal null
|
||||
|
||||
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 "determines the correct dimentions", ->
|
||||
@recipe.depth.should.equal 1
|
||||
@recipe.height.should.equal 3
|
||||
@recipe.width.should.equal 2
|
||||
|
||||
it 'returns null for an invalid slot', ->
|
||||
expect(recipe.getStackAtSlot(12)).to.be.null
|
||||
|
||||
describe '_parsePattern', ->
|
||||
describe "for 3D recipes", ->
|
||||
|
||||
beforeEach ->
|
||||
recipe = new Recipe
|
||||
itemSlug: 'oak_wood_planks',
|
||||
input: [new Stack itemSlug:new ItemSlug('oak_wood')],
|
||||
pattern:'... .0. ...'
|
||||
@recipe = new Recipe id:"test2", output:new Stack item:@obsidianBox
|
||||
for x in [0..2]
|
||||
for y in [0..2]
|
||||
for z in [0..2]
|
||||
continue if x is 1 and y is 1
|
||||
continue if x is 1 and z is 1
|
||||
continue if y is 1 and z is 1
|
||||
|
||||
it 'normalizes invalid characters', ->
|
||||
recipe._parsePattern('$$0 #() 010').should.equal '..0 ... 010'
|
||||
@recipe.setInputAt x, y, z, new Stack item:@obsidian
|
||||
|
||||
it 'removes extra characters', ->
|
||||
recipe._parsePattern('000 000 000 000').should.equal '000 000 000'
|
||||
it "returns assigned values as expected", ->
|
||||
@recipe.getInputAt(0, 0, 0).item.displayName.should.equal "Obsidian"
|
||||
@recipe.getInputAt(2, 0, 0).item.displayName.should.equal "Obsidian"
|
||||
@recipe.getInputAt(0, 2, 0).item.displayName.should.equal "Obsidian"
|
||||
@recipe.getInputAt(2, 2, 2).item.displayName.should.equal "Obsidian"
|
||||
expect(@recipe.getInputAt(1, 1, 1)).to.equal null
|
||||
expect(@recipe.getInputAt(1, 1, 0)).to.equal null
|
||||
|
||||
it 'fills in missing characters', ->
|
||||
recipe._parsePattern('000000').should.equal '000 000 ...'
|
||||
|
||||
it 'fills in spaces', ->
|
||||
recipe._parsePattern('000000000').should.equal '000 000 000'
|
||||
it "determines the correct dimentions", ->
|
||||
@recipe.depth.should.equal 3
|
||||
@recipe.height.should.equal 3
|
||||
@recipe.width.should.equal 3
|
||||
@@ -5,19 +5,38 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Stack extends BaseModel
|
||||
module.exports = class Stack
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
|
||||
attributes.quantity ?= 1
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
constructor: (attributes={})->
|
||||
@item = attributes.item
|
||||
@quantity = attributes.quantity
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
item:
|
||||
get: -> return @_item
|
||||
set: (item)->
|
||||
if not item? then throw new Error "item is required"
|
||||
if @_item is item then return
|
||||
if @_item? then throw new Error "item cannot be reassigned"
|
||||
@_item = item
|
||||
|
||||
modPack:
|
||||
get: -> return @_item.modPack
|
||||
set: -> throw new Error "modPack cannot be replaced"
|
||||
|
||||
quantity:
|
||||
get: -> return @_quantity
|
||||
set: (quantity)->
|
||||
quantity = parseInt "#{quantity}"
|
||||
quantity = if Number.isNaN(quantity) then 0 else Math.max(0, quantity)
|
||||
@_quantity = quantity
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@quantity} #{@itemSlug}"
|
||||
return "Stack:#{@item}×#{@quantity}"
|
||||
@@ -0,0 +1,124 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack_json.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
fixtures = require "../fixtures"
|
||||
ModPackJsonFormatter = require "./mod_pack_json_formatter"
|
||||
ModPackJsonParser = require "./mod_pack_json_parser"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe "ModPackJsonParser & ModPackJsonFormatter", ->
|
||||
|
||||
beforeEach ->
|
||||
@formatter = new ModPackJsonFormatter
|
||||
@parser = new ModPackJsonParser
|
||||
|
||||
@modPack = fixtures.createModPack id:"alpha", displayName:"ALPHA"
|
||||
|
||||
@runTest = =>
|
||||
@string1 = @formatter.format @modPack
|
||||
@string2 = @formatter.format @parser.parse @string1
|
||||
@result = @parser.parse @string2
|
||||
|
||||
describe "an empty modpack", ->
|
||||
|
||||
beforeEach -> @runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "contains the modpack's own properties", ->
|
||||
@result.id.should.equal @modPack.id
|
||||
@result.displayName.should.equal @modPack.displayName
|
||||
|
||||
it "doesn't contain a mods list", ->
|
||||
expect(@result.mods).to.beUndefined
|
||||
|
||||
describe "a modpack with a single mod", ->
|
||||
|
||||
beforeEach ->
|
||||
@mod = fixtures.createMod modPack:@modPack, id:"bravo", displayName:"BRAVO"
|
||||
|
||||
describe "containing only a gatherable item", ->
|
||||
|
||||
beforeEach ->
|
||||
@oakWood = fixtures.configureOakWood @mod
|
||||
@runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "contains the correct mod", ->
|
||||
mod = @result.mods[@mod.id]
|
||||
mod.displayName.should.equal @mod.displayName
|
||||
|
||||
it "contains the item", ->
|
||||
item = @result.mods[@mod.id].items[@oakWood.id]
|
||||
item.displayName.should.equal @oakWood.displayName
|
||||
|
||||
describe "containing a multi-step item & it's requirements", ->
|
||||
|
||||
beforeEach ->
|
||||
@craftingTable = fixtures.configureCraftingTable @mod
|
||||
@oakPlank = fixtures.configureOakPlank @mod
|
||||
@runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "contains oak planks", ->
|
||||
item = @result.mods[@mod.id].items[@oakPlank.id]
|
||||
item.displayName.should.equal @oakPlank.displayName
|
||||
|
||||
it "has the recipe for a crafting table", ->
|
||||
recipe = @result.mods[@mod.id].items[@craftingTable.id].firstRecipe
|
||||
recipe.getInputAt(0, 0).item.id.should.equal @oakPlank.id
|
||||
recipe.getInputAt(0, 1).item.id.should.equal @oakPlank.id
|
||||
recipe.getInputAt(1, 0).item.id.should.equal @oakPlank.id
|
||||
recipe.getInputAt(1, 1).item.id.should.equal @oakPlank.id
|
||||
recipe.output.quantity.should.equal 1
|
||||
|
||||
describe "containing a complex item which needs tools & it's requirements", ->
|
||||
|
||||
beforeEach ->
|
||||
@cake = fixtures.configureCake @mod
|
||||
@runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "has the recipe for a cake", ->
|
||||
recipe = @result.mods[@mod.id].items[@cake.id].firstRecipe
|
||||
|
||||
describe "a modpack with multiple mods", ->
|
||||
|
||||
beforeEach ->
|
||||
@modA = fixtures.createMod modPack:@modPack, id:"bravo", displayName:"BRAVO"
|
||||
@modB = fixtures.createMod modPack:@modPack, id:"charlie", displayName:"CHARLIE"
|
||||
|
||||
describe "where items are used in recipes crossing mods", ->
|
||||
|
||||
beforeEach ->
|
||||
@stick = fixtures.configureStick @modA
|
||||
@ironIngot = fixtures.configureIronIngot @modA
|
||||
@ironSword = fixtures.configureIronSword @modB
|
||||
@runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "has each item in the correct mod", ->
|
||||
@result.mods[@modA.id].items[@ironIngot.id].displayName.should.equal @ironIngot.displayName
|
||||
@result.mods[@modB.id].items[@ironSword.id].displayName.should.equal @ironSword.displayName
|
||||
|
||||
it "has the correct recipe for an iron sword", ->
|
||||
recipe = @result.mods[@modB.id].items[@ironSword.id].firstRecipe
|
||||
recipe.output.item.id.should.equal @ironSword.id
|
||||
recipe.output.quantity.should.equal 1
|
||||
recipe.getInputAt(1, 0).item.id.should.equal @ironIngot.id
|
||||
recipe.getInputAt(1, 1).item.id.should.equal @ironIngot.id
|
||||
recipe.getInputAt(1, 2).item.id.should.equal @stick.id
|
||||
@@ -0,0 +1,101 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack_json_formatter.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPackJsonParser
|
||||
|
||||
constructor: ->
|
||||
@_reset()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
format: (modPack)->
|
||||
@_reset()
|
||||
return JSON.stringify @_formatModPack modPack
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_formatItem: (item)->
|
||||
result = {}
|
||||
result.id = item.id
|
||||
result.displayName = item.displayName
|
||||
|
||||
if item.isGatherable and item.firstRecipe?
|
||||
result.gatherable = true
|
||||
|
||||
return result
|
||||
|
||||
_formatMod: (mod)->
|
||||
result = {}
|
||||
result.id = mod.id
|
||||
result.displayName = mod.displayName
|
||||
|
||||
for itemId, item of mod.items
|
||||
result.items ?= []
|
||||
@_itemIndexById[item.id] = @_itemIndex++
|
||||
result.items.push @_formatItem item
|
||||
|
||||
return result
|
||||
|
||||
_formatModPack: (modPack)->
|
||||
result = {}
|
||||
result.id = modPack.id
|
||||
result.displayName = modPack.displayName
|
||||
|
||||
for modId, mod of modPack.mods
|
||||
result.mods ?= []
|
||||
result.mods.push @_formatMod mod
|
||||
|
||||
if result.mods?
|
||||
for modResult in result.mods
|
||||
for itemResult in modResult.items
|
||||
item = modPack.mods[modResult.id].items[itemResult.id]
|
||||
for recipeId, recipe of item.recipesAsPrimary
|
||||
itemResult.recipes ?= []
|
||||
itemResult.recipes.push @_formatRecipe recipe
|
||||
|
||||
return result
|
||||
|
||||
_formatRecipe: (recipe)->
|
||||
result = {}
|
||||
result.id = recipe.id
|
||||
|
||||
if recipe.output.quantity > 1
|
||||
result.quantity = recipe.output.quantity
|
||||
|
||||
result.width = recipe.width
|
||||
result.height = recipe.height
|
||||
result.depth = recipe.depth if recipe.depth > 1
|
||||
result.inputs = []
|
||||
|
||||
for x in [0...recipe.width]
|
||||
for y in [0...recipe.height]
|
||||
for z in [0...recipe.depth]
|
||||
inputStack = @_formatStack recipe.getInputAt x, y, z
|
||||
result.inputs.push inputStack
|
||||
|
||||
for itemId, stack of recipe.extras
|
||||
result.extras ?= []
|
||||
result.extras.push @_formatStack stack
|
||||
|
||||
for itemId, item of recipe.tools
|
||||
result.tools ?= []
|
||||
result.tools.push @_itemIndexById[itemId]
|
||||
|
||||
return result
|
||||
|
||||
_formatStack: (stack)->
|
||||
return null unless stack?
|
||||
|
||||
itemIndex = @_itemIndexById[stack.item.id]
|
||||
if stack.quantity is 1 then return itemIndex
|
||||
return [itemIndex, stack.quantity]
|
||||
|
||||
_reset: ->
|
||||
@_itemIndex = 0
|
||||
@_itemIndexById = {}
|
||||
@@ -0,0 +1,164 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack_json_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require "../game/item"
|
||||
Mod = require "../game/mod"
|
||||
ModPack = require "../game/mod_pack"
|
||||
Recipe = require "../game/recipe"
|
||||
Stack = require "../game/stack"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPackJsonParser
|
||||
|
||||
constructor: ->
|
||||
@_reset()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
parse: (arg, fileName=null)->
|
||||
@_reset()
|
||||
@_fileName = fileName
|
||||
|
||||
if _.isString arg
|
||||
@_parseText arg
|
||||
else
|
||||
@_parseObject arg
|
||||
|
||||
return @_modPack
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_parseInteger: (text, defaultValue)->
|
||||
result = parseInt "#{text}"
|
||||
result = if Number.isNaN result then defaultValue else result
|
||||
return result
|
||||
|
||||
_parseText: (text)->
|
||||
try
|
||||
obj = JSON.parse text
|
||||
catch error
|
||||
@_throwError "could not parse JSON: #{error}"
|
||||
|
||||
@_parseObject obj
|
||||
|
||||
_parseObject: (obj)->
|
||||
@_data = obj
|
||||
@_parseModPack()
|
||||
@_parseMods()
|
||||
@_parseItems()
|
||||
@_parseRecipes()
|
||||
|
||||
_parseModPack: ->
|
||||
if not @_data? then @_throwError "there is no valid data"
|
||||
if not @_data.id? then @_throwError "modPack requires an id"
|
||||
if not @_data.displayName? then @_throwError "modPack requires a displayName"
|
||||
|
||||
@_modPack = new ModPack id:@_data.id, displayName:@_data.displayName
|
||||
|
||||
_parseMods: ->
|
||||
return unless @_data.mods?
|
||||
|
||||
for modData, index in @_data.mods
|
||||
@_location = "mods[#{index}]"
|
||||
if not modData.id? then @_throwError "mod requires an id"
|
||||
if not modData.displayName? then @_throwError "mod requires a displayName"
|
||||
|
||||
new Mod modPack:@_modPack, id:modData.id, displayName:modData.displayName
|
||||
|
||||
_parseItems: ->
|
||||
return unless @_data.mods?
|
||||
|
||||
for modData in @_data.mods
|
||||
continue unless modData.items?
|
||||
|
||||
mod = @_modPack.mods[modData.id]
|
||||
for itemData, index in modData.items
|
||||
@_location = "<#{mod.id}>.items[#{index}]"
|
||||
if not itemData.id? then @_throwError "item requires an id"
|
||||
if not itemData.displayName? then @_throwError "item requires a displayName"
|
||||
|
||||
item = new Item mod:mod, id:itemData.id, displayName:itemData.displayName
|
||||
item.gatherable = itemData.gatherable if itemData.gatherable?
|
||||
@_items.push item
|
||||
|
||||
_parseRecipes: ->
|
||||
return unless @_data.mods?
|
||||
|
||||
for modData in @_data.mods
|
||||
continue unless modData.items?
|
||||
|
||||
mod = @_modPack.mods[modData.id]
|
||||
for itemData, index in modData.items
|
||||
continue unless itemData.recipes?
|
||||
|
||||
item = mod.items[itemData.id]
|
||||
for recipeData, index in itemData.recipes
|
||||
@_location = "<#{itemData.id}>.recipes[#{index}]"
|
||||
if not recipeData.id? then @_throwError "recipe requires id"
|
||||
if not recipeData.inputs? then @_throwError "recipe requires inputs"
|
||||
|
||||
quantity = @_parseInteger recipeData.quantity, 1
|
||||
outputStack = new Stack item:item, quantity:quantity
|
||||
recipe = new Recipe id:recipeData.id, output:outputStack
|
||||
|
||||
depth = @_parseInteger recipeData.depth, 1
|
||||
height = @_parseInteger recipeData.height, 3
|
||||
width = @_parseInteger recipeData.width, 3
|
||||
|
||||
index = 0
|
||||
for x in [0...width]
|
||||
for y in [0...height]
|
||||
for z in [0...depth]
|
||||
stack = @_parseStack recipeData.inputs[index]
|
||||
if stack? then recipe.setInputAt x, y, z, stack
|
||||
index++
|
||||
|
||||
if recipeData.extras
|
||||
for stackData in recipeData.extras
|
||||
recipe.addExtra @_parseStack stackData
|
||||
|
||||
if recipeData.tools
|
||||
for index in recipeData.tools
|
||||
toolItem = @_items[index]
|
||||
if not toolItem? then @_throwError "there is no item #{index}"
|
||||
recipe.addTool toolItem
|
||||
|
||||
_parseStack: (stackData)->
|
||||
return null unless stackData?
|
||||
if _.isArray(stackData)
|
||||
if stackData.length isnt 2 then @_throwError "input stacks must have an item index and a quantity"
|
||||
index = stackData[0]
|
||||
quantity = stackData[1]
|
||||
else
|
||||
index = stackData
|
||||
quantity = 1
|
||||
|
||||
item = @_items[index]
|
||||
if not item? then @_throwError "there is no item #{index}"
|
||||
|
||||
return new Stack item:item, quantity:quantity
|
||||
|
||||
_reset: ->
|
||||
@_data = null
|
||||
@_fileName = null
|
||||
@_items = []
|
||||
@_location = null
|
||||
@_modPack = null
|
||||
|
||||
_throwError: (message, cause=null)->
|
||||
if @_location? then message = "#{@_location}: #{message}"
|
||||
if @_fileName? and @_location? then message = "@#{message}"
|
||||
if @_fileName? then message = "#{@_fileName}#{message}"
|
||||
if cause? then message = "#{message}: #{cause}"
|
||||
|
||||
error = new Error message
|
||||
error.cause = cause if cause?
|
||||
error.fileName = @_fileName if @_fileName?
|
||||
error.location = @_location if @_location?
|
||||
|
||||
throw error
|
||||
@@ -0,0 +1,55 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack_store.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
ModPackJsonParser = require "../parsing/mod_pack_json_parser"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPackStore
|
||||
|
||||
constructor: ->
|
||||
@_data = {}
|
||||
@_loading = {}
|
||||
@_parser = new ModPackJsonParser
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
Object.defineProperties ModPackStore,
|
||||
instance:
|
||||
get: ->
|
||||
@_instance ?= new ModPackStore
|
||||
return @_instance
|
||||
set: ->
|
||||
throw new Error "cannot assign instance"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
get: (modPackId)->
|
||||
return @_data[modPackId]
|
||||
|
||||
load: (modPackId)->
|
||||
if not @_loading[modPackId]?
|
||||
@_loading[modPackId] = w.promise (resolve, reject)->
|
||||
url = c.url.modPackArchive modPackId:modPackId
|
||||
|
||||
onError = (xhr, status, message)=>
|
||||
logger.error "failed to load mod pack #{modPackId}: #{status} — #{message}"
|
||||
reject new Error message
|
||||
|
||||
onSuccess = (data, status, xhr)=>
|
||||
logger.info "laoded mod pack: #{modPackId}"
|
||||
try
|
||||
@_parser.reset()
|
||||
modPack = @_parser.parse data, url
|
||||
@_data[modPack.id] = modPack
|
||||
resolve modPack
|
||||
catch error
|
||||
reject error
|
||||
|
||||
$.ajax dataType: "text", error: onError, success: onSuccess, url: url
|
||||
|
||||
return @_loading[modPackId]
|
||||
@@ -15,6 +15,7 @@ HeaderController = require './header/header_controller'
|
||||
ImageLoader = require './image_loader'
|
||||
Mod = require '../models/game/mod'
|
||||
ModPack = require '../models/game/mod_pack'
|
||||
ModPackStore = require '../models/store/mod_pack_store'
|
||||
Router = require './router'
|
||||
|
||||
########################################################################################################################
|
||||
@@ -30,7 +31,6 @@ module.exports = class SiteController extends BaseController
|
||||
@client = options.client
|
||||
@fileCache = new FileCache c.url.modpackArchive()
|
||||
@imageLoader = new ImageLoader defaultUrl:'/images/unknown.png'
|
||||
@modPack = new ModPack {}, fileCache:@fileCache
|
||||
@router = new Router this
|
||||
@storage = options.storage
|
||||
|
||||
@@ -41,23 +41,6 @@ module.exports = class SiteController extends BaseController
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
loadDefaultModPack: ->
|
||||
makeResponder = (m)-> return ->
|
||||
m.activeModVersion.fetch() if m.activeModVersion?
|
||||
|
||||
for modSlug, modData of c.defaultMods
|
||||
mod = new Mod {slug:modSlug}, {fileCache:@fileCache}
|
||||
mod.on c.event.change + ':activeModVersion', makeResponder mod
|
||||
@storage.register "mod:#{mod.slug}", mod, 'activeVersion', modData.defaultVersion
|
||||
mod.fetch()
|
||||
|
||||
@modPack.addMod mod
|
||||
|
||||
if global.env isnt 'prerender'
|
||||
@modPack.once c.event.sync, =>
|
||||
@$pageContent.removeClass 'hidden'
|
||||
@$pageContentLoading.addClass 'hidden'
|
||||
|
||||
loadCurrentUser: ->
|
||||
@client.getCurrentUser()
|
||||
.then (response)=>
|
||||
|
||||
@@ -29,46 +29,6 @@ adsense.skyscraper.margin = 24 # px
|
||||
adsense.skyscraper.slotIds = ['7613920409', '9574673605', '3388539204']
|
||||
adsense.skyscraper.width = 160 # px
|
||||
|
||||
exports.defaultMods = defaultMods = {}
|
||||
defaultMods.minecraft = { defaultVersion: '1.7.10' } # Minecraft must be first
|
||||
|
||||
defaultMods.advanced_solar_panels = { defaultVersion: '3.5.1' }
|
||||
defaultMods.agricraft = { defaultVersion: '1.4.6' }
|
||||
defaultMods.applied_energistics_2 = { defaultVersion: 'rv1-stable-1' }
|
||||
defaultMods.big_reactors = { defaultVersion: '0.4.2A2' }
|
||||
defaultMods.buildcraft = { defaultVersion: '1.7.18' }
|
||||
defaultMods.computercraft = { defaultVersion: '1.74' }
|
||||
defaultMods.draconic_evolution = { defaultVersion: '1.0.2h' }
|
||||
defaultMods.ender_storage = { defaultVersion: '1.4.5.29' }
|
||||
defaultMods.enderio = { defaultVersion: '2.2.7.325' }
|
||||
defaultMods.extra_cells = { defaultVersion: '2.2.73b129' }
|
||||
defaultMods.extra_utilities = { defaultVersion: '1.2.2' }
|
||||
defaultMods.forestry = { defaultVersion: '3.4.0.7' }
|
||||
defaultMods.forge_multipart = { defaultVersion: '1.2.0.345' }
|
||||
defaultMods.galacticraft = { defaultVersion: '3.0.12.404' }
|
||||
defaultMods.hydraulicraft = { defaultVersion: '2.1.242' }
|
||||
defaultMods.ic2_classic = { defaultVersion: 'none' }
|
||||
defaultMods.industrial_craft_2 = { defaultVersion: '2.2.663' }
|
||||
defaultMods.iron_chests = { defaultVersion: '6.0.62.742' }
|
||||
defaultMods.jabba = { defaultVersion: '1.2.1a' }
|
||||
defaultMods.logistics_pipes = { defaultVersion: '0.9.3.100' }
|
||||
defaultMods.mekanism = { defaultVersion: '7.1.1.127' }
|
||||
defaultMods.minefactory_reloaded = { defaultVersion: '2.8.0RC8-86' }
|
||||
defaultMods.modular_powersuits = { defaultVersion: '0.11.0-300-thermal-expansion' }
|
||||
defaultMods.opencomputers = { defaultVersion: '1.5.22' }
|
||||
defaultMods.quantum_flux = { defaultVersion: '1.3.4' }
|
||||
defaultMods.project_red = { defaultVersion: '4.5.16.77' }
|
||||
defaultMods.redstone_arsenal = { defaultVersion: '9.5.0' }
|
||||
defaultMods.railcraft = { defaultVersion: '9.5.0' }
|
||||
defaultMods.simply_jetpacks = { defaultVersion: '1.4.1' }
|
||||
defaultMods.solar_expansion = { defaultVersion: '1.6a' }
|
||||
defaultMods.solar_flux = { defaultVersion: '0.5b' }
|
||||
defaultMods.storage_drawers = { defaultVersion: '1.7.10-1.6.2' }
|
||||
defaultMods.thermal_dynamics = { defaultVersion: '1.7.10r1.2.0' }
|
||||
defaultMods.thermal_expansion = { defaultVersion: '1.7.10r4.1.4' }
|
||||
defaultMods.thermal_foundation = { defaultVersion: '1.7.10r1.2.5' }
|
||||
defaultMods.tinkers_construct = { defaultVersion: '1.7.10-1.8.8' }
|
||||
|
||||
exports.duration = duration = {}
|
||||
duration.snap = 100
|
||||
duration.fast = 200
|
||||
@@ -119,6 +79,9 @@ login.clientIds =
|
||||
'staging': '3d75ed772ce5004180d6'
|
||||
'production': 'ce71be7f66926ff6ff38'
|
||||
|
||||
exports.modpack = modpack = {}
|
||||
modpack.default = "crafting-guide-default"
|
||||
|
||||
exports.modelState = modelState = {}
|
||||
modelState.unloaded = 'unloaded'
|
||||
modelState.loading = 'loading'
|
||||
@@ -186,7 +149,7 @@ url.login = _.template "/login"
|
||||
url.mod = _.template "/browse/<%= modSlug %>/"
|
||||
url.modData = _.template "/data/<%= modSlug %>/mod.cg"
|
||||
url.modIcon = _.template "/data/<%= modSlug %>/icon.png"
|
||||
url.modpackArchive = _.template "/data/modpack.cg"
|
||||
url.modPackData = _.template "/data/<%= modPackId %>/modpack.json"
|
||||
url.modVersionData = _.template "/data/<%= modSlug %>/versions/<%= modVersion %>/mod-version.cg"
|
||||
url.root = _.template "/"
|
||||
url.tutorial = _.template "/browse/<%= modSlug %>/tutorials/<%= tutorialSlug %>/"
|
||||
|
||||
Reference in New Issue
Block a user