Massive refactoring to clean up models
* Tweak style of quantity label in output box to be more visible * Move data files for all mods under a directory for the specific version of the mod * Add a `silent` property to the base model to prevent models which shouldn't be observed from emitting events * Change the term "itemSlug" to just "slug" across the board * Change all models so that they don't require their parent in their constructors and so that they don't automatically add themselves to their parents lists * Remove a bunch of unnecessary event triggering * Tighten up access to various lists of children across all models * Refactor the guts of the V2 parser into an abstract base class so it can be used for other parsers in the future * Remove a number of unused methods
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
###
|
||||
Crafting Guide - base_collection.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BaseCollection extends Backbone.Collection
|
||||
@@ -13,7 +13,7 @@ All rights reserved.
|
||||
module.exports = class BaseModel extends Backbone.Model
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
options.allowedSyncMethods = []
|
||||
options.silent ?= true
|
||||
super attributes, options
|
||||
|
||||
makeGetter = (name)-> return -> @get name
|
||||
@@ -22,9 +22,9 @@ module.exports = class BaseModel extends Backbone.Model
|
||||
continue if name is 'id'
|
||||
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
|
||||
|
||||
@allowedSyncMethods = options.allowedSyncMethods
|
||||
@silent = options.silent
|
||||
@state = ModelState.unloaded
|
||||
|
||||
@state = ModelState.unloaded
|
||||
@on 'request', => @state = ModelState.loading
|
||||
@on 'sync', => @state = ModelState.loaded
|
||||
@on 'error', => @state = ModelState.error
|
||||
@@ -61,6 +61,7 @@ module.exports = class BaseModel extends Backbone.Model
|
||||
success: (text, status, xhr)=> resolve @onLoadSucceeded text, status, xhr
|
||||
error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr
|
||||
|
||||
@loading.catch -> # do nothing. prevents unhandled promise warnings
|
||||
return @loading
|
||||
|
||||
parse: (text)->
|
||||
@@ -70,6 +71,7 @@ module.exports = class BaseModel extends Backbone.Model
|
||||
throw new Error "#{@constructor.name} (#{@cid}) is not permitted to #{method}"
|
||||
|
||||
trigger: (name)->
|
||||
return if @silent
|
||||
logger.trace "#{@constructor.name}.#{@cid} triggered a \"#{name}\" event"
|
||||
super
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
###
|
||||
Crafting Guide - command_parser_base.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CommandParserBase
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
options.showAllErrors ?= false
|
||||
|
||||
@_model = options.model
|
||||
@_showAllErrors = options.showAllErrors
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@COMMAND = /\ *([^:]*):?(.*)/
|
||||
|
||||
@COMMENT = /([^\\]?)#.*/
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
parse: (text)->
|
||||
@_rawData = {}
|
||||
@_lineNumber = 1
|
||||
|
||||
lines = text.split '\n'
|
||||
for i in [0...lines.length]
|
||||
@_lineNumber = i + 1
|
||||
commands = @_parseLine lines[i]
|
||||
for command in commands
|
||||
@_handleErrors @_execute, command
|
||||
|
||||
@_handleErrors @_buildModel, @_rawData, @_model
|
||||
return @_model
|
||||
|
||||
unparse: ->
|
||||
builder = new StringBuilder context:@model
|
||||
@_unparseModel builder, model
|
||||
return builder.toString()
|
||||
|
||||
# Subclass Methods #############################################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
throw new Error 'Subclasses must override this method'
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
throw new Error 'Subclasses must override this method'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_execute: (command)->
|
||||
method = this["_command_#{command.name}"]
|
||||
if not method? then throw new Error "Unknown command: #{command.name}"
|
||||
|
||||
@_handleErrors method, command.args
|
||||
|
||||
_parseLine: (line)->
|
||||
line = line.replace CommandParserBase.COMMENT, '$1'
|
||||
line = line.trim()
|
||||
return [] if line.length is 0
|
||||
|
||||
lineParts = (part.trim() for part in line.split(';'))
|
||||
commands = []
|
||||
for linePart in lineParts
|
||||
continue if linePart.length is 0
|
||||
|
||||
match = CommandParserBase.COMMAND.exec linePart
|
||||
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
|
||||
|
||||
args = []
|
||||
args = (s.trim() for s in match[2].split(',')) if match[2]?
|
||||
commands.push name:match[1], args:args
|
||||
|
||||
return commands
|
||||
|
||||
_handleErrors: (callback, args...)->
|
||||
if args.length is 1 and _.isArray(args[0]) then args = args[0]
|
||||
|
||||
try
|
||||
callback.apply this, args
|
||||
catch e
|
||||
e.message = "line #{@_lineNumber}: #{e.message}"
|
||||
if not @_showAllErrors then throw e
|
||||
logger.error e.message
|
||||
@@ -18,16 +18,17 @@ module.exports = class CraftingGrid extends BaseModel
|
||||
attributes.recipe ?= null
|
||||
super attributes, options
|
||||
|
||||
Object.defineProperty @prototype, 'slotCount', get:-> CraftingGrid.SLOT_COUNT
|
||||
Object.defineProperties this,
|
||||
'slotCount': { get:-> CraftingGrid.SLOT_COUNT }
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getItemDisplayAt: (index)->
|
||||
if index >= @slotCount then throw new Error "index (#{index}) must be less than #{@slotCount}"
|
||||
getItemDisplayAt: (slot)->
|
||||
if slot >= @slotCount then throw new Error "slot (#{slot}) must be less than #{@slotCount}"
|
||||
return null unless @recipe?
|
||||
|
||||
itemSlug = @recipe.getItemSlugAt index
|
||||
return null unless itemSlug?
|
||||
slug = @recipe.getItemSlugAt slot
|
||||
return null unless slug?
|
||||
|
||||
itemDisplay = @modPack.findItemDisplay itemSlug
|
||||
itemDisplay = @modPack.findItemDisplay slug
|
||||
return itemDisplay
|
||||
|
||||
@@ -6,6 +6,7 @@ All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
Inventory = require './inventory'
|
||||
|
||||
########################################################################################################################
|
||||
@@ -17,44 +18,50 @@ module.exports = class CraftingPlan extends BaseModel
|
||||
attributes.includingTools ?= false
|
||||
super attributes, options
|
||||
|
||||
@have = new Inventory
|
||||
@want = new Inventory
|
||||
@need = new Inventory
|
||||
@result = new Inventory
|
||||
@storage = options.storage
|
||||
@have = new Inventory
|
||||
@want = new Inventory
|
||||
@need = new Inventory
|
||||
@result = new Inventory
|
||||
|
||||
@have.on 'change', => @craft()
|
||||
@want.on 'change', => @craft()
|
||||
@modPack.on 'change', => @craft()
|
||||
@clear silent:true
|
||||
|
||||
@on 'change:includingTools', => @craft()
|
||||
@have.on Event.change, => @craft()
|
||||
@want.on Event.change, => @craft()
|
||||
@modPack.on Event.add, => @craft()
|
||||
|
||||
@clear()
|
||||
@on Event.change + ':includingTools', => @craft()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
clear: ->
|
||||
clear: (options={})->
|
||||
options.silent ?= false
|
||||
|
||||
@steps = []
|
||||
@need.clear()
|
||||
@result.clear()
|
||||
|
||||
@trigger 'change', this
|
||||
@trigger 'change', this unless options.silent
|
||||
return this
|
||||
|
||||
craft: ->
|
||||
@clear()
|
||||
@clear silent:true
|
||||
@need.silent = @result.silent = true
|
||||
|
||||
@result.addInventory @have
|
||||
|
||||
@steps = {}
|
||||
@want.each (stack)=>
|
||||
@_findSteps stack.itemSlug
|
||||
@need.add stack.itemSlug, stack.quantity
|
||||
@_findSteps stack.slug
|
||||
@need.add stack.slug, stack.quantity
|
||||
|
||||
@steps = _.values @steps
|
||||
@_resolveNeeds()
|
||||
@_removeExtraSteps()
|
||||
@result.addInventory @want
|
||||
|
||||
@need.silent = @result.silent = false
|
||||
@need.trigger 'change', @need
|
||||
@result.trigger 'change', @result
|
||||
@trigger 'change', this
|
||||
|
||||
# Event Methods ################################################################################
|
||||
@@ -82,10 +89,10 @@ module.exports = class CraftingPlan extends BaseModel
|
||||
@steps[recipe.slug] = recipe:recipe
|
||||
|
||||
_chooseRecipe: (item)->
|
||||
return item.recipes[0]
|
||||
return item.getPrimaryRecipe()
|
||||
|
||||
_findSteps: (itemSlug)->
|
||||
item = @modPack.findItem itemSlug
|
||||
_findSteps: (slug)->
|
||||
item = @modPack.findItem slug
|
||||
return unless item?
|
||||
return unless item.isCraftable
|
||||
return if item.isGatherable
|
||||
@@ -94,16 +101,16 @@ module.exports = class CraftingPlan extends BaseModel
|
||||
|
||||
if @includingTools
|
||||
for toolStack in recipe.tools
|
||||
if not @_hasStep toolStack.itemSlug
|
||||
@_findSteps toolStack.itemSlug
|
||||
if not @_hasStep toolStack.slug
|
||||
@_findSteps toolStack.slug
|
||||
|
||||
for inputStack in recipe.input
|
||||
@_findSteps inputStack.itemSlug
|
||||
@_findSteps inputStack.slug
|
||||
|
||||
@_addStep recipe
|
||||
|
||||
_hasStep: (itemSlug)->
|
||||
return @steps[itemSlug]?
|
||||
_hasStep: (slug)->
|
||||
return @steps[slug]?
|
||||
|
||||
_removeExtraSteps: ->
|
||||
result = (step for step in @steps when step.multiplier > 0)
|
||||
@@ -118,25 +125,25 @@ module.exports = class CraftingPlan extends BaseModel
|
||||
|
||||
if @includingTools
|
||||
for stack in recipe.tools
|
||||
slug = stack.itemSlug
|
||||
slug = stack.slug
|
||||
available = @result.quantityOf(slug) + @need.quantityOf(slug)
|
||||
needed = Math.max 0, stack.quantity - available
|
||||
|
||||
@need.add stack.itemSlug, needed
|
||||
@result.add stack.itemSlug, needed
|
||||
@need.add stack.slug, needed
|
||||
@result.add stack.slug, needed
|
||||
|
||||
for stack in recipe.input
|
||||
needed = step.multiplier * stack.quantity
|
||||
consumed = Math.min needed, @result.quantityOf(stack.itemSlug)
|
||||
consumed = Math.min needed, @result.quantityOf(stack.slug)
|
||||
remaining = needed - consumed
|
||||
|
||||
@result.remove stack.itemSlug, consumed
|
||||
@need.add stack.itemSlug, remaining
|
||||
@result.remove stack.slug, consumed
|
||||
@need.add stack.slug, remaining
|
||||
|
||||
for stack in recipe.output
|
||||
created = stack.quantity * step.multiplier
|
||||
consumed = Math.min created, @need.quantityOf stack.itemSlug
|
||||
consumed = Math.min created, @need.quantityOf stack.slug
|
||||
remaining = created - consumed
|
||||
|
||||
@result.add stack.itemSlug, remaining
|
||||
@need.remove stack.itemSlug, consumed
|
||||
@result.add stack.slug, remaining
|
||||
@need.remove stack.slug, consumed
|
||||
|
||||
@@ -81,7 +81,7 @@ module.exports = class CraftingTable extends BaseModel
|
||||
getToolNames: ->
|
||||
recipe = @plan.steps[@_step]?.recipe
|
||||
return '' unless recipe?
|
||||
toolSlugs = (stack.itemSlug for stack in recipe.tools)
|
||||
toolSlugs = (stack.slug for stack in recipe.tools)
|
||||
toolNames = (@modPack.findName(slug) for slug in toolSlugs).join ', '
|
||||
return toolNames
|
||||
|
||||
|
||||
@@ -15,78 +15,80 @@ module.exports = class Inventory extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
@clear()
|
||||
@clear silent:true
|
||||
|
||||
Object.defineProperty @prototype, 'isEmpty', get:-> @_slugs.length is 0
|
||||
Object.defineProperties this,
|
||||
isEmpty: { get:-> @_slugs.length is 0 }
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (itemSlug, quantity=1)->
|
||||
@_add itemSlug, quantity
|
||||
@trigger Event.add, this, itemSlug, quantity
|
||||
add: (slug, quantity=1)->
|
||||
@_add slug, quantity
|
||||
@trigger Event.add, this, slug, quantity
|
||||
@trigger Event.change, this
|
||||
return this
|
||||
|
||||
addInventory: (inventory)->
|
||||
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
|
||||
@silent = true
|
||||
inventory.each (stack)=> @_add stack.slug, stack.quantity
|
||||
@silent = false
|
||||
|
||||
@trigger Event.change, this
|
||||
return this
|
||||
|
||||
clear: ->
|
||||
clear: (options={})->
|
||||
options.silent ?= false
|
||||
@_stacks = {}
|
||||
@_slugs = []
|
||||
@trigger 'change', self
|
||||
|
||||
@trigger Event.change, this unless options.silent
|
||||
|
||||
clone: ->
|
||||
inventory = new Inventory
|
||||
@each (stack)-> inventory.add stack.itemSlug, stack.quantity
|
||||
inventory.addInventory this
|
||||
return inventory
|
||||
|
||||
each: (onStack)->
|
||||
for itemSlug in @_slugs
|
||||
stack = @_stacks[itemSlug]
|
||||
onStack stack
|
||||
each: (callback)->
|
||||
for slug in @_slugs
|
||||
callback @_stacks[slug]
|
||||
|
||||
getStack: (itemSlug)->
|
||||
return @_stacks[itemSlug]
|
||||
|
||||
hasAtLeast: (itemSlug, quantity=1)->
|
||||
hasAtLeast: (slug, quantity=1)->
|
||||
if quantity is 0 then return true
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
stack = @_stacks[slug]
|
||||
return false unless stack?
|
||||
return stack.quantity >= quantity
|
||||
|
||||
pop: ->
|
||||
itemSlug = @_slugs.pop()
|
||||
return null unless itemSlug?
|
||||
slug = @_slugs.pop()
|
||||
return null unless slug?
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
delete @_stacks[itemSlug]
|
||||
stack = @_stacks[slug]
|
||||
delete @_stacks[slug]
|
||||
|
||||
@trigger Event.remove, this, stack.itemSlug, stack.quantity
|
||||
@trigger Event.remove, this, stack.slug, stack.quantity
|
||||
@trigger Event.change, this
|
||||
return stack
|
||||
|
||||
quantityOf: (itemSlug)->
|
||||
stack = @_stacks[itemSlug]
|
||||
quantityOf: (slug)->
|
||||
stack = @_stacks[slug]
|
||||
return 0 unless stack?
|
||||
return stack.quantity
|
||||
|
||||
remove: (itemSlug, quantity=1)->
|
||||
remove: (slug, quantity=1)->
|
||||
return if quantity is 0
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
if not stack? then throw new Error "cannot remove #{itemSlug} since it is not in this inventory"
|
||||
stack = @_stacks[slug]
|
||||
if not stack? then throw new Error "cannot remove #{slug} since it is not in this inventory"
|
||||
if stack.quantity < quantity
|
||||
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{itemSlug} in this inventory"
|
||||
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{slug} in this inventory"
|
||||
|
||||
stack.quantity -= quantity
|
||||
if stack.quantity is 0
|
||||
delete @_stacks[itemSlug]
|
||||
@_slugs = _(@_slugs).without itemSlug
|
||||
delete @_stacks[slug]
|
||||
@_slugs = _(@_slugs).without slug
|
||||
|
||||
@trigger Event.remove, this, itemSlug, quantity
|
||||
@trigger Event.remove, this, slug, quantity
|
||||
@trigger Event.change, this
|
||||
return this
|
||||
|
||||
@@ -94,9 +96,9 @@ module.exports = class Inventory extends BaseModel
|
||||
result = []
|
||||
@each (stack)->
|
||||
if stack.quantity > 1
|
||||
result.push [stack.quantity, stack.itemSlug]
|
||||
result.push [stack.quantity, stack.slug]
|
||||
else
|
||||
result.push stack.itemSlug
|
||||
result.push stack.slug
|
||||
return result
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
@@ -116,14 +118,15 @@ module.exports = class Inventory extends BaseModel
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_add: (itemSlug, quantity=1)->
|
||||
_add: (slug, quantity=1)->
|
||||
return unless slug?
|
||||
return if quantity is 0
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
stack = @_stacks[slug]
|
||||
if not stack?
|
||||
stack = new Stack itemSlug:itemSlug, quantity:quantity
|
||||
@_stacks[itemSlug] = stack
|
||||
@_slugs.push itemSlug
|
||||
stack = new Stack slug:slug, quantity:quantity
|
||||
@_stacks[slug] = stack
|
||||
@_slugs.push slug
|
||||
@_slugs.sort()
|
||||
else
|
||||
stack.quantity += quantity
|
||||
|
||||
@@ -5,33 +5,38 @@ Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
BaseCollection = require './base_collection'
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
Recipe = require './recipe'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Item extends BaseModel
|
||||
|
||||
@DEFAULT_STACK_SIZE = 64
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.name? then throw new Error 'attributes.name is required'
|
||||
if not attributes.modVersion? then throw new Error 'attributes.modVersion is required'
|
||||
|
||||
attributes.isGatherable ?= false
|
||||
attributes.recipes ?= []
|
||||
attributes.slug ?= _.slugify attributes.name
|
||||
attributes.stackSize ?= Item.DEFAULT_STACK_SIZE
|
||||
super attributes, options
|
||||
|
||||
@modVersion.addItem this
|
||||
|
||||
Object.defineProperty @prototype, 'isCraftable', get:-> @recipes.length > 0
|
||||
@_recipes = []
|
||||
Object.defineProperties this,
|
||||
'isCraftable': { get:-> @_recipes.length > 0 }
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addRecipe: (recipe)->
|
||||
if recipe.item isnt this then throw new Error "cannot add a recipe which isn't associated with this item"
|
||||
@recipes.push recipe
|
||||
if recipe.slug isnt @slug then throw new Error "cannot add a recipe for #{recipe.slug} to #{@slug}"
|
||||
@_recipes.push recipe
|
||||
|
||||
eachRecipe: (callback)->
|
||||
for recipe in @_recipes
|
||||
callback recipe
|
||||
|
||||
getPrimaryRecipe: ->
|
||||
return @_recipes[0]
|
||||
|
||||
compareTo: (that)->
|
||||
if this.slug isnt that.slug
|
||||
@@ -52,13 +57,10 @@ module.exports = class Item extends BaseModel
|
||||
if _.slugify(@name) isnt @slug
|
||||
result.push ', slug:'; result.push @slug
|
||||
|
||||
if @stackSize isnt Item.DEFAULT_STACK_SIZE
|
||||
result.push ', stackSize:'; result.push @stackSize
|
||||
|
||||
if @recipes.length > 0
|
||||
result.push ', recipes:'
|
||||
result.push @recipes.length
|
||||
result.push ' items'
|
||||
result.push ', recipes:«'
|
||||
result.push @_recipes.length
|
||||
result.push ' items»'
|
||||
|
||||
result.push '}'
|
||||
return result.join ''
|
||||
|
||||
@@ -41,7 +41,7 @@ module.exports = class ItemPage extends BaseModel
|
||||
_updateLocation: ->
|
||||
list = @plan.want.toList()
|
||||
if list.length is 1
|
||||
itemSlug = if _.isArray(list[0]) then list[0][1] else list[0]
|
||||
router.navigate "/item/#{itemSlug}"
|
||||
slug = if _.isArray(list[0]) then list[0][1] else list[0]
|
||||
router.navigate "/item/#{slug}"
|
||||
else
|
||||
router.navigate "/"
|
||||
@@ -6,6 +6,7 @@ All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -20,9 +21,5 @@ module.exports = class Mod extends BaseModel
|
||||
|
||||
parse: (response)->
|
||||
|
||||
sync: (method, model, options={})->
|
||||
if method isnt 'read' then throw new Error "Mod data can only be read, not #{method}d"
|
||||
super method, model, options
|
||||
|
||||
url: ->
|
||||
return "/data/#{@slug}/mod.cg"
|
||||
return Url.mod modSlug:@slug
|
||||
|
||||
@@ -16,35 +16,18 @@ ModVersionParser = require './mod_version_parser'
|
||||
module.exports = class ModPack extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
attributes.modVersions ?= []
|
||||
super attributes, options
|
||||
|
||||
@_modVersions = []
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addModVersion: (modVersion)->
|
||||
if modVersion.modPack isnt this then throw new Error "the mod version must be associated with this mod pack"
|
||||
return if @modVersions.indexOf(modVersion) isnt -1
|
||||
|
||||
@modVersions.push modVersion
|
||||
@trigger Event.add, modVersion, this
|
||||
|
||||
@modVersions.sort (a, b)-> a.compareTo b
|
||||
@trigger Event.sort, this
|
||||
@trigger Event.change, this
|
||||
return this
|
||||
|
||||
enableModsForItem: (name)->
|
||||
for modVersion in @modVersions
|
||||
continue if modVersion.enabled
|
||||
if modVersion.hasRecipe name
|
||||
modVersion.enabled = true
|
||||
|
||||
findItem: (itemSlug, options={})->
|
||||
findItem: (slug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
for modVersion in @modVersions
|
||||
for modVersion in @_modVersions
|
||||
continue unless modVersion.enabled or options.includeDisabled
|
||||
item = modVersion.items[itemSlug]
|
||||
item = modVersion.findItem slug
|
||||
return item if item?
|
||||
|
||||
return null
|
||||
@@ -53,9 +36,9 @@ module.exports = class ModPack extends BaseModel
|
||||
options.includeDisabled ?= false
|
||||
slug = _.slugify name
|
||||
|
||||
for modVersion in @modVersions
|
||||
for modVersion in @_modVersions
|
||||
continue unless modVersion.enabled or options.includeDisabled
|
||||
item = modVersion.items[slug]
|
||||
item = modVersion.findItem slug
|
||||
return item if item?
|
||||
|
||||
return null
|
||||
@@ -63,12 +46,12 @@ module.exports = class ModPack extends BaseModel
|
||||
findName: (slug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
for modVersion in @modVersions
|
||||
for modVersion in @_modVersions
|
||||
continue unless modVersion.enabled or options.includeDisabled
|
||||
name = modVersion.findName slug
|
||||
return name if name
|
||||
|
||||
return slug
|
||||
return null
|
||||
|
||||
findItemDisplay: (slug)->
|
||||
result = {}
|
||||
@@ -76,39 +59,50 @@ module.exports = class ModPack extends BaseModel
|
||||
if item?
|
||||
result.modSlug = item.modVersion.slug
|
||||
result.modVersion = item.modVersion.version
|
||||
result.itemSlug = item.slug
|
||||
result.slug = item.slug
|
||||
result.itemName = item.name
|
||||
else
|
||||
result.modSlug = _.slugify DefaultModVersions[0].name
|
||||
result.modVersion = DefaultModVersions[0].version
|
||||
result.itemSlug = slug
|
||||
result.slug = slug
|
||||
result.itemName = @findName slug, includeDisabled:true
|
||||
|
||||
result.iconUrl = Url.itemIcon result
|
||||
result.itemUrl = Url.item result
|
||||
return result
|
||||
|
||||
hasRecipe: (name, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
for modVersion in @modVersions
|
||||
continue unless modVersion.enabled or options.includeDisabled
|
||||
return true if modVersion.hasRecipe name
|
||||
|
||||
return false
|
||||
|
||||
isValidName: (name, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
slug = _.slugify name
|
||||
for modVersion in @modVersions
|
||||
for modVersion in @_modVersions
|
||||
continue unless modVersion.enabled or options.includeDisabled
|
||||
name = modVersion.findName slug
|
||||
return true if name
|
||||
|
||||
return false
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
addModVersion: (modVersion)->
|
||||
return if @_modVersions.indexOf(modVersion) isnt -1
|
||||
|
||||
@_modVersions.push modVersion
|
||||
@trigger Event.add, modVersion, this
|
||||
|
||||
@_modVersions.sort (a, b)-> a.compareTo b
|
||||
@trigger Event.change, this
|
||||
|
||||
return this
|
||||
|
||||
eachModVersion: (callback)->
|
||||
for modVersion in @_modVersions
|
||||
callback modVersion
|
||||
|
||||
getModVersions: ->
|
||||
return @_modVersions[..]
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "ModPack (#{@cid}) {modVersions:#{@modVersions.length} items}"
|
||||
return "ModPack (#{@cid}) {modVersions:#{@_modVersions.length} items}"
|
||||
|
||||
@@ -5,40 +5,38 @@ Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
{RequiredMods} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
BaseCollection = require './base_collection'
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
Item = require './item'
|
||||
{RequiredMods} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModVersion extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.modPack? then throw new Error 'attributes.modPack is required'
|
||||
if _.isEmpty(attributes.name) then throw new Error 'attributes.name cannot be empty'
|
||||
if _.isEmpty(attributes.version) then throw new Error 'attributes.version cannot be empty'
|
||||
if not attributes.name? then throw new Error 'attributes.name is required'
|
||||
if not attributes.version? then throw new Error 'attributes.version is required'
|
||||
|
||||
attributes.description ?= ''
|
||||
attributes.enabled ?= true
|
||||
attributes.items ?= {}
|
||||
attributes.names ?= {}
|
||||
attributes.slug ?= _.slugify attributes.name
|
||||
super attributes, options
|
||||
|
||||
@modPack.addModVersion this
|
||||
@_items = {}
|
||||
@_names = {}
|
||||
@_slugs = []
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addItem: (item)->
|
||||
if item.modVersion isnt this then throw new Error "cannot add item not associated with this mod version"
|
||||
if @items[item.slug]? then throw new Error "duplicate item for #{item.name}"
|
||||
|
||||
@items[item.slug] = item
|
||||
@names[item.slug] = item.name
|
||||
@trigger Event.add, item, this
|
||||
@trigger Event.change, this
|
||||
if @_items[item.slug]? then throw new Error "duplicate item for #{item.name}"
|
||||
|
||||
@_items[item.slug] = item
|
||||
item.modVersion = this
|
||||
@registerSlug item.slug, item.name
|
||||
return this
|
||||
|
||||
compareTo: (that)->
|
||||
@@ -56,26 +54,49 @@ module.exports = class ModVersion extends BaseModel
|
||||
else
|
||||
return if this.name < that.name then -1 else +1
|
||||
|
||||
findName: (slug)->
|
||||
return @names[slug]
|
||||
eachItem: (callback)->
|
||||
for slug in @_slugs
|
||||
callback @_items[slug], slug
|
||||
return this
|
||||
|
||||
hasRecipe: (name)->
|
||||
item = @findItemByName name
|
||||
return false unless item?
|
||||
return item.recipes.length > 0
|
||||
eachName: (callback)->
|
||||
for slug in @_slugs
|
||||
callback @_names[slug], slug
|
||||
return this
|
||||
|
||||
findItem: (slug)->
|
||||
return @_items[slug]
|
||||
|
||||
findItemByName: (name)->
|
||||
return @findItem _.slugify name
|
||||
|
||||
findName: (slug)->
|
||||
return @_names[slug]
|
||||
|
||||
registerSlug: (slug, name)->
|
||||
@names[slug] = name
|
||||
@trigger Event.change + ':names', this, @names
|
||||
@trigger Event.change, this
|
||||
hasSlug = @_names[slug]?
|
||||
@_names[slug] = name
|
||||
|
||||
if not hasSlug
|
||||
@_slugs.push slug
|
||||
@_slugs.sort()
|
||||
@_slugs = _.uniq @_slugs, true
|
||||
|
||||
return this
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
currentSilent = @silent
|
||||
@silent = true
|
||||
|
||||
ModVersionParser = require './mod_version_parser' # to avoid require cycles
|
||||
@_parser ?= new ModVersionParser modVersion:this
|
||||
@_parser ?= new ModVersionParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
@silent = currentSilent
|
||||
@trigger Event.change, this
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
@@ -88,4 +109,5 @@ module.exports = class ModVersion extends BaseModel
|
||||
enabled:#{@enabled},
|
||||
name:#{@name},
|
||||
version:#{@version},
|
||||
items:#{_.keys(@items).length} items}"
|
||||
items:#{_.keys(@_items).length} items
|
||||
}"
|
||||
|
||||
@@ -16,10 +16,10 @@ module.exports = class ModVersionParser
|
||||
@CURRENT_VERSION = '2'
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modVersion? then throw new Error 'options.modVersion is required'
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
options.showAllErrors ?= false
|
||||
|
||||
@_modVersion = options.modVersion
|
||||
@_model = options.model
|
||||
@_parsers =
|
||||
'1': new ModVersionParserV1 options
|
||||
'2': new ModVersionParserV2 options
|
||||
@@ -34,13 +34,9 @@ module.exports = class ModVersionParser
|
||||
parser = @_parsers['2']
|
||||
|
||||
if not parser? then throw new Error "cannot parse version #{data.dataVersion} mod descriptions"
|
||||
parser.parse data
|
||||
|
||||
oldLevel = logger.level
|
||||
logger.level = Logger.WARNING
|
||||
result = parser.parse data
|
||||
logger.level = oldLevel
|
||||
|
||||
return @_modVersion
|
||||
return @_model
|
||||
|
||||
unparse: (dataVersion=ModVersionParser.CURRENT_VERSION)->
|
||||
if not modVersion? then throw new Error 'modVersion is required'
|
||||
|
||||
@@ -59,19 +59,19 @@ module.exports = class NameFinder
|
||||
names = []
|
||||
nameMap = {}
|
||||
|
||||
for modVersion in @modPack.modVersions
|
||||
continue unless modVersion.enabled or @includeDisabledMods
|
||||
@modPack.eachModVersion (modVersion)=>
|
||||
return unless modVersion.enabled or @includeDisabledMods
|
||||
|
||||
for slug, name of modVersion.names
|
||||
continue if nameMap[name]
|
||||
modVersion.eachName (name, slug)=>
|
||||
return if nameMap[name]
|
||||
|
||||
item = modVersion.items[slug]
|
||||
item = modVersion.findItem slug
|
||||
if not @includeGatherable
|
||||
continue unless item? and (not item.isGatherable)
|
||||
return unless item? and (not item.isGatherable)
|
||||
|
||||
scanName = "#{modVersion.name} : #{name}"
|
||||
if nameHint?
|
||||
continue unless @_isMatch scanName.toLowerCase(), nameHint
|
||||
return unless @_isMatch scanName.toLowerCase(), nameHint
|
||||
|
||||
nameMap[name] = name
|
||||
names.push value:name, label:scanName, modVersion:modVersion
|
||||
|
||||
@@ -15,8 +15,8 @@ Stack = require '../stack'
|
||||
module.exports = class ModVersionParserV1
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modVersion? then throw new Error 'options.modVersion is required'
|
||||
@_modVersion = options.modVersion
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
@_model = options.model
|
||||
@_errorLocation = 'the header information'
|
||||
|
||||
parse: (data)->
|
||||
@@ -27,25 +27,48 @@ module.exports = class ModVersionParserV1
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeDefaultPattern: (input)->
|
||||
itemCount = input.length
|
||||
slotCount = _.reduce input, ((total, stack)-> total + stack.quantity), 0
|
||||
|
||||
return '... .0. ...' if itemCount is 1 and slotCount is 1
|
||||
return '00. 00. ...' if itemCount is 1 and slotCount is 4
|
||||
return '000 000 000' if itemCount is 1 and slotCount is 9
|
||||
|
||||
result = ['.', '.', '.', '.', '.', '.', '.', '.', '.']
|
||||
indexes = [4, 7, 1, 3, 5, 6, 8, 0, 2]
|
||||
|
||||
for i in [0...input.length]
|
||||
stack = input[i]
|
||||
for j in [0...stack.quantity]
|
||||
index = indexes.shift()
|
||||
result[index] = "#{i}"
|
||||
|
||||
pattern = result.join ''
|
||||
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
|
||||
return pattern
|
||||
|
||||
_findOrCreateItem: (name)->
|
||||
item = @_modVersion.findItemByName name
|
||||
item = @_model.findItemByName name
|
||||
if not item?
|
||||
item = new Item modVersion:@_modVersion, name:name
|
||||
@_modVersion.registerSlug item.slug, item.name
|
||||
item = new Item name:name
|
||||
@_model.addItem item
|
||||
return item
|
||||
|
||||
# Parsing Methods ##############################################################################
|
||||
|
||||
_parseModVersion: (data)->
|
||||
if not data? then throw new Error 'mod description data is missing'
|
||||
if not data.name? then throw new Error 'name is required'
|
||||
if not data.version? then throw new Error 'version is required'
|
||||
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
|
||||
|
||||
if data.name isnt @_modVersion.name
|
||||
throw new Error "the data is for #{data.name}, not #{@_modVersion.name} as expected"
|
||||
if data.version isnt @_modVersion.version
|
||||
throw new Error "the data is for version #{data.version}, not #{@_modVersion.version} as expected"
|
||||
if data.name isnt @_model.name
|
||||
throw new Error "the data is for #{data.name}, not #{@_model.name} as expected"
|
||||
if data.version isnt @_model.version
|
||||
throw new Error "the data is for version #{data.version}, not #{@_model.version} as expected"
|
||||
|
||||
@_modVersion.description = data.description or ''
|
||||
@_model.description = data.description or ''
|
||||
@_parseRawMaterials data.raw_materials
|
||||
|
||||
for index in [0...data.recipes.length]
|
||||
@@ -54,7 +77,7 @@ module.exports = class ModVersionParserV1
|
||||
recipe = @_parseRecipe recipeData
|
||||
recipe._originalIndex = index
|
||||
|
||||
return @_modVersion
|
||||
return @_model
|
||||
|
||||
_parseRawMaterials: (data)->
|
||||
return unless data? and data.length > 0
|
||||
@@ -84,7 +107,7 @@ module.exports = class ModVersionParserV1
|
||||
output: @_parseStackList(data.output, field:'output', canBeEmpty:false)
|
||||
input: @_parseStackList(data.input, field:'input', canBeEmpty:true)
|
||||
tools: @_parseStackList(data.tools, field:'tools', canBeEmpty:true)
|
||||
attributes.pattern = data.pattern if data.pattern?
|
||||
attributes.pattern = data.pattern or @_computeDefaultPattern attributes.input
|
||||
|
||||
recipe = new Recipe attributes
|
||||
return recipe
|
||||
@@ -102,9 +125,9 @@ module.exports = class ModVersionParserV1
|
||||
|
||||
name = data[1]
|
||||
slug = _.slugify name
|
||||
@_modVersion.registerSlug slug, name
|
||||
@_model.registerSlug slug, name
|
||||
|
||||
return new Stack itemSlug:slug, quantity:data[0]
|
||||
return new Stack slug:slug, quantity:data[0]
|
||||
|
||||
_parseStackList: (data, options={})->
|
||||
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
|
||||
@@ -191,9 +214,9 @@ module.exports = class ModVersionParserV1
|
||||
else if stackList.length is 1
|
||||
stack = stackList[0]
|
||||
if stack.quantity is 1
|
||||
result.push '"' + @_modVersion.findName(stack.itemSlug) + '"'
|
||||
result.push '"' + @_model.findName(stack.slug) + '"'
|
||||
else
|
||||
result.push '[[' + stack.quantity + ', "' + @_modVersion.findName(stack.itemSlug) + '"]]'
|
||||
result.push '[[' + stack.quantity + ', "' + @_model.findName(stack.slug) + '"]]'
|
||||
else
|
||||
result.push '['
|
||||
|
||||
@@ -202,17 +225,17 @@ module.exports = class ModVersionParserV1
|
||||
stacks.sort (a, b)->
|
||||
if a.quantity isnt b.quantity
|
||||
return if a.quantity > b.quantity then -1 else +1
|
||||
if a.itemSlug isnt b.itemSlug
|
||||
return if a.itemSlug < b.itemSlug then -1 else +1
|
||||
if a.slug isnt b.slug
|
||||
return if a.slug < b.slug then -1 else +1
|
||||
return 0
|
||||
|
||||
firstItem = true
|
||||
for stack in stacks
|
||||
result.push ', ' if not firstItem
|
||||
if stack.quantity is 1
|
||||
result.push '"' + @_modVersion.findName(stack.itemSlug) + '"'
|
||||
result.push '"' + @_model.findName(stack.slug) + '"'
|
||||
else
|
||||
result.push '[' + stack.quantity + ', "' + @_modVersion.findName(stack.itemSlug) + '"]'
|
||||
result.push '[' + stack.quantity + ', "' + @_model.findName(stack.slug) + '"]'
|
||||
firstItem = false
|
||||
|
||||
result.push ']'
|
||||
|
||||
@@ -5,97 +5,38 @@ Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
Item = require '../item'
|
||||
ModVersion = require '../mod_version'
|
||||
Recipe = require '../recipe'
|
||||
Stack = require '../stack'
|
||||
StringBuilder = require '../string_builder'
|
||||
CommandParserBase = require '../command_parser_base'
|
||||
Item = require '../item'
|
||||
ModVersion = require '../mod_version'
|
||||
Recipe = require '../recipe'
|
||||
Stack = require '../stack'
|
||||
StringBuilder = require '../string_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModVersionParserV2
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modVersion? then throw new Error 'options.modVersion is required'
|
||||
options.showAllErrors ?= false
|
||||
|
||||
@_modVersion = options.modVersion
|
||||
@_showAllErrors = options.showAllErrors
|
||||
module.exports = class ModVersionParserV2 extends CommandParserBase
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@COMMAND = /\ *([^:]*):?(.*)/
|
||||
|
||||
@COMMENT = /([^\\]?)#.*/
|
||||
|
||||
@INTEGER = /[0-9]+/
|
||||
|
||||
@PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/
|
||||
|
||||
@STACK = /^([0-9]+) +(.*)$/
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
# CommandParserBase Overrides ##################################################################
|
||||
|
||||
parse: (text)->
|
||||
@_modVersionData = {}
|
||||
@_lineNumber = 1
|
||||
_buildModel: (rawData, model)->
|
||||
@_buildModVersion rawData, model
|
||||
|
||||
lines = text.split '\n'
|
||||
for i in [0...lines.length]
|
||||
@_lineNumber = i + 1
|
||||
commands = @_parseLine lines[i]
|
||||
for command in commands
|
||||
@_handleErrors @_execute, command
|
||||
|
||||
return @_handleErrors @_buildModVersion, @_modVersionData, @_modVersion
|
||||
|
||||
unparse: ->
|
||||
builder = new StringBuilder context:@_modVersion
|
||||
@_unparseModVersion builder
|
||||
return builder.toString()
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_execute: (command)->
|
||||
method = this["_command_#{command.name}"]
|
||||
if not method? then throw new Error "Unknown command: #{command.name}"
|
||||
|
||||
@_handleErrors method, command.args
|
||||
|
||||
_parseLine: (line)->
|
||||
line = line.replace ModVersionParserV2.COMMENT, '$1'
|
||||
line = line.trim()
|
||||
return [] if line.length is 0
|
||||
|
||||
lineParts = (part.trim() for part in line.split(';'))
|
||||
commands = []
|
||||
for linePart in lineParts
|
||||
continue if linePart.length is 0
|
||||
|
||||
match = ModVersionParserV2.COMMAND.exec linePart
|
||||
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
|
||||
|
||||
args = []
|
||||
args = (s.trim() for s in match[2].split(',')) if match[2]?
|
||||
commands.push name:match[1], args:args
|
||||
|
||||
return commands
|
||||
|
||||
_handleErrors: (callback, args...)->
|
||||
if args.length is 1 and _.isArray(args[0]) then args = args[0]
|
||||
|
||||
try
|
||||
callback.apply this, args
|
||||
catch e
|
||||
e.message = "line #{@_lineNumber}: #{e.message}"
|
||||
if not @_showAllErrors then throw e
|
||||
logger.error e.message
|
||||
_unparseModel: (builder, model)->
|
||||
@_unparseModVersion builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_description: (descriptionParts...)->
|
||||
if @_modVersionData.description? then throw new Error 'duplicate declaration of "description"'
|
||||
@_modVersionData.description = descriptionParts.join ', '
|
||||
if @_rawData.description? then throw new Error 'duplicate declaration of "description"'
|
||||
@_rawData.description = descriptionParts.join ', '
|
||||
|
||||
_command_extras: (extraTerms...)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "extras" before "recipe"'
|
||||
@@ -120,16 +61,16 @@ module.exports = class ModVersionParserV2
|
||||
if not name.length > 0 then throw new Error 'the item name cannot be empty'
|
||||
|
||||
@_itemData = name:name, line:@_lineNumber
|
||||
@_modVersionData.items ?= []
|
||||
@_modVersionData.items.push @_itemData
|
||||
@_rawData.items ?= []
|
||||
@_rawData.items.push @_itemData
|
||||
|
||||
@_recipeData = null
|
||||
|
||||
_command_name: (name='')->
|
||||
if @_modVersionData.name? then throw new Error 'duplicate declaration of "name"'
|
||||
if @_rawData.name? then throw new Error 'duplicate declaration of "name"'
|
||||
if not name.length > 0 then throw new Error 'the mod name cannot be empty'
|
||||
|
||||
@_modVersionData.name = name
|
||||
@_rawData.name = name
|
||||
|
||||
_command_input: (inputNames...)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
|
||||
@@ -175,10 +116,10 @@ module.exports = class ModVersionParserV2
|
||||
@_recipeData.tools.push name
|
||||
|
||||
_command_version: (version='')->
|
||||
if @_modVersionData.version? then throw new Error 'duplicate declaration of "version"'
|
||||
if @_rawData.version? then throw new Error 'duplicate declaration of "version"'
|
||||
if version.length is 0 then throw new Error 'version cannot be empty'
|
||||
|
||||
@_modVersionData.version = version
|
||||
@_rawData.version = version
|
||||
|
||||
# Object Creation Methods ######################################################################
|
||||
|
||||
@@ -208,7 +149,8 @@ module.exports = class ModVersionParserV2
|
||||
itemData.gatherable ?= false
|
||||
itemData.recipes ?= []
|
||||
|
||||
item = new Item modVersion:modVersion, name:itemData.name, isGatherable:itemData.gatherable
|
||||
item = new Item name:itemData.name, isGatherable:itemData.gatherable
|
||||
modVersion.addItem item
|
||||
|
||||
for recipeData in itemData.recipes
|
||||
@_handleErrors @_buildRecipe, modVersion, item, recipeData
|
||||
@@ -228,7 +170,7 @@ module.exports = class ModVersionParserV2
|
||||
for name in recipeData.input
|
||||
slug = _.slugify name
|
||||
modVersion.registerSlug slug, name
|
||||
inputStacks.push new Stack itemSlug:slug, quantity:0
|
||||
inputStacks.push new Stack slug:slug, quantity:0
|
||||
|
||||
for c in recipeData.pattern
|
||||
continue if c is '.'
|
||||
@@ -240,42 +182,43 @@ module.exports = class ModVersionParserV2
|
||||
for i in [0...inputStacks.length]
|
||||
stack = inputStacks[i]
|
||||
if stack.quantity is 0
|
||||
name = modVersion.findName stack.itemSlug
|
||||
name = modVersion.findName stack.slug
|
||||
throw new Error "#{name} is an input for this recipe, but it is not in the pattern"
|
||||
|
||||
outputStacks = [ new Stack itemSlug:item.slug, quantity:recipeData.quantity ]
|
||||
outputStacks = [ new Stack slug:item.slug, quantity:recipeData.quantity ]
|
||||
for extraData in recipeData.extras
|
||||
slug = _.slugify extraData.name
|
||||
modVersion.registerSlug slug, extraData.name
|
||||
outputStacks.push new Stack itemSlug:slug, quantity:extraData.quantity
|
||||
outputStacks.push new Stack slug:slug, quantity:extraData.quantity
|
||||
|
||||
toolStacks = []
|
||||
for name in recipeData.tools
|
||||
slug = _.slugify name
|
||||
modVersion.registerSlug slug, name
|
||||
toolStacks.push new Stack itemSlug:slug, quantity:1
|
||||
toolStacks.push new Stack slug:slug, quantity:1
|
||||
|
||||
attributes =
|
||||
input: inputStacks
|
||||
item: item
|
||||
pattern: recipeData.pattern
|
||||
output: outputStacks
|
||||
tools: toolStacks
|
||||
input: inputStacks
|
||||
name: item.name
|
||||
pattern: recipeData.pattern
|
||||
output: outputStacks
|
||||
tools: toolStacks
|
||||
|
||||
recipe = new Recipe attributes
|
||||
item.addRecipe recipe
|
||||
return recipe
|
||||
|
||||
# Un-parsing Methods ###########################################################################
|
||||
|
||||
_unparseModVersion: (builder)->
|
||||
itemList = _.values @modVersion.items
|
||||
_unparseModVersion: (builder, modVersion)->
|
||||
itemList = _.values modVersion.items
|
||||
itemList.sort (a, b)-> a.compareTo b
|
||||
|
||||
builder
|
||||
.line 'schema: ', 2
|
||||
.line 'name: ', @modVersion.name
|
||||
.line 'version: ', @modVersion.version
|
||||
.onlyIf @modVersion.description?, => builder.line 'description: ', @modVersion.description
|
||||
.line 'name: ', modVersion.name
|
||||
.line 'version: ', modVersion.version
|
||||
.onlyIf modVersion.description?, => builder.line 'description: ', modVersion.description
|
||||
.line()
|
||||
.onlyIf itemList.length > 0, =>
|
||||
builder.loop itemList, delimiter:'\n', onEach:(b, i)=> @_unparseItem(b, i)
|
||||
@@ -291,13 +234,13 @@ module.exports = class ModVersionParserV2
|
||||
.outdent()
|
||||
|
||||
_unparseRecipe: (builder, recipe)->
|
||||
inputNames = (builder.context.findName(stack.itemSlug) for stack in recipe.input)
|
||||
inputNames = (builder.context.findName(stack.slug) for stack in recipe.input)
|
||||
inputNames.sort()
|
||||
|
||||
patternMap = {'.', '.'}
|
||||
for i in [0...recipe.input.length]
|
||||
stack = recipe.input[i]
|
||||
patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.itemSlug)}"
|
||||
patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.slug)}"
|
||||
|
||||
pattern = recipe.pattern or recipe.defaultPattern
|
||||
newPattern = []
|
||||
@@ -333,9 +276,9 @@ module.exports = class ModVersionParserV2
|
||||
|
||||
_unparseStackList: (builder, stackList)->
|
||||
if stackList.length is 1 and stackList[0].quantity is 1
|
||||
builder.push builder.context.findName(stackList[0].itemSlug)
|
||||
builder.push builder.context.findName(stackList[0].slug)
|
||||
else
|
||||
builder.loop stackList, onEach:(b, stack)=>
|
||||
builder
|
||||
.onlyIf stack.quantity > 1, => builder.push stack.quantity, ' '
|
||||
.push builder.context.findName stack.itemSlug
|
||||
.push builder.context.findName stack.slug
|
||||
|
||||
@@ -13,50 +13,36 @@ Stack = require './stack'
|
||||
module.exports = class Recipe extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.input? then throw new Error 'attributes.input is required'
|
||||
if not attributes.item? then throw new Error 'attributes.item is required'
|
||||
if attributes.item?
|
||||
attributes.name = attributes.item.name
|
||||
attributes.slug = attributes.item.slug
|
||||
attributes.output ?= [new Stack slug:item.slug, quantity:1]
|
||||
|
||||
attributes.output ?= [new Stack itemSlug:attributes.item.slug]
|
||||
if not attributes.name? then throw new Error 'attributes.name is required'
|
||||
if not attributes.input? then throw new Error 'attributes.input is required'
|
||||
if not attributes.pattern? then throw new Error 'attributes.pattern is required'
|
||||
|
||||
attributes.item ?= null
|
||||
attributes.output ?= [new Stack slug:_.slugify(attributes.name), quantity:1]
|
||||
attributes.pattern = @_parsePattern attributes.pattern
|
||||
attributes.slug ?= attributes.output[0].slug
|
||||
attributes.tools ?= []
|
||||
super attributes, options
|
||||
|
||||
@item.addRecipe this
|
||||
|
||||
Object.defineProperties this,
|
||||
'defaultPattern': { get: -> @_computeDefaultPattern() }
|
||||
'name': { get: -> @item.name }
|
||||
'slug': { get: -> @item.slug }
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getItemSlugAt: (index)->
|
||||
getItemSlugAt: (patternSlot)->
|
||||
pattern = if @pattern? then @pattern else @_computeDefaultPattern()
|
||||
|
||||
trueIndex = 0:0, 1:1, 2:2, 3:4, 4:5, 5:6, 6:8, 7:9, 8:10
|
||||
patternDigit = pattern[trueIndex[index]]
|
||||
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.itemSlug
|
||||
|
||||
make: (inventory, missing)->
|
||||
for stack in @input
|
||||
itemSlug = stack.itemSlug
|
||||
needed = stack.quantity
|
||||
while needed > 0
|
||||
if inventory.hasAtLeast itemSlug
|
||||
inventory.remove itemSlug
|
||||
else
|
||||
missing.add itemSlug
|
||||
|
||||
for stack in @output
|
||||
inventory.add stack.itemSlug, stack.quantity
|
||||
|
||||
return this
|
||||
return stack.slug
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
@@ -109,24 +95,3 @@ module.exports = class Recipe extends BaseModel
|
||||
pattern = array.join ''
|
||||
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
|
||||
return pattern
|
||||
|
||||
_computeDefaultPattern: ->
|
||||
itemCount = @input.length
|
||||
slotCount = _.reduce @input, ((total, stack)-> total + stack.quantity), 0
|
||||
|
||||
return '... .0. ...' if itemCount is 1 and slotCount is 1
|
||||
return '00. 00. ...' if itemCount is 1 and slotCount is 4
|
||||
return '000 000 000' if itemCount is 1 and slotCount is 9
|
||||
|
||||
result = ['.', '.', '.', '.', '.', '.', '.', '.', '.']
|
||||
indexes = [4, 7, 1, 3, 5, 6, 8, 0, 2]
|
||||
|
||||
for i in [0...@input.length]
|
||||
stack = @input[i]
|
||||
for j in [0...stack.quantity]
|
||||
index = indexes.shift()
|
||||
result[index] = "#{i}"
|
||||
|
||||
pattern = result.join ''
|
||||
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
|
||||
return pattern
|
||||
|
||||
@@ -5,30 +5,18 @@ Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Stack
|
||||
module.exports = class Stack extends BaseModel
|
||||
|
||||
constructor: (attributes={})->
|
||||
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
|
||||
if not attributes.slug? then throw new Error 'attributes.slug is required'
|
||||
attributes.quantity ?= 1
|
||||
|
||||
@itemSlug = attributes.itemSlug
|
||||
@quantity = attributes.quantity
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
canMerge: (stack)->
|
||||
return @itemSlug is stack.itemSlug
|
||||
|
||||
merge: (stack)->
|
||||
if not @canMerge stack
|
||||
throw new Error "this stack of #{@itemSlug} cannot merge a stack of #{@stack.itemSlug}"
|
||||
|
||||
@quantity += stack.quantity
|
||||
return this
|
||||
super attributes
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@quantity} #{@itemSlug}"
|
||||
return "#{@quantity} #{@slug}"
|
||||
|
||||
Reference in New Issue
Block a user