Complete basic crafting functionality
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
###
|
||||
# Crafting Guide - crafting_plan.coffee
|
||||
#
|
||||
# Copyright (c) 2014 by Redwood Labs
|
||||
# All rights reserved.
|
||||
###
|
||||
|
||||
Inventory = require './inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingPlan
|
||||
|
||||
constructor: (@catalog, includingTools)->
|
||||
if not @catalog then throw new Error 'catalog is required'
|
||||
@includingTools = if includingTools then true else false
|
||||
@clear()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
clear: ->
|
||||
@make = new Inventory
|
||||
@need = new Inventory
|
||||
@result = new Inventory
|
||||
@steps = []
|
||||
@_expected = new Inventory
|
||||
@_pending = null
|
||||
|
||||
return this
|
||||
|
||||
craft: (name, quantity=1, have=null)->
|
||||
@clear()
|
||||
@result.addInventory(have) if have?
|
||||
|
||||
@_expected.add name, quantity
|
||||
@_pending = @_expected.clone()
|
||||
while not @_pending.isEmpty
|
||||
@_processPending()
|
||||
|
||||
@steps.reverse()
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name} { need:#{@need}, make:#{@make}, result:#{@result} }"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_processPending: ->
|
||||
targetItem = @_pending.pop()
|
||||
return unless targetItem?
|
||||
|
||||
recipes = @catalog.findRecipes targetItem.name
|
||||
return if not recipes.length > 0
|
||||
recipe = recipes[0]
|
||||
|
||||
if @includingTools
|
||||
for tool in recipe.tools
|
||||
totalExpected = @result.quantityOf(tool.name) + @_expected.quantityOf(tool.name)
|
||||
if totalExpected < tool.quantity
|
||||
@_pending.add tool.name, tool.quantity
|
||||
@_expected.add tool.name, tool.quantity
|
||||
|
||||
while @_totalQuantityOf(targetItem.name) < @_expected.quantityOf(targetItem.name)
|
||||
@steps.push recipe
|
||||
|
||||
for item in recipe.input
|
||||
@_processInputItem item
|
||||
|
||||
for item in recipe.output
|
||||
@_processOutputItem item
|
||||
|
||||
_processInputItem: (item)->
|
||||
quantityAvailable = @result.quantityOf item.name
|
||||
quantityUsed = Math.min quantityAvailable, item.quantity
|
||||
quantityNeeded = item.quantity - quantityUsed
|
||||
|
||||
@result.remove item.name, quantityUsed
|
||||
@_pending.add item.name, quantityNeeded
|
||||
@need.add item.name, quantityNeeded
|
||||
|
||||
_processOutputItem: (item)->
|
||||
quantityMissing = @need.quantityOf item.name
|
||||
quantityUsed = Math.min quantityMissing, item.quantity
|
||||
quantityLeft = item.quantity - quantityUsed
|
||||
|
||||
@need.remove item.name, quantityUsed
|
||||
@result.add item.name, quantityLeft
|
||||
@make.add item.name, item.quantity
|
||||
|
||||
_totalQuantityOf: (name)->
|
||||
return @result.quantityOf(name) - @need.quantityOf(name)
|
||||
@@ -5,8 +5,10 @@
|
||||
# All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
Inventory = require './inventory'
|
||||
BaseModel = require './base_model'
|
||||
CraftingPlan = require './crafting_plan'
|
||||
{Event} = require '../constants'
|
||||
Inventory = require './inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -14,14 +16,36 @@ module.exports = class CraftingTable extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.catalog? then throw new Error "attributes.catalog is required"
|
||||
attributes.name ?= null
|
||||
attributes.quantity ?= 1
|
||||
attributes.includingTools ?= false
|
||||
attributes.have ?= new Inventory
|
||||
attributes.plan ?= null
|
||||
super attributes, options
|
||||
|
||||
@have = new Inventory
|
||||
@make = new Inventory
|
||||
@get = new Inventory
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
craft: (name, quantity=1, includingTools=false)->
|
||||
toolPhrase = if includingTools then ' includingTools' else ''
|
||||
logger.verbose "calculating build plan for #{quantity} #{name}#{toolPhrase} with inventory: #{@have}"
|
||||
craft: ->
|
||||
if not @name?.length
|
||||
@plan = null
|
||||
return
|
||||
|
||||
toolPhrase = if @includingTools then ' includingTools' else ''
|
||||
logger.verbose "calculating build plan for #{@quantity} #{@name}#{toolPhrase} with inventory: #{@have}"
|
||||
|
||||
plan = new CraftingPlan @catalog, @includingTools
|
||||
plan.includingTools = @includingTools
|
||||
plan.craft @name, @quantity, @have
|
||||
|
||||
@plan = plan
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name} (#{@cid}) {
|
||||
name:#{@name},
|
||||
quantity:#{@quantity},
|
||||
includingTools:#{@includingTools},
|
||||
have:#{@have},
|
||||
plan:#{@plan}
|
||||
}"
|
||||
|
||||
@@ -17,14 +17,19 @@ module.exports = class Inventory extends BaseModel
|
||||
super attributes, options
|
||||
@clear()
|
||||
|
||||
Object.defineProperty this, 'isEmpty', get:-> @_names.length is 0
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (name, quantity=1)->
|
||||
return if quantity is 0
|
||||
|
||||
item = @_items[name]
|
||||
if not item?
|
||||
item = new Item name:name, quantity:quantity
|
||||
@_items[name] = item
|
||||
@_names.push name
|
||||
@_names.sort()
|
||||
else
|
||||
item.quantity += quantity
|
||||
|
||||
@@ -32,10 +37,19 @@ module.exports = class Inventory extends BaseModel
|
||||
@trigger Event.change, this
|
||||
return this
|
||||
|
||||
addInventory: (inventory)->
|
||||
inventory.each (item)=> @add item.name, item.quantity
|
||||
return this
|
||||
|
||||
clear: ->
|
||||
@_items = {}
|
||||
@_names = []
|
||||
|
||||
clone: ->
|
||||
inventory = new Inventory
|
||||
@each (item)-> inventory.add item.name, item.quantity
|
||||
return inventory
|
||||
|
||||
each: (onItem)->
|
||||
for name in @_names
|
||||
item = @_items[name]
|
||||
@@ -48,7 +62,25 @@ module.exports = class Inventory extends BaseModel
|
||||
return false unless item?
|
||||
return item.quantity >= quantity
|
||||
|
||||
pop: ->
|
||||
name = @_names.pop()
|
||||
return null unless name?
|
||||
|
||||
item = @_items[name]
|
||||
delete @_items[name]
|
||||
|
||||
@trigger Event.remove, this, item.name, item.quantity
|
||||
@trigger Event.change, this
|
||||
return item
|
||||
|
||||
quantityOf: (name)->
|
||||
item = @_items[name]
|
||||
return 0 unless item?
|
||||
return item.quantity
|
||||
|
||||
remove: (name, quantity=1)->
|
||||
return if quantity is 0
|
||||
|
||||
item = @_items[name]
|
||||
if not item? then throw new Error "cannot remove #{name} since it is not in this inventory"
|
||||
if item.quantity < quantity
|
||||
@@ -63,13 +95,22 @@ module.exports = class Inventory extends BaseModel
|
||||
@trigger Event.change, this
|
||||
return this
|
||||
|
||||
toList: ->
|
||||
result = []
|
||||
@each (item)->
|
||||
if item.quantity > 1
|
||||
result.push [item.quantity, item.name]
|
||||
else
|
||||
result.push item.name
|
||||
return result
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") { items: ["]
|
||||
|
||||
needsDelimiter = false
|
||||
for name, item of @_items
|
||||
@each (item)->
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push item.toString()
|
||||
needsDelimiter = true
|
||||
|
||||
@@ -19,6 +19,22 @@ module.exports = class Recipe extends BaseModel
|
||||
|
||||
Object.defineProperty this, 'name', get:-> @output[0].name
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
make: (inventory, missing)->
|
||||
for item in @input
|
||||
needed = item.quantity
|
||||
while needed > 0
|
||||
if inventory.hasAtLeast item.name
|
||||
inventory.remove item.name
|
||||
else
|
||||
missing.add item.name
|
||||
|
||||
for item in @output
|
||||
inventory.add item.name, item.quantity
|
||||
|
||||
return this
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
|
||||
@@ -21,7 +21,7 @@ module.exports = class RecipeBook extends BaseModel
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getRecipes: (name)->
|
||||
findRecipes: (name)->
|
||||
result = []
|
||||
for recipe in @recipes
|
||||
if recipe.name is name
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{DefaultBookUrls} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
RecipeBookParser = require './recipe_book_parser'
|
||||
|
||||
@@ -19,11 +18,17 @@ module.exports = class RecipeCatalog extends BaseModel
|
||||
super attributes, options
|
||||
|
||||
@_parser = new RecipeBookParser
|
||||
if @books.length is 0
|
||||
@loadAllBooks DefaultBookUrls
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
findRecipes: (name)->
|
||||
result = []
|
||||
for book in @books
|
||||
for recipe in book.findRecipes name
|
||||
result.push recipe
|
||||
|
||||
return result
|
||||
|
||||
loadBook: (url)->
|
||||
w.promise (resolve, reject)=>
|
||||
@trigger Event.load.started, this, url
|
||||
@@ -35,6 +40,15 @@ module.exports = class RecipeCatalog extends BaseModel
|
||||
error: (xhr, status, error)=>
|
||||
reject @onBookLoadFailed(url, error, status, xhr)
|
||||
|
||||
loadBookData: (data)->
|
||||
book = @_parser.parse data
|
||||
@books.push book
|
||||
@books.sort (a, b)->
|
||||
return 0 if a.modName is b.modName
|
||||
return if a.modName < b.modName then -1 else +1
|
||||
|
||||
return book
|
||||
|
||||
loadAllBooks: (urlList)->
|
||||
promises = (@loadBook(url) for url in urlList)
|
||||
return w.settle promises
|
||||
@@ -43,11 +57,7 @@ module.exports = class RecipeCatalog extends BaseModel
|
||||
|
||||
onBookLoaded: (url, data, status, xhr)->
|
||||
try
|
||||
book = @_parser.parse data
|
||||
@books.push book
|
||||
@books.sort (a, b)->
|
||||
return 0 if a.modName is b.modName
|
||||
return if a.modName < b.modName then -1 else +1
|
||||
book = @loadBookData data
|
||||
|
||||
logger.info "loaded recipe book from #{url}: #{book}"
|
||||
@trigger Event.load.succeeded, this, book
|
||||
|
||||
Reference in New Issue
Block a user