wip
This commit is contained in:
@@ -94,6 +94,10 @@ GitHub.file.itemDescription = {}
|
||||
GitHub.file.itemDescription.fileName = _.template "item.cg"
|
||||
GitHub.file.itemDescription.path = _.template "/data/<%= modSlug %>/items/<%= itemSlug %>"
|
||||
|
||||
exports.Limits = Limits = {}
|
||||
Limits.maximumGraphSize = 10000
|
||||
Limits.maximumPlanCount = 10000
|
||||
|
||||
exports.Login = Login = {}
|
||||
Login.authorizeUrl = _.template "https://github.com/login/oauth/authorize" +
|
||||
"?client_id=<%= clientId %>&scope=public_repo&state=<%= state %>"
|
||||
|
||||
@@ -32,45 +32,39 @@ module.exports = class CraftPageController extends PageController
|
||||
@modPack = options.modPack
|
||||
@storage = options.storage
|
||||
|
||||
@model.plan.on Event.change, => @tryRefresh()
|
||||
@model.craftsman.on Event.change, => @tryRefresh()
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onCraftTool: (itemSlug)->
|
||||
# TODO: implement this
|
||||
|
||||
onHaveInventoryChanged: ->
|
||||
@storage.store 'crafting-plan:have', @model.plan.have.unparse()
|
||||
@storage.store 'crafting-plan:have', @model.craftsman.have.unparse()
|
||||
|
||||
onMoveNeedToHave: (itemSlug)->
|
||||
quantity = @model.plan.need.quantityOf itemSlug
|
||||
@model.plan.have.add itemSlug, quantity
|
||||
quantity = @model.craftsman.need.quantityOf itemSlug
|
||||
@model.craftsman.have.add itemSlug, quantity
|
||||
@onHaveInventoryChanged()
|
||||
|
||||
onRemoveFromHaveInventory: (itemSlug)->
|
||||
@model.plan.have.remove itemSlug
|
||||
@model.craftsman.have.remove itemSlug
|
||||
@onHaveInventoryChanged()
|
||||
|
||||
onRemoveFromWant: (itemSlug)->
|
||||
@model.plan.want.remove itemSlug
|
||||
@model.craftsman.want.remove itemSlug
|
||||
@onWantInventoryChange()
|
||||
|
||||
onStopUsingTool: (itemSlug)->
|
||||
# TODO: implement this
|
||||
|
||||
onStepComplete: (stepController)->
|
||||
step = stepController.model
|
||||
for stack in step.recipe.output
|
||||
@model.plan.have.add stack.itemSlug, stack.quantity * step.multiplier
|
||||
@model.craftsman.have.add stack.itemSlug, stack.quantity * step.multiplier
|
||||
@onHaveInventoryChanged()
|
||||
|
||||
onWantInventoryChange: ->
|
||||
text = @model.plan.want.unparse()
|
||||
text = @model.craftsman.want.unparse()
|
||||
url = Url.crafting inventoryText:text
|
||||
router.navigate url
|
||||
|
||||
if @model.plan.want.isEmpty
|
||||
@model.plan.have.clear()
|
||||
if @model.craftsman.want.isEmpty
|
||||
@model.craftsman.have.clear()
|
||||
@onHaveInventoryChanged()
|
||||
|
||||
# PageController Overrides #####################################################################
|
||||
@@ -89,24 +83,15 @@ module.exports = class CraftPageController extends PageController
|
||||
@wantInventoryController = @addChild InventoryController, '.want .view__inventory',
|
||||
imageLoader: @imageLoader
|
||||
isAcceptable: (item)=> item.isCraftable
|
||||
model: @model.plan.want
|
||||
model: @model.craftsman.want
|
||||
modPack: @modPack
|
||||
firstButtonType: 'remove'
|
||||
@wantInventoryController.on Event.button.first, (c, s)=> @onRemoveFromWant(s)
|
||||
@wantInventoryController.on Event.change, (c)=> @onWantInventoryChange()
|
||||
|
||||
# @toolsInUseController = @addChild InventoryController, '.tools .view__inventory',
|
||||
# imageLoader: @imageLoader
|
||||
# model: @model.plan.toolsInUse
|
||||
# modPack: @modPack
|
||||
# firstButtonType: 'down'
|
||||
# secondButtonType: 'up'
|
||||
# @toolsInUseController.on Event.button.first, (c, s)=> @onStopUsingTool(s)
|
||||
# @toolsInUseController.of Event.button.second, (c, s)=> @onCraftTool(s)
|
||||
|
||||
@haveInventoryController = @addChild InventoryController, '.have .view__inventory',
|
||||
imageLoader: @imageLoader
|
||||
model: @model.plan.have
|
||||
model: @model.craftsman.have
|
||||
modPack: @modPack
|
||||
firstButtonType: 'down'
|
||||
@haveInventoryController.on Event.button.first, (c, s)=>
|
||||
@@ -116,7 +101,7 @@ module.exports = class CraftPageController extends PageController
|
||||
@needInventoryController = @addChild InventoryController, '.need .view__inventory',
|
||||
editable: false
|
||||
imageLoader: @imageLoader
|
||||
model: @model.plan.need
|
||||
model: null
|
||||
modPack: @modPack
|
||||
firstButtonType: 'up'
|
||||
@needInventoryController.on Event.button.first, (c, s)=> @onMoveNeedToHave(s)
|
||||
@@ -131,11 +116,12 @@ module.exports = class CraftPageController extends PageController
|
||||
super
|
||||
|
||||
onWillRender: ->
|
||||
@model.plan.have.clear()
|
||||
@model.plan.have.parse @storage.load('crafting-plan:have')
|
||||
@model.craftsman.have.clear()
|
||||
@model.craftsman.have.parse @storage.load('crafting-plan:have')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@needInventoryController.model = @model.craftsman.plan?.need
|
||||
@_refreshSectionVisibility()
|
||||
@_refreshSteps()
|
||||
@adsenseController.fillAdPositions()
|
||||
@@ -150,10 +136,10 @@ module.exports = class CraftPageController extends PageController
|
||||
# Private Methods ################################################################################
|
||||
|
||||
_isStepCompletable: (controller)->
|
||||
return not @model.plan.want.hasAtLeast controller.model.outputItemSlug
|
||||
return not @model.craftsman.want.hasAtLeast controller.model.outputItemSlug
|
||||
|
||||
_refreshSectionVisibility: ->
|
||||
if @model.plan.want.isEmpty
|
||||
if @model.craftsman.want.isEmpty
|
||||
for $el in [@$toolsSection, @$ingredientsSection, @$stepsSection]
|
||||
@hide $el
|
||||
@show @$instructionsSection
|
||||
@@ -163,10 +149,11 @@ module.exports = class CraftPageController extends PageController
|
||||
@hide @$instructionsSection
|
||||
|
||||
_refreshSteps: ->
|
||||
steps = @model.craftsman.plan?.steps or []
|
||||
@_stepControllers ?= []
|
||||
index = 0
|
||||
|
||||
for step in @model.plan.steps
|
||||
for step in steps
|
||||
controller = @_stepControllers[index]
|
||||
if not controller?
|
||||
controller = new StepController
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
###
|
||||
Crafting Guide - crafting_table_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
ImageLoader = require './image_loader'
|
||||
MinimalRecipeController = require './minimal_recipe_controller'
|
||||
_ = require 'underscore'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingTableController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.templateName = 'crafting_table'
|
||||
super options
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onNextClicked: ->
|
||||
@model.stepIndex += 1
|
||||
|
||||
onPrevClicked: ->
|
||||
@model.stepIndex -= 1
|
||||
|
||||
onReportProblem: ->
|
||||
itemList = @model.plan.want.unparse()
|
||||
toolsMessage = if @model.plan.includingTools then '(including tools)' else ''
|
||||
message = "When I was on step #{@model.stepIndex + 1} of making:
|
||||
\n\n#{itemList}#{toolsMessage}\n\nI noticed that...\n"
|
||||
global.feedbackController.enterFeedback message
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@recipeController = @addChild MinimalRecipeController, '.view__minimal_recipe',
|
||||
imageLoader: @imageLoader
|
||||
modPack: @modPack
|
||||
|
||||
@$next = @$('.next')
|
||||
@$prev = @$('.prev')
|
||||
@$problemControl = @$('.problem')
|
||||
@$title = @$('h2 p')
|
||||
@$tool = @$('.tool p')
|
||||
|
||||
@$multiplier = $('<p class="multiplier"></p>')
|
||||
@$('.output').append @$multiplier
|
||||
|
||||
@defaultTitle = @$title.html()
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@$prev.removeClass 'enabled'
|
||||
@$next.removeClass 'enabled'
|
||||
|
||||
if @model.hasSteps
|
||||
if @model.hasPrevStep then @$prev.addClass 'enabled'
|
||||
if @model.hasNextStep then @$next.addClass 'enabled'
|
||||
@$title.html "Step #{@model.stepIndex + 1} of #{@model.stepCount}"
|
||||
else
|
||||
@$title.html @defaultTitle
|
||||
|
||||
currentStep = @model.currentStep
|
||||
@recipeController.model = currentStep?.recipe
|
||||
|
||||
if currentStep?.multiplier > 1
|
||||
@$multiplier.html "×#{currentStep.multiplier}"
|
||||
else
|
||||
@$multiplier.html ''
|
||||
|
||||
if not (@model.hasSteps and global.feedbackController?)
|
||||
@$problemControl.addClass 'hidden'
|
||||
else
|
||||
@$problemControl.removeClass 'hidden'
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click .next': 'onNextClicked'
|
||||
'click .prev': 'onPrevClicked'
|
||||
'click .problem a': 'onReportProblem'
|
||||
@@ -33,7 +33,6 @@ module.exports = class InventoryController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@@ -57,6 +56,8 @@ module.exports = class InventoryController extends BaseController
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onClearButtonClicked: ->
|
||||
return unless @model?
|
||||
|
||||
@model.clear()
|
||||
@trigger Event.clear, this
|
||||
@trigger Event.change, this
|
||||
@@ -65,6 +66,8 @@ module.exports = class InventoryController extends BaseController
|
||||
@trigger Event.button.first, this, stackController?.model?.itemSlug
|
||||
|
||||
onItemChosen: (itemSlug)->
|
||||
return unless @model?
|
||||
|
||||
@model.add itemSlug, 1
|
||||
@trigger Event.add, this, itemSlug
|
||||
@trigger Event.change, this
|
||||
@@ -95,11 +98,12 @@ module.exports = class InventoryController extends BaseController
|
||||
|
||||
@$icon.attr 'src', @icon
|
||||
@$title.html @title
|
||||
@$clearButton.disabled = @model.isEmpty
|
||||
|
||||
@_refreshStacks()
|
||||
|
||||
if @model.isEmpty
|
||||
@$clearButton.disabled = @model?.isEmpty
|
||||
|
||||
if not @model or @model.isEmpty
|
||||
@show @$emptyPlaceholder
|
||||
else
|
||||
@hide @$emptyPlaceholder
|
||||
@@ -120,27 +124,28 @@ module.exports = class InventoryController extends BaseController
|
||||
@_stackControllers ?= []
|
||||
index = 0
|
||||
|
||||
@model.each (stack)=>
|
||||
controller = @_stackControllers[index]
|
||||
if not controller?
|
||||
controller = new StackController
|
||||
editable: @editable
|
||||
firstButtonType: @firstButtonType
|
||||
imageLoader: @imageLoader
|
||||
model: stack
|
||||
modPack: @modPack
|
||||
secondButtonType: @secondButtonType
|
||||
shouldEnableButton: @shouldEnableButton
|
||||
controller.on Event.change, => @trigger Event.change, this
|
||||
controller.on Event.button.first, (c)=> @onFirstButtonClicked(c)
|
||||
controller.on Event.button.second, (c)=> @onSecondButtonClicked(c)
|
||||
controller.render()
|
||||
if @model?
|
||||
@model.each (stack)=>
|
||||
controller = @_stackControllers[index]
|
||||
if not controller?
|
||||
controller = new StackController
|
||||
editable: @editable
|
||||
firstButtonType: @firstButtonType
|
||||
imageLoader: @imageLoader
|
||||
model: stack
|
||||
modPack: @modPack
|
||||
secondButtonType: @secondButtonType
|
||||
shouldEnableButton: @shouldEnableButton
|
||||
controller.on Event.change, => @trigger Event.change, this
|
||||
controller.on Event.button.first, (c)=> @onFirstButtonClicked(c)
|
||||
controller.on Event.button.second, (c)=> @onSecondButtonClicked(c)
|
||||
controller.render()
|
||||
|
||||
@_stackControllers.push controller
|
||||
@$itemContainer.append controller.$el
|
||||
else
|
||||
controller.model = stack
|
||||
index += 1
|
||||
@_stackControllers.push controller
|
||||
@$itemContainer.append controller.$el
|
||||
else
|
||||
controller.model = stack
|
||||
index += 1
|
||||
|
||||
while @_stackControllers.length > index
|
||||
@_stackControllers.pop().remove()
|
||||
|
||||
@@ -5,11 +5,11 @@ Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
_ = require 'underscore'
|
||||
BaseController = require './base_controller'
|
||||
{Event} = require '../constants'
|
||||
InventoryController = require './inventory_controller'
|
||||
MinimalRecipeController = require './minimal_recipe_controller'
|
||||
_ = require 'underscore'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -56,7 +56,7 @@ module.exports = class StepController extends BaseController
|
||||
return super
|
||||
|
||||
refresh: ->
|
||||
itemDisplay = @modPack.findItemDisplay @model.outputItemSlug
|
||||
itemDisplay = @modPack.findItemDisplay @model.recipe.output[0].itemSlug
|
||||
@$header.html "#{@model.number}. #{itemDisplay.itemName}"
|
||||
|
||||
@inventoryController.model = @model.inventory
|
||||
|
||||
@@ -5,34 +5,32 @@ Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
CraftingPlan = require './crafting_plan'
|
||||
CraftingTable = require './crafting_table'
|
||||
{Event} = require '../constants'
|
||||
Inventory = require './inventory'
|
||||
ModPack = require './mod_pack'
|
||||
BaseModel = require './base_model'
|
||||
Craftsman = require './crafting/craftsman'
|
||||
{Event} = require '../constants'
|
||||
Inventory = require './inventory'
|
||||
ModPack = require './mod_pack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftPage extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
attributes.modPack ?= new ModPack
|
||||
attributes.params ?= null
|
||||
attributes.plan ?= new CraftingPlan modPack:attributes.modPack
|
||||
attributes.table ?= new CraftingTable plan:attributes.plan
|
||||
if not attributes.modPack then throw new Error 'attributes.modPack is required'
|
||||
attributes.params ?= null
|
||||
attributes.craftsman ?= new Craftsman attributes.modPack
|
||||
super attributes, options
|
||||
|
||||
@modPack.on Event.change, => @_consumeParams()
|
||||
@on Event.change + ':params', => @_consumeParams()
|
||||
@plan.on Event.change, => @trigger Event.change, this
|
||||
@craftsman.on Event.change, => @trigger Event.change, this
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_consumeParams: ->
|
||||
return unless @params?
|
||||
|
||||
@plan.want.clear()
|
||||
@craftsman.want.clear()
|
||||
if not @params.inventoryText?
|
||||
@params = null
|
||||
else
|
||||
@@ -42,7 +40,7 @@ module.exports = class CraftPage extends BaseModel
|
||||
inventory.each (stack)=>
|
||||
item = @modPack.findItem stack.itemSlug, enableAsNeeded:true
|
||||
return unless item? and item.isCraftable
|
||||
@plan.want.add stack.itemSlug, stack.quantity
|
||||
@craftsman.want.add stack.itemSlug, stack.quantity
|
||||
inventory.remove stack.itemSlug
|
||||
|
||||
if inventory.isEmpty then @params = null
|
||||
|
||||
@@ -5,46 +5,90 @@ Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
Inventory = require '../inventory'
|
||||
_ = require '../../underscore_mixins'
|
||||
SimpleInventory = require '../simple_inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingPlan
|
||||
|
||||
constructor: (steps, wanted)->
|
||||
constructor: (steps, want, modPack)->
|
||||
if not modPack? then throw new Error 'modPack is required'
|
||||
if not steps? then throw new Error 'steps is required'
|
||||
if not wanted? then throw new Error 'wanted is required'
|
||||
if not want? then throw new Error 'want is required'
|
||||
|
||||
@_produced = new Inventory
|
||||
@_required = new Inventory
|
||||
@_steps = steps
|
||||
@_wanted = wanted
|
||||
@_made = null
|
||||
@_modPack = modPack
|
||||
@_need = null
|
||||
@_rawScores = {}
|
||||
@_scores = {}
|
||||
@_steps = steps
|
||||
@_want = want
|
||||
|
||||
@_computeRequired()
|
||||
@_numberSteps()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
computeRequired: ->
|
||||
@_need = new SimpleInventory modPack:@_modPack
|
||||
@_made = new SimpleInventory modPack:@_modPack
|
||||
@_need.addInventory @_want
|
||||
|
||||
for i in [@_steps.length-1..0] by -1
|
||||
step = @_steps[i]
|
||||
step.multiplier = 0
|
||||
|
||||
for stack in step.recipe.output
|
||||
if not stack?
|
||||
throw new Error 'stack should not be null here'
|
||||
|
||||
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
|
||||
while @_need.quantityOf(qualifiedSlug) > 0
|
||||
@_executeStep step
|
||||
|
||||
@_made.addInventory @_want
|
||||
|
||||
hasRawScore: (name)->
|
||||
return @_rawScores[name]?
|
||||
|
||||
getRawScore: (name)->
|
||||
return @_rawScores[name]
|
||||
|
||||
setRawScore: (name, rawScore)->
|
||||
@_rawScores[name] = rawScore
|
||||
|
||||
hasScore: (name)->
|
||||
return @_scores[name]?
|
||||
|
||||
getScore: (name)->
|
||||
return @_scores[name]
|
||||
|
||||
setScore: (name, score)->
|
||||
@_scores[name] = score
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
length:
|
||||
get: -> @steps.length
|
||||
produced:
|
||||
get: -> @_produced
|
||||
required:
|
||||
get: -> @_required
|
||||
made:
|
||||
get: -> @_made
|
||||
need:
|
||||
get: -> @_need
|
||||
steps:
|
||||
get: -> @_steps
|
||||
wanted:
|
||||
get: -> @_wanted
|
||||
want:
|
||||
get: -> @_want
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = ["To Make:"]
|
||||
@_wanted.each (stack)->
|
||||
@_want.each (stack)->
|
||||
result.push " #{stack}"
|
||||
|
||||
result.push "Start with:"
|
||||
@_required.each (stack)->
|
||||
@_need.each (stack)->
|
||||
result.push " #{stack}"
|
||||
|
||||
result.push "Use these recipes:"
|
||||
@@ -52,44 +96,47 @@ module.exports = class CraftingPlan
|
||||
result.push " #{step}"
|
||||
|
||||
result.push "To produce:"
|
||||
@_produced.each (stack)->
|
||||
@_made.each (stack)->
|
||||
result.push " #{stack}"
|
||||
|
||||
if _.keys(@_rawScores).length > 0
|
||||
result.push "Scores:"
|
||||
for criteria, score of @_rawScores
|
||||
if @_scores[criteria]?
|
||||
result.push " #{criteria}: #{score} (#{@_scores[criteria]})"
|
||||
else
|
||||
result.push " #{criteria}: #{score}"
|
||||
|
||||
return result.join '\n'
|
||||
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeRequired: ->
|
||||
@_required.addInventory @_wanted
|
||||
|
||||
for i in [@_steps.length-1..0] by -1
|
||||
step = @_steps[i]
|
||||
|
||||
for stack in step.recipe.output
|
||||
while @_required.quantityOf(stack.itemSlug) > 0
|
||||
@_executeStep step
|
||||
|
||||
@_produced.addInventory @_wanted
|
||||
|
||||
_executeStep: (step)->
|
||||
step.repeat += 1
|
||||
step.multiplier += 1
|
||||
recipe = step.recipe
|
||||
|
||||
for stack in recipe.input
|
||||
available = @_produced.quantityOf stack.itemSlug
|
||||
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
|
||||
|
||||
available = @_made.quantityOf qualifiedSlug
|
||||
required = recipe.getQuantityRequired stack.itemSlug
|
||||
consumed = Math.min required, available
|
||||
deficit = required - consumed
|
||||
|
||||
@_produced.remove stack.itemSlug, consumed
|
||||
@_required.add stack.itemSlug, deficit
|
||||
@_made.remove qualifiedSlug, consumed
|
||||
@_need.add qualifiedSlug, deficit
|
||||
|
||||
for stack in recipe.output
|
||||
deficit = @_required.quantityOf stack.itemSlug
|
||||
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
|
||||
|
||||
deficit = @_need.quantityOf qualifiedSlug
|
||||
created = recipe.getQuantityProduced stack.itemSlug
|
||||
replenished = Math.min deficit, created
|
||||
surplus = created - replenished
|
||||
|
||||
@_produced.add stack.itemSlug, surplus
|
||||
@_required.remove stack.itemSlug, replenished
|
||||
@_made.add qualifiedSlug, surplus
|
||||
@_need.remove qualifiedSlug, replenished
|
||||
|
||||
_numberSteps: ->
|
||||
for step, i in @_steps
|
||||
step.number = i + 1
|
||||
|
||||
@@ -5,25 +5,55 @@ Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
SimpleInventory = require '../simple_inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingStep
|
||||
|
||||
constructor: (recipe, repeat=0)->
|
||||
constructor: (recipe, modPack, multiplier=0)->
|
||||
if not recipe? then throw new Error 'recipe is required'
|
||||
if repeat < 0 then throw new Error 'repeat must be at least 1'
|
||||
if not modPack? then throw new Error 'modPack is required'
|
||||
if multiplier < 0 then throw new Error 'multiplier must be at least 1'
|
||||
|
||||
@_recipe = recipe
|
||||
@repeat = repeat
|
||||
@number = null
|
||||
|
||||
@_inventory = null
|
||||
@_modPack = modPack
|
||||
@_multiplier = multiplier
|
||||
@_recipe = recipe
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
inventory:
|
||||
get: ->
|
||||
if not @_inventory?
|
||||
@_refreshInventory()
|
||||
return @_inventory
|
||||
|
||||
recipe:
|
||||
get: -> @_recipe
|
||||
|
||||
multiplier:
|
||||
get: -> @_multiplier
|
||||
set: (newMultiplier)->
|
||||
@_multiplier = newMultiplier
|
||||
@_refreshInventory() if @_inventory?
|
||||
|
||||
slug:
|
||||
get: -> "#{@multiplier}x #{@_recipe.slug}"
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@repeat}x #{@recipe.slug}"
|
||||
return @slug
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshInventory: ->
|
||||
@_inventory = new SimpleInventory
|
||||
for stack in @_recipe.input
|
||||
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
|
||||
@_inventory.add qualifiedSlug, @multiplier * @_recipe.getQuantityRequired stack.itemSlug
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
###
|
||||
Crafting Guide - craftsman.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
_ = require 'underscore'
|
||||
BaseModel = require '../base_model'
|
||||
{Event} = require '../../constants'
|
||||
GraphBuilder = require './graph_builder'
|
||||
Inventory = require '../inventory'
|
||||
PlanBuilder = require './plan_builder'
|
||||
PlanEvaluator = require './plan_evaluator'
|
||||
w = require 'when'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Craftsman extends BaseModel
|
||||
|
||||
@::ANALYZE_STEP_INCREMENT = 100
|
||||
|
||||
@::GRAPH_STEP_INCREMENT = 10
|
||||
|
||||
@::PLAN_STEP_INCREMENT = 50
|
||||
|
||||
@::STAGE =
|
||||
WAITING: 'waiting'
|
||||
GRAPHING: 'examining recipes'
|
||||
PLANNING: 'computing plans'
|
||||
ANALYZING: 'analyzing plans'
|
||||
COMPLETE: 'complete'
|
||||
|
||||
constructor: (modPack)->
|
||||
if not modPack? then throw new Error 'modPack is required'
|
||||
attributes =
|
||||
paused: false
|
||||
stage: @STAGE.WAITING
|
||||
stageCount: 0
|
||||
super attributes, {}
|
||||
|
||||
@_modPack = modPack
|
||||
|
||||
reset = _.throttle (=> @reset()), 100
|
||||
|
||||
@_have = new Inventory
|
||||
@_have.on Event.change, reset
|
||||
|
||||
@_want = new Inventory
|
||||
@_want.on Event.change, reset
|
||||
|
||||
@on Event.change + ':paused', reset
|
||||
@on Event.change + ':stage', => logger.info "Craftsman has started #{@stage}..."
|
||||
@reset()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
work: ->
|
||||
return if @_want.isEmpty
|
||||
logger.verbose => "stage: #{@stage}(#{@stageCount}) working..."
|
||||
|
||||
if not @_graphBuilder?
|
||||
want = new Inventory {modPack:@_modPack}, clone:@_want
|
||||
want.localize()
|
||||
|
||||
have = new Inventory {modPack:@_modPack}, clone:@_have
|
||||
have.localize()
|
||||
|
||||
logger.info -> "Craftsman starting to build #{want} from #{have}"
|
||||
|
||||
@_graphBuilder = new GraphBuilder modPack:@_modPack, want:want, have:have
|
||||
@stage = @STAGE.GRAPHING
|
||||
@stageCount = 0
|
||||
else if not @_graphBuilder.complete
|
||||
@_graphBuilder.expandGraph @GRAPH_STEP_INCREMENT
|
||||
@stageCount = @_graphBuilder.stepCount
|
||||
else if not @_planBuilder?
|
||||
logger.debug => "Craftsman finished computing graph:\n#{@_graphBuilder.rootNode}"
|
||||
|
||||
@_planBuilder = new PlanBuilder @_graphBuilder.rootNode, @_modPack, want:@_graphBuilder.want
|
||||
@stage = @STAGE.PLANNING
|
||||
@stageCount = 0
|
||||
else if not @_planBuilder.complete
|
||||
@_planBuilder.producePlans @PLAN_STEP_INCREMENT
|
||||
@stageCount = @_planBuilder.plans.length
|
||||
else if not @_planEvaluator?
|
||||
@_planEvaluator = new PlanEvaluator @_planBuilder.plans
|
||||
@stage = @STAGE.ANALYZING
|
||||
@stageCount = 0
|
||||
else if not @_planEvaluator.complete
|
||||
@_planEvaluator.scorePlans @ANALYZE_STEP_INCREMENT
|
||||
@stageCount = @_planEvaluator.lastScored
|
||||
else
|
||||
@_plans = [
|
||||
@_planEvaluator.findBestPlan PlanEvaluator::CRITERIA.FEWEST_STEPS
|
||||
@_planEvaluator.findBestPlan PlanEvaluator::CRITERIA.LEAST_MATERIALS
|
||||
]
|
||||
@_plans[0].computeRequired()
|
||||
logger.info => "Craftsman has finished with plans: #{(p.toString() for p in @_plans).join('\n')}"
|
||||
@trigger Event.change + ':complete', this
|
||||
@trigger Event.change, this
|
||||
|
||||
@_scheduleNextWork()
|
||||
return @complete
|
||||
|
||||
reset: ->
|
||||
@_graphBuilder = null
|
||||
@_planBuilder = null
|
||||
@_planEvaluator = null
|
||||
@_plans = null
|
||||
|
||||
@stage = @STAGE.WAITING
|
||||
@stageCount = 0
|
||||
|
||||
@_scheduleNextWork()
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
complete:
|
||||
get: -> @_plans?
|
||||
|
||||
have:
|
||||
get: -> @_have
|
||||
|
||||
plan:
|
||||
get: -> @_plans?[0]
|
||||
|
||||
want:
|
||||
get: -> @_want
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_scheduleNextWork: ->
|
||||
return if @paused
|
||||
return if @want.isEmpty
|
||||
return if @complete
|
||||
|
||||
_.defer => @work()
|
||||
@@ -7,6 +7,7 @@ All rights reserved.
|
||||
|
||||
Inventory = require '../inventory'
|
||||
InventoryNode = require './inventory_node'
|
||||
{Limits} = require '../../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -14,12 +15,15 @@ module.exports = class GraphBuilder
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
if not options.want? then throw new Error 'options.want is required'
|
||||
|
||||
@modPack = options.modPack
|
||||
@_wanted = options.wanted ?= new Inventory
|
||||
@_maximumGraphSize = Limits.maximumGraphSize
|
||||
@_modPack = options.modPack
|
||||
@_stepCount = 0
|
||||
@_want = options.want
|
||||
|
||||
@_wanted.on 'change', => @reset()
|
||||
@reset()
|
||||
@_rootNode = new InventoryNode modPack:@_modPack, inventory:@_want
|
||||
@_queue = [@_rootNode]
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
@@ -38,9 +42,6 @@ module.exports = class GraphBuilder
|
||||
@_stepCount += 1
|
||||
|
||||
reset: ->
|
||||
@_rootNode = new InventoryNode modPack:@modPack, inventory:@wanted
|
||||
@_queue = [@_rootNode]
|
||||
@_stepCount = 0
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
@@ -48,6 +49,7 @@ module.exports = class GraphBuilder
|
||||
|
||||
complete:
|
||||
get: ->
|
||||
return true if @_stepCount > @_maximumGraphSize
|
||||
return false unless @_rootNode?
|
||||
return false unless @_queue?
|
||||
return false unless @_queue.length is 0
|
||||
@@ -59,8 +61,8 @@ module.exports = class GraphBuilder
|
||||
stepCount:
|
||||
get: -> @_stepCount
|
||||
|
||||
wanted:
|
||||
get: -> @_wanted
|
||||
want:
|
||||
get: -> @_want
|
||||
|
||||
# Object Overrides ############################################################################
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ module.exports = class ItemNode extends CraftingNode
|
||||
@::TYPE = CraftingNode::TYPES.ITEM
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.item? then throw new Error 'options.item is required'
|
||||
if not options.item?
|
||||
throw new Error 'options.item is required'
|
||||
super options
|
||||
|
||||
@item = options.item
|
||||
@@ -32,7 +33,7 @@ module.exports = class ItemNode extends CraftingNode
|
||||
|
||||
isGatherable: ->
|
||||
return true if @item.isGatherable
|
||||
return true unless @getRecipes().length > 0
|
||||
return true if @getRecipes().length is 0
|
||||
return false
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
@@ -43,7 +44,7 @@ module.exports = class ItemNode extends CraftingNode
|
||||
|
||||
_createChildren: (result=[])->
|
||||
recipes = @getRecipes()
|
||||
return [] unless recipes.length > 0
|
||||
return [] if @gatherable
|
||||
|
||||
for recipe in recipes
|
||||
child = new RecipeNode modPack:@modPack, recipe:recipe
|
||||
@@ -62,7 +63,9 @@ module.exports = class ItemNode extends CraftingNode
|
||||
return false
|
||||
|
||||
_checkValidity: ->
|
||||
return false if @_isRepeatedItem()
|
||||
return true unless @children.length > 0
|
||||
|
||||
for child in @children
|
||||
return true if child.valid
|
||||
return false
|
||||
@@ -79,3 +82,13 @@ module.exports = class ItemNode extends CraftingNode
|
||||
for child in @children
|
||||
parts.push child.toString indent:nextIndent
|
||||
return parts.join '\n'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_isRepeatedItem: ->
|
||||
nextParent = @parent
|
||||
while nextParent?
|
||||
return true if nextParent.item is @item
|
||||
nextParent = nextParent.parent
|
||||
|
||||
return false
|
||||
|
||||
@@ -9,20 +9,23 @@ CraftingNode = require './crafting_node'
|
||||
CraftingPlan = require './crafting_plan'
|
||||
CraftingStep = require './crafting_step'
|
||||
Inventory = require '../inventory'
|
||||
{Limits} = require '../../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class PlanBuilder
|
||||
|
||||
constructor: (rootNode, options={})->
|
||||
constructor: (rootNode, modPack, options={})->
|
||||
if not rootNode? then throw new Error 'rootNode is required'
|
||||
|
||||
@wanted = options.wanted
|
||||
@want = options.want
|
||||
|
||||
@_choiceNodes = []
|
||||
@_complete = false
|
||||
@_plans = []
|
||||
@_rootNode = rootNode
|
||||
@_choiceNodes = []
|
||||
@_complete = false
|
||||
@_maxPlanCount = Limits.maximumPlanCount
|
||||
@_modPack = modPack
|
||||
@_plans = []
|
||||
@_rootNode = rootNode
|
||||
|
||||
@_isolateChoiceNodes()
|
||||
|
||||
@@ -31,6 +34,9 @@ module.exports = class PlanBuilder
|
||||
producePlans: (maxPlans=null)->
|
||||
maxPlans = if maxPlans then @plans.length + maxPlans else Number.MAX_VALUE
|
||||
|
||||
if @plans.length >= @_maxPlanCount
|
||||
@_complete = true
|
||||
|
||||
while not @complete and (@plans.length < maxPlans)
|
||||
plan = @_captureCurrentPlan()
|
||||
if plan?
|
||||
@@ -47,12 +53,16 @@ module.exports = class PlanBuilder
|
||||
complete:
|
||||
get: -> @_complete
|
||||
|
||||
maxPlanCount:
|
||||
get: -> @_maxPlanCount
|
||||
set: (value)-> @_maxPlanCount = value
|
||||
|
||||
plans:
|
||||
get: -> @_plans
|
||||
|
||||
wanted:
|
||||
get: -> @_wanted
|
||||
set: (wanted)-> @_wanted = wanted or new Inventory
|
||||
want:
|
||||
get: -> @_want
|
||||
set: (want)-> @_want = want or new Inventory
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
@@ -83,9 +93,9 @@ module.exports = class PlanBuilder
|
||||
continue if seenRecipes[recipeSlug]?
|
||||
|
||||
seenRecipes[recipeSlug] = true
|
||||
steps.push new CraftingStep node.recipe
|
||||
steps.push new CraftingStep node.recipe, @_modPack
|
||||
|
||||
plan = new CraftingPlan steps, @_wanted
|
||||
plan = new CraftingPlan steps, @_want, @_modPack
|
||||
return plan
|
||||
|
||||
_incrementChoiceNodes: ->
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
###
|
||||
Crafting Guide - plan_evaluator.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class PlanEvaluator
|
||||
|
||||
@::CRITERIA =
|
||||
FEWEST_STEPS: 'fewest steps'
|
||||
LEAST_MATERIALS: 'least materials'
|
||||
|
||||
constructor: (plans)->
|
||||
if not plans? then throw new Error 'plans is required'
|
||||
|
||||
@_plans = plans
|
||||
@_lastScored = 0
|
||||
@_normalized = false
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
findBestPlan: (mainCriteria)->
|
||||
criteriaList = [mainCriteria].concat (criteria for key, criteria of @CRITERIA when criteria isnt mainCriteria)
|
||||
@_plans.sort (a, b)->
|
||||
for criteria in criteriaList
|
||||
scoreA = a.getScore criteria
|
||||
scoreB = b.getScore criteria
|
||||
|
||||
if scoreA isnt scoreB
|
||||
return if scoreA > scoreB then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
return @_plans[0]
|
||||
|
||||
scorePlans: (count)->
|
||||
count ?= @_plans.length
|
||||
return if @complete
|
||||
|
||||
maxPlanIndex = Math.min @_lastScored + count, @_plans.length - 1
|
||||
for i in [@_lastScored..maxPlanIndex]
|
||||
@_plans[i].computeRequired()
|
||||
@_scorePlan @_plans[i]
|
||||
@_lastScored = i
|
||||
|
||||
if @complete then @_normalizeScores()
|
||||
|
||||
return @complete
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
complete:
|
||||
get: -> @_lastScored is @_plans.length - 1
|
||||
|
||||
lastScored:
|
||||
get: -> @_lastScored
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeFewestStepsScore: (plan)->
|
||||
total = 0
|
||||
for step in plan.steps
|
||||
total += step.multiplier
|
||||
plan.setRawScore @CRITERIA.FEWEST_STEPS, total
|
||||
|
||||
_computeLeastMaterialsScore: (plan)->
|
||||
total = 0
|
||||
plan.need.each (stack)->
|
||||
total += stack.quantity
|
||||
plan.setRawScore @CRITERIA.LEAST_MATERIALS, total
|
||||
|
||||
_normalizeScores: ->
|
||||
for key, criteria of @CRITERIA
|
||||
maxScore = 0
|
||||
minScore = Number.MAX_VALUE
|
||||
|
||||
for plan in @_plans
|
||||
continue unless plan.hasRawScore criteria
|
||||
maxScore = Math.max maxScore, plan.getRawScore(criteria)
|
||||
minScore = Math.min minScore, plan.getRawScore(criteria)
|
||||
|
||||
adjustedMaxScore = maxScore - minScore
|
||||
for plan in @_plans
|
||||
continue unless plan.hasRawScore criteria
|
||||
if adjustedMaxScore > 0
|
||||
adjustedRawScore = plan.getRawScore(criteria) - minScore
|
||||
plan.setScore criteria, 1.0 - (1.0 * adjustedRawScore / adjustedMaxScore)
|
||||
else
|
||||
plan.setScore criteria, 1.0
|
||||
|
||||
_scorePlan: (plan)->
|
||||
@_computeFewestStepsScore plan
|
||||
@_computeLeastMaterialsScore plan
|
||||
@@ -1,72 +0,0 @@
|
||||
###
|
||||
Crafting Guide - crafting_table.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingTable extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.plan? then throw new Error "attributes.plan is required"
|
||||
super attributes, options
|
||||
|
||||
@plan.on 'change', => @reset()
|
||||
@_stepIndex = 0
|
||||
|
||||
Object.defineProperties this, {
|
||||
currentStep: { get:@getCurrentStep }
|
||||
hasNextStep: { get:@hasNextStep }
|
||||
hasPrevStep: { get:@hasPrevStep }
|
||||
hasSteps: { get:@hasSteps }
|
||||
stepCount: { get:@getStepCount }
|
||||
stepIndex: { get:@getStepIndex, set:@setStepIndex }
|
||||
}
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
reset: ->
|
||||
@stepIndex = 0
|
||||
return this
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
hasNextStep: ->
|
||||
return @_stepIndex + 1 < @plan.steps.length
|
||||
|
||||
hasPrevStep: ->
|
||||
return @_stepIndex > 0
|
||||
|
||||
hasSteps: ->
|
||||
return @plan.steps.length > 0
|
||||
|
||||
getCurrentStep: ->
|
||||
return @plan.steps[@_stepIndex]
|
||||
|
||||
getStepIndex: ->
|
||||
return @_stepIndex
|
||||
|
||||
setStepIndex: (newStepIndex)->
|
||||
oldStepIndex = @_stepIndex
|
||||
newStepIndex = Math.max 0, Math.min @plan.steps.length - 1, newStepIndex
|
||||
|
||||
@_stepIndex = newStepIndex
|
||||
|
||||
@trigger Event.change + ':stepIndex', this, oldStepIndex, newStepIndex
|
||||
@trigger Event.change, this
|
||||
|
||||
return this
|
||||
|
||||
getStepCount: ->
|
||||
return 0 unless @plan.steps?
|
||||
return @plan.steps.length
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name} (#{@cid}) { plan:#{@plan}, step:#{@_stepIndex} }"
|
||||
@@ -21,6 +21,9 @@ module.exports = class Inventory extends BaseModel
|
||||
attributes.modPack ?= null
|
||||
@clear()
|
||||
|
||||
if options.clone?
|
||||
@addInventory options.clone
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@Delimiters =
|
||||
@@ -100,8 +103,8 @@ module.exports = class Inventory extends BaseModel
|
||||
|
||||
quantityOf: (itemSlug)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return 0 unless stack?
|
||||
return stack.quantity
|
||||
return stack.quantity if stack
|
||||
return 0
|
||||
|
||||
remove: (itemSlug, quantity=null)->
|
||||
stack = @_stacks[itemSlug]
|
||||
@@ -172,7 +175,6 @@ module.exports = class Inventory extends BaseModel
|
||||
total += stack.quantity
|
||||
return total
|
||||
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isEmpty: { get:@prototype.getIsEmpty }
|
||||
totalQuantity: { get:@prototype.getTotalQuantity }
|
||||
|
||||
@@ -21,15 +21,16 @@ module.exports = class Item extends BaseModel
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.name? then throw new Error 'attributes.name is required'
|
||||
|
||||
attributes.description ?= null
|
||||
attributes.group ?= Item.Group.Other
|
||||
attributes.isGatherable ?= false
|
||||
attributes.modVersion ?= null
|
||||
attributes.officialUrl ?= null
|
||||
attributes.slug ?= ItemSlug.slugify attributes.name
|
||||
attributes.videos ?= []
|
||||
attributes.description ?= null
|
||||
attributes.group ?= Item.Group.Other
|
||||
attributes.ignoreDuringCrafting ?= false
|
||||
attributes.isGatherable ?= false
|
||||
attributes.modVersion ?= null
|
||||
attributes.officialUrl ?= null
|
||||
attributes.slug ?= ItemSlug.slugify attributes.name
|
||||
attributes.videos ?= []
|
||||
|
||||
options.logEvents ?= false
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
@on Event.change + ':modVersion', =>
|
||||
|
||||
@@ -5,11 +5,10 @@ Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
CraftingPlan = require './crafting_plan'
|
||||
_ = require 'underscore'
|
||||
{Event} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
_ = require 'underscore'
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -57,4 +56,4 @@ module.exports = class ItemPage extends BaseModel
|
||||
result = _.values result
|
||||
return null unless result.length > 0
|
||||
|
||||
return result.sort (a, b)-> a.compareTo b
|
||||
return result.sort (a, b)-> a.compareTo b
|
||||
|
||||
@@ -20,24 +20,35 @@ module.exports = class ModPack extends BaseModel
|
||||
super attributes, options
|
||||
|
||||
@_mods = []
|
||||
@_cache = {}
|
||||
|
||||
@on Event.change, => @_cache = {}
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
findItem: (itemSlug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
key = "#{itemSlug}-#{options.includeDisabled}"
|
||||
@_cache.itemBySlug ?= {}
|
||||
item = @_cache.itemBySlug[key]
|
||||
return item if item?
|
||||
|
||||
if itemSlug.isQualified
|
||||
mod = @getMod itemSlug.mod
|
||||
if mod?
|
||||
item = mod.findItem itemSlug, options
|
||||
return item if item?
|
||||
|
||||
for mod in @_mods
|
||||
continue unless mod.enabled or options.includeDisabled
|
||||
item = mod.findItem itemSlug, options
|
||||
return item if item?
|
||||
if not item?
|
||||
for mod in @_mods
|
||||
continue unless mod.enabled or options.includeDisabled
|
||||
item = mod.findItem itemSlug, options
|
||||
break if item?
|
||||
|
||||
return null
|
||||
if item?
|
||||
@_cache.itemBySlug[key] = item
|
||||
|
||||
return item
|
||||
|
||||
findItemByName: (name, options={})->
|
||||
options.enableAsNeeded ?= false
|
||||
@@ -82,10 +93,16 @@ module.exports = class ModPack extends BaseModel
|
||||
|
||||
return null
|
||||
|
||||
findRecipes: (itemSlug, result=[], options={})->
|
||||
findRecipes: (itemSlug, options={})->
|
||||
options.alwaysFromOwningMod ?= false
|
||||
return null unless itemSlug?
|
||||
|
||||
key = "#{itemSlug}-#{options.alwaysFromOwningMod}"
|
||||
@_cache.recipesBySlug ?= {}
|
||||
result = @_cache.recipesBySlug[key]
|
||||
return result if result?
|
||||
|
||||
result = []
|
||||
for mod in @_mods
|
||||
if not mod.enabled
|
||||
owningMod = itemSlug.isQualified and (itemSlug.mod is mod.slug)
|
||||
@@ -93,8 +110,16 @@ module.exports = class ModPack extends BaseModel
|
||||
|
||||
mod.findRecipes itemSlug, result, options
|
||||
|
||||
@_cache.recipesBySlug[key] = result
|
||||
return if result.length > 0 then result else null
|
||||
|
||||
qualifySlug: (itemSlug)->
|
||||
return itemSlug if itemSlug.isQualified
|
||||
|
||||
item = @findItem itemSlug
|
||||
return item.slug if item?
|
||||
return itemSlug
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
addMod: (mod)->
|
||||
|
||||
@@ -63,6 +63,13 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
|
||||
|
||||
@_recipeData = null
|
||||
|
||||
_command_ignoreDuringCrafting: (value)->
|
||||
if not @_itemData? then throw new Error 'cannot declare "ignoreDuringCraft" before "item"'
|
||||
if @_itemData.ignoreDuringCrafting? then throw new Error 'duplicate declaration of "ignoreDuringCraft"'
|
||||
if not (value in ['yes', 'no']) then throw new Error 'ignoreDuringCraft must be either "yes" or "no"'
|
||||
|
||||
@_itemData.ignoreDuringCrafting = (value is 'yes')
|
||||
|
||||
_command_input: (inputNames...)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
|
||||
if @_recipeData.input.length isnt 0 then throw new Error 'duplicate declaration of "input"'
|
||||
@@ -148,11 +155,16 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
|
||||
|
||||
_buildItem: (modVersion, itemData)->
|
||||
@_lineNumber = itemData.line
|
||||
itemData.gatherable ?= false
|
||||
itemData.recipes ?= []
|
||||
itemData.gatherable ?= false
|
||||
itemData.ignoreDuringCrafting ?= false
|
||||
itemData.recipes ?= []
|
||||
|
||||
if itemData.type is 'new'
|
||||
item = new Item name:itemData.name, isGatherable:itemData.gatherable, group:itemData.group
|
||||
item = new Item
|
||||
name: itemData.name,
|
||||
ignoreDuringCrafting: itemData.ignoreDuringCrafting,
|
||||
isGatherable: itemData.gatherable,
|
||||
group: itemData.group
|
||||
modVersion.addItem item
|
||||
itemData.slug = item.slug
|
||||
else
|
||||
|
||||
@@ -35,7 +35,10 @@ module.exports = class Recipe extends BaseModel
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
@_computeQuantities attributes.pattern
|
||||
|
||||
@on Event.change + ':modVersion', => @_slug = null
|
||||
@on Event.change + ':pattern', => @_patternCache = null
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@@ -77,24 +80,22 @@ module.exports = class Recipe extends BaseModel
|
||||
|
||||
getQuantityRequired: (itemSlug)->
|
||||
total = 0
|
||||
for i in [0...@pattern.length]
|
||||
stack = @getStackAtSlot i
|
||||
continue unless stack?
|
||||
|
||||
for stack, index in @input
|
||||
if ItemSlug.equal stack.itemSlug, itemSlug
|
||||
total += stack.quantity
|
||||
total += @_quantities[index] * stack.quantity
|
||||
|
||||
return total
|
||||
|
||||
hasAllTools: (modPack)->
|
||||
modPack ?= @modVersion?.mod?.modPack
|
||||
return true unless modPack
|
||||
return true unless modPack?
|
||||
|
||||
for stack in @tools
|
||||
return false unless modPack.findItem stack.itemSlug
|
||||
return true
|
||||
|
||||
isConditionSatisfied: (modPack)->
|
||||
return true unless @condition
|
||||
return true unless @condition?
|
||||
modPack ?= @modVersion?.mod?.modPack
|
||||
|
||||
result = false
|
||||
@@ -145,36 +146,38 @@ module.exports = class Recipe extends BaseModel
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getSlug: ->
|
||||
if not @_slug?
|
||||
builder = new StringBuilder
|
||||
delimiterNeeded = false
|
||||
for stack in @input
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
|
||||
builder.push '>'
|
||||
for stack in @tools
|
||||
builder.push stack.itemSlug.qualified
|
||||
builder.push '>'
|
||||
|
||||
delimiterNeeded = false
|
||||
for stack in @output
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
|
||||
@_slug = builder.toString()
|
||||
|
||||
return @_slug
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
slug: {get:@prototype.getSlug}
|
||||
|
||||
slug:
|
||||
get: ->
|
||||
if not @_slug?
|
||||
builder = new StringBuilder
|
||||
delimiterNeeded = false
|
||||
for stack in @input
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
|
||||
builder.push '>'
|
||||
builder.push @pattern
|
||||
builder.push '>'
|
||||
for stack in @tools
|
||||
builder.push stack.itemSlug.qualified
|
||||
builder.push '>'
|
||||
|
||||
delimiterNeeded = false
|
||||
for stack in @output
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
|
||||
@_slug = builder.toString()
|
||||
|
||||
return @_slug
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
@@ -215,6 +218,22 @@ module.exports = class Recipe extends BaseModel
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeQuantities: (pattern)->
|
||||
quantityMap = {}
|
||||
|
||||
for c in pattern.split ''
|
||||
continue if c is '.'
|
||||
continue if c is ' '
|
||||
|
||||
if quantityMap[c]?
|
||||
quantityMap[c] += 1
|
||||
else
|
||||
quantityMap[c] = 1
|
||||
|
||||
@_quantities = []
|
||||
for i in [0...@input.length]
|
||||
@_quantities.push quantityMap["#{i}"]
|
||||
|
||||
_parsePattern: (pattern)->
|
||||
return unless pattern?
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
###
|
||||
Crafting Guide - recipe_step.coffee
|
||||
|
||||
Copyright (c) 2014 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class RecipeStep extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.recipe? then throw new Error 'attributes.recipe is required'
|
||||
attributes.multiple ?= 1
|
||||
super attributes, options
|
||||
|
||||
@recipe.on 'change', =>
|
||||
@trigger 'change:recipe'
|
||||
@trigger 'change'
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getItemAt: (index)->
|
||||
return @recipe.getItemAt index
|
||||
@@ -0,0 +1,200 @@
|
||||
###
|
||||
Crafting Guide - inventory.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
ItemSlug = require './item_slug'
|
||||
SimpleStack = require './simple_stack'
|
||||
_ = require 'underscore'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class SimpleInventory
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
@clear()
|
||||
|
||||
if options.modPack?
|
||||
@modPack = options.modPack
|
||||
|
||||
if options.clone?
|
||||
@addInventory options.clone
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@Delimiters =
|
||||
Item: '.'
|
||||
Stack: ':'
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (itemSlug, quantity=1)->
|
||||
return this unless quantity > 0
|
||||
|
||||
@_add itemSlug, quantity
|
||||
return this
|
||||
|
||||
addInventory: (inventory)->
|
||||
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
|
||||
return this
|
||||
|
||||
clear: (options={})->
|
||||
@_stacks = {}
|
||||
@_itemSlugs = []
|
||||
|
||||
clone: ->
|
||||
inventory = new Inventory
|
||||
inventory.addInventory this
|
||||
return inventory
|
||||
|
||||
each: (callback)->
|
||||
for itemSlug in @_itemSlugs
|
||||
callback @_stacks[itemSlug]
|
||||
|
||||
getSlugs: ->
|
||||
return @_itemSlugs[..]
|
||||
|
||||
hasAtLeast: (itemSlug, quantity=1)->
|
||||
if quantity is 0 then return true
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
return false unless stack?
|
||||
return stack.quantity >= quantity
|
||||
|
||||
localize: ->
|
||||
if not @modPack? then throw new Error 'localize requires @modPack'
|
||||
|
||||
newSlugs = []
|
||||
for itemSlug in @_itemSlugs
|
||||
stack = @_stacks[itemSlug]
|
||||
|
||||
qualifiedSlug = @modPack.findItem(itemSlug)?.slug
|
||||
if qualifiedSlug?
|
||||
delete @_stacks[itemSlug]
|
||||
newSlugs.push qualifiedSlug
|
||||
@_stacks[qualifiedSlug] = stack
|
||||
stack.itemSlug = qualifiedSlug
|
||||
else
|
||||
newSlugs.push itemSlug
|
||||
|
||||
@_itemSlugs = newSlugs
|
||||
@_sort()
|
||||
|
||||
pop: ->
|
||||
itemSlug = @_itemSlugs.pop()
|
||||
return null unless itemSlug?
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
delete @_stacks[itemSlug]
|
||||
|
||||
return stack
|
||||
|
||||
quantityOf: (itemSlug)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return stack.quantity if stack
|
||||
return 0
|
||||
|
||||
remove: (itemSlug, quantity=null)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return this unless stack?
|
||||
|
||||
quantity ?= stack.quantity
|
||||
return this unless quantity > 0
|
||||
|
||||
if stack.quantity < quantity
|
||||
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{itemSlug} in this inventory"
|
||||
|
||||
stack.quantity -= quantity
|
||||
if stack.quantity is 0
|
||||
delete @_stacks[itemSlug]
|
||||
@_itemSlugs = (s for s in @_itemSlugs when not ItemSlug.equal(s, itemSlug))
|
||||
|
||||
return this
|
||||
|
||||
# Parsing Methods ##############################################################################
|
||||
|
||||
parse: (data)->
|
||||
return this if not data? or data.length is 0
|
||||
|
||||
stacks = data.split Inventory.Delimiters.Stack
|
||||
for stackText in stacks
|
||||
stackParts = stackText.split Inventory.Delimiters.Item
|
||||
if stackParts.length is 2
|
||||
quantity = parseInt stackParts[0], 10
|
||||
itemSlug = ItemSlug.slugify stackParts[1]
|
||||
else if stackParts.length is 1
|
||||
quantity = 1
|
||||
itemSlug = ItemSlug.slugify stackParts[0]
|
||||
else
|
||||
throw new Error "expected #{stackText} to have 0 or 1 parts"
|
||||
|
||||
if itemSlug.qualified.length > 0
|
||||
@add itemSlug, quantity
|
||||
|
||||
return this
|
||||
|
||||
unparse: (options={})->
|
||||
parts = []
|
||||
@each (stack)=>
|
||||
slugText = stack.itemSlug.item
|
||||
if @modPack?
|
||||
item = @modPack.findItem ItemSlug.slugify slugText
|
||||
if item? and item.slug.qualified isnt stack.itemSlug.qualified
|
||||
slugText = stack.itemSlug.qualified
|
||||
|
||||
if stack.quantity is 1
|
||||
parts.push slugText
|
||||
else
|
||||
parts.push "#{stack.quantity}#{SimpleInventory.Delimiters.Item}#{slugText}"
|
||||
|
||||
return parts.join SimpleInventory.Delimiters.Stack
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getIsEmpty: ->
|
||||
return @_itemSlugs.length is 0
|
||||
|
||||
getTotalQuantity: ->
|
||||
total = 0
|
||||
@each (stack)->
|
||||
total += stack.quantity
|
||||
return total
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isEmpty: { get:@prototype.getIsEmpty }
|
||||
totalQuantity: { get:@prototype.getTotalQuantity }
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") {items: ["]
|
||||
|
||||
needsDelimiter = false
|
||||
@each (stack)->
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push stack.toString()
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push '}'
|
||||
return result.join ''
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_add: (itemSlug, quantity=1)->
|
||||
return unless itemSlug?
|
||||
return unless quantity > 0
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
if not stack?
|
||||
stack = new SimpleStack itemSlug:itemSlug, quantity:quantity
|
||||
@_stacks[itemSlug] = stack
|
||||
@_itemSlugs.push itemSlug
|
||||
@_sort()
|
||||
else
|
||||
stack.quantity += quantity
|
||||
|
||||
_sort: ->
|
||||
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
|
||||
@@ -1,35 +0,0 @@
|
||||
###
|
||||
Crafting Guide - step.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
Inventory = require './inventory'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Step extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.outputItemSlug? then throw new Error 'attributes.outputItemSlug is required'
|
||||
if not attributes.recipe? then throw new Error 'attributes.recipe is required'
|
||||
|
||||
attributes.inventory = new Inventory
|
||||
attributes.number ?= 1
|
||||
attributes.outputItemSlug ?= null
|
||||
attributes.multiplier ?= 1
|
||||
attributes.recipe ?= null
|
||||
super attributes, options
|
||||
|
||||
@_computeInventory()
|
||||
@on Event.change + ':multiplier', => @_computeInventory()
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeInventory: ->
|
||||
@inventory.clear()
|
||||
for stack in @recipe.input
|
||||
@inventory.add stack.itemSlug, stack.quantity * @multiplier
|
||||
@@ -18,44 +18,49 @@ describe 'crafting_plan.coffee', ->
|
||||
plans.length.should.equal 1
|
||||
|
||||
plan = plans[0]
|
||||
plan.required.unparse().should.equal 'coal'
|
||||
plan.produced.unparse().should.equal 'coal'
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal 'coal'
|
||||
plan.made.unparse().should.equal 'coal'
|
||||
|
||||
it 'can compute a single item with one single step plan', ->
|
||||
plans = fixtures.makePlans [1, 'test__charcoal']
|
||||
plans.length.should.equal 1
|
||||
|
||||
plan = plans[0]
|
||||
plan.required.unparse().should.equal 'coal:8.oak_wood'
|
||||
plan.produced.unparse().should.equal '8.charcoal'
|
||||
(s.toString() for s in plan.steps).should.eql ['1x 8 test__oak_wood,test__coal>>8 test__charcoal']
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal 'coal:8.oak_wood'
|
||||
plan.made.unparse().should.equal '8.charcoal'
|
||||
(s.toString() for s in plan.steps).should.eql ['1x 8 test__oak_wood,test__coal>.0. ... .1.>>8 test__charcoal']
|
||||
|
||||
it 'can compute a large quantity of a single item with one single step plan', ->
|
||||
plans = fixtures.makePlans [15, 'test__charcoal']
|
||||
plans.length.should.equal 1
|
||||
|
||||
plan = plans[0]
|
||||
plan.required.unparse().should.equal '2.coal:16.oak_wood'
|
||||
plan.produced.unparse().should.equal '16.charcoal'
|
||||
(s.toString() for s in plan.steps).should.eql ['2x 8 test__oak_wood,test__coal>>8 test__charcoal']
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal '2.coal:16.oak_wood'
|
||||
plan.made.unparse().should.equal '16.charcoal'
|
||||
(s.toString() for s in plan.steps).should.eql ['2x 8 test__oak_wood,test__coal>.0. ... .1.>>8 test__charcoal']
|
||||
|
||||
it 'can compute a single item with multiple plans', ->
|
||||
plans = fixtures.makePlans [1, 'test__iron_ingot']
|
||||
plans.length.should.equal 2
|
||||
|
||||
plan = plans[0]
|
||||
plan.required.unparse().should.equal 'coal:8.iron_ore:8.oak_wood'
|
||||
plan.produced.unparse().should.equal '7.charcoal:8.iron_ingot'
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal 'coal:8.iron_ore:8.oak_wood'
|
||||
plan.made.unparse().should.equal '7.charcoal:8.iron_ingot'
|
||||
(s.toString() for s in plan.steps).should.eql [
|
||||
'1x 8 test__oak_wood,test__coal>>8 test__charcoal'
|
||||
'1x 8 test__iron_ore,test__charcoal>test__furnace>8 test__iron_ingot'
|
||||
'1x 8 test__oak_wood,test__coal>.0. ... .1.>>8 test__charcoal'
|
||||
'1x 8 test__iron_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
]
|
||||
|
||||
plan = plans[1]
|
||||
plan.required.unparse().should.equal 'coal:8.iron_ore'
|
||||
plan.produced.unparse().should.equal '8.iron_ingot'
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.equal 'coal:8.iron_ore'
|
||||
plan.made.unparse().should.equal '8.iron_ingot'
|
||||
(s.toString() for s in plan.steps).should.eql [
|
||||
'1x 8 test__iron_ore,test__coal>test__furnace>8 test__iron_ingot'
|
||||
'1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
]
|
||||
|
||||
it 'can compute multiple items with multiple plans', ->
|
||||
@@ -63,33 +68,40 @@ describe 'crafting_plan.coffee', ->
|
||||
plans.length.should.equal 4
|
||||
|
||||
for plan in plans
|
||||
plan.required.unparse().should.match /16.copper_ore.*8.iron_ore/
|
||||
plan.produced.unparse().should.match /copper_block.*:iron_sword/
|
||||
plan.computeRequired()
|
||||
plan.need.unparse().should.match /16.copper_ore.*8.iron_ore/
|
||||
plan.made.unparse().should.match /copper_block.*:iron_sword/
|
||||
|
||||
plan = plans[0]
|
||||
plan.required.unparse().should.match /3.coal.*9.oak_wood/
|
||||
plan.produced.unparse().should.match /7.charcoal/
|
||||
"#{plan.steps[3]}".should.equal '1x 8 test__iron_ore,test__charcoal>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[4]}".should.equal '2x 8 test__copper_ore,test__coal>test__furnace>8 test__copper_ingot'
|
||||
plan.need.unparse().should.match /3.coal.*9.oak_wood/
|
||||
plan.made.unparse().should.match /7.charcoal/
|
||||
"#{plan.steps[3]}".should.equal \
|
||||
'1x 8 test__iron_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[4]}".should.equal \
|
||||
'2x 8 test__copper_ore,test__coal>.0. ... .1.>test__furnace>8 test__copper_ingot'
|
||||
plan.steps.length.should.equal 7
|
||||
|
||||
plan = plans[1]
|
||||
plan.required.unparse().should.match /3.coal.*:oak_wood/
|
||||
plan.produced.unparse().should.not.match /charcoal/
|
||||
"#{plan.steps[2]}".should.equal '1x 8 test__iron_ore,test__coal>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[3]}".should.equal '2x 8 test__copper_ore,test__coal>test__furnace>8 test__copper_ingot'
|
||||
plan.need.unparse().should.match /3.coal.*:oak_wood/
|
||||
plan.made.unparse().should.not.match /charcoal/
|
||||
"#{plan.steps[2]}".should.equal '1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[3]}".should.equal \
|
||||
'2x 8 test__copper_ore,test__coal>.0. ... .1.>test__furnace>8 test__copper_ingot'
|
||||
plan.steps.length.should.equal 6
|
||||
|
||||
plan = plans[2]
|
||||
plan.required.unparse().should.match /^coal.*9.oak_wood/
|
||||
plan.produced.unparse().should.match /5.charcoal/
|
||||
"#{plan.steps[3]}".should.equal '1x 8 test__iron_ore,test__charcoal>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[4]}".should.equal '2x 8 test__copper_ore,test__charcoal>test__furnace>8 test__copper_ingot'
|
||||
plan.need.unparse().should.match /^coal.*9.oak_wood/
|
||||
plan.made.unparse().should.match /5.charcoal/
|
||||
"#{plan.steps[3]}".should.equal \
|
||||
'1x 8 test__iron_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[4]}".should.equal \
|
||||
'2x 8 test__copper_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__copper_ingot'
|
||||
plan.steps.length.should.equal 7
|
||||
|
||||
plan = plans[3]
|
||||
plan.required.unparse().should.match /2.coal.*9.oak_wood/
|
||||
plan.produced.unparse().should.match /6.charcoal/
|
||||
"#{plan.steps[3]}".should.equal '1x 8 test__iron_ore,test__coal>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[4]}".should.equal '2x 8 test__copper_ore,test__charcoal>test__furnace>8 test__copper_ingot'
|
||||
plan.need.unparse().should.match /2.coal.*9.oak_wood/
|
||||
plan.made.unparse().should.match /6.charcoal/
|
||||
"#{plan.steps[3]}".should.equal '1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
|
||||
"#{plan.steps[4]}".should.equal \
|
||||
'2x 8 test__copper_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__copper_ingot'
|
||||
plan.steps.length.should.equal 7
|
||||
|
||||
@@ -6,6 +6,7 @@ All rights reserved.
|
||||
###
|
||||
|
||||
GraphBuilder = require '../../src/coffee/models/crafting/graph_builder'
|
||||
Inventory = require '../../src/coffee/models/inventory'
|
||||
ItemSlug = require '../../src/coffee/models/item_slug'
|
||||
Mod = require '../../src/coffee/models/mod'
|
||||
ModPack = require '../../src/coffee/models/mod_pack'
|
||||
@@ -30,8 +31,10 @@ MOD_VERSION_FILE =
|
||||
pattern: .00 .00 ...
|
||||
|
||||
item: Coal
|
||||
gatherable: yes
|
||||
|
||||
item: Cobblestone
|
||||
gatherable: yes
|
||||
|
||||
item: Copper Block
|
||||
recipe:
|
||||
@@ -56,6 +59,7 @@ MOD_VERSION_FILE =
|
||||
quantity: 9
|
||||
|
||||
item: Copper Ore
|
||||
gatherable: yes
|
||||
|
||||
item: Furnace
|
||||
recipe:
|
||||
@@ -64,6 +68,7 @@ MOD_VERSION_FILE =
|
||||
tools: Crafting Table
|
||||
|
||||
item: Iron Ore
|
||||
gatherable: yes
|
||||
|
||||
item: Iron Ingot
|
||||
recipe:
|
||||
@@ -95,12 +100,22 @@ MOD_VERSION_FILE =
|
||||
quantity: 4
|
||||
|
||||
item: Oak Wood
|
||||
gatherable: yes
|
||||
|
||||
item: Stick
|
||||
recipe:
|
||||
input: Oak Planks
|
||||
pattern: .0. .0. ...
|
||||
quantity: 4
|
||||
|
||||
item: String
|
||||
gatherable: yes
|
||||
|
||||
item: Wool
|
||||
gatherable: yes
|
||||
recipe:
|
||||
input: String
|
||||
pattern: 00. 00. ...
|
||||
"""
|
||||
|
||||
########################################################################################################################
|
||||
@@ -108,7 +123,7 @@ MOD_VERSION_FILE =
|
||||
module.exports = fixtures =
|
||||
|
||||
makeGraphBuilder: ->
|
||||
return new GraphBuilder modPack:fixtures.makeModPack()
|
||||
return new GraphBuilder modPack:fixtures.makeModPack(), want:new Inventory
|
||||
|
||||
makeModPack: ->
|
||||
modPack = new ModPack
|
||||
@@ -125,17 +140,21 @@ module.exports = fixtures =
|
||||
makePlans: (stacks...)->
|
||||
modPack = fixtures.makeModPack()
|
||||
|
||||
graphBuilder = fixtures.makeGraphBuilder()
|
||||
want = new Inventory
|
||||
for stack in stacks
|
||||
graphBuilder.wanted.add ItemSlug.slugify(stack[1]), stack[0]
|
||||
want.add ItemSlug.slugify(stack[1]), stack[0]
|
||||
|
||||
graphBuilder = new GraphBuilder modPack:fixtures.makeModPack(), want:want
|
||||
graphBuilder.expandGraph()
|
||||
|
||||
planBuilder = new PlanBuilder graphBuilder.rootNode, wanted:graphBuilder.wanted
|
||||
planBuilder = new PlanBuilder graphBuilder.rootNode, modPack, want:graphBuilder.want
|
||||
return planBuilder.producePlans()
|
||||
|
||||
makeTree: (itemSlug, quantity=1)->
|
||||
builder = fixtures.makeGraphBuilder()
|
||||
builder.wanted.add ItemSlug.slugify itemSlug
|
||||
want = new Inventory
|
||||
want.add ItemSlug.slugify itemSlug
|
||||
|
||||
builder = new GraphBuilder modPack:fixtures.makeModPack(), want:want
|
||||
builder.expandGraph()
|
||||
|
||||
return builder.rootNode
|
||||
|
||||
@@ -22,7 +22,7 @@ describe 'GraphBuilder.coffee', ->
|
||||
describe 'expand', ->
|
||||
|
||||
it 'can work a few steps at a time', ->
|
||||
builder.wanted.add ItemSlug.slugify 'test__iron_sword'
|
||||
builder.want.add ItemSlug.slugify 'test__iron_sword'
|
||||
builder.expandGraph 9
|
||||
|
||||
builder.rootNode.depth.should.equal 6
|
||||
@@ -45,7 +45,7 @@ describe 'GraphBuilder.coffee', ->
|
||||
describe 'can build a tree for', ->
|
||||
|
||||
runSingleItemTreeBuildingTest = (slug, depth, size)->
|
||||
builder.wanted.add ItemSlug.slugify slug
|
||||
builder.want.add ItemSlug.slugify slug
|
||||
builder.expandGraph 100
|
||||
|
||||
builder.rootNode.depth.should.equal depth
|
||||
@@ -68,4 +68,7 @@ describe 'GraphBuilder.coffee', ->
|
||||
runSingleItemTreeBuildingTest 'test__iron_sword', 8, 18
|
||||
|
||||
it 'an item with one recursive recipe', ->
|
||||
runSingleItemTreeBuildingTest 'test__copper_ingot', 10, 24
|
||||
runSingleItemTreeBuildingTest 'test__copper_ingot', 6, 15
|
||||
|
||||
it 'an item which gatherable and craftable', ->
|
||||
runSingleItemTreeBuildingTest 'test__wool', 2, 2
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
###
|
||||
Crafting Guide - plan_evaluator.test.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
CraftingPlan = require '../../src/coffee/models/crafting/crafting_plan'
|
||||
fixtures = require './fixtures'
|
||||
Inventory = require '../../src/coffee/models/inventory'
|
||||
PlanEvaluator = require '../../src/coffee/models/crafting/plan_evaluator'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
criteria = evaluator = planA = planB = planC = plans = wanted = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'plan_evaluator.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
criteria = PlanEvaluator::CRITERIA.FEWEST_STEPS
|
||||
|
||||
describe 'findBestPlan', ->
|
||||
|
||||
it 'can find the shortest of two options', ->
|
||||
evaluator = new PlanEvaluator fixtures.makePlans [1, 'test__copper_block']
|
||||
evaluator.scorePlans()
|
||||
|
||||
bestPlan = evaluator.findBestPlan criteria
|
||||
bestPlan.getScore(criteria).should.equal 1.0
|
||||
|
||||
it 'can find the shortest of many options', ->
|
||||
evaluator = new PlanEvaluator fixtures.makePlans [1, 'test__copper_block'], [1, 'test__iron_sword']
|
||||
evaluator.scorePlans()
|
||||
|
||||
bestPlan = evaluator.findBestPlan criteria
|
||||
bestPlan.getScore(criteria).should.equal 1.0
|
||||
|
||||
it 'can break ties using the non-primary criteria', ->
|
||||
evaluator = new PlanEvaluator fixtures.makePlans [2, 'test__iron_ingot'], [1, 'test__charcoal']
|
||||
evaluator.scorePlans()
|
||||
|
||||
bestPlan = evaluator.findBestPlan criteria
|
||||
bestPlan.getScore(criteria).should.equal 1.0
|
||||
|
||||
(p.getRawScore('fewest steps') for p in evaluator._plans).should.eql [2, 2]
|
||||
(p.getRawScore('least materials') for p in evaluator._plans).should.eql [17, 18]
|
||||
|
||||
describe '_normalizeScores', ->
|
||||
|
||||
beforeEach ->
|
||||
modPack = fixtures.makeModPack()
|
||||
wanted = new Inventory modPack:modPack
|
||||
planA = new CraftingPlan [], wanted, modPack
|
||||
planB = new CraftingPlan [], wanted, modPack
|
||||
planC = new CraftingPlan [], wanted, modPack
|
||||
plans = [planA, planB, planC]
|
||||
evaluator = new PlanEvaluator plans
|
||||
criteria = PlanEvaluator::CRITERIA.FEWEST_STEPS
|
||||
|
||||
it 'does not set scores when none have been evaluated', ->
|
||||
evaluator._normalizeScores()
|
||||
(plan.hasRawScore(criteria) for plan in plans).should.eql [false, false, false]
|
||||
|
||||
it 'assigns a 1.0 when all plans equal', ->
|
||||
for plan in plans
|
||||
plan.setRawScore criteria, 5
|
||||
|
||||
evaluator._normalizeScores()
|
||||
(plan.getScore(criteria) for plan in plans).should.eql [1.0, 1.0, 1.0]
|
||||
|
||||
it 'computes correct scores from raw scores', ->
|
||||
planA.setRawScore criteria, 7
|
||||
planB.setRawScore criteria, 8
|
||||
planC.setRawScore criteria, 9
|
||||
|
||||
evaluator._normalizeScores()
|
||||
(plan.getScore(criteria) for plan in plans).should.eql [1.0, 0.5, 0.0]
|
||||
@@ -97,5 +97,5 @@ describe 'mod_version.coffee', ->
|
||||
(r.output[0].itemSlug.item for r in recipes).sort().should.eql ['bucket', 'cake', 'cake']
|
||||
|
||||
it 'skip recipes whose conditions are not met', ->
|
||||
recipes = modVersion.findRecipes ItemSlug.slugify 'test__cake'
|
||||
recipes = modVersion.findRecipes ItemSlug.slugify('test__cake')
|
||||
recipes.length.should.equal 2
|
||||
|
||||
Reference in New Issue
Block a user