Restructure static files into a single directory
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
###
|
||||
Crafting Guide - constants.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
# Minecraft must be first
|
||||
exports.DefaultMods = [
|
||||
'minecraft',
|
||||
'applied_energistics_2',
|
||||
'buildcraft',
|
||||
'enderio',
|
||||
'industrial_craft_2',
|
||||
'railcraft',
|
||||
'thermal_expansion',
|
||||
]
|
||||
|
||||
exports.Duration = Duration = {}
|
||||
Duration.snap = 200
|
||||
Duration.fast = Duration.snap * 2
|
||||
Duration.normal = Duration.fast * 2
|
||||
Duration.slow = Duration.normal * 2
|
||||
|
||||
exports.Event = Event = {}
|
||||
Event.add = 'add' # collection, item...
|
||||
Event.change = 'change' # model
|
||||
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
|
||||
Event.transitionEnd = 'webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend'
|
||||
|
||||
exports.Key = Key = {}
|
||||
Key.Return = 13
|
||||
|
||||
exports.Opacity = Opacity = {}
|
||||
Opacity.hidden = 1e-6
|
||||
Opacity.shown = 1
|
||||
|
||||
exports.RequiredMods = [ 'minecraft' ]
|
||||
|
||||
exports.ModelState = ModelState = {}
|
||||
ModelState.unloaded = 'unloaded'
|
||||
ModelState.loading = 'loading'
|
||||
ModelState.loaded = 'loaded'
|
||||
ModelState.failed = 'failed'
|
||||
|
||||
exports.Text = Text = {}
|
||||
Text.title = 'Crafting Guide for Minecraft | The Ultimate Step-by-Step Tutorial for Making Anything in Minecraft'
|
||||
|
||||
exports.Url = Url = {}
|
||||
Url.crafting = _.template "/craft/<%= inventoryText %>"
|
||||
Url.itemIcon = _.template "/browse/<%= modSlug %>/<%= itemSlug %>/icon.png"
|
||||
Url.item = _.template "/browse/<%= modSlug %>/<%= itemSlug %>/"
|
||||
Url.mod = _.template "/browse/<%= modSlug %>/"
|
||||
Url.modData = _.template "/data/<%= modSlug %>/mod.cg"
|
||||
Url.modIcon = _.template "/browse/<%= modSlug %>/icon.png"
|
||||
Url.modVersion = _.template "/data/<%= modSlug %>/<%= modVersion %>/mod-version.cg"
|
||||
|
||||
exports.UrlParam = UrlParam = {}
|
||||
UrlParam.quantity = 'count'
|
||||
UrlParam.recipe = 'recipeName'
|
||||
UrlParam.includingTools = 'tools'
|
||||
@@ -0,0 +1,124 @@
|
||||
###
|
||||
Crafting Guide - base_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
views = require '../views'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BaseController extends Backbone.View
|
||||
|
||||
constructor: (options={})->
|
||||
@tryRefresh = _.debounce @_tryRefresh, 100
|
||||
Object.defineProperty this, 'model', get:@getModel, set:@setModel
|
||||
|
||||
@_model = null
|
||||
@_rendered = false
|
||||
@_parent = options.parent
|
||||
@_children = []
|
||||
|
||||
Object.defineProperty this, 'rendered', get:-> return @_rendered
|
||||
@_loadTemplate options.templateName
|
||||
super options
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
addChild: (Controller, atSelector, options={})->
|
||||
options.el = @$(atSelector)[0]
|
||||
options.parent = this
|
||||
|
||||
child = new Controller options
|
||||
child.render()
|
||||
@_children.push child
|
||||
return child
|
||||
|
||||
refresh: ->
|
||||
logger.verbose => "#{this} refreshing"
|
||||
|
||||
routeLinkClick: (event)->
|
||||
href = $(event.currentTarget).attr 'href'
|
||||
router.navigate href, trigger:true
|
||||
return false
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onDidModelChange: ->
|
||||
@tryRefresh()
|
||||
|
||||
onDidModelSync: ->
|
||||
@tryRefresh()
|
||||
|
||||
onWillRender: -> # do nothing
|
||||
|
||||
onDidRender: ->
|
||||
@tryRefresh()
|
||||
|
||||
onWillShow: -> # do nothing
|
||||
|
||||
onDidShow: -> # do nothing
|
||||
|
||||
onWillChangeModel: (oldModel, newModel)->
|
||||
if oldModel?.on?
|
||||
@stopListening oldModel
|
||||
if newModel?.on?
|
||||
@listenTo newModel, 'sync', (e)=> @onDidModelSync e
|
||||
@listenTo newModel, 'change', (e)=> @onDidModelChange e
|
||||
return true
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getModel: ->
|
||||
return @_model
|
||||
|
||||
setModel: (newModel)->
|
||||
return if @model is newModel
|
||||
return unless @onWillChangeModel @_model, newModel
|
||||
|
||||
@_model = newModel
|
||||
@tryRefresh()
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return {}
|
||||
|
||||
render: (options={})->
|
||||
return this unless not @_rendered or options.force
|
||||
|
||||
data = (@model?.toHash? and @model.toHash()) or @model or {}
|
||||
|
||||
if not @_template?
|
||||
logger.error => "Default render called for #{@constructor.name} without a template"
|
||||
return this
|
||||
|
||||
logger.verbose => "#{this} rendering with data: #{data}"
|
||||
@onWillRender()
|
||||
$oldEl = @$el
|
||||
$newEl = Backbone.$(@_template(data))
|
||||
if $oldEl
|
||||
$oldEl.replaceWith $newEl
|
||||
$newEl.addClass $oldEl.attr 'class'
|
||||
|
||||
@setElement $newEl
|
||||
@_rendered = true
|
||||
@onDidRender()
|
||||
|
||||
return this
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name}.#{@cid}"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_loadTemplate: (templateName)->
|
||||
if templateName?
|
||||
@_template = views[templateName]
|
||||
|
||||
_tryRefresh: ->
|
||||
return unless @_rendered
|
||||
@refresh()
|
||||
@@ -0,0 +1,58 @@
|
||||
###
|
||||
Crafting Guide - browse_page_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
PageController = require './page_controller'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
ModController = require './mod_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BrowsePageController extends PageController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.templateName = 'browse_page'
|
||||
super options
|
||||
|
||||
@modPack = options.modPack
|
||||
@modPack.on Event.change, => @tryRefresh()
|
||||
|
||||
# PageController Overrides #####################################################################
|
||||
|
||||
getTitle: ->
|
||||
return 'Browse'
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$modContainer = @$('.mods')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@_controllers ?= []
|
||||
controllerIndex = 0
|
||||
|
||||
@modPack.eachMod (mod)=>
|
||||
return unless mod.enabled
|
||||
|
||||
controller = @_controllers[controllerIndex]
|
||||
if not controller?
|
||||
controller = new ModController model:mod
|
||||
controller.render()
|
||||
@$modContainer.append controller.$el
|
||||
else
|
||||
controller.model = mod
|
||||
|
||||
controllerIndex += 1
|
||||
|
||||
while @_controllers.length > controllerIndex
|
||||
controller = @_controllers.pop()
|
||||
controller.$el.addClass 'removing'
|
||||
controller.$el.one Event.transitionEnd -> controller.$el.remove()
|
||||
|
||||
super
|
||||
@@ -0,0 +1,33 @@
|
||||
###
|
||||
Crafting Guide - configure_page_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
PageController = require './page_controller'
|
||||
ModPackController = require './mod_pack_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ConfigurePageController extends PageController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
if not options.storage? then throw new Error 'options.storage is required'
|
||||
options.templateName = 'configure_page'
|
||||
super options
|
||||
|
||||
@modPack = options.modPack
|
||||
@storage = options.storage
|
||||
|
||||
# PageController Overrides #####################################################################
|
||||
|
||||
getTitle: ->
|
||||
return "Configure"
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@modPackController = @addChild ModPackController, '.view__mod_pack', model:@modPack, storage:@storage
|
||||
super
|
||||
@@ -0,0 +1,113 @@
|
||||
###
|
||||
Crafting Guide - craft_page_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
CraftingTableController = require './crafting_table_controller'
|
||||
CraftPage = require '../models/craft_page'
|
||||
{Event} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
InventoryController = require './inventory_controller'
|
||||
ModPackController = require './mod_pack_controller'
|
||||
NameFinder = require '../models/name_finder'
|
||||
PageController = require './page_controller'
|
||||
Storage = require '../models/storage'
|
||||
{Text} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftPageController extends PageController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
|
||||
options.model ?= new CraftPage modPack:options.modPack
|
||||
options.storage ?= new Storage storage:window.localStorage
|
||||
options.templateName = 'craft_page'
|
||||
super options
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@storage = options.storage
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onToolsBoxToggled: ->
|
||||
@model.plan.includingTools = @$('.includeTools:checked').length isnt 0
|
||||
|
||||
# PageController Overrides #####################################################################
|
||||
|
||||
getTitle: ->
|
||||
return 'Craft'
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onWillRender: ->
|
||||
@storage.register 'crafting-plan', @model.plan, 'includingTools'
|
||||
@model.plan.have.clear()
|
||||
@model.plan.have.parse @storage.load('crafting-plan:have')
|
||||
super
|
||||
|
||||
onDidRender: ->
|
||||
@wantController = @addChild InventoryController, '.want',
|
||||
editable: true
|
||||
icon: '/images/fishing_rod.png'
|
||||
imageLoader: @imageLoader
|
||||
model: @model.plan.want
|
||||
modPack: @model.modPack
|
||||
onChange: => @_updateLocation()
|
||||
title: 'Items you want'
|
||||
|
||||
@haveController = @addChild InventoryController, '.have',
|
||||
editable: true,
|
||||
imageLoader: @imageLoader
|
||||
model: @model.plan.have
|
||||
modPack: @model.modPack
|
||||
onChange: => @_saveHaveInventory()
|
||||
nameFinder: new NameFinder @model.modPack, includeGatherable:true
|
||||
title: 'Items you have'
|
||||
|
||||
@needController = @addChild InventoryController, '.need',
|
||||
editable: false
|
||||
icon: '/images/boots.png'
|
||||
imageLoader: @imageLoader
|
||||
model: @model.plan.need
|
||||
modPack: @model.modPack
|
||||
title: "Items you'll need"
|
||||
|
||||
@craftingTableController = @addChild CraftingTableController, '.view__crafting_table',
|
||||
imageLoader: @imageLoader
|
||||
model: @model.table
|
||||
modPack: @model.modPack
|
||||
|
||||
@$('.want .toolbar').append '<label><input class="includeTools" type="checkbox"> include tools</label>'
|
||||
@$includeToolsBox = @$('.includeTools')
|
||||
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
if @model.plan.includingTools
|
||||
@$includeToolsBox.attr 'checked', 'checked'
|
||||
else
|
||||
@$includeToolsBox.removeAttr 'checked'
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'change .includeTools': 'onToolsBoxToggled'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_saveHaveInventory: ->
|
||||
@storage.store 'crafting-plan:have', @model.plan.have.unparse()
|
||||
|
||||
_updateLocation: ->
|
||||
text = @model.plan.want.unparse()
|
||||
url = Url.crafting inventoryText:text
|
||||
router.navigate url
|
||||
@@ -0,0 +1,74 @@
|
||||
###
|
||||
Crafting Guide - crafting_grid_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingGridController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.templateName = 'crafting_grid'
|
||||
super options
|
||||
|
||||
@_imageLoader = options.imageLoader
|
||||
@_modPack = options.modPack
|
||||
@_slotCount = 9
|
||||
|
||||
@_modPack.on Event.change, => @tryRefresh()
|
||||
|
||||
# BaseController Methods #######################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@slots = []
|
||||
for el in @$('td')
|
||||
$el = $(el)
|
||||
@slots.push a:$el.find('a'), img:$el.find('img')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
for index in [0...@slots.length]
|
||||
slot = @slots[index]
|
||||
|
||||
slot.a.addClass 'empty'
|
||||
slot.a.removeAttr 'href'
|
||||
slot.img.attr 'src', '/images/empty.png'
|
||||
slot.img.removeAttr 'alt'
|
||||
|
||||
display = @_getItemDisplayAt index
|
||||
if display?
|
||||
slot.a.removeClass 'empty'
|
||||
slot.a.attr 'href', display.itemUrl
|
||||
slot.a.attr 'title', display.itemName
|
||||
@_imageLoader.load display.iconUrl, slot.img
|
||||
slot.img.attr 'alt', display.itemName
|
||||
|
||||
@$el.tooltip show:{delay:Duration.snap, duration:Duration.fast}
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click td a': 'routeLinkClick'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_getItemDisplayAt: (slot)->
|
||||
if slot >= @_slotCount then throw new Error "slot (#{slot}) must be less than #{@_slotCount}"
|
||||
return null unless @model?
|
||||
|
||||
itemSlug = @model.getItemSlugAt slot
|
||||
return null unless itemSlug?
|
||||
|
||||
itemDisplay = @_modPack.findItemDisplay itemSlug
|
||||
return itemDisplay
|
||||
@@ -0,0 +1,94 @@
|
||||
###
|
||||
Crafting Guide - crafting_table_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
MinimalRecipeController = require './minimal_recipe_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingTableController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.templateName = 'crafting_table'
|
||||
super options
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onNextClicked: ->
|
||||
@model.stepIndex += 1
|
||||
|
||||
onPrevClicked: ->
|
||||
@model.stepIndex -= 1
|
||||
|
||||
onReportProblem: ->
|
||||
itemList = @model.plan.want.unparse()
|
||||
toolsMessage = if @model.plan.includingTools then '(including tools)' else ''
|
||||
message = "When I was on step #{@model.stepIndex + 1} of making:
|
||||
\n\n#{itemList}#{toolsMessage}\n\nI noticed that...\n"
|
||||
global.feedbackController.enterFeedback message
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@recipeController = @addChild MinimalRecipeController, '.view__minimal_recipe',
|
||||
imageLoader: @imageLoader
|
||||
modPack: @modPack
|
||||
|
||||
@$next = @$('.next')
|
||||
@$prev = @$('.prev')
|
||||
@$problemControl = @$('.problem')
|
||||
@$title = @$('h2 p')
|
||||
@$tool = @$('.tool p')
|
||||
|
||||
@$multiplier = $('<p class="multiplier"></p>')
|
||||
@$('.output').append @$multiplier
|
||||
|
||||
@defaultTitle = @$title.html()
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@$prev.removeClass 'enabled'
|
||||
@$next.removeClass 'enabled'
|
||||
|
||||
if @model.hasSteps
|
||||
if @model.hasPrevStep then @$prev.addClass 'enabled'
|
||||
if @model.hasNextStep then @$next.addClass 'enabled'
|
||||
@$title.html "Step #{@model.stepIndex + 1} of #{@model.stepCount}"
|
||||
else
|
||||
@$title.html @defaultTitle
|
||||
|
||||
currentStep = @model.currentStep
|
||||
@recipeController.model = currentStep?.recipe
|
||||
|
||||
if currentStep?.multiplier > 1
|
||||
@$multiplier.html "×#{currentStep.multiplier}"
|
||||
else
|
||||
@$multiplier.html ''
|
||||
|
||||
if not (@model.hasSteps and global.feedbackController?)
|
||||
@$problemControl.hide duration:Duration.snap
|
||||
else
|
||||
@$problemControl.show duration:Duration.snap
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click .next': 'onNextClicked'
|
||||
'click .prev': 'onPrevClicked'
|
||||
'click .problem a': 'onReportProblem'
|
||||
@@ -0,0 +1,112 @@
|
||||
###
|
||||
Crafting Guide - feedback_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Duration} = require '../constants'
|
||||
EmailClient = require '../models/email_client'
|
||||
{Key} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class FeedbackController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
options.model ?= new EmailClient
|
||||
options.templateName = 'feedback'
|
||||
super options
|
||||
|
||||
Object.defineProperty this, 'isOpen', get:-> @$el.offset().left is 0
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
enterFeedback: (message)->
|
||||
promise = w(true)
|
||||
if not @isOpen then promise = @onToggle()
|
||||
promise.then => @$commentField.val message
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onSendClicked: ->
|
||||
return unless @$commentField.val().length > 0
|
||||
@$sendButton.attr 'disabled', 'disabled'
|
||||
|
||||
message =
|
||||
subject:'Crafting Guide Feedback'
|
||||
body:
|
||||
"""
|
||||
url: #{window.location.href}
|
||||
name: #{@$nameField.val()}
|
||||
email: #{@$emailField.val()}
|
||||
comment:
|
||||
|
||||
#{@$commentField.val()}
|
||||
"""
|
||||
|
||||
@model.send(message)
|
||||
.then =>
|
||||
@onToggle()
|
||||
@$error.slideUp duration:Duration.normal
|
||||
.catch (error)=>
|
||||
@$error.slideDown duration:Duration.normal
|
||||
.finally =>
|
||||
@$sendButton.removeAttr 'disabled'
|
||||
|
||||
onTextChanged: (event)->
|
||||
if @$commentField.val().length > 0
|
||||
@$sendButton.removeAttr 'disabled'
|
||||
else
|
||||
@$sendButton.attr 'disabled', 'disabled'
|
||||
|
||||
onToggle: ->
|
||||
w.promise (resolve)=>
|
||||
@$screen.off 'click'
|
||||
|
||||
if @isOpen
|
||||
@$screen.css display:'none'
|
||||
@$el.animate {left:-@$el.outerWidth()}, duration:Duration.fast, complete:=>
|
||||
@$commentField.val ''
|
||||
@$('input, textarea').blur()
|
||||
resolve()
|
||||
else
|
||||
@$screen.on 'click', => @onToggle()
|
||||
@$screen.css display:'block'
|
||||
|
||||
@$el.animate {left:0}, duration:Duration.fast, complete:=>
|
||||
if @$nameField.val().length is 0
|
||||
@$nameField.focus()
|
||||
else if @$emailField.val().length is 0
|
||||
@$emailField.focus()
|
||||
else
|
||||
@$commentField.focus()
|
||||
|
||||
resolve()
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$screen = $('.view__screen')
|
||||
|
||||
@$commentField = @$('textarea[name="comment"]')
|
||||
@$emailField = @$('input[name="email"]')
|
||||
@$error = @$('.error')
|
||||
@$nameField = @$('input[name="name"]')
|
||||
@$sendButton = @$('button[name="send"]')
|
||||
|
||||
windowHeight = window.innerHeight
|
||||
@$el.delay(Duration.slow).animate {left:-@$el.outerWidth()}, Duration.normal
|
||||
@onTextChanged()
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Methods ########################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click .label': 'onToggle'
|
||||
'click button': 'onSendClicked'
|
||||
'input textarea': 'onTextChanged'
|
||||
'keyup textarea': 'onTextChanged'
|
||||
@@ -0,0 +1,95 @@
|
||||
###
|
||||
Crafting Guide - full_recipe_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
CraftingGridController = require './crafting_grid_controller'
|
||||
{Duration} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
Inventory = require '../models/inventory'
|
||||
InventoryTableController = require './inventory_table_controller'
|
||||
StringBuilder = require '../models/string_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class FullRecipeController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.templateName = 'full_recipe'
|
||||
super options
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@gridController = @addChild CraftingGridController, '.view__crafting_grid',
|
||||
modPack: @modPack
|
||||
imageLoader: @imageLoader
|
||||
|
||||
@inputController = @addChild InventoryTableController, '.input .view__inventory_table',
|
||||
editable: false
|
||||
imageLoader: @imageLoader
|
||||
model: new Inventory
|
||||
modPack: @modPack
|
||||
|
||||
@outputController = @addChild InventoryTableController, '.output .view__inventory_table',
|
||||
editable: false
|
||||
imageLoader: @imageLoader
|
||||
model: new Inventory
|
||||
modPack: @modPack
|
||||
|
||||
@$toolContainer = @$('.tool')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@gridController.model = @model
|
||||
|
||||
@$el.tooltip show:{delay:Duration.normal, duration:Duration.normal}
|
||||
|
||||
@_refreshInputs()
|
||||
@_refreshOutputs()
|
||||
@_refreshTools()
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Methods ########################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click a': 'routeLinkClick'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshInputs: ->
|
||||
inputs = @inputController.model
|
||||
inputs.clear()
|
||||
|
||||
if @model?
|
||||
for stack in @model.input
|
||||
inputs.add stack.itemSlug, stack.quantity
|
||||
|
||||
|
||||
_refreshOutputs: ->
|
||||
outputs = @outputController.model
|
||||
outputs.clear()
|
||||
|
||||
if @model?
|
||||
for stack in @model.output
|
||||
outputs.add stack.itemSlug, stack.quantity
|
||||
|
||||
_refreshTools: ->
|
||||
@$toolContainer.empty()
|
||||
return unless @model?
|
||||
|
||||
builder = new StringBuilder
|
||||
builder.loop @model.tools, delimiter:', ', onEach:(b, stack)=>
|
||||
display = @modPack.findItemDisplay stack.itemSlug
|
||||
b.push "<a href=\"#{display.itemUrl}\">#{display.itemName}</a>"
|
||||
@$toolContainer.html builder.toString()
|
||||
@@ -0,0 +1,60 @@
|
||||
###
|
||||
Crafting Guide - header_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class HeaderController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
super options
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onLogoClicked: ->
|
||||
router.navigate '/', trigger:true
|
||||
return false
|
||||
|
||||
onNavItemClicked: (event)->
|
||||
return true unless $(event.currentTarget).attr('href')?
|
||||
|
||||
router.navigate $(event.currentTarget).attr('href'), trigger:true
|
||||
return false
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
render: ->
|
||||
# Overriding render because the header is already part of the stock page layout, and we
|
||||
# don't need to replace it here.
|
||||
@_rendered = true
|
||||
|
||||
@$navLinks = ($(el) for el in @$('.navBar a'))
|
||||
|
||||
zIndex = @$navLinks.length + 100
|
||||
for $navLink in @$navLinks
|
||||
$navLink.css 'z-index', zIndex--
|
||||
|
||||
refresh: ->
|
||||
for $navLink in @$navLinks
|
||||
linkPage = $navLink.data('page')
|
||||
if @model is linkPage
|
||||
$navLink.addClass 'selected'
|
||||
else
|
||||
$navLink.removeClass 'selected'
|
||||
|
||||
if @model.indexOf(linkPage) isnt -1
|
||||
$navLink.find('.dot').css 'opacity': 1;
|
||||
else
|
||||
$navLink.find('.dot').css 'opacity': 0;
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click a.logo': 'onLogoClicked'
|
||||
'click .navBar a': 'onNavItemClicked'
|
||||
@@ -0,0 +1,22 @@
|
||||
###
|
||||
Crafting Guide - home_page_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
PageController = require './page_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class HomeController extends PageController
|
||||
|
||||
constructor: (options={})->
|
||||
options.templateName = 'home_page'
|
||||
super options
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click a': 'routeLinkClick'
|
||||
@@ -0,0 +1,105 @@
|
||||
###
|
||||
Crafting Guide - image_loader.coffee
|
||||
|
||||
Copyright (c) 2014 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
{Duration} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ImageLoader
|
||||
|
||||
constructor: (options={})->
|
||||
options.defaultUrl ?= null
|
||||
|
||||
if not options.defaultUrl?
|
||||
options.onLoading ?= -> @hide()
|
||||
options.onLoad ?= -> @show()
|
||||
|
||||
@defaultUrl = options.defaultUrl
|
||||
@onLoading = options.onLoading
|
||||
@onLoad = options.onLoad
|
||||
|
||||
@_images = {}
|
||||
|
||||
if @defaultUrl
|
||||
@_defaultImage = new Image
|
||||
@_defaultImage.src = @defaultUrl
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@load: (imageUrl, $el, options={}) ->
|
||||
loader = new ImageLoader options
|
||||
loader.load $el, options
|
||||
return loader
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
isLoaded: (imageUrl)->
|
||||
data = @_images[imageUrl]
|
||||
return false unless data?
|
||||
return data.isLoaded
|
||||
|
||||
load: (imageUrl, $el)->
|
||||
return if not $el
|
||||
|
||||
data = @preload imageUrl
|
||||
if $el.attr('src')?
|
||||
return if $el.attr('src').indexOf(imageUrl) isnt -1
|
||||
|
||||
if @onLoading? then @onLoading.call $el
|
||||
$el.data 'isLoading', true
|
||||
$el.data 'isLoaded', false
|
||||
|
||||
if data.isLoaded
|
||||
@_loadImageIntoElement data.imageUrl, $el
|
||||
else
|
||||
if @defaultUrl?
|
||||
$el.attr 'src', @defaultUrl
|
||||
else
|
||||
$el.removeAttr 'src'
|
||||
|
||||
if data.elements.indexOf($el) is -1 then data.elements.push $el
|
||||
|
||||
return this
|
||||
|
||||
preload: (imageUrl)->
|
||||
data = @_images[imageUrl]
|
||||
if not data?
|
||||
data = imageUrl:imageUrl, elements:[], image:new Image, isLoaded:false
|
||||
@_images[imageUrl] = data
|
||||
|
||||
data.image = new Image
|
||||
data.image.onload = => @_onImageLoaded data
|
||||
data.image.onerror = => @_onImageError data
|
||||
data.image.src = imageUrl
|
||||
|
||||
return data
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_onImageLoaded: (data)->
|
||||
for $el in data.elements
|
||||
@_loadImageIntoElement data.imageUrl, $el
|
||||
|
||||
data.elements = []
|
||||
data.isLoaded = true
|
||||
|
||||
_onImageError: (data)->
|
||||
if @defaultUrl?
|
||||
data.imageUrl = @defaultUrl
|
||||
|
||||
for $el in data.elements
|
||||
@_loadImageIntoElement @defaultUrl, $el
|
||||
|
||||
data.elements = []
|
||||
data.isLoaded = true
|
||||
|
||||
_loadImageIntoElement: (imageUrl, $el)->
|
||||
$el.data 'isLoading', false
|
||||
$el.data 'isLoaded', true
|
||||
$el.attr 'src', imageUrl
|
||||
|
||||
if @onLoad? then @onLoad.call $el
|
||||
@@ -0,0 +1,181 @@
|
||||
###
|
||||
Crafting Guide - inventory_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
{Key} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
NameFinder = require '../models/name_finder'
|
||||
StackController = require './stack_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class InventoryController extends BaseController
|
||||
|
||||
@MAX_QUANTITY = 9999
|
||||
|
||||
@ONLY_DIGITS = /^[0-9]*$/
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
|
||||
@editable = options.editable ?= true
|
||||
@icon = options.icon ?= '/images/chest_front.png'
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
@nameFinder = options.nameFinder ?= new NameFinder options.modPack
|
||||
@onChange = options.onChange ?= -> # do nothing
|
||||
@title = options.title ?= 'Inventory'
|
||||
|
||||
options.templateName = 'inventory'
|
||||
super options
|
||||
|
||||
@_stackControllers = []
|
||||
|
||||
@listenTo @modPack, Event.change, => @refresh()
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onAddButtonClicked: ->
|
||||
if @$nameField.val().trim().length is 0
|
||||
@$nameField.focus()
|
||||
return
|
||||
|
||||
item = @modPack.findItemByName @$nameField.val()
|
||||
return unless item?
|
||||
|
||||
@model.add item.slug, 1
|
||||
@$nameField.val ''
|
||||
|
||||
@$scrollbox.scrollTop @$scrollbox.prop 'scrollHeight'
|
||||
@$nameField.autocomplete 'close'
|
||||
|
||||
@onChange()
|
||||
|
||||
onClearButtonClicked: ->
|
||||
@model.clear()
|
||||
@onChange()
|
||||
|
||||
onItemSelected: ->
|
||||
func = =>
|
||||
@onNameFieldChanged()
|
||||
@onAddButtonClicked()
|
||||
@$nameField.blur()
|
||||
|
||||
setTimeout func, 10 # needed to allow the autocomplete to finish
|
||||
return true
|
||||
|
||||
onNameFieldBlur: ->
|
||||
item = @modPack.findItemByName @$nameField.val()
|
||||
@$nameField.val if item? then item.name else ''
|
||||
@onNameFieldChanged()
|
||||
|
||||
onNameFieldChanged: ->
|
||||
@_refreshButtonState()
|
||||
|
||||
onNameFieldFocused: ->
|
||||
@$nameField.val ''
|
||||
@$nameField.autocomplete 'search'
|
||||
|
||||
onNameFieldKeyUp: (event)->
|
||||
if event.which is Key.Return
|
||||
@onAddButtonClicked()
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$addButton = @$('button[name="add"]')
|
||||
@$clearButton = @$('button[name="clear"]')
|
||||
@$icon = @$('.icon')
|
||||
@$editPanel = @$('.edit')
|
||||
@$nameField = @$('input[name="name"]')
|
||||
@$scrollbox = @$('.scrollbox')
|
||||
@$table = @$('table')
|
||||
@$toolbar = @$('.toolbar')
|
||||
@$title = @$('h2 p')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@$editPanel.css display:(if @editable then 'table-row' else 'none')
|
||||
@$toolbar.css display:(if @editable then 'block' else 'none')
|
||||
@$scrollbox.css bottom:(if @editable then @$toolbar.height() else '0')
|
||||
|
||||
@$icon.attr 'src', @icon
|
||||
@$title.html @title
|
||||
|
||||
@_refreshStacks()
|
||||
@_refreshNameAutocomplete()
|
||||
@_refreshButtonState()
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'blur input[name="name"]': 'onNameFieldBlur'
|
||||
'click button[name="add"]': 'onAddButtonClicked'
|
||||
'click button[name="clear"]': 'onClearButtonClicked'
|
||||
'focus input[name="name"]': 'onNameFieldFocused'
|
||||
'input input[name="name"]': 'onNameFieldChanged'
|
||||
'keyup input[name="name"]': 'onNameFieldKeyUp'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshButtonState: ->
|
||||
if @model.isEmpty then @$clearButton.attr('disabled', 'disabled') else @$clearButton.removeAttr('disabled')
|
||||
|
||||
noText = @$nameField.val().trim().length is 0
|
||||
itemValid = @modPack.findItemByName(@$nameField.val())?
|
||||
disable = not (itemValid or noText)
|
||||
if disable then @$addButton.attr('disabled', 'disabled') else @$addButton.removeAttr('disabled')
|
||||
|
||||
_refreshNameAutocomplete: ->
|
||||
onChanged = => @onNameFieldChanged()
|
||||
onSelected = => @onItemSelected()
|
||||
|
||||
@$nameField.autocomplete
|
||||
source: (request, callback)=> callback @nameFinder.search request.term
|
||||
delay: 0
|
||||
minLength: 0
|
||||
change: onChanged
|
||||
close: onChanged
|
||||
select: onSelected
|
||||
|
||||
_refreshStacks: ->
|
||||
@_stackControllers ?= []
|
||||
index = 0
|
||||
|
||||
$lastRow = @$table.find 'tr:last-child'
|
||||
@model.each (stack)=>
|
||||
controller = @_stackControllers[index]
|
||||
if not controller?
|
||||
controller = new StackController
|
||||
editable: @editable
|
||||
imageLoader: @imageLoader
|
||||
model: stack
|
||||
modPack: @modPack
|
||||
onChange: @onChange
|
||||
onRemove: if not @editable then null else (stack)=> @_removeStack(stack)
|
||||
controller.render()
|
||||
controller.$el.hide()
|
||||
controller.$el.insertBefore $lastRow
|
||||
controller.$el.slideDown duration:Duration.fast
|
||||
@_stackControllers.push controller
|
||||
else
|
||||
controller.model = stack
|
||||
index += 1
|
||||
|
||||
while @_stackControllers.length > index
|
||||
controller = @_stackControllers.pop()
|
||||
controller.$el.fadeOut duration:Duration.fast, complete:-> @remove()
|
||||
|
||||
_removeStack: (stack)->
|
||||
@model.remove stack.itemSlug, stack.quantity
|
||||
@@ -0,0 +1,184 @@
|
||||
###
|
||||
Crafting Guide - inventory_table_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Duration} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
ItemSlug = require '../models/item_slug'
|
||||
{Key} = require '../constants'
|
||||
NameFinder = require '../models/name_finder'
|
||||
StackController = require './stack_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class InventoryTableController extends BaseController
|
||||
|
||||
@ONLY_DIGITS = /^[0-9]*$/
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
|
||||
@editable = options.editable ?= true
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
@nameFinder = options.nameFinder ?= new NameFinder options.modPack
|
||||
@onChange = options.onChange ?= -> # do nothing
|
||||
|
||||
options.templateName = 'inventory_table'
|
||||
super options
|
||||
|
||||
@_stackControllers = []
|
||||
|
||||
@listenTo @modPack, 'change', => @refresh()
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onAddButtonClicked: ->
|
||||
name = @modpack.findItemByName @$nameField.val()
|
||||
return unless item?
|
||||
|
||||
@model.add item.slug, parseInt(@$quantityField.val())
|
||||
@$nameField.val ''
|
||||
@$quantityField.val '1'
|
||||
|
||||
@$scrollbox.scrollTop @$scrollbox.prop 'scrollHeight'
|
||||
@$nameField.autocomplete 'close'
|
||||
|
||||
@onChange()
|
||||
|
||||
onClearButtonClicked: ->
|
||||
@model.clear()
|
||||
@onChange()
|
||||
|
||||
onItemSelected: ->
|
||||
func = =>
|
||||
@onNameFieldChanged()
|
||||
@onAddButtonClicked()
|
||||
@$nameField.blur()
|
||||
|
||||
setTimeout func, 10 # needed to allow the autocomplete to finish
|
||||
return true
|
||||
|
||||
onNameFieldBlur: ->
|
||||
item = @modPack.findItemByName @$nameField.val()
|
||||
@$nameField.val if item? then item.name else ''
|
||||
@onNameFieldChanged()
|
||||
|
||||
onNameFieldChanged: ->
|
||||
item = @modPack.findItemByName @$nameField.val()
|
||||
@_updateButtonState()
|
||||
|
||||
onNameFieldFocused: ->
|
||||
@$nameField.val ''
|
||||
@$nameField.autocomplete('search')
|
||||
|
||||
onNameFieldKeyUp: (event)->
|
||||
if event.which is Key.Return
|
||||
@onAddButtonClicked()
|
||||
|
||||
onQuantityFieldBlur: ->
|
||||
value = @$quantityField.val().replace /[^0-9]/g, ''
|
||||
if value.length is 0 then value = '1'
|
||||
value = Math.min value, 64
|
||||
@$quantityField.val value
|
||||
@onQuantityFieldChanged()
|
||||
|
||||
onQuantityFieldChanged: ->
|
||||
if not @$quantityField.val().match /^[0-9]*$/
|
||||
@$quantityField.addClass 'error', 0
|
||||
@$quantityField.addClass 'error-new', 0
|
||||
@$quantityField.removeClass 'error-new', Duration.slow
|
||||
@$quantityField.focus()
|
||||
return
|
||||
|
||||
@$quantityField.removeClass 'error', Duration.normal
|
||||
@$quantityField.removeClass 'error-new', Duration.normal
|
||||
@_updateButtonState()
|
||||
|
||||
onQuantityFieldFocused: ->
|
||||
@$quantityField.val ''
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$addButton = @$('button[name="add"]')
|
||||
@$clearButton = @$('button[name="clear"]')
|
||||
@$editPanel = @$('.edit')
|
||||
@$nameField = @$('input[name="name"]')
|
||||
@$quantityField = @$('input[name="quantity"]')
|
||||
@$scrollbox = @$('.scrollbox')
|
||||
@$table = @$('table')
|
||||
@$toolbar = @$('.toolbar')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@$editPanel.css display:(if @editable then 'table-row' else 'none')
|
||||
@$toolbar.css display:(if @editable then 'block' else 'none')
|
||||
@$scrollbox.css bottom:(if @editable then @$toolbar.height() else '0')
|
||||
|
||||
if _.isEmpty(@$quantityField.val()) then @$quantityField.val '1'
|
||||
|
||||
@$table.find('tr:not(:last-child)').remove()
|
||||
$lastRow = @$table.find 'tr:last-child'
|
||||
@_stackControllers = []
|
||||
@model.each (stack)=>
|
||||
controller = new StackController
|
||||
editable: @editable
|
||||
imageLoader: @imageLoader
|
||||
model: stack
|
||||
modPack: @modPack
|
||||
onRemove: if not @editable then null else (stack)=> @_removeStack(stack)
|
||||
|
||||
controller.render()
|
||||
controller.$el.insertBefore $lastRow
|
||||
@_stackControllers.push controller
|
||||
|
||||
@_updateNameAutocomplete()
|
||||
@_updateButtonState()
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'blur input[name="name"]': 'onNameFieldBlur'
|
||||
'blur input[name="quantity"]': 'onQuantityFieldBlur'
|
||||
'click button[name="add"]': 'onAddButtonClicked'
|
||||
'click button[name="clear"]': 'onClearButtonClicked'
|
||||
'focus input[name="name"]': 'onNameFieldFocused'
|
||||
'focus input[name="quantity"]': 'onQuantityFieldFocused'
|
||||
'input input[name="name"]': 'onNameFieldChanged'
|
||||
'input input[name="quantity"]': 'onQuantityFieldChanged'
|
||||
'keyup input[name="name"]': 'onNameFieldKeyUp'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_removeStack: (stack)->
|
||||
@model.remove stack.itemSlug, stack.quantity
|
||||
|
||||
_updateNameAutocomplete: ->
|
||||
onChanged = => @onNameFieldChanged()
|
||||
onSelected = => @onItemSelected()
|
||||
|
||||
@$nameField.autocomplete
|
||||
source: (request, callback)=> callback @nameFinder.search request.term
|
||||
delay: 0
|
||||
minLength: 0
|
||||
change: onChanged
|
||||
close: onChanged
|
||||
select: onSelected
|
||||
|
||||
_updateButtonState: ->
|
||||
if @model.isEmpty then @$clearButton.attr('disabled', 'disabled') else @$clearButton.removeAttr('disabled')
|
||||
|
||||
itemValid = @modPack.findItemByName(@$nameField.val())?
|
||||
quantityValid = @$quantityField.val().match(InventoryTableController.ONLY_DIGITS)
|
||||
disable = not (itemValid and quantityValid)
|
||||
if disable then @$addButton.attr('disabled', 'disabled') else @$addButton.removeAttr('disabled')
|
||||
@@ -0,0 +1,43 @@
|
||||
###
|
||||
Crafting Guide - item_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.templateName = 'item'
|
||||
super options
|
||||
|
||||
@_imageLoader = options.imageLoader
|
||||
@_modPack = options.modPack
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$icon = @$('img')
|
||||
@$name = @$('.itemName')
|
||||
@$nameLink = @$('a')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
display = @_modPack.findItemDisplay @model.slug
|
||||
|
||||
@_imageLoader.load display.iconUrl, @$icon
|
||||
@$name.html display.itemName
|
||||
@$nameLink.attr 'href', display.itemUrl
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click a': 'routeLinkClick'
|
||||
@@ -0,0 +1,84 @@
|
||||
###
|
||||
Crafting Guide - item_group_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
Item = require '../models/item'
|
||||
ItemController = require './item_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemGroupController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.title ?= ''
|
||||
options.templateName = 'item_group'
|
||||
super options
|
||||
|
||||
@_delayStep = 20
|
||||
@_imageLoader = options.imageLoader
|
||||
@_itemControllers = []
|
||||
@_modPack = options.modPack
|
||||
@_title = options.title
|
||||
|
||||
Object.defineProperties this,
|
||||
title: {get:@getTitle, set:@setTitle}
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getTitle: ->
|
||||
return @_title
|
||||
|
||||
setTitle: (title)->
|
||||
@_title = title
|
||||
@tryRefresh()
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$title = @$('h2')
|
||||
@$items = @$('.panel')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@$title.html @_title
|
||||
@_refreshItems()
|
||||
super
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_createItemController: (item, delay)->
|
||||
controller = new ItemController imageLoader:@_imageLoader, model:item, modPack:@_modPack
|
||||
@_itemControllers.push controller
|
||||
|
||||
attachController = =>
|
||||
controller.render()
|
||||
@$items.append controller.$el
|
||||
|
||||
_.delay attachController, delay
|
||||
|
||||
_refreshItems: ->
|
||||
controllerIndex = 0
|
||||
delay = 0
|
||||
|
||||
if @model?
|
||||
for item in @model
|
||||
controller = @_itemControllers[controllerIndex]
|
||||
if not controller?
|
||||
@_createItemController item, delay
|
||||
delay += @_delayStep
|
||||
else
|
||||
controller.model = item
|
||||
controller.refresh()
|
||||
controllerIndex += 1
|
||||
|
||||
while @_itemControllers.length > controllerIndex
|
||||
controller = @_itemControllers.pop()
|
||||
controller.$el.slideUp duration:Duration.normal, complete:-> @remove()
|
||||
@@ -0,0 +1,174 @@
|
||||
###
|
||||
Crafting Guide - item_page_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
FullRecipeController = require './full_recipe_controller'
|
||||
ImageLoader = require './image_loader'
|
||||
Item = require '../models/item'
|
||||
ItemGroupController = require './item_group_controller'
|
||||
ItemPage = require '../models/item_page'
|
||||
ItemSlug = require '../models/item_slug'
|
||||
PageController = require './page_controller'
|
||||
{Text} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemPageController extends PageController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.itemSlug? then throw new Error 'options.itemSlug is required'
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
|
||||
options.model ?= new ItemPage modPack:options.modPack
|
||||
options.templateName ?= 'item_page'
|
||||
|
||||
super options
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
@_itemSlug = options.itemSlug
|
||||
|
||||
@modPack.on Event.change, => @tryRefresh()
|
||||
|
||||
# PageController Overrides #####################################################################
|
||||
|
||||
getTitle: ->
|
||||
return @model.item?.name
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@_usedAsToolToMakeController = @addChild ItemGroupController, '.usedAsToolToMake .view__item_group',
|
||||
imageLoader: @imageLoader
|
||||
modPack: @modPack
|
||||
|
||||
@_similarItemsController = @addChild ItemGroupController, '.similar .view__item_group',
|
||||
imageLoader: @imageLoader
|
||||
modPack: @modPack
|
||||
|
||||
@_usedToMakeController = @addChild ItemGroupController, '.usedToMake .view__item_group',
|
||||
imageLoader: @imageLoader
|
||||
modPack: @modPack
|
||||
|
||||
@$usedAsToolToMakeSection = @$('.usedAsToolToMake')
|
||||
@$byline = @$('.byline')
|
||||
@$bylineLink = @$('.byline a')
|
||||
@$craftingPlanLink = @$('a.craftingPlan')
|
||||
@$name = @$('h1.name')
|
||||
@$recipeContainer = @$('.recipes .panel')
|
||||
@$recipesSection = @$('.recipes')
|
||||
@$similarSection = @$('.similar')
|
||||
@$titleImage = @$('.titleImage img')
|
||||
@$usedToMakeSection = @$('.usedToMake')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@_resolveItemSlug()
|
||||
|
||||
if @model.item?
|
||||
display = @modPack.findItemDisplay @model.item.slug
|
||||
@$craftingPlanLink.attr href:display.craftingUrl
|
||||
@$craftingPlanLink.fadeIn duration:Duration.fast
|
||||
@imageLoader.load display.iconUrl, @$titleImage
|
||||
@$name.html display.itemName
|
||||
|
||||
@$el.slideDown duration:Duration.normal
|
||||
else
|
||||
@$el.slideUp duration:Duration.normal
|
||||
|
||||
@_refreshByline()
|
||||
@_refreshRecipes()
|
||||
@_refreshSimilarItems()
|
||||
@_refreshUsedAsToolToMake()
|
||||
@_refreshUsedToMake()
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click a.craftingPlan': 'routeLinkClick'
|
||||
'click .byline a': 'routeLinkClick'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshUsedAsToolToMake: ->
|
||||
@_usedAsToolToMakeController.title = 'Used as Tool to Make'
|
||||
|
||||
@_usedAsToolToMakeController.model = @model.findToolForRecipes()
|
||||
if @_usedAsToolToMakeController.model?
|
||||
@$usedAsToolToMakeSection.slideDown duration:Duration.normal
|
||||
else
|
||||
@$usedAsToolToMakeSection.slideUp duration:Duration.normal
|
||||
|
||||
_refreshByline: ->
|
||||
mod = @model.item?.modVersion?.mod
|
||||
if mod?.name?.length > 0
|
||||
@$bylineLink.attr 'href', Url.mod modSlug:mod.slug
|
||||
@$bylineLink.html mod.name
|
||||
@$byline.fadeIn duration:Duration.fast
|
||||
else
|
||||
@$byline.fadeOut duration:Duration.fast
|
||||
|
||||
_refreshUsedToMake: ->
|
||||
@_usedToMakeController.title = 'Used to Make'
|
||||
|
||||
@_usedToMakeController.model = @model.findComponentInItems()
|
||||
if @_usedToMakeController.model?
|
||||
@$usedToMakeSection.slideDown duration:Duration.normal
|
||||
else
|
||||
@$usedToMakeSection.slideUp duration:Duration.normal
|
||||
|
||||
_refreshRecipes: ->
|
||||
@_recipeControllers ?= []
|
||||
index = 0
|
||||
|
||||
recipes = @model.findRecipes()
|
||||
if recipes?
|
||||
@$recipesSection.slideDown duration:Duration.normal
|
||||
|
||||
for recipe in @model.findRecipes()
|
||||
controller = @_recipeControllers[index]
|
||||
if not controller?
|
||||
controller = new FullRecipeController imageLoader:@imageLoader, modPack:@modPack, model:recipe
|
||||
@_recipeControllers.push controller
|
||||
controller.render()
|
||||
@$recipeContainer.append controller.$el
|
||||
else
|
||||
controller.model = recipe
|
||||
index++
|
||||
else
|
||||
@$recipesSection.slideUp duration:Duration.normal
|
||||
|
||||
while @_recipeControllers.length > index
|
||||
controller = @_recipeControllers.pop()
|
||||
controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove()
|
||||
|
||||
_refreshSimilarItems: ->
|
||||
group = @model.item?.group
|
||||
if group? and group isnt Item.Group.Other
|
||||
@_similarItemsController.title = "Other #{group}"
|
||||
@_similarItemsController.model = @model.findSimilarItems()
|
||||
else
|
||||
@_similarItemsController.model = null
|
||||
|
||||
if @_similarItemsController.model?
|
||||
@$similarSection.slideDown duration:Duration.normal
|
||||
else
|
||||
@$similarSection.slideUp duration:Duration.normal
|
||||
|
||||
_resolveItemSlug: ->
|
||||
item = @modPack.findItem @_itemSlug, includeDisabled:false
|
||||
if item? and not ItemSlug.equal item.slug, @_itemSlug
|
||||
router.navigate Url.item(modSlug:item.slug.mod, itemSlug:item.slug.item), trigger:true
|
||||
return
|
||||
|
||||
@model.item = item
|
||||
@@ -0,0 +1,80 @@
|
||||
###
|
||||
Crafting Guide - minimal_recipe_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
CraftingGridController = require './crafting_grid_controller'
|
||||
{Duration} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
StringBuilder = require '../models/string_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class MinimalRecipeController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.templateName = 'minimal_recipe'
|
||||
super options
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@gridController = @addChild CraftingGridController, '.view__crafting_grid',
|
||||
modPack: @modPack
|
||||
imageLoader: @imageLoader
|
||||
|
||||
@$outputImg = @$('.output img')
|
||||
@$outputLink = @$('.output a')
|
||||
@$outputQuantity = @$('.quantity')
|
||||
@$toolContainer = @$('.tool')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@gridController.model = @model
|
||||
|
||||
@$outputImg.attr 'src', '/images/empty.png'
|
||||
@$outputImg.removeAttr 'alt'
|
||||
@$outputLink.removeAttr 'href'
|
||||
@$outputQuantity.html ''
|
||||
|
||||
if @model?
|
||||
outputStack = @model.output[0]
|
||||
if outputStack?
|
||||
display = @modPack.findItemDisplay outputStack.itemSlug
|
||||
@$outputLink.attr 'href', display.itemUrl
|
||||
@$outputLink.attr 'title', display.itemName
|
||||
@$outputImg.attr 'alt', display.itemName
|
||||
@$outputQuantity.html outputStack.quantity if outputStack.quantity > 1
|
||||
|
||||
@imageLoader.load display.iconUrl, @$outputImg
|
||||
|
||||
@$el.tooltip show:{delay:Duration.snap, duration:Duration.fast}
|
||||
|
||||
@_refreshTools()
|
||||
super
|
||||
|
||||
# Backbone.View Methods ########################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click a': 'routeLinkClick'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshTools: ->
|
||||
@$toolContainer.empty()
|
||||
return unless @model?
|
||||
|
||||
builder = new StringBuilder
|
||||
builder.loop @model.tools, delimiter:', ', onEach:(b, stack)=>
|
||||
display = @modPack.findItemDisplay stack.itemSlug
|
||||
b.push "<a href=\"#{display.itemUrl}\">#{display.itemName}</a>"
|
||||
@$toolContainer.html builder.toString()
|
||||
@@ -0,0 +1,43 @@
|
||||
###
|
||||
Crafting Guide - mod_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
options.templateName = 'mod'
|
||||
super options
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$link = @$('a')
|
||||
@$logo = @$('.logo')
|
||||
@$name = @$('.name p')
|
||||
@$description = @$('.description p')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
if @model?
|
||||
@$el.removeClass 'empty'
|
||||
|
||||
@$link.attr 'href', Url.mod modSlug:@model.slug
|
||||
@$logo.attr 'src', Url.modIcon modSlug:@model.slug
|
||||
@$name.html @model.name
|
||||
@$description.html @model.description
|
||||
else
|
||||
@$el.addClass 'empty'
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click a': 'routeLinkClick'
|
||||
@@ -0,0 +1,77 @@
|
||||
###
|
||||
Crafting Guide - mod_pack_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{DefaultMods} = require '../constants'
|
||||
{Duration} = require '../constants'
|
||||
Mod = require '../models/mod'
|
||||
ModSelectorController = require './mod_selector_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPackController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
options.templateName = 'mod_pack'
|
||||
super options
|
||||
|
||||
@_controllers = []
|
||||
@storage = options.storage
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onSuggestModClicked: ->
|
||||
return unless global.feedbackController?
|
||||
global.feedbackController.enterFeedback 'Please add mod:\n\n'
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$mods = @$('.mods')
|
||||
@$toolbar = @$('.toolbar')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
if not @model?
|
||||
@_controllers = []
|
||||
@$('table tr').remove()
|
||||
return
|
||||
|
||||
index = 0
|
||||
mods = @model.getMods()
|
||||
while index < Math.min @_controllers.length, mods.length
|
||||
controller = @_controllers[index]
|
||||
controller.model = mods[index]
|
||||
index++
|
||||
|
||||
while @_controllers.length < mods.length
|
||||
controller = new ModSelectorController model:mods[index], storage:@storage
|
||||
controller.render()
|
||||
@_controllers.push controller
|
||||
controller.$el.hide duration:0
|
||||
|
||||
@$mods.append controller.$el
|
||||
controller.$el.slideDown duration:Duration.normal
|
||||
index++
|
||||
|
||||
while @_controllers.length > mods.length
|
||||
controller = @_controllers.pop()
|
||||
controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove()
|
||||
|
||||
if global.feedbackController?
|
||||
@$toolbar.show duration:0
|
||||
else
|
||||
@$toolbar.hide duration:0
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click button[name="suggestMod"]': 'onSuggestModClicked'
|
||||
@@ -0,0 +1,153 @@
|
||||
###
|
||||
Crafting Guide - mod_page_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
{Duration} = require '../constants'
|
||||
Item = require '../models/item'
|
||||
ItemGroupController = require './item_group_controller'
|
||||
Mod = require '../models/mod'
|
||||
ModPack = require '../models/mod_pack'
|
||||
PageController = require './page_controller'
|
||||
{Text} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPageController extends PageController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.delayStep ?= 10
|
||||
options.templateName = 'mod_page'
|
||||
super options
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
|
||||
@_delayStep = options.delayStep
|
||||
@_effectiveModVersion = null
|
||||
@_groupControllers = []
|
||||
|
||||
Object.defineProperties this,
|
||||
effectiveModVersion: {get:@_getEffectiveModVersion}
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onVersionChanged: ->
|
||||
version = @$versionSelector.val()
|
||||
modVersion = @model.getModVersion version
|
||||
@_effectiveModVersion = modVersion
|
||||
if modVersion? then modVersion.fetch()
|
||||
@refresh()
|
||||
|
||||
# PageController Overrides #####################################################################
|
||||
|
||||
getTitle: ->
|
||||
return @model?.name
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$name = @$('.name')
|
||||
@$byline = @$('.byline p')
|
||||
@$description = @$('.description p')
|
||||
@$documentationLink = @$('.documentation')
|
||||
@$downloadLink = @$('.download')
|
||||
@$homePageLink = @$('.homePage')
|
||||
@$groupContainer = @$('.itemGroups')
|
||||
@$titleImage = @$('.titleImage img')
|
||||
@$versionSelector = @$('select.version')
|
||||
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
if @model?
|
||||
@$name.html @model.name
|
||||
@$byline.html "by #{@model.author}"
|
||||
@$titleImage.attr 'src', Url.modIcon modSlug:@model.slug
|
||||
@$description.html @model.description
|
||||
|
||||
@$el.slideDown duration:Duration.normal
|
||||
else
|
||||
@$el.slideUp duration:Duration.normal
|
||||
|
||||
@_refreshLink @$homePageLink, @model.homePageUrl
|
||||
@_refreshLink @$documentationLink, @model.documentationUrl
|
||||
@_refreshLink @$downloadLink, @model.downloadUrl
|
||||
|
||||
@_refreshItemGroups()
|
||||
@_refreshVersions()
|
||||
super
|
||||
|
||||
# Backbone.View Methods ########################################################################
|
||||
|
||||
events:
|
||||
'change select.version': 'onVersionChanged'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_getEffectiveModVersion: ->
|
||||
return @_effectiveModVersion if @_effectiveModVersion?
|
||||
return null unless @model?
|
||||
|
||||
modVersion = @model.activeModVersion
|
||||
modVersion ?= @model.getModVersion Mod.Version.Latest
|
||||
modVersion.fetch() if modVersion?
|
||||
|
||||
return modVersion
|
||||
|
||||
_refreshItemGroups: ->
|
||||
groupIndex = 0
|
||||
modVersion = @effectiveModVersion
|
||||
if modVersion?
|
||||
modVersion.eachGroup (group)=>
|
||||
controller = @_groupControllers[groupIndex]
|
||||
items = modVersion.allItemsInGroup group
|
||||
if not controller?
|
||||
title = if group is Item.Group.Other then 'Items' else group
|
||||
controller = new ItemGroupController
|
||||
imageLoader: @imageLoader
|
||||
model: items
|
||||
modPack: @modPack
|
||||
title: title
|
||||
controller.render()
|
||||
@$groupContainer.append controller.$el
|
||||
@_groupControllers[groupIndex] = controller
|
||||
else
|
||||
controller.modVersion = modVersion
|
||||
controller.model = items
|
||||
controller.refresh()
|
||||
groupIndex++
|
||||
|
||||
while @_groupControllers.length > groupIndex + 1
|
||||
controller = @_groupControllers.pop()
|
||||
controller.$el.slideUp duration:Duration.normal, -> @remove()
|
||||
|
||||
_refreshLink: ($link, url)->
|
||||
if url?
|
||||
$link.slideDown duration:Duration.normal
|
||||
$link.attr 'href', url
|
||||
else
|
||||
$link.slideUp duration:Duration.normal
|
||||
|
||||
_refreshVersions: ->
|
||||
@$versionSelector.empty()
|
||||
return unless @model?
|
||||
|
||||
effectiveModVersion = @effectiveModVersion
|
||||
versionCount = 0
|
||||
@model.eachModVersion (modVersion)=>
|
||||
option = $("<option value=\"#{modVersion.version}\">#{modVersion.version}</option>")
|
||||
if modVersion is effectiveModVersion
|
||||
option.attr 'selected', 'selected'
|
||||
@$versionSelector.append option
|
||||
versionCount++
|
||||
|
||||
if versionCount <= 1
|
||||
@$versionSelector.attr 'disabled', 'disabled'
|
||||
else
|
||||
@$versionSelector.removeAttr 'disabled'
|
||||
@@ -0,0 +1,70 @@
|
||||
###
|
||||
Crafting Guide - mod_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Event} = require '../constants'
|
||||
Mod = require '../models/mod'
|
||||
{RequiredMods} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModSelectorController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
options.templateName = 'mod_selector'
|
||||
super options
|
||||
|
||||
@_storage = options.storage
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onVersionChanged: ->
|
||||
@model.activeVersion = @$version.val()
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$version = @$('select')
|
||||
@$nameLink = @$('.name a')
|
||||
@$nameText = @$('.name p')
|
||||
@$description = @$('.description p')
|
||||
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
if @model.activeVersion is Mod.Version.None
|
||||
@$el.addClass 'disabled'
|
||||
else
|
||||
@$el.removeClass 'disabled'
|
||||
|
||||
@$version.empty()
|
||||
|
||||
if not (@model.slug in RequiredMods)
|
||||
option = $("<option value=\"none\">Disabled</option>")
|
||||
if @model.activeVersion is Mod.Version.None
|
||||
option.attr 'selected', 'selected'
|
||||
@$version.append option
|
||||
|
||||
@model.eachModVersion (modVersion)=>
|
||||
option = $("<option value=\"#{modVersion.version}\">#{modVersion.version}</option>")
|
||||
if modVersion is @model.activeModVersion
|
||||
option.attr 'selected', 'selected'
|
||||
@$version.append option
|
||||
|
||||
@$nameLink.attr 'href', Url.mod(modSlug:@model.slug)
|
||||
@$nameText.html @model.name
|
||||
|
||||
@$description.html @model.description
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'change select': 'onVersionChanged'
|
||||
'click a': 'routeLinkClick'
|
||||
@@ -0,0 +1,37 @@
|
||||
###
|
||||
Crafting Guide - page_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Text} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class PageController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
super options
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getTitle: ->
|
||||
# subclasses should override this to return the page-specific portion of the title
|
||||
return null
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
refresh: ->
|
||||
title = @getTitle()
|
||||
title = if title? then title.trim() else ''
|
||||
|
||||
if title.length > 0
|
||||
title += " | #{Text.title}"
|
||||
else
|
||||
title = Text.title
|
||||
|
||||
$('title').html title
|
||||
|
||||
super
|
||||
@@ -0,0 +1,25 @@
|
||||
###
|
||||
Crafting Guide - recipe_step_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class RecipeStepController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.templateName = 'recipe_step'
|
||||
super options
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$slotImages = (@$slots.push $(el) for el in @$('.table-slot img'))
|
||||
@$output = @$('.output')
|
||||
super
|
||||
@@ -0,0 +1,125 @@
|
||||
###
|
||||
Crafting Guide - stack_controller.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
{Key} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class StackController extends BaseController
|
||||
|
||||
@MAX_QUANTITY = 9999
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
|
||||
options.editable ?= false
|
||||
options.onChange ?= -> # do nothing
|
||||
options.onRemove ?= (stack)-> # do nothing
|
||||
options.templateName = 'stack'
|
||||
super options
|
||||
|
||||
@editable = options.editable
|
||||
@modPack = options.modPack
|
||||
@onChange = options.onChange
|
||||
@onRemove = options.onRemove
|
||||
@_imageLoader = options.imageLoader
|
||||
|
||||
@modPack.on Event.change, => @tryRefresh()
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onQuantityFieldBlur: ->
|
||||
quantityText = @$quantityField.val().trim()
|
||||
if quantityText.length is 0
|
||||
quantity = @_priorValue
|
||||
else if not quantityText.match /^[0-9]*$/
|
||||
quantity = 1
|
||||
else
|
||||
quantity = parseInt quantityText, 10
|
||||
if _.isNaN quantity then quantity = 1
|
||||
|
||||
quantity = Math.min quantity, StackController.MAX_QUANTITY
|
||||
quantity = Math.max 1, quantity
|
||||
|
||||
if quantity?
|
||||
@$quantityField.val "#{quantity}"
|
||||
@$quantityField.removeClass 'error', Duration.snap
|
||||
@$quantityField.removeClass 'error-new', Duration.snap
|
||||
|
||||
@model.quantity = quantity
|
||||
@onChange()
|
||||
|
||||
onQuantityFieldChanged: ->
|
||||
quantityText = @$quantityField.val().trim()
|
||||
if not quantityText.match /^[0-9]*$/
|
||||
@$quantityField.addClass 'error', 0
|
||||
@$quantityField.addClass 'error-new', 0
|
||||
@$quantityField.removeClass 'error-new', Duration.fast
|
||||
else
|
||||
@$quantityField.removeClass 'error', Duration.snap
|
||||
@$quantityField.removeClass 'error-new', Duration.snap
|
||||
|
||||
onQuantityFieldFocused: ->
|
||||
if @editable
|
||||
@_priorValue = @model.quantity
|
||||
@$quantityField.val ''
|
||||
else
|
||||
@$quantityField.blur()
|
||||
|
||||
onQuantityKeyUp: (event)->
|
||||
if event.which is Key.Return
|
||||
@$quantityField.blur()
|
||||
|
||||
onRemoveClicked: ->
|
||||
@onRemove @model
|
||||
@onChange()
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$action = @$('.action')
|
||||
@$image = @$('.icon img')
|
||||
@$nameLink = @$('.name a')
|
||||
@$quantityField = @$('.quantity input')
|
||||
@$removeButton = @$('button.remove')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
display = @modPack.findItemDisplay @model.itemSlug
|
||||
|
||||
@_imageLoader.load display.iconUrl, @$image
|
||||
@$nameLink.html display.itemName
|
||||
@$nameLink.attr 'href', display.itemUrl
|
||||
@$quantityField.val @model.quantity
|
||||
|
||||
if @editable
|
||||
@$quantityField.removeAttr 'readonly'
|
||||
@$quantityField.addClass 'editable'
|
||||
else
|
||||
@$quantityField.attr 'readonly', 'readonly'
|
||||
@$quantityField.removeClass 'editable'
|
||||
|
||||
@$action.css display:(if @editable then 'table-cell' else 'none')
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'blur .quantity input': 'onQuantityFieldBlur'
|
||||
'click button.remove': 'onRemoveClicked'
|
||||
'click .name a': 'routeLinkClick'
|
||||
'focus .quantity input': 'onQuantityFieldFocused'
|
||||
'input .quantity input': 'onQuantityFieldChanged'
|
||||
'keyup .quantity input': 'onQuantityKeyUp'
|
||||
@@ -0,0 +1,178 @@
|
||||
###
|
||||
Crafting Guide - router.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
|
||||
BrowsePageController = require './controllers/browse_page_controller'
|
||||
CraftPageController = require './controllers/craft_page_controller'
|
||||
ConfigurePageController = require './controllers/configure_page_controller'
|
||||
{DefaultMods} = require './constants'
|
||||
{Duration} = require './constants'
|
||||
{Event} = require './constants'
|
||||
HeaderController = require './controllers/header_controller'
|
||||
ItemPageController = require './controllers/item_page_controller'
|
||||
ItemSlug = require './models/item_slug'
|
||||
ImageLoader = require './controllers/image_loader'
|
||||
Mod = require './models/mod'
|
||||
ModPack = require './models/mod_pack'
|
||||
ModPageController = require './controllers/mod_page_controller'
|
||||
{Opacity} = require './constants'
|
||||
Storage = require './models/storage'
|
||||
{Url} = require './constants'
|
||||
UrlParams = require './url_params'
|
||||
HomePageController = require './controllers/home_page_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingGuideRouter extends Backbone.Router
|
||||
|
||||
constructor: (options={})->
|
||||
@_page = null
|
||||
@_pageControllers = {}
|
||||
@_lastReported = null
|
||||
super options
|
||||
|
||||
@imageLoader = new ImageLoader defaultUrl:'/images/unknown.png'
|
||||
@modPack = new ModPack
|
||||
@storage = new Storage storage:window.localStorage
|
||||
@_defaultOptions = imageLoader:@imageLoader, modPack:@modPack, storage:@storage
|
||||
|
||||
@headerController = new HeaderController el:'.view__header'
|
||||
@headerController.render()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
loadDefaultModPack: ->
|
||||
makeResponder = (m)-> return ->
|
||||
m.activeModVersion.fetch() if m.activeModVersion?
|
||||
|
||||
for modSlug in DefaultMods
|
||||
mod = new Mod slug:modSlug
|
||||
mod.on Event.change + ':activeModVersion', makeResponder mod
|
||||
@storage.register "mod:#{mod.slug}", mod, 'activeVersion'
|
||||
mod.fetch()
|
||||
|
||||
@modPack.addMod mod
|
||||
|
||||
# Backbone.Router Overrides ####################################################################
|
||||
|
||||
navigate: ->
|
||||
super
|
||||
@_recordPageView()
|
||||
|
||||
routes:
|
||||
'(/)': 'route__home'
|
||||
'browse(/)': 'route__browse'
|
||||
'browse/:modSlug(/)': 'route__browseMod'
|
||||
'browse/:modSlug/:itemSlug(/)': 'route__browseModItem'
|
||||
'configure(/)': 'route__configure'
|
||||
'craft(/)': 'route__craft'
|
||||
'craft/:text': 'route__craft'
|
||||
|
||||
'item/:itemSlug': 'deprecated__item'
|
||||
'crafting/(:text)': 'deprecated__crafting'
|
||||
'mod/:modSlug': 'deprecated__mod'
|
||||
'mod/:modSlug/:itemSlug': 'deprecated__modItem'
|
||||
|
||||
# Route Methods ################################################################################
|
||||
|
||||
route__home: ->
|
||||
params = new UrlParams recipeName:{type:'string'}, count:{type:'integer'}
|
||||
if params.recipeName?
|
||||
@deprecated__v1_root params
|
||||
return
|
||||
|
||||
@_setPage 'home', new HomePageController _.extend {}, @_defaultOptions
|
||||
|
||||
route__browse: ->
|
||||
@_setPage 'browse', new BrowsePageController _.extend {}, @_defaultOptions
|
||||
|
||||
route__browseMod: (modSlug)->
|
||||
controller = new ModPageController _.extend {}, @_defaultOptions
|
||||
controller.model = @modPack.getMod modSlug
|
||||
@_setPage 'browseMod', controller
|
||||
|
||||
route__browseModItem: (modSlug, itemSlug)->
|
||||
slug = new ItemSlug modSlug, itemSlug
|
||||
controller = new ItemPageController _.extend {itemSlug:slug}, @_defaultOptions
|
||||
@_setPage 'browseModItem', controller
|
||||
|
||||
route__configure: ->
|
||||
@_setPage 'configure', new ConfigurePageController _.extend {}, @_defaultOptions
|
||||
|
||||
route__craft: (text)->
|
||||
controller = new CraftPageController _.extend {}, @_defaultOptions
|
||||
controller.model.params = inventoryText:text
|
||||
@_setPage 'craft', controller
|
||||
|
||||
# Deprecated Route Methods #####################################################################
|
||||
|
||||
deprecated__crafting: (text)->
|
||||
controller = new CraftPageController _.extend {}, @_defaultOptions
|
||||
controller.model.params = inventoryText:text
|
||||
@_setPage 'crafting', controller
|
||||
|
||||
deprecated__item: (itemSlug)->
|
||||
controller = new ItemPageController _.extend {itemSlug:ItemSlug.slugify(itemSlug)}, @_defaultOptions
|
||||
@_setPage 'item', controller
|
||||
|
||||
deprecated__modItem: (modSlug, itemSlug)->
|
||||
slug = new ItemSlug modSlug, itemSlug
|
||||
controller = new ItemPageController _.extend {itemSlug:slug}, @_defaultOptions
|
||||
@_setPage 'item', controller
|
||||
|
||||
deprecated__mod: (modSlug)->
|
||||
controller = new ModPageController _.extend {}, @_defaultOptions
|
||||
controller.model = @modPack.getMod modSlug
|
||||
@_setPage 'mod', controller
|
||||
|
||||
deprecated__v1_root: (params)->
|
||||
text = ''
|
||||
if params.recipeName?
|
||||
if params.count? and params.count > 1
|
||||
text = "#{params.count}.#{_.slugify(params.recipeName)}"
|
||||
else
|
||||
text = _.slugify params.recipeName
|
||||
|
||||
@navigate Url.crafting(inventoryText:text), trigger:true
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_recordPageView: ->
|
||||
pathname = window.location.pathname
|
||||
|
||||
if global.env is 'production' and ga?
|
||||
logger.info -> "Recording GA page view: #{pathname}"
|
||||
ga 'send', 'pageview', pathname
|
||||
else
|
||||
logger.info -> "Suppressing GA page view: #{pathname}"
|
||||
|
||||
_setPage: (page, controller)->
|
||||
return if @_controller is controller
|
||||
|
||||
logger.info -> "changing to page controller: #{controller.constructor.name}"
|
||||
@headerController.model = page
|
||||
|
||||
showDuration = Duration.normal
|
||||
show = =>
|
||||
@_page = page
|
||||
@_controller = controller
|
||||
|
||||
controller.onWillShow()
|
||||
controller.render()
|
||||
|
||||
$pageContent = $('.page')
|
||||
controller.$el.addClass 'page'
|
||||
$pageContent.replaceWith controller.$el
|
||||
|
||||
controller.$el.slideDown showDuration, ->
|
||||
controller.onDidShow()
|
||||
|
||||
if @_controller?
|
||||
showDuration = showDuration / 2
|
||||
@_controller.$el.slideUp showDuration, show
|
||||
else
|
||||
show()
|
||||
@@ -0,0 +1,98 @@
|
||||
###
|
||||
Crafting Guide - logger.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Logger
|
||||
|
||||
@TRACE = {name:'TRACE ', value:0}
|
||||
@DEBUG = {name:'DEBUG ', value:1}
|
||||
@VERBOSE = {name:'VERBOSE', value:2}
|
||||
@INFO = {name:'INFO ', value:3}
|
||||
@WARNING = {name:'WARNING', value:4}
|
||||
@ERROR = {name:'ERROR ', value:5}
|
||||
@FATAL = {name:'FATAL ', value:6}
|
||||
|
||||
ALL_LEVELS = [@TRACE, @DEBUG, @VERBOSE, @INFO, @WARNING, @ERROR, @FATAL]
|
||||
|
||||
constructor: (options={})->
|
||||
options.level ?= Logger.FATAL
|
||||
@formatText = options.format
|
||||
@formatText ?= "<%= timestamp %> | <%= level %> | <%= indent %><%= message %>"
|
||||
@level = @_parseLevel options
|
||||
|
||||
@_format = _.template @formatText
|
||||
@_indent = ''
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
indent: ->
|
||||
@_indent += ' '
|
||||
|
||||
log: (level, message)->
|
||||
return unless level.value >= @level.value
|
||||
message = message() if _.isFunction message
|
||||
|
||||
entry = {timestamp:new Date(), level:level, message:message, indent:@_indent}
|
||||
entry.level ?= @level
|
||||
|
||||
lines = @_formatEntry entry
|
||||
if entry.level.value < Logger.WARNING.value
|
||||
console.log(line) for line in lines
|
||||
else
|
||||
console.error(line) for line in lines
|
||||
|
||||
outdent: ->
|
||||
@_indent = @_indent[0...@_indent.length - 4]
|
||||
|
||||
# Log Methods ##################################################################################
|
||||
|
||||
trace: (message)-> @log Logger.TRACE, message
|
||||
|
||||
debug: (message)-> @log Logger.DEBUG, message
|
||||
|
||||
verbose: (message)-> @log Logger.VERBOSE, message
|
||||
|
||||
info: (message)-> @log Logger.INFO, message
|
||||
|
||||
warning: (message)-> @log Logger.WARNING, message
|
||||
|
||||
error: (message)->
|
||||
message = "#{message.stack}" if message.stack?
|
||||
@log Logger.ERROR, message
|
||||
|
||||
fatal: (message)-> @log Logger.FATAL, message
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_formatEntry: (entry, lines=[])->
|
||||
message = entry.message.replace /\\n/g, '\n'
|
||||
for line in message.split '\n'
|
||||
result = []
|
||||
result.push @_format
|
||||
timestamp: "#{entry.timestamp}"
|
||||
level: entry.level.name
|
||||
message: line
|
||||
indent: entry.indent
|
||||
lines.push result.join ''
|
||||
return lines
|
||||
|
||||
_parseLevel: (options)->
|
||||
return Logger.FATAL unless _(options).has 'level'
|
||||
level = options.level
|
||||
|
||||
if not level?
|
||||
candidates = []
|
||||
else if _.isString level
|
||||
candidates = (l for l in ALL_LEVELS when l.name.trim().toLowerCase() is level.trim().toLowerCase())
|
||||
else if _.isNumber level
|
||||
candidates = (l for l in ALL_LEVELS when l.value is level)
|
||||
else if level?
|
||||
candidates = (l for l in ALL_LEVELS when l is level)
|
||||
|
||||
throw new Error "invalid level: #{level}" unless candidates.length > 0
|
||||
return candidates[0]
|
||||
@@ -0,0 +1,45 @@
|
||||
###
|
||||
Crafting Guide - main.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
require './underscore_mixins'
|
||||
require './polyfill'
|
||||
|
||||
views = require './views'
|
||||
FeedbackController = require './controllers/feedback_controller'
|
||||
Logger = require './logger'
|
||||
CraftingGuideRouter = require './crafting_guide_router'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
if typeof(global) is 'undefined'
|
||||
window.global = window
|
||||
global = window.global
|
||||
|
||||
global.logger = new Logger
|
||||
|
||||
switch window.location.hostname
|
||||
when 'localhost'
|
||||
global.env = 'development'
|
||||
logger.level = Logger.DEBUG
|
||||
when 'new.crafting-guide.com'
|
||||
global.env = 'staging'
|
||||
logger.level = Logger.VERBOSE
|
||||
when 'crafting-guide.com'
|
||||
global.env = 'production'
|
||||
logger.level = Logger.INFO
|
||||
|
||||
global.router = new CraftingGuideRouter
|
||||
global.util = require 'util'
|
||||
global.views = views
|
||||
|
||||
global.feedbackController = new FeedbackController el:'.view__feedback'
|
||||
feedbackController.render()
|
||||
|
||||
global.router.loadDefaultModPack()
|
||||
|
||||
logger.info -> "CraftingGuide is ready"
|
||||
Backbone.history.start pushState:true
|
||||
@@ -0,0 +1,91 @@
|
||||
###
|
||||
Crafting Guide - base_model.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
{Event} = require '../constants'
|
||||
{ModelState} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BaseModel extends Backbone.Model
|
||||
|
||||
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)
|
||||
|
||||
@logEvents = options.logEvents or false
|
||||
@state = ModelState.unloaded
|
||||
|
||||
@loading = null
|
||||
|
||||
Object.defineProperties this,
|
||||
isUnloaded: { get:-> @state is ModelState.unloaded }
|
||||
isLoading: { get:-> @state is ModelState.loading }
|
||||
isLoaded: { get:-> @state is ModelState.loaded }
|
||||
isError: { get:-> @state is ModelState.error }
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onLoadSucceeded: (text, status, xhr)->
|
||||
try
|
||||
@set @parse text
|
||||
|
||||
@state = ModelState.loaded
|
||||
@trigger Event.change, this
|
||||
@trigger 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 = ModelState.error
|
||||
logger.error => "#{@constructor.name}.#{@cid} failed to load: status:#{status}, message:#{error}"
|
||||
@trigger 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 = ModelState.loading
|
||||
@trigger Event.request, this
|
||||
@loading = 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
|
||||
|
||||
@loading.catch -> # do nothing. prevents unhandled promise warnings
|
||||
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}"
|
||||
@@ -0,0 +1,47 @@
|
||||
###
|
||||
Crafting Guide - craft_page.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
CraftingPlan = require './crafting_plan'
|
||||
CraftingTable = require './crafting_table'
|
||||
{Event} = require '../constants'
|
||||
Inventory = require './inventory'
|
||||
ModPack = require './mod_pack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftPage extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
attributes.modPack ?= new ModPack
|
||||
attributes.params ?= null
|
||||
attributes.plan ?= new CraftingPlan modPack:attributes.modPack
|
||||
attributes.table ?= new CraftingTable plan:attributes.plan
|
||||
super attributes, options
|
||||
|
||||
@modPack.on Event.change, => @_consumeParams()
|
||||
@on Event.change + ':params', => @_consumeParams()
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_consumeParams: ->
|
||||
return unless @params?
|
||||
|
||||
@plan.want.clear()
|
||||
if not @params.inventoryText?
|
||||
@params = null
|
||||
else
|
||||
inventory = new Inventory
|
||||
inventory.parse @params.inventoryText
|
||||
|
||||
inventory.each (stack)=>
|
||||
item = @modPack.findItem stack.itemSlug, enableAsNeeded:true
|
||||
return unless item? and item.isCraftable
|
||||
@plan.want.add stack.itemSlug, stack.quantity
|
||||
inventory.remove stack.itemSlug
|
||||
|
||||
if inventory.isEmpty then @params = null
|
||||
@@ -0,0 +1,204 @@
|
||||
###
|
||||
Crafting Guide - crafting_plan.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
Inventory = require './inventory'
|
||||
ItemSlug = require './item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingPlan extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.modPack then throw new Error 'modPack is required'
|
||||
attributes.includingTools ?= false
|
||||
super attributes, options
|
||||
|
||||
@have = new Inventory modPack:@modPack
|
||||
@want = new Inventory modPack:@modPack
|
||||
@need = new Inventory modPack:@modPack
|
||||
@result = new Inventory modPack:@modPack
|
||||
|
||||
recraft = _.debounce (=> @craft()), 100
|
||||
for inventory in [@have, @want]
|
||||
inventory.on 'change', recraft
|
||||
|
||||
@on Event.change + ':includingTools', recraft
|
||||
|
||||
@clear()
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
clear: (options={})->
|
||||
@steps = []
|
||||
@need.clear()
|
||||
@result.clear()
|
||||
|
||||
@trigger 'change', this
|
||||
return this
|
||||
|
||||
craft: ->
|
||||
toolsMessage = if @includingTools then ' (including tools)' else ''
|
||||
haveMessage = if @have.isEmpty then '' else " starting with #{@have.unparse()}"
|
||||
logger.info => "crafting #{@want.unparse()}#{toolsMessage}#{haveMessage}"
|
||||
|
||||
@clear()
|
||||
@have.localize()
|
||||
@want.localize()
|
||||
|
||||
@result.addInventory @have
|
||||
|
||||
@steps = {}
|
||||
@want.each (stack)=>
|
||||
@_findSteps stack.itemSlug, {}, ignoreGatherable:true
|
||||
item = @modPack.findItem stack.itemSlug
|
||||
@need.add item.slug, stack.quantity
|
||||
|
||||
@steps = (step for recipeSlug, step of @steps)
|
||||
@_resolveNeeds()
|
||||
@_removeExtraSteps()
|
||||
|
||||
@result.addInventory @want
|
||||
|
||||
@need.trigger 'change', @need
|
||||
@result.trigger 'change', @result
|
||||
@trigger 'change', this
|
||||
|
||||
removeUncraftableItems: ->
|
||||
toRemove = []
|
||||
@want.each (stack)=>
|
||||
item = @modPack.findItem stack.itemSlug
|
||||
if not item? then toRemove.push stack.itemSlug
|
||||
|
||||
for itemSlug in toRemove
|
||||
@want.remove itemSlug
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onIncludingToolsChanged: ->
|
||||
@storage.setItem 'includingTools', "#{@includingTools}"
|
||||
@craft()
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name} {
|
||||
have:#{@have},
|
||||
want:#{@want},
|
||||
need:#{@need},
|
||||
result:#{@result},
|
||||
steps:#{@steps}
|
||||
}"
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_chooseRecipe: (item)->
|
||||
recipes = @modPack.findRecipes item.slug
|
||||
return null unless recipes? and recipes.length > 0
|
||||
return recipes[0]
|
||||
|
||||
_findSteps: (itemSlug, parentSteps={})->
|
||||
item = @modPack.findItem itemSlug
|
||||
return unless item?
|
||||
return unless item.isCraftable
|
||||
|
||||
ignoreGatherable = @want.hasAtLeast itemSlug, 1
|
||||
if (not item.isGatherable) or ignoreGatherable
|
||||
recipes = @modPack.findRecipes item.slug
|
||||
recipes ?= []
|
||||
|
||||
if parentSteps[item.slug]?
|
||||
logger.verbose -> "found cycle at #{item.slug}"
|
||||
throw new Error 'invalid recipe path'
|
||||
parentSteps[item.slug] = item
|
||||
|
||||
logger.verbose -> "exploring: #{item.slug}"
|
||||
logger.indent()
|
||||
|
||||
currentSteps = _.clone @steps
|
||||
foundValidRecipe = false
|
||||
for i in [0...recipes.length] by 1
|
||||
recipe = recipes[i]
|
||||
logger.verbose -> "trying recipe #{i+1} of #{recipes.length}: #{recipe.slug}"
|
||||
if @steps[recipe.slug]?
|
||||
logger.verbose -> "already accepted this recipe"
|
||||
foundValidRecipe = true
|
||||
break
|
||||
|
||||
try
|
||||
if @includingTools
|
||||
for toolStack in recipe.tools
|
||||
if not @_hasStep toolStack.itemSlug
|
||||
@_findSteps toolStack.itemSlug, parentSteps
|
||||
|
||||
for inputStack in recipe.input
|
||||
@_findSteps inputStack.itemSlug, parentSteps
|
||||
|
||||
logger.verbose -> "adding step for: #{recipe.slug}"
|
||||
@steps[recipe.slug] = recipe:recipe, itemSlug:item.slug
|
||||
foundValidRecipe = true
|
||||
break
|
||||
catch error
|
||||
logger.verbose -> "recipe didn't work out: #{recipe.slug}"
|
||||
if error.message isnt 'invalid recipe path' then throw error
|
||||
@steps = _.clone currentSteps
|
||||
|
||||
delete parentSteps[item.slug]
|
||||
logger.outdent()
|
||||
|
||||
if not (foundValidRecipe or item.isGatherable)
|
||||
logger.verbose -> "could not find a valid recipe for #{item.slug}"
|
||||
throw new Error 'invalid recipe path'
|
||||
|
||||
_hasStep: (itemSlug)->
|
||||
for recipeSlug, step of @steps
|
||||
return true if step.recipe.produces itemSlug
|
||||
return false
|
||||
|
||||
_qualifyItemSlug: (itemSlug)->
|
||||
item = @modPack.findItem itemSlug
|
||||
return item.slug if item?
|
||||
return itemSlug
|
||||
|
||||
_removeExtraSteps: ->
|
||||
result = (step for step in @steps when step.multiplier > 0)
|
||||
@steps = result
|
||||
|
||||
_resolveNeeds: ->
|
||||
for i in [@steps.length-1..0] by -1
|
||||
step = @steps[i]
|
||||
recipe = step.recipe
|
||||
|
||||
step.multiplier = Math.ceil(@need.quantityOf(step.itemSlug) / recipe.output[0].quantity)
|
||||
|
||||
if @includingTools
|
||||
for stack in recipe.tools
|
||||
itemSlug = @_qualifyItemSlug stack.itemSlug
|
||||
available = @result.quantityOf(itemSlug) + @need.quantityOf(itemSlug)
|
||||
needed = Math.max 0, stack.quantity - available
|
||||
|
||||
@need.add itemSlug, needed
|
||||
@result.add itemSlug, needed
|
||||
|
||||
for stack in recipe.input
|
||||
itemSlug = @_qualifyItemSlug stack.itemSlug
|
||||
needed = step.multiplier * stack.quantity
|
||||
consumed = Math.min needed, @result.quantityOf itemSlug
|
||||
remaining = needed - consumed
|
||||
|
||||
@result.remove itemSlug, consumed
|
||||
@need.add itemSlug, remaining
|
||||
|
||||
for stack in recipe.output
|
||||
itemSlug = @_qualifyItemSlug stack.itemSlug
|
||||
created = stack.quantity * step.multiplier
|
||||
consumed = Math.min created, @need.quantityOf itemSlug
|
||||
remaining = created - consumed
|
||||
|
||||
@result.add itemSlug, remaining
|
||||
@need.remove itemSlug, consumed
|
||||
@@ -0,0 +1,72 @@
|
||||
###
|
||||
Crafting Guide - crafting_table.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class CraftingTable extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.plan? then throw new Error "attributes.plan is required"
|
||||
super attributes, options
|
||||
|
||||
@plan.on 'change', => @reset()
|
||||
@_stepIndex = 0
|
||||
|
||||
Object.defineProperties this, {
|
||||
currentStep: { get:@getCurrentStep }
|
||||
hasNextStep: { get:@hasNextStep }
|
||||
hasPrevStep: { get:@hasPrevStep }
|
||||
hasSteps: { get:@hasSteps }
|
||||
stepCount: { get:@getStepCount }
|
||||
stepIndex: { get:@getStepIndex, set:@setStepIndex }
|
||||
}
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
reset: ->
|
||||
@stepIndex = 0
|
||||
return this
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
hasNextStep: ->
|
||||
return @_stepIndex + 1 < @plan.steps.length
|
||||
|
||||
hasPrevStep: ->
|
||||
return @_stepIndex > 0
|
||||
|
||||
hasSteps: ->
|
||||
return @plan.steps.length > 0
|
||||
|
||||
getCurrentStep: ->
|
||||
return @plan.steps[@_stepIndex]
|
||||
|
||||
getStepIndex: ->
|
||||
return @_stepIndex
|
||||
|
||||
setStepIndex: (newStepIndex)->
|
||||
oldStepIndex = @_stepIndex
|
||||
newStepIndex = Math.max 0, Math.min @plan.steps.length - 1, newStepIndex
|
||||
|
||||
@_stepIndex = newStepIndex
|
||||
|
||||
@trigger Event.change + ':stepIndex', this, oldStepIndex, newStepIndex
|
||||
@trigger Event.change, this
|
||||
|
||||
return this
|
||||
|
||||
getStepCount: ->
|
||||
return 0 unless @plan.steps?
|
||||
return @plan.steps.length
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@constructor.name} (#{@cid}) { plan:#{@plan}, step:#{@_stepIndex} }"
|
||||
@@ -0,0 +1,57 @@
|
||||
###
|
||||
Crafting Guide - email_client.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class EmailClient
|
||||
|
||||
constructor: ->
|
||||
@baseUrl = 'https://mandrillapp.com:443/api/1.0'
|
||||
@key = 'zaERWIuTVJaq0seCjjgVqw'
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
send: (options={})->
|
||||
options.body ?= "(no body)"
|
||||
options.fromAddress ?= "[email protected]"
|
||||
options.fromName ?= "Crafting Guide Website"
|
||||
options.subject ?= "(no subject)"
|
||||
options.toAddress ?= "[email protected]"
|
||||
options.toName ?= "Crafting Guide"
|
||||
|
||||
body =
|
||||
key: @key
|
||||
message:
|
||||
from_email: options.fromAddress
|
||||
from_name: options.fromName
|
||||
subject: options.subject
|
||||
text: options.body
|
||||
to: [ email:options.toAddress, name:options.toName, type:'to' ]
|
||||
|
||||
logger.info -> "sending email: #{util.inspect(body)}"
|
||||
|
||||
w.promise (resolve, reject)=>
|
||||
|
||||
onSuccess = (data, status, request)->
|
||||
logger.info -> "sending email result: #{util.inspect(data)}, status:#{status}"
|
||||
data = if _.isArray data then data[0] else data
|
||||
if data.status isnt "sent"
|
||||
reject status:data.status, message:data.reject_reason
|
||||
else
|
||||
resolve status:data.status
|
||||
|
||||
onError = (request, status, error)->
|
||||
logger.error -> "sending email failed: #{status}, error:#{error}"
|
||||
reject status:status, message:error
|
||||
|
||||
$.ajax "#{@baseUrl}/messages/send.json",
|
||||
cache: false
|
||||
data: body
|
||||
dataType: 'json'
|
||||
error: onError
|
||||
success: onSuccess
|
||||
type: 'POST'
|
||||
@@ -0,0 +1,196 @@
|
||||
###
|
||||
Crafting Guide - inventory.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
ItemSlug = require './item_slug'
|
||||
{RequiredMods} = require '../constants'
|
||||
Stack = require './stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Inventory extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
attributes.modPack ?= null
|
||||
@clear()
|
||||
|
||||
Object.defineProperties this,
|
||||
isEmpty: { get:-> @_itemSlugs.length is 0 }
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@Delimiters =
|
||||
Item: '.'
|
||||
Stack: ':'
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
add: (itemSlug, quantity=1)->
|
||||
@_add itemSlug, quantity
|
||||
@trigger Event.add, this, itemSlug, quantity
|
||||
@trigger Event.change, this
|
||||
return this
|
||||
|
||||
addInventory: (inventory)->
|
||||
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
|
||||
|
||||
@trigger Event.change, this
|
||||
return this
|
||||
|
||||
clear: (options={})->
|
||||
@_stacks = {}
|
||||
@_itemSlugs = []
|
||||
|
||||
@trigger Event.change, this
|
||||
|
||||
clone: ->
|
||||
inventory = new Inventory
|
||||
inventory.addInventory this
|
||||
return inventory
|
||||
|
||||
each: (callback)->
|
||||
for itemSlug in @_itemSlugs
|
||||
callback @_stacks[itemSlug]
|
||||
|
||||
getSlugs: ->
|
||||
return @_itemSlugs[..]
|
||||
|
||||
hasAtLeast: (itemSlug, quantity=1)->
|
||||
if quantity is 0 then return true
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
return false unless stack?
|
||||
return stack.quantity >= quantity
|
||||
|
||||
localize: ->
|
||||
if not @modPack? then throw new Error 'localize requires @modPack'
|
||||
|
||||
newSlugs = []
|
||||
for itemSlug in @_itemSlugs
|
||||
stack = @_stacks[itemSlug]
|
||||
|
||||
qualifiedSlug = @modPack.findItem(itemSlug)?.slug
|
||||
if qualifiedSlug?
|
||||
delete @_stacks[itemSlug]
|
||||
newSlugs.push qualifiedSlug
|
||||
@_stacks[qualifiedSlug] = stack
|
||||
stack.itemSlug = qualifiedSlug
|
||||
else
|
||||
newSlugs.push itemSlug
|
||||
|
||||
@_itemSlugs = newSlugs
|
||||
@_sort()
|
||||
|
||||
pop: ->
|
||||
itemSlug = @_itemSlugs.pop()
|
||||
return null unless itemSlug?
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
delete @_stacks[itemSlug]
|
||||
|
||||
@trigger Event.remove, this, stack.itemSlug, stack.quantity
|
||||
@trigger Event.change, this
|
||||
return stack
|
||||
|
||||
quantityOf: (itemSlug)->
|
||||
stack = @_stacks[itemSlug]
|
||||
return 0 unless stack?
|
||||
return stack.quantity
|
||||
|
||||
remove: (itemSlug, quantity=null)->
|
||||
return if quantity is 0
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
if not stack? then throw new Error "cannot remove #{itemSlug} since it is not in this inventory"
|
||||
|
||||
quantity ?= stack.quantity
|
||||
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 = _(@_itemSlugs).without itemSlug
|
||||
|
||||
@trigger Event.remove, this, itemSlug, quantity
|
||||
@trigger Event.change, this
|
||||
return this
|
||||
|
||||
# Parsing Methods ##############################################################################
|
||||
|
||||
parse: (data)->
|
||||
return this if not data? or data.length is 0
|
||||
|
||||
stacks = data.split Inventory.Delimiters.Stack
|
||||
for stackText in stacks
|
||||
stackParts = stackText.split Inventory.Delimiters.Item
|
||||
if stackParts.length is 2
|
||||
quantity = parseInt stackParts[0], 10
|
||||
itemSlug = ItemSlug.slugify stackParts[1]
|
||||
else if stackParts.length is 1
|
||||
quantity = 1
|
||||
itemSlug = ItemSlug.slugify stackParts[0]
|
||||
else
|
||||
throw new Error "expected #{stackText} to have 0 or 1 parts"
|
||||
|
||||
if itemSlug.qualified.length > 0
|
||||
@add itemSlug, quantity
|
||||
|
||||
return this
|
||||
|
||||
unparse: (options={})->
|
||||
parts = []
|
||||
@each (stack)=>
|
||||
slugText = stack.itemSlug.item
|
||||
if @modPack?
|
||||
item = @modPack.findItem ItemSlug.slugify slugText
|
||||
if item? and item.slug.qualified isnt stack.itemSlug.qualified
|
||||
slugText = item.slug.qualified
|
||||
|
||||
if stack.quantity is 1
|
||||
parts.push slugText
|
||||
else
|
||||
parts.push "#{stack.quantity}#{Inventory.Delimiters.Item}#{slugText}"
|
||||
|
||||
return parts.join Inventory.Delimiters.Stack
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
result = [@constructor.name, " (", @cid, ") { items: ["]
|
||||
|
||||
needsDelimiter = false
|
||||
@each (stack)->
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push stack.toString()
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push '}'
|
||||
return result.join ''
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_add: (itemSlug, quantity=1)->
|
||||
return unless itemSlug?
|
||||
return if quantity is 0
|
||||
|
||||
stack = @_stacks[itemSlug]
|
||||
if not stack?
|
||||
stack = new Stack itemSlug:itemSlug, quantity:quantity
|
||||
@listenTo stack, Event.change, => @trigger Event.change, stack
|
||||
@_stacks[itemSlug] = stack
|
||||
@_itemSlugs.push itemSlug
|
||||
@_sort()
|
||||
else
|
||||
stack.quantity += quantity
|
||||
|
||||
_sort: ->
|
||||
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
|
||||
@@ -0,0 +1,73 @@
|
||||
###
|
||||
Crafting Guide - item.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
ItemSlug = require './item_slug'
|
||||
Recipe = require './recipe'
|
||||
StringBuilder = require './string_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
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.isGatherable ?= false
|
||||
attributes.modVersion ?= null
|
||||
attributes.officialUrl ?= null
|
||||
attributes.slug ?= ItemSlug.slugify attributes.name
|
||||
attributes.videos ?= []
|
||||
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
@on 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
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getIsCraftable: ->
|
||||
if not @_isCraftable?
|
||||
@_isCraftable = false
|
||||
if @modVersion?
|
||||
@_isCraftable = @modVersion.hasRecipes @slug
|
||||
|
||||
return @_isCraftable
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isCraftable: {get:@prototype.getIsCraftable}
|
||||
|
||||
# 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()
|
||||
@@ -0,0 +1,57 @@
|
||||
###
|
||||
Crafting Guide - item_page.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
CraftingPlan = require './crafting_plan'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemPage extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.modPack? then throw new Error 'attributes.modPack is required'
|
||||
attributes.item ?= null
|
||||
super attributes, options
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
findComponentInItems: ->
|
||||
return @_findRecipesMatching (recipe)=> recipe.requires @item.slug
|
||||
|
||||
findSimilarItems: ->
|
||||
return null unless @item?.modVersion?
|
||||
|
||||
result = []
|
||||
@item.modVersion.eachItemInGroup @item.group, (item)=>
|
||||
result.push item
|
||||
|
||||
return null unless result.length > 0
|
||||
return result
|
||||
|
||||
findRecipes: ->
|
||||
return @modPack.findRecipes @item?.slug, [], alwaysFromOwningMod:true
|
||||
|
||||
findToolForRecipes: ->
|
||||
return @_findRecipesMatching (recipe)=> recipe.requiresTool @item.slug
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_findRecipesMatching: (callback)->
|
||||
return null unless @item?
|
||||
|
||||
result = {}
|
||||
@modPack.eachMod (mod)=>
|
||||
mod.eachRecipe (recipe)=>
|
||||
if callback(recipe)
|
||||
outputItem = @modPack.findItem recipe.itemSlug, includeDisabled:true
|
||||
result[outputItem.slug] = outputItem
|
||||
|
||||
result = _.values result
|
||||
return null unless result.length > 0
|
||||
|
||||
return result.sort (a, b)-> a.compareTo b
|
||||
@@ -0,0 +1,109 @@
|
||||
###
|
||||
Crafting Guide - item_slug.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
{RequiredMods} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ItemSlug
|
||||
|
||||
constructor: ->
|
||||
@_item = @_mod = null
|
||||
|
||||
if arguments.length is 1
|
||||
@item = arguments[0]
|
||||
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)->
|
||||
aIsRequired = a.mod in RequiredMods
|
||||
bIsRequired = b.mod in RequiredMods
|
||||
|
||||
if aIsRequired isnt bIsRequired
|
||||
return -1 if aIsRequired
|
||||
return +1 if bIsRequired
|
||||
else if a.isQualified isnt b.isQualified
|
||||
return -1 if a.isQualified
|
||||
return +1 if b.isQualified
|
||||
else if a.mod isnt b.mod
|
||||
return if a.mod < b.mod then -1 else +1
|
||||
else if a.item isnt b.item
|
||||
return if a.item < b.item then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
@equal: (a, 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 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 #############################################################################
|
||||
|
||||
getIsQualified: ->
|
||||
return @_mod?
|
||||
|
||||
getItem: ->
|
||||
return @_item
|
||||
|
||||
setItem: (item)->
|
||||
if not item? then throw new Error 'item is required'
|
||||
@_item = item
|
||||
|
||||
@mod = @mod # reset @_qualified
|
||||
|
||||
getMod: ->
|
||||
return @_mod
|
||||
|
||||
setMod: (mod)->
|
||||
@_mod = mod
|
||||
@_qualified = if @_mod? then _.composeSlugs(@_mod, @_item) else @_item
|
||||
|
||||
getQualified: ->
|
||||
return @_qualified
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isQualified: { get:@prototype.getIsQualified }
|
||||
mod: { get:@prototype.getMod, set:@prototype.setMod }
|
||||
item: { get:@prototype.getItem, set:@prototype.setItem }
|
||||
qualified: { get:@prototype.getQualified }
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return @_qualified
|
||||
|
||||
valueOf: ->
|
||||
return @_qualified.valueOf()
|
||||
@@ -0,0 +1,186 @@
|
||||
###
|
||||
Crafting Guide - mod.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
{RequiredMods} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
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 = []
|
||||
|
||||
Object.defineProperties this,
|
||||
'activeModVersion': { get:-> @_activeModVersion }
|
||||
'activeVersion': { get:@getActiveVersion, set:@setActiveVersion }
|
||||
'enabled': { get:-> @_activeModVersion? }
|
||||
|
||||
# Class Methods ##################################################################################
|
||||
|
||||
@Version =
|
||||
None: 'none'
|
||||
Latest: 'latest'
|
||||
|
||||
# Public Methods #################################################################################
|
||||
|
||||
compareTo: (that)->
|
||||
thisRequired = this.slug in RequiredMods
|
||||
thatRequired = that.slug in RequiredMods
|
||||
|
||||
if thisRequired isnt thatRequired
|
||||
return -1 if thisRequired
|
||||
return +1 if thatRequired
|
||||
else
|
||||
if this.name isnt that.name
|
||||
return if this.name < that.name then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
# ModVersion Proxy Methods #####################################################################
|
||||
|
||||
eachItem: (callback)->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
effectiveModVersion.eachItem callback
|
||||
|
||||
eachName: (callback)->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
effectiveModVersion.eachName callback
|
||||
|
||||
eachRecipe: (callback)->
|
||||
effectiveModVersion = @_activeModVersion or @getModVersion Mod.Version.Latest
|
||||
effectiveModVersion.eachRecipe 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
|
||||
|
||||
findName: (itemSlug)->
|
||||
return unless @_activeModVersion?
|
||||
@_activeModVersion.findName itemSlug
|
||||
|
||||
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
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
addModVersion: (modVersion)->
|
||||
return unless modVersion?
|
||||
return if @_modVersions.indexOf(modVersion) isnt -1
|
||||
|
||||
@_modVersions.push modVersion
|
||||
@listenTo modVersion, Event.change, => @trigger Event.change, this
|
||||
modVersion.mod = this
|
||||
|
||||
@trigger Event.add + ':modVersion', modVersion, this
|
||||
@trigger Event.change + ':version', modVersion, this
|
||||
@trigger 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
|
||||
|
||||
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
|
||||
|
||||
getActiveVersion: ->
|
||||
return @_activeVersion
|
||||
|
||||
setActiveVersion: (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 Event.change + ':activeVersion', this, @_activeVersion
|
||||
@trigger Event.change, this
|
||||
else
|
||||
for modVersion in @_modVersions
|
||||
if version is modVersion.version
|
||||
@_activateModVersion modVersion
|
||||
break
|
||||
|
||||
@_activeVersion = version
|
||||
@trigger Event.change + ':activeVersion', this, @_activeVersion
|
||||
@trigger Event.change, this
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
parse: (text)->
|
||||
ModParser = require './mod_parser' # to avoid require cycles
|
||||
@_parser ?= new ModParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return Url.modData modSlug:@slug
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_activateModVersion: (modVersion)->
|
||||
if @_activeModVersion? then @stopListening @_activeModVersion
|
||||
@_activeModVersion = modVersion
|
||||
@trigger Event.change + ':activeModVersion', this, @_activeModVersion
|
||||
|
||||
logger.verbose => "#{@slug} switched to version #{@_activeVersion}"
|
||||
|
||||
if @_activeModVersion?
|
||||
@listenTo @_activeModVersion, 'all', -> @trigger.apply this, arguments
|
||||
@@ -0,0 +1,131 @@
|
||||
###
|
||||
Crafting Guide - mod_pack.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{DefaultModVersions} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
ModVersionParser = require './mod_version_parser'
|
||||
Recipe = require './recipe'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModPack extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
super attributes, options
|
||||
|
||||
@_mods = []
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
findItem: (itemSlug, options={})->
|
||||
options.includeDisabled ?= false
|
||||
|
||||
if itemSlug.isQualified
|
||||
mod = @getMod itemSlug.mod
|
||||
if mod?
|
||||
item = mod.findItem itemSlug, options
|
||||
return item if item?
|
||||
|
||||
for mod in @_mods
|
||||
continue unless mod.enabled or options.includeDisabled
|
||||
item = mod.findItem itemSlug, options
|
||||
return item if item?
|
||||
|
||||
return null
|
||||
|
||||
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 = {}
|
||||
item = @findItem itemSlug, includeDisabled:true
|
||||
if item?
|
||||
result.modSlug = item.slug.mod
|
||||
result.modVersion = item.modVersion.version
|
||||
result.itemSlug = item.slug.item
|
||||
result.itemName = item.name
|
||||
else
|
||||
result.modSlug = @_mods[0].slug
|
||||
result.modVersion = @_mods[0].activeVersion
|
||||
result.itemSlug = itemSlug.item
|
||||
result.itemName = @findName itemSlug, includeDisabled:true
|
||||
|
||||
result.craftingUrl = Url.crafting inventoryText:itemSlug.item
|
||||
result.iconUrl = Url.itemIcon result
|
||||
result.itemUrl = Url.item result
|
||||
return result
|
||||
|
||||
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
|
||||
|
||||
findRecipes: (itemSlug, result=[], options={})->
|
||||
options.alwaysFromOwningMod ?= false
|
||||
return null unless itemSlug?
|
||||
|
||||
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
|
||||
|
||||
result.sort (a, b)-> Recipe.compareFor a, b, itemSlug
|
||||
|
||||
return if result.length > 0 then result else null
|
||||
|
||||
# Property 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, Event.change, => @trigger Event.change, this
|
||||
@trigger Event.add + ':mod', mod, this
|
||||
|
||||
@_mods.sort (a, b)-> a.compareTo b
|
||||
@trigger Event.sort + ':mod', this
|
||||
@trigger 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
|
||||
|
||||
getMods: ->
|
||||
return @_mods[..]
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "ModPack (#{@cid}) {modVersions:«#{@_mods.length} items»}"
|
||||
@@ -0,0 +1,19 @@
|
||||
###
|
||||
Crafting Guide - mod_parser.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
ModParserV1 = require './parser_versions/mod_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new ModParserV1 options
|
||||
@@ -0,0 +1,181 @@
|
||||
###
|
||||
Crafting Guide - mod_version.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
Item = require './item'
|
||||
ItemSlug = require './item_slug'
|
||||
Recipe = require './recipe'
|
||||
{RequiredMods} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
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
|
||||
|
||||
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 ################################################################################
|
||||
|
||||
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
|
||||
|
||||
for recipe in _.values @_recipes
|
||||
if options.onlyPrimary
|
||||
if recipe.itemSlug.matches itemSlug
|
||||
result.push recipe
|
||||
else
|
||||
if recipe.produces itemSlug
|
||||
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 './mod_version_parser' # to avoid require cycles
|
||||
@_parser ?= new ModVersionParser model:this
|
||||
@_parser.parse text
|
||||
|
||||
return null # prevent calling `set`
|
||||
|
||||
url: ->
|
||||
return Url.modVersion modSlug:@modSlug, modVersion:@version
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "ModVersion (#{@cid}) {
|
||||
modSlug:#{@modSlug}, version:#{@version}, items:«#{@_slugs.length} items»
|
||||
}"
|
||||
@@ -0,0 +1,19 @@
|
||||
###
|
||||
Crafting Guide - mod_version_parser.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
VersionedParserBase = require './versioned_parser_base'
|
||||
ModVersionParserV1 = require './parser_versions/mod_version_parser_v1'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class ModVersionParser extends VersionedParserBase
|
||||
|
||||
# VersionedParserBase Overrides ################################################################
|
||||
|
||||
_createParsers: (options)->
|
||||
return result =
|
||||
'1': new ModVersionParserV1 options
|
||||
@@ -0,0 +1,68 @@
|
||||
###
|
||||
Crafting Guide - name_finder.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class NameFinder
|
||||
|
||||
constructor: (modPack, options={})->
|
||||
if not modPack? then throw new Error 'modPack is required'
|
||||
|
||||
options.includeGatherable ?= false
|
||||
options.includeDisabledMods ?= false
|
||||
options.limit ?= 25
|
||||
|
||||
@includeDisabledMods = options.includeDisabledMods
|
||||
@includeGatherable = options.includeGatherable
|
||||
@limit = options.limit
|
||||
@modPack = modPack
|
||||
@names = []
|
||||
@_nameMap = {}
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
search: (nameHint='')->
|
||||
nameHint = null if nameHint.trim() is ''
|
||||
nameHint = nameHint.toLowerCase() if nameHint?
|
||||
names = @_findNames nameHint
|
||||
|
||||
names.sort (a, b)->
|
||||
c = a.mod.compareTo b.mod
|
||||
if c isnt 0 then return c
|
||||
|
||||
return 0 if a.label is b.label
|
||||
return if a.label < b.label then -1 else +1
|
||||
|
||||
names = names[0...@limit]
|
||||
|
||||
return names
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_findNames: (nameHint=null)->
|
||||
names = []
|
||||
nameMap = {}
|
||||
hintRegex = new RegExp(nameHint, 'i') if nameHint?
|
||||
|
||||
@modPack.eachMod (mod)=>
|
||||
return unless mod.enabled or @includeDisabledMods
|
||||
|
||||
mod.eachName (name, itemSlug)=>
|
||||
return if nameMap[name]?
|
||||
|
||||
if not @includeGatherable
|
||||
item = mod.findItem itemSlug
|
||||
return unless item? and item.isCraftable
|
||||
|
||||
scanName = "#{mod.name} : #{name}"
|
||||
if hintRegex?
|
||||
return unless hintRegex.test scanName
|
||||
|
||||
nameMap[name] = name
|
||||
names.push value:name, label:scanName, mod:mod
|
||||
|
||||
return names
|
||||
@@ -0,0 +1,130 @@
|
||||
###
|
||||
Crafting Guide - command_parser_version_base.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
StringBuilder = require '../../models/string_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
@_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
|
||||
|
||||
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.trim() for s in match[2].split(',')) if match[2]?
|
||||
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
|
||||
logger.error -> e.message
|
||||
|
||||
_parseHereDoc: (line)->
|
||||
hereDocIndex = line.indexOf '<<-'
|
||||
return null unless hereDocIndex isnt -1
|
||||
|
||||
hereDocStopText = line[hereDocIndex+3...line.length]
|
||||
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 line in hereDocLines
|
||||
shortestIndent = Math.min line.match(/( *).*/)[1].length, shortestIndent
|
||||
|
||||
for i in [0...hereDocLines.length]
|
||||
hereDocLines[i] = hereDocLines[i][shortestIndent..]
|
||||
|
||||
return null unless hereDocLines.length > 0
|
||||
return hereDocLines.join '\n'
|
||||
@@ -0,0 +1,45 @@
|
||||
###
|
||||
Crafting Guide - item_parser_v1.coffee
|
||||
|
||||
Copyright (c) 2015 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)->
|
||||
@_unparseItem builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_description: (textParts...)->
|
||||
@_rawData.text ?= ''
|
||||
@_rawData.text += textParts.join ', '
|
||||
|
||||
_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_video: (youTubeId, name)->
|
||||
if not youTubeId?.length then throw new Error 'video declaration requires a YouTubeID'
|
||||
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)->
|
||||
item.description = rawData.description if rawData.description
|
||||
item.officialUrl = rawData.officialUrl if rawData.officialUrl
|
||||
item.videos = rawData.videos if rawData.videos
|
||||
@@ -0,0 +1,82 @@
|
||||
###
|
||||
Crafting Guide - mod_parser_v2.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
Mod = require '../mod'
|
||||
ModVersion = require '../mod_version'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
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)->
|
||||
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_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_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_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
|
||||
|
||||
for version in rawData.versions
|
||||
model.addModVersion new ModVersion modSlug:model.slug, version:version
|
||||
@@ -0,0 +1,299 @@
|
||||
###
|
||||
Crafting Guide - mod_version_parser_v2.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
CommandParserVersionBase = require './command_parser_version_base'
|
||||
Item = require '../item'
|
||||
ItemSlug = require '../item_slug'
|
||||
ModVersion = require '../mod_version'
|
||||
Recipe = require '../recipe'
|
||||
Stack = require '../simple_stack'
|
||||
StringBuilder = require '../string_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
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.extras? then throw new Error 'duplicate declaration of "extras"'
|
||||
|
||||
@_recipeData.extras = []
|
||||
for term in extraTerms
|
||||
match = ModVersionParserV1.STACK.exec term
|
||||
if match?
|
||||
@_recipeData.extras.push quantity:parseInt(match[1]), name:match[2]
|
||||
else
|
||||
@_recipeData.extras.push quantity:1, name: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_input: (inputNames...)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
|
||||
if @_recipeData.input? then throw new Error 'duplicate declaration of "input"'
|
||||
|
||||
@_recipeData.input = []
|
||||
for name in inputNames
|
||||
if name.length is 0 then throw new Error 'input names cannot be empty'
|
||||
@_recipeData.input.push name
|
||||
|
||||
_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 = parseInt quantity
|
||||
|
||||
_command_recipe: ->
|
||||
if not @_itemData? then throw new Error 'cannot delcare "recipe" before "item"'
|
||||
|
||||
@_recipeData = line:@_lineNumber
|
||||
@_itemData.recipes ?= []
|
||||
@_itemData.recipes.push @_recipeData
|
||||
|
||||
_command_tools: (toolNames...)->
|
||||
if not @_recipeData? then throw new Error 'cannot declare "tools" before "recipe"'
|
||||
if @_recipeData.tools? then throw new Error 'duplicate declaration of "tools"'
|
||||
|
||||
@_recipeData.tools = []
|
||||
for name in toolNames
|
||||
if name.length is 0 then throw new Error 'tool names cannot be empty'
|
||||
@_recipeData.tools.push name
|
||||
|
||||
_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
|
||||
|
||||
# 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.recipes ?= []
|
||||
|
||||
if itemData.type is 'new'
|
||||
item = new Item name:itemData.name, 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
|
||||
|
||||
for recipeData in itemData.recipes
|
||||
@_handleErrors @_buildRecipe, modVersion, itemData, recipeData
|
||||
|
||||
return item
|
||||
|
||||
_buildRecipe: (modVersion, itemData, recipeData)->
|
||||
@_lineNumber = recipeData.line
|
||||
if not recipeData.input? then throw new Error 'the "input" declaration is required'
|
||||
if not recipeData.pattern? then throw new Error 'the "pattern" declaration is required'
|
||||
|
||||
createSlug = (name)=>
|
||||
item = @_rawData.items[name]
|
||||
if item?
|
||||
slug = new ItemSlug modVersion.modSlug, _.slugify name
|
||||
else
|
||||
slug = new ItemSlug _.slugify name
|
||||
modVersion.registerName slug, name
|
||||
return slug
|
||||
|
||||
recipeData.quantity ?= 1
|
||||
recipeData.extras ?= []
|
||||
recipeData.tools ?= []
|
||||
|
||||
inputStacks = []
|
||||
for name in recipeData.input
|
||||
inputSlug = createSlug name
|
||||
inputStacks.push new Stack itemSlug:inputSlug, quantity:0
|
||||
|
||||
for c in recipeData.pattern
|
||||
continue if c is '.'
|
||||
continue if c is ' '
|
||||
stack = inputStacks[parseInt(c)]
|
||||
if not stack? then throw new Error "there is no input #{c} in this recipe"
|
||||
stack.quantity += 1
|
||||
|
||||
for i in [0...inputStacks.length]
|
||||
stack = inputStacks[i]
|
||||
if stack.quantity is 0
|
||||
name = modVersion.findName stack.itemSlug
|
||||
throw new Error "#{name} is an input for this recipe, but it is not in the pattern"
|
||||
|
||||
outputStacks = [ new Stack itemSlug:itemData.slug, quantity:recipeData.quantity ]
|
||||
for extraData in recipeData.extras
|
||||
outputSlug = createSlug extraData.name
|
||||
outputStacks.push new Stack itemSlug:outputSlug, quantity:extraData.quantity
|
||||
|
||||
toolStacks = []
|
||||
for name in recipeData.tools
|
||||
toolSlug = createSlug name
|
||||
toolStacks.push new Stack itemSlug:toolSlug, quantity:1
|
||||
|
||||
recipe = new Recipe
|
||||
input: inputStacks
|
||||
output: outputStacks
|
||||
pattern: recipeData.pattern
|
||||
tools: toolStacks
|
||||
|
||||
modVersion.addRecipe recipe
|
||||
return recipe
|
||||
|
||||
# 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 recipes.length > 0, =>
|
||||
builder.loop recipes, delimiter:'', onEach:(b, r)=> @_unparseRecipe(b, r)
|
||||
.outdent()
|
||||
|
||||
_unparseRecipe: (builder, recipe)->
|
||||
inputNames = (builder.context.findName(stack.itemSlug) for stack in recipe.input)
|
||||
inputNames.sort()
|
||||
|
||||
patternMap = {'.', '.'}
|
||||
for i in [0...recipe.input.length]
|
||||
stack = recipe.input[i]
|
||||
patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.itemSlug)}"
|
||||
|
||||
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 extraOutputs.length > 0, =>
|
||||
builder
|
||||
.push 'extras: '
|
||||
.call => @_unparseStackList builder, extraOutputs
|
||||
.line()
|
||||
.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
|
||||
@@ -0,0 +1,191 @@
|
||||
###
|
||||
Crafting Guide - recipe.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
Stack = require './stack'
|
||||
StringBuilder = require './string_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
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.modVersion ?= null
|
||||
attributes.tools ?= []
|
||||
options.logEvents ?= false
|
||||
super attributes, options
|
||||
|
||||
@on Event.change + ':modVersion', => @_slug = 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.getQuantityProducedOf itemSlug
|
||||
bValue = b.getQuantityProducedOf itemSlug
|
||||
if aValue isnt bValue
|
||||
return if aValue > bValue then -1 else +1
|
||||
|
||||
aValue = a.getOutputCount()
|
||||
bValue = b.getOutputCount()
|
||||
if aValue isnt bValue
|
||||
return if aValue > bValue then -1 else +1
|
||||
|
||||
aValue = a.tools.length
|
||||
bValue = b.tools.length
|
||||
if aValue isnt bValue
|
||||
return if aValue < bValue then -1 else +1
|
||||
|
||||
aValue = a.getInputCount()
|
||||
bValue = b.getInputCount()
|
||||
if aValue isnt bValue
|
||||
return if aValue < bValue then -1 else +1
|
||||
|
||||
return 0
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getInputCount: ->
|
||||
result = 0
|
||||
for stack in @input
|
||||
result += stack.quantity
|
||||
return result
|
||||
|
||||
getItemSlugAt: (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.itemSlug
|
||||
|
||||
getOutputCount: ->
|
||||
result = 0
|
||||
for stack in @output
|
||||
result += stack.quantity
|
||||
return result
|
||||
|
||||
getQuantityProducedOf: (itemSlug)->
|
||||
for stack in @output
|
||||
if stack.itemSlug.matches itemSlug
|
||||
return stack.quantity
|
||||
|
||||
return 0
|
||||
|
||||
produces: (itemSlug)->
|
||||
if not @_produces?
|
||||
@_produces = {}
|
||||
|
||||
for stack in @output
|
||||
@_produces[stack.itemSlug.qualified] = true
|
||||
@_produces[stack.itemSlug.item] = true
|
||||
|
||||
return @_produces[itemSlug.qualified]
|
||||
|
||||
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 #############################################################################
|
||||
|
||||
getSlug: ->
|
||||
if not @_slug?
|
||||
builder = new StringBuilder
|
||||
builder
|
||||
.loop(@input, delimiter:',', onEach:(b, stack)-> b.push stack.itemSlug.qualified)
|
||||
.onlyIf @tools.length > 0, (b)=>
|
||||
b.push(' + ').loop(@tools, delimiter:',', onEach:(b, stack)-> b.push stack.itemSlug.qualified)
|
||||
.push(' => ')
|
||||
.loop(@output, delimiter:',', onEach:(b, stack)-> b.push stack.itemSlug.qualified)
|
||||
@_slug = builder.toString()
|
||||
|
||||
return @_slug
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
slug: {get:@prototype.getSlug}
|
||||
|
||||
# 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 stack.toString()
|
||||
needsDelimiter = true
|
||||
result.push ']'
|
||||
|
||||
result.push ", output:["
|
||||
needsDelimiter = false
|
||||
for stack in @output
|
||||
if needsDelimiter then result.push ', '
|
||||
result.push stack.toString()
|
||||
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 ##############################################################################
|
||||
|
||||
_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
|
||||
@@ -0,0 +1,26 @@
|
||||
###
|
||||
Crafting Guide - recipe_step.coffee
|
||||
|
||||
Copyright (c) 2014 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class RecipeStep extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.recipe? then throw new Error 'attributes.recipe is required'
|
||||
attributes.multiple ?= 1
|
||||
super attributes, options
|
||||
|
||||
@recipe.on 'change', =>
|
||||
@trigger 'change:recipe'
|
||||
@trigger 'change'
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
getItemAt: (index)->
|
||||
return @recipe.getItemAt index
|
||||
@@ -0,0 +1,22 @@
|
||||
###
|
||||
Crafting Guide - simple_stack.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
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
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return "#{@quantity} #{@itemSlug}"
|
||||
@@ -0,0 +1,23 @@
|
||||
###
|
||||
Crafting Guide - stack.coffee
|
||||
|
||||
Copyright (c) 2014-2015 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}"
|
||||
@@ -0,0 +1,57 @@
|
||||
###
|
||||
Crafting Guide - storage.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Storage
|
||||
|
||||
constructor: (options={})->
|
||||
options.storage ?= window.sessionStorage
|
||||
|
||||
@storage = options.storage
|
||||
@_models = {}
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
load: (key)->
|
||||
value = @storage.getItem key
|
||||
logger.verbose -> "loaded #{value} from #{key}"
|
||||
return value
|
||||
|
||||
store: (key, value)->
|
||||
@storage.setItem key, value
|
||||
logger.verbose -> "stored #{value} into #{key}"
|
||||
|
||||
register: (key, model, properties...)->
|
||||
modelData = @_models[key]
|
||||
if not modelData? then modelData = @_models[key] = model:model, properties:{}
|
||||
makeStoreMethod = (k, m, p)=> return => @_storeProperty(k, m, p)
|
||||
|
||||
for property in properties
|
||||
continue if modelData.properties[property]?
|
||||
modelData.properties[property] = property
|
||||
|
||||
@_loadProperty key, model, property
|
||||
model.on "change:#{property}", makeStoreMethod(key, model, property)
|
||||
|
||||
unregister: (key, property)->
|
||||
modelData = @_models[key]
|
||||
return unless modelData?
|
||||
delete modelData.properties[key]
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_loadProperty: (key, model, property)->
|
||||
value = @load "#{key}:#{property}"
|
||||
value = JSON.parse(value) if value?
|
||||
return unless value?
|
||||
|
||||
model[property] = value
|
||||
|
||||
_storeProperty: (key, model, property)->
|
||||
value = JSON.stringify model[property]
|
||||
@store "#{key}:#{property}", value
|
||||
@@ -0,0 +1,122 @@
|
||||
###
|
||||
Crafting Guide - string_builder.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class StringBuilder
|
||||
|
||||
constructor: (options={})->
|
||||
options.indent ?= 0
|
||||
options.indentString ?= ' '
|
||||
|
||||
@context = options.context
|
||||
|
||||
@_initialIndent = options.indent
|
||||
@_indent = options.indent
|
||||
@_indentString = options.indentString
|
||||
@_pieces = []
|
||||
|
||||
# Public Methods ###############################################################################
|
||||
|
||||
call: ->
|
||||
args = (arg for arg in arguments)
|
||||
callback = args.pop()
|
||||
args.unshift this
|
||||
return unless _.isFunction callback
|
||||
|
||||
callback.apply null, args
|
||||
|
||||
clear: ->
|
||||
@_indent = @_initialIndent
|
||||
@_pieces = []
|
||||
return this
|
||||
|
||||
indent: ->
|
||||
@_indent += 1
|
||||
return this
|
||||
|
||||
line: (args...)->
|
||||
@push.apply(this, args) if args.length > 0
|
||||
@push '\n'
|
||||
|
||||
loop: (list, options={})->
|
||||
options.start ?= ''
|
||||
options.end ?= ''
|
||||
options.indent ?= false
|
||||
options.delimiter ?= ', '
|
||||
options.onEach ?= (builder, item)-> builder.push item
|
||||
|
||||
@_pushText options.start
|
||||
if options.indent then @indent()
|
||||
|
||||
isFirst = true
|
||||
for item in list
|
||||
if not isFirst then @push options.delimiter
|
||||
isFirst = false
|
||||
options.onEach this, item
|
||||
|
||||
if options.indent then @outdent()
|
||||
@_pushText options.end
|
||||
|
||||
return this
|
||||
|
||||
onlyIf: (condition, callback=null)->
|
||||
return this unless condition
|
||||
|
||||
callback ?= (builder)-> # do nothing
|
||||
callback this
|
||||
return this
|
||||
|
||||
outdent: ->
|
||||
@_indent -= 1
|
||||
return this
|
||||
|
||||
push: ->
|
||||
for i in [0...arguments.length]
|
||||
arg = arguments[i]
|
||||
|
||||
if _.isArray arg
|
||||
@push.apply this, arg
|
||||
else if _.isString arg
|
||||
@_pushText arg
|
||||
else
|
||||
@_pushText "#{arg}"
|
||||
|
||||
return this
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
return @_pieces.join ''
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_pushIndent: ->
|
||||
for i in [0...@_indent]
|
||||
@_pieces.push @_indentString
|
||||
|
||||
_pushText: (text)->
|
||||
return if text.length is 0
|
||||
index = 0
|
||||
|
||||
if @_pieces.length > 0
|
||||
lastPiece = @_pieces[@_pieces.length-1]
|
||||
if lastPiece[lastPiece.length-1] is '\n'
|
||||
@_pushIndent()
|
||||
|
||||
while true
|
||||
newLineAt = text.indexOf '\n', index
|
||||
break if newLineAt is -1
|
||||
|
||||
@_pieces.push text[index..newLineAt]
|
||||
index = newLineAt + 1
|
||||
|
||||
break if newLineAt is text.length - 1
|
||||
@_pushIndent()
|
||||
|
||||
if text.length > index
|
||||
@_pieces.push text[index...text.length]
|
||||
@@ -0,0 +1,49 @@
|
||||
###
|
||||
Crafting Guide - versioned_parser_base.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class VersionedParserBase
|
||||
|
||||
constructor: (options={})->
|
||||
@_parsers = @_createParsers options
|
||||
@_currentSchema = _.chain(@_parsers).keys().last().value()
|
||||
|
||||
# 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
|
||||
|
||||
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]
|
||||
@@ -0,0 +1,13 @@
|
||||
###
|
||||
Crafting Table - polyfill.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
if not Array.prototype.clear?
|
||||
Object.defineProperty Array.prototype, 'clear', value:->
|
||||
@splice 0, @length
|
||||
return this
|
||||
@@ -0,0 +1,84 @@
|
||||
###
|
||||
Crafting Guide - crafting_plan.test.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
CraftingPlan = require '../models/crafting_plan'
|
||||
ItemSlug = require '../models/item_slug'
|
||||
Mod = require '../models/mod'
|
||||
ModPack = require '../models/mod_pack'
|
||||
ModVersion = require '../models/mod_version'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
modPack = plan = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'crafting_plan.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
mod = new Mod name:'Minecraft', slug:'minecraft'
|
||||
mod.addModVersion new ModVersion modSlug:mod.slug, version:'1.7.10'
|
||||
mod.activeModVersion.parse """
|
||||
schema:1
|
||||
|
||||
item:Oak Plank; recipe:; input:Oak Log; pattern:... .0. ...; quantity:4
|
||||
item:Stick; recipe:; input:Oak Plank; pattern:... .0. .0.; quantity:4
|
||||
item:Crafting Table; recipe:; input:Oak Plank; pattern:00. 00. ...
|
||||
item:Furnace; recipe:; input:Cobblestone; pattern:000 0.0 000; tools:Crafting Table
|
||||
item:Iron Ingot; recipe:; input:Iron Ore, furnace fuel; pattern:.0. ... .1.; tools:Furnace
|
||||
item:Iron Sword; recipe:; input:Iron Ingot, Stick; pattern:.0. .0. .1.; tools:Crafting Table
|
||||
"""
|
||||
modPack = new ModPack
|
||||
modPack.addMod mod
|
||||
|
||||
plan = new CraftingPlan modPack:modPack, includingTools:false
|
||||
|
||||
describe 'craft', ->
|
||||
|
||||
describe 'under the simplest conditions', ->
|
||||
|
||||
it 'can craft a single step recipe', ->
|
||||
plan.want.add ItemSlug.slugify 'oak_plank'
|
||||
plan.craft()
|
||||
plan.need.unparse().should.equal 'oak_log'
|
||||
plan.result.unparse().should.equal '4.oak_plank'
|
||||
|
||||
it 'can craft a multi-step recipe', ->
|
||||
plan.want.add ItemSlug.slugify 'crafting_table'
|
||||
plan.craft()
|
||||
plan.need.unparse().should.equal 'oak_log'
|
||||
plan.result.unparse().should.equal 'crafting_table'
|
||||
|
||||
it 'can craft a multi-step recipe using tools', ->
|
||||
plan.want.add ItemSlug.slugify 'furnace'
|
||||
plan.craft()
|
||||
plan.need.unparse().should.equal '8.cobblestone'
|
||||
plan.result.unparse().should.equal 'furnace'
|
||||
|
||||
it 'can craft a multi-step recipe re-using tools', ->
|
||||
plan.want.add ItemSlug.slugify 'iron_sword'
|
||||
plan.craft()
|
||||
plan.need.unparse().should.equal '2.furnace_fuel:2.iron_ore:oak_log'
|
||||
plan.result.unparse().should.equal 'iron_sword:2.oak_plank:3.stick'
|
||||
|
||||
describe 'with building tools', ->
|
||||
|
||||
it 'can craft a multi-step recipe using tools', ->
|
||||
plan.includingTools = true
|
||||
plan.want.add ItemSlug.slugify 'furnace'
|
||||
plan.craft()
|
||||
plan.need.unparse().should.equal '8.cobblestone:oak_log'
|
||||
plan.result.unparse().should.equal 'crafting_table:furnace'
|
||||
|
||||
it 'can craft a multi-step recipe re-using tools', ->
|
||||
plan.includingTools = true
|
||||
plan.want.add ItemSlug.slugify 'iron_sword'
|
||||
plan.craft()
|
||||
|
||||
plan.need.unparse().should.eql '8.cobblestone:2.furnace_fuel:2.iron_ore:2.oak_log'
|
||||
plan.result.unparse().should.equal 'crafting_table:furnace:' +
|
||||
'iron_sword:2.oak_plank:3.stick'
|
||||
@@ -0,0 +1,28 @@
|
||||
###
|
||||
# Crafting Guide - event_recorder.coffee
|
||||
#
|
||||
# Copyright (c) 2014-2015 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 = []
|
||||
|
||||
Object.defineProperty this, 'names', get:-> e.event for e in @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
|
||||
|
||||
reset: ->
|
||||
@events = []
|
||||
@@ -0,0 +1,211 @@
|
||||
###
|
||||
# Crafting Guide - inventory.test.coffee
|
||||
#
|
||||
# Copyright (c) 2014-2015 by Redwood Labs
|
||||
# All rights reserved.
|
||||
###
|
||||
|
||||
{Event} = require '../constants'
|
||||
EventRecorder = require './event_recorder'
|
||||
Inventory = require '../models/inventory'
|
||||
Item = require '../models/item'
|
||||
ItemSlug = require '../models/item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
inventory = modPack = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'inventory.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
inventory = new Inventory {}, silent:false
|
||||
inventory.add ItemSlug.slugify('wool'), 4
|
||||
inventory.add ItemSlug.slugify('string'), 20
|
||||
inventory.add ItemSlug.slugify('boat')
|
||||
|
||||
describe 'add', ->
|
||||
|
||||
it 'can add to an empty inventory', ->
|
||||
inventory.add ItemSlug.slugify('iron_ingot'), 4
|
||||
stack = inventory._stacks['iron_ingot']
|
||||
stack.constructor.name.should.equal 'Stack'
|
||||
stack.itemSlug.qualified.should.equal 'iron_ingot'
|
||||
stack.quantity.should.equal 4
|
||||
|
||||
it 'can augment quantity of existing items', ->
|
||||
inventory.add ItemSlug.slugify('wool'), 2
|
||||
inventory.unparse().should.equal 'boat:20.string:6.wool'
|
||||
|
||||
it 'can add zero quantity', ->
|
||||
inventory.add ItemSlug.slugify('wool'), 0
|
||||
inventory.unparse().should.equal 'boat:20.string:4.wool'
|
||||
|
||||
it 'emits the proper events', ->
|
||||
events = new EventRecorder inventory
|
||||
inventory.add ItemSlug.slugify('iron_ingot'), 10
|
||||
events.names.should.eql [Event.add, Event.change]
|
||||
|
||||
describe 'addInventory', ->
|
||||
|
||||
it 'can add to an empty inventory', ->
|
||||
newInventory = new Inventory
|
||||
newInventory.addInventory inventory
|
||||
newInventory.unparse().should.equal 'boat:20.string:4.wool'
|
||||
|
||||
it 'can add a mix of new and existing items', ->
|
||||
newInventory = new Inventory
|
||||
newInventory.add ItemSlug.slugify('string'), 2
|
||||
newInventory.addInventory inventory
|
||||
newInventory.unparse().should.equal 'boat:22.string:4.wool'
|
||||
|
||||
describe 'clone', ->
|
||||
|
||||
it 'creates an empty inventory from an empty inventory', ->
|
||||
a = new Inventory
|
||||
b = a.clone()
|
||||
b._itemSlugs.should.eql []
|
||||
|
||||
it 'faithfully copies an existing inventory', ->
|
||||
copy = inventory.clone()
|
||||
copy.unparse().should.equal 'boat:20.string:4.wool'
|
||||
|
||||
describe 'each', ->
|
||||
|
||||
it 'works with an empty inventory', ->
|
||||
inventory = new Inventory
|
||||
result = []
|
||||
inventory.each (item)-> result.push item.name
|
||||
result.should.eql []
|
||||
|
||||
it 'works when items have only been added', ->
|
||||
result = []
|
||||
inventory.each (stack)-> result.push stack.itemSlug.qualified
|
||||
result.should.eql ['boat', 'string', 'wool']
|
||||
|
||||
it 'works when items have been augmented', ->
|
||||
inventory.add ItemSlug.slugify 'iron_ingot'
|
||||
inventory.add ItemSlug.slugify 'boat'
|
||||
inventory.add ItemSlug.slugify('wool'), 2
|
||||
|
||||
result = []
|
||||
inventory.each (stack)-> result.push stack.itemSlug.qualified
|
||||
result.should.eql ['boat', 'iron_ingot', 'string', 'wool']
|
||||
|
||||
describe 'hasAtLeast', ->
|
||||
|
||||
it 'works when the item is completely absent', ->
|
||||
answer = inventory.hasAtLeast 'chicken', 1
|
||||
answer.should.be.false
|
||||
|
||||
it 'always returns true for zero quantity', ->
|
||||
inventory.hasAtLeast('chicken', 0).should.be.true
|
||||
inventory.hasAtLeast('wool', 0).should.be.true
|
||||
|
||||
it 'works for a quantity above 1', ->
|
||||
inventory.hasAtLeast('wool', 3).should.be.true
|
||||
inventory.hasAtLeast('wool', 4).should.be.true
|
||||
inventory.hasAtLeast('wool', 5).should.be.false
|
||||
|
||||
describe 'localize', ->
|
||||
|
||||
before ->
|
||||
modPack =
|
||||
modSlug:
|
||||
wool: 'minecraft'
|
||||
string: 'minecraft'
|
||||
boat: 'minecraft'
|
||||
stone_gear: 'buildcraft'
|
||||
findItem: (slug)->
|
||||
return slug:new ItemSlug @modSlug[slug.item], slug.item
|
||||
|
||||
it 'replaces item slugs with qualified slugs', ->
|
||||
inventory.add ItemSlug.slugify 'stone_gear'
|
||||
inventory.modPack = modPack
|
||||
inventory.localize()
|
||||
|
||||
slugs = []
|
||||
inventory.each (stack)-> slugs.push stack.itemSlug.qualified
|
||||
slugs.should.eql [
|
||||
'minecraft__boat'
|
||||
'minecraft__string'
|
||||
'minecraft__wool'
|
||||
'buildcraft__stone_gear'
|
||||
]
|
||||
|
||||
it 'ignores qualified slugs', ->
|
||||
inventory.add ItemSlug.slugify 'buildcraft__stone_gear'
|
||||
inventory.modPack = modPack
|
||||
inventory.localize()
|
||||
|
||||
slugs = []
|
||||
inventory.each (stack)-> slugs.push stack.itemSlug.qualified
|
||||
slugs.should.eql [
|
||||
'minecraft__boat'
|
||||
'minecraft__string'
|
||||
'minecraft__wool'
|
||||
'buildcraft__stone_gear'
|
||||
]
|
||||
|
||||
describe 'parse', ->
|
||||
|
||||
beforeEach ->
|
||||
inventory = new Inventory {}, silent:false
|
||||
|
||||
it 'ignores an empty string', ->
|
||||
result = inventory.parse ''
|
||||
result.unparse().should.eql ''
|
||||
|
||||
it 'can parse a single item without quantity', ->
|
||||
result = inventory.parse 'wool'
|
||||
result.unparse().should.equal 'wool'
|
||||
|
||||
it 'can parse a single item with quantity', ->
|
||||
result = inventory.parse '4.wool'
|
||||
result.unparse().should.equal '4.wool'
|
||||
|
||||
it 'can parse multiple mixed-type items', ->
|
||||
result = inventory.parse '4.wool:10.string:boat'
|
||||
result.unparse().should.equal 'boat:10.string:4.wool'
|
||||
|
||||
describe 'pop', ->
|
||||
|
||||
it 'returns null for an empty inventory', ->
|
||||
inventory = new Inventory
|
||||
result = inventory.pop()
|
||||
expect(result).to.be.null
|
||||
|
||||
it 'completely removes the last item', ->
|
||||
stack = inventory.pop()
|
||||
stack.itemSlug.qualified.should.equal 'wool'
|
||||
stack.quantity.should.equal 4
|
||||
inventory.unparse().should.equal 'boat:20.string'
|
||||
|
||||
it 'triggers the right events', ->
|
||||
events = new EventRecorder inventory
|
||||
result = inventory.pop()
|
||||
events.names.should.eql [Event.remove, Event.change]
|
||||
|
||||
describe 'remove', ->
|
||||
|
||||
it 'throws when the item is absent', ->
|
||||
expect(-> inventory.remove('chicken')).to.throw Error,
|
||||
'cannot remove chicken since it is not in this inventory'
|
||||
|
||||
it 'throws when the item has insufficient quantity', ->
|
||||
expect(-> inventory.remove('wool', 10)).to.throw Error,
|
||||
'cannot remove 10: only 4 wool in this inventory'
|
||||
|
||||
it 'removes all items by default', ->
|
||||
inventory.remove 'wool'
|
||||
expect(inventory._stacks.wool).to.be.empty
|
||||
|
||||
it 'removes a quantity above 1', ->
|
||||
inventory.remove 'wool', 3
|
||||
inventory._stacks.wool.quantity.should.equal 1
|
||||
|
||||
it 'emits the proper events', ->
|
||||
events = new EventRecorder inventory
|
||||
inventory.remove 'wool'
|
||||
events.names.should.eql [Event.change, Event.remove, Event.change]
|
||||
@@ -0,0 +1,136 @@
|
||||
###
|
||||
Crafting Guide - item_slug.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
ItemSlug = require '../models/item_slug'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'item_slug.coffee', ->
|
||||
|
||||
describe 'constructor', ->
|
||||
|
||||
it 'can handle one argument', ->
|
||||
slug = new ItemSlug 'alpha'
|
||||
slug.item.should.equal 'alpha'
|
||||
expect(slug.mod).to.be.null
|
||||
slug.qualified.should.equal 'alpha'
|
||||
|
||||
it 'can handle two arguments', ->
|
||||
slug = new ItemSlug 'alpha', 'bravo'
|
||||
slug.mod.should.equal 'alpha'
|
||||
slug.item.should.equal 'bravo'
|
||||
slug.qualified.should.equal 'alpha__bravo'
|
||||
|
||||
it 'throws with zero arguments', ->
|
||||
f = -> new ItemSlug
|
||||
expect(f).to.throw Error, 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
|
||||
|
||||
it 'throws with more arguments', ->
|
||||
f = -> new ItemSlug 'alpha', 'bravo', 'charlie'
|
||||
expect(f).to.throw Error, 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
|
||||
|
||||
describe 'ItemSlug.compare', ->
|
||||
|
||||
it 'sorts qualified slugs first', ->
|
||||
a = new ItemSlug 'alpha'
|
||||
b = new ItemSlug 'bravo', 'charlie'
|
||||
|
||||
ItemSlug.compare(a, b).should.equal +1
|
||||
ItemSlug.compare(b, a).should.equal -1
|
||||
|
||||
it 'sorts by mod when both are qualified', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'charlie', 'delta'
|
||||
|
||||
ItemSlug.compare(a, b).should.equal -1
|
||||
ItemSlug.compare(b, a).should.equal +1
|
||||
|
||||
it 'sorts by item when both are qualified in the same mod', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'charlie', 'bravo'
|
||||
|
||||
ItemSlug.compare(a, b).should.equal -1
|
||||
ItemSlug.compare(b, a).should.equal +1
|
||||
|
||||
it 'sorts by item when not qualified', ->
|
||||
a = new ItemSlug 'alpha'
|
||||
b = new ItemSlug 'bravo'
|
||||
|
||||
ItemSlug.compare(a, b).should.equal -1
|
||||
ItemSlug.compare(b, a).should.equal +1
|
||||
|
||||
describe 'ItemSlug.equal', ->
|
||||
|
||||
it 'requires both to have the same mod', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'alpha', 'charlie'
|
||||
c = new ItemSlug 'alpha', 'bravo'
|
||||
|
||||
ItemSlug.equal(a, b).should.be.false
|
||||
ItemSlug.equal(a, c).should.be.true
|
||||
|
||||
it 'requires both to have the same item', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'alpha', 'charlie'
|
||||
c = new ItemSlug 'alpha', 'bravo'
|
||||
|
||||
ItemSlug.equal(a, b).should.be.false
|
||||
ItemSlug.equal(a, c).should.be.true
|
||||
|
||||
describe 'ItemSlug.slugify', ->
|
||||
|
||||
it 'can slugify a pure name', ->
|
||||
slug = ItemSlug.slugify 'Alpha Bravo (Charlie)'
|
||||
slug.item.should.equal 'alpha_bravo_charlie'
|
||||
expect(slug.mod).to.be.null
|
||||
|
||||
it 'can slugify a simple item slug', ->
|
||||
slug = ItemSlug.slugify 'alpha_bravo_charlie'
|
||||
slug.item.should.equal 'alpha_bravo_charlie'
|
||||
expect(slug.mod).to.be.null
|
||||
|
||||
it 'can slugify a fully-qualified slug', ->
|
||||
slug = ItemSlug.slugify 'alpha_bravo__charlie_delta'
|
||||
slug.mod.should.equal 'alpha_bravo'
|
||||
slug.item.should.equal 'charlie_delta'
|
||||
|
||||
describe 'matches', ->
|
||||
|
||||
it 'ignores mod when either is unqualified', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'bravo'
|
||||
c = new ItemSlug 'charlie'
|
||||
|
||||
a.matches(b).should.be.true
|
||||
a.matches(c).should.be.false
|
||||
|
||||
it 'observes differences in mod when all are qualified', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'charlie', 'bravo'
|
||||
c = new ItemSlug 'delta', 'echo'
|
||||
d = new ItemSlug 'alpha', 'bravo'
|
||||
|
||||
a.matches(b).should.be.false
|
||||
a.matches(c).should.be.false
|
||||
a.matches(d).should.be.true
|
||||
|
||||
describe 'isQualified', ->
|
||||
|
||||
it 'returns true only when the mod slug is set', ->
|
||||
a = new ItemSlug 'alpha', 'bravo'
|
||||
b = new ItemSlug 'charlie'
|
||||
a.isQualified.should.be.true
|
||||
b.isQualified.should.be.false
|
||||
|
||||
describe '[]', ->
|
||||
|
||||
it 'allows slugs as a key', ->
|
||||
slug = new ItemSlug 'alpha', 'bravo'
|
||||
data = {}
|
||||
data[slug] = 'foo'
|
||||
data['alpha__bravo'].should.equal 'foo'
|
||||
data[slug].should.equal 'foo'
|
||||
@@ -0,0 +1,30 @@
|
||||
###
|
||||
Crafting Guide - mod.test.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
Mod = require '../models/mod'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
mod = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'mod.coffee', ->
|
||||
|
||||
beforeEach -> mod = new Mod name:'Test', slug:'test'
|
||||
|
||||
describe 'compareTo', ->
|
||||
|
||||
it 'lists required mods first', ->
|
||||
minecraft = new Mod name:'Minecraft', slug:'minecraft'
|
||||
mod.compareTo(minecraft).should.equal +1
|
||||
minecraft.compareTo(mod).should.equal -1
|
||||
|
||||
it 'sorts by name second', ->
|
||||
buildcraft = new Mod name:'Buildcraft', slug:'buildcraft'
|
||||
mod.compareTo(buildcraft).should.equal +1
|
||||
buildcraft.compareTo(mod).should.equal -1
|
||||
@@ -0,0 +1,103 @@
|
||||
###
|
||||
Crafting Guide - mod_pack.test.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
Item = require '../models/item'
|
||||
ItemSlug = require '../models/item_slug'
|
||||
Mod = require '../models/mod'
|
||||
ModPack = require '../models/mod_pack'
|
||||
ModVersion = require '../models/mod_version'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
buildcraft = industrialCraft = minecraft = modPack = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'mod_pack.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
minecraft = new Mod slug:'minecraft', name:'Minecraft'
|
||||
minecraft.addModVersion new ModVersion modSlug:minecraft.slug, version:'1.7.10'
|
||||
minecraft.activeModVersion.addItem new Item name:'Wool'
|
||||
minecraft.activeModVersion.addItem new Item name:'Bed', recipes:['']
|
||||
minecraft.activeModVersion.registerName ItemSlug.slugify('iron_chestplate'), 'Iron Chestplate'
|
||||
|
||||
buildcraft = new Mod slug:'buildcraft', name:'Buildcraft'
|
||||
buildcraft.addModVersion new ModVersion modSlug:buildcraft.slug, version:'6.2.6'
|
||||
buildcraft.activeModVersion.addItem new Item name:'Stone Gear', recipes:['']
|
||||
buildcraft.activeModVersion.addItem new Item name:'Wrench', recipes:['']
|
||||
buildcraft.activeVersion = Mod.Version.None
|
||||
|
||||
industrialCraft = new Mod slug:'industrial_craft', name:'Industrial Craft'
|
||||
industrialCraft.addModVersion new ModVersion modSlug:industrialCraft.slug, version:'2.0'
|
||||
industrialCraft.activeModVersion.addItem new Item name:'Resin'
|
||||
industrialCraft.activeModVersion.addItem new Item name:'Rubber'
|
||||
industrialCraft.activeModVersion.addItem new Item name:'Wrench', recipes:['']
|
||||
industrialCraft.activeVersion = Mod.Version.None
|
||||
|
||||
modPack = new ModPack
|
||||
modPack.addMod minecraft
|
||||
modPack.addMod buildcraft
|
||||
modPack.addMod industrialCraft
|
||||
|
||||
describe 'findItem', ->
|
||||
|
||||
it 'can find an item by partial slug', ->
|
||||
item = modPack.findItem ItemSlug.slugify 'wool'
|
||||
item.slug.qualified.should.equal 'minecraft__wool'
|
||||
|
||||
it 'can find an item by full slug', ->
|
||||
item = modPack.findItem ItemSlug.slugify 'minecraft__wool'
|
||||
item.name.should.equal 'Wool'
|
||||
|
||||
it 'can find an ambiguous item by full slug', ->
|
||||
buildcraft.activeVersion = Mod.Version.Latest
|
||||
industrialCraft.activeVersion = Mod.Version.Latest
|
||||
item = modPack.findItem ItemSlug.slugify 'industrial_craft__wrench'
|
||||
item.name.should.equal 'Wrench'
|
||||
item.modVersion.mod.name.should.equal 'Industrial Craft'
|
||||
|
||||
it 'can find an ambiguous item by partial slug', ->
|
||||
buildcraft.activeVersion = Mod.Version.Latest
|
||||
industrialCraft.activeVersion = Mod.Version.Latest
|
||||
item = modPack.findItem ItemSlug.slugify 'wrench'
|
||||
item.name.should.equal 'Wrench'
|
||||
item.modVersion.mod.name.should.equal 'Buildcraft'
|
||||
|
||||
describe 'findItemByName', ->
|
||||
|
||||
it 'finds the requested item', ->
|
||||
item = modPack.findItemByName 'Bed'
|
||||
item.name.should.equal 'Bed'
|
||||
|
||||
it 'ignores disabled mod versions', ->
|
||||
item = modPack.findItemByName 'Stone Gear'
|
||||
expect(item).to.be.null
|
||||
|
||||
describe 'findItemDisplay', ->
|
||||
|
||||
it 'returns all data for a regular Minecraft item', ->
|
||||
display = modPack.findItemDisplay ItemSlug.slugify 'bed'
|
||||
display.iconUrl.should.equal '/browse/minecraft/bed/icon.png'
|
||||
display.itemUrl.should.equal '/browse/minecraft/bed/'
|
||||
display.itemName.should.equal 'Bed'
|
||||
display.modSlug.should.equal 'minecraft'
|
||||
|
||||
it 'returns all data for an item in an enabled mod', ->
|
||||
buildcraft.activeVersion = '6.2.6'
|
||||
display = modPack.findItemDisplay ItemSlug.slugify 'stone_gear'
|
||||
display.iconUrl.should.equal '/browse/buildcraft/stone_gear/icon.png'
|
||||
display.itemUrl.should.equal '/browse/buildcraft/stone_gear/'
|
||||
display.itemName.should.equal 'Stone Gear'
|
||||
display.modSlug.should.equal 'buildcraft'
|
||||
|
||||
it 'assumes an unfound item is from Minecraft', ->
|
||||
display = modPack.findItemDisplay ItemSlug.slugify 'iron_chestplate'
|
||||
display.iconUrl.should.equal '/browse/minecraft/iron_chestplate/icon.png'
|
||||
display.itemUrl.should.equal '/browse/minecraft/iron_chestplate/'
|
||||
display.itemName.should.equal 'Iron Chestplate'
|
||||
display.modSlug.should.equal 'minecraft'
|
||||
@@ -0,0 +1,96 @@
|
||||
###
|
||||
Crafting Guide - mod_version.test.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
Item = require '../models/item'
|
||||
ItemSlug = require '../models/item_slug'
|
||||
ModVersion = require '../models/mod_version'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
modVersion = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'mod_version.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
modVersion = new ModVersion modSlug:'test', version:'0.0'
|
||||
modVersion.addItem new Item name:'underscore', group:'punctuation'
|
||||
modVersion.addItem new Item name:'bravo', group:'letter'
|
||||
modVersion.addItem new Item name:'alpha', group:'letter'
|
||||
modVersion.addItem new Item name:'one', group:'number'
|
||||
modVersion.addItem new Item name:'two', group:'number'
|
||||
|
||||
describe 'constructor', ->
|
||||
|
||||
it 'requires a mod slug', ->
|
||||
expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.modSlug is required'
|
||||
|
||||
it 'requires a mod version', ->
|
||||
expect(-> new ModVersion modSlug:'test').to.throw Error, 'attributes.version is required'
|
||||
|
||||
describe 'addItem', ->
|
||||
|
||||
it 'refuses to add duplicates', ->
|
||||
modVersion.addItem new Item name:'Wool'
|
||||
expect(-> modVersion.addItem new Item name:'Wool').to.throw Error, 'duplicate item for Wool'
|
||||
|
||||
it 'adds an item indexed by its slug', ->
|
||||
modVersion.addItem new Item name:'Wool'
|
||||
modVersion._items.wool.name.should.equal 'Wool'
|
||||
|
||||
it 'sets the modVersion', ->
|
||||
modVersion.addItem new Item name:'Wool'
|
||||
modVersion._items.wool.modVersion.should.equal modVersion
|
||||
|
||||
describe 'eachGroup', ->
|
||||
|
||||
it 'returns all the groups in order', ->
|
||||
groupNames = []
|
||||
modVersion.eachGroup (groupName)-> groupNames.push groupName
|
||||
groupNames.should.eql ['letter', 'number', 'punctuation']
|
||||
|
||||
describe 'eachItemInGroup', ->
|
||||
|
||||
it 'returns immediately for unknown group', ->
|
||||
slugs = []
|
||||
modVersion.eachItemInGroup 'foobar', (item)-> slugs.push item.slug.qualified
|
||||
slugs.should.eql []
|
||||
|
||||
it 'calls callback for exactly the items in a group in order', ->
|
||||
slugs = []
|
||||
modVersion.eachItemInGroup 'letter', (item)-> slugs.push item.slug.qualified
|
||||
slugs.should.eql ['test__alpha', 'test__bravo']
|
||||
|
||||
slugs = []
|
||||
modVersion.eachItemInGroup 'number', (item)-> slugs.push item.slug.qualified
|
||||
slugs.should.eql ['test__one', 'test__two']
|
||||
|
||||
|
||||
describe 'findItemByName', ->
|
||||
|
||||
it 'locates items by slugified name', ->
|
||||
modVersion.addItem new Item name:'Crafting Table'
|
||||
modVersion.findItemByName('Crafting Table').slug.qualified.should.equal 'test__crafting_table'
|
||||
|
||||
describe 'findRecipes', ->
|
||||
|
||||
beforeEach ->
|
||||
modVersion = new ModVersion modSlug:'test', version:'1.0'
|
||||
modVersion.parse """
|
||||
schema:1
|
||||
|
||||
item: Cake
|
||||
recipe:; input: Milk, Sugar, Egg, Wheat; pattern: 000 121 333; extras: 3 Bucket
|
||||
recipe:; input: Milk, Cocoa Beans, Egg, Wheat; pattern: 000 121 333; extras: 3 Bucket
|
||||
item: Bucket
|
||||
recipe:; input: Iron Ingot; pattern: ... 0.0 .0.
|
||||
"""
|
||||
|
||||
it 'finds all recipes which list item as output', ->
|
||||
recipes = modVersion.findRecipes ItemSlug.slugify('Bucket')
|
||||
(r.output[0].itemSlug.item for r in recipes).sort().should.eql ['bucket', 'cake', 'cake']
|
||||
@@ -0,0 +1,45 @@
|
||||
###
|
||||
Crafting Guide - command_parser_version_base.test.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
CommandParserVersionBase = require '../../models/parser_versions/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).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.should.equal 'alpha\nbravo\ncharlie'
|
||||
|
||||
it 'identifies an empty heredoc', ->
|
||||
parser._lines = ['command: <<-END', 'END']
|
||||
parser._lineNumber = 1
|
||||
|
||||
result = parser._parseHereDoc parser._lines[0]
|
||||
expect(result).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.should.equal 'alpha\n bravo\ncharlie'
|
||||
@@ -0,0 +1,251 @@
|
||||
###
|
||||
Crafting Guide - mod_version_parser_v1.test.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
CommandParserVersionBase = require '../../models/parser_versions/command_parser_version_base'
|
||||
ItemSlug = require '../../models/item_slug'
|
||||
ModVersion = require '../../models/mod_version'
|
||||
ModVersionParserV1 = require '../../models/parser_versions/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 '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 'Recipe', ->
|
||||
|
||||
beforeEach -> baseText = 'item: Charlie; '
|
||||
|
||||
describe 'input', ->
|
||||
|
||||
it 'adds "input" when present', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern: ... 012 ...'
|
||||
slugs = (s.itemSlug.item for s in modVersion.findRecipes(ItemSlug.slugify('charlie'))[0].input)
|
||||
slugs.should.eql ['alpha', 'bravo', 'charlie']
|
||||
|
||||
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']
|
||||
|
||||
describe 'pattern', ->
|
||||
|
||||
it 'adds "pattern" when present', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
|
||||
modVersion.findRecipes(ItemSlug.slugify('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 input 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 an input'
|
||||
|
||||
it 'computes the input stack sizes from the pattern', ->
|
||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern:111 .0. 2.2'
|
||||
recipe = modVersion.findRecipes(ItemSlug.slugify('charlie'))[0]
|
||||
recipe.input[0].quantity.should.equal 1
|
||||
recipe.input[1].quantity.should.equal 3
|
||||
recipe.input[2].quantity.should.equal 2
|
||||
|
||||
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('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('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 '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('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('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', 'test__delta', 'charlie', '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('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('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: Potato, furnace fuel
|
||||
pattern: .0. ... .1.
|
||||
tools: Furnace
|
||||
|
||||
group: Functional Blocks
|
||||
|
||||
item: Furnace
|
||||
recipe:
|
||||
input: Cobblestone
|
||||
pattern: 000 0.0 000
|
||||
tools: Crafting Table
|
||||
|
||||
update: Iron Ingot
|
||||
recipe:
|
||||
input: Iron Dust, furnace fuel
|
||||
pattern: .0. ... .1.
|
||||
tools: 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
|
||||
@@ -0,0 +1,86 @@
|
||||
###
|
||||
Crafting Guide - recipe.test.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
Item = require '../models/item'
|
||||
ItemSlug = require '../models/item_slug'
|
||||
Recipe = require '../models/recipe'
|
||||
Stack = require '../models/stack'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
input = output = pattern = recipe = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'recipe.coffee', ->
|
||||
|
||||
describe 'constructor', ->
|
||||
|
||||
beforeEach ->
|
||||
input = [
|
||||
new Stack(itemSlug:new ItemSlug('iron_gear')),
|
||||
new Stack(itemSlug:new ItemSlug('gold_ingot'), quantity:4)
|
||||
]
|
||||
pattern = '.1. 101 .1.'
|
||||
|
||||
it 'requires input', ->
|
||||
expect(-> new Recipe slug:'gold_gear', pattern:pattern).to.throw Error, 'attributes.input is required'
|
||||
|
||||
it 'requires a pattern', ->
|
||||
expect(-> new Recipe slug:'gold_gear', input:input).to.throw Error, 'attributes.pattern is required'
|
||||
|
||||
it 'requires either outputs or a slug', ->
|
||||
f = -> new Recipe input:input, pattern:pattern
|
||||
expect(f).to.throw 'attributes.itemSlug or attributes.output is required'
|
||||
|
||||
it 'creates default output', ->
|
||||
recipe = new Recipe itemSlug:ItemSlug.slugify('gold_gear'), input:input, pattern:pattern
|
||||
recipe.output.length.should.equal 1
|
||||
recipe.output[0].itemSlug.qualified.should.equal 'gold_gear'
|
||||
recipe.output[0].quantity.should.equal 1
|
||||
|
||||
it 'assigns a default slug', ->
|
||||
recipe = new Recipe input:input, pattern:pattern, output:[new Stack itemSlug:ItemSlug.slugify('gold_gear')]
|
||||
recipe.itemSlug.qualified.should.equal 'gold_gear'
|
||||
|
||||
describe 'getItemSlugAt', ->
|
||||
|
||||
beforeEach ->
|
||||
input = [
|
||||
new Stack itemSlug:ItemSlug.slugify('iron_gear')
|
||||
new Stack itemSlug:ItemSlug.slugify('gold_ingot'), quantity:4
|
||||
]
|
||||
recipe = new Recipe itemSlug:'gold_gear', input:input, pattern:'.1. 101 .1.'
|
||||
|
||||
it 'returns the proper item for an early slot', ->
|
||||
recipe.getItemSlugAt(1).qualified.should.equal 'gold_ingot'
|
||||
|
||||
it 'returns the proper item for a late slot', ->
|
||||
recipe.getItemSlugAt(4).qualified.should.equal 'iron_gear'
|
||||
|
||||
it 'returns null for an invalid slot', ->
|
||||
expect(recipe.getItemSlugAt(12)).to.be.null
|
||||
|
||||
describe '_parsePattern', ->
|
||||
|
||||
beforeEach ->
|
||||
recipe = new Recipe
|
||||
itemSlug: 'oak_wood_planks',
|
||||
input: [new Stack itemSlug:new ItemSlug('oak_wood')],
|
||||
pattern:'... .0. ...'
|
||||
|
||||
it 'normalizes invalid characters', ->
|
||||
recipe._parsePattern('$$0 #() 010').should.equal '..0 ... 010'
|
||||
|
||||
it 'removes extra characters', ->
|
||||
recipe._parsePattern('000 000 000 000').should.equal '000 000 000'
|
||||
|
||||
it 'fills in missing characters', ->
|
||||
recipe._parsePattern('000000').should.equal '000 000 ...'
|
||||
|
||||
it 'fills in spaces', ->
|
||||
recipe._parsePattern('000000000').should.equal '000 000 000'
|
||||
@@ -0,0 +1,84 @@
|
||||
###
|
||||
Crafting Guide - string_builder.test.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
StringBuilder = require '../models/string_builder'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
builder = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'string_builder.coffee', ->
|
||||
|
||||
beforeEach -> builder = new StringBuilder
|
||||
|
||||
describe 'call', ->
|
||||
|
||||
it 'works with no arguments', ->
|
||||
builder.call (b)-> b.push 'foo'
|
||||
builder.toString().should.equal 'foo'
|
||||
|
||||
it 'works with multiple arguments', ->
|
||||
builder.call 'foo', 'bar', 'baz', (builder, a, b, c)-> builder.loop [a, b, c]
|
||||
builder.toString().should.equal 'foo, bar, baz'
|
||||
|
||||
describe 'loop', ->
|
||||
|
||||
it 'can make an empty list', ->
|
||||
builder.loop [], start:'[', end:']'
|
||||
builder.toString().should.equal '[]'
|
||||
|
||||
it 'can make a list with a single element', ->
|
||||
builder.loop ['foo'], start:'[', end:']'
|
||||
builder.toString().should.equal '[foo]'
|
||||
|
||||
it 'can make a list with many elements', ->
|
||||
builder.loop ['foo', 'bar', 'baz'], start:'[', end:']'
|
||||
builder.toString().should.equal '[foo, bar, baz]'
|
||||
|
||||
it 'can make a list with a custom callback', ->
|
||||
builder.loop ['foo', 'bar', 'baz'], start:'[', end:']', onEach:(b, i)-> b.push "\"#{i}\""
|
||||
builder.toString().should.equal '["foo", "bar", "baz"]'
|
||||
|
||||
it 'can use a custom delimiter', ->
|
||||
builder.loop ['foo', 'bar', 'baz'], delimiter:'|'
|
||||
builder.toString().should.equal 'foo|bar|baz'
|
||||
|
||||
it 'can indent content', ->
|
||||
builder.loop ['foo', 'bar', 'baz'], start:'[\n', end:'\n]', delimiter:',\n', indent:true
|
||||
builder.toString().should.equal '[\n foo,\n bar,\n baz\n]'
|
||||
|
||||
describe 'onlyIf', ->
|
||||
|
||||
it 'calls the callback on true', ->
|
||||
builder.onlyIf true, (b)-> b.push 'foo'
|
||||
builder.toString().should.equal 'foo'
|
||||
|
||||
describe 'push', ->
|
||||
|
||||
it 'can build a simple string', ->
|
||||
builder.push('foo').push(' bar').push(' baz')
|
||||
builder.toString().should.equal 'foo bar baz'
|
||||
|
||||
it 'can build a multi-line string', ->
|
||||
builder.push('foo').push('\nbar\n').push('baz')
|
||||
builder.toString().should.equal 'foo\nbar\nbaz'
|
||||
|
||||
it 'can build an indented multi-line string', ->
|
||||
builder
|
||||
.push 'foo\n'
|
||||
.indent()
|
||||
.push 'bar\n'
|
||||
.outdent()
|
||||
.push 'baz'
|
||||
|
||||
builder.toString().should.equal 'foo\n bar\nbaz'
|
||||
|
||||
it 'can pick apart multiple newlines in a single chunk', ->
|
||||
builder.indent().push('foo\nbar\nbaz').outdent().push('\nbif')
|
||||
builder.toString().should.equal 'foo\n bar\n baz\nbif'
|
||||
@@ -0,0 +1,45 @@
|
||||
###
|
||||
# Crafting Guide - test.coffee
|
||||
#
|
||||
# Copyright (c) 2014-2015 by Redwood Labs
|
||||
# All rights reserved.
|
||||
###
|
||||
|
||||
# Test Set-up ##########################################################################################################
|
||||
|
||||
chai.use require 'sinon-chai'
|
||||
chai.config.includeStack = true
|
||||
|
||||
if typeof(global) is 'undefined'
|
||||
window.global = window
|
||||
|
||||
global.assert = chai.assert
|
||||
global.expect = chai.expect
|
||||
global.should = chai.should()
|
||||
global.util = require 'util'
|
||||
|
||||
Logger = require '../logger'
|
||||
global.logger = new Logger level:Logger.DEBUG
|
||||
|
||||
require '../polyfill'
|
||||
require '../underscore_mixins'
|
||||
|
||||
# Test Registry ########################################################################################################
|
||||
|
||||
mocha.setup 'bdd'
|
||||
|
||||
# tests are roughly in order of how errors should be tackled
|
||||
require './string_builder.test'
|
||||
require './item_slug.test'
|
||||
require './inventory.test'
|
||||
require './recipe.test'
|
||||
require './mod_version.test'
|
||||
require './mod.test'
|
||||
require './mod_pack.test'
|
||||
require './parser_versions/command_parser_version_base.test'
|
||||
require './parser_versions/mod_version_parser_v1.test'
|
||||
require './crafting_plan.test'
|
||||
|
||||
mocha.checkLeaks()
|
||||
mocha.globals ['LiveReload']
|
||||
mocha.run()
|
||||
@@ -0,0 +1,30 @@
|
||||
###
|
||||
Crafting Guide - underscore.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
_.mixin
|
||||
|
||||
slugify: (text)->
|
||||
return null unless text?
|
||||
|
||||
result = text.toLowerCase()
|
||||
result = result.replace /[^a-zA-Z0-9_]/g, '_'
|
||||
result = result.replace /__+/, '_'
|
||||
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
|
||||
@@ -0,0 +1,52 @@
|
||||
###
|
||||
Crafting Guide - url_params.coffee
|
||||
|
||||
Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
url = require 'url'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class UrlParams
|
||||
|
||||
constructor: (parseMap, url=null)->
|
||||
if not parseMap? then throw new Error 'parseMap is required'
|
||||
url ?= window.location.href
|
||||
|
||||
@_parseMap = {}
|
||||
for name, options of parseMap
|
||||
options.type ?= 'string'
|
||||
options.default ?= null
|
||||
|
||||
options.parse = this["_#{options.type}"]
|
||||
if not options.parse? then throw new Error "#{options.type} is not a valid type"
|
||||
|
||||
@_parseMap[name] = options
|
||||
|
||||
@parse url
|
||||
|
||||
parse: (urlText)->
|
||||
params = url.parse(urlText, true).query
|
||||
for name, options of @_parseMap
|
||||
value = params[name]
|
||||
if value?
|
||||
this[name] = options.parse value
|
||||
else
|
||||
this[name] = options.default
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_boolean: (text)->
|
||||
return true if text in ['true', 'yes']
|
||||
return false
|
||||
|
||||
_integer: (text)->
|
||||
return null unless text?
|
||||
result = parseInt text
|
||||
return null unless _.isNumber result
|
||||
return result
|
||||
|
||||
_string: (text)->
|
||||
return text
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user