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:
@@ -47,16 +47,11 @@ All rights reserved.
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quantity {
|
.quantity {
|
||||||
position: absolute; right: 0; bottom: 0;
|
position: absolute; right: 0.1em; bottom: 0.1em;
|
||||||
color: black;
|
color: white;
|
||||||
font-family: PressStart;
|
font-family: PressStart;
|
||||||
font-size: $font-size-large;
|
font-size: $font-size-large;
|
||||||
}
|
text-shadow: -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000;
|
||||||
|
|
||||||
.quantity + .quantity {
|
|
||||||
color: white;
|
|
||||||
right: 0.2em; bottom: 0.2em;
|
|
||||||
z-index: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ ModelState.loaded = 'loaded'
|
|||||||
ModelState.failed = 'failed'
|
ModelState.failed = 'failed'
|
||||||
|
|
||||||
exports.Url = Url = {}
|
exports.Url = Url = {}
|
||||||
Url.itemIcon = _.template "/data/<%= modSlug %>/<%= modVersion %>/images/<%= itemSlug %>.png"
|
Url.itemIcon = _.template "/data/<%= modSlug %>/<%= modVersion %>/images/<%= slug %>.png"
|
||||||
Url.item = _.template "/item/<%= encodeURIComponent(itemName) %>"
|
Url.item = _.template "/item/<%= encodeURIComponent(itemName) %>"
|
||||||
Url.mod = _.template "/data/<%= modSlug %>/mod.cg"
|
Url.mod = _.template "/data/<%= modSlug %>/mod.cg"
|
||||||
Url.modVersion = _.template "/data/<%= modSlug %>/<%= modVersion %>/mod-version.cg"
|
Url.modVersion = _.template "/data/<%= modSlug %>/<%= modVersion %>/mod-version.cg"
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ module.exports = class BaseController extends Backbone.View
|
|||||||
return unless @onWillChangeModel @_model, newModel
|
return unless @onWillChangeModel @_model, newModel
|
||||||
|
|
||||||
@_model = newModel
|
@_model = newModel
|
||||||
|
@_model.silent = false
|
||||||
@_tryRefresh()
|
@_tryRefresh()
|
||||||
|
|
||||||
# Backbone.View Overrides ######################################################################
|
# Backbone.View Overrides ######################################################################
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ module.exports = class CraftingTableController extends BaseController
|
|||||||
@imageLoader = options.imageLoader
|
@imageLoader = options.imageLoader
|
||||||
@modPack = options.modPack
|
@modPack = options.modPack
|
||||||
|
|
||||||
|
@model.plan.silent = false
|
||||||
|
|
||||||
# Event Methods ################################################################################
|
# Event Methods ################################################################################
|
||||||
|
|
||||||
onNextClicked: ->
|
onNextClicked: ->
|
||||||
@@ -79,7 +81,7 @@ module.exports = class CraftingTableController extends BaseController
|
|||||||
|
|
||||||
outputStack = @model.output
|
outputStack = @model.output
|
||||||
if outputStack?
|
if outputStack?
|
||||||
display = @modPack.findItemDisplay outputStack.itemSlug
|
display = @modPack.findItemDisplay outputStack.slug
|
||||||
@$outputLink.attr 'href', display.itemUrl
|
@$outputLink.attr 'href', display.itemUrl
|
||||||
@$outputLink.attr 'title', display.itemName
|
@$outputLink.attr 'title', display.itemName
|
||||||
@$outputImg.attr 'alt', display.itemName
|
@$outputImg.attr 'alt', display.itemName
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ module.exports = class ModPackController extends BaseController
|
|||||||
|
|
||||||
onWillRender: ->
|
onWillRender: ->
|
||||||
for attributes in DefaultModVersions
|
for attributes in DefaultModVersions
|
||||||
modVersion = new ModVersion _.extend attributes, modPack:@model
|
modVersion = new ModVersion attributes
|
||||||
modVersion.fetch()
|
modVersion.fetch()
|
||||||
|
@model.addModVersion modVersion
|
||||||
|
|
||||||
onDidRender: ->
|
onDidRender: ->
|
||||||
@$table = @$('table')
|
@$table = @$('table')
|
||||||
@@ -47,13 +48,14 @@ module.exports = class ModPackController extends BaseController
|
|||||||
return
|
return
|
||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
while index < Math.min @_controllers.length, @model.modVersions.length
|
modVersions = @model.getModVersions()
|
||||||
|
while index < Math.min @_controllers.length, modVersions.length
|
||||||
controller = @_controllers[index]
|
controller = @_controllers[index]
|
||||||
controller.model = @model.modVersions[index]
|
controller.model = modVersions[index]
|
||||||
index++
|
index++
|
||||||
|
|
||||||
while @_controllers.length < @model.modVersions.length
|
while @_controllers.length < modVersions.length
|
||||||
controller = new ModVersionController model:@model.modVersions[index]
|
controller = new ModVersionController model:modVersions[index]
|
||||||
controller.render()
|
controller.render()
|
||||||
@_controllers.push controller
|
@_controllers.push controller
|
||||||
controller.$el.hide duration:0
|
controller.$el.hide duration:0
|
||||||
@@ -61,7 +63,7 @@ module.exports = class ModPackController extends BaseController
|
|||||||
controller.$el.slideDown duration:Duration.normal
|
controller.$el.slideDown duration:Duration.normal
|
||||||
index++
|
index++
|
||||||
|
|
||||||
while @_controllers.length > @model.modVersions.length
|
while @_controllers.length > modVersions.length
|
||||||
controller = @_controllers.pop()
|
controller = @_controllers.pop()
|
||||||
controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove()
|
controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove()
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ module.exports = class StackController extends BaseController
|
|||||||
super
|
super
|
||||||
|
|
||||||
refresh: ->
|
refresh: ->
|
||||||
display = @modPack.findItemDisplay @model.itemSlug
|
display = @modPack.findItemDisplay @model.slug
|
||||||
|
|
||||||
@$nameField.html display.itemName
|
@$nameField.html display.itemName
|
||||||
@$quantityField.html @model.quantity
|
@$quantityField.html @model.quantity
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ All rights reserved.
|
|||||||
###
|
###
|
||||||
|
|
||||||
require './underscore_mixins'
|
require './underscore_mixins'
|
||||||
|
require './polyfill'
|
||||||
|
|
||||||
views = require './views'
|
views = require './views'
|
||||||
FeedbackController = require './controllers/feedback_controller'
|
FeedbackController = require './controllers/feedback_controller'
|
||||||
|
|||||||
@@ -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
|
module.exports = class BaseModel extends Backbone.Model
|
||||||
|
|
||||||
constructor: (attributes={}, options={})->
|
constructor: (attributes={}, options={})->
|
||||||
options.allowedSyncMethods = []
|
options.silent ?= true
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|
||||||
makeGetter = (name)-> return -> @get name
|
makeGetter = (name)-> return -> @get name
|
||||||
@@ -22,9 +22,9 @@ module.exports = class BaseModel extends Backbone.Model
|
|||||||
continue if name is 'id'
|
continue if name is 'id'
|
||||||
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
|
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 'request', => @state = ModelState.loading
|
||||||
@on 'sync', => @state = ModelState.loaded
|
@on 'sync', => @state = ModelState.loaded
|
||||||
@on 'error', => @state = ModelState.error
|
@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
|
success: (text, status, xhr)=> resolve @onLoadSucceeded text, status, xhr
|
||||||
error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr
|
error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr
|
||||||
|
|
||||||
|
@loading.catch -> # do nothing. prevents unhandled promise warnings
|
||||||
return @loading
|
return @loading
|
||||||
|
|
||||||
parse: (text)->
|
parse: (text)->
|
||||||
@@ -70,6 +71,7 @@ module.exports = class BaseModel extends Backbone.Model
|
|||||||
throw new Error "#{@constructor.name} (#{@cid}) is not permitted to #{method}"
|
throw new Error "#{@constructor.name} (#{@cid}) is not permitted to #{method}"
|
||||||
|
|
||||||
trigger: (name)->
|
trigger: (name)->
|
||||||
|
return if @silent
|
||||||
logger.trace "#{@constructor.name}.#{@cid} triggered a \"#{name}\" event"
|
logger.trace "#{@constructor.name}.#{@cid} triggered a \"#{name}\" event"
|
||||||
super
|
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
|
attributes.recipe ?= null
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|
||||||
Object.defineProperty @prototype, 'slotCount', get:-> CraftingGrid.SLOT_COUNT
|
Object.defineProperties this,
|
||||||
|
'slotCount': { get:-> CraftingGrid.SLOT_COUNT }
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
getItemDisplayAt: (index)->
|
getItemDisplayAt: (slot)->
|
||||||
if index >= @slotCount then throw new Error "index (#{index}) must be less than #{@slotCount}"
|
if slot >= @slotCount then throw new Error "slot (#{slot}) must be less than #{@slotCount}"
|
||||||
return null unless @recipe?
|
return null unless @recipe?
|
||||||
|
|
||||||
itemSlug = @recipe.getItemSlugAt index
|
slug = @recipe.getItemSlugAt slot
|
||||||
return null unless itemSlug?
|
return null unless slug?
|
||||||
|
|
||||||
itemDisplay = @modPack.findItemDisplay itemSlug
|
itemDisplay = @modPack.findItemDisplay slug
|
||||||
return itemDisplay
|
return itemDisplay
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ All rights reserved.
|
|||||||
###
|
###
|
||||||
|
|
||||||
BaseModel = require './base_model'
|
BaseModel = require './base_model'
|
||||||
|
{Event} = require '../constants'
|
||||||
Inventory = require './inventory'
|
Inventory = require './inventory'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
@@ -17,44 +18,50 @@ module.exports = class CraftingPlan extends BaseModel
|
|||||||
attributes.includingTools ?= false
|
attributes.includingTools ?= false
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|
||||||
@have = new Inventory
|
@have = new Inventory
|
||||||
@want = new Inventory
|
@want = new Inventory
|
||||||
@need = new Inventory
|
@need = new Inventory
|
||||||
@result = new Inventory
|
@result = new Inventory
|
||||||
@storage = options.storage
|
|
||||||
|
|
||||||
@have.on 'change', => @craft()
|
@clear silent:true
|
||||||
@want.on 'change', => @craft()
|
|
||||||
@modPack.on 'change', => @craft()
|
|
||||||
|
|
||||||
@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 ###############################################################################
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
clear: ->
|
clear: (options={})->
|
||||||
|
options.silent ?= false
|
||||||
|
|
||||||
@steps = []
|
@steps = []
|
||||||
@need.clear()
|
@need.clear()
|
||||||
@result.clear()
|
@result.clear()
|
||||||
|
|
||||||
@trigger 'change', this
|
@trigger 'change', this unless options.silent
|
||||||
return this
|
return this
|
||||||
|
|
||||||
craft: ->
|
craft: ->
|
||||||
@clear()
|
@clear silent:true
|
||||||
|
@need.silent = @result.silent = true
|
||||||
|
|
||||||
@result.addInventory @have
|
@result.addInventory @have
|
||||||
|
|
||||||
@steps = {}
|
@steps = {}
|
||||||
@want.each (stack)=>
|
@want.each (stack)=>
|
||||||
@_findSteps stack.itemSlug
|
@_findSteps stack.slug
|
||||||
@need.add stack.itemSlug, stack.quantity
|
@need.add stack.slug, stack.quantity
|
||||||
|
|
||||||
@steps = _.values @steps
|
@steps = _.values @steps
|
||||||
@_resolveNeeds()
|
@_resolveNeeds()
|
||||||
@_removeExtraSteps()
|
@_removeExtraSteps()
|
||||||
@result.addInventory @want
|
@result.addInventory @want
|
||||||
|
|
||||||
|
@need.silent = @result.silent = false
|
||||||
|
@need.trigger 'change', @need
|
||||||
|
@result.trigger 'change', @result
|
||||||
@trigger 'change', this
|
@trigger 'change', this
|
||||||
|
|
||||||
# Event Methods ################################################################################
|
# Event Methods ################################################################################
|
||||||
@@ -82,10 +89,10 @@ module.exports = class CraftingPlan extends BaseModel
|
|||||||
@steps[recipe.slug] = recipe:recipe
|
@steps[recipe.slug] = recipe:recipe
|
||||||
|
|
||||||
_chooseRecipe: (item)->
|
_chooseRecipe: (item)->
|
||||||
return item.recipes[0]
|
return item.getPrimaryRecipe()
|
||||||
|
|
||||||
_findSteps: (itemSlug)->
|
_findSteps: (slug)->
|
||||||
item = @modPack.findItem itemSlug
|
item = @modPack.findItem slug
|
||||||
return unless item?
|
return unless item?
|
||||||
return unless item.isCraftable
|
return unless item.isCraftable
|
||||||
return if item.isGatherable
|
return if item.isGatherable
|
||||||
@@ -94,16 +101,16 @@ module.exports = class CraftingPlan extends BaseModel
|
|||||||
|
|
||||||
if @includingTools
|
if @includingTools
|
||||||
for toolStack in recipe.tools
|
for toolStack in recipe.tools
|
||||||
if not @_hasStep toolStack.itemSlug
|
if not @_hasStep toolStack.slug
|
||||||
@_findSteps toolStack.itemSlug
|
@_findSteps toolStack.slug
|
||||||
|
|
||||||
for inputStack in recipe.input
|
for inputStack in recipe.input
|
||||||
@_findSteps inputStack.itemSlug
|
@_findSteps inputStack.slug
|
||||||
|
|
||||||
@_addStep recipe
|
@_addStep recipe
|
||||||
|
|
||||||
_hasStep: (itemSlug)->
|
_hasStep: (slug)->
|
||||||
return @steps[itemSlug]?
|
return @steps[slug]?
|
||||||
|
|
||||||
_removeExtraSteps: ->
|
_removeExtraSteps: ->
|
||||||
result = (step for step in @steps when step.multiplier > 0)
|
result = (step for step in @steps when step.multiplier > 0)
|
||||||
@@ -118,25 +125,25 @@ module.exports = class CraftingPlan extends BaseModel
|
|||||||
|
|
||||||
if @includingTools
|
if @includingTools
|
||||||
for stack in recipe.tools
|
for stack in recipe.tools
|
||||||
slug = stack.itemSlug
|
slug = stack.slug
|
||||||
available = @result.quantityOf(slug) + @need.quantityOf(slug)
|
available = @result.quantityOf(slug) + @need.quantityOf(slug)
|
||||||
needed = Math.max 0, stack.quantity - available
|
needed = Math.max 0, stack.quantity - available
|
||||||
|
|
||||||
@need.add stack.itemSlug, needed
|
@need.add stack.slug, needed
|
||||||
@result.add stack.itemSlug, needed
|
@result.add stack.slug, needed
|
||||||
|
|
||||||
for stack in recipe.input
|
for stack in recipe.input
|
||||||
needed = step.multiplier * stack.quantity
|
needed = step.multiplier * stack.quantity
|
||||||
consumed = Math.min needed, @result.quantityOf(stack.itemSlug)
|
consumed = Math.min needed, @result.quantityOf(stack.slug)
|
||||||
remaining = needed - consumed
|
remaining = needed - consumed
|
||||||
|
|
||||||
@result.remove stack.itemSlug, consumed
|
@result.remove stack.slug, consumed
|
||||||
@need.add stack.itemSlug, remaining
|
@need.add stack.slug, remaining
|
||||||
|
|
||||||
for stack in recipe.output
|
for stack in recipe.output
|
||||||
created = stack.quantity * step.multiplier
|
created = stack.quantity * step.multiplier
|
||||||
consumed = Math.min created, @need.quantityOf stack.itemSlug
|
consumed = Math.min created, @need.quantityOf stack.slug
|
||||||
remaining = created - consumed
|
remaining = created - consumed
|
||||||
|
|
||||||
@result.add stack.itemSlug, remaining
|
@result.add stack.slug, remaining
|
||||||
@need.remove stack.itemSlug, consumed
|
@need.remove stack.slug, consumed
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ module.exports = class CraftingTable extends BaseModel
|
|||||||
getToolNames: ->
|
getToolNames: ->
|
||||||
recipe = @plan.steps[@_step]?.recipe
|
recipe = @plan.steps[@_step]?.recipe
|
||||||
return '' unless 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 ', '
|
toolNames = (@modPack.findName(slug) for slug in toolSlugs).join ', '
|
||||||
return toolNames
|
return toolNames
|
||||||
|
|
||||||
|
|||||||
@@ -15,78 +15,80 @@ module.exports = class Inventory extends BaseModel
|
|||||||
|
|
||||||
constructor: (attributes={}, options={})->
|
constructor: (attributes={}, options={})->
|
||||||
super 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 ###############################################################################
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
add: (itemSlug, quantity=1)->
|
add: (slug, quantity=1)->
|
||||||
@_add itemSlug, quantity
|
@_add slug, quantity
|
||||||
@trigger Event.add, this, itemSlug, quantity
|
@trigger Event.add, this, slug, quantity
|
||||||
@trigger Event.change, this
|
@trigger Event.change, this
|
||||||
return this
|
return this
|
||||||
|
|
||||||
addInventory: (inventory)->
|
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
|
@trigger Event.change, this
|
||||||
return this
|
return this
|
||||||
|
|
||||||
clear: ->
|
clear: (options={})->
|
||||||
|
options.silent ?= false
|
||||||
@_stacks = {}
|
@_stacks = {}
|
||||||
@_slugs = []
|
@_slugs = []
|
||||||
@trigger 'change', self
|
|
||||||
|
@trigger Event.change, this unless options.silent
|
||||||
|
|
||||||
clone: ->
|
clone: ->
|
||||||
inventory = new Inventory
|
inventory = new Inventory
|
||||||
@each (stack)-> inventory.add stack.itemSlug, stack.quantity
|
inventory.addInventory this
|
||||||
return inventory
|
return inventory
|
||||||
|
|
||||||
each: (onStack)->
|
each: (callback)->
|
||||||
for itemSlug in @_slugs
|
for slug in @_slugs
|
||||||
stack = @_stacks[itemSlug]
|
callback @_stacks[slug]
|
||||||
onStack stack
|
|
||||||
|
|
||||||
getStack: (itemSlug)->
|
hasAtLeast: (slug, quantity=1)->
|
||||||
return @_stacks[itemSlug]
|
|
||||||
|
|
||||||
hasAtLeast: (itemSlug, quantity=1)->
|
|
||||||
if quantity is 0 then return true
|
if quantity is 0 then return true
|
||||||
|
|
||||||
stack = @_stacks[itemSlug]
|
stack = @_stacks[slug]
|
||||||
return false unless stack?
|
return false unless stack?
|
||||||
return stack.quantity >= quantity
|
return stack.quantity >= quantity
|
||||||
|
|
||||||
pop: ->
|
pop: ->
|
||||||
itemSlug = @_slugs.pop()
|
slug = @_slugs.pop()
|
||||||
return null unless itemSlug?
|
return null unless slug?
|
||||||
|
|
||||||
stack = @_stacks[itemSlug]
|
stack = @_stacks[slug]
|
||||||
delete @_stacks[itemSlug]
|
delete @_stacks[slug]
|
||||||
|
|
||||||
@trigger Event.remove, this, stack.itemSlug, stack.quantity
|
@trigger Event.remove, this, stack.slug, stack.quantity
|
||||||
@trigger Event.change, this
|
@trigger Event.change, this
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
quantityOf: (itemSlug)->
|
quantityOf: (slug)->
|
||||||
stack = @_stacks[itemSlug]
|
stack = @_stacks[slug]
|
||||||
return 0 unless stack?
|
return 0 unless stack?
|
||||||
return stack.quantity
|
return stack.quantity
|
||||||
|
|
||||||
remove: (itemSlug, quantity=1)->
|
remove: (slug, quantity=1)->
|
||||||
return if quantity is 0
|
return if quantity is 0
|
||||||
|
|
||||||
stack = @_stacks[itemSlug]
|
stack = @_stacks[slug]
|
||||||
if not stack? then throw new Error "cannot remove #{itemSlug} since it is not in this inventory"
|
if not stack? then throw new Error "cannot remove #{slug} since it is not in this inventory"
|
||||||
if stack.quantity < quantity
|
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
|
stack.quantity -= quantity
|
||||||
if stack.quantity is 0
|
if stack.quantity is 0
|
||||||
delete @_stacks[itemSlug]
|
delete @_stacks[slug]
|
||||||
@_slugs = _(@_slugs).without itemSlug
|
@_slugs = _(@_slugs).without slug
|
||||||
|
|
||||||
@trigger Event.remove, this, itemSlug, quantity
|
@trigger Event.remove, this, slug, quantity
|
||||||
@trigger Event.change, this
|
@trigger Event.change, this
|
||||||
return this
|
return this
|
||||||
|
|
||||||
@@ -94,9 +96,9 @@ module.exports = class Inventory extends BaseModel
|
|||||||
result = []
|
result = []
|
||||||
@each (stack)->
|
@each (stack)->
|
||||||
if stack.quantity > 1
|
if stack.quantity > 1
|
||||||
result.push [stack.quantity, stack.itemSlug]
|
result.push [stack.quantity, stack.slug]
|
||||||
else
|
else
|
||||||
result.push stack.itemSlug
|
result.push stack.slug
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Object Overrides #############################################################################
|
# Object Overrides #############################################################################
|
||||||
@@ -116,14 +118,15 @@ module.exports = class Inventory extends BaseModel
|
|||||||
|
|
||||||
# Private Methods ##############################################################################
|
# Private Methods ##############################################################################
|
||||||
|
|
||||||
_add: (itemSlug, quantity=1)->
|
_add: (slug, quantity=1)->
|
||||||
|
return unless slug?
|
||||||
return if quantity is 0
|
return if quantity is 0
|
||||||
|
|
||||||
stack = @_stacks[itemSlug]
|
stack = @_stacks[slug]
|
||||||
if not stack?
|
if not stack?
|
||||||
stack = new Stack itemSlug:itemSlug, quantity:quantity
|
stack = new Stack slug:slug, quantity:quantity
|
||||||
@_stacks[itemSlug] = stack
|
@_stacks[slug] = stack
|
||||||
@_slugs.push itemSlug
|
@_slugs.push slug
|
||||||
@_slugs.sort()
|
@_slugs.sort()
|
||||||
else
|
else
|
||||||
stack.quantity += quantity
|
stack.quantity += quantity
|
||||||
|
|||||||
@@ -5,33 +5,38 @@ Copyright (c) 2014-2015 by Redwood Labs
|
|||||||
All rights reserved.
|
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
|
module.exports = class Item extends BaseModel
|
||||||
|
|
||||||
@DEFAULT_STACK_SIZE = 64
|
|
||||||
|
|
||||||
constructor: (attributes={}, options={})->
|
constructor: (attributes={}, options={})->
|
||||||
if not attributes.name? then throw new Error 'attributes.name is required'
|
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.isGatherable ?= false
|
||||||
attributes.recipes ?= []
|
|
||||||
attributes.slug ?= _.slugify attributes.name
|
attributes.slug ?= _.slugify attributes.name
|
||||||
attributes.stackSize ?= Item.DEFAULT_STACK_SIZE
|
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|
||||||
@modVersion.addItem this
|
@_recipes = []
|
||||||
|
Object.defineProperties this,
|
||||||
Object.defineProperty @prototype, 'isCraftable', get:-> @recipes.length > 0
|
'isCraftable': { get:-> @_recipes.length > 0 }
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
addRecipe: (recipe)->
|
addRecipe: (recipe)->
|
||||||
if recipe.item isnt this then throw new Error "cannot add a recipe which isn't associated with this item"
|
if recipe.slug isnt @slug then throw new Error "cannot add a recipe for #{recipe.slug} to #{@slug}"
|
||||||
@recipes.push recipe
|
@_recipes.push recipe
|
||||||
|
|
||||||
|
eachRecipe: (callback)->
|
||||||
|
for recipe in @_recipes
|
||||||
|
callback recipe
|
||||||
|
|
||||||
|
getPrimaryRecipe: ->
|
||||||
|
return @_recipes[0]
|
||||||
|
|
||||||
compareTo: (that)->
|
compareTo: (that)->
|
||||||
if this.slug isnt that.slug
|
if this.slug isnt that.slug
|
||||||
@@ -52,13 +57,10 @@ module.exports = class Item extends BaseModel
|
|||||||
if _.slugify(@name) isnt @slug
|
if _.slugify(@name) isnt @slug
|
||||||
result.push ', slug:'; result.push @slug
|
result.push ', slug:'; result.push @slug
|
||||||
|
|
||||||
if @stackSize isnt Item.DEFAULT_STACK_SIZE
|
|
||||||
result.push ', stackSize:'; result.push @stackSize
|
|
||||||
|
|
||||||
if @recipes.length > 0
|
if @recipes.length > 0
|
||||||
result.push ', recipes:'
|
result.push ', recipes:«'
|
||||||
result.push @recipes.length
|
result.push @_recipes.length
|
||||||
result.push ' items'
|
result.push ' items»'
|
||||||
|
|
||||||
result.push '}'
|
result.push '}'
|
||||||
return result.join ''
|
return result.join ''
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ module.exports = class ItemPage extends BaseModel
|
|||||||
_updateLocation: ->
|
_updateLocation: ->
|
||||||
list = @plan.want.toList()
|
list = @plan.want.toList()
|
||||||
if list.length is 1
|
if list.length is 1
|
||||||
itemSlug = if _.isArray(list[0]) then list[0][1] else list[0]
|
slug = if _.isArray(list[0]) then list[0][1] else list[0]
|
||||||
router.navigate "/item/#{itemSlug}"
|
router.navigate "/item/#{slug}"
|
||||||
else
|
else
|
||||||
router.navigate "/"
|
router.navigate "/"
|
||||||
@@ -6,6 +6,7 @@ All rights reserved.
|
|||||||
###
|
###
|
||||||
|
|
||||||
BaseModel = require './base_model'
|
BaseModel = require './base_model'
|
||||||
|
{Url} = require '../constants'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -20,9 +21,5 @@ module.exports = class Mod extends BaseModel
|
|||||||
|
|
||||||
parse: (response)->
|
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: ->
|
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
|
module.exports = class ModPack extends BaseModel
|
||||||
|
|
||||||
constructor: (attributes={}, options={})->
|
constructor: (attributes={}, options={})->
|
||||||
attributes.modVersions ?= []
|
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|
||||||
|
@_modVersions = []
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
addModVersion: (modVersion)->
|
findItem: (slug, options={})->
|
||||||
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={})->
|
|
||||||
options.includeDisabled ?= false
|
options.includeDisabled ?= false
|
||||||
|
|
||||||
for modVersion in @modVersions
|
for modVersion in @_modVersions
|
||||||
continue unless modVersion.enabled or options.includeDisabled
|
continue unless modVersion.enabled or options.includeDisabled
|
||||||
item = modVersion.items[itemSlug]
|
item = modVersion.findItem slug
|
||||||
return item if item?
|
return item if item?
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@@ -53,9 +36,9 @@ module.exports = class ModPack extends BaseModel
|
|||||||
options.includeDisabled ?= false
|
options.includeDisabled ?= false
|
||||||
slug = _.slugify name
|
slug = _.slugify name
|
||||||
|
|
||||||
for modVersion in @modVersions
|
for modVersion in @_modVersions
|
||||||
continue unless modVersion.enabled or options.includeDisabled
|
continue unless modVersion.enabled or options.includeDisabled
|
||||||
item = modVersion.items[slug]
|
item = modVersion.findItem slug
|
||||||
return item if item?
|
return item if item?
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@@ -63,12 +46,12 @@ module.exports = class ModPack extends BaseModel
|
|||||||
findName: (slug, options={})->
|
findName: (slug, options={})->
|
||||||
options.includeDisabled ?= false
|
options.includeDisabled ?= false
|
||||||
|
|
||||||
for modVersion in @modVersions
|
for modVersion in @_modVersions
|
||||||
continue unless modVersion.enabled or options.includeDisabled
|
continue unless modVersion.enabled or options.includeDisabled
|
||||||
name = modVersion.findName slug
|
name = modVersion.findName slug
|
||||||
return name if name
|
return name if name
|
||||||
|
|
||||||
return slug
|
return null
|
||||||
|
|
||||||
findItemDisplay: (slug)->
|
findItemDisplay: (slug)->
|
||||||
result = {}
|
result = {}
|
||||||
@@ -76,39 +59,50 @@ module.exports = class ModPack extends BaseModel
|
|||||||
if item?
|
if item?
|
||||||
result.modSlug = item.modVersion.slug
|
result.modSlug = item.modVersion.slug
|
||||||
result.modVersion = item.modVersion.version
|
result.modVersion = item.modVersion.version
|
||||||
result.itemSlug = item.slug
|
result.slug = item.slug
|
||||||
result.itemName = item.name
|
result.itemName = item.name
|
||||||
else
|
else
|
||||||
result.modSlug = _.slugify DefaultModVersions[0].name
|
result.modSlug = _.slugify DefaultModVersions[0].name
|
||||||
result.modVersion = DefaultModVersions[0].version
|
result.modVersion = DefaultModVersions[0].version
|
||||||
result.itemSlug = slug
|
result.slug = slug
|
||||||
result.itemName = @findName slug, includeDisabled:true
|
result.itemName = @findName slug, includeDisabled:true
|
||||||
|
|
||||||
result.iconUrl = Url.itemIcon result
|
result.iconUrl = Url.itemIcon result
|
||||||
result.itemUrl = Url.item result
|
result.itemUrl = Url.item result
|
||||||
return 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={})->
|
isValidName: (name, options={})->
|
||||||
options.includeDisabled ?= false
|
options.includeDisabled ?= false
|
||||||
|
|
||||||
slug = _.slugify name
|
slug = _.slugify name
|
||||||
for modVersion in @modVersions
|
for modVersion in @_modVersions
|
||||||
continue unless modVersion.enabled or options.includeDisabled
|
continue unless modVersion.enabled or options.includeDisabled
|
||||||
name = modVersion.findName slug
|
name = modVersion.findName slug
|
||||||
return true if name
|
return true if name
|
||||||
|
|
||||||
return false
|
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 #############################################################################
|
# Object Overrides #############################################################################
|
||||||
|
|
||||||
toString: ->
|
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.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
BaseModel = require './base_model'
|
BaseCollection = require './base_collection'
|
||||||
{Event} = require '../constants'
|
BaseModel = require './base_model'
|
||||||
{RequiredMods} = require '../constants'
|
{Event} = require '../constants'
|
||||||
{Url} = require '../constants'
|
Item = require './item'
|
||||||
|
{RequiredMods} = require '../constants'
|
||||||
|
{Url} = require '../constants'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
module.exports = class ModVersion extends BaseModel
|
module.exports = class ModVersion extends BaseModel
|
||||||
|
|
||||||
constructor: (attributes={}, options={})->
|
constructor: (attributes={}, options={})->
|
||||||
if not attributes.modPack? then throw new Error 'attributes.modPack is required'
|
if not attributes.name? then throw new Error 'attributes.name is required'
|
||||||
if _.isEmpty(attributes.name) then throw new Error 'attributes.name cannot be empty'
|
if not attributes.version? then throw new Error 'attributes.version is required'
|
||||||
if _.isEmpty(attributes.version) then throw new Error 'attributes.version cannot be empty'
|
|
||||||
|
|
||||||
attributes.description ?= ''
|
attributes.description ?= ''
|
||||||
attributes.enabled ?= true
|
attributes.enabled ?= true
|
||||||
attributes.items ?= {}
|
|
||||||
attributes.names ?= {}
|
|
||||||
attributes.slug ?= _.slugify attributes.name
|
attributes.slug ?= _.slugify attributes.name
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|
||||||
@modPack.addModVersion this
|
@_items = {}
|
||||||
|
@_names = {}
|
||||||
|
@_slugs = []
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
addItem: (item)->
|
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}"
|
||||||
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
|
|
||||||
|
|
||||||
|
@_items[item.slug] = item
|
||||||
|
item.modVersion = this
|
||||||
|
@registerSlug item.slug, item.name
|
||||||
return this
|
return this
|
||||||
|
|
||||||
compareTo: (that)->
|
compareTo: (that)->
|
||||||
@@ -56,26 +54,49 @@ module.exports = class ModVersion extends BaseModel
|
|||||||
else
|
else
|
||||||
return if this.name < that.name then -1 else +1
|
return if this.name < that.name then -1 else +1
|
||||||
|
|
||||||
findName: (slug)->
|
eachItem: (callback)->
|
||||||
return @names[slug]
|
for slug in @_slugs
|
||||||
|
callback @_items[slug], slug
|
||||||
|
return this
|
||||||
|
|
||||||
hasRecipe: (name)->
|
eachName: (callback)->
|
||||||
item = @findItemByName name
|
for slug in @_slugs
|
||||||
return false unless item?
|
callback @_names[slug], slug
|
||||||
return item.recipes.length > 0
|
return this
|
||||||
|
|
||||||
|
findItem: (slug)->
|
||||||
|
return @_items[slug]
|
||||||
|
|
||||||
|
findItemByName: (name)->
|
||||||
|
return @findItem _.slugify name
|
||||||
|
|
||||||
|
findName: (slug)->
|
||||||
|
return @_names[slug]
|
||||||
|
|
||||||
registerSlug: (slug, name)->
|
registerSlug: (slug, name)->
|
||||||
@names[slug] = name
|
hasSlug = @_names[slug]?
|
||||||
@trigger Event.change + ':names', this, @names
|
@_names[slug] = name
|
||||||
@trigger Event.change, this
|
|
||||||
|
if not hasSlug
|
||||||
|
@_slugs.push slug
|
||||||
|
@_slugs.sort()
|
||||||
|
@_slugs = _.uniq @_slugs, true
|
||||||
|
|
||||||
return this
|
return this
|
||||||
|
|
||||||
# Backbone.Model Overrides #####################################################################
|
# Backbone.Model Overrides #####################################################################
|
||||||
|
|
||||||
parse: (text)->
|
parse: (text)->
|
||||||
|
currentSilent = @silent
|
||||||
|
@silent = true
|
||||||
|
|
||||||
ModVersionParser = require './mod_version_parser' # to avoid require cycles
|
ModVersionParser = require './mod_version_parser' # to avoid require cycles
|
||||||
@_parser ?= new ModVersionParser modVersion:this
|
@_parser ?= new ModVersionParser model:this
|
||||||
@_parser.parse text
|
@_parser.parse text
|
||||||
|
|
||||||
|
@silent = currentSilent
|
||||||
|
@trigger Event.change, this
|
||||||
|
|
||||||
return null # prevent calling `set`
|
return null # prevent calling `set`
|
||||||
|
|
||||||
url: ->
|
url: ->
|
||||||
@@ -88,4 +109,5 @@ module.exports = class ModVersion extends BaseModel
|
|||||||
enabled:#{@enabled},
|
enabled:#{@enabled},
|
||||||
name:#{@name},
|
name:#{@name},
|
||||||
version:#{@version},
|
version:#{@version},
|
||||||
items:#{_.keys(@items).length} items}"
|
items:#{_.keys(@_items).length} items
|
||||||
|
}"
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ module.exports = class ModVersionParser
|
|||||||
@CURRENT_VERSION = '2'
|
@CURRENT_VERSION = '2'
|
||||||
|
|
||||||
constructor: (options={})->
|
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
|
options.showAllErrors ?= false
|
||||||
|
|
||||||
@_modVersion = options.modVersion
|
@_model = options.model
|
||||||
@_parsers =
|
@_parsers =
|
||||||
'1': new ModVersionParserV1 options
|
'1': new ModVersionParserV1 options
|
||||||
'2': new ModVersionParserV2 options
|
'2': new ModVersionParserV2 options
|
||||||
@@ -34,13 +34,9 @@ module.exports = class ModVersionParser
|
|||||||
parser = @_parsers['2']
|
parser = @_parsers['2']
|
||||||
|
|
||||||
if not parser? then throw new Error "cannot parse version #{data.dataVersion} mod descriptions"
|
if not parser? then throw new Error "cannot parse version #{data.dataVersion} mod descriptions"
|
||||||
|
parser.parse data
|
||||||
|
|
||||||
oldLevel = logger.level
|
return @_model
|
||||||
logger.level = Logger.WARNING
|
|
||||||
result = parser.parse data
|
|
||||||
logger.level = oldLevel
|
|
||||||
|
|
||||||
return @_modVersion
|
|
||||||
|
|
||||||
unparse: (dataVersion=ModVersionParser.CURRENT_VERSION)->
|
unparse: (dataVersion=ModVersionParser.CURRENT_VERSION)->
|
||||||
if not modVersion? then throw new Error 'modVersion is required'
|
if not modVersion? then throw new Error 'modVersion is required'
|
||||||
|
|||||||
@@ -59,19 +59,19 @@ module.exports = class NameFinder
|
|||||||
names = []
|
names = []
|
||||||
nameMap = {}
|
nameMap = {}
|
||||||
|
|
||||||
for modVersion in @modPack.modVersions
|
@modPack.eachModVersion (modVersion)=>
|
||||||
continue unless modVersion.enabled or @includeDisabledMods
|
return unless modVersion.enabled or @includeDisabledMods
|
||||||
|
|
||||||
for slug, name of modVersion.names
|
modVersion.eachName (name, slug)=>
|
||||||
continue if nameMap[name]
|
return if nameMap[name]
|
||||||
|
|
||||||
item = modVersion.items[slug]
|
item = modVersion.findItem slug
|
||||||
if not @includeGatherable
|
if not @includeGatherable
|
||||||
continue unless item? and (not item.isGatherable)
|
return unless item? and (not item.isGatherable)
|
||||||
|
|
||||||
scanName = "#{modVersion.name} : #{name}"
|
scanName = "#{modVersion.name} : #{name}"
|
||||||
if nameHint?
|
if nameHint?
|
||||||
continue unless @_isMatch scanName.toLowerCase(), nameHint
|
return unless @_isMatch scanName.toLowerCase(), nameHint
|
||||||
|
|
||||||
nameMap[name] = name
|
nameMap[name] = name
|
||||||
names.push value:name, label:scanName, modVersion:modVersion
|
names.push value:name, label:scanName, modVersion:modVersion
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ Stack = require '../stack'
|
|||||||
module.exports = class ModVersionParserV1
|
module.exports = class ModVersionParserV1
|
||||||
|
|
||||||
constructor: (options={})->
|
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'
|
||||||
@_modVersion = options.modVersion
|
@_model = options.model
|
||||||
@_errorLocation = 'the header information'
|
@_errorLocation = 'the header information'
|
||||||
|
|
||||||
parse: (data)->
|
parse: (data)->
|
||||||
@@ -27,25 +27,48 @@ module.exports = class ModVersionParserV1
|
|||||||
|
|
||||||
# Private Methods ##############################################################################
|
# 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)->
|
_findOrCreateItem: (name)->
|
||||||
item = @_modVersion.findItemByName name
|
item = @_model.findItemByName name
|
||||||
if not item?
|
if not item?
|
||||||
item = new Item modVersion:@_modVersion, name:name
|
item = new Item name:name
|
||||||
@_modVersion.registerSlug item.slug, item.name
|
@_model.addItem item
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
# Parsing Methods ##############################################################################
|
||||||
|
|
||||||
_parseModVersion: (data)->
|
_parseModVersion: (data)->
|
||||||
if not data? then throw new Error 'mod description data is missing'
|
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.name? then throw new Error 'name is required'
|
||||||
if not data.version? then throw new Error 'version 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 not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
|
||||||
|
|
||||||
if data.name isnt @_modVersion.name
|
if data.name isnt @_model.name
|
||||||
throw new Error "the data is for #{data.name}, not #{@_modVersion.name} as expected"
|
throw new Error "the data is for #{data.name}, not #{@_model.name} as expected"
|
||||||
if data.version isnt @_modVersion.version
|
if data.version isnt @_model.version
|
||||||
throw new Error "the data is for version #{data.version}, not #{@_modVersion.version} as expected"
|
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
|
@_parseRawMaterials data.raw_materials
|
||||||
|
|
||||||
for index in [0...data.recipes.length]
|
for index in [0...data.recipes.length]
|
||||||
@@ -54,7 +77,7 @@ module.exports = class ModVersionParserV1
|
|||||||
recipe = @_parseRecipe recipeData
|
recipe = @_parseRecipe recipeData
|
||||||
recipe._originalIndex = index
|
recipe._originalIndex = index
|
||||||
|
|
||||||
return @_modVersion
|
return @_model
|
||||||
|
|
||||||
_parseRawMaterials: (data)->
|
_parseRawMaterials: (data)->
|
||||||
return unless data? and data.length > 0
|
return unless data? and data.length > 0
|
||||||
@@ -84,7 +107,7 @@ module.exports = class ModVersionParserV1
|
|||||||
output: @_parseStackList(data.output, field:'output', canBeEmpty:false)
|
output: @_parseStackList(data.output, field:'output', canBeEmpty:false)
|
||||||
input: @_parseStackList(data.input, field:'input', canBeEmpty:true)
|
input: @_parseStackList(data.input, field:'input', canBeEmpty:true)
|
||||||
tools: @_parseStackList(data.tools, field:'tools', 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
|
recipe = new Recipe attributes
|
||||||
return recipe
|
return recipe
|
||||||
@@ -102,9 +125,9 @@ module.exports = class ModVersionParserV1
|
|||||||
|
|
||||||
name = data[1]
|
name = data[1]
|
||||||
slug = _.slugify name
|
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={})->
|
_parseStackList: (data, options={})->
|
||||||
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
|
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
|
else if stackList.length is 1
|
||||||
stack = stackList[0]
|
stack = stackList[0]
|
||||||
if stack.quantity is 1
|
if stack.quantity is 1
|
||||||
result.push '"' + @_modVersion.findName(stack.itemSlug) + '"'
|
result.push '"' + @_model.findName(stack.slug) + '"'
|
||||||
else
|
else
|
||||||
result.push '[[' + stack.quantity + ', "' + @_modVersion.findName(stack.itemSlug) + '"]]'
|
result.push '[[' + stack.quantity + ', "' + @_model.findName(stack.slug) + '"]]'
|
||||||
else
|
else
|
||||||
result.push '['
|
result.push '['
|
||||||
|
|
||||||
@@ -202,17 +225,17 @@ module.exports = class ModVersionParserV1
|
|||||||
stacks.sort (a, b)->
|
stacks.sort (a, b)->
|
||||||
if a.quantity isnt b.quantity
|
if a.quantity isnt b.quantity
|
||||||
return if a.quantity > b.quantity then -1 else +1
|
return if a.quantity > b.quantity then -1 else +1
|
||||||
if a.itemSlug isnt b.itemSlug
|
if a.slug isnt b.slug
|
||||||
return if a.itemSlug < b.itemSlug then -1 else +1
|
return if a.slug < b.slug then -1 else +1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
firstItem = true
|
firstItem = true
|
||||||
for stack in stacks
|
for stack in stacks
|
||||||
result.push ', ' if not firstItem
|
result.push ', ' if not firstItem
|
||||||
if stack.quantity is 1
|
if stack.quantity is 1
|
||||||
result.push '"' + @_modVersion.findName(stack.itemSlug) + '"'
|
result.push '"' + @_model.findName(stack.slug) + '"'
|
||||||
else
|
else
|
||||||
result.push '[' + stack.quantity + ', "' + @_modVersion.findName(stack.itemSlug) + '"]'
|
result.push '[' + stack.quantity + ', "' + @_model.findName(stack.slug) + '"]'
|
||||||
firstItem = false
|
firstItem = false
|
||||||
|
|
||||||
result.push ']'
|
result.push ']'
|
||||||
|
|||||||
@@ -5,97 +5,38 @@ Copyright (c) 2014-2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
Item = require '../item'
|
CommandParserBase = require '../command_parser_base'
|
||||||
ModVersion = require '../mod_version'
|
Item = require '../item'
|
||||||
Recipe = require '../recipe'
|
ModVersion = require '../mod_version'
|
||||||
Stack = require '../stack'
|
Recipe = require '../recipe'
|
||||||
StringBuilder = require '../string_builder'
|
Stack = require '../stack'
|
||||||
|
StringBuilder = require '../string_builder'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
module.exports = class ModVersionParserV2
|
module.exports = class ModVersionParserV2 extends CommandParserBase
|
||||||
|
|
||||||
constructor: (options={})->
|
|
||||||
if not options.modVersion? then throw new Error 'options.modVersion is required'
|
|
||||||
options.showAllErrors ?= false
|
|
||||||
|
|
||||||
@_modVersion = options.modVersion
|
|
||||||
@_showAllErrors = options.showAllErrors
|
|
||||||
|
|
||||||
# Class Methods ################################################################################
|
# Class Methods ################################################################################
|
||||||
|
|
||||||
@COMMAND = /\ *([^:]*):?(.*)/
|
|
||||||
|
|
||||||
@COMMENT = /([^\\]?)#.*/
|
|
||||||
|
|
||||||
@INTEGER = /[0-9]+/
|
@INTEGER = /[0-9]+/
|
||||||
|
|
||||||
@PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/
|
@PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/
|
||||||
|
|
||||||
@STACK = /^([0-9]+) +(.*)$/
|
@STACK = /^([0-9]+) +(.*)$/
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
# CommandParserBase Overrides ##################################################################
|
||||||
|
|
||||||
parse: (text)->
|
_buildModel: (rawData, model)->
|
||||||
@_modVersionData = {}
|
@_buildModVersion rawData, model
|
||||||
@_lineNumber = 1
|
|
||||||
|
|
||||||
lines = text.split '\n'
|
_unparseModel: (builder, model)->
|
||||||
for i in [0...lines.length]
|
@_unparseModVersion builder, model
|
||||||
@_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
|
|
||||||
|
|
||||||
# Command Methods ##############################################################################
|
# Command Methods ##############################################################################
|
||||||
|
|
||||||
_command_description: (descriptionParts...)->
|
_command_description: (descriptionParts...)->
|
||||||
if @_modVersionData.description? then throw new Error 'duplicate declaration of "description"'
|
if @_rawData.description? then throw new Error 'duplicate declaration of "description"'
|
||||||
@_modVersionData.description = descriptionParts.join ', '
|
@_rawData.description = descriptionParts.join ', '
|
||||||
|
|
||||||
_command_extras: (extraTerms...)->
|
_command_extras: (extraTerms...)->
|
||||||
if not @_recipeData? then throw new Error 'cannot declare "extras" before "recipe"'
|
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'
|
if not name.length > 0 then throw new Error 'the item name cannot be empty'
|
||||||
|
|
||||||
@_itemData = name:name, line:@_lineNumber
|
@_itemData = name:name, line:@_lineNumber
|
||||||
@_modVersionData.items ?= []
|
@_rawData.items ?= []
|
||||||
@_modVersionData.items.push @_itemData
|
@_rawData.items.push @_itemData
|
||||||
|
|
||||||
@_recipeData = null
|
@_recipeData = null
|
||||||
|
|
||||||
_command_name: (name='')->
|
_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'
|
if not name.length > 0 then throw new Error 'the mod name cannot be empty'
|
||||||
|
|
||||||
@_modVersionData.name = name
|
@_rawData.name = name
|
||||||
|
|
||||||
_command_input: (inputNames...)->
|
_command_input: (inputNames...)->
|
||||||
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
|
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
|
||||||
@@ -175,10 +116,10 @@ module.exports = class ModVersionParserV2
|
|||||||
@_recipeData.tools.push name
|
@_recipeData.tools.push name
|
||||||
|
|
||||||
_command_version: (version='')->
|
_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'
|
if version.length is 0 then throw new Error 'version cannot be empty'
|
||||||
|
|
||||||
@_modVersionData.version = version
|
@_rawData.version = version
|
||||||
|
|
||||||
# Object Creation Methods ######################################################################
|
# Object Creation Methods ######################################################################
|
||||||
|
|
||||||
@@ -208,7 +149,8 @@ module.exports = class ModVersionParserV2
|
|||||||
itemData.gatherable ?= false
|
itemData.gatherable ?= false
|
||||||
itemData.recipes ?= []
|
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
|
for recipeData in itemData.recipes
|
||||||
@_handleErrors @_buildRecipe, modVersion, item, recipeData
|
@_handleErrors @_buildRecipe, modVersion, item, recipeData
|
||||||
@@ -228,7 +170,7 @@ module.exports = class ModVersionParserV2
|
|||||||
for name in recipeData.input
|
for name in recipeData.input
|
||||||
slug = _.slugify name
|
slug = _.slugify name
|
||||||
modVersion.registerSlug slug, 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
|
for c in recipeData.pattern
|
||||||
continue if c is '.'
|
continue if c is '.'
|
||||||
@@ -240,42 +182,43 @@ module.exports = class ModVersionParserV2
|
|||||||
for i in [0...inputStacks.length]
|
for i in [0...inputStacks.length]
|
||||||
stack = inputStacks[i]
|
stack = inputStacks[i]
|
||||||
if stack.quantity is 0
|
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"
|
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
|
for extraData in recipeData.extras
|
||||||
slug = _.slugify extraData.name
|
slug = _.slugify extraData.name
|
||||||
modVersion.registerSlug slug, 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 = []
|
toolStacks = []
|
||||||
for name in recipeData.tools
|
for name in recipeData.tools
|
||||||
slug = _.slugify name
|
slug = _.slugify name
|
||||||
modVersion.registerSlug slug, name
|
modVersion.registerSlug slug, name
|
||||||
toolStacks.push new Stack itemSlug:slug, quantity:1
|
toolStacks.push new Stack slug:slug, quantity:1
|
||||||
|
|
||||||
attributes =
|
attributes =
|
||||||
input: inputStacks
|
input: inputStacks
|
||||||
item: item
|
name: item.name
|
||||||
pattern: recipeData.pattern
|
pattern: recipeData.pattern
|
||||||
output: outputStacks
|
output: outputStacks
|
||||||
tools: toolStacks
|
tools: toolStacks
|
||||||
|
|
||||||
recipe = new Recipe attributes
|
recipe = new Recipe attributes
|
||||||
|
item.addRecipe recipe
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
# Un-parsing Methods ###########################################################################
|
# Un-parsing Methods ###########################################################################
|
||||||
|
|
||||||
_unparseModVersion: (builder)->
|
_unparseModVersion: (builder, modVersion)->
|
||||||
itemList = _.values @modVersion.items
|
itemList = _.values modVersion.items
|
||||||
itemList.sort (a, b)-> a.compareTo b
|
itemList.sort (a, b)-> a.compareTo b
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.line 'schema: ', 2
|
.line 'schema: ', 2
|
||||||
.line 'name: ', @modVersion.name
|
.line 'name: ', modVersion.name
|
||||||
.line 'version: ', @modVersion.version
|
.line 'version: ', modVersion.version
|
||||||
.onlyIf @modVersion.description?, => builder.line 'description: ', @modVersion.description
|
.onlyIf modVersion.description?, => builder.line 'description: ', modVersion.description
|
||||||
.line()
|
.line()
|
||||||
.onlyIf itemList.length > 0, =>
|
.onlyIf itemList.length > 0, =>
|
||||||
builder.loop itemList, delimiter:'\n', onEach:(b, i)=> @_unparseItem(b, i)
|
builder.loop itemList, delimiter:'\n', onEach:(b, i)=> @_unparseItem(b, i)
|
||||||
@@ -291,13 +234,13 @@ module.exports = class ModVersionParserV2
|
|||||||
.outdent()
|
.outdent()
|
||||||
|
|
||||||
_unparseRecipe: (builder, recipe)->
|
_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()
|
inputNames.sort()
|
||||||
|
|
||||||
patternMap = {'.', '.'}
|
patternMap = {'.', '.'}
|
||||||
for i in [0...recipe.input.length]
|
for i in [0...recipe.input.length]
|
||||||
stack = recipe.input[i]
|
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
|
pattern = recipe.pattern or recipe.defaultPattern
|
||||||
newPattern = []
|
newPattern = []
|
||||||
@@ -333,9 +276,9 @@ module.exports = class ModVersionParserV2
|
|||||||
|
|
||||||
_unparseStackList: (builder, stackList)->
|
_unparseStackList: (builder, stackList)->
|
||||||
if stackList.length is 1 and stackList[0].quantity is 1
|
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
|
else
|
||||||
builder.loop stackList, onEach:(b, stack)=>
|
builder.loop stackList, onEach:(b, stack)=>
|
||||||
builder
|
builder
|
||||||
.onlyIf stack.quantity > 1, => builder.push stack.quantity, ' '
|
.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
|
module.exports = class Recipe extends BaseModel
|
||||||
|
|
||||||
constructor: (attributes={}, options={})->
|
constructor: (attributes={}, options={})->
|
||||||
if not attributes.input? then throw new Error 'attributes.input is required'
|
if attributes.item?
|
||||||
if not attributes.item? then throw new Error 'attributes.item is required'
|
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.pattern = @_parsePattern attributes.pattern
|
||||||
|
attributes.slug ?= attributes.output[0].slug
|
||||||
attributes.tools ?= []
|
attributes.tools ?= []
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|
||||||
@item.addRecipe this
|
|
||||||
|
|
||||||
Object.defineProperties this,
|
|
||||||
'defaultPattern': { get: -> @_computeDefaultPattern() }
|
|
||||||
'name': { get: -> @item.name }
|
|
||||||
'slug': { get: -> @item.slug }
|
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
getItemSlugAt: (index)->
|
getItemSlugAt: (patternSlot)->
|
||||||
pattern = if @pattern? then @pattern else @_computeDefaultPattern()
|
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
|
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?
|
||||||
return null unless patternDigit.match /[0-9]/
|
return null unless patternDigit.match /[0-9]/
|
||||||
|
|
||||||
stack = @input[parseInt(patternDigit)]
|
stack = @input[parseInt(patternDigit)]
|
||||||
return null unless stack?
|
return null unless stack?
|
||||||
|
|
||||||
return stack.itemSlug
|
return stack.slug
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# Object Overrides #############################################################################
|
# Object Overrides #############################################################################
|
||||||
|
|
||||||
@@ -109,24 +95,3 @@ module.exports = class Recipe extends BaseModel
|
|||||||
pattern = array.join ''
|
pattern = array.join ''
|
||||||
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
|
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
|
||||||
return pattern
|
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.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
|
BaseModel = require './base_model'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
module.exports = class Stack
|
module.exports = class Stack extends BaseModel
|
||||||
|
|
||||||
constructor: (attributes={})->
|
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
|
attributes.quantity ?= 1
|
||||||
|
super attributes
|
||||||
@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
|
|
||||||
|
|
||||||
# Object Overrides #############################################################################
|
# Object Overrides #############################################################################
|
||||||
|
|
||||||
toString: ->
|
toString: ->
|
||||||
return "#{@quantity} #{@itemSlug}"
|
return "#{@quantity} #{@slug}"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
###
|
||||||
|
Crafting Table - polyfill.coffee
|
||||||
|
|
||||||
|
Copyright (c) 2015 by Redwood Labs
|
||||||
|
All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
if not Array.prototype.clear?
|
||||||
|
Object.defineProperty Array.prototype, 'clear', value:->
|
||||||
|
@splice 0, @length
|
||||||
|
return this
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
.output
|
.output
|
||||||
a: img
|
a: img
|
||||||
p.quantity
|
p.quantity
|
||||||
p.quantity
|
|
||||||
p.multiplier
|
p.multiplier
|
||||||
.next
|
.next
|
||||||
.problem
|
.problem
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ Copyright (c) 2014-2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
ModPack = require '../src/scripts/models/mod_pack'
|
|
||||||
CraftingPlan = require '../src/scripts/models/crafting_plan'
|
CraftingPlan = require '../src/scripts/models/crafting_plan'
|
||||||
|
ModVersion = require '../src/scripts/models/mod_version'
|
||||||
|
ModPack = require '../src/scripts/models/mod_pack'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -17,8 +18,8 @@ modPack = plan = null
|
|||||||
describe 'CraftingPlan', ->
|
describe 'CraftingPlan', ->
|
||||||
|
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
modPack = new ModPack
|
modVersion = new ModVersion name:'Minecraft', version:'1.7.10'
|
||||||
modPack.loadModVersionData """
|
modVersion.parse """
|
||||||
schema:2; name:Minecraft; version:1.7.10
|
schema:2; name:Minecraft; version:1.7.10
|
||||||
|
|
||||||
item:Oak Plank; recipe:; input:Oak Log; pattern:... .0. ...; quantity:4
|
item:Oak Plank; recipe:; input:Oak Log; pattern:... .0. ...; quantity:4
|
||||||
@@ -28,6 +29,9 @@ describe 'CraftingPlan', ->
|
|||||||
item:Iron Ingot; recipe:; input:Iron Ore, furnace fuel; pattern:.0. ... .1.; tools:Furnace
|
item:Iron Ingot; recipe:; input:Iron Ore, furnace fuel; pattern:.0. ... .1.; tools:Furnace
|
||||||
item:Iron Sword; recipe:; input:Iron Ingot, Stick; pattern:.0. .0. .1.; tools:Crafting Table
|
item:Iron Sword; recipe:; input:Iron Ingot, Stick; pattern:.0. .0. .1.; tools:Crafting Table
|
||||||
"""
|
"""
|
||||||
|
modPack = new ModPack
|
||||||
|
modPack.addModVersion modVersion
|
||||||
|
|
||||||
plan = new CraftingPlan modPack:modPack, includingTools:false
|
plan = new CraftingPlan modPack:modPack, includingTools:false
|
||||||
|
|
||||||
describe 'craft', ->
|
describe 'craft', ->
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ inventory = null
|
|||||||
describe 'Inventory', ->
|
describe 'Inventory', ->
|
||||||
|
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
inventory = new Inventory
|
inventory = new Inventory {}, silent:false
|
||||||
inventory.add 'wool', 4
|
inventory.add 'wool', 4
|
||||||
inventory.add 'string', 20
|
inventory.add 'string', 20
|
||||||
inventory.add 'boat'
|
inventory.add 'boat'
|
||||||
@@ -30,7 +30,7 @@ describe 'Inventory', ->
|
|||||||
inventory.add 'iron_ingot', 4
|
inventory.add 'iron_ingot', 4
|
||||||
stack = inventory._stacks['iron_ingot']
|
stack = inventory._stacks['iron_ingot']
|
||||||
stack.constructor.name.should.equal 'Stack'
|
stack.constructor.name.should.equal 'Stack'
|
||||||
stack.itemSlug.should.equal 'iron_ingot'
|
stack.slug.should.equal 'iron_ingot'
|
||||||
stack.quantity.should.equal 4
|
stack.quantity.should.equal 4
|
||||||
|
|
||||||
it 'can augment quantity of existing items', ->
|
it 'can augment quantity of existing items', ->
|
||||||
@@ -80,7 +80,7 @@ describe 'Inventory', ->
|
|||||||
|
|
||||||
it 'works when items have only been added', ->
|
it 'works when items have only been added', ->
|
||||||
result = []
|
result = []
|
||||||
inventory.each (stack)-> result.push stack.itemSlug
|
inventory.each (stack)-> result.push stack.slug
|
||||||
result.should.eql ['boat', 'string', 'wool']
|
result.should.eql ['boat', 'string', 'wool']
|
||||||
|
|
||||||
it 'works when items have been augmented', ->
|
it 'works when items have been augmented', ->
|
||||||
@@ -89,7 +89,7 @@ describe 'Inventory', ->
|
|||||||
inventory.add 'wool', 2
|
inventory.add 'wool', 2
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
inventory.each (stack)-> result.push stack.itemSlug
|
inventory.each (stack)-> result.push stack.slug
|
||||||
result.should.eql ['boat', 'iron_ingot', 'string', 'wool']
|
result.should.eql ['boat', 'iron_ingot', 'string', 'wool']
|
||||||
|
|
||||||
describe 'hasAtLeast', ->
|
describe 'hasAtLeast', ->
|
||||||
@@ -116,7 +116,7 @@ describe 'Inventory', ->
|
|||||||
|
|
||||||
it 'completely removes the last item', ->
|
it 'completely removes the last item', ->
|
||||||
stack = inventory.pop()
|
stack = inventory.pop()
|
||||||
stack.itemSlug.should.equal 'wool'
|
stack.slug.should.equal 'wool'
|
||||||
stack.quantity.should.equal 4
|
stack.quantity.should.equal 4
|
||||||
inventory.toList().should.eql ['boat', [20, 'string']]
|
inventory.toList().should.eql ['boat', [20, 'string']]
|
||||||
|
|
||||||
|
|||||||
+14
-52
@@ -19,43 +19,22 @@ describe 'ModPack', ->
|
|||||||
|
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
minecraft = new ModVersion name:'Minecraft', version:'1.7.10', enabled:true
|
minecraft = new ModVersion name:'Minecraft', version:'1.7.10', enabled:true
|
||||||
new Item modVersion:minecraft, name:'Wool'
|
minecraft.addItem new Item name:'Wool'
|
||||||
new Item modVersion:minecraft, name:'Bed', recipes:['']
|
minecraft.addItem new Item name:'Bed', recipes:['']
|
||||||
minecraft.registerSlug 'iron_chestplate', 'Iron Chestplate'
|
minecraft.registerSlug 'iron_chestplate', 'Iron Chestplate'
|
||||||
|
|
||||||
buildcraft = new ModVersion name:'Buildcraft', version:'4.0', enabled:false
|
buildcraft = new ModVersion name:'Buildcraft', version:'6.2.6', enabled:false
|
||||||
new Item modVersion:buildcraft, name:'Stone Gear', recipes:['']
|
buildcraft.addItem new Item name:'Stone Gear', recipes:['']
|
||||||
new Item modVersion:buildcraft, name:'Bed', recipes:['']
|
buildcraft.addItem new Item name:'Bed', recipes:['']
|
||||||
|
|
||||||
industrialCraft = new ModVersion name:'Industrial Craft', version:'2.0', enabled:false
|
industrialCraft = new ModVersion name:'Industrial Craft', version:'2.0', enabled:false
|
||||||
new Item modVersion:industrialCraft, name:'Resin'
|
industrialCraft.addItem new Item name:'Resin'
|
||||||
new Item modVersion:industrialCraft, name:'Rubber', recipes:['']
|
industrialCraft.addItem new Item name:'Rubber'
|
||||||
|
|
||||||
modPack = new ModPack modVersions:[minecraft, buildcraft, industrialCraft]
|
modPack = new ModPack
|
||||||
|
modPack.addModVersion minecraft
|
||||||
describe 'enableModsForItem', ->
|
modPack.addModVersion buildcraft
|
||||||
|
modPack.addModVersion industrialCraft
|
||||||
it 'it ignores already-enabled mod versions', ->
|
|
||||||
buildcraft.enabled = true
|
|
||||||
modPack.enableModsForItem 'Stone Gear'
|
|
||||||
|
|
||||||
minecraft.enabled.should.be.true
|
|
||||||
buildcraft.enabled.should.be.true
|
|
||||||
industrialCraft.enabled.should.be.false
|
|
||||||
|
|
||||||
it 'it ignores mod versions not containing the item', ->
|
|
||||||
modPack.enableModsForItem 'Stone Gear'
|
|
||||||
|
|
||||||
minecraft.enabled.should.be.true
|
|
||||||
buildcraft.enabled.should.be.true
|
|
||||||
industrialCraft.enabled.should.be.false
|
|
||||||
|
|
||||||
it 'enables disabled mod versions with the item', ->
|
|
||||||
modPack.enableModsForItem 'Rubber'
|
|
||||||
|
|
||||||
minecraft.enabled.should.be.true
|
|
||||||
buildcraft.enabled.should.be.false
|
|
||||||
industrialCraft.enabled.should.be.true
|
|
||||||
|
|
||||||
describe 'findItemByName', ->
|
describe 'findItemByName', ->
|
||||||
|
|
||||||
@@ -75,7 +54,7 @@ describe 'ModPack', ->
|
|||||||
|
|
||||||
it 'returns all data for a regular Minecraft item', ->
|
it 'returns all data for a regular Minecraft item', ->
|
||||||
display = modPack.findItemDisplay 'bed'
|
display = modPack.findItemDisplay 'bed'
|
||||||
display.iconUrl.should.equal '/data/minecraft/images/bed.png'
|
display.iconUrl.should.equal '/data/minecraft/1.7.10/images/bed.png'
|
||||||
display.itemUrl.should.equal '/item/Bed'
|
display.itemUrl.should.equal '/item/Bed'
|
||||||
display.itemName.should.equal 'Bed'
|
display.itemName.should.equal 'Bed'
|
||||||
display.modSlug.should.equal 'minecraft'
|
display.modSlug.should.equal 'minecraft'
|
||||||
@@ -83,7 +62,7 @@ describe 'ModPack', ->
|
|||||||
it 'returns all data for an item in an enabled mod', ->
|
it 'returns all data for an item in an enabled mod', ->
|
||||||
buildcraft.enabled = true
|
buildcraft.enabled = true
|
||||||
display = modPack.findItemDisplay 'stone_gear'
|
display = modPack.findItemDisplay 'stone_gear'
|
||||||
display.iconUrl.should.equal '/data/buildcraft/images/stone_gear.png'
|
display.iconUrl.should.equal '/data/buildcraft/6.2.6/images/stone_gear.png'
|
||||||
display.itemUrl.should.equal '/item/Stone%20Gear'
|
display.itemUrl.should.equal '/item/Stone%20Gear'
|
||||||
display.itemName.should.equal 'Stone Gear'
|
display.itemName.should.equal 'Stone Gear'
|
||||||
display.modSlug.should.equal 'buildcraft'
|
display.modSlug.should.equal 'buildcraft'
|
||||||
@@ -94,24 +73,7 @@ describe 'ModPack', ->
|
|||||||
|
|
||||||
it 'assumes an unfound item is from Minecraft', ->
|
it 'assumes an unfound item is from Minecraft', ->
|
||||||
display = modPack.findItemDisplay 'iron_chestplate'
|
display = modPack.findItemDisplay 'iron_chestplate'
|
||||||
display.iconUrl.should.equal '/data/minecraft/images/iron_chestplate.png'
|
display.iconUrl.should.equal '/data/minecraft/1.7.10/images/iron_chestplate.png'
|
||||||
display.itemUrl.should.equal '/item/Iron%20Chestplate'
|
display.itemUrl.should.equal '/item/Iron%20Chestplate'
|
||||||
display.itemName.should.equal 'Iron Chestplate'
|
display.itemName.should.equal 'Iron Chestplate'
|
||||||
display.modSlug.should.equal 'minecraft'
|
display.modSlug.should.equal 'minecraft'
|
||||||
|
|
||||||
describe 'hasRecipe', ->
|
|
||||||
|
|
||||||
it 'returns true when the item is present and has recipes', ->
|
|
||||||
modPack.hasRecipe('Bed').should.be.true
|
|
||||||
|
|
||||||
it 'returns false when the item is not present', ->
|
|
||||||
modPack.hasRecipe('Iron Sword').should.be.false
|
|
||||||
|
|
||||||
it 'returns false when the item does not have recipes', ->
|
|
||||||
modPack.hasRecipe('Wool').should.be.false
|
|
||||||
|
|
||||||
it 'ignores disabled mod versions', ->
|
|
||||||
modPack.hasRecipe('Stone Gear').should.be.false
|
|
||||||
|
|
||||||
it 'includes disabled mod versions when include disabled is requested', ->
|
|
||||||
modPack.hasRecipe('Stone Gear', includeDisabled:true).should.be.true
|
|
||||||
|
|||||||
@@ -21,25 +21,29 @@ describe 'ModVersion', ->
|
|||||||
describe 'constructor', ->
|
describe 'constructor', ->
|
||||||
|
|
||||||
it 'requires a mod name', ->
|
it 'requires a mod name', ->
|
||||||
expect(-> new ModVersion version:'0.0').to.throw Error, 'name cannot be empty'
|
expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.name is required'
|
||||||
|
|
||||||
it 'requires a mod version', ->
|
it 'requires a mod version', ->
|
||||||
expect(-> new ModVersion name:'Test').to.throw Error, 'version cannot be empty'
|
expect(-> new ModVersion name:'Test').to.throw Error, 'attributes.version is required'
|
||||||
|
|
||||||
it 'supplies default values', ->
|
it 'supplies default values', ->
|
||||||
modVersion.description.should.equal ''
|
modVersion.description.should.equal ''
|
||||||
modVersion.items.should.eql {}
|
|
||||||
modVersion.enabled.should.be.true
|
modVersion.enabled.should.be.true
|
||||||
|
modVersion.slug.should.equal 'test'
|
||||||
|
|
||||||
describe 'addItem', ->
|
describe 'addItem', ->
|
||||||
|
|
||||||
it 'refuses to add duplicates', ->
|
it 'refuses to add duplicates', ->
|
||||||
new Item modVersion:modVersion, name:'Wool'
|
modVersion.addItem new Item name:'Wool'
|
||||||
expect(-> new Item modVersion:modVersion, name:'Wool').to.throw Error, 'duplicate item for Wool'
|
expect(-> modVersion.addItem new Item name:'Wool').to.throw Error, 'duplicate item for Wool'
|
||||||
|
|
||||||
it 'adds an item indexes by its slug', ->
|
it 'adds an item indexed by its slug', ->
|
||||||
new Item modVersion:modVersion, name:'Wool'
|
modVersion.addItem new Item name:'Wool'
|
||||||
modVersion.items.wool.name.should.equal '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 'compareTo', ->
|
describe 'compareTo', ->
|
||||||
|
|
||||||
@@ -56,19 +60,5 @@ describe 'ModVersion', ->
|
|||||||
describe 'findItemByName', ->
|
describe 'findItemByName', ->
|
||||||
|
|
||||||
it 'locates items by slugified name', ->
|
it 'locates items by slugified name', ->
|
||||||
new Item modVersion:modVersion, name:'Crafting Table'
|
modVersion.addItem new Item name:'Crafting Table'
|
||||||
modVersion.findItemByName('Crafting Table').slug.should.equal 'crafting_table'
|
modVersion.findItemByName('Crafting Table').slug.should.equal 'crafting_table'
|
||||||
|
|
||||||
describe 'hasRecipe', ->
|
|
||||||
|
|
||||||
it 'returns false for an unknown item', ->
|
|
||||||
new Item modVersion:modVersion, name:'Oak Wood Planks', recipes:['foo']
|
|
||||||
modVersion.hasRecipe('Pineapple Upside-Down Cake').should.be.false
|
|
||||||
|
|
||||||
it 'returns false for a un-craftable item', ->
|
|
||||||
new Item modVersion:modVersion, name:'Wool'
|
|
||||||
modVersion.hasRecipe('Wool').should.be.false
|
|
||||||
|
|
||||||
it 'returns true for a craftable item', ->
|
|
||||||
new Item modVersion:modVersion, name:'Oak Wood Planks', recipes:['foo']
|
|
||||||
modVersion.hasRecipe('Oak Wood Planks').should.be.true
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe "ModVersionParserV1", ->
|
|||||||
|
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
modVersion = new ModVersion name:'Test', version:'0.0'
|
modVersion = new ModVersion name:'Test', version:'0.0'
|
||||||
parser = new ModVersionParserV1 modVersion:modVersion
|
parser = new ModVersionParserV1 model:modVersion
|
||||||
|
|
||||||
describe '_parseModVersion', ->
|
describe '_parseModVersion', ->
|
||||||
|
|
||||||
@@ -51,8 +51,7 @@ describe "ModVersionParserV1", ->
|
|||||||
modVersion = parser._parseModVersion data
|
modVersion = parser._parseModVersion data
|
||||||
modVersion.name.should.equal 'Test'
|
modVersion.name.should.equal 'Test'
|
||||||
modVersion.version.should.equal '0.0'
|
modVersion.version.should.equal '0.0'
|
||||||
slugs = (slug for slug, item of modVersion.items).sort()
|
modVersion._slugs.should.eql ['bed', 'crafting_table', 'planks', 'sugar', 'sugar_cane', 'wool']
|
||||||
slugs.should.eql ['bed', 'sugar']
|
|
||||||
|
|
||||||
describe '_parseRawMaterials', ->
|
describe '_parseRawMaterials', ->
|
||||||
|
|
||||||
@@ -66,17 +65,17 @@ describe "ModVersionParserV1", ->
|
|||||||
|
|
||||||
it 'adds items marked as gatherable', ->
|
it 'adds items marked as gatherable', ->
|
||||||
parser._parseRawMaterials ['Wool']
|
parser._parseRawMaterials ['Wool']
|
||||||
modVersion.items['wool'].isGatherable.should.be.true
|
modVersion._items['wool'].isGatherable.should.be.true
|
||||||
|
|
||||||
it 'marks an existing item as gatherable', ->
|
it 'marks an existing item as gatherable', ->
|
||||||
item = new Item modVersion:modVersion, name:'Wool'
|
modVersion.addItem new Item name:'Wool'
|
||||||
modVersion.items['wool'].isGatherable.should.be.false
|
modVersion._items['wool'].isGatherable.should.be.false
|
||||||
parser._parseRawMaterials ['Wool']
|
parser._parseRawMaterials ['Wool']
|
||||||
modVersion.items['wool'].isGatherable.should.be.true
|
modVersion._items['wool'].isGatherable.should.be.true
|
||||||
|
|
||||||
it 'registers the names of the items', ->
|
it 'registers the names of the items', ->
|
||||||
parser._parseRawMaterials ['Wool']
|
parser._parseRawMaterials ['Wool']
|
||||||
modVersion.names['wool'].should.equal 'Wool'
|
modVersion._names['wool'].should.equal 'Wool'
|
||||||
|
|
||||||
describe '_parseRecipe', ->
|
describe '_parseRecipe', ->
|
||||||
|
|
||||||
@@ -95,15 +94,15 @@ describe "ModVersionParserV1", ->
|
|||||||
input: [[3, 'planks'], [3, 'wool']]
|
input: [[3, 'planks'], [3, 'wool']]
|
||||||
tools: 'crafting table'
|
tools: 'crafting table'
|
||||||
recipe = parser._parseRecipe data
|
recipe = parser._parseRecipe data
|
||||||
(stack.itemSlug for stack in recipe.output).should.eql ['bed']
|
(stack.slug for stack in recipe.output).should.eql ['bed']
|
||||||
(stack.itemSlug for stack in recipe.input).sort().should.eql ['planks', 'wool']
|
(stack.slug for stack in recipe.input).sort().should.eql ['planks', 'wool']
|
||||||
(stack.itemSlug for stack in recipe.tools).should.eql ['crafting_table']
|
(stack.slug for stack in recipe.tools).should.eql ['crafting_table']
|
||||||
|
|
||||||
it 'can parse a recipe without tools', ->
|
it 'can parse a recipe without tools', ->
|
||||||
recipe = parser._parseRecipe {output:'sugar', input:'sugar cane'}
|
recipe = parser._parseRecipe {output:'sugar', input:'sugar cane'}
|
||||||
(stack.itemSlug for stack in recipe.output).should.eql ['sugar']
|
(stack.slug for stack in recipe.output).should.eql ['sugar']
|
||||||
(stack.itemSlug for stack in recipe.input).sort().should.eql ['sugar_cane']
|
(stack.slug for stack in recipe.input).sort().should.eql ['sugar_cane']
|
||||||
(stack.itemSlug for stack in recipe.tools).should.eql []
|
(stack.slug for stack in recipe.tools).should.eql []
|
||||||
|
|
||||||
it 'registers all names', ->
|
it 'registers all names', ->
|
||||||
data =
|
data =
|
||||||
@@ -111,7 +110,7 @@ describe "ModVersionParserV1", ->
|
|||||||
input: [[3, 'Oak Wood Planks'], [3, 'Wool']]
|
input: [[3, 'Oak Wood Planks'], [3, 'Wool']]
|
||||||
tools: 'Crafting Table'
|
tools: 'Crafting Table'
|
||||||
parser._parseRecipe data
|
parser._parseRecipe data
|
||||||
_.keys(modVersion.names).sort().should.eql ['bed', 'crafting_table', 'oak_wood_planks', 'wool']
|
modVersion._slugs.should.eql ['bed', 'crafting_table', 'oak_wood_planks', 'wool']
|
||||||
|
|
||||||
describe '_parseStack', ->
|
describe '_parseStack', ->
|
||||||
|
|
||||||
@@ -123,11 +122,11 @@ describe "ModVersionParserV1", ->
|
|||||||
|
|
||||||
it 'can fill in a missing number', ->
|
it 'can fill in a missing number', ->
|
||||||
stack = parser._parseStack 'boat'
|
stack = parser._parseStack 'boat'
|
||||||
stack.itemSlug.should.equal 'boat'
|
stack.slug.should.equal 'boat'
|
||||||
stack.quantity.should.equal 1
|
stack.quantity.should.equal 1
|
||||||
|
|
||||||
stack2 = parser._parseStack ['boat']
|
stack2 = parser._parseStack ['boat']
|
||||||
stack2.itemSlug.should.equal 'boat'
|
stack2.slug.should.equal 'boat'
|
||||||
stack2.quantity.should.equal 1
|
stack2.quantity.should.equal 1
|
||||||
|
|
||||||
it 'requires the data to start with a number', ->
|
it 'requires the data to start with a number', ->
|
||||||
@@ -144,7 +143,7 @@ describe "ModVersionParserV1", ->
|
|||||||
|
|
||||||
it 'can promote a single item to a list', ->
|
it 'can promote a single item to a list', ->
|
||||||
list = parser._parseStackList 'boat'
|
list = parser._parseStackList 'boat'
|
||||||
(stack.itemSlug for stack in list).should.eql ['boat']
|
(stack.slug for stack in list).should.eql ['boat']
|
||||||
|
|
||||||
it 'can require a list to be non-empty', ->
|
it 'can require a list to be non-empty', ->
|
||||||
parser._errorLocation = 'boat'
|
parser._errorLocation = 'boat'
|
||||||
@@ -157,4 +156,4 @@ describe "ModVersionParserV1", ->
|
|||||||
|
|
||||||
it 'can parse a non-empty list', ->
|
it 'can parse a non-empty list', ->
|
||||||
list = parser._parseStackList [[3, 'plank'], [3, 'wool']]
|
list = parser._parseStackList [[3, 'plank'], [3, 'wool']]
|
||||||
(stack.itemSlug for stack in list).sort().should.eql ['plank', 'wool']
|
(stack.slug for stack in list).sort().should.eql ['plank', 'wool']
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
modVersion = new ModVersion name:'Test', version:'0.0'
|
modVersion = new ModVersion name:'Test', version:'0.0'
|
||||||
parser = new ModVersionParserV2 modVersion:modVersion
|
parser = new ModVersionParserV2 model:modVersion
|
||||||
|
|
||||||
describe 'Item', ->
|
describe 'Item', ->
|
||||||
|
|
||||||
@@ -29,15 +29,15 @@ describe 'ModVersionParserV2', ->
|
|||||||
recipe:; input:Alpha; pattern:... .0. ...;
|
recipe:; input:Alpha; pattern:... .0. ...;
|
||||||
recipe:; input:Bravo; pattern:... 0.0 ...;"
|
recipe:; input:Bravo; pattern:... 0.0 ...;"
|
||||||
modVersion = parser.parse baseText + recipes
|
modVersion = parser.parse baseText + recipes
|
||||||
recipes = modVersion.items.charlie.recipes
|
recipes = modVersion._items.charlie._recipes
|
||||||
recipes[0].input[0].itemSlug.should.equal 'alpha'
|
recipes[0].input[0].slug.should.equal 'alpha'
|
||||||
recipes[1].input[0].itemSlug.should.equal 'bravo'
|
recipes[1].input[0].slug.should.equal 'bravo'
|
||||||
|
|
||||||
describe 'name', ->
|
describe 'name', ->
|
||||||
|
|
||||||
it 'adds the name when present', ->
|
it 'adds the name when present', ->
|
||||||
modVersion = parser.parse baseText + 'item: Charlie'
|
modVersion = parser.parse baseText + 'item: Charlie'
|
||||||
modVersion.items.charlie.name.should.equal 'Charlie'
|
modVersion._items.charlie.name.should.equal 'Charlie'
|
||||||
|
|
||||||
it 'requires a non-empty name', ->
|
it 'requires a non-empty name', ->
|
||||||
func = -> parser.parse baseText + 'item: \n'
|
func = -> parser.parse baseText + 'item: \n'
|
||||||
@@ -47,7 +47,7 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'adds "gatherable" when present', ->
|
it 'adds "gatherable" when present', ->
|
||||||
modVersion = parser.parse baseText + 'item: Alpha Bravo; gatherable: yes'
|
modVersion = parser.parse baseText + 'item: Alpha Bravo; gatherable: yes'
|
||||||
modVersion.items.alpha_bravo.isGatherable.should.be.true
|
modVersion._items.alpha_bravo.isGatherable.should.be.true
|
||||||
|
|
||||||
it 'does not allow a duplicate "gatherable" declaration', ->
|
it 'does not allow a duplicate "gatherable" declaration', ->
|
||||||
func = -> parser.parse baseText + 'item: Alpha Bravo; gatherable: yes; gatherable: yes'
|
func = -> parser.parse baseText + 'item: Alpha Bravo; gatherable: yes; gatherable: yes'
|
||||||
@@ -67,7 +67,7 @@ describe 'ModVersionParserV2', ->
|
|||||||
modVersion = parser.parse 'item: Alpha; version:0.0; name:Test'
|
modVersion = parser.parse 'item: Alpha; version:0.0; name:Test'
|
||||||
modVersion.name.should.equal 'Test'
|
modVersion.name.should.equal 'Test'
|
||||||
modVersion.version.should.equal '0.0'
|
modVersion.version.should.equal '0.0'
|
||||||
modVersion.items.alpha.name.should.equal 'Alpha'
|
modVersion._items.alpha.name.should.equal 'Alpha'
|
||||||
|
|
||||||
it 'does not allow duplicate item declarations', ->
|
it 'does not allow duplicate item declarations', ->
|
||||||
func = -> parser.parse 'version:0.0; name:Test; item:Charlie; item:Charlie'
|
func = -> parser.parse 'version:0.0; name:Test; item:Charlie; item:Charlie'
|
||||||
@@ -75,7 +75,7 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'allows multiple items', ->
|
it 'allows multiple items', ->
|
||||||
modVersion = parser.parse 'name:Test; version:0.0; item:Bravo; item:Charlie'
|
modVersion = parser.parse 'name:Test; version:0.0; item:Bravo; item:Charlie'
|
||||||
_.keys(modVersion.items).sort().should.eql ['bravo', 'charlie']
|
_.keys(modVersion._items).sort().should.eql ['bravo', 'charlie']
|
||||||
|
|
||||||
describe 'name', ->
|
describe 'name', ->
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'adds "input" when present', ->
|
it 'adds "input" when present', ->
|
||||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern: ... 012 ...'
|
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern: ... 012 ...'
|
||||||
slugs = (s.itemSlug for s in modVersion.items.charlie.recipes[0].input)
|
slugs = (s.slug for s in modVersion._items.charlie._recipes[0].input)
|
||||||
slugs.should.eql ['alpha', 'bravo', 'charlie']
|
slugs.should.eql ['alpha', 'bravo', 'charlie']
|
||||||
|
|
||||||
it 'requires an "input" declaration', ->
|
it 'requires an "input" declaration', ->
|
||||||
@@ -140,13 +140,13 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'registers slugs for each input name', ->
|
it 'registers slugs for each input name', ->
|
||||||
modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...'
|
modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...'
|
||||||
_.keys(modVersion.names).sort().should.eql ['charlie', 'delta', 'echo', 'foxtrot']
|
modVersion._slugs.should.eql ['charlie', 'delta', 'echo', 'foxtrot']
|
||||||
|
|
||||||
describe 'pattern', ->
|
describe 'pattern', ->
|
||||||
|
|
||||||
it 'adds "pattern" when present', ->
|
it 'adds "pattern" when present', ->
|
||||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
|
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
|
||||||
modVersion.items.charlie.recipes[0].pattern.should.equal '... .0. .1.'
|
modVersion._items.charlie._recipes[0].pattern.should.equal '... .0. .1.'
|
||||||
|
|
||||||
it 'requires a "pattern" declaration', ->
|
it 'requires a "pattern" declaration', ->
|
||||||
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
|
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
|
||||||
@@ -174,7 +174,7 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'computes the input stack sizes from the pattern', ->
|
it 'computes the input stack sizes from the pattern', ->
|
||||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern:111 .0. 2.2'
|
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern:111 .0. 2.2'
|
||||||
recipe = modVersion.items.charlie.recipes[0]
|
recipe = modVersion._items.charlie._recipes[0]
|
||||||
recipe.input[0].quantity.should.equal 1
|
recipe.input[0].quantity.should.equal 1
|
||||||
recipe.input[1].quantity.should.equal 3
|
recipe.input[1].quantity.should.equal 3
|
||||||
recipe.input[2].quantity.should.equal 2
|
recipe.input[2].quantity.should.equal 2
|
||||||
@@ -190,7 +190,7 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'adds "quantity" when present', ->
|
it 'adds "quantity" when present', ->
|
||||||
modVersion = parser.parse baseText + 'quantity: 2'
|
modVersion = parser.parse baseText + 'quantity: 2'
|
||||||
modVersion.items.charlie.recipes[0].output[0].quantity.should.equal 2
|
modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 2
|
||||||
|
|
||||||
it 'does not allow a duplicate "quantity" declaration', ->
|
it 'does not allow a duplicate "quantity" declaration', ->
|
||||||
func = -> parser.parse baseText + 'quantity:1; quantity:2'
|
func = -> parser.parse baseText + 'quantity:1; quantity:2'
|
||||||
@@ -202,7 +202,7 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'assumes a quantity of 1 by default', ->
|
it 'assumes a quantity of 1 by default', ->
|
||||||
modVersion = parser.parse baseText
|
modVersion = parser.parse baseText
|
||||||
modVersion.items.charlie.recipes[0].output[0].quantity.should.equal 1
|
modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 1
|
||||||
|
|
||||||
it 'does not allow "quantity" before recipe', ->
|
it 'does not allow "quantity" before recipe', ->
|
||||||
func = -> parser.parse 'name:Alpha; version:1; item:Bravo; quantity:12; recipe:;'
|
func = -> parser.parse 'name:Alpha; version:1; item:Bravo; quantity:12; recipe:;'
|
||||||
@@ -215,18 +215,18 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'adds a single item as the default output', ->
|
it 'adds a single item as the default output', ->
|
||||||
modVersion = parser.parse baseText
|
modVersion = parser.parse baseText
|
||||||
stack = modVersion.items.bravo.recipes[0].output[0]
|
stack = modVersion._items.bravo._recipes[0].output[0]
|
||||||
stack.itemSlug.should.equal 'bravo'
|
stack.slug.should.equal 'bravo'
|
||||||
stack.quantity.should.equal 1
|
stack.quantity.should.equal 1
|
||||||
|
|
||||||
it 'can add multiple extras with quantities', ->
|
it 'can add multiple extras with quantities', ->
|
||||||
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
|
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
|
||||||
output = modVersion.items.bravo.recipes[0].output
|
output = modVersion._items.bravo._recipes[0].output
|
||||||
output[0].itemSlug.should.equal 'bravo'
|
output[0].slug.should.equal 'bravo'
|
||||||
output[0].quantity.should.equal 1
|
output[0].quantity.should.equal 1
|
||||||
output[1].itemSlug.should.equal 'delta'
|
output[1].slug.should.equal 'delta'
|
||||||
output[1].quantity.should.equal 2
|
output[1].quantity.should.equal 2
|
||||||
output[2].itemSlug.should.equal 'echo'
|
output[2].slug.should.equal 'echo'
|
||||||
output[2].quantity.should.equal 4
|
output[2].quantity.should.equal 4
|
||||||
|
|
||||||
it 'does not allow "extras" before "recipe"', ->
|
it 'does not allow "extras" before "recipe"', ->
|
||||||
@@ -235,7 +235,7 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'registers slugs for each output name', ->
|
it 'registers slugs for each output name', ->
|
||||||
modVersion = parser.parse baseText + 'extras:Delta, Echo'
|
modVersion = parser.parse baseText + 'extras:Delta, Echo'
|
||||||
_.keys(modVersion.names).sort().should.eql ['bravo', 'charlie', 'delta', 'echo']
|
modVersion._slugs.should.eql ['bravo', 'charlie', 'delta', 'echo']
|
||||||
|
|
||||||
it 'does not allow a duplicate "extras" declaration', ->
|
it 'does not allow a duplicate "extras" declaration', ->
|
||||||
func = -> parser.parse baseText + 'extras:Echo; extras:Delta'
|
func = -> parser.parse baseText + 'extras:Echo; extras:Delta'
|
||||||
@@ -248,17 +248,17 @@ describe 'ModVersionParserV2', ->
|
|||||||
|
|
||||||
it 'can add a single tool', ->
|
it 'can add a single tool', ->
|
||||||
modVersion = parser.parse baseText + 'tools: Furnace'
|
modVersion = parser.parse baseText + 'tools: Furnace'
|
||||||
modVersion.items.bravo.recipes[0].tools[0].itemSlug.should.equal 'furnace'
|
modVersion._items.bravo._recipes[0].tools[0].slug.should.equal 'furnace'
|
||||||
|
|
||||||
it 'can add multiple tools', ->
|
it 'can add multiple tools', ->
|
||||||
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
||||||
tools = modVersion.items.bravo.recipes[0].tools
|
tools = modVersion._items.bravo._recipes[0].tools
|
||||||
tools[0].itemSlug.should.equal 'crafting_table'
|
tools[0].slug.should.equal 'crafting_table'
|
||||||
tools[1].itemSlug.should.equal 'furnace'
|
tools[1].slug.should.equal 'furnace'
|
||||||
|
|
||||||
it 'registers slugs for each tool name', ->
|
it 'registers slugs for each tool name', ->
|
||||||
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
||||||
_.keys(modVersion.names).sort().should.eql ['bravo', 'charlie', 'crafting_table', 'furnace']
|
modVersion._slugs.should.eql ['bravo', 'charlie', 'crafting_table', 'furnace']
|
||||||
|
|
||||||
it 'does not allow a duplicate "tools" declaration', ->
|
it 'does not allow a duplicate "tools" declaration', ->
|
||||||
func = -> parser.parse baseText + 'tools:Crafting Table; tools:Furnace'
|
func = -> parser.parse baseText + 'tools:Crafting Table; tools:Furnace'
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ global.util = require 'util'
|
|||||||
Logger = require '../src/scripts/logger'
|
Logger = require '../src/scripts/logger'
|
||||||
global.logger = new Logger level:Logger.TRACE
|
global.logger = new Logger level:Logger.TRACE
|
||||||
|
|
||||||
|
require '../src/scripts/polyfill'
|
||||||
require '../src/scripts/underscore_mixins'
|
require '../src/scripts/underscore_mixins'
|
||||||
|
|
||||||
# Test Registry ########################################################################################################
|
# Test Registry ########################################################################################################
|
||||||
|
|||||||
Reference in New Issue
Block a user