Move model files to crafting-guide-common
This commit is contained in:
@@ -18,7 +18,7 @@ global.π = Math.PI
|
||||
global.ε = 0.0001
|
||||
global.w = require 'when'
|
||||
|
||||
{Logger} = require 'crafting-guide-common'
|
||||
{Logger} = require('crafting-guide-common').util
|
||||
global.logger = new Logger
|
||||
|
||||
Tracker = require './tracker'
|
||||
@@ -63,7 +63,7 @@ storage = new Storage storage:global.localStorage
|
||||
|
||||
tracker.trackPageView()
|
||||
|
||||
{CraftingGuideClient} = require 'crafting-guide-common'
|
||||
{CraftingGuideClient} = require('crafting-guide-common').api
|
||||
client = _(new CraftingGuideClient(baseUrl:apiBaseUrl)).extend Backbone.Events
|
||||
client.onStatusChanged = (client, oldStatus, newStatus)->
|
||||
logger.info "Crafting Guide server status changed from #{oldStatus} to #{newStatus}"
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - base_model.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BaseModel extends Backbone.Model
|
||||
|
||||
@_loadingQueue = []
|
||||
@_isDraining = false
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
options.logEvents ?= true
|
||||
super attributes, options
|
||||
|
||||
makeGetter = (name)-> return -> @get name
|
||||
makeSetter = (name)-> return (value)-> @set name, value
|
||||
for name, value of attributes
|
||||
continue if name is 'id'
|
||||
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
|
||||
|
||||
@fileCache = options.fileCache or null
|
||||
@loading = null
|
||||
@logEvents = options.logEvents or false
|
||||
@state = c.modelState.unloaded
|
||||
|
||||
Object.defineProperties this,
|
||||
isUnloaded: { get:-> @state is c.modelState.unloaded }
|
||||
isLoading: { get:-> @state is c.modelState.loading }
|
||||
isLoaded: { get:-> @state is c.modelState.loaded }
|
||||
isError: { get:-> @state is c.modelState.error }
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onLoadSucceeded: (text, status, xhr)->
|
||||
try
|
||||
@set @parse text
|
||||
|
||||
@state = c.modelState.loaded
|
||||
@trigger c.event.change, this
|
||||
@trigger c.event.sync, this
|
||||
logger.info => "#{@constructor.name}.#{@cid} loaded successfully"
|
||||
catch e
|
||||
logger.error -> "A parsing error occured: #{e.stack}"
|
||||
@onLoadFailed e.message, 'parsing failed', xhr
|
||||
|
||||
onLoadFailed: (error, status, xhr)->
|
||||
@state = c.modelState.error
|
||||
logger.error => "#{@constructor.name}.#{@cid} failed to load: status:#{status}, message:#{error}"
|
||||
@trigger c.event.error, this, error
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
fetch: (options={})->
|
||||
options.force ?= false
|
||||
return if (@isLoading or @isLoaded) and not options.force
|
||||
|
||||
url = @url()
|
||||
logger.info => "#{@constructor.name}.#{@cid} reading from url: #{url}"
|
||||
|
||||
@state = c.modelState.loading
|
||||
@trigger c.event.request, this
|
||||
|
||||
loadFromServer = =>
|
||||
w.promise (resolve, reject)=>
|
||||
$.ajax
|
||||
url: url
|
||||
dataType: 'text'
|
||||
success: (text, status, xhr)=> resolve @onLoadSucceeded text, status, xhr
|
||||
error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr
|
||||
|
||||
if @fileCache?
|
||||
@loading = @fileCache.loading.then =>
|
||||
if @fileCache.hasFile url
|
||||
return @_addToLoadingQueue @fileCache.getFile(url), 'success', {url:url}
|
||||
else
|
||||
@loading = loadFromServer()
|
||||
else
|
||||
@loading = loadFromServer()
|
||||
|
||||
@loading.catch (e)-> # do nothing
|
||||
return @loading
|
||||
|
||||
parse: (text)->
|
||||
return JSON.parse text
|
||||
|
||||
sync: (method, model)->
|
||||
throw new Error "#{@constructor.name}.#{@cid} is not permitted to #{method}"
|
||||
|
||||
trigger: (name, model, args...)->
|
||||
if @logEvents
|
||||
argText = ("#{arg}"[0..50] for arg in args).join ", "
|
||||
logger.trace => "#{@constructor.name}.#{@cid} triggered event #{name} with args: #{argText}"
|
||||
super
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name}.#{@cid}"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_addToLoadingQueue: (text, status, xhr)->
|
||||
deferred = w.defer()
|
||||
|
||||
BaseModel._loadingQueue.push resolve:deferred.resolve, func:(=> @onLoadSucceeded text, status, xhr)
|
||||
@_drainLoadingQueue()
|
||||
return deferred.promise
|
||||
|
||||
_drainLoadingQueue: ->
|
||||
return if @_isDraining
|
||||
@_isDraining = true
|
||||
|
||||
drainDelay = 50
|
||||
drain = =>
|
||||
toLoad = BaseModel._loadingQueue.shift()
|
||||
if not toLoad?
|
||||
@_isDraining = false
|
||||
else
|
||||
toLoad.func()
|
||||
toLoad.resolve(true)
|
||||
|
||||
_.delay drain, drainDelay
|
||||
|
||||
_.delay drain, drainDelay
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - converter.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require "../models/game/item"
|
||||
Mod = require "../models/game/mod"
|
||||
ModPack = require "../models/game/mod_pack"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Converter
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
convert: (id, displayName, oldModPack)->
|
||||
modSlugToIdMap = {}
|
||||
itemSlugToIdMap = {}
|
||||
|
||||
newModPack = new ModPack id:id, displayName:displayName
|
||||
oldModPack.eachMod (oldMod)=>
|
||||
newMod = new Mod id:_.uniqueId("mod-"), displayName:oldMod.name, modPack:newModPack
|
||||
modSlugToIdMap[oldMod.slug.toString()] = newMod.id
|
||||
|
||||
oldMod.eachItem (oldItem)=>
|
||||
newItem = new Item id:_.uniqueId("item-"), displayName:oldItem.name, mod:newMod
|
||||
itemSlugToIdMap[oldItem.slug.toString()] = newItem.id
|
||||
|
||||
if oldItem.isGatherable?
|
||||
newItem.isGatherable = oldItem.isGatherable
|
||||
|
||||
oldModPack.eachMod (oldMod)=>
|
||||
newMod = newModPack.mods[modSlugToIdMap[oldMod.slug.toString()]]
|
||||
|
||||
|
||||
return newModPack
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_convertItem: (oldItem, newMod)->
|
||||
|
||||
_convertMod: (oldMod, newModPack)->
|
||||
return newMod
|
||||
@@ -1,36 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - event_recorder.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
util = require 'util'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class EventRecorder
|
||||
|
||||
constructor: (model)->
|
||||
if not model? then throw new Error 'model is required'
|
||||
|
||||
@model = model
|
||||
@events = []
|
||||
|
||||
@model.on 'all', (event, model, args...)=>
|
||||
logger.verbose -> "#{model?.constructor?.name}(#{model?.cid}) emitted #{event}
|
||||
with args: #{util.inspect(args)}"
|
||||
@events.push id:model?.cid, event:event, args:args
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
reset: ->
|
||||
@events = []
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getNames: ->
|
||||
return (e.event for e in @events)
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
names: {get:@prototype.getNames}
|
||||
@@ -1,243 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - inventory.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
ItemSlug = require './item_slug'
|
||||
Stack = require './stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Inventory extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
attributes.modPack ?= null
|
||||
@clear()
|
||||
|
||||
if options.clone?
|
||||
@addInventory options.clone
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@Delimiters =
|
||||
Item: '.'
|
||||
Stack: ':'
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (itemSlug, quantity=1, options={})->
|
||||
return this unless quantity > 0
|
||||
|
||||
@_add itemSlug, quantity, options
|
||||
@trigger c.event.add, this, itemSlug, quantity
|
||||
@trigger c.event.change, this
|
||||
return this
|
||||
|
||||
addInventory: (inventory)->
|
||||
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
|
||||
|
||||
@trigger c.event.change, this
|
||||
return this
|
||||
|
||||
clear: (options={})->
|
||||
@_stacks = {}
|
||||
@_itemSlugs = []
|
||||
|
||||
@trigger c.event.change, this
|
||||
|
||||
clone: ->
|
||||
inventory = new Inventory
|
||||
inventory.addInventory this
|
||||
return inventory
|
||||
|
||||
each: (callback)->
|
||||
for itemSlug in @_itemSlugs
|
||||
stack = @_stacks[itemSlug]
|
||||
continue unless stack?
|
||||
callback stack
|
||||
|
||||
getSlugs: ->
|
||||
return @_itemSlugs[..]
|
||||
|
||||
hasAtLeast: (itemSlug, quantity=1)->
|
||||
if quantity is 0 then return true
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
return false unless stack?
|
||||
return stack.quantity >= quantity
|
||||
|
||||
localize: ->
|
||||
if not @modPack? then throw new Error 'localize requires @modPack'
|
||||
|
||||
changed = false
|
||||
newSlugs = []
|
||||
newStacks = []
|
||||
for itemSlug in @_itemSlugs
|
||||
stack = @_stacks[itemSlug]
|
||||
continue unless stack?
|
||||
|
||||
qualifiedSlug = if itemSlug.isQualified then itemSlug else null
|
||||
if not qualifiedSlug?
|
||||
qualifiedSlug = @modPack.findItem(itemSlug)?.slug
|
||||
changed = qualifiedSlug?
|
||||
|
||||
if qualifiedSlug?
|
||||
newSlugs.push qualifiedSlug
|
||||
newStacks.push new Stack itemSlug:qualifiedSlug, quantity:stack.quantity
|
||||
else
|
||||
newSlugs.push itemSlug
|
||||
newStacks.push stack
|
||||
|
||||
if changed
|
||||
for itemSlug, stack of @_stacks
|
||||
@stopListening stack
|
||||
|
||||
@_itemSlugs = newSlugs
|
||||
@_stacks = {}
|
||||
for stack in newStacks
|
||||
@_stacks[stack.itemSlug] = stack
|
||||
@listenTo stack, c.event.change, => @trigger c.event.change, this
|
||||
|
||||
@_sort()
|
||||
@trigger c.event.change, this
|
||||
|
||||
pop: ->
|
||||
itemSlug = @_itemSlugs.pop()
|
||||
return null unless itemSlug?
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
delete @_stacks[itemSlug]
|
||||
|
||||
@trigger c.event.remove, this, stack.itemSlug, stack.quantity
|
||||
@trigger c.event.change, this
|
||||
return stack
|
||||
|
||||
quantityOf: (itemSlug)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return stack.quantity if stack
|
||||
return 0
|
||||
|
||||
remove: (itemSlug, quantity=null)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return this unless stack?
|
||||
|
||||
quantity ?= stack.quantity
|
||||
return this unless quantity > 0
|
||||
|
||||
if stack.quantity < quantity
|
||||
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{itemSlug} in this inventory"
|
||||
|
||||
stack.quantity -= quantity
|
||||
if stack.quantity is 0
|
||||
@stopListening stack
|
||||
delete @_stacks[itemSlug]
|
||||
@_itemSlugs = (s for s in @_itemSlugs when not ItemSlug.equal(s, itemSlug))
|
||||
|
||||
@trigger c.event.remove, this, itemSlug, quantity
|
||||
@trigger c.event.change, this
|
||||
return this
|
||||
|
||||
toDescription: ->
|
||||
return null if @isEmpty
|
||||
return null unless @modPack?
|
||||
|
||||
item = @modPack.findItem @_itemSlugs[0]
|
||||
extras = @_itemSlugs.length - 1
|
||||
|
||||
result = "#{item.name}"
|
||||
if extras > 0 then result += " and #{extras} more..."
|
||||
|
||||
return result
|
||||
|
||||
# Parsing Methods ##############################################################################
|
||||
|
||||
parse: (data)->
|
||||
return this if not data? or data.length is 0
|
||||
|
||||
stacks = data.split Inventory.Delimiters.Stack
|
||||
for stackText in stacks
|
||||
stackParts = stackText.split Inventory.Delimiters.Item
|
||||
if stackParts.length is 2
|
||||
quantity = parseInt stackParts[0], 10
|
||||
itemSlug = ItemSlug.slugify stackParts[1]
|
||||
else if stackParts.length is 1
|
||||
quantity = 1
|
||||
itemSlug = ItemSlug.slugify stackParts[0]
|
||||
else
|
||||
throw new Error "expected #{stackText} to have 0 or 1 parts"
|
||||
|
||||
if itemSlug.qualified.length > 0
|
||||
@add itemSlug, quantity
|
||||
|
||||
return this
|
||||
|
||||
unparse: (options={})->
|
||||
parts = []
|
||||
@each (stack)=>
|
||||
slugText = stack.itemSlug.item
|
||||
if @modPack?
|
||||
item = @modPack.findItem ItemSlug.slugify slugText
|
||||
if item? and item.slug.qualified isnt stack.itemSlug.qualified
|
||||
slugText = stack.itemSlug.qualified
|
||||
|
||||
if stack.quantity is 1
|
||||
parts.push slugText
|
||||
else
|
||||
parts.push "#{stack.quantity}#{Inventory.Delimiters.Item}#{slugText}"
|
||||
|
||||
return parts.join Inventory.Delimiters.Stack
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isEmpty:
|
||||
get: -> @_itemSlugs.length is 0
|
||||
|
||||
totalQuantity:
|
||||
get: ->
|
||||
total = 0
|
||||
@each (stack)->
|
||||
total += stack.quantity
|
||||
return total
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") {items: ["]
|
||||
|
||||
needsDelimiter = false
|
||||
@each (stack)->
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push stack.toString()
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push '}'
|
||||
return result.join ''
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_add: (itemSlug, quantity=1, options={})->
|
||||
options.insert ?= false
|
||||
return unless itemSlug?
|
||||
return unless quantity > 0
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
if not stack?
|
||||
stack = new Stack itemSlug:itemSlug, quantity:quantity
|
||||
@listenTo stack, c.event.change, => @trigger c.event.change, this
|
||||
@_stacks[itemSlug] = stack
|
||||
if options.insert
|
||||
@_itemSlugs.unshift itemSlug
|
||||
else
|
||||
@_itemSlugs.push itemSlug
|
||||
@_sort()
|
||||
else
|
||||
stack.quantity += quantity
|
||||
|
||||
_sort: ->
|
||||
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
|
||||
@@ -1,90 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
ItemSlug = require './item_slug'
|
||||
Recipe = require './recipe'
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Item extends BaseModel
|
||||
|
||||
@Group = Other:'Other'
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.name? then throw new Error 'attributes.name is required'
|
||||
|
||||
attributes.description ?= null
|
||||
attributes.group ?= Item.Group.Other
|
||||
attributes.ignoreDuringCrafting ?= false
|
||||
attributes.isGatherable ?= false
|
||||
attributes.modVersion ?= null
|
||||
attributes.officialUrl ?= null
|
||||
attributes.slug ?= ItemSlug.slugify attributes.name
|
||||
attributes.videos ?= []
|
||||
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
@on c.event.change + ':modVersion', =>
|
||||
@_isCraftable = null
|
||||
@slug.mod = @modVersion?.modSlug
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
compareTo: (that)->
|
||||
if this.slug isnt that.slug
|
||||
return if this.slug < that.slug then -1 else +1
|
||||
if this.name isnt that.name
|
||||
return if this.name < that.name then -1 else +1
|
||||
return 0
|
||||
|
||||
unparse: ->
|
||||
ItemParser = require '../parsing/item_parser' # to avoid require cycles
|
||||
@_parser ?= new ItemParser model:this
|
||||
return @_parser.unparse()
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getIsCraftable: ->
|
||||
if not @_isCraftable?
|
||||
@_isCraftable = false
|
||||
if @modVersion?
|
||||
@_isCraftable = @modVersion.hasRecipes @slug
|
||||
|
||||
return @_isCraftable
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isCraftable: {get:@prototype.getIsCraftable}
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
ItemParser = require '../parsing/item_parser' # to avoid require cycles
|
||||
@_parser ?= new ItemParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.itemData modSlug:@slug.mod, itemSlug:@slug.item
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
builder = new StringBuilder
|
||||
return builder
|
||||
.push @constructor.name, ' (', @cid, ') { '
|
||||
.push 'name:"', @name, '", '
|
||||
.push 'isCraftable:', @isCraftable, ', '
|
||||
.push 'isGatherable:', @isGatherable, ', '
|
||||
.onlyIf (@group isnt Item.Group.Other), (b)=>
|
||||
b.push 'group:"', @group, '", '
|
||||
.push 'slug:"', @slug, '", '
|
||||
.push '}'
|
||||
.toString()
|
||||
@@ -1,96 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_slug.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemSlug
|
||||
|
||||
constructor: ->
|
||||
@_item = @_mod = null
|
||||
|
||||
if arguments.length is 1
|
||||
parts = _.decomposeSlug arguments[0]
|
||||
@_mod = _.slugify parts[0]
|
||||
@item = _.slugify parts[1]
|
||||
else if arguments.length is 2
|
||||
@_mod = arguments[0]
|
||||
@item = arguments[1]
|
||||
else
|
||||
throw new Error 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@compare: (a, b)->
|
||||
if a.item isnt b.item
|
||||
return if a.item < b.item then -1 else +1
|
||||
if a.mod isnt b.mod
|
||||
return if a.mod < b.mod then -1 else +1
|
||||
return 0
|
||||
|
||||
@equal: (a, b)->
|
||||
return true if not a? and not b?
|
||||
return false unless a? and b?
|
||||
return false unless a.mod is b.mod
|
||||
return false unless a.item is b.item
|
||||
return true
|
||||
|
||||
@slugify: (arg)->
|
||||
return arg if arg?.constructor?.name is 'ItemSlug'
|
||||
|
||||
[modSlug, itemSlug] = _.decomposeSlug arg
|
||||
itemSlug = _.slugify itemSlug
|
||||
|
||||
if modSlug?
|
||||
return new ItemSlug modSlug, itemSlug
|
||||
else
|
||||
return new ItemSlug itemSlug
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
compareTo: (that)->
|
||||
return ItemSlug.compare this, that
|
||||
|
||||
matches: (slug, options={exact:false})->
|
||||
return false unless slug?
|
||||
return false unless typeof(slug.matches) is 'function'
|
||||
|
||||
if slug.isQualified and this.isQualified
|
||||
return slug.qualified is this.qualified
|
||||
else
|
||||
return slug.item is this.item
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isQualified:
|
||||
get: -> @_mod?
|
||||
|
||||
item:
|
||||
get: -> @_item
|
||||
|
||||
set: (newItem)->
|
||||
if not newItem? then throw new Error 'item is required'
|
||||
@_item = newItem
|
||||
@mod = @mod # reset @_qualified
|
||||
|
||||
mod:
|
||||
get: -> @_mod
|
||||
|
||||
set: (newMod)->
|
||||
@_mod = newMod
|
||||
@_qualified = if @_mod? then _.composeSlugs(@_mod, @_item) else @_item
|
||||
|
||||
qualified:
|
||||
get: -> @_qualified
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return @_qualified
|
||||
|
||||
valueOf: ->
|
||||
return @_qualified.valueOf()
|
||||
@@ -1,234 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Mod extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.slug? then throw new Error 'attributes.slug is required'
|
||||
|
||||
attributes.author ?= ''
|
||||
attributes.description ?= ''
|
||||
attributes.documentationUrl ?= null
|
||||
attributes.downloadUrl ?= null
|
||||
attributes.homePageUrl ?= null
|
||||
attributes.modPack ?= null
|
||||
attributes.name ?= ''
|
||||
|
||||
super attributes, options
|
||||
|
||||
@_activeModVersion = null
|
||||
@_activeVersion = null
|
||||
@_modVersions = []
|
||||
@_tutorials = []
|
||||
|
||||
# Class Methods ##################################################################################
|
||||
|
||||
@Version: Version =
|
||||
None: 'none'
|
||||
Latest: 'latest'
|
||||
|
||||
# Public Methods #################################################################################
|
||||
|
||||
compareTo: (that)->
|
||||
thisRequired = this.slug in c.requiredMods
|
||||
thatRequired = that.slug in c.requiredMods
|
||||
|
||||
if thisRequired isnt thatRequired
|
||||
return -1 if thisRequired
|
||||
return +1 if thatRequired
|
||||
else if this.slug isnt that.slug
|
||||
return if this.slug < that.slug then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
activeModVersion:
|
||||
get: -> @_activeModVersion
|
||||
|
||||
activeVersion:
|
||||
get: ->
|
||||
return @_activeVersion
|
||||
|
||||
set: (version)->
|
||||
return if version is @_activeVersion
|
||||
|
||||
version ?= Mod.Version.None
|
||||
if version is Mod.Version.Latest then version = _.last(@_modVersions).version
|
||||
|
||||
if version is Mod.Version.None
|
||||
@_activeVersion = version
|
||||
@_activateModVersion null
|
||||
|
||||
@trigger c.event.change + ':activeVersion', this, @_activeVersion
|
||||
@trigger c.event.change, this
|
||||
else
|
||||
for modVersion in @_modVersions
|
||||
if version is modVersion.version
|
||||
@_activateModVersion modVersion
|
||||
break
|
||||
|
||||
@_activeVersion = version
|
||||
@trigger c.event.change + ':activeVersion', this, @_activeVersion
|
||||
@trigger c.event.change, this
|
||||
|
||||
enabled:
|
||||
get: -> @_activeModVersion?
|
||||
|
||||
modVersions:
|
||||
get: -> @_modVersions[..]
|
||||
|
||||
tutorials:
|
||||
get: -> @getAllTutorials()
|
||||
|
||||
# Item Methods #################################################################################
|
||||
|
||||
chooseRandomItem: ->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
return null unless effectiveModVersion?
|
||||
|
||||
return effectiveModVersion.chooseRandomItem()
|
||||
|
||||
eachItem: (callback)->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
effectiveModVersion.eachItem callback
|
||||
|
||||
findItem: (slug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
options.enableAsNeeded ?= false
|
||||
|
||||
if not options.includeDisabled
|
||||
return unless @_activeModVersion?
|
||||
return @_activeModVersion.findItem slug
|
||||
else
|
||||
for modVersion in @_modVersions
|
||||
modVersion.fetch()
|
||||
|
||||
item = modVersion.findItem slug
|
||||
if item?
|
||||
if options.enableAsNeeded then @setActiveVersion modVersion.version
|
||||
return item
|
||||
|
||||
return null
|
||||
|
||||
findItemByName: (name)->
|
||||
return unless @_activeModVersion?
|
||||
@_activeModVersion.findItemByName name
|
||||
|
||||
# ModVersion Methods ###########################################################################
|
||||
|
||||
addModVersion: (modVersion)->
|
||||
return unless modVersion?
|
||||
return if @_modVersions.indexOf(modVersion) isnt -1
|
||||
|
||||
@_modVersions.push modVersion
|
||||
@listenTo modVersion, c.event.change, => @trigger c.event.change, this
|
||||
modVersion.fileCache = this.fileCache
|
||||
modVersion.mod = this
|
||||
|
||||
@trigger c.event.add + ':modVersion', modVersion, this
|
||||
@trigger c.event.change + ':version', modVersion, this
|
||||
@trigger c.event.change, this
|
||||
|
||||
if not @activeVersion? then @activeVersion = modVersion.version
|
||||
if modVersion.version is @_activeVersion then @_activateModVersion modVersion
|
||||
return this
|
||||
|
||||
eachModVersion: (callback)->
|
||||
for modVersion in @_modVersions
|
||||
callback modVersion
|
||||
|
||||
getAllModVersions: ->
|
||||
return @_modVersions[..]
|
||||
|
||||
getModVersion: (version)->
|
||||
return null if version is Mod.Version.None
|
||||
return @_modVersions[0] if version is Mod.Version.Latest
|
||||
|
||||
for modVersion in @_modVersions
|
||||
return modVersion if modVersion.version is version
|
||||
|
||||
return null
|
||||
|
||||
# Name Methods #################################################################################
|
||||
|
||||
eachName: (callback)->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
effectiveModVersion.eachName callback
|
||||
|
||||
findName: (itemSlug)->
|
||||
return unless @_activeModVersion?
|
||||
@_activeModVersion.findName itemSlug
|
||||
|
||||
# Recipe Methods ###############################################################################
|
||||
|
||||
eachRecipe: (callback)->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
effectiveModVersion.eachRecipe callback
|
||||
|
||||
findRecipes: (itemSlug, result=[], options={})->
|
||||
options.alwaysFromOwningMod ?= false
|
||||
|
||||
if @_activeModVersion?
|
||||
return @_activeModVersion.findRecipes itemSlug, result, options
|
||||
else if options.alwaysFromOwningMod and itemSlug.mod is @slug
|
||||
return @getModVersion(Mod.Version.Latest).findRecipes itemSlug, result, options
|
||||
|
||||
return null
|
||||
|
||||
# Tutorial Methods #############################################################################
|
||||
|
||||
addTutorial: (tutorial)->
|
||||
return unless tutorial?
|
||||
if @getTutorial(tutorial.slug)? then throw new Error "duplicate tutorial: #{tutorial.name}"
|
||||
@_tutorials.push tutorial
|
||||
tutorial.modSlug = @slug
|
||||
|
||||
getAllTutorials: ->
|
||||
return @_tutorials[..]
|
||||
|
||||
getTutorial: (tutorialSlug)->
|
||||
for tutorial in @_tutorials
|
||||
return tutorial if tutorial.slug is tutorialSlug
|
||||
return null
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
ModParser = require '../parsing/mod_parser' # to avoid require cycles
|
||||
@_parser ?= new ModParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
@_verifyActiveModVersion()
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.modData modSlug:@slug
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_activateModVersion: (modVersion)->
|
||||
if @_activeModVersion? then @stopListening @_activeModVersion
|
||||
@_activeModVersion = modVersion
|
||||
@trigger c.event.change + ':activeModVersion', this, @_activeModVersion
|
||||
|
||||
logger.verbose => "#{@slug} switched to version #{@_activeVersion}"
|
||||
|
||||
if @_activeModVersion?
|
||||
@listenTo @_activeModVersion, 'all', -> @trigger.apply this, arguments
|
||||
|
||||
_verifyActiveModVersion: ->
|
||||
if (@_activeVersion isnt Version.None) and (not @_activeModVersion?)
|
||||
logger.warning => "#{@slug} no longer has a version #{@_activeVersion}, using latest instead"
|
||||
@activeVersion = Version.Latest
|
||||
@@ -1,202 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Mod = require './mod'
|
||||
ModVersionParser = require '../parsing/mod_version_parser'
|
||||
Recipe = require './recipe'
|
||||
SimpleInventory = require '../crafting/simple_inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPack extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
|
||||
@_mods = []
|
||||
@_cache = {}
|
||||
|
||||
@on c.event.change, => @_cache = {}
|
||||
|
||||
# Item Methods #################################################################################
|
||||
|
||||
chooseRandomItem: ->
|
||||
return null unless @_mods.length > 0
|
||||
|
||||
modIndex = Math.floor Math.random() * @_mods.length
|
||||
return @_mods[modIndex].chooseRandomItem()
|
||||
|
||||
findItem: (itemSlug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
key = "#{itemSlug}-#{options.includeDisabled}"
|
||||
@_cache.itemBySlug ?= {}
|
||||
item = @_cache.itemBySlug[key]
|
||||
return item if item?
|
||||
|
||||
if itemSlug.isQualified
|
||||
mod = @getMod itemSlug.mod
|
||||
if mod?
|
||||
item = mod.findItem itemSlug, options
|
||||
|
||||
if not item?
|
||||
for mod in @_mods
|
||||
continue unless mod.enabled or options.includeDisabled
|
||||
item = mod.findItem itemSlug, options
|
||||
break if item?
|
||||
|
||||
if item?
|
||||
@_cache.itemBySlug[key] = item
|
||||
|
||||
return item
|
||||
|
||||
findItemByName: (name, options={})->
|
||||
options.enableAsNeeded ?= false
|
||||
options.includeDisabled = true if options.enableAsNeeded
|
||||
|
||||
for mod in @_mods
|
||||
continue unless mod.enabled or options.includeDisabled
|
||||
item = mod.findItemByName name, options
|
||||
return item if item?
|
||||
|
||||
return null
|
||||
|
||||
findItemDisplay: (itemSlug)->
|
||||
if not itemSlug? then throw new Error 'itemSlug is required'
|
||||
|
||||
result = {slug:itemSlug}
|
||||
item = @findItem itemSlug, includeDisabled:true
|
||||
if item?
|
||||
result.itemName = item.name
|
||||
result.itemSlug = item.slug.item
|
||||
result.modSlug = item.slug.mod
|
||||
result.modVersion = item.modVersion.version
|
||||
else
|
||||
result.itemName = @findName itemSlug, includeDisabled:true
|
||||
result.itemSlug = itemSlug.item
|
||||
result.modSlug = @_mods[0].slug
|
||||
result.modVersion = @_mods[0].activeVersion
|
||||
|
||||
craftingUrlInventory = new SimpleInventory modPack:this
|
||||
if item?.multiblock?
|
||||
craftingUrlInventory.addInventory item.multiblock.inventory
|
||||
else
|
||||
craftingUrlInventory.add itemSlug
|
||||
|
||||
result.craftingUrl = c.url.crafting inventoryText:craftingUrlInventory.unparse()
|
||||
result.iconUrl = c.url.itemIcon result
|
||||
result.itemUrl = c.url.item result
|
||||
result.modName = @getMod(result.modSlug).name
|
||||
return result
|
||||
|
||||
qualifySlug: (itemSlug)->
|
||||
return itemSlug if itemSlug.isQualified
|
||||
|
||||
item = @findItem itemSlug
|
||||
return item.slug if item?
|
||||
return itemSlug
|
||||
|
||||
# Mod Methods ##################################################################################
|
||||
|
||||
addMod: (mod)->
|
||||
if not mod? then throw new Error 'mod is required'
|
||||
return if @_mods.indexOf(mod) isnt -1
|
||||
|
||||
mod.modPack = this
|
||||
@_mods.push mod
|
||||
@listenTo mod, c.event.change, (modVersion)=> @_onModVersionLoaded modVersion
|
||||
@trigger c.event.add + ':mod', mod, this
|
||||
|
||||
@_mods.sort (a, b)-> a.compareTo b
|
||||
@trigger c.event.sort + ':mod', this
|
||||
@trigger c.event.change, this
|
||||
|
||||
return this
|
||||
|
||||
eachMod: (callback)->
|
||||
for mod in @_mods
|
||||
callback mod
|
||||
|
||||
getMod: (slug)->
|
||||
for mod in @_mods
|
||||
return mod if mod.slug is slug
|
||||
return null
|
||||
|
||||
getAllMods: ->
|
||||
return @_mods[..]
|
||||
|
||||
removeMod: (mod)->
|
||||
index = @_mods.indexOf mod
|
||||
return unless index >= 0
|
||||
|
||||
@_mods.splice index, 1
|
||||
|
||||
@trigger c.event.remove, this, mod.slug
|
||||
@trigger c.event.change, this
|
||||
|
||||
# Name Methods #################################################################################
|
||||
|
||||
findName: (slug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
for mod in @_mods
|
||||
continue unless mod.enabled or options.includeDisabled
|
||||
name = mod.findName slug
|
||||
return name if name
|
||||
|
||||
return null
|
||||
|
||||
# Recipe Methods ###############################################################################
|
||||
|
||||
findRecipes: (itemSlug, options={})->
|
||||
options.alwaysFromOwningMod ?= false
|
||||
return null unless itemSlug?
|
||||
|
||||
key = "#{itemSlug}-#{options.alwaysFromOwningMod}"
|
||||
@_cache.recipesBySlug ?= {}
|
||||
result = @_cache.recipesBySlug[key]
|
||||
return result if result?
|
||||
|
||||
result = []
|
||||
for mod in @_mods
|
||||
if not mod.enabled
|
||||
owningMod = itemSlug.isQualified and (itemSlug.mod is mod.slug)
|
||||
continue unless owningMod and options.alwaysFromOwningMod
|
||||
|
||||
mod.findRecipes itemSlug, result, options
|
||||
|
||||
@_cache.recipesBySlug[key] = result
|
||||
return if result.length > 0 then result else null
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "ModPack (#{@cid}) {modVersions:«#{@_mods.length} items»}"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_onModVersionLoaded: (modVersion)->
|
||||
mods = @getAllMods()
|
||||
return true unless mods.length > 0
|
||||
|
||||
for mod in mods
|
||||
if mod.isError
|
||||
@removeMod mod
|
||||
continue
|
||||
|
||||
modVersions = mod.getAllModVersions()
|
||||
return true unless modVersions.length > 0
|
||||
continue if mod.activeVersion is Mod.Version.None
|
||||
|
||||
activeModVersion = mod.activeModVersion
|
||||
return true unless activeModVersion?
|
||||
return true if activeModVersion.isUnloaded
|
||||
return true if activeModVersion.isLoading
|
||||
|
||||
@trigger c.event.change, this
|
||||
@trigger c.event.sync, this
|
||||
@@ -1,200 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_version.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Item = require './item'
|
||||
ItemSlug = require './item_slug'
|
||||
Recipe = require './recipe'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModVersion extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.modSlug? then throw new Error 'attributes.modSlug is required'
|
||||
if not attributes.version? then throw new Error 'attributes.version is required'
|
||||
attributes.mod ?= null
|
||||
super attributes, options
|
||||
|
||||
@_groups = {}
|
||||
@_items = {}
|
||||
@_names = {}
|
||||
@_recipes = {}
|
||||
@_slugs = []
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
compareTo: (that)->
|
||||
if this.mod? and that.mod?
|
||||
return this.mod.compareTo that.mod
|
||||
|
||||
if this.modSlug isnt that.modSlug
|
||||
return if this.modSlug < that.modSlug then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
sort: ->
|
||||
@_slugs.sort (a, b)-> ItemSlug.compare a, b
|
||||
|
||||
# Item Methods #################################################################################
|
||||
|
||||
addItem: (item)->
|
||||
if @findItem(item.slug)? then throw new Error "duplicate item for #{item.name}"
|
||||
|
||||
item.modVersion = this
|
||||
@_items[item.slug.item] = item
|
||||
@_groups[item.group] ?= {}
|
||||
@_groups[item.group][item.slug.item] = item
|
||||
|
||||
@registerName item.slug, item.name
|
||||
return this
|
||||
|
||||
allItemsInGroup: (group)->
|
||||
result = []
|
||||
@eachItemInGroup group, (item)-> result.push item
|
||||
return null if result.length is 0
|
||||
return result
|
||||
|
||||
chooseRandomItem: ->
|
||||
itemIndex = Math.floor Math.random() * @_slugs.length
|
||||
return @_slugs[itemIndex]
|
||||
|
||||
eachItem: (callback)->
|
||||
for slug in @_slugs
|
||||
item = @findItem slug
|
||||
continue unless item?
|
||||
callback item
|
||||
return this
|
||||
|
||||
eachItemInGroup: (group, callback)->
|
||||
itemMap = @_groups[group]
|
||||
return unless itemMap?
|
||||
|
||||
items = _.values(itemMap).sort (a, b)-> a.compareTo b
|
||||
for item in items
|
||||
callback item
|
||||
|
||||
findItem: (itemSlug)->
|
||||
return @_items[itemSlug.item]
|
||||
|
||||
findItemByName: (name)->
|
||||
for itemSlug, item of @_items
|
||||
return item if item.name is name
|
||||
return null
|
||||
|
||||
# Group Methods ################################################################################
|
||||
|
||||
getAllGroups: ->
|
||||
result = []
|
||||
@eachGroup (group)-> result.push group
|
||||
return result
|
||||
|
||||
eachGroup: (callback)->
|
||||
groupNames = _.keys @_groups
|
||||
groupNames.sort (a, b)->
|
||||
if a is b then return 0
|
||||
if a is Item.Group.Other then return -1
|
||||
if b is Item.Group.Other then return +1
|
||||
return if a < b then -1 else +1
|
||||
|
||||
for groupName in groupNames
|
||||
callback groupName
|
||||
|
||||
# Name Methods #################################################################################
|
||||
|
||||
eachName: (callback)->
|
||||
for slug in @_slugs
|
||||
callback @_names[slug.item], slug
|
||||
return this
|
||||
|
||||
findName: (itemSlug)->
|
||||
return @_names[itemSlug.item]
|
||||
|
||||
registerName: (itemSlug, name)->
|
||||
return if @_names[itemSlug.item]
|
||||
@_names[itemSlug.item] = name
|
||||
@_slugs.push itemSlug
|
||||
return this
|
||||
|
||||
# Recipe Methods ###############################################################################
|
||||
|
||||
addRecipe: (recipe)->
|
||||
return unless recipe?
|
||||
|
||||
recipe.modVersion = this
|
||||
if @_recipes[recipe.slug]? then throw new Error "duplicate recipe: #{recipe.slug}"
|
||||
|
||||
@_recipes[recipe.slug] = recipe
|
||||
return this
|
||||
|
||||
eachRecipe: (callback)->
|
||||
recipes = _.values(@_recipes).sort (a, b)-> Recipe.compareFor a, b
|
||||
for recipe in recipes
|
||||
callback recipe
|
||||
return this
|
||||
|
||||
findRecipes: (itemSlug, result=[], options={})->
|
||||
options.onlyPrimary ?= false
|
||||
options.forCrafting ?= false
|
||||
|
||||
primaryRecipes = []
|
||||
otherRecipes = []
|
||||
|
||||
for recipe in _.values @_recipes
|
||||
continue unless recipe.isConditionSatisfied()
|
||||
continue unless recipe.hasAllTools()
|
||||
continue if options.forCrafting and recipe.ignoreDuringCrafting
|
||||
|
||||
if recipe.itemSlug.matches itemSlug
|
||||
primaryRecipes.push recipe
|
||||
else if recipe.produces itemSlug
|
||||
otherRecipes.push recipe
|
||||
|
||||
for recipe in primaryRecipes
|
||||
result.push recipe
|
||||
|
||||
if not options.onlyPrimary and result.length is 0
|
||||
for recipe in otherRecipes
|
||||
result.push recipe
|
||||
|
||||
return result
|
||||
|
||||
findExternalRecipes: ->
|
||||
result = {}
|
||||
for k, recipe of @_recipes
|
||||
continue if recipe.itemSlug.isQualified
|
||||
|
||||
recipeList = result[recipe.itemSlug]
|
||||
if not recipeList then recipeList = result[recipe.itemSlug] = []
|
||||
|
||||
recipeList.push recipe
|
||||
|
||||
return result
|
||||
|
||||
hasRecipes: (itemSlug)->
|
||||
for k, recipe of @_recipes
|
||||
return true if recipe.produces itemSlug
|
||||
return false
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
ModVersionParser = require '../parsing/mod_version_parser' # to avoid require cycles
|
||||
@_parser ?= new ModVersionParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.modVersionData modSlug:@modSlug, modVersion:@version
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "ModVersion (#{@cid}) {
|
||||
modSlug:#{@modSlug}, version:#{@version}, items:«#{@_slugs.length} items»
|
||||
}"
|
||||
@@ -1,85 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - multiblock.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Inventory = require './inventory'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Multiblock extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.input? then throw new Error 'attributes.input is required'
|
||||
if not attributes.layers? then throw new Error 'attributes.layers is required'
|
||||
if attributes.layers.length < 1 then throw new Error 'attributes.layers.length must be >= 1'
|
||||
super attributes, options
|
||||
|
||||
@_analyzePattern()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getStackAt: (x, y, z)->
|
||||
value = @_stackCache[y]?[z]?[x]
|
||||
return null if value is undefined
|
||||
return value
|
||||
|
||||
getLayerInventory: (y)->
|
||||
@_layerInventories ?= []
|
||||
result = @_layerInventories[y]
|
||||
if not result? then result = @_layerInventories[y] = new Inventory
|
||||
return result
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
depth:
|
||||
get: -> @_depth
|
||||
|
||||
height:
|
||||
get: -> @_height
|
||||
|
||||
inventory:
|
||||
get: -> @_inventory
|
||||
|
||||
width:
|
||||
get: -> @_width
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_analyzeLayer: (layer, y, stackCacheLayer)->
|
||||
rows = layer.split ' '
|
||||
@_depth = Math.max @_depth, rows.length
|
||||
|
||||
for row in rows
|
||||
@_width = Math.max @_width, row.length
|
||||
stackCacheRow = []
|
||||
stackCacheLayer.push stackCacheRow
|
||||
@_analyzeRow row, stackCacheRow, @getLayerInventory(y)
|
||||
|
||||
_analyzePattern: ->
|
||||
@_depth = @_height = @_width = 0
|
||||
@_inventory = new Inventory
|
||||
@_stackCache = []
|
||||
|
||||
for layer, y in @layers
|
||||
stackCacheLayer = []
|
||||
@_stackCache.push stackCacheLayer
|
||||
@_analyzeLayer layer, y, stackCacheLayer
|
||||
|
||||
@_height = @layers.length
|
||||
|
||||
_analyzeRow: (row, stackCacheRow, layerInventory)->
|
||||
for cell, x in row.split ''
|
||||
index = parseInt cell
|
||||
stack = null
|
||||
|
||||
if not _.isNaN index
|
||||
stack = @input[index]
|
||||
@_inventory.add stack.itemSlug, stack.quantity
|
||||
layerInventory.add stack.itemSlug, stack.quantity
|
||||
|
||||
stackCacheRow.push stack
|
||||
@@ -1,247 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - recipe.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
ItemSlug = require './item_slug'
|
||||
Stack = require './stack'
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Recipe extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.input? then throw new Error 'attributes.input is required'
|
||||
if not attributes.pattern? then throw new Error 'attributes.pattern is required'
|
||||
|
||||
if attributes.itemSlug? and not attributes.output?
|
||||
attributes.output = [new Stack itemSlug:attributes.itemSlug, quantity:1]
|
||||
else if attributes.output? and not attributes.itemSlug?
|
||||
if attributes.output.length is 0 then throw new Error 'attributes.output cannot be empty'
|
||||
attributes.itemSlug = attributes.output[0].itemSlug
|
||||
else
|
||||
throw new Error 'attributes.itemSlug or attributes.output is required'
|
||||
|
||||
attributes.pattern = @_parsePattern attributes.pattern
|
||||
|
||||
attributes.condition ?= null
|
||||
attributes.ignoreDuringCrafting ?= false
|
||||
attributes.modVersion ?= null
|
||||
attributes.tools ?= []
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
@_computeQuantities attributes.pattern
|
||||
|
||||
@on c.event.change + ':modVersion', => @_slug = null
|
||||
@on c.event.change + ':pattern', => @_patternCache = null
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@compareFor: (a, b, itemSlug)->
|
||||
if itemSlug?
|
||||
aValue = a.itemSlug.matches itemSlug
|
||||
bValue = b.itemSlug.matches itemSlug
|
||||
if aValue isnt bValue
|
||||
return -1 if aValue
|
||||
return +1 if bValue
|
||||
|
||||
aValue = a.getQuantityProduced itemSlug
|
||||
bValue = b.getQuantityProduced itemSlug
|
||||
if aValue isnt bValue
|
||||
return if aValue > bValue then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getStackAtSlot: (patternSlot)->
|
||||
trueIndex = 0:0, 1:1, 2:2, 3:4, 4:5, 5:6, 6:8, 7:9, 8:10
|
||||
patternDigit = @pattern[trueIndex[patternSlot]]
|
||||
return null unless patternDigit?
|
||||
return null unless patternDigit.match /[0-9]/
|
||||
|
||||
stack = @input[parseInt(patternDigit)]
|
||||
return null unless stack?
|
||||
|
||||
return stack
|
||||
|
||||
getQuantityProduced: (itemSlug)->
|
||||
total = 0
|
||||
for stack in @output
|
||||
if stack.itemSlug.matches itemSlug
|
||||
total += stack.quantity
|
||||
|
||||
return total
|
||||
|
||||
getQuantityRequired: (itemSlug)->
|
||||
total = 0
|
||||
for stack, index in @input
|
||||
if ItemSlug.equal stack.itemSlug, itemSlug
|
||||
total += @_quantities[index] * stack.quantity
|
||||
|
||||
return total
|
||||
|
||||
hasAllTools: (modPack)->
|
||||
modPack ?= @modVersion?.mod?.modPack
|
||||
return true unless modPack?
|
||||
|
||||
for stack in @tools
|
||||
return false unless modPack.findItem stack.itemSlug
|
||||
return true
|
||||
|
||||
isConditionSatisfied: (modPack)->
|
||||
return true unless @condition?
|
||||
modPack ?= @modVersion?.mod?.modPack
|
||||
|
||||
result = false
|
||||
if @condition.verb is 'item'
|
||||
if modPack?.findItemByName(@condition.noun)?
|
||||
result = true
|
||||
else if @condition.verb is 'mod'
|
||||
modPack.eachMod (mod)=>
|
||||
if mod.name is @condition.noun
|
||||
result = true
|
||||
|
||||
if @condition.inverted then result = not result
|
||||
return result
|
||||
|
||||
isPassThroughFor: (itemSlug)->
|
||||
return @getQuantityProduced(itemSlug) is @getQuantityRequired(itemSlug)
|
||||
|
||||
produces: (itemSlug)->
|
||||
if not @_produces?
|
||||
@_produces = {}
|
||||
|
||||
for stack in @output
|
||||
actuallyProduces = not @isPassThroughFor stack.itemSlug
|
||||
@_produces[stack.itemSlug.qualified] = actuallyProduces
|
||||
|
||||
result = @_produces[itemSlug.qualified] or @_produces[itemSlug.item]
|
||||
return result
|
||||
|
||||
requires: (itemSlug)->
|
||||
for stack in @input
|
||||
if stack.itemSlug.matches itemSlug
|
||||
return true
|
||||
return false
|
||||
|
||||
requiresTool: (itemSlug)->
|
||||
for stack in @tools
|
||||
if stack.itemSlug.matches itemSlug
|
||||
return true
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
slug:
|
||||
get: ->
|
||||
if not @_slug?
|
||||
builder = new StringBuilder
|
||||
delimiterNeeded = false
|
||||
for stack in @input
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
|
||||
builder.push '>'
|
||||
builder.push @pattern
|
||||
builder.push '>'
|
||||
for stack in @tools
|
||||
builder.push stack.itemSlug.qualified
|
||||
builder.push '>'
|
||||
|
||||
delimiterNeeded = false
|
||||
for stack in @output
|
||||
if delimiterNeeded then builder.push ','
|
||||
delimiterNeeded = true
|
||||
|
||||
if stack.quantity > 1 then builder.push stack.quantity, ' '
|
||||
builder.push stack.itemSlug.qualified
|
||||
|
||||
@_slug = builder.toString()
|
||||
|
||||
return @_slug
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") { name:", @name]
|
||||
|
||||
result.push ", input:["
|
||||
needsDelimiter = false
|
||||
for stack in @input
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push @getQuantityRequired stack.itemSlug
|
||||
result.push ' '
|
||||
result.push stack.itemSlug
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push ", output:["
|
||||
needsDelimiter = false
|
||||
for stack in @output
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push @getQuantityProduced stack.itemSlug
|
||||
result.push ' '
|
||||
result.push stack.itemSlug
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
if @tools.length > 0
|
||||
result.push ", tools:["
|
||||
needsDelimiter = false
|
||||
for stack in @tools
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push stack.toString()
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push '}'
|
||||
return result.join ''
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeQuantities: (pattern)->
|
||||
quantityMap = {}
|
||||
|
||||
index = 0
|
||||
while index < pattern.length
|
||||
c = pattern[index]
|
||||
index += 1
|
||||
|
||||
continue if c is '.'
|
||||
continue if c is ' '
|
||||
|
||||
if quantityMap[c]?
|
||||
quantityMap[c] += 1
|
||||
else
|
||||
quantityMap[c] = 1
|
||||
|
||||
@_quantities = []
|
||||
for i in [0...@input.length]
|
||||
@_quantities.push quantityMap["#{i}"]
|
||||
|
||||
_parsePattern: (pattern)->
|
||||
return unless pattern?
|
||||
|
||||
pattern = pattern.replace /\ /g, ''
|
||||
return if pattern.length is 0
|
||||
|
||||
pattern = pattern.replace /[^0-9]/g, '.'
|
||||
|
||||
array = pattern.split ''
|
||||
array = array[0...9]
|
||||
while array.length isnt 9
|
||||
array.push '.'
|
||||
|
||||
pattern = array.join ''
|
||||
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
|
||||
return pattern
|
||||
@@ -1,33 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - simple_stack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
ItemSlug = require './item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Stack
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
|
||||
attributes.quantity ?= 1
|
||||
|
||||
@itemSlug = attributes.itemSlug
|
||||
@quantity = attributes.quantity
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@compare: (a, b)->
|
||||
if a? and not b? then return -1
|
||||
if not a? and b? then return +1
|
||||
if a.quantity isnt b.quantity
|
||||
return if a.quantity > b.quantity then -1 else +1
|
||||
return ItemSlug.compare a.itemSlug, b.itemSlug
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@quantity} #{@itemSlug}"
|
||||
@@ -1,23 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - stack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Stack extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
|
||||
attributes.quantity ?= 1
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@quantity} #{@itemSlug}"
|
||||
@@ -1,138 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - command_parser_version_base.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CommandParserVersionBase
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
options.showAllErrors ?= false
|
||||
|
||||
@_model = options.model
|
||||
@_showAllErrors = options.showAllErrors
|
||||
@errors = []
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@COMMAND = /\ *([^:]*):?(.*)/
|
||||
|
||||
@COMMENT = /([^\\]?)#.*/
|
||||
|
||||
@simplify: (text)->
|
||||
text = text.trim()
|
||||
text = text.replace /\ */g , ' '
|
||||
text = text.replace /\n/g, ';'
|
||||
text = text.replace /; */g, ';'
|
||||
text = text.replace /;;*/g, ';'
|
||||
text = text.replace /: /g, ':'
|
||||
return text
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
parse: (text)->
|
||||
@_rawData = {}
|
||||
@_lineNumber = 1
|
||||
@errors = []
|
||||
|
||||
@_lines = text.split '\n'
|
||||
@_lineNumber = 0
|
||||
while @_lineNumber < @_lines.length
|
||||
@_lineNumber += 1
|
||||
commands = @_parseLine @_lines[@_lineNumber - 1]
|
||||
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'
|
||||
|
||||
_command_schema: -> # do nothing
|
||||
|
||||
# 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 CommandParserVersionBase.COMMENT, '$1'
|
||||
line = line.trim()
|
||||
return [] if line.length is 0
|
||||
|
||||
[line, hereDoc] = @_parseHereDoc line
|
||||
|
||||
lineParts = (part.trim() for part in line.split(';'))
|
||||
commands = []
|
||||
for linePart in lineParts
|
||||
continue if linePart.length is 0
|
||||
|
||||
match = CommandParserVersionBase.COMMAND.exec linePart
|
||||
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
|
||||
|
||||
args = []
|
||||
args = (s for s in match[2].split(',') when s.length > 0) if match[2]?
|
||||
args = (s.trim() for s in args)
|
||||
args = (s for s in args when s.length > 0)
|
||||
args.push hereDoc if hereDoc?
|
||||
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
|
||||
@errors.push e
|
||||
logger.error -> e.message
|
||||
|
||||
_parseHereDoc: (line)->
|
||||
hereDocIndex = line.indexOf '<<-'
|
||||
return [line, null] unless hereDocIndex isnt -1
|
||||
|
||||
hereDocStopText = line[hereDocIndex+3...line.length]
|
||||
line = line[0...hereDocIndex]
|
||||
|
||||
hereDocLines = []
|
||||
while true
|
||||
@_lineNumber += 1
|
||||
break if @_lineNumber >= @_lines.length
|
||||
|
||||
nextLine = @_lines[@_lineNumber-1]
|
||||
break if nextLine.trim() is hereDocStopText
|
||||
hereDocLines.push nextLine
|
||||
|
||||
shortestIndent = Number.MAX_VALUE
|
||||
for hereDocLine in hereDocLines
|
||||
continue if hereDocLine.trim().length is 0
|
||||
shortestIndent = Math.min hereDocLine.match(/( *).*/)[1].length, shortestIndent
|
||||
|
||||
for i in [0...hereDocLines.length]
|
||||
hereDocLines[i] = hereDocLines[i][shortestIndent..]
|
||||
|
||||
return [line, null] unless hereDocLines.length > 0
|
||||
return [line, hereDocLines.join('\n')]
|
||||
@@ -1,48 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - command_parser_version_base.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
parser = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'command_parser_version_base.coffee', ->
|
||||
|
||||
beforeEach -> parser = new CommandParserVersionBase model:{}
|
||||
|
||||
describe '_parseHereDoc', ->
|
||||
|
||||
it 'returns null for non-heredoc lines', ->
|
||||
result = parser._parseHereDoc 'foobar: baz'
|
||||
expect(result[1]).to.be.null
|
||||
|
||||
it 'identifies the right text for a real heredoc', ->
|
||||
parser._lines = ['command: <<-END', 'alpha', 'bravo', 'charlie', 'END', 'command1: arg2']
|
||||
parser._lineNumber = 1
|
||||
|
||||
result = parser._parseHereDoc parser._lines[0]
|
||||
result[0].should.equal 'command: '
|
||||
result[1].should.equal 'alpha\nbravo\ncharlie'
|
||||
|
||||
it 'identifies an empty heredoc', ->
|
||||
parser._lines = ['command: <<-END', 'END']
|
||||
parser._lineNumber = 1
|
||||
|
||||
result = parser._parseHereDoc parser._lines[0]
|
||||
result[0].should.equal 'command: '
|
||||
expect(result[1]).to.be.null
|
||||
|
||||
it 'trims smallest leading whitespace', ->
|
||||
parser._lines = ['command: <<-END', ' alpha', ' bravo', '', ' charlie', 'END', 'command1: arg2']
|
||||
parser._lineNumber = 1
|
||||
|
||||
result = parser._parseHereDoc parser._lines[0]
|
||||
result[0].should.equal 'command: '
|
||||
result[1].should.equal 'alpha\n bravo\n\ncharlie'
|
||||
@@ -1,19 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
ItemParserV1 = require './item_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new ItemParserV1 options
|
||||
@@ -1,72 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_parser_v1.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemParserV1 extends CommandParserVersionBase
|
||||
|
||||
# CommandParserVersionBase Overrides ###########################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
@_buildItem rawData, model
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
builder.line 'schema: ', 1
|
||||
builder.line()
|
||||
|
||||
@_unparseItem builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_description: (textParts...)->
|
||||
if not @_rawData.description?
|
||||
@_rawData.description = ''
|
||||
else
|
||||
@_rawData.description += '\n'
|
||||
@_rawData.description += textParts.join ', '
|
||||
|
||||
_command_officialUrl: (officialUrl)->
|
||||
if @_rawData.officialUrl? then throw new Error 'duplicate declaration of "officialUrl"'
|
||||
if not officialUrl? or (officialUrl.length is 0) then throw new Error 'officialUrl cannot be empty'
|
||||
@_rawData.officialUrl = officialUrl
|
||||
|
||||
_command_video: (youTubeId, nameParts...)->
|
||||
if not youTubeId?.length then throw new Error 'video declaration requires a YouTubeID'
|
||||
name = nameParts.join ', '
|
||||
if not name?.length then throw new Error 'video declaration requires a name'
|
||||
|
||||
@_rawData.videos ?= []
|
||||
@_rawData.videos.push youTubeId:youTubeId, name:name
|
||||
|
||||
# Object Building Methods ######################################################################
|
||||
|
||||
_buildItem: (rawData, model)->
|
||||
model.description = rawData.description if rawData.description?
|
||||
model.officialUrl = rawData.officialUrl if rawData.officialUrl?
|
||||
model.videos = rawData.videos if rawData.videos?
|
||||
|
||||
# Un-parsing Methods ###########################################################################
|
||||
|
||||
_unparseItem: (builder, model)->
|
||||
if model.officialUrl?
|
||||
builder.line 'officialUrl: ', model.officialUrl
|
||||
builder.line()
|
||||
|
||||
if model.description?
|
||||
if model.description.indexOf('\n') isnt -1
|
||||
builder.line 'description: <<-END'
|
||||
builder.line model.description
|
||||
builder.line 'END'
|
||||
else
|
||||
builder.line 'description: ', model.description
|
||||
builder.line()
|
||||
|
||||
for video in model.videos
|
||||
builder.line 'video: ', video.youTubeId, ', ', video.name
|
||||
builder.line()
|
||||
@@ -1,99 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item_parser_v1.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require '../game/item'
|
||||
ItemParserV1 = require './item_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
baseText = item = parser = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'item_parser_v1.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
item = new Item name:'alpha'
|
||||
parser = new ItemParserV1 model:item
|
||||
|
||||
describe 'officialUrl', ->
|
||||
|
||||
it 'may be omitted', ->
|
||||
parser.parse 'schema: 1\nvideo: youtubeid, Video Alpha\ndescription: Bravo, Charlie'
|
||||
expect(item.officialUrl).to.be.null
|
||||
|
||||
it 'is assigned properly when given', ->
|
||||
parser.parse 'schema: 1\nofficialUrl: http://testurl.com'
|
||||
item.officialUrl.should.equal 'http://testurl.com'
|
||||
|
||||
it 'does not allow duplicate declarations', ->
|
||||
func = -> parser.parse 'schema: 1\nofficialUrl: http://testurl.com\nofficialUrl: http://testurl2.com'
|
||||
expect(func).to.throw Error, 'duplicate'
|
||||
|
||||
it 'does not allow an empty value if given', ->
|
||||
func = -> parser.parse 'schema: 1\nofficialUrl:'
|
||||
expect(func).to.throw Error, 'empty'
|
||||
|
||||
describe 'description', ->
|
||||
|
||||
it 'may be omitted', ->
|
||||
parser.parse 'schema: 1\nvideo: youTubeId, Video Alpha\nofficialUrl: http://testurl.com'
|
||||
expect(item.description).to.be.null
|
||||
|
||||
it 'is assigned properly when given', ->
|
||||
parser.parse 'schema: 1\ndescription: Alpha Bravo Charlie'
|
||||
item.description.should.equal 'Alpha Bravo Charlie'
|
||||
|
||||
it 'can be a heredoc', ->
|
||||
parser.parse 'schema: 1\ndescription: <<-END\nAlpha\nBravo\nCharlie\nEND'
|
||||
item.description.should.equal 'Alpha\nBravo\nCharlie'
|
||||
|
||||
it 'concatenates multiple declarations', ->
|
||||
parser.parse 'schema: 1\ndescription: Alpha\ndescription: Bravo'
|
||||
item.description.should.equal 'Alpha\nBravo'
|
||||
|
||||
describe 'video', ->
|
||||
|
||||
it 'may be omitted', ->
|
||||
parser.parse 'schema: 1\nofficialUrl: http://testurl.com\ndescription: Alpha Bravo Charlie'
|
||||
item.videos.should.eql []
|
||||
|
||||
it 'is assigned properly when given', ->
|
||||
parser.parse 'schema: 1\nvideo: youtubeid, Alpha Bravo'
|
||||
item.videos[0].should.eql youTubeId:'youtubeid', name:'Alpha Bravo'
|
||||
item.videos.length.should.equal 1
|
||||
|
||||
it 'may be included multiple times', ->
|
||||
parser.parse 'schema: 1\nvideo: youtubeid1, Alpha\nvideo: youtubeid2, Bravo'
|
||||
item.videos[0].should.eql youTubeId:'youtubeid1', name:'Alpha'
|
||||
item.videos[1].should.eql youTubeId:'youtubeid2', name:'Bravo'
|
||||
item.videos.length.should.equal 2
|
||||
|
||||
it 'requires a YouTubeId and name', ->
|
||||
func = -> parser.parse 'schema: 1\nvideo: alpha'
|
||||
expect(func).to.throw Error, 'requires a name'
|
||||
|
||||
describe 'unparsing', ->
|
||||
|
||||
it 'can round-trip a fully described item', ->
|
||||
text = """
|
||||
schema: 1
|
||||
|
||||
officialUrl: http://testurl.com
|
||||
|
||||
description: <<-END
|
||||
Alpha
|
||||
Bravo
|
||||
END
|
||||
|
||||
video: youtubeid1, Alpha Bravo
|
||||
video: youtubeid2, Charlie Delta
|
||||
|
||||
|
||||
"""
|
||||
parser.parse text
|
||||
parser.unparse().should.equal text
|
||||
@@ -1,19 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
ModParserV1 = require './mod_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new ModParserV1 options
|
||||
@@ -1,94 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_parser_v1.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
Mod = require '../game/mod'
|
||||
ModVersion = require '../game/mod_version'
|
||||
Tutorial = require '../site/tutorial'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModParserV1 extends CommandParserVersionBase
|
||||
|
||||
# CommandParserVersionBase Overrides ###########################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
@_buildMod rawData, model
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
@_unparseMod builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_author: (authorParts...)->
|
||||
if @_rawData.author? then throw new Error 'duplicate declaration of "author"'
|
||||
author = authorParts.join ', '
|
||||
if author.length is 0 then throw new Error '"author" cannot be empty, but may be omitted'
|
||||
|
||||
@_rawData.author = author
|
||||
|
||||
_command_description: (descriptionParts...)->
|
||||
if @_rawData.description? then throw new Error 'duplicate declaration of "description"'
|
||||
description = descriptionParts.join ', '
|
||||
if description.length is 0 then throw new Error '"description" cannot be empty, but may be omitted'
|
||||
|
||||
@_rawData.description = description
|
||||
|
||||
_command_documentationUrl: (documentationUrl)->
|
||||
documentationUrl ?= ''
|
||||
if @_rawData.documentationUrl? then throw new Error 'duplicate declaration of "documentationUrl"'
|
||||
if documentationUrl.length is 0 then throw new Error 'documentationUrl cannot be empty (omit it instead)'
|
||||
@_rawData.documentationUrl = documentationUrl
|
||||
|
||||
_command_downloadUrl: (downloadUrl)->
|
||||
if @_rawData.downloadUrl? then throw new Error 'duplicate declaration of "downloadUrl"'
|
||||
if downloadUrl.length is 0 then throw new Error 'downloadUrl cannot be empty (omit it instead)'
|
||||
@_rawData.downloadUrl = downloadUrl
|
||||
|
||||
_command_homePageUrl: (homePageUrl='')->
|
||||
if @_rawData.homePageUrl? then throw new Error 'duplicate declaration of "homePageUrl"'
|
||||
if homePageUrl.length is 0 then throw new Error 'homePageUrl cannot be empty'
|
||||
|
||||
@_rawData.homePageUrl = homePageUrl
|
||||
|
||||
_command_name: (name)->
|
||||
if @_rawData.name? then throw new Error 'duplicate declaration of "name"'
|
||||
if name.length is 0 then throw new Error '"name" cannot be empty'
|
||||
@_rawData.name = name
|
||||
|
||||
_command_tutorial: (nameParts...)->
|
||||
name = nameParts.join(', ').trim()
|
||||
if name.length is 0 then throw new Error '"name" cannot be empty'
|
||||
@_rawData.tutorialNames ?= []
|
||||
@_rawData.tutorialNames.push name
|
||||
|
||||
_command_version: (version='')->
|
||||
if version.length is 0 then throw new Error 'version cannot be empty'
|
||||
|
||||
@_rawData.versions ?= []
|
||||
@_rawData.versions.push version
|
||||
|
||||
# Object Building Methods ######################################################################
|
||||
|
||||
_buildMod: (rawData, model)->
|
||||
if not rawData.name? then throw new Error 'the "name" declaration is required'
|
||||
if not rawData.homePageUrl? then throw new Error 'the "homePageUrl" declaration is required'
|
||||
if not rawData.versions? then throw new Error 'at least one "version" declaration is required'
|
||||
|
||||
model.author = rawData.author if rawData.author?
|
||||
model.description = rawData.description if rawData.description?
|
||||
model.documentationUrl = rawData.documentationUrl if rawData.documentationUrl?
|
||||
model.downloadUrl = rawData.downloadUrl if rawData.downloadUrl?
|
||||
model.name = rawData.name
|
||||
model.homePageUrl = rawData.homePageUrl
|
||||
|
||||
if rawData.tutorialNames?
|
||||
for tutorialName in rawData.tutorialNames
|
||||
model.addTutorial new Tutorial name:tutorialName
|
||||
|
||||
for version in rawData.versions
|
||||
model.addModVersion new ModVersion modSlug:model.slug, version:version
|
||||
@@ -1,19 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_version_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
ModVersionParserV1 = require './mod_version_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModVersionParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new ModVersionParserV1 options
|
||||
@@ -1,380 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_version_parser_v1.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
Item = require '../game/item'
|
||||
ItemSlug = require '../game/item_slug'
|
||||
ModVersion = require '../game/mod_version'
|
||||
Multiblock = require '../game/multiblock'
|
||||
Recipe = require '../game/recipe'
|
||||
Stack = require '../game/simple_stack'
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModVersionParserV1 extends CommandParserVersionBase
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@INTEGER = /[0-9]+/
|
||||
|
||||
@PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/
|
||||
|
||||
@STACK = /^([0-9]+) +(.*)$/
|
||||
|
||||
# CommandParserVersionBase Overrides ###########################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
@_buildModVersion rawData, model
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
@_unparseModVersion builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_extras: (extraTerms...)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "extras" before "recipe"'
|
||||
if @_recipeData.output.length isnt 1 then throw new Error 'duplicate declaration of "extras"'
|
||||
|
||||
for term in extraTerms
|
||||
@_recipeData.output.push @_parseStack term
|
||||
|
||||
_command_gatherable: (gatherable)->
|
||||
if not @_itemData? then throw new Error 'cannot declare "gatherable" before "item"'
|
||||
if @_itemData.gatherable? then throw new Error 'duplicate declaration of "gatherable"'
|
||||
if not (gatherable in ['yes', 'no']) then throw new Error 'gatherable must be either "yes" or "no"'
|
||||
|
||||
@_itemData.gatherable = (gatherable is 'yes')
|
||||
|
||||
_command_group: (group)->
|
||||
if group.length is 0 then throw new Error 'a group name cannot be empty'
|
||||
@_rawData.group = group
|
||||
|
||||
_command_item: (name='')->
|
||||
if not name.length > 0 then throw new Error 'the item name cannot be empty'
|
||||
|
||||
@_itemData = name:name, line:@_lineNumber, group:@_rawData.group, type:'new'
|
||||
@_rawData.items ?= {}
|
||||
@_rawData.items[name] = @_itemData
|
||||
|
||||
@_recipeData = null
|
||||
|
||||
_command_ignoreDuringCrafting: (value)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "ignoreDuringCrafting" before "recipe"'
|
||||
if @_recipeData.ignoreDuringCrafting? then throw new Error 'duplicate declaration of "ignoreDuringCrafting"'
|
||||
if not (value in ['yes', 'no']) then throw new Error 'ignoreDuringCrafting must be either "yes" or "no"'
|
||||
|
||||
@_recipeData.ignoreDuringCrafting = (value is 'yes')
|
||||
|
||||
_command_input: (stackDescriptions...)->
|
||||
activeData = if @_recipeData? then @_recipeData else @_multiblockData
|
||||
|
||||
if not activeData? then throw new Error 'cannot declare "input" before "recipe" or "multiblock"'
|
||||
if activeData.input.length isnt 0 then throw new Error 'duplicate declaration of "input"'
|
||||
|
||||
for stackDescription in stackDescriptions
|
||||
activeData.input.push @_parseStack stackDescription
|
||||
|
||||
_command_layer: (layerText)->
|
||||
if not @_multiblockData? then throw new Error 'cannot declare "layer" before "multiblock"'
|
||||
if not layerText? then throw new Error 'cannot have an empty layer'
|
||||
if layerText.length is 0 then throw new Error 'cannot have an empty layer'
|
||||
|
||||
@_multiblockData.layers.push layerText
|
||||
|
||||
_command_multiblock: ->
|
||||
if not @_itemData? then throw new Error 'cannot declare "multiblock" before "item"'
|
||||
if @_itemData.multiblockData? then throw new Error 'duplicate declaration of "multiblock"'
|
||||
|
||||
@_recipeData = null
|
||||
@_multiblockData = input:[], layers:[], line:@_lineNumber
|
||||
@_itemData.multiblockData = @_multiblockData
|
||||
|
||||
_command_onlyIf: (condition)->
|
||||
words = condition.split ' '
|
||||
if words.length < 2 then throw new Error 'condition must include a verb followed by a noun'
|
||||
|
||||
inverted = false
|
||||
if words[0] is 'not'
|
||||
inverted = true
|
||||
words.shift()
|
||||
|
||||
verb = words[0]
|
||||
noun = words[1..].join ' '
|
||||
|
||||
if not (verb in ['item', 'mod']) then throw new Error "unknown verb: #{verb}"
|
||||
@_recipeData.condition = verb:verb, noun:noun, inverted:inverted
|
||||
|
||||
_command_pattern: (pattern='')->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "pattern" before "recipe"'
|
||||
if @_recipeData.pattern? then throw new Error 'duplicate declaration of "pattern"'
|
||||
if not ModVersionParserV1.PATTERN.test pattern
|
||||
throw new Error 'a pattern must have 9 digits using 0-9 for items and "." for an empty spot;
|
||||
spaces are optional'
|
||||
|
||||
@_recipeData.pattern = pattern
|
||||
|
||||
_command_quantity: (quantity)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "quantity" before "recipe"'
|
||||
if @_recipeData.quantity? then throw new Error 'duplicate declaration of "quantity"'
|
||||
if not ModVersionParserV1.INTEGER.test(quantity) then throw new Error 'quantity must be an integer'
|
||||
|
||||
@_recipeData.quantity = quantity
|
||||
@_recipeData.output[0].quantity = parseInt quantity
|
||||
|
||||
_command_recipe: ->
|
||||
if not @_itemData? then throw new Error 'cannot delcare "recipe" before "item"'
|
||||
|
||||
@_multiblockData = null
|
||||
@_recipeData = line:@_lineNumber, input:[], output:[{quantity:1, name:@_itemData.name}], tools:[]
|
||||
@_itemData.recipes ?= []
|
||||
@_itemData.recipes.push @_recipeData
|
||||
|
||||
_command_tools: (toolNames...)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "tools" before "recipe"'
|
||||
if @_recipeData.tools.length isnt 0 then throw new Error 'duplicate declaration of "tools"'
|
||||
|
||||
for name in toolNames
|
||||
if name.length is 0 then throw new Error 'tool names cannot be empty'
|
||||
@_recipeData.tools.push name:name, quantity:1
|
||||
|
||||
_command_update: (name='')->
|
||||
if not name.length > 0 then throw new 'the item name cannot be empty'
|
||||
|
||||
@_itemData = name:name, line:@_lineNumber, type:'update'
|
||||
@_recipeData = null
|
||||
|
||||
@_rawData.items ?= {}
|
||||
@_rawData.items[name] = @_itemData
|
||||
|
||||
# Parsing Helpers ##############################################################################
|
||||
|
||||
_parseStack: (stackText)->
|
||||
match = ModVersionParserV1.STACK.exec stackText
|
||||
if match?
|
||||
return quantity:parseInt(match[1]), name:match[2]
|
||||
else
|
||||
return quantity:1, name:stackText
|
||||
|
||||
# Object Creation Methods ######################################################################
|
||||
|
||||
_buildModVersion: (modVersionData, modVersion)->
|
||||
modVersionData.items ?= []
|
||||
|
||||
for itemName, itemData of modVersionData.items
|
||||
@_handleErrors @_buildItem, modVersion, itemData
|
||||
|
||||
modVersion.sort()
|
||||
return modVersion
|
||||
|
||||
_buildItem: (modVersion, itemData)->
|
||||
@_lineNumber = itemData.line
|
||||
itemData.gatherable ?= false
|
||||
itemData.ignoreDuringCrafting ?= false
|
||||
itemData.recipes ?= []
|
||||
|
||||
if itemData.type is 'new'
|
||||
item = new Item
|
||||
name: itemData.name,
|
||||
ignoreDuringCrafting: itemData.ignoreDuringCrafting,
|
||||
isGatherable: itemData.gatherable,
|
||||
group: itemData.group
|
||||
modVersion.addItem item
|
||||
itemData.slug = item.slug
|
||||
else
|
||||
itemData.slug = ItemSlug.slugify itemData.name
|
||||
modVersion.registerName itemData.slug, itemData.name
|
||||
|
||||
if itemData.multiblockData
|
||||
item.multiblock = @_handleErrors @_buildMultiblock, modVersion, item, itemData.multiblockData
|
||||
|
||||
for recipeData in itemData.recipes
|
||||
@_handleErrors @_buildRecipe, modVersion, itemData, recipeData
|
||||
|
||||
return item
|
||||
|
||||
_buildMultiblock: (modVersion, item, multiblockData)->
|
||||
@_lineNumber = multiblockData.line
|
||||
if multiblockData.layers.length is 0 then throw new Error '"multiblock" requires at least one "layer"'
|
||||
if multiblockData.input.length is 0 then throw new Error '"multiblock" requires at least one "input"'
|
||||
|
||||
input = @_buildStackList modVersion, multiblockData.input
|
||||
multiblock = new Multiblock input:input, layers:multiblockData.layers
|
||||
return multiblock
|
||||
|
||||
_buildRecipe: (modVersion, itemData, recipeData)->
|
||||
@_lineNumber = recipeData.line
|
||||
if recipeData.input.length is 0 then throw new Error 'the "input" declaration is required'
|
||||
if not recipeData.pattern? then throw new Error 'the "pattern" declaration is required'
|
||||
|
||||
recipe = new Recipe
|
||||
condition: recipeData.condition
|
||||
ignoreDuringCrafting: recipeData.ignoreDuringCrafting
|
||||
input: @_buildStackList modVersion, recipeData.input, recipeData.pattern
|
||||
output: @_buildStackList modVersion, recipeData.output
|
||||
pattern: recipeData.pattern
|
||||
tools: @_buildStackList modVersion, recipeData.tools
|
||||
|
||||
modVersion.addRecipe recipe
|
||||
return recipe
|
||||
|
||||
_buildStackList: (modVersion, data, pattern=null)->
|
||||
createSlug = (name)=>
|
||||
item = @_rawData.items[name]
|
||||
if item? and item.type isnt 'update'
|
||||
slug = new ItemSlug modVersion.modSlug, _.slugify name
|
||||
else
|
||||
slug = new ItemSlug name
|
||||
modVersion.registerName slug, name
|
||||
return slug
|
||||
|
||||
stacks = []
|
||||
for stackData in data
|
||||
itemSlug = createSlug stackData.name
|
||||
stacks.push new Stack itemSlug:itemSlug, quantity:stackData.quantity
|
||||
|
||||
if pattern?
|
||||
expectedIndexes = _.reduce [0...stacks.length], ((obj, i)-> obj[i] = true; return obj), {}
|
||||
for c in pattern
|
||||
continue if c is '.'
|
||||
continue if c is ' '
|
||||
delete expectedIndexes[c]
|
||||
if not stacks[parseInt(c)]? then throw new Error "there is no item #{c} in this recipe"
|
||||
|
||||
unusedNames = _.map(_.keys(expectedIndexes), ((i)-> data[parseInt(i)].name))
|
||||
if unusedNames.length > 1
|
||||
throw new Error "#{unusedNames.join(', ')} are listed for this recipe, but do not appear in the pattern"
|
||||
else if unusedNames.length is 1
|
||||
throw new Error "#{unusedNames[0]} is listed for this recipe, but does not appear in the pattern"
|
||||
|
||||
return stacks
|
||||
|
||||
# Un-parsing Methods ###########################################################################
|
||||
|
||||
_unparseModVersion: (builder, modVersion)->
|
||||
builder
|
||||
.line 'schema: ', 1
|
||||
.line()
|
||||
|
||||
modVersion.eachGroup (group)=>
|
||||
@_unparseGroup builder, modVersion, group
|
||||
|
||||
externalRecipes = modVersion.findExternalRecipes()
|
||||
keys = _.keys(externalRecipes).sort()
|
||||
for itemSlugText in keys
|
||||
recipeList = externalRecipes[itemSlugText]
|
||||
|
||||
builder
|
||||
.line 'update: ', modVersion.findName ItemSlug.slugify(itemSlugText)
|
||||
.indent()
|
||||
.loop(recipeList, delimiter:'', onEach:(b, r)=> @_unparseRecipe(b, r))
|
||||
.outdent()
|
||||
.line()
|
||||
|
||||
_unparseGroup: (builder, modVersion, group)->
|
||||
if group isnt Item.Group.Other
|
||||
builder
|
||||
.line 'group: ', group
|
||||
.line()
|
||||
.indent()
|
||||
|
||||
modVersion.eachItemInGroup group, (item)=>
|
||||
@_unparseItem builder, modVersion, item
|
||||
builder.line()
|
||||
|
||||
if group isnt Item.Group.Other
|
||||
builder.outdent()
|
||||
|
||||
_unparseItem: (builder, modVersion, item)->
|
||||
recipes = modVersion.findRecipes item.slug, [], onlyPrimary:true
|
||||
|
||||
builder
|
||||
.line 'item: ', item.name
|
||||
.indent()
|
||||
.onlyIf item.isGatherable, => builder.line 'gatherable: yes'
|
||||
.onlyIf item.multiblock?, =>
|
||||
builder
|
||||
.indent()
|
||||
.call => @_unparseMultiblock builder, item.multiblock
|
||||
.outdent()
|
||||
.onlyIf recipes.length > 0, =>
|
||||
builder.loop recipes, delimiter:'', onEach:(b, r)=> @_unparseRecipe(b, r)
|
||||
.outdent()
|
||||
|
||||
_unparseMultiblock: (builder, multiblock)->
|
||||
builder
|
||||
.line 'multiblock:'
|
||||
.indent()
|
||||
.push "input: "
|
||||
.call => @_unparseStackList builder, multiblock.input
|
||||
.push ";\n"
|
||||
.loop(multiblock.layers, delimiter:'', onEach:(builder, layer)=> builder.line "layer: #{layer}")
|
||||
.outdent()
|
||||
|
||||
_unparseRecipe: (builder, recipe)->
|
||||
inputStacks = recipe.input[..]
|
||||
inputStacks.sort (a, b)-> Stack.compare a, b
|
||||
inputNames = []
|
||||
for stack in inputStacks
|
||||
name = builder.context.findName stack.itemSlug
|
||||
if stack.quantity > 1
|
||||
inputNames.push "#{stack.quantity} #{name}"
|
||||
else
|
||||
inputNames.push name
|
||||
|
||||
patternMap = {'.', '.'}
|
||||
for i in [0...recipe.input.length]
|
||||
stack = recipe.input[i]
|
||||
name = builder.context.findName(stack.itemSlug)
|
||||
name = "#{stack.quantity} #{name}" if stack.quantity > 1
|
||||
patternMap["#{i}"] = "#{inputNames.indexOf name}"
|
||||
|
||||
pattern = recipe.pattern or recipe.defaultPattern
|
||||
newPattern = []
|
||||
for c in pattern.split('')
|
||||
newPattern.push patternMap[c]
|
||||
newPattern = newPattern.join ''
|
||||
newPattern = newPattern.replace /(...)(...)(...)/, '$1 $2 $3'
|
||||
|
||||
quantity = recipe.output[0].quantity
|
||||
|
||||
extraOutputs = recipe.output[0...recipe.output.length]
|
||||
extraOutputs.shift()
|
||||
|
||||
builder
|
||||
.line 'recipe:'
|
||||
.indent()
|
||||
.onlyIf recipe.condition?, =>
|
||||
builder.push 'onlyIf: '
|
||||
.onlyIf recipe.condition.inverted, -> builder.push 'not '
|
||||
.line recipe.condition.verb, ' ', recipe.condition.noun
|
||||
.onlyIf extraOutputs.length > 0, =>
|
||||
builder
|
||||
.push 'extras: '
|
||||
.call => @_unparseStackList builder, extraOutputs
|
||||
.line()
|
||||
.onlyIf recipe.ignoreDuringCrafting, => builder.line 'ignoreDuringCrafting: yes'
|
||||
.push 'input: '
|
||||
.loop inputNames
|
||||
.line()
|
||||
.line 'pattern: ', newPattern
|
||||
.onlyIf quantity > 1, => builder.line 'quantity: ', quantity
|
||||
.onlyIf recipe.tools.length > 0, =>
|
||||
builder
|
||||
.push 'tools: '
|
||||
.call => @_unparseStackList builder, recipe.tools
|
||||
.line()
|
||||
.outdent()
|
||||
|
||||
_unparseStackList: (builder, stackList)->
|
||||
if stackList.length is 1 and stackList[0].quantity is 1
|
||||
builder.push builder.context.findName(stackList[0].itemSlug)
|
||||
else
|
||||
builder.loop stackList, onEach:(b, stack)=>
|
||||
builder
|
||||
.onlyIf stack.quantity > 1, => builder.push stack.quantity, ' '
|
||||
.push builder.context.findName stack.itemSlug
|
||||
@@ -1,344 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_version_parser_v1.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
ItemSlug = require '../game/item_slug'
|
||||
ModVersion = require '../game/mod_version'
|
||||
ModVersionParserV1 = require './mod_version_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
baseText = modVersion = parser = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'mod_version_parser_v1.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
modVersion = new ModVersion modSlug:'test', version:'0.0'
|
||||
parser = new ModVersionParserV1 model:modVersion
|
||||
|
||||
describe 'Item', ->
|
||||
|
||||
it 'allows multiple recipes', ->
|
||||
recipes = "item: Charlie;
|
||||
recipe:; input:Alpha; pattern:... .0. ...;
|
||||
recipe:; input:Bravo; pattern:... 0.0 ...;"
|
||||
modVersion = parser.parse recipes
|
||||
recipes = modVersion.findRecipes ItemSlug.slugify 'test__charlie'
|
||||
recipes[0].input[0].itemSlug.qualified.should.equal 'alpha'
|
||||
recipes[1].input[0].itemSlug.qualified.should.equal 'bravo'
|
||||
|
||||
describe 'name', ->
|
||||
|
||||
it 'adds the name when present', ->
|
||||
modVersion = parser.parse 'item: Charlie'
|
||||
modVersion._items.charlie.name.should.equal 'Charlie'
|
||||
|
||||
it 'requires a non-empty name', ->
|
||||
func = -> parser.parse 'item: \n'
|
||||
expect(func).to.throw Error, 'cannot be empty'
|
||||
|
||||
describe 'gatherable', ->
|
||||
|
||||
it 'adds "gatherable" when present', ->
|
||||
modVersion = parser.parse 'item: Alpha Bravo; gatherable: yes'
|
||||
modVersion._items.alpha_bravo.isGatherable.should.be.true
|
||||
|
||||
it 'does not allow a duplicate "gatherable" declaration', ->
|
||||
func = -> parser.parse 'item: Alpha Bravo; gatherable: yes; gatherable: yes'
|
||||
expect(func).to.throw Error, 'duplicate'
|
||||
|
||||
it 'requires "gatherable" to be "yes" or "no"', ->
|
||||
func = -> parser.parse 'item: Alpha Bravo; gatherable: true'
|
||||
expect(func).to.throw Error, 'gatherable must be'
|
||||
|
||||
it 'does not allow "gatherable" before "item"', ->
|
||||
func = -> parser.parse 'gatherable: yes; item: Alpha Bravo; gatherable: yes'
|
||||
expect(func).to.throw Error, '"gatherable" before "item"'
|
||||
|
||||
describe 'Multiblock', ->
|
||||
|
||||
it 'adds a "multiblock" when present', ->
|
||||
modVersion = parser.parse 'item: Alpha; multiblock:; input:Bravo; layer: 0'
|
||||
item = modVersion.findItemByName 'Alpha'
|
||||
item.multiblock.height.should.equal 1
|
||||
|
||||
it 'requires "item" be declared before "multiblock"', ->
|
||||
func = -> parser.parse "multiblock:"
|
||||
expect(func).to.throw Error, '"multiblock" before "item"'
|
||||
|
||||
it 'prohibits multiple "multiblock" commands per item"', ->
|
||||
func = -> parser.parse "item: Alpha; multiblock:; multiblock:"
|
||||
expect(func).to.throw Error, 'duplicate'
|
||||
|
||||
it 'prohibits multiblocks with no inputs', ->
|
||||
func = -> parser.parse "item: Alpha; multiblock:; layer: 000"
|
||||
expect(func).to.throw Error, 'at least one "input"'
|
||||
|
||||
describe 'layer', ->
|
||||
|
||||
it 'allows multiple "layer" commands', ->
|
||||
modVersion = parser.parse 'item: Alpha; multiblock:; input:Bravo, Charlie; layer: 01 10; layer: 10 01'
|
||||
item = modVersion.findItemByName 'Alpha'
|
||||
item.multiblock.depth.should.equal 2
|
||||
item.multiblock.height.should.equal 2
|
||||
item.multiblock.width.should.equal 2
|
||||
|
||||
it 'prohibits empty layers', ->
|
||||
func = -> parser.parse 'item: Alpha; multiblock:; input: Bravo; layer:; layer: 00 00'
|
||||
expect(func).to.throw Error, 'empty layer'
|
||||
|
||||
it 'prohibits multiblocks with no layers', ->
|
||||
func = -> parser.parse "item: Alpha; multiblock:; input: Bravo"
|
||||
expect(func).to.throw Error, 'at least one "layer"'
|
||||
|
||||
describe 'Recipe', ->
|
||||
|
||||
beforeEach -> baseText = 'item: Charlie; '
|
||||
|
||||
describe 'input', ->
|
||||
|
||||
it 'adds "input" when present', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern: ... 010 ...'
|
||||
charlieSlug = ItemSlug.slugify('test__charlie')
|
||||
slugs = (s.itemSlug.item for s in modVersion.findRecipes(charlieSlug)[0].input)
|
||||
slugs.should.eql ['alpha', 'bravo']
|
||||
|
||||
it 'requires an "input" declaration', ->
|
||||
func = -> parser.parse baseText + 'recipe:; pattern: ... .0. ...'
|
||||
expect(func).to.throw Error, 'the "input" declaration is required'
|
||||
|
||||
it 'does not allow a duplicate "input" declaration', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:....0....; input:Bravo'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "input"'
|
||||
|
||||
it 'does not allow "input" before "recipe"', ->
|
||||
func = -> parser.parse baseText + 'input:Alpha, Bravo; recipe:; pattern:....0....'
|
||||
expect(func).to.throw Error, 'cannot declare "input" before "recipe"'
|
||||
|
||||
it 'registers slugs for each input name', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...'
|
||||
(s.item for s in modVersion._slugs).should.eql ['charlie', 'delta', 'echo', 'foxtrot']
|
||||
|
||||
it 'correctly handles recipes which use the same input multiple times', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha; pattern:.0..0....'
|
||||
recipe = _.values(modVersion._recipes)[0]
|
||||
recipe.getQuantityRequired(ItemSlug.slugify('alpha')).should.equal 2
|
||||
|
||||
it 'allows a quantity for each input', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input: 12 Delta, 3 Echo; pattern:... 0.1 ...'
|
||||
recipe = _.values(modVersion._recipes)[0]
|
||||
recipe.input[0].quantity.should.equal 12
|
||||
recipe.input[0].itemSlug.qualified.should.equal 'delta'
|
||||
recipe.input[1].quantity.should.equal 3
|
||||
recipe.input[1].itemSlug.qualified.should.equal 'echo'
|
||||
|
||||
describe 'pattern', ->
|
||||
|
||||
it 'adds "pattern" when present', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
|
||||
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].pattern.should.equal '... .0. .1.'
|
||||
|
||||
it 'requires a "pattern" declaration', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
|
||||
expect(func).to.throw Error, 'the "pattern" declaration is required'
|
||||
|
||||
it 'does not allow a duplicate "pattern" declaration', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:....0..1.; pattern:01.......'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "pattern"'
|
||||
|
||||
it 'requires pattern to be the right length', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:000'
|
||||
expect(func).to.throw Error, 'a pattern must have'
|
||||
|
||||
it 'requires pattern to only use proper characters', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:abc def ghi'
|
||||
expect(func).to.throw Error, 'a pattern must have'
|
||||
|
||||
it 'requires pattern to only refer to existing items', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:... 010 ...'
|
||||
expect(func).to.throw Error, 'there is no item 1 in this recipe'
|
||||
|
||||
it 'requires all items to appear in the pattern', ->
|
||||
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern: 000 0.0 000'
|
||||
expect(func).to.throw Error, 'Bravo is listed'
|
||||
|
||||
it 'does not allow "pattern" before "recipe"', ->
|
||||
func = -> parser.parse baseText + 'pattern:... .0. ...; recipe:; inputs:Alpha'
|
||||
expect(func).to.throw Error, 'cannot declare "pattern" before "recipe"'
|
||||
|
||||
describe 'quantity', ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = 'item: Charlie; recipe:; input:Alpha; pattern:...0.0...; '
|
||||
|
||||
it 'adds "quantity" when present', ->
|
||||
modVersion = parser.parse baseText + 'quantity: 2'
|
||||
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].output[0].quantity.should.equal 2
|
||||
|
||||
it 'does not allow a duplicate "quantity" declaration', ->
|
||||
func = -> parser.parse baseText + 'quantity:1; quantity:2'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "quantity"'
|
||||
|
||||
it 'requires quantity to be an integer', ->
|
||||
func = -> parser.parse baseText + 'quantity:ten'
|
||||
expect(func).to.throw Error, 'quantity must be an integer'
|
||||
|
||||
it 'assumes a quantity of 1 by default', ->
|
||||
modVersion = parser.parse baseText
|
||||
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].output[0].quantity.should.equal 1
|
||||
|
||||
it 'does not allow "quantity" before recipe', ->
|
||||
func = -> parser.parse 'item:Bravo; quantity:12; recipe:;'
|
||||
expect(func).to.throw Error, 'cannot declare "quantity" before "recipe"'
|
||||
|
||||
describe 'onlyIf', ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = 'item: Charlie; recipe:; input:Alpha; pattern:...0.0...; '
|
||||
|
||||
it 'understands the "item" verb', ->
|
||||
modVersion = parser.parse baseText + 'onlyIf: item Iron Ingot'
|
||||
recipes = []
|
||||
modVersion.eachRecipe (recipe)-> recipes.push recipe
|
||||
recipe = recipes[0]
|
||||
recipe.condition.should.eql verb:'item', noun:'Iron Ingot', inverted:false
|
||||
|
||||
it 'understands the "mod" verb', ->
|
||||
modVersion = parser.parse baseText + 'onlyIf: mod BuildCraft'
|
||||
recipes = []
|
||||
modVersion.eachRecipe (recipe)-> recipes.push recipe
|
||||
recipe = recipes[0]
|
||||
recipe.condition.should.eql verb:'mod', noun:'BuildCraft', inverted:false
|
||||
|
||||
it 'understands inverting verbs', ->
|
||||
modVersion = parser.parse baseText + 'onlyIf: not item Iron Ingot'
|
||||
recipes = []
|
||||
modVersion.eachRecipe (recipe)-> recipes.push recipe
|
||||
recipe = recipes[0]
|
||||
recipe.condition.should.eql verb:'item', noun:'Iron Ingot', inverted:true
|
||||
|
||||
it 'requires at least two words', ->
|
||||
func = -> parser.parse baseText + 'onlyIf: item'
|
||||
expect(func).to.throw Error, 'verb followed by a noun'
|
||||
|
||||
it 'only allows known verbs', ->
|
||||
func = -> parser.parse baseText + 'onlyIf: foo Iron Ingot'
|
||||
expect(func).to.throw Error, 'unknown verb'
|
||||
|
||||
describe 'output', ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = 'item: Delta; item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
|
||||
|
||||
it 'adds a single item as the default output', ->
|
||||
modVersion = parser.parse baseText
|
||||
stack = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].output[0]
|
||||
stack.itemSlug.qualified.should.equal 'test__bravo'
|
||||
stack.quantity.should.equal 1
|
||||
|
||||
it 'can add multiple extras with quantities', ->
|
||||
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
|
||||
output = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].output
|
||||
output[0].itemSlug.qualified.should.equal 'test__bravo'
|
||||
output[0].quantity.should.equal 1
|
||||
output[1].itemSlug.qualified.should.equal 'test__delta'
|
||||
output[1].quantity.should.equal 2
|
||||
output[2].itemSlug.qualified.should.equal 'echo'
|
||||
output[2].quantity.should.equal 4
|
||||
|
||||
it 'does not allow "extras" before "recipe"', ->
|
||||
func = -> parser.parse 'item:Bravo; extras:Charlie'
|
||||
expect(func).to.throw Error, 'cannot declare "extras" before "recipe"'
|
||||
|
||||
it 'registers slugs for each output name', ->
|
||||
modVersion = parser.parse baseText + 'extras:Delta, Echo'
|
||||
(s.qualified for s in modVersion._slugs).should.eql [
|
||||
'test__bravo', 'charlie', 'test__delta', 'echo'
|
||||
]
|
||||
|
||||
it 'does not allow a duplicate "extras" declaration', ->
|
||||
func = -> parser.parse baseText + 'extras:Echo; extras:Delta'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "extras"'
|
||||
|
||||
describe 'tools', ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = 'item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
|
||||
|
||||
it 'can add a single tool', ->
|
||||
modVersion = parser.parse baseText + 'tools: Furnace'
|
||||
modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].tools[0].itemSlug.item.should.equal 'furnace'
|
||||
|
||||
it 'can add multiple tools', ->
|
||||
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
||||
tools = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].tools
|
||||
tools[0].itemSlug.item.should.equal 'crafting_table'
|
||||
tools[1].itemSlug.item.should.equal 'furnace'
|
||||
|
||||
it 'registers slugs for each tool name', ->
|
||||
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
||||
(s.item for s in modVersion._slugs).should.eql ['bravo', 'charlie', 'crafting_table', 'furnace']
|
||||
|
||||
it 'does not allow a duplicate "tools" declaration', ->
|
||||
func = -> parser.parse baseText + 'tools:Crafting Table; tools:Furnace'
|
||||
expect(func).to.throw Error, 'duplicate declaration of "tools"'
|
||||
|
||||
describe "unparsing", ->
|
||||
|
||||
beforeEach ->
|
||||
baseText = """
|
||||
schema: 1
|
||||
|
||||
group: Agriculture
|
||||
|
||||
item: Apple
|
||||
|
||||
item: Baked Potato
|
||||
recipe:
|
||||
input: furnace fuel, Potato
|
||||
pattern: .1. ... .0.
|
||||
tools: Furnace
|
||||
|
||||
item: (filled) Canned Food
|
||||
recipe:
|
||||
input: 4 (Empty) Tin Can, Apple
|
||||
pattern: .1. .0. ...
|
||||
|
||||
item: Pyramid
|
||||
multiblock:
|
||||
input: Cobblestone
|
||||
layer: 000 000 000
|
||||
layer: ... .0. ...
|
||||
|
||||
group: Functional Blocks
|
||||
|
||||
item: Furnace
|
||||
recipe:
|
||||
input: Cobblestone
|
||||
pattern: 000 0.0 000
|
||||
tools: Crafting Table
|
||||
|
||||
update: Iron Ingot
|
||||
recipe:
|
||||
input: furnace fuel, Iron Dust
|
||||
pattern: .1. ... .0.
|
||||
tools: Furnace
|
||||
recipe:
|
||||
onlyIf: item Redstone Furnace
|
||||
input: Iron Ore
|
||||
pattern: ... .0. ...
|
||||
tools: Redstone Furnace
|
||||
"""
|
||||
|
||||
it 'can round-trip a data file', ->
|
||||
text = parser.unparse parser.parse baseText
|
||||
actual = CommandParserVersionBase.simplify text
|
||||
expected = CommandParserVersionBase.simplify baseText
|
||||
|
||||
actual.should.equal expected
|
||||
@@ -1,19 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - tutorial_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
TutorialParserV1 = require './tutorial_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class TutorialParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new TutorialParserV1 options
|
||||
@@ -1,65 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - tutorial_parser_v1.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
Tutorial = require '../site/tutorial'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class TutorialParserV1 extends CommandParserVersionBase
|
||||
|
||||
# CommandParserVersionBase Overrides ###########################################################
|
||||
|
||||
_buildModel: (rawData, model)->
|
||||
@_buildTutorial rawData, model
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
@_unparseTutorial builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_content: (contentParts...)->
|
||||
if not @_rawData.currentSection? then throw new Error 'cannot declare "title" before "section"'
|
||||
if @_rawData.currentSection.content? then throw new Error 'duplicate declaration of content'
|
||||
content = contentParts.join(', ').trim()
|
||||
if not content.length > 0 then throw new Error 'content cannot be empty'
|
||||
|
||||
@_rawData.currentSection.content = content
|
||||
|
||||
_command_officialUrl: (officialUrl)->
|
||||
if @_rawData.officialUrl? then throw new Error 'duplicate declaration of "officialUrl"'
|
||||
if officialUrl.length is 0 then throw new Error 'officialUrl cannot be empty'
|
||||
@_rawData.officialUrl = officialUrl
|
||||
|
||||
_command_section: (textParts...)->
|
||||
@_rawData.sections ?= []
|
||||
@_rawData.sections.push @_rawData.currentSection = {}
|
||||
|
||||
_command_title: (titleParts...)->
|
||||
if not @_rawData.currentSection? then throw new Error 'cannot declare "title" before "section"'
|
||||
if @_rawData.currentSection.title? then throw new Error 'duplicate declaration of title'
|
||||
title = titleParts.join(', ').trim()
|
||||
if not title.length > 0 then throw new Error 'title cannot be empty'
|
||||
|
||||
@_rawData.currentSection.title = title
|
||||
|
||||
_command_video: (youTubeId, nameParts...)->
|
||||
if not youTubeId?.length then throw new Error 'video declaration requires a YouTubeID'
|
||||
name = nameParts.join ', '
|
||||
if not name?.length then throw new Error 'video declaration requires a name'
|
||||
|
||||
@_rawData.videos ?= []
|
||||
@_rawData.videos.push youTubeId:youTubeId, name:name
|
||||
|
||||
# Object Building Methods ######################################################################
|
||||
|
||||
_buildTutorial: (rawData, model)->
|
||||
if not rawData.sections? then throw new Error 'the "section" declaration is required'
|
||||
|
||||
model.officialUrl = rawData.officialUrl
|
||||
model.videos = rawData.videos
|
||||
model.sections = rawData.sections
|
||||
@@ -1,51 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - versioned_parser_base.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class VersionedParserBase
|
||||
|
||||
constructor: (options={})->
|
||||
@_parsers = @_createParsers options
|
||||
@_currentSchema = _.chain(@_parsers).keys().last().value()
|
||||
@errors = []
|
||||
|
||||
# Class Members ################################################################################
|
||||
|
||||
@SCHEMA = /schema: *([0-9]+)/
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
parse: (text)->
|
||||
return unless text?
|
||||
|
||||
schema = @_identifySchema text
|
||||
parser = @_parsers[schema]
|
||||
if not parser? then throw new Error "schema version #{schema} is not supported"
|
||||
|
||||
parser.parse text
|
||||
@errors = parser.errors
|
||||
|
||||
return @_model
|
||||
|
||||
unparse: (schema=null)->
|
||||
schema ?= @_currentSchema
|
||||
|
||||
parser = @_parsers["#{schema}"]
|
||||
if not parser? then throw new Error "version #{schema} is not supported"
|
||||
|
||||
return parser.unparse()
|
||||
|
||||
# Overridable Methods ##########################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
throw new Error 'subclasses must override this method'
|
||||
|
||||
_identifySchema: (text)->
|
||||
match = VersionedParserBase.SCHEMA.exec text
|
||||
if not match? then throw new Error 'missing "schema" declaration'
|
||||
return match[1]
|
||||
@@ -1,33 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - tutorial.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Tutorial extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.name?.length > 0 then throw new Error "attributes.name cannot be empty"
|
||||
attributes.modSlug ?= null
|
||||
attributes.officialUrl ?= null
|
||||
attributes.sections ?= []
|
||||
attributes.slug ?= _.slugify attributes.name
|
||||
attributes.videos ?= []
|
||||
super attributes, options
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
TutorialParser = require '../parsing/tutorial_parser' # to avoid require cycles
|
||||
@_parser ?= new TutorialParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.tutorialData modSlug:@modSlug, tutorialSlug:@slug
|
||||
@@ -1,129 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - base_model.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BaseModel extends Backbone.Model
|
||||
|
||||
@_loadingQueue = []
|
||||
@_isDraining = false
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
options.logEvents ?= true
|
||||
super attributes, options
|
||||
|
||||
makeGetter = (name)-> return -> @get name
|
||||
makeSetter = (name)-> return (value)-> @set name, value
|
||||
for name, value of attributes
|
||||
continue if name is 'id'
|
||||
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
|
||||
|
||||
@fileCache = options.fileCache or null
|
||||
@loading = null
|
||||
@logEvents = options.logEvents or false
|
||||
@state = c.modelState.unloaded
|
||||
|
||||
Object.defineProperties this,
|
||||
isUnloaded: { get:-> @state is c.modelState.unloaded }
|
||||
isLoading: { get:-> @state is c.modelState.loading }
|
||||
isLoaded: { get:-> @state is c.modelState.loaded }
|
||||
isError: { get:-> @state is c.modelState.error }
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onLoadSucceeded: (text, status, xhr)->
|
||||
try
|
||||
@set @parse text
|
||||
|
||||
@state = c.modelState.loaded
|
||||
@trigger c.event.change, this
|
||||
@trigger c.event.sync, this
|
||||
logger.info => "#{@constructor.name}.#{@cid} loaded successfully"
|
||||
catch e
|
||||
logger.error -> "A parsing error occured: #{e.stack}"
|
||||
@onLoadFailed e.message, 'parsing failed', xhr
|
||||
|
||||
onLoadFailed: (error, status, xhr)->
|
||||
@state = c.modelState.error
|
||||
logger.error => "#{@constructor.name}.#{@cid} failed to load: status:#{status}, message:#{error}"
|
||||
@trigger c.event.error, this, error
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
fetch: (options={})->
|
||||
options.force ?= false
|
||||
return if (@isLoading or @isLoaded) and not options.force
|
||||
|
||||
url = @url()
|
||||
logger.info => "#{@constructor.name}.#{@cid} reading from url: #{url}"
|
||||
|
||||
@state = c.modelState.loading
|
||||
@trigger c.event.request, this
|
||||
|
||||
loadFromServer = =>
|
||||
w.promise (resolve, reject)=>
|
||||
$.ajax
|
||||
url: url
|
||||
dataType: 'text'
|
||||
success: (text, status, xhr)=> resolve @onLoadSucceeded text, status, xhr
|
||||
error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr
|
||||
|
||||
if @fileCache?
|
||||
@loading = @fileCache.loading.then =>
|
||||
if @fileCache.hasFile url
|
||||
return @_addToLoadingQueue @fileCache.getFile(url), 'success', {url:url}
|
||||
else
|
||||
@loading = loadFromServer()
|
||||
else
|
||||
@loading = loadFromServer()
|
||||
|
||||
@loading.catch (e)-> # do nothing
|
||||
return @loading
|
||||
|
||||
parse: (text)->
|
||||
return JSON.parse text
|
||||
|
||||
sync: (method, model)->
|
||||
throw new Error "#{@constructor.name}.#{@cid} is not permitted to #{method}"
|
||||
|
||||
trigger: (name, model, args...)->
|
||||
if @logEvents
|
||||
argText = ("#{arg}"[0..50] for arg in args).join ", "
|
||||
logger.trace => "#{@constructor.name}.#{@cid} triggered event #{name} with args: #{argText}"
|
||||
super
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name}.#{@cid}"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_addToLoadingQueue: (text, status, xhr)->
|
||||
deferred = w.defer()
|
||||
|
||||
BaseModel._loadingQueue.push resolve:deferred.resolve, func:(=> @onLoadSucceeded text, status, xhr)
|
||||
@_drainLoadingQueue()
|
||||
return deferred.promise
|
||||
|
||||
_drainLoadingQueue: ->
|
||||
return if @_isDraining
|
||||
@_isDraining = true
|
||||
|
||||
drainDelay = 50
|
||||
drain = =>
|
||||
toLoad = BaseModel._loadingQueue.shift()
|
||||
if not toLoad?
|
||||
@_isDraining = false
|
||||
else
|
||||
toLoad.func()
|
||||
toLoad.resolve(true)
|
||||
|
||||
_.delay drain, drainDelay
|
||||
|
||||
_.delay drain, drainDelay
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - crafting_plan.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Inventory = require "../game/inventory"
|
||||
{StringBuilder} = require "crafting-guide-common"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingPlan
|
||||
|
||||
constructor: (attributes={})->
|
||||
@_id = _.uniqueId "crafting-plan-"
|
||||
@_make = null
|
||||
@_need = null
|
||||
@have = attributes.have
|
||||
@steps = attributes.steps
|
||||
@want = attributes.want
|
||||
|
||||
@_computeResources()
|
||||
@_consolidateSteps()
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
have: # an Inventory specifying what the player already has
|
||||
get: -> return @_have
|
||||
set: (have)->
|
||||
have ?= new Inventory
|
||||
if @_have is have then return
|
||||
if @_have? then throw new Error "have cannot be reassigned"
|
||||
@_have = new Inventory have
|
||||
|
||||
id: # a string uniquely specifying this crafting plan
|
||||
get: -> return @_id
|
||||
set: -> throw new Error "id is not assignabled"
|
||||
|
||||
make: # an Inventory specifying what the results of executing the plan will be
|
||||
get: -> return @_make
|
||||
set: -> throw new Error "make cannot be assigned"
|
||||
|
||||
need: # an Inventory specifying what the player will need to gather before executing the plan
|
||||
get: -> return @_need
|
||||
set: -> throw new Error "need cannot be assigned"
|
||||
|
||||
want: # an Inventory specifying what the player wants to make
|
||||
get: -> return @_want
|
||||
set: (want)->
|
||||
if not want? then throw new Error "want is required"
|
||||
if want.isEmpty then throw new Error "want cannot be empty"
|
||||
if @_want is want then return
|
||||
if @_want? then throw new Error "want cannot be reassigned"
|
||||
@_want = new Inventory want
|
||||
|
||||
steps: # an array of CraftingPlanSteps detailing the steps of the plan
|
||||
get: -> return @_steps
|
||||
set: (steps)->
|
||||
steps ?= []
|
||||
if steps is @_steps then return
|
||||
if @_steps? then throw new Error "steps cannot be reassigned"
|
||||
@_steps = steps
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: (options={})->
|
||||
options.full ?= false
|
||||
|
||||
if options.full
|
||||
b = new StringBuilder
|
||||
b.line "CraftingPlan<", @_id, ">"
|
||||
b.indent()
|
||||
b.line "have: ", @_have.toString full:true
|
||||
b.line "want: ", @_want.toString full:true
|
||||
b.line "need: ", @_need.toString full:true
|
||||
b.line "steps:"
|
||||
b.indent()
|
||||
b.loop @_steps, delimiter:"\n", onEach:(b, step)->
|
||||
b.push step.count, " × ", step.recipe.toString(full:true)
|
||||
b.line()
|
||||
b.outdent()
|
||||
b.line "make: ", @_make.toString full:true
|
||||
b.outdent()
|
||||
|
||||
return b.toString()
|
||||
else
|
||||
return "CraftingPlan:#{@_make.toString(full:true)}<#{@_id}>"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeResources: ->
|
||||
need = new Inventory @_want
|
||||
make = new Inventory @_have
|
||||
steps = @_steps[..].reverse()
|
||||
|
||||
for step in steps
|
||||
step.count = 0
|
||||
|
||||
for productStack in step.recipe.allProducts
|
||||
continue unless need.contains productStack.item
|
||||
productCount = Math.ceil need.getQuantity(productStack.item) / productStack.quantity
|
||||
step.count = Math.max step.count, productCount
|
||||
|
||||
continue unless step.count > 0
|
||||
|
||||
for itemId, item of step.recipe.inputs
|
||||
amountNeeded = step.count * step.recipe.computeQuantityRequired item
|
||||
amountAvailable = make.getQuantity item
|
||||
amountUsed = Math.min amountAvailable, amountNeeded
|
||||
amountMissing = amountNeeded - amountUsed
|
||||
|
||||
make.remove item, amountUsed
|
||||
need.add item, amountMissing
|
||||
|
||||
for productStack in step.recipe.allProducts
|
||||
amountCreated = step.count * productStack.quantity
|
||||
amountNeeded = need.getQuantity productStack.item
|
||||
amountFulfilled = Math.min amountNeeded, amountCreated
|
||||
amountSurplus = amountCreated - amountFulfilled
|
||||
|
||||
need.remove productStack.item, amountFulfilled
|
||||
make.add productStack.item, amountSurplus
|
||||
|
||||
make.merge @_want
|
||||
|
||||
@_make = make
|
||||
@_need = need
|
||||
|
||||
_consolidateSteps: ->
|
||||
steps = []
|
||||
stepsByRecipeId = {}
|
||||
|
||||
for step in @_steps
|
||||
priorStep = stepsByRecipeId[step.recipe.id]
|
||||
if priorStep?
|
||||
priorStep.count += step.count
|
||||
else if step.count > 0
|
||||
steps.push step
|
||||
stepsByRecipeId[step.recipe.id] = step
|
||||
|
||||
@_steps = steps
|
||||
@@ -1,99 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - crafting_plan.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CraftingPlan = require './crafting_plan'
|
||||
CraftingPlanStep = require './crafting_plan_step'
|
||||
Inventory = require '../game/inventory'
|
||||
fixtures = require '../fixtures'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe "CraftingPlan", ->
|
||||
|
||||
beforeEach ->
|
||||
@mod = fixtures.createMod()
|
||||
@want = new Inventory
|
||||
@have = new Inventory
|
||||
|
||||
describe "with a single step plan", ->
|
||||
|
||||
beforeEach ->
|
||||
@oakPlank = fixtures.configureOakPlank @mod
|
||||
@want.add @oakPlank, 11
|
||||
|
||||
@plan = new CraftingPlan want:@want, steps:[
|
||||
new CraftingPlanStep @oakPlank.firstRecipe
|
||||
]
|
||||
|
||||
it "correctly computes the step counts", ->
|
||||
@plan.steps[0].count.should.equal 3
|
||||
|
||||
it "correctly determines the inputs needed", ->
|
||||
@plan.need.toString(full:true).should.equal "3 Oak Wood"
|
||||
|
||||
it "correctly computes the products created", ->
|
||||
@plan.make.toString(full:true).should.equal "12 Oak Planks"
|
||||
|
||||
describe "with a multi-step plan", ->
|
||||
|
||||
beforeEach ->
|
||||
@ironIngot = fixtures.configureIronIngot @mod
|
||||
@ironSword = fixtures.configureIronSword @mod
|
||||
@oakPlank = fixtures.configureOakPlank @mod
|
||||
@stick = fixtures.configureStick @mod
|
||||
|
||||
@want.add @ironSword, 20
|
||||
|
||||
@plan = new CraftingPlan want:@want, steps:[
|
||||
new CraftingPlanStep @oakPlank.firstRecipe
|
||||
new CraftingPlanStep @stick.firstRecipe
|
||||
new CraftingPlanStep @ironIngot.firstRecipe
|
||||
new CraftingPlanStep @ironSword.firstRecipe
|
||||
]
|
||||
|
||||
it "correctly computes the step counts", ->
|
||||
@plan.steps[0].count.should.equal 3
|
||||
@plan.steps[1].count.should.equal 5
|
||||
@plan.steps[2].count.should.equal 5
|
||||
@plan.steps[3].count.should.equal 20
|
||||
|
||||
it "correctly determines the inputs needed", ->
|
||||
@plan.need.toString(full:true).should.equal "5 Coal, 40 Iron Ore, 3 Oak Wood"
|
||||
|
||||
it "correctly computes the products created", ->
|
||||
@plan.make.toString(full:true).should.equal "20 Iron Sword, 2 Oak Planks"
|
||||
|
||||
describe "with a plan which recycles some items", ->
|
||||
|
||||
beforeEach ->
|
||||
@bucket = fixtures.configureBucket @mod
|
||||
@cake = fixtures.configureCake @mod
|
||||
@ironIngot = fixtures.configureIronIngot @mod
|
||||
@milkBucket = fixtures.configureMilkBucket @mod
|
||||
@sugar = fixtures.configureSugar @mod
|
||||
|
||||
@want.add @cake, 2
|
||||
|
||||
@plan = new CraftingPlan want:@want, steps:[
|
||||
new CraftingPlanStep @ironIngot.firstRecipe
|
||||
new CraftingPlanStep @bucket.firstRecipe
|
||||
new CraftingPlanStep @milkBucket.firstRecipe
|
||||
new CraftingPlanStep @sugar.firstRecipe
|
||||
new CraftingPlanStep @cake.firstRecipe
|
||||
]
|
||||
|
||||
it "correctly computes the step counts", ->
|
||||
@plan.steps.length.should.equal 3
|
||||
@plan.steps[0].count.should.equal 6
|
||||
@plan.steps[1].count.should.equal 4
|
||||
@plan.steps[2].count.should.equal 2
|
||||
|
||||
it "correctly determines the inputs needed", ->
|
||||
@plan.need.toString(full:true).should.equal "2 Egg, 6 Milk, 4 Sugar Cane, 6 Wheat"
|
||||
|
||||
it "correctly computes the products created", ->
|
||||
@plan.make.toString(full:true).should.equal "2 Cake"
|
||||
@@ -1,39 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - crafting_plan_step.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingPlanStep
|
||||
|
||||
constructor: (recipe, count=1)->
|
||||
@_id = _.uniqueId "crafting-plan-step-"
|
||||
@recipe = recipe
|
||||
@count = count
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
recipe:
|
||||
get: -> return @_recipe
|
||||
set: (recipe)->
|
||||
if not recipe? then throw new Error 'recipe is required'
|
||||
if @_recipe is recipe then return
|
||||
if @_recipe? then throw new Error 'recipe cannot be reassigned'
|
||||
@_recipe = recipe
|
||||
|
||||
count:
|
||||
get: -> return @_count
|
||||
set: (count)->
|
||||
count = parseInt "#{count}"
|
||||
count = if Number.isNaN(count) then 0 else Math.max 0, count
|
||||
@_count = count
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "CraftingPlanStep:#{@_recipe}×#{@_count}<#{@_id}>"
|
||||
@@ -1,105 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - evaluation.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Evaluation
|
||||
|
||||
constructor: (attributes={})->
|
||||
@evaluator = attributes.evaluator
|
||||
@item = attributes.item if attributes.item?
|
||||
@recipe = attributes.recipe if attributes.recipe?
|
||||
@baseScore = attributes.baseScore
|
||||
|
||||
@_id = _.uniqueId "evaluation-"
|
||||
@_includedTools = {}
|
||||
@_toolScore = null
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
baseScore:
|
||||
get: -> return @_baseScore
|
||||
set: (baseScore)->
|
||||
baseScore = parseFloat "#{baseScore}"
|
||||
baseScore = if Number.isNaN(baseScore) then null else baseScore
|
||||
@_baseScore = baseScore
|
||||
|
||||
evaluator:
|
||||
get: -> return @_evaluator
|
||||
set: (evaluator)->
|
||||
if not evaluator? then throw new Error "evaluator is required"
|
||||
if @_evaluator is evaluator then return
|
||||
if @_evaluator? then throw new Error "evaluator cannot be reassigned"
|
||||
@_evaluator = evaluator
|
||||
|
||||
includedTools:
|
||||
get: -> return @_includedTools
|
||||
set: -> throw new Error "includedTools cannot be assigned"
|
||||
|
||||
item:
|
||||
get: -> return @_item
|
||||
set: (item)->
|
||||
if @_recipe? then throw new Error "this evaluation is for a recipe: cannot set an item"
|
||||
if @_item is item then return
|
||||
if not item? then throw new Error "item cannot be assigned null"
|
||||
if @_item? then throw new Error "item cannot be reassigned"
|
||||
@_item = item
|
||||
|
||||
recipe:
|
||||
get: -> return @_recipe
|
||||
set: (recipe)->
|
||||
if @_item? then throw new Error "this evaluation is for an item: cannot set an recipe"
|
||||
if @_recipe is recipe then return
|
||||
if not recipe? then throw new Error "recipe cannot be assigned null"
|
||||
if @_recipe? then throw new Error "recipe cannot be reassigned"
|
||||
@_recipe = recipe
|
||||
|
||||
toolScore:
|
||||
get: -> @_computeToolScore()
|
||||
set: -> throw new Error "toolScore cannot be assigned"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addIncludedTool: (item)->
|
||||
return if @_includedTools[item.id]?
|
||||
@_includedTools[item.id] = item
|
||||
@_toolScore = null
|
||||
|
||||
addIncludedToolsFrom: (evaluation)->
|
||||
for id, toolItem of evaluation.includedTools
|
||||
@addIncludedTool toolItem
|
||||
|
||||
computeTotalScore: (quantity=1)->
|
||||
if @item?
|
||||
multiplier = quantity
|
||||
else if @recipe?
|
||||
multiplier = Math.ceil 1.0 * quantity / @recipe.output.quantity
|
||||
|
||||
return @baseScore * multiplier + @toolScore
|
||||
|
||||
isToolIncluded: (item)->
|
||||
return @_includedTools[item.id]?
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
obj = if @item? then @item else @recipe
|
||||
return "#{@evaluator.constructor.name}:#{obj}@#{@baseScore}<#{@_id}>"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeToolScore: ->
|
||||
if not @_toolScore?
|
||||
@_toolScore = 0
|
||||
for id, toolItem of @_includedTools
|
||||
evaluation = @evaluator.evaluateItem toolItem
|
||||
continue unless evaluation?.baseScore?
|
||||
@_toolScore += evaluation.baseScore
|
||||
|
||||
return @_toolScore
|
||||
@@ -1,102 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - evaluator.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Evaluation = require './evaluation'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Evaluator
|
||||
|
||||
constructor: ->
|
||||
@_id = _.uniqueId "evaluator-"
|
||||
@_evaluations = {}
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
id:
|
||||
get: -> return @_id
|
||||
set: -> throw new Error "id cannot be assigned"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
evaluateItem: (item)->
|
||||
return null unless item?
|
||||
|
||||
evaluation = @_evaluations[item.id]
|
||||
if not evaluation?
|
||||
evaluation = @_evaluations[item.id] = new Evaluation evaluator:this, item:item
|
||||
|
||||
recipeEvaluation = @_findBestRecipeEvaluationFor item
|
||||
if recipeEvaluation?
|
||||
evaluation.baseScore = recipeEvaluation.baseScore
|
||||
evaluation.addIncludedToolsFrom recipeEvaluation
|
||||
else
|
||||
@_computeGatherableItemScore item, evaluation
|
||||
|
||||
return evaluation
|
||||
|
||||
evaluateRecipe: (recipe)->
|
||||
return null unless recipe?
|
||||
|
||||
evaluation = @_evaluations[recipe.id]
|
||||
if not evaluation?
|
||||
evaluation = @_evaluations[recipe.id] = new Evaluation evaluator:this, recipe:recipe
|
||||
@_computeRecipeScore recipe, evaluation
|
||||
|
||||
for id, item of recipe.inputs
|
||||
inputEvaluation = @evaluateItem item
|
||||
evaluation.addIncludedToolsFrom inputEvaluation
|
||||
|
||||
for id, toolItem of recipe.tools
|
||||
evaluation.addIncludedTool toolItem
|
||||
evaluation.addIncludedToolsFrom @evaluateItem toolItem
|
||||
|
||||
return evaluation
|
||||
|
||||
getOrderedRecipes: (item, quantity=1)->
|
||||
recipes = (recipe for recipeId, recipe of item.recipes)
|
||||
|
||||
recipes.sort (a, b)=>
|
||||
scoreA = @evaluateRecipe(a).computeTotalScore quantity
|
||||
scoreB = @evaluateRecipe(b).computeTotalScore quantity
|
||||
|
||||
if scoreA isnt scoreB
|
||||
return if scoreA < scoreB then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
return recipes
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name}<#{@id}>"
|
||||
|
||||
# Overrideable Methods #########################################################################
|
||||
|
||||
_computeGatherableItemScore: (item, evaluation)->
|
||||
throw new Error "#{@constructor.name} must override _computeGatherableItemScore"
|
||||
|
||||
_computeRecipeScore: (recipe, evaluation)->
|
||||
throw new Error "#{@constructor.name} must override _computeRecipeScore"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_findBestRecipeEvaluationFor: (item)->
|
||||
return null if item.isGatherable
|
||||
result = null
|
||||
|
||||
for id, recipe of item.recipes
|
||||
evaluation = @evaluateRecipe recipe
|
||||
continue unless evaluation?.baseScore?
|
||||
|
||||
if not result? then result = evaluation
|
||||
if evaluation.baseScore < result.baseScore then result = evaluation
|
||||
|
||||
return result
|
||||
@@ -1,92 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - plan_builder.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
CraftingPlan = require "./crafting_plan"
|
||||
CraftingPlanStep = require "./crafting_plan_step"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class PlanBuilder
|
||||
|
||||
constructor: (evaluator)->
|
||||
@evaluator = evaluator
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
evaluator:
|
||||
get: -> return @_evaluator
|
||||
set: (evaluator)->
|
||||
if not evaluator? then throw new Error "evaluator is required"
|
||||
if @_evaluator is evaluator then return
|
||||
if @_evaluator? then throw new Error "evaluator cannot be reassigned"
|
||||
@_evaluator = evaluator
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
createPlan: (want, have)->
|
||||
@_alreadyMaking = {}
|
||||
@_recipesInUse = {}
|
||||
|
||||
stepList = []
|
||||
for itemId, stack of want.stacks
|
||||
steps = @_findStepsForItem stack.item, stack.quantity
|
||||
return null unless steps?
|
||||
|
||||
stepList.push steps
|
||||
|
||||
plan = new CraftingPlan want:want, have:have, steps:_.flatten(stepList)
|
||||
return plan
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_findStepsForItem: (item, quantity=1)->
|
||||
steps = null
|
||||
|
||||
if not @_alreadyMaking[item.id]
|
||||
@_alreadyMaking[item.id] = true
|
||||
|
||||
recipes = @_evaluator.getOrderedRecipes item, quantity
|
||||
if recipes.length is 0
|
||||
steps = []
|
||||
else
|
||||
for recipe in recipes
|
||||
continue if @_recipesInUse[recipe.id]?
|
||||
|
||||
steps = @_findStepsForRecipe recipe, quantity
|
||||
break if steps?
|
||||
|
||||
delete @_alreadyMaking[item.id]
|
||||
|
||||
return steps
|
||||
|
||||
_findStepsForRecipe: (recipe, quantity=1)->
|
||||
steps = null
|
||||
|
||||
if not @_recipesInUse[recipe.id]
|
||||
@_recipesInUse[recipe.id] = true
|
||||
|
||||
invalidRecipe = false
|
||||
for itemId, item of recipe.inputs
|
||||
inputSteps = @_findStepsForItem item, quantity * recipe.computeQuantityRequired(item)
|
||||
if not inputSteps?
|
||||
invalidRecipe = true
|
||||
break
|
||||
|
||||
steps ?= []
|
||||
for step in inputSteps
|
||||
steps.push step
|
||||
|
||||
if invalidRecipe
|
||||
steps = null
|
||||
else
|
||||
steps.push new CraftingPlanStep recipe
|
||||
|
||||
delete @_recipesInUse[recipe.id]
|
||||
|
||||
return steps
|
||||
@@ -1,158 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - plan_builder.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
fixtures = require '../fixtures'
|
||||
Inventory = require '../game/inventory'
|
||||
PlanBuilder = require './plan_builder'
|
||||
ResourcesEvaluator = require './resources_evaluator'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe "PlanBuilder", ->
|
||||
|
||||
beforeEach ->
|
||||
@planner = new PlanBuilder new ResourcesEvaluator
|
||||
@mod = fixtures.createMod()
|
||||
@want = new Inventory
|
||||
@have = new Inventory
|
||||
|
||||
describe "for a gatherable item, creates a plan that", ->
|
||||
|
||||
beforeEach ->
|
||||
@oakWood = fixtures.configureOakWood @mod
|
||||
@want.add @oakWood, 2
|
||||
@plan = @planner.createPlan @want
|
||||
|
||||
it "has no steps at all", ->
|
||||
@plan.steps.length.should.equal 0
|
||||
|
||||
it "demands the item itself as the input", ->
|
||||
@plan.need.toString(full:true).should.equal "2 Oak Wood"
|
||||
|
||||
it "produces the item as the result", ->
|
||||
@plan.make.toString(full:true).should.equal "2 Oak Wood"
|
||||
|
||||
describe "for a single item with a one-step recipe, it creates a plan that", ->
|
||||
|
||||
beforeEach ->
|
||||
@oakPlank = fixtures.configureOakPlank @mod
|
||||
@want.add @oakPlank, 63
|
||||
@plan = @planner.createPlan @want
|
||||
|
||||
it "has the recipe as the only step", ->
|
||||
@plan.steps.length.should.equal 1
|
||||
@plan.steps[0].recipe.output.item.displayName.should.equal "Oak Planks"
|
||||
|
||||
it "demands the right inputs", ->
|
||||
@plan.need.toString(full:true).should.equal "16 Oak Wood"
|
||||
|
||||
it "produces the right results", ->
|
||||
@plan.make.toString(full:true).should.equal "64 Oak Planks"
|
||||
|
||||
describe "for a single item with a complex recipe, it creates a plan that", ->
|
||||
|
||||
beforeEach ->
|
||||
@ironSword = fixtures.configureIronSword @mod
|
||||
@want.add @ironSword, 2
|
||||
@plan = @planner.createPlan @want
|
||||
|
||||
it "has the right steps", ->
|
||||
@plan.steps.length.should.equal 4
|
||||
@plan.steps[0].recipe.output.item.displayName.should.equal "Iron Ingot"
|
||||
@plan.steps[1].recipe.output.item.displayName.should.equal "Oak Planks"
|
||||
@plan.steps[2].recipe.output.item.displayName.should.equal "Stick"
|
||||
@plan.steps[3].recipe.output.item.displayName.should.equal "Iron Sword"
|
||||
|
||||
it "demands the right inputs", ->
|
||||
@plan.need.toString(full:true).should.equal "1 Coal, 8 Iron Ore, 1 Oak Wood"
|
||||
|
||||
it "produces the right results", ->
|
||||
@plan.make.toString(full:true).should.equal "4 Iron Ingot, 2 Iron Sword, 2 Oak Planks, 2 Stick"
|
||||
|
||||
describe "for multiple items with complex recipes, it creates a plan that", ->
|
||||
|
||||
beforeEach ->
|
||||
@ironSword = fixtures.configureIronSword @mod
|
||||
@ironShovel = fixtures.configureIronShovel @mod
|
||||
@want.add @ironSword, 3
|
||||
@want.add @ironShovel, 3
|
||||
@plan = @planner.createPlan @want
|
||||
|
||||
it "has consolidated the common steps", ->
|
||||
(step.recipe.output.item.displayName for step in @plan.steps).should.eql [
|
||||
"Iron Ingot", "Oak Planks", "Stick", "Iron Sword", "Iron Shovel"
|
||||
]
|
||||
|
||||
it "requires the correct repetitions for each step", ->
|
||||
(step.count for step in @plan.steps).should.eql [ 2, 2, 3, 3, 3 ]
|
||||
|
||||
it "demands the right inputs", ->
|
||||
@plan.need.toString(full:true).should.equal "2 Coal, 16 Iron Ore, 2 Oak Wood"
|
||||
|
||||
it "produces the right results", ->
|
||||
@plan.make.toString(full:true).should.equal(
|
||||
"7 Iron Ingot, 3 Iron Shovel, 3 Iron Sword, 2 Oak Planks, 3 Stick"
|
||||
)
|
||||
|
||||
describe "for an item which has a recursive recipe, it creates a plan that", ->
|
||||
|
||||
beforeEach ->
|
||||
@ironBlock = fixtures.configureIronBlock @mod
|
||||
@want.add @ironBlock, 4
|
||||
@plan = @planner.createPlan @want
|
||||
|
||||
it "avoids the recursive options", ->
|
||||
(step.recipe.output.item.displayName for step in @plan.steps).should.eql [
|
||||
"Iron Ingot", "Iron Block"
|
||||
]
|
||||
|
||||
it "requires the correct repetitions for each step", ->
|
||||
(step.count for step in @plan.steps).should.eql [ 5, 4 ]
|
||||
|
||||
it "demands the right inputs", ->
|
||||
@plan.need.toString(full:true).should.equal "5 Coal, 40 Iron Ore"
|
||||
|
||||
it "produces the right results", ->
|
||||
@plan.make.toString(full:true).should.equal(
|
||||
"4 Iron Block, 4 Iron Ingot"
|
||||
)
|
||||
|
||||
describe "when making enormous quantities of something simple", ->
|
||||
|
||||
beforeEach ->
|
||||
@oakPlank = fixtures.configureOakPlank @mod
|
||||
@want.add @oakPlank, Number.MAX_VALUE / 2
|
||||
@start = Date.now()
|
||||
@plan = @planner.createPlan @want
|
||||
@duration = Date.now() - @start
|
||||
|
||||
it "doesn't take forever to create the plan", ->
|
||||
@duration.should.be.lessThan 10
|
||||
|
||||
describe "when making an item which can be made with and without a tool, for a", ->
|
||||
|
||||
beforeEach ->
|
||||
@oakPlank = fixtures.configureOakPlank @mod
|
||||
@saw = fixtures.configureSaw @mod
|
||||
|
||||
describe "small batch, the plan", ->
|
||||
|
||||
beforeEach ->
|
||||
@want.add @oakPlank, 2
|
||||
@plan = @planner.createPlan @want
|
||||
|
||||
it "avoids the expensive tool", ->
|
||||
(step.recipe.needsTools for step in @plan.steps).should.eql [ false ]
|
||||
|
||||
describe "large batch, the plan", ->
|
||||
|
||||
beforeEach ->
|
||||
@want.add @oakPlank, 2048
|
||||
@plan = @planner.createPlan @want
|
||||
|
||||
it "uses the tool for efficiency", ->
|
||||
(step.recipe.needsTools for step in @plan.steps).should.eql [ true ]
|
||||
@@ -1,40 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - resources_evaluator.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Evaluator = require './evaluator'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ResourcesEvaluator extends Evaluator
|
||||
|
||||
# Evaluator Overrides ##########################################################################
|
||||
|
||||
_computeRecipeScore: (recipe, evaluation)->
|
||||
evaluation.baseScore = 0
|
||||
|
||||
for x in [0...recipe.width]
|
||||
for y in [0...recipe.height]
|
||||
for z in [0...recipe.depth]
|
||||
stack = recipe.getInputAt x, y, z
|
||||
continue unless stack?
|
||||
|
||||
inputEvaluation = @evaluateItem stack.item
|
||||
if inputEvaluation?.baseScore?
|
||||
evaluation.baseScore += inputEvaluation.baseScore * stack.quantity
|
||||
else
|
||||
evaluation.baseScore = null
|
||||
return
|
||||
|
||||
for id, extraStack of recipe.extras
|
||||
extraEvaluation = @evaluateItem extraStack.item
|
||||
continue unless extraEvaluation?.baseScore?
|
||||
evaluation.baseScore -= extraStack.quantity * extraEvaluation.baseScore
|
||||
|
||||
evaluation.baseScore = evaluation.baseScore / recipe.output.quantity
|
||||
|
||||
_computeGatherableItemScore: (item, evaluation)->
|
||||
evaluation.baseScore = 1
|
||||
@@ -1,129 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - resources_evaluator.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
ResourcesEvaluator = require './resources_evaluator'
|
||||
fixtures = require '../fixtures'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe "ResourcesEvaluator", ->
|
||||
|
||||
beforeEach ->
|
||||
@evaluator = new ResourcesEvaluator
|
||||
@mod = fixtures.createMod()
|
||||
|
||||
describe 'evaluating a gatherable item', ->
|
||||
|
||||
beforeEach ->
|
||||
@cobblestone = fixtures.configureCobblestone @mod
|
||||
@evaluation = @evaluator.evaluateItem @cobblestone
|
||||
|
||||
it 'should return a score of 1 for each item', ->
|
||||
# 1 cobblestone
|
||||
@evaluation.computeTotalScore().should.equal 1
|
||||
|
||||
describe 'evaluating a first-level recipe', ->
|
||||
|
||||
beforeEach ->
|
||||
@craftingTable = fixtures.configureCraftingTable @mod
|
||||
@evaluation = @evaluator.evaluateItem @craftingTable
|
||||
|
||||
it 'should return a score which is the sum of all resources', ->
|
||||
# 1 oak wood => 4 planks (1/4)
|
||||
# 4 oak planks => 1 crafting table (4 * 1/4 = 1)
|
||||
@evaluation.computeTotalScore().should.equal 1
|
||||
|
||||
describe 'evaluating a recipe which requires a tool', ->
|
||||
|
||||
beforeEach ->
|
||||
@furnace = fixtures.configureFurnace @mod
|
||||
@evaluation = @evaluator.evaluateItem @furnace
|
||||
|
||||
it 'includes the cost of the tool', ->
|
||||
# 1 oak wood => 4 planks (1/4)
|
||||
# 4 oak planks => 1 crafting table (4 * 1/4 = 1)
|
||||
# 8 cobblestone =(crafting table)=> 1 furnace (8 + 1 = 9)
|
||||
@evaluation.computeTotalScore().should.equal 9
|
||||
|
||||
describe 'evaluating a recipe where a tool requires a tool', ->
|
||||
|
||||
beforeEach ->
|
||||
@ironIngot = fixtures.configureIronIngot @mod
|
||||
@evaluation = @evaluator.evaluateItem @ironIngot
|
||||
|
||||
it 'should include both tools in the score', ->
|
||||
# raw ingredients:
|
||||
# 8 iron ore + 1 coal =(furnace)=> 8 iron ingots ((8 + 1) / 8 = 1.125)
|
||||
# tools:
|
||||
# 8 cobblestone =(crafting table)=> 1 furnace (8)
|
||||
# 4 oak planks => 1 crafting table (4 * 1/4 = 1)
|
||||
# 1 oak wood => 4 planks (1/4)
|
||||
@evaluation.computeTotalScore().should.equal 10.125
|
||||
|
||||
describe 'evaluating a recipe which requires a tool multiple times', ->
|
||||
|
||||
beforeEach ->
|
||||
@ironSword = fixtures.configureIronSword @mod
|
||||
@evaluation = @evaluator.evaluateItem @ironSword
|
||||
|
||||
it 'should include the tool only once', ->
|
||||
# raw ingredients:
|
||||
# 1 oak wood ==> 4 planks (0.25)
|
||||
# 2 oak planks ==> 4 sticks (0.125)
|
||||
# 8 iron ore + 1 coal =(furnace)=> 8 iron ingots (1.125)
|
||||
# 2 iron ingot + 1 stick =(crafting table)=> 1 iron sword (2.375)
|
||||
#
|
||||
# tools:
|
||||
# 8 cobblestone =(crafting table)=> 1 furnace (8)
|
||||
#
|
||||
# 1 oak wood => 4 planks (0.25)
|
||||
# 4 oak planks => 1 crafting table (1)
|
||||
#
|
||||
@evaluation.computeTotalScore().should.equal 11.375
|
||||
|
||||
describe 'evaluating a recipe which has a recursive recipe', ->
|
||||
|
||||
beforeEach ->
|
||||
@ironBlock = fixtures.configureIronBlock @mod
|
||||
@evaluation = @evaluator.evaluateItem @ironBlock
|
||||
|
||||
it 'should avoid the recursive recipes', ->
|
||||
# raw ingredients:
|
||||
# 8 iron ore + 1 coal =(furnace)=> 8 iron ingots ((8 + 1) / 8 = 1.125)
|
||||
# 9 iron ingot =(crafting table)=> 1 iron block (10.125)
|
||||
#
|
||||
# tools:
|
||||
# 8 cobblestone =(crafting table)=> 1 furnace (8)
|
||||
# 4 oak planks => 1 crafting table (4 * 1/4 = 1)
|
||||
# 1 oak wood => 4 planks (1/4)
|
||||
@evaluation.computeTotalScore().should.equal 19.125
|
||||
|
||||
describe 'evaluating a recipe with multiple ouputs', ->
|
||||
|
||||
beforeEach ->
|
||||
@cake = fixtures.configureCake @mod
|
||||
@evaluation = @evaluator.evaluateItem @cake
|
||||
|
||||
it 'should discount the extra outputs', ->
|
||||
# raw ingredients:
|
||||
# 8 iron ore + 1 coal =(furnace)=> 8 iron ingot (1.125)
|
||||
# 3 iron ingot =(crafting table)=> 1 bucket (3.375)
|
||||
# 1 milk + 1 bucket ==> 1 milk bucket (4.375)
|
||||
# 1 sugar cane ==> 1 sugar (1)
|
||||
# 3 wheat + 1 egg + 2 sugar + 3 milk bucket =(crafting table)=> 1 cake, 3 buckets (9)
|
||||
#
|
||||
# furnace:
|
||||
# 8 cobblestone =(crafting table)=> 1 furnace (8)
|
||||
#
|
||||
# crafting table
|
||||
# 1 oak wood ==> 4 oak planks (0.25)
|
||||
# 4 planks ==> 1 crafting table (1)
|
||||
@evaluation.computeTotalScore().should.equal 18
|
||||
|
||||
describe 'evaluating a recipe which is only ever made as an extra', ->
|
||||
|
||||
it 'should still be able to come up with an evaluation'
|
||||
@@ -1,41 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - steps_evaluator.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Evaluator = require './evaluator'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class StepsEvaluator extends Evaluator
|
||||
|
||||
# Evaluator Overrides ##########################################################################
|
||||
|
||||
_computeRecipeScore: (recipe, evaluation)->
|
||||
evaluation.score = 0
|
||||
|
||||
for id, stack of recipe.inputs
|
||||
inputEvaluation = @evaluateItem stack.item
|
||||
if not inputEvaluation?.score?
|
||||
evaluation.score = null
|
||||
return
|
||||
|
||||
evaluation.score = Math.min evaluation.score, inputEvaluation.score + 1
|
||||
|
||||
for id, item of recipe.tools
|
||||
continue if evaluation.isToolIncluded item
|
||||
|
||||
toolEvaluation = @evaluateItem stack.item
|
||||
if not toolEvaluation?.score?
|
||||
evaluation.score = null
|
||||
return
|
||||
|
||||
evaluation.addIncludedTool item
|
||||
evaluation.score += toolEvaluation.score
|
||||
|
||||
return result
|
||||
|
||||
_computeGatherableItemScore: (item, evaluation)->
|
||||
evaluation.score = 0
|
||||
@@ -1,36 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - event_recorder.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
util = require 'util'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class EventRecorder
|
||||
|
||||
constructor: (model)->
|
||||
if not model? then throw new Error 'model is required'
|
||||
|
||||
@model = model
|
||||
@events = []
|
||||
|
||||
@model.on 'all', (event, model, args...)=>
|
||||
logger.verbose -> "#{model?.constructor?.name}(#{model?.cid}) emitted #{event}
|
||||
with args: #{util.inspect(args)}"
|
||||
@events.push id:model?.cid, event:event, args:args
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
reset: ->
|
||||
@events = []
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getNames: ->
|
||||
return (e.event for e in @events)
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
names: {get:@prototype.getNames}
|
||||
@@ -1,319 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - fixtures.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require "./game/item"
|
||||
Mod = require "./game/mod"
|
||||
ModPack = require "./game/mod_pack"
|
||||
Recipe = require "./game/recipe"
|
||||
Stack = require "./game/stack"
|
||||
|
||||
# Instance Creation Fixtures ###########################################################################################
|
||||
|
||||
exports.createModPack = createModPack = (attributes={})->
|
||||
attributes.id ?= _.uniqueId "mod-pack-"
|
||||
attributes.displayName ?= "Test ModPack"
|
||||
return new ModPack attributes
|
||||
|
||||
exports.createMod = createMod = (attributes={})->
|
||||
attributes.modPack ?= createModPack()
|
||||
attributes.id ?= _.uniqueId "mod-"
|
||||
attributes.displayName ?= "Test Mod"
|
||||
return new Mod attributes
|
||||
|
||||
exports.createItem = createItem = (attributes={})->
|
||||
attributes.mod ?= createMod()
|
||||
attributes.id ?= _.uniqueId "item-"
|
||||
attributes.displayName ?= _.uniqueId "Test Item "
|
||||
return new Item attributes
|
||||
|
||||
exports.createRecipe = createRecipe = (attributes={})->
|
||||
attributes.id ?= _.uniqueId "recipe-"
|
||||
attributes.output ?= createItem()
|
||||
return new Recipe attributes
|
||||
|
||||
exports.createStack = createStack = (attributes={})->
|
||||
attributes.item ?= createItem()
|
||||
attributes.quantity ?= 1
|
||||
return new Stack attributes
|
||||
|
||||
# Item Configuration Fixtures ##########################################################################################
|
||||
|
||||
exports.configureBucket = configureBucket = (mod)->
|
||||
bucket = mod.modPack.findItem "bucket"
|
||||
if not bucket?
|
||||
ironIngot = configureIronIngot mod
|
||||
bucket = createItem mod:mod, id:"bucket", displayName:"Bucket"
|
||||
craftingTable = configureCraftingTable mod
|
||||
|
||||
recipe = createRecipe output:createStack item:bucket
|
||||
recipe.setInputAt 0, 0, createStack item:ironIngot
|
||||
recipe.setInputAt 1, 1, createStack item:ironIngot
|
||||
recipe.setInputAt 2, 0, createStack item:ironIngot
|
||||
recipe.addTool craftingTable
|
||||
|
||||
return bucket
|
||||
|
||||
exports.configureCake = configureCake = (mod)->
|
||||
cake = mod.modPack.findItem "cake"
|
||||
if not cake?
|
||||
bucket = configureBucket mod
|
||||
cake = createItem mod:mod, id:"cake", displayName:"Cake"
|
||||
craftingTable = configureCraftingTable mod
|
||||
egg = configureEgg mod
|
||||
milkBucket = configureMilkBucket mod
|
||||
sugar = configureSugar mod
|
||||
wheat = configureWheat mod
|
||||
|
||||
recipe = createRecipe output:createStack item:cake
|
||||
recipe.setInputAt 0, 0, createStack item:milkBucket
|
||||
recipe.setInputAt 1, 0, createStack item:milkBucket
|
||||
recipe.setInputAt 2, 0, createStack item:milkBucket
|
||||
recipe.setInputAt 0, 1, createStack item:sugar
|
||||
recipe.setInputAt 1, 1, createStack item:egg
|
||||
recipe.setInputAt 2, 1, createStack item:sugar
|
||||
recipe.setInputAt 0, 2, createStack item:wheat
|
||||
recipe.setInputAt 1, 2, createStack item:wheat
|
||||
recipe.setInputAt 2, 2, createStack item:wheat
|
||||
recipe.addTool craftingTable
|
||||
recipe.addExtra createStack item:bucket, quantity:3
|
||||
|
||||
return cake
|
||||
|
||||
exports.configureCoal = configureCoal = (mod)->
|
||||
coal = mod.modPack.findItem "coal"
|
||||
if not coal?
|
||||
coal = createItem mod:mod, id:"coal", displayName:"Coal"
|
||||
return coal
|
||||
|
||||
exports.configureCobblestone = configureCobblestone = (mod)->
|
||||
cobblestone = mod.modPack.findItem "cobblestone"
|
||||
if not cobblestone?
|
||||
cobblestone = createItem mod:mod, displayName:"Cobblestone"
|
||||
return cobblestone
|
||||
|
||||
exports.configureCraftingTable = configureCraftingTable = (mod)->
|
||||
craftingTable = mod.modPack.findItem "crafting_table"
|
||||
if not craftingTable?
|
||||
craftingTable = createItem mod:mod, id:"crafting_table", displayName:"Crafting Table"
|
||||
oakPlanks = configureOakPlank mod
|
||||
|
||||
recipe = createRecipe output:createStack item:craftingTable
|
||||
recipe.setInputAt 0, 0, createStack item:oakPlanks
|
||||
recipe.setInputAt 1, 0, createStack item:oakPlanks
|
||||
recipe.setInputAt 0, 1, createStack item:oakPlanks
|
||||
recipe.setInputAt 1, 1, createStack item:oakPlanks
|
||||
|
||||
return craftingTable
|
||||
|
||||
exports.configureEgg = configureEgg = (mod)->
|
||||
egg = mod.modPack.findItem "egg"
|
||||
if not egg?
|
||||
egg = createItem mod:mod, id:"egg", displayName:"Egg"
|
||||
return egg
|
||||
|
||||
exports.configureFurnace = configureFurnace = (mod)->
|
||||
furnace = mod.modPack.findItem "furnace"
|
||||
if not furnace?
|
||||
cobblestone = configureCobblestone mod
|
||||
craftingTable = configureCraftingTable mod
|
||||
furnace = createItem mod:mod, displayName:"Furnace"
|
||||
|
||||
recipe = createRecipe output:createStack item:furnace
|
||||
recipe.setInputAt 0, 0, createStack item:cobblestone
|
||||
recipe.setInputAt 1, 0, createStack item:cobblestone
|
||||
recipe.setInputAt 2, 0, createStack item:cobblestone
|
||||
recipe.setInputAt 0, 1, createStack item:cobblestone
|
||||
recipe.setInputAt 2, 1, createStack item:cobblestone
|
||||
recipe.setInputAt 0, 2, createStack item:cobblestone
|
||||
recipe.setInputAt 1, 2, createStack item:cobblestone
|
||||
recipe.setInputAt 2, 2, createStack item:cobblestone
|
||||
recipe.addTool craftingTable
|
||||
|
||||
return furnace
|
||||
|
||||
exports.configureIronIngot = configureIronIngot = (mod)->
|
||||
ironIngot = mod.modPack.findItem "iron_ingot"
|
||||
if not ironIngot?
|
||||
coal = configureCoal mod
|
||||
furnace = configureFurnace mod
|
||||
ironIngot = createItem mod:mod, id:"iron_ingot", displayName:"Iron Ingot"
|
||||
ironOre = configureIronOre mod
|
||||
|
||||
recipe = createRecipe output:createStack item:ironIngot, quantity:8
|
||||
recipe.setInputAt 1, 0, createStack item:ironOre, quantity:8
|
||||
recipe.setInputAt 1, 2, createStack item:coal
|
||||
recipe.addTool furnace
|
||||
|
||||
return ironIngot
|
||||
|
||||
exports.configureIronBlock = configureIronBlock = (mod)->
|
||||
ironBlock = mod.modPack.findItem "iron_block"
|
||||
if not ironBlock?
|
||||
craftingTable = configureCraftingTable mod
|
||||
ironBlock = createItem mod:mod, id:"iron_block", displayName:"Iron Block"
|
||||
ironIngot = configureIronIngot mod
|
||||
|
||||
recipe = createRecipe output:createStack item:ironBlock
|
||||
for x in [0..2]
|
||||
for y in [0..2]
|
||||
recipe.setInputAt x, y, createStack item:ironIngot
|
||||
recipe.addTool craftingTable
|
||||
|
||||
recipe = createRecipe output:createStack item:ironIngot, quantity:9
|
||||
recipe.setInputAt 1, 1, createStack item:ironBlock
|
||||
|
||||
return ironBlock
|
||||
|
||||
exports.configureIronSword = configureIronSword = (mod)->
|
||||
ironSword = mod.modPack.findItem "iron_sword"
|
||||
if not ironSword?
|
||||
craftingTable = configureCraftingTable mod
|
||||
ironIngot = configureIronIngot mod
|
||||
ironSword = createItem mod:mod, id:"iron_sword", displayName:"Iron Sword"
|
||||
stick = configureStick mod
|
||||
|
||||
recipe = createRecipe output:createStack item:ironSword
|
||||
recipe.setInputAt 1, 0, createStack item:ironIngot
|
||||
recipe.setInputAt 1, 1, createStack item:ironIngot
|
||||
recipe.setInputAt 1, 2, createStack item:stick
|
||||
recipe.addTool craftingTable
|
||||
|
||||
return ironSword
|
||||
|
||||
exports.configureIronShovel = configureIronShovel = (mod)->
|
||||
ironShovel = mod.modPack.findItem "iron_shovel"
|
||||
if not ironShovel?
|
||||
craftingTable = configureCraftingTable mod
|
||||
ironIngot = configureIronIngot mod
|
||||
ironShovel = createItem mod:mod, id:"iron_shovel", displayName:"Iron Shovel"
|
||||
stick = configureStick mod
|
||||
|
||||
recipe = createRecipe output:createStack item:ironShovel
|
||||
recipe.setInputAt 1, 0, createStack item:ironIngot
|
||||
recipe.setInputAt 1, 1, createStack item:stick
|
||||
recipe.setInputAt 1, 2, createStack item:stick
|
||||
recipe.addTool craftingTable
|
||||
|
||||
return ironShovel
|
||||
|
||||
exports.configureIronOre = configureIronOre = (mod)->
|
||||
ironOre = mod.modPack.findItem "iron_ore"
|
||||
if not ironOre?
|
||||
ironOre = createItem mod:mod, id:"iron_ore", displayName:"Iron Ore"
|
||||
return ironOre
|
||||
|
||||
exports.configureMilk = configureMilk = (mod)->
|
||||
milk = mod.modPack.findItem "milk"
|
||||
if not milk?
|
||||
milk = createItem mod:mod, id:"milk", displayName:"Milk"
|
||||
return milk
|
||||
|
||||
exports.configureMilkBucket = configureMilkBucket = (mod)->
|
||||
milkBucket = mod.modPack.findItem "milk_bucket"
|
||||
if not milkBucket?
|
||||
bucket = configureBucket mod
|
||||
milk = configureMilk mod
|
||||
milkBucket = createItem mod:mod, id:"milkBucket", displayName:"Milk Bucket"
|
||||
|
||||
recipe = createRecipe output:createStack item:milkBucket
|
||||
recipe.setInputAt 1, 0, createStack item:milk
|
||||
recipe.setInputAt 1, 1, createStack item:bucket
|
||||
|
||||
return milkBucket
|
||||
|
||||
exports.configureOakPlank = configureOakPlank = (mod)->
|
||||
oakPlanks = mod.modPack.findItem "oak_planks"
|
||||
if not oakPlanks?
|
||||
oakPlanks = createItem mod:mod, id:"oak_planks", displayName:"Oak Planks"
|
||||
oakWood = configureOakWood mod
|
||||
|
||||
recipe = createRecipe output:createStack item:oakPlanks, quantity:4
|
||||
recipe.setInputAt 1, 1, createStack item:oakWood
|
||||
|
||||
return oakPlanks
|
||||
|
||||
exports.configureOakWood = configureOakWood = (mod)->
|
||||
oakWood = mod.modPack.findItem "oak_wood"
|
||||
if not oakWood?
|
||||
oakWood = createItem mod:mod, id:"oak_wood", displayName:"Oak Wood"
|
||||
return oakWood
|
||||
|
||||
exports.configureObsidian = configureObsidian = (mod)->
|
||||
obsidian = mod.modPack.findItem "obsidian"
|
||||
if not obsidian?
|
||||
obsidian = createItem mod:mod, id:"obsidian", displayName:"Obsidian"
|
||||
return obsidian
|
||||
|
||||
exports.configureRedstoneDust = configureRedstoneDust = (mod)->
|
||||
redstoneDust = mod.modPack.findItem "redstone_dust"
|
||||
if not redstoneDust?
|
||||
redstoneDust = createItem mod:mod, id:"redstone_dust", displayName:"Redstone Dust"
|
||||
return redstoneDust
|
||||
|
||||
exports.configureStick = configureStick = (mod)->
|
||||
stick = mod.modPack.findItem "stick"
|
||||
if not stick?
|
||||
oakPlanks = configureOakPlank mod
|
||||
stick = createItem mod:mod, id:"stick", displayName:"Stick"
|
||||
|
||||
recipe = createRecipe output:createStack item:stick, quantity:4
|
||||
recipe.setInputAt 0, 0, createStack item:oakPlanks
|
||||
recipe.setInputAt 0, 1, createStack item:oakPlanks
|
||||
|
||||
return stick
|
||||
|
||||
exports.configureSugar = configureSugar = (mod)->
|
||||
sugar = mod.modPack.findItem "sugar"
|
||||
if not sugar?
|
||||
sugar = createItem mod:mod, id:"sugar", displayName:"Sugar"
|
||||
sugarCane = configureSugarCane mod
|
||||
|
||||
recipe = createRecipe output:createStack item:sugar
|
||||
recipe.setInputAt 1, 1, createStack item:sugarCane
|
||||
|
||||
return sugar
|
||||
|
||||
exports.configureSaw = configureSaw = (mod)->
|
||||
saw = mod.modPack.findItem "saw"
|
||||
if not saw?
|
||||
craftingTable = configureCraftingTable mod
|
||||
ironBlock = configureIronBlock mod
|
||||
ironIngot = configureIronIngot mod
|
||||
oakPlank = configureOakPlank mod
|
||||
oakWood = configureOakWood mod
|
||||
redstoneDust = configureRedstoneDust mod
|
||||
saw = createItem mod:mod, id:"saw", displayName:"Saw"
|
||||
|
||||
recipe = createRecipe output:createStack item:saw
|
||||
recipe.setInputAt 0, 0, createStack item:oakPlank
|
||||
recipe.setInputAt 1, 0, createStack item:ironIngot
|
||||
recipe.setInputAt 2, 0, createStack item:oakPlank
|
||||
recipe.setInputAt 0, 1, createStack item:oakPlank
|
||||
recipe.setInputAt 1, 1, createStack item:ironBlock
|
||||
recipe.setInputAt 2, 1, createStack item:oakPlank
|
||||
recipe.setInputAt 0, 2, createStack item:oakPlank
|
||||
recipe.setInputAt 1, 2, createStack item:redstoneDust
|
||||
recipe.setInputAt 2, 2, createStack item:oakPlank
|
||||
recipe.addTool craftingTable
|
||||
|
||||
recipe = createRecipe output:createStack item:oakPlank, quantity:8
|
||||
recipe.setInputAt 1, 1, createStack item:oakWood
|
||||
recipe.addTool saw
|
||||
|
||||
return saw
|
||||
|
||||
exports.configureSugarCane = configureSugarCane = (mod)->
|
||||
sugarCane = mod.modPack.findItem "sugar_cane"
|
||||
if not sugarCane?
|
||||
sugarCane = createItem mod:mod, id:"sugar_cane", displayName:"Sugar Cane"
|
||||
return sugarCane
|
||||
|
||||
exports.configureWheat = configureWheat = (mod)->
|
||||
wheat = mod.modPack.findItem "wheat"
|
||||
if not wheat?
|
||||
wheat = createItem mod:mod, id:"wheat", displayName:"Wheat"
|
||||
return wheat
|
||||
@@ -1,90 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - inventory.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Stack = require './stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Inventory
|
||||
|
||||
constructor: (inventory=null)->
|
||||
@_id = _.uniqueId "inventory-"
|
||||
@_stacks = {}
|
||||
|
||||
if inventory? then @merge inventory
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
isEmpty:
|
||||
get: -> (id for id, stack of @_stacks).length is 0
|
||||
|
||||
stacks:
|
||||
get: -> return @_stacks
|
||||
set: -> throw new Error "stacks cannot be replaced"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (item, quantity)->
|
||||
return unless item?
|
||||
return if quantity is 0
|
||||
|
||||
existingStack = @_stacks[item.id]
|
||||
if existingStack?
|
||||
if existingStack.quantity + quantity < 0 then throw new Error "cannot have a negative quantity"
|
||||
existingStack.quantity += quantity
|
||||
else
|
||||
if quantity < 0 then throw new Error "cannot have a negative quantity"
|
||||
@_stacks[item.id] = new Stack item:item, quantity:quantity
|
||||
|
||||
if @_stacks[item.id].quantity is 0
|
||||
delete @_stacks[item.id]
|
||||
|
||||
clear: ->
|
||||
@_stacks = {}
|
||||
|
||||
contains: (item)->
|
||||
return @_stacks[item.id]?
|
||||
|
||||
getQuantity: (item)->
|
||||
existingStack = @_stacks[item.id]
|
||||
return 0 unless existingStack?
|
||||
return existingStack.quantity
|
||||
|
||||
merge: (inventory)->
|
||||
for id, stack of inventory.stacks
|
||||
@add stack.item, stack.quantity
|
||||
|
||||
remove: (item, quantity)->
|
||||
@add item, -1 * quantity
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: (options={})->
|
||||
options.full ?= false
|
||||
|
||||
if options.full
|
||||
result = []
|
||||
needsDelimiter = false
|
||||
|
||||
stackList = (stack for itemId, stack of @_stacks)
|
||||
stackList.sort (a, b)->
|
||||
if a.item.displayName isnt b.item.displayName
|
||||
return if a.item.displayName < b.item.displayName then -1 else +1
|
||||
return 0
|
||||
|
||||
for stack in stackList
|
||||
if needsDelimiter then result.push ", "
|
||||
needsDelimiter = true
|
||||
|
||||
result.push stack.quantity
|
||||
result.push " "
|
||||
result.push stack.item.displayName
|
||||
return result.join ""
|
||||
else
|
||||
return "Inventory<#{@_id}>@#{(id for id, item of @_stacks).length}"
|
||||
@@ -1,96 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - item.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Item
|
||||
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@displayName = attributes.displayName
|
||||
@isGatherable = attributes.isGatherable
|
||||
@mod = attributes.mod
|
||||
|
||||
@_hasPrimaryRecipe = false
|
||||
@_recipesAsPrimary = {}
|
||||
@_recipesAsExtra = {}
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
displayName: # a string containing the user-facing name of this item
|
||||
get: -> return @_displayName
|
||||
set: (displayName)->
|
||||
if not displayName? then throw new Error "displayName is required"
|
||||
@_displayName = displayName
|
||||
|
||||
firstRecipe: # the first Recipe returned by iterating the `recipes` property
|
||||
get: ->
|
||||
recipeList = (recipe for id, recipe of @recipes)
|
||||
return null unless recipeList.length > 0
|
||||
return recipeList[0]
|
||||
set: -> throw new Error "firstRecipe cannot be assigned"
|
||||
|
||||
id: # a string containing a unique identifier for this item
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
return if @_id is id
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
isGatherable: # whether this item can be gathered directly without needing to be crafted
|
||||
get: ->
|
||||
return true if @_isGatherable is true
|
||||
return false if (id for id, recipe of @_recipesAsPrimary).length > 0
|
||||
return false if (id for id, recipe of @_recipesAsExtra).length > 0
|
||||
return true
|
||||
set: (isGatherable)->
|
||||
@_isGatherable = null unless isGatherable?
|
||||
@_isGatherable = !!isGatherable
|
||||
|
||||
mod: # the Mod which adds this item to the game
|
||||
get: -> return @_mod
|
||||
set: (mod)->
|
||||
if not mod? then throw new Error "mod is required"
|
||||
if @_mod is mod then return
|
||||
if @_mod? then throw new Error "mod cannot be reassigned"
|
||||
@_mod = mod
|
||||
@_mod.addItem this
|
||||
|
||||
modPack: # the ModPack containing this item
|
||||
get: -> return @_mod.modPack
|
||||
set: -> throw new Error "modPack cannot be assigned"
|
||||
|
||||
recipes: # a hash of recipeId to Recipe containing `recipesAsPrimary` if not empty or else `recipesAsExtra`
|
||||
get: -> return if @_hasPrimaryRecipe then @_recipesAsPrimary else @_recipesAsExtra
|
||||
set: -> throw new Error "recipes cannot be assigned"
|
||||
|
||||
recipesAsPrimary: # a hash of recipeId to Recipe where this item is the primary output
|
||||
get: -> return @_recipesAsPrimary
|
||||
set: -> throw new Error "recipes cannot be assigned"
|
||||
|
||||
recipesAsExtra: # a hash of recipeId to Recipe where this item is an extra output
|
||||
get: -> return @_recipesAsExtra
|
||||
set: -> throw new Error "recipesAsExtra cannot be assigned"
|
||||
|
||||
# Public Recipes ###############################################################################
|
||||
|
||||
addRecipe: (recipe)->
|
||||
if recipe.output.item is this
|
||||
@_recipesAsPrimary[recipe.id] = recipe
|
||||
@_hasPrimaryRecipe = true
|
||||
else if recipe.extras[this.id]?.item is this
|
||||
@_recipesAsExtra[recipe.id] = recipe
|
||||
else
|
||||
throw new Error "recipe<#{recipe.id}> does not produce this item<#{@id}>"
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "Item:#{@displayName}<#{@id}>"
|
||||
@@ -1,62 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Mod
|
||||
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@displayName = attributes.displayName
|
||||
@modPack = attributes.modPack
|
||||
|
||||
@_items = {}
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
displayName:
|
||||
get: -> return @_displayName
|
||||
set: (displayName)->
|
||||
if not displayName? then throw new Error "displayName is required"
|
||||
return if @_displayName is displayName
|
||||
@_displayName = displayName
|
||||
|
||||
id:
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
return if @_id is id
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
items:
|
||||
get: -> return @_items
|
||||
set: -> throw new Error "items cannot be replaced"
|
||||
|
||||
modPack:
|
||||
get: -> return @_modPack
|
||||
set: (modPack)->
|
||||
if not modPack? then throw new Error "modPack is required"
|
||||
if @_modPack is modPack then return
|
||||
if @_modPack? then throw new Error "modPack cannot be reassigned"
|
||||
@_modPack = modPack
|
||||
@_modPack.addMod this
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addItem: (item)->
|
||||
if not item? then return
|
||||
if @_items[item.id] is item then return
|
||||
@_items[item.id] = item
|
||||
item.mod = this
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "Mod:#{@displayName}<#{@id}>"
|
||||
@@ -1,59 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPack
|
||||
|
||||
constructor: (attributes={})->
|
||||
@id = attributes.id
|
||||
@displayName = attributes.displayName
|
||||
|
||||
@_mods = {}
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
displayName: # a string containing the user-displayable name of this ModPack
|
||||
get: -> return @_displayName
|
||||
set: (displayName)->
|
||||
if not displayName? then throw new Error "displayName is required"
|
||||
if @_displayName is displayName then return
|
||||
@_displayName = displayName
|
||||
|
||||
id: # a string which uniquely identifies this ModPack
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
if @_id is id then return
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
mods: # a hash of mod id to Mod containing all the mods which are part of this ModPack
|
||||
get: -> return @_mods
|
||||
set: -> throw new Error "mods cannot be replaced"
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addMod: (mod)->
|
||||
if not mod? then return
|
||||
if @_mods[mod.id] is mod then return
|
||||
@_mods[mod.id] = mod
|
||||
mod.modPack = this
|
||||
|
||||
findItem: (itemId)->
|
||||
for modId, mod of @mods
|
||||
item = mod.items[itemId]
|
||||
return item if item?
|
||||
|
||||
return null
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "ModPack:#{@displayName}<#{@id}>"
|
||||
@@ -1,174 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - recipe.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Recipe
|
||||
|
||||
constructor: (attributes={})->
|
||||
@depth = attributes.depth
|
||||
@height = attributes.height
|
||||
@id = attributes.id
|
||||
@output = attributes.output
|
||||
@width = attributes.width
|
||||
|
||||
@_extras = {}
|
||||
@_inputs = {}
|
||||
@_inputGrid = []
|
||||
@_tools = {}
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
allProducts: # an array of Stacks starting the the primary output of this recipe
|
||||
get: -> return [].concat @output, (stack for id, stack of @extras)
|
||||
set: -> throw new Error "allProducts cannot be assigned"
|
||||
|
||||
depth: # an integer specifying the number of layers to this recipe
|
||||
get: -> return @_depth
|
||||
set: (depth)->
|
||||
depth = parseInt "#{depth}"
|
||||
depth = if Number.isNaN(depth) then 0 else Math.max(0, depth)
|
||||
@_depth = depth
|
||||
|
||||
extras: # a hash of item id to Stack of all the non-primary outputs of this recipe
|
||||
get: -> return @_extras
|
||||
set: -> throw new Error "extras cannot be replaced"
|
||||
|
||||
id: # a string which uniquely identifies this recipe
|
||||
get: -> return @_id
|
||||
set: (id)->
|
||||
if not id? then throw new Error "id is required"
|
||||
if @_id is id then return
|
||||
if @_id? then throw new Error "id cannot be reassigned"
|
||||
@_id = id
|
||||
|
||||
height: # an integer specifying the number of rows needed by this recipe
|
||||
get: -> return @_height
|
||||
set: (height)->
|
||||
height = parseInt "#{height}"
|
||||
height = if Number.isNaN(height) then 0 else Math.max(0, height)
|
||||
@_height = height
|
||||
|
||||
inputs: # a hash of item id to Item containing all the inputs to this recipe
|
||||
get: -> return @_inputs
|
||||
set: -> throw new Error "inputs cannot be replaced"
|
||||
|
||||
needsTools: # a boolean indicating whether this recipe requires a tool
|
||||
get: -> return (id for id, toolItem of @_tools).length > 0
|
||||
|
||||
output: # a Stack specifying the primary output of this recipe
|
||||
get: -> return @_output
|
||||
set: (output)->
|
||||
if not output? then throw new Error "output is required"
|
||||
if @_output is output then return
|
||||
if @_output? then throw new Error "output cannot be reassigned"
|
||||
@_output = output
|
||||
@_output.item.addRecipe this
|
||||
|
||||
modPack: # the ModPack to which this recipe belongs
|
||||
get: -> return @_output.modPack
|
||||
set: -> throw new Error "modPack cannot be replaced"
|
||||
|
||||
tools: # a hash of item id to Item of all the tools required for this recipe
|
||||
get: -> return @_tools
|
||||
set: -> throw new Error "tools cannot be assigned"
|
||||
|
||||
width: # an integer specifying the number of columns needed by this recipe
|
||||
get: -> return @_width
|
||||
set: (width)->
|
||||
width = parseInt "#{width}"
|
||||
width = if Number.isNaN(width) then 0 else Math.max(0, width)
|
||||
@_width = width
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addExtra: (stack)->
|
||||
return unless stack
|
||||
return if @_extras[stack.item.id] is stack
|
||||
@_extras[stack.item.id] = stack
|
||||
stack.item.addRecipe this
|
||||
|
||||
addTool: (item)->
|
||||
return unless item
|
||||
@_tools[item.id] = item
|
||||
|
||||
computeQuantityRequired: (item)->
|
||||
result = 0
|
||||
|
||||
for x in [0...@width]
|
||||
for y in [0...@height]
|
||||
for z in [0...@depth]
|
||||
stack = @getInputAt x, y, z
|
||||
continue unless stack?
|
||||
continue unless stack.item.id is item.id
|
||||
result += stack.quantity
|
||||
|
||||
return result
|
||||
|
||||
computeQuantityProduced: (item)->
|
||||
result = 0
|
||||
|
||||
if @_output.item.id is item.id
|
||||
result += @_output.quantity
|
||||
|
||||
for itemId, stack of @_extras
|
||||
continue unless itemId is item.id
|
||||
result += stack.quantity
|
||||
|
||||
return result
|
||||
|
||||
getInputAt: ->
|
||||
[x, y, z] = [0, 0, 0]
|
||||
if arguments.length is 3
|
||||
[x, y, z] = arguments
|
||||
else
|
||||
[x, y] = arguments
|
||||
|
||||
return @_inputGrid[x]?[y]?[z] or null
|
||||
|
||||
setInputAt: ->
|
||||
[x, y, z, stack] = [0, 0, 0, null]
|
||||
if arguments.length is 4
|
||||
[x, y, z, stack] = arguments
|
||||
else
|
||||
[x, y, stack] = arguments
|
||||
|
||||
@_depth = Math.max @_depth, z + 1
|
||||
@_height = Math.max @_height, y + 1
|
||||
@_width = Math.max @_width, x + 1
|
||||
|
||||
@_inputGrid[x] ?= []
|
||||
@_inputGrid[x][y] ?= []
|
||||
@_inputGrid[x][y][z] = stack
|
||||
|
||||
if stack?.item? then @_inputs[stack.item.id] = stack.item
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: (options={})->
|
||||
options.full ?= false
|
||||
|
||||
if options.full
|
||||
b = new StringBuilder
|
||||
b.loop (item for id, item of @inputs), delimiter:" + ", onEach:(b, item)=>
|
||||
b.push @computeQuantityRequired(item), " ", item.displayName
|
||||
b.push " ="
|
||||
b.onlyIf @needsTools, (b)=>
|
||||
b.push "("
|
||||
b.loop (toolItem for id, toolItem of @tools), onEach:(b, toolItem)-> b.push toolItem.displayName
|
||||
b.push ")"
|
||||
b.push "=> "
|
||||
b.loop @allProducts, delimiter:" + ", onEach:(b, stack)=>
|
||||
b.push stack.quantity, " ", stack.item.displayName
|
||||
|
||||
return b.toString()
|
||||
else
|
||||
return "Recipe:#{@output}<#{@id}>"
|
||||
@@ -1,72 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - recipe.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
fixtures = require "../fixtures"
|
||||
Item = require "./item"
|
||||
Recipe = require "./recipe"
|
||||
Stack = require "./stack"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe "Recipe", ->
|
||||
|
||||
beforeEach ->
|
||||
@mod = fixtures.createMod()
|
||||
@stick = fixtures.configureStick @mod
|
||||
@ironIngot = fixtures.configureIronIngot @mod
|
||||
@obsidian = fixtures.configureObsidian @mod
|
||||
|
||||
@ironSword = new Item id:"iron_sword", displayName:"Iron Sword", mod:@mod
|
||||
@obsidianBox = new Item id:"obsidian_box", displayName:"Obsidian Box", mod:@mod
|
||||
|
||||
describe "getting & setting inputs", ->
|
||||
|
||||
describe "for 2D recipes", ->
|
||||
|
||||
beforeEach ->
|
||||
@recipe = new Recipe id:"test1", output:new Stack item:@ironSword
|
||||
@recipe.setInputAt 1, 0, new Stack item:@ironIngot
|
||||
@recipe.setInputAt 1, 1, new Stack item:@ironIngot
|
||||
@recipe.setInputAt 1, 2, new Stack item:@stick
|
||||
|
||||
it "returns assigned values as expected", ->
|
||||
expect(@recipe.getInputAt(0, 0)).to.equal null
|
||||
@recipe.getInputAt(1, 0).item.displayName.should.equal "Iron Ingot"
|
||||
@recipe.getInputAt(1, 1).item.displayName.should.equal "Iron Ingot"
|
||||
@recipe.getInputAt(1, 2).item.displayName.should.equal "Stick"
|
||||
expect(@recipe.getInputAt(2, 2)).to.equal null
|
||||
|
||||
it "determines the correct dimentions", ->
|
||||
@recipe.depth.should.equal 1
|
||||
@recipe.height.should.equal 3
|
||||
@recipe.width.should.equal 2
|
||||
|
||||
describe "for 3D recipes", ->
|
||||
|
||||
beforeEach ->
|
||||
@recipe = new Recipe id:"test2", output:new Stack item:@obsidianBox
|
||||
for x in [0..2]
|
||||
for y in [0..2]
|
||||
for z in [0..2]
|
||||
continue if x is 1 and y is 1
|
||||
continue if x is 1 and z is 1
|
||||
continue if y is 1 and z is 1
|
||||
|
||||
@recipe.setInputAt x, y, z, new Stack item:@obsidian
|
||||
|
||||
it "returns assigned values as expected", ->
|
||||
@recipe.getInputAt(0, 0, 0).item.displayName.should.equal "Obsidian"
|
||||
@recipe.getInputAt(2, 0, 0).item.displayName.should.equal "Obsidian"
|
||||
@recipe.getInputAt(0, 2, 0).item.displayName.should.equal "Obsidian"
|
||||
@recipe.getInputAt(2, 2, 2).item.displayName.should.equal "Obsidian"
|
||||
expect(@recipe.getInputAt(1, 1, 1)).to.equal null
|
||||
expect(@recipe.getInputAt(1, 1, 0)).to.equal null
|
||||
|
||||
it "determines the correct dimentions", ->
|
||||
@recipe.depth.should.equal 3
|
||||
@recipe.height.should.equal 3
|
||||
@recipe.width.should.equal 3
|
||||
@@ -1,42 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - stack.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Stack
|
||||
|
||||
constructor: (attributes={})->
|
||||
@item = attributes.item
|
||||
@quantity = attributes.quantity
|
||||
|
||||
# Properties ###################################################################################
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
|
||||
item:
|
||||
get: -> return @_item
|
||||
set: (item)->
|
||||
if not item? then throw new Error "item is required"
|
||||
if @_item is item then return
|
||||
if @_item? then throw new Error "item cannot be reassigned"
|
||||
@_item = item
|
||||
|
||||
modPack:
|
||||
get: -> return @_item.modPack
|
||||
set: -> throw new Error "modPack cannot be replaced"
|
||||
|
||||
quantity:
|
||||
get: -> return @_quantity
|
||||
set: (quantity)->
|
||||
quantity = parseInt "#{quantity}"
|
||||
quantity = if Number.isNaN(quantity) then 0 else Math.max(0, quantity)
|
||||
@_quantity = quantity
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "Stack:#{@item}×#{@quantity}"
|
||||
@@ -1,124 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack_json.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
fixtures = require "../fixtures"
|
||||
ModPackJsonFormatter = require "./mod_pack_json_formatter"
|
||||
ModPackJsonParser = require "./mod_pack_json_parser"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe "ModPackJsonParser & ModPackJsonFormatter", ->
|
||||
|
||||
beforeEach ->
|
||||
@formatter = new ModPackJsonFormatter
|
||||
@parser = new ModPackJsonParser
|
||||
|
||||
@modPack = fixtures.createModPack id:"alpha", displayName:"ALPHA"
|
||||
|
||||
@runTest = =>
|
||||
@string1 = @formatter.format @modPack
|
||||
@string2 = @formatter.format @parser.parse @string1
|
||||
@result = @parser.parse @string2
|
||||
|
||||
describe "an empty modpack", ->
|
||||
|
||||
beforeEach -> @runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "contains the modpack's own properties", ->
|
||||
@result.id.should.equal @modPack.id
|
||||
@result.displayName.should.equal @modPack.displayName
|
||||
|
||||
it "doesn't contain a mods list", ->
|
||||
expect(@result.mods).to.beUndefined
|
||||
|
||||
describe "a modpack with a single mod", ->
|
||||
|
||||
beforeEach ->
|
||||
@mod = fixtures.createMod modPack:@modPack, id:"bravo", displayName:"BRAVO"
|
||||
|
||||
describe "containing only a gatherable item", ->
|
||||
|
||||
beforeEach ->
|
||||
@oakWood = fixtures.configureOakWood @mod
|
||||
@runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "contains the correct mod", ->
|
||||
mod = @result.mods[@mod.id]
|
||||
mod.displayName.should.equal @mod.displayName
|
||||
|
||||
it "contains the item", ->
|
||||
item = @result.mods[@mod.id].items[@oakWood.id]
|
||||
item.displayName.should.equal @oakWood.displayName
|
||||
|
||||
describe "containing a multi-step item & it's requirements", ->
|
||||
|
||||
beforeEach ->
|
||||
@craftingTable = fixtures.configureCraftingTable @mod
|
||||
@oakPlank = fixtures.configureOakPlank @mod
|
||||
@runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "contains oak planks", ->
|
||||
item = @result.mods[@mod.id].items[@oakPlank.id]
|
||||
item.displayName.should.equal @oakPlank.displayName
|
||||
|
||||
it "has the recipe for a crafting table", ->
|
||||
recipe = @result.mods[@mod.id].items[@craftingTable.id].firstRecipe
|
||||
recipe.getInputAt(0, 0).item.id.should.equal @oakPlank.id
|
||||
recipe.getInputAt(0, 1).item.id.should.equal @oakPlank.id
|
||||
recipe.getInputAt(1, 0).item.id.should.equal @oakPlank.id
|
||||
recipe.getInputAt(1, 1).item.id.should.equal @oakPlank.id
|
||||
recipe.output.quantity.should.equal 1
|
||||
|
||||
describe "containing a complex item which needs tools & it's requirements", ->
|
||||
|
||||
beforeEach ->
|
||||
@cake = fixtures.configureCake @mod
|
||||
@runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "has the recipe for a cake", ->
|
||||
recipe = @result.mods[@mod.id].items[@cake.id].firstRecipe
|
||||
|
||||
describe "a modpack with multiple mods", ->
|
||||
|
||||
beforeEach ->
|
||||
@modA = fixtures.createMod modPack:@modPack, id:"bravo", displayName:"BRAVO"
|
||||
@modB = fixtures.createMod modPack:@modPack, id:"charlie", displayName:"CHARLIE"
|
||||
|
||||
describe "where items are used in recipes crossing mods", ->
|
||||
|
||||
beforeEach ->
|
||||
@stick = fixtures.configureStick @modA
|
||||
@ironIngot = fixtures.configureIronIngot @modA
|
||||
@ironSword = fixtures.configureIronSword @modB
|
||||
@runTest()
|
||||
|
||||
it "can survive a round trip", ->
|
||||
@string1.should.equal @string2
|
||||
|
||||
it "has each item in the correct mod", ->
|
||||
@result.mods[@modA.id].items[@ironIngot.id].displayName.should.equal @ironIngot.displayName
|
||||
@result.mods[@modB.id].items[@ironSword.id].displayName.should.equal @ironSword.displayName
|
||||
|
||||
it "has the correct recipe for an iron sword", ->
|
||||
recipe = @result.mods[@modB.id].items[@ironSword.id].firstRecipe
|
||||
recipe.output.item.id.should.equal @ironSword.id
|
||||
recipe.output.quantity.should.equal 1
|
||||
recipe.getInputAt(1, 0).item.id.should.equal @ironIngot.id
|
||||
recipe.getInputAt(1, 1).item.id.should.equal @ironIngot.id
|
||||
recipe.getInputAt(1, 2).item.id.should.equal @stick.id
|
||||
@@ -1,101 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack_json_formatter.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPackJsonParser
|
||||
|
||||
constructor: ->
|
||||
@_reset()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
format: (modPack)->
|
||||
@_reset()
|
||||
return JSON.stringify @_formatModPack modPack
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_formatItem: (item)->
|
||||
result = {}
|
||||
result.id = item.id
|
||||
result.displayName = item.displayName
|
||||
|
||||
if item.isGatherable and item.firstRecipe?
|
||||
result.gatherable = true
|
||||
|
||||
return result
|
||||
|
||||
_formatMod: (mod)->
|
||||
result = {}
|
||||
result.id = mod.id
|
||||
result.displayName = mod.displayName
|
||||
|
||||
for itemId, item of mod.items
|
||||
result.items ?= []
|
||||
@_itemIndexById[item.id] = @_itemIndex++
|
||||
result.items.push @_formatItem item
|
||||
|
||||
return result
|
||||
|
||||
_formatModPack: (modPack)->
|
||||
result = {}
|
||||
result.id = modPack.id
|
||||
result.displayName = modPack.displayName
|
||||
|
||||
for modId, mod of modPack.mods
|
||||
result.mods ?= []
|
||||
result.mods.push @_formatMod mod
|
||||
|
||||
if result.mods?
|
||||
for modResult in result.mods
|
||||
for itemResult in modResult.items
|
||||
item = modPack.mods[modResult.id].items[itemResult.id]
|
||||
for recipeId, recipe of item.recipesAsPrimary
|
||||
itemResult.recipes ?= []
|
||||
itemResult.recipes.push @_formatRecipe recipe
|
||||
|
||||
return result
|
||||
|
||||
_formatRecipe: (recipe)->
|
||||
result = {}
|
||||
result.id = recipe.id
|
||||
|
||||
if recipe.output.quantity > 1
|
||||
result.quantity = recipe.output.quantity
|
||||
|
||||
result.width = recipe.width
|
||||
result.height = recipe.height
|
||||
result.depth = recipe.depth if recipe.depth > 1
|
||||
result.inputs = []
|
||||
|
||||
for x in [0...recipe.width]
|
||||
for y in [0...recipe.height]
|
||||
for z in [0...recipe.depth]
|
||||
inputStack = @_formatStack recipe.getInputAt x, y, z
|
||||
result.inputs.push inputStack
|
||||
|
||||
for itemId, stack of recipe.extras
|
||||
result.extras ?= []
|
||||
result.extras.push @_formatStack stack
|
||||
|
||||
for itemId, item of recipe.tools
|
||||
result.tools ?= []
|
||||
result.tools.push @_itemIndexById[itemId]
|
||||
|
||||
return result
|
||||
|
||||
_formatStack: (stack)->
|
||||
return null unless stack?
|
||||
|
||||
itemIndex = @_itemIndexById[stack.item.id]
|
||||
if stack.quantity is 1 then return itemIndex
|
||||
return [itemIndex, stack.quantity]
|
||||
|
||||
_reset: ->
|
||||
@_itemIndex = 0
|
||||
@_itemIndexById = {}
|
||||
@@ -1,164 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - mod_pack_json_parser.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
Item = require "../game/item"
|
||||
Mod = require "../game/mod"
|
||||
ModPack = require "../game/mod_pack"
|
||||
Recipe = require "../game/recipe"
|
||||
Stack = require "../game/stack"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPackJsonParser
|
||||
|
||||
constructor: ->
|
||||
@_reset()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
parse: (arg, fileName=null)->
|
||||
@_reset()
|
||||
@_fileName = fileName
|
||||
|
||||
if _.isString arg
|
||||
@_parseText arg
|
||||
else
|
||||
@_parseObject arg
|
||||
|
||||
return @_modPack
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_parseInteger: (text, defaultValue)->
|
||||
result = parseInt "#{text}"
|
||||
result = if Number.isNaN result then defaultValue else result
|
||||
return result
|
||||
|
||||
_parseText: (text)->
|
||||
try
|
||||
obj = JSON.parse text
|
||||
catch error
|
||||
@_throwError "could not parse JSON: #{error}"
|
||||
|
||||
@_parseObject obj
|
||||
|
||||
_parseObject: (obj)->
|
||||
@_data = obj
|
||||
@_parseModPack()
|
||||
@_parseMods()
|
||||
@_parseItems()
|
||||
@_parseRecipes()
|
||||
|
||||
_parseModPack: ->
|
||||
if not @_data? then @_throwError "there is no valid data"
|
||||
if not @_data.id? then @_throwError "modPack requires an id"
|
||||
if not @_data.displayName? then @_throwError "modPack requires a displayName"
|
||||
|
||||
@_modPack = new ModPack id:@_data.id, displayName:@_data.displayName
|
||||
|
||||
_parseMods: ->
|
||||
return unless @_data.mods?
|
||||
|
||||
for modData, index in @_data.mods
|
||||
@_location = "mods[#{index}]"
|
||||
if not modData.id? then @_throwError "mod requires an id"
|
||||
if not modData.displayName? then @_throwError "mod requires a displayName"
|
||||
|
||||
new Mod modPack:@_modPack, id:modData.id, displayName:modData.displayName
|
||||
|
||||
_parseItems: ->
|
||||
return unless @_data.mods?
|
||||
|
||||
for modData in @_data.mods
|
||||
continue unless modData.items?
|
||||
|
||||
mod = @_modPack.mods[modData.id]
|
||||
for itemData, index in modData.items
|
||||
@_location = "<#{mod.id}>.items[#{index}]"
|
||||
if not itemData.id? then @_throwError "item requires an id"
|
||||
if not itemData.displayName? then @_throwError "item requires a displayName"
|
||||
|
||||
item = new Item mod:mod, id:itemData.id, displayName:itemData.displayName
|
||||
item.gatherable = itemData.gatherable if itemData.gatherable?
|
||||
@_items.push item
|
||||
|
||||
_parseRecipes: ->
|
||||
return unless @_data.mods?
|
||||
|
||||
for modData in @_data.mods
|
||||
continue unless modData.items?
|
||||
|
||||
mod = @_modPack.mods[modData.id]
|
||||
for itemData, index in modData.items
|
||||
continue unless itemData.recipes?
|
||||
|
||||
item = mod.items[itemData.id]
|
||||
for recipeData, index in itemData.recipes
|
||||
@_location = "<#{itemData.id}>.recipes[#{index}]"
|
||||
if not recipeData.id? then @_throwError "recipe requires id"
|
||||
if not recipeData.inputs? then @_throwError "recipe requires inputs"
|
||||
|
||||
quantity = @_parseInteger recipeData.quantity, 1
|
||||
outputStack = new Stack item:item, quantity:quantity
|
||||
recipe = new Recipe id:recipeData.id, output:outputStack
|
||||
|
||||
depth = @_parseInteger recipeData.depth, 1
|
||||
height = @_parseInteger recipeData.height, 3
|
||||
width = @_parseInteger recipeData.width, 3
|
||||
|
||||
index = 0
|
||||
for x in [0...width]
|
||||
for y in [0...height]
|
||||
for z in [0...depth]
|
||||
stack = @_parseStack recipeData.inputs[index]
|
||||
if stack? then recipe.setInputAt x, y, z, stack
|
||||
index++
|
||||
|
||||
if recipeData.extras
|
||||
for stackData in recipeData.extras
|
||||
recipe.addExtra @_parseStack stackData
|
||||
|
||||
if recipeData.tools
|
||||
for index in recipeData.tools
|
||||
toolItem = @_items[index]
|
||||
if not toolItem? then @_throwError "there is no item #{index}"
|
||||
recipe.addTool toolItem
|
||||
|
||||
_parseStack: (stackData)->
|
||||
return null unless stackData?
|
||||
if _.isArray(stackData)
|
||||
if stackData.length isnt 2 then @_throwError "input stacks must have an item index and a quantity"
|
||||
index = stackData[0]
|
||||
quantity = stackData[1]
|
||||
else
|
||||
index = stackData
|
||||
quantity = 1
|
||||
|
||||
item = @_items[index]
|
||||
if not item? then @_throwError "there is no item #{index}"
|
||||
|
||||
return new Stack item:item, quantity:quantity
|
||||
|
||||
_reset: ->
|
||||
@_data = null
|
||||
@_fileName = null
|
||||
@_items = []
|
||||
@_location = null
|
||||
@_modPack = null
|
||||
|
||||
_throwError: (message, cause=null)->
|
||||
if @_location? then message = "#{@_location}: #{message}"
|
||||
if @_fileName? and @_location? then message = "@#{message}"
|
||||
if @_fileName? then message = "#{@_fileName}#{message}"
|
||||
if cause? then message = "#{message}: #{cause}"
|
||||
|
||||
error = new Error message
|
||||
error.cause = cause if cause?
|
||||
error.fileName = @_fileName if @_fileName?
|
||||
error.location = @_location if @_location?
|
||||
|
||||
throw error
|
||||
@@ -5,10 +5,12 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
Craftsman = require '../crafting/craftsman'
|
||||
Inventory = require '../game/inventory'
|
||||
ModPack = require '../game/mod_pack'
|
||||
CraftingGuideCommon = require "crafting-guide-common"
|
||||
|
||||
{BaseModel} = CraftingGuideCommon.deprecated
|
||||
{Craftsman} = CraftingGuideCommon.deprecated.crafting
|
||||
{Inventory} = CraftingGuideCommon.deprecated.game
|
||||
{ModPack} = CraftingGuideCommon.deprecated.game
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
{BaseModel} = require("crafting-guide-common").deprecated
|
||||
w = require "when"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
w = require "when"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class FileCache extends Backbone.Events
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
#
|
||||
# Crafting Guide - file_cache.test.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
FileCache = require './file_cache'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
{BaseModel} = require("crafting-guide-common").deprecated
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
{BaseModel} = require("crafting-guide-common").deprecated
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
ItemSlug = require '../game/item_slug'
|
||||
{BaseModel} = require('crafting-guide-common').deprecated
|
||||
{ItemSlug} = require('crafting-guide-common').deprecated.game
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
{BaseModel} = require('crafting-guide-common').deprecated
|
||||
MarkdownImage = require './markdown_image'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#
|
||||
# Crafting Guide - tutorial.coffee
|
||||
#
|
||||
# Copyright © 2014-2016 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BaseModel = require '../base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Tutorial extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.name?.length > 0 then throw new Error "attributes.name cannot be empty"
|
||||
attributes.modSlug ?= null
|
||||
attributes.officialUrl ?= null
|
||||
attributes.sections ?= []
|
||||
attributes.slug ?= _.slugify attributes.name
|
||||
attributes.videos ?= []
|
||||
super attributes, options
|
||||
|
||||
# Backbone.Model Overrides #####################################################################
|
||||
|
||||
parse: (text)->
|
||||
TutorialParser = require '../parsing/tutorial_parser' # to avoid require cycles
|
||||
@_parser ?= new TutorialParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return c.url.tutorialData modSlug:@modSlug, tutorialSlug:@slug
|
||||
@@ -6,6 +6,7 @@
|
||||
#
|
||||
|
||||
ModPackJsonParser = require "../parsing/mod_pack_json_parser"
|
||||
w = require "when"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
{CraftingGuideClient} = require('crafting-guide-common').api
|
||||
ModBallotController = require './mod_ballot/mod_ballot_controller'
|
||||
ModTileController = require './mod_tile/mod_tile_controller'
|
||||
PageController = require '../page_controller'
|
||||
{CraftingGuideClient} = require 'crafting-guide-common'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ module.exports = class AdsenseController
|
||||
adCount = @_computeAdCount @_computeAdType()
|
||||
|
||||
if adCount is 0
|
||||
logger.verbose "Adsense is waiting for room to insert ads"
|
||||
logger.trace "Adsense is waiting for room to insert ads"
|
||||
@_waiting = true
|
||||
_.delay (=> @_waiting = false; @_waitForPageReadiness()), c.adsense.readinessCheckInterval
|
||||
else
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
BaseController = require '../../base_controller'
|
||||
ItemSelector = require '../../../models/site/item_selector'
|
||||
ElementController = require './element/element_controller'
|
||||
w = require "when"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
BaseController = require '../../base_controller'
|
||||
CraftingGridController = require '../crafting_grid/crafting_grid_controller'
|
||||
SlotController = require '../slot/slot_controller'
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
{StringBuilder} = require('crafting-guide-common').util
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
BaseController = require '../base_controller'
|
||||
CraftPage = require '../../models/site/craft_page'
|
||||
Craftsman = require '../../models/crafting/craftsman'
|
||||
{Craftsman} = require('crafting-guide-common').deprecated.crafting
|
||||
CraftsmanWorkingController = require './craftsman_working/craftsman_working_controller'
|
||||
InventoryController = require '../common/inventory/inventory_controller'
|
||||
PageController = require '../page_controller'
|
||||
SimpleInventory = require '../../models/crafting/simple_inventory'
|
||||
{SimpleInventory} = require('crafting-guide-common').deprecated.crafting
|
||||
StepController = require './step/step_controller'
|
||||
|
||||
########################################################################################################################
|
||||
@@ -64,6 +64,7 @@ module.exports = class CraftPageController extends PageController
|
||||
sampleInventory.parse inventoryText
|
||||
@model.craftsman.want.addInventory sampleInventory
|
||||
@_scrollTo @$workingSection
|
||||
@onWantInventoryChanged()
|
||||
|
||||
return false
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
|
||||
BaseController = require '../../base_controller'
|
||||
Craftsman = require '../../../models/crafting/craftsman'
|
||||
{Craftsman} = require('crafting-guide-common').deprecated.crafting
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
BaseController = require '../../base_controller'
|
||||
InventoryController = require '../../common/inventory/inventory_controller'
|
||||
RecipeController = require '../../common/recipe/recipe_controller'
|
||||
SimpleInventory = require '../../../models/crafting/simple_inventory'
|
||||
{SimpleInventory} = require('crafting-guide-common').deprecated.crafting
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
BaseController = require '../base_controller'
|
||||
EmailClient = require '../../models/site/email_client'
|
||||
w = require "when"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -6,15 +6,16 @@
|
||||
#
|
||||
|
||||
EditableFile = require '../../models/site/editable_file'
|
||||
Item = require '../../models/game/item'
|
||||
{Item} = require('crafting-guide-common').deprecated.game
|
||||
ItemGroupController = require '../common/item_group/item_group_controller'
|
||||
ItemPage = require '../../models/site/item_page'
|
||||
ItemSlug = require '../../models/game/item_slug'
|
||||
{ItemSlug} = require('crafting-guide-common').deprecated.game
|
||||
MarkdownSectionController = require '../common/markdown_section/markdown_section_controller'
|
||||
MultiblockViewerController = require './multiblock_viewer/multiblock_viewer_controller'
|
||||
PageController = require '../page_controller'
|
||||
RecipeDetailController = require './recipe_detail/recipe_detail_controller'
|
||||
VideoController = require '../common/video/video_controller'
|
||||
w = require "when"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
BaseController = require '../../base_controller'
|
||||
CraftingGridController = require '../../common/crafting_grid/crafting_grid_controller'
|
||||
Inventory = require '../../../models/game/inventory'
|
||||
{Inventory} = require('crafting-guide-common').deprecated.game
|
||||
InventoryController = require '../../common/inventory/inventory_controller'
|
||||
{StringBuilder} = require 'crafting-guide-common'
|
||||
{StringBuilder} = require('crafting-guide-common').util
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
PageController = require '../page_controller'
|
||||
GitHubUser = require '../../models/site/github_user'
|
||||
{CraftingGuideClient} = require 'crafting-guide-common'
|
||||
{CraftingGuideClient} = require('crafting-guide-common').api
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
PageController = require '../page_controller'
|
||||
Item = require '../../models/game/item'
|
||||
{Item} = require('crafting-guide-common').deprecated.game
|
||||
ItemGroupController = require '../common/item_group/item_group_controller'
|
||||
Mod = require '../../models/game/mod'
|
||||
{Mod} = require('crafting-guide-common').deprecated.game
|
||||
PageController = require '../page_controller'
|
||||
TutorialController = require './tutorial/tutorial_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
BrowsePageController = require './browse_page/browse_page_controller'
|
||||
CraftPageController = require './craft_page/craft_page_controller'
|
||||
ItemPageController = require './item_page/item_page_controller'
|
||||
ItemSlug = require '../models/game/item_slug'
|
||||
LoginPageController = require './login_page/login_page_controller'
|
||||
ModPageController = require './mod_page/mod_page_controller'
|
||||
NewsPageController = require './news_page/news_page_controller'
|
||||
TutorialPageController = require './tutorial_page/tutorial_page_controller'
|
||||
UrlParams = require './url_params'
|
||||
BrowsePageController = require './browse_page/browse_page_controller'
|
||||
CraftPageController = require './craft_page/craft_page_controller'
|
||||
ItemPageController = require './item_page/item_page_controller'
|
||||
{ItemSlug} = require('crafting-guide-common').deprecated.game
|
||||
LoginPageController = require './login_page/login_page_controller'
|
||||
ModPageController = require './mod_page/mod_page_controller'
|
||||
NewsPageController = require './news_page/news_page_controller'
|
||||
TutorialPageController = require './tutorial_page/tutorial_page_controller'
|
||||
UrlParams = require './url_params'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
|
||||
@@ -13,9 +13,8 @@ FooterController = require './footer/footer_controller'
|
||||
GitHubUser = require '../models/site/github_user'
|
||||
HeaderController = require './header/header_controller'
|
||||
ImageLoader = require './image_loader'
|
||||
Mod = require '../models/game/mod'
|
||||
ModPack = require '../models/game/mod_pack'
|
||||
ModPackStore = require '../models/store/mod_pack_store'
|
||||
{Mod} = require('crafting-guide-common').deprecated.game
|
||||
{ModPack} = require('crafting-guide-common').deprecated.game
|
||||
Router = require './router'
|
||||
|
||||
########################################################################################################################
|
||||
@@ -31,6 +30,7 @@ module.exports = class SiteController extends BaseController
|
||||
@client = options.client
|
||||
@fileCache = new FileCache c.url.modpackArchive()
|
||||
@imageLoader = new ImageLoader defaultUrl:'/images/unknown.png'
|
||||
@modPack = new ModPack {}, fileCache:@fileCache
|
||||
@router = new Router this
|
||||
@storage = options.storage
|
||||
|
||||
@@ -41,6 +41,23 @@ module.exports = class SiteController extends BaseController
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
loadDefaultModPack: ->
|
||||
makeResponder = (m)-> return ->
|
||||
m.activeModVersion.fetch() if m.activeModVersion?
|
||||
|
||||
for modSlug, modData of c.defaultMods
|
||||
mod = new Mod {slug:modSlug}, {fileCache:@fileCache}
|
||||
mod.on c.event.change + ':activeModVersion', makeResponder mod
|
||||
@storage.register "mod:#{mod.slug}", mod, 'activeVersion', modData.defaultVersion
|
||||
mod.fetch()
|
||||
|
||||
@modPack.addMod mod
|
||||
|
||||
if global.env isnt 'prerender'
|
||||
@modPack.once c.event.sync, =>
|
||||
@$pageContent.removeClass 'hidden'
|
||||
@$pageContentLoading.addClass 'hidden'
|
||||
|
||||
loadCurrentUser: ->
|
||||
@client.getCurrentUser()
|
||||
.then (response)=>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
w = require "when"
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Tracker
|
||||
|
||||
+43
-48
@@ -5,6 +5,9 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
_ = require "./underscore"
|
||||
_.extend exports, require("crafting-guide-common").constants
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
exports.adsense = adsense = {}
|
||||
@@ -29,31 +32,52 @@ adsense.skyscraper.margin = 24 # px
|
||||
adsense.skyscraper.slotIds = ['7613920409', '9574673605', '3388539204']
|
||||
adsense.skyscraper.width = 160 # px
|
||||
|
||||
exports.defaultMods = defaultMods = {}
|
||||
defaultMods.minecraft = { defaultVersion: '1.7.10' } # Minecraft must be first
|
||||
|
||||
defaultMods.advanced_solar_panels = { defaultVersion: '3.5.1' }
|
||||
defaultMods.agricraft = { defaultVersion: '1.7.10_1.5.0' }
|
||||
defaultMods.applied_energistics_2 = { defaultVersion: 'rv1-stable-1' }
|
||||
defaultMods.big_reactors = { defaultVersion: '0.4.2A2' }
|
||||
defaultMods.buildcraft = { defaultVersion: '1.7.18' }
|
||||
defaultMods.computercraft = { defaultVersion: '1.74' }
|
||||
defaultMods.draconic_evolution = { defaultVersion: '1.0.2h' }
|
||||
defaultMods.ender_storage = { defaultVersion: '1.4.5.29' }
|
||||
defaultMods.enderio = { defaultVersion: '2.2.7.325' }
|
||||
defaultMods.extra_cells = { defaultVersion: '2.2.73b129' }
|
||||
defaultMods.extra_utilities = { defaultVersion: '1.2.2' }
|
||||
defaultMods.forestry = { defaultVersion: '3.4.0.7' }
|
||||
defaultMods.forge_multipart = { defaultVersion: '1.2.0.345' }
|
||||
defaultMods.galacticraft = { defaultVersion: '3.0.12.404' }
|
||||
defaultMods.hydraulicraft = { defaultVersion: '2.1.242' }
|
||||
defaultMods.ic2_classic = { defaultVersion: 'none' }
|
||||
defaultMods.industrial_craft_2 = { defaultVersion: '2.2.663' }
|
||||
defaultMods.iron_chests = { defaultVersion: '6.0.62.742' }
|
||||
defaultMods.jabba = { defaultVersion: '1.2.1a' }
|
||||
defaultMods.logistics_pipes = { defaultVersion: '0.9.3.100' }
|
||||
defaultMods.mekanism = { defaultVersion: '7.1.1.127' }
|
||||
defaultMods.minefactory_reloaded = { defaultVersion: '2.8.0RC8-86' }
|
||||
defaultMods.modular_powersuits = { defaultVersion: '0.11.0-300-thermal-expansion' }
|
||||
defaultMods.opencomputers = { defaultVersion: '1.5.22' }
|
||||
defaultMods.quantum_flux = { defaultVersion: '1.3.4' }
|
||||
defaultMods.project_red = { defaultVersion: '4.5.16.77' }
|
||||
defaultMods.redstone_arsenal = { defaultVersion: '9.5.0' }
|
||||
defaultMods.railcraft = { defaultVersion: '9.5.0' }
|
||||
defaultMods.simply_jetpacks = { defaultVersion: '1.4.1' }
|
||||
defaultMods.solar_expansion = { defaultVersion: '1.6a' }
|
||||
defaultMods.solar_flux = { defaultVersion: '0.5b' }
|
||||
defaultMods.storage_drawers = { defaultVersion: '1.7.10-1.6.2' }
|
||||
defaultMods.thermal_dynamics = { defaultVersion: '1.7.10r1.2.0' }
|
||||
defaultMods.thermal_expansion = { defaultVersion: '1.7.10r4.1.4' }
|
||||
defaultMods.thermal_foundation = { defaultVersion: '1.7.10r1.2.5' }
|
||||
defaultMods.tinkers_construct = { defaultVersion: '1.7.10-1.8.8' }
|
||||
|
||||
exports.duration = duration = {}
|
||||
duration.snap = 100
|
||||
duration.fast = 200
|
||||
duration.normal = 400
|
||||
duration.slow = 1200
|
||||
|
||||
exports.event = event = {}
|
||||
event.add = 'add' # collection, item...
|
||||
event.button = {}
|
||||
event.button.complete = 'button:complete' # controller
|
||||
event.button.first = 'button:first' # controller, buttonType
|
||||
event.button.second = 'button:second' # controller, buttonType
|
||||
event.change = 'change' # model
|
||||
event.click = 'click' # event
|
||||
event.load = {}
|
||||
event.load.started = 'load:started' # controller, url
|
||||
event.load.succeeded = 'load:succeeded' # controller, book
|
||||
event.load.failed = 'load:failed' # controller, error message
|
||||
event.load.finished = 'load:finished' # controller
|
||||
event.remove = 'remove' # collection, item...
|
||||
event.request = 'request' # model
|
||||
event.route = 'route'
|
||||
event.sort = 'sort'
|
||||
event.sync = 'sync' # model, response
|
||||
|
||||
exports.gitHub = gitHub = {}
|
||||
gitHub.file = {}
|
||||
gitHub.file.itemDescription = {}
|
||||
@@ -67,10 +91,6 @@ key.escape = 27
|
||||
key.upArrow = 38
|
||||
key.downArrow = 40
|
||||
|
||||
exports.limits = limits = {}
|
||||
limits.maximumGraphSize = 5000
|
||||
limits.maximumPlanCount = 5000
|
||||
|
||||
exports.login = login = {}
|
||||
login.authorizeUrl = _.template "https://github.com/login/oauth/authorize" +
|
||||
"?client_id=<%= clientId %>&scope=public_repo&state=<%= state %>"
|
||||
@@ -82,12 +102,6 @@ login.clientIds =
|
||||
exports.modpack = modpack = {}
|
||||
modpack.default = "crafting-guide-default"
|
||||
|
||||
exports.modelState = modelState = {}
|
||||
modelState.unloaded = 'unloaded'
|
||||
modelState.loading = 'loading'
|
||||
modelState.loaded = 'loaded'
|
||||
modelState.failed = 'failed'
|
||||
|
||||
exports.opacity = opacity = {}
|
||||
opacity.hidden = 1e-6
|
||||
opacity.shown = 1
|
||||
@@ -138,22 +152,3 @@ tracking.category.modVote = 'mod-vote'
|
||||
tracking.category.multiblock = 'multiblock'
|
||||
tracking.category.navigate = 'navigate'
|
||||
tracking.category.search = 'search'
|
||||
|
||||
exports.url = url = {}
|
||||
url.crafting = _.template "/craft/<%= inventoryText %>"
|
||||
url.item = _.template "/browse/<%= modSlug %>/<%= itemSlug %>/"
|
||||
url.itemData = _.template "/data/<%= modSlug %>/items/<%= itemSlug %>/item.cg"
|
||||
url.itemIcon = _.template "/data/<%= modSlug %>/items/<%= itemSlug %>/icon.png"
|
||||
url.itemImageDir = _.template "/data/<%= modSlug %>/items/<%= itemSlug %>"
|
||||
url.login = _.template "/login"
|
||||
url.mod = _.template "/browse/<%= modSlug %>/"
|
||||
url.modData = _.template "/data/<%= modSlug %>/mod.cg"
|
||||
url.modIcon = _.template "/data/<%= modSlug %>/icon.png"
|
||||
url.modPackData = _.template "/data/<%= modPackId %>/modpack.json"
|
||||
url.modVersionData = _.template "/data/<%= modSlug %>/versions/<%= modVersion %>/mod-version.cg"
|
||||
url.root = _.template "/"
|
||||
url.tutorial = _.template "/browse/<%= modSlug %>/tutorials/<%= tutorialSlug %>/"
|
||||
url.tutorialData = _.template "/data/<%= modSlug %>/tutorials/<%= tutorialSlug %>/tutorial.cg"
|
||||
url.tutorialIcon = _.template "/data/<%= modSlug %>/tutorials/<%= tutorialSlug %>/icon.png"
|
||||
url.tutorialIcon = _.template "/data/<%= modSlug %>/tutorials/<%= tutorialSlug %>/icon.png"
|
||||
url.tutorialImageDir = _.template "/data/<%= modSlug %>/tutorials/<%= tutorialSlug %>"
|
||||
|
||||
@@ -5,33 +5,4 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
module.exports = _ = require 'underscore'
|
||||
|
||||
_.mixin require 'underscore.inflections'
|
||||
_.mixin require('crafting-guide-common').stringMixins
|
||||
|
||||
_.mixin
|
||||
parseMarkdown: (text)->
|
||||
return markdown.parse text, 'Maruku'
|
||||
|
||||
slugify: (text)->
|
||||
return null unless text?
|
||||
|
||||
result = text.toLowerCase()
|
||||
result = result.replace /[^a-zA-Z0-9_]/g, '_'
|
||||
result = result.replace /__+/g, '_'
|
||||
result = result.replace /^_/, ''
|
||||
result = result.replace /_$/, ''
|
||||
return result
|
||||
|
||||
composeSlugs: (part1, part2)->
|
||||
return "#{part1}__#{part2}"
|
||||
|
||||
decomposeSlug: (slug)->
|
||||
return [null, null] unless slug?
|
||||
|
||||
parts = slug.split '__'
|
||||
if parts.length is 1
|
||||
parts = [ null, parts[0] ]
|
||||
|
||||
return parts
|
||||
module.exports = _ = require('crafting-guide-common')._
|
||||
|
||||
+1
-10
@@ -7,13 +7,4 @@
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
global.Backbone = require 'backbone'
|
||||
global._ = require './common/underscore'
|
||||
global.c = require './common/constants'
|
||||
|
||||
module.exports =
|
||||
constants: require './common/constants'
|
||||
Mod: require './client/models/game/mod'
|
||||
ModParser: require './client/models/parsing/mod_parser'
|
||||
ModVersion: require './client/models/game/mod_version'
|
||||
ModVersionParser: require './client/models/parsing/mod_version_parser'
|
||||
# Just to keep NPM happy... nothing is actually exported.
|
||||
|
||||
@@ -9,6 +9,7 @@ express = require 'express'
|
||||
http = require 'http'
|
||||
middleware = require './middleware'
|
||||
routes = require './routes'
|
||||
w = require "when"
|
||||
|
||||
############################################################################################################
|
||||
|
||||
|
||||
@@ -9,12 +9,6 @@ require('dotenv').config()
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
global._ = require './underscore'
|
||||
global.c = require './constants'
|
||||
global.w = require 'when'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
CraftingGuideServer = require './crafting_guide_server'
|
||||
server = new CraftingGuideServer process.env.WEBSITE_SERVER_PORT
|
||||
server.start()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
###
|
||||
#
|
||||
# Crafting Guide - test.coffee
|
||||
#
|
||||
# Copyright (c) 2014-2015 by Redwood Labs
|
||||
# All rights reserved.
|
||||
###
|
||||
#
|
||||
|
||||
# Test Set-up ##########################################################################################################
|
||||
|
||||
@@ -27,5 +27,5 @@ global.π = Math.PI
|
||||
global.Backbone = Backbone = require 'backbone'
|
||||
Backbone.$ = $
|
||||
|
||||
{Logger} = require 'crafting-guide-common'
|
||||
{Logger} = require('crafting-guide-common').util
|
||||
global.logger = new Logger level:Logger.FATAL
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# Crafting Guide - underscore.coffee
|
||||
#
|
||||
# Copyright (c) 2014-2017 by Redwood Labs
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = _ = require("crafting-guide-common")._
|
||||
Reference in New Issue
Block a user