Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-10-29 22:29:57 -07:00
commit 7e389d4084
16 changed files with 81 additions and 25 deletions

View file

@ -210,12 +210,23 @@ module.exports = class LevelBus extends Bus
@changedSessionProperties.state = true
@reallySaveSession() # Make sure it saves right away; don't debounce it.
onNewGoalStates: ({goalStates})->
state = @session.get 'state'
unless utils.kindaEqual state.goalStates, goalStates # Only save when goals really change
# TODO: this log doesn't capture when null-status goals are being set during world streaming. Where can they be coming from?
return console.error("Somehow trying to save null goal states!", goalStates) if _.find(goalStates, (gs) -> not gs.status)
state.goalStates = goalStates
onNewGoalStates: ({goalStates}) ->
# TODO: this log doesn't capture when null-status goals are being set during world streaming. Where can they be coming from?
return console.error("Somehow trying to save null goal states!", newGoalStates) if _.find(newGoalStates, (gs) -> not gs.status)
newGoalStates = goalStates
state = @session.get('state')
oldGoalStates = state.goalStates or {}
changed = false
for goalKey, goalState of newGoalStates
continue if oldGoalStates[goalKey]?.status is 'success' and goalState.status isnt 'success' # don't undo success, this property is for keying off achievements
continue if utils.kindaEqual state.goalStates?[goalKey], goalState # Only save when goals really change
changed = true
oldGoalStates[goalKey] = _.cloneDeep newGoalStates[goalKey]
if changed
state.goalStates = oldGoalStates
@session.set 'state', state
@changedSessionProperties.state = true
@saveSession()

View file

@ -43,7 +43,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
super(options)
@originalScripts = options.scripts
@session = options.session
@debugScripts = CocoView.getQueryVariable 'dev'
@debugScripts = application.isIPadApp or CocoView.getQueryVariable 'dev'
@initProperties()
@addScriptSubscriptions()
@beginTicking()
@ -193,7 +193,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
for sprite in noteGroup.sprites
if sprite.move?
sprite.move.duration ?= DEFAULT_BOT_MOVE_DURATION
sprite.id ?= 'Captain Anya'
sprite.id ?= 'Hero Placeholder'
noteGroup.script ?= {}
noteGroup.script.yields ?= true
noteGroup.script.skippable ?= true

View file

@ -647,6 +647,7 @@ module.exports = Lank = class Lank extends CocoClass
@notifySpeechUpdated e
onClearDialogue: (e) ->
return unless @labels.dialogue?.text
@labels.dialogue?.setText null
@dialogueSoundInstance?.stop()
@notifySpeechUpdated {}

View file

@ -105,9 +105,10 @@ _.extend ThangTypeSchema.properties,
shapes: c.object {title: 'Shapes', additionalProperties: ShapeObjectSchema}
containers: c.object {title: 'Containers', additionalProperties: ContainerObjectSchema}
animations: c.object {title: 'Animations', additionalProperties: RawAnimationObjectSchema}
kind: c.shortString {enum: ['Unit', 'Floor', 'Wall', 'Doodad', 'Misc', 'Mark', 'Item'], default: 'Misc', title: 'Kind'}
kind: c.shortString {enum: ['Unit', 'Floor', 'Wall', 'Doodad', 'Misc', 'Mark', 'Item', 'Hero'], default: 'Misc', title: 'Kind'}
terrains: c.array {title: 'Terrains', description: 'If specified, limits this ThangType to levels with matching terrains.', uniqueItems: true}, c.terrainString
gems: {type: 'integer', minimum: 0, title: 'Gem Cost', description: 'How many gems this item or hero costs.'}
heroClass: {type: 'string', enum: ['Warrior', 'Ranger', 'Wizard'], title: 'Hero Class', description: 'What class this is (if a hero) or is restricted to (if an item). Leave undefined for most items.'}
actions: c.object {title: 'Actions', additionalProperties: {$ref: '#/definitions/action'}}
soundTriggers: c.object {title: 'Sound Triggers', additionalProperties: c.array({}, {$ref: '#/definitions/sound'})},
say: c.object {format: 'slug-props', additionalProperties: {$ref: '#/definitions/sound'}},

View file

@ -120,3 +120,6 @@ module.exports =
'tome:maximize-toggled': c.object {title: 'Maximize Toggled', description: 'Published when the Tome has changed maximize/minimize state.'}
'tome:select-primary-sprite': c.object {title: 'Select Primary Sprite', description: 'Published to get the most important sprite\'s code selected.'}
'tome:required-code-fragment-deleted': c.object {title: 'Required Code Fragment Deleted', description: 'Published when a required code fragment is deleted from the sample code.', required: ['codeFragment']},
codeFragment: {type: 'string'}

View file

@ -14,4 +14,5 @@ if entryGroupSlugs
div(class="properties properties-" + slug + " nano-content")
else
// Hero; group by items, no tabs.
h4(data-i18n="play_level.tome_your_skills")
.properties

View file

@ -15,6 +15,7 @@ CocoCollection = require 'collections/CocoCollection'
LC = (componentName, config) -> original: LevelComponent[componentName + 'ID'], majorVersion: 0, config: config
DEFAULT_COMPONENTS =
Unit: [LC('Equips')]
Hero: [LC('Equips')]
Floor: [
LC('Exists', stateless: true)
LC('Physical', width: 20, height: 17, depth: 2, shape: 'sheet', pos: {x: 10, y: 8.5, z: 1})

View file

@ -354,7 +354,7 @@ module.exports = class ThangTypeEditView extends RootView
newThangType = if e.major then @thangType.cloneNewMajorVersion() else @thangType.cloneNewMinorVersion()
newThangType.set('commitMessage', e.commitMessage)
newThangType.updateI18NCoverage() if newThangType.get('i18nCoverage')
res = newThangType.save()
return unless res
modal = $('#save-version-modal')
@ -406,9 +406,14 @@ module.exports = class ThangTypeEditView extends RootView
pushChangesToPreview: =>
return if @temporarilyIgnoringChanges
keysProcessed = {}
for key of @thangType.attributes
keysProcessed[key] = true
continue if key is 'components'
@thangType.set(key, @treema.data[key])
for key, value of @treema.data when not keysProcessed[key]
@thangType.set(key, value)
@updateSelectBox()
@refreshAnimation()
@updateDots()

View file

@ -23,7 +23,7 @@ module.exports = class ChooseHeroView extends CocoView
constructor: (options) ->
super options
@heroes = new CocoCollection([], {model: ThangType})
@heroes.url = '/db/thang.type?view=heroes&project=original,name,slug,soundTriggers,featureImage'
@heroes.url = '/db/thang.type?view=heroes&project=original,name,slug,soundTriggers,featureImage,gems,heroClass,description'
@supermodel.loadCollection(@heroes, 'heroes')
@stages = {}

View file

@ -32,7 +32,7 @@ module.exports = class InventoryView extends CocoView
@equipment = options.equipment or @options.session?.get('heroConfig')?.inventory or me.get('heroConfig')?.inventory or {}
@equipment = $.extend true, {}, @equipment
@requireLevelEquipment()
@items.url = '/db/thang.type?view=items&project=name,components,original,rasterIcon'
@items.url = '/db/thang.type?view=items&project=name,components,original,rasterIcon,gems,description,heroClass'
@supermodel.loadCollection(@items, 'items')
destroy: ->
@ -329,7 +329,7 @@ module.exports = class InventoryView extends CocoView
'simple-boots': '53e237bf53457600003e3f05'
'longsword': '53e218d853457600003e3ebe'
'leather-tunic': '53e22eac53457600003e3efc'
#'leather-boots': '53e2384453457600003e3f07'
'leather-boots': '53e2384453457600003e3f07'
'programmaticon-i': '53e4108204c00d4607a89f78'
'crude-glasses': '53e238df53457600003e3f0b'
'builders-hammer': '53f4e6e3d822c23505b74f42'
@ -363,7 +363,7 @@ module.exports = class InventoryView extends CocoView
inWorldMap = $('#world-map-view').length
for slot, item of necessaryGear
continue if item is 'leather-tunic' and inWorldMap # Don't tell them they need it until they need it in the level
continue if equipment[slot] and not (item is 'builders-hammer' and equipment[slot] is gear.longsword)
continue if equipment[slot] and not ((item is 'builders-hammer' and equipment[slot] is gear.longsword) or (item is 'leather-boots' and equipment[slot] is gear['simple-boots']))
availableSlotSelector = "#available-equipment li[data-item-id='#{gear[item]}']"
@highlightElement availableSlotSelector, delay: 500, sides: ['right'], rotation: Math.PI / 2
@$el.find(availableSlotSelector).addClass 'should-equip'

View file

@ -11,7 +11,7 @@ module.exports = class OptionsView extends CocoView
template: template
aceConfig: {}
defaultConfig:
language: 'javascript'
language: 'python'
keyBindings: 'default'
invisibles: false
indentGuides: false

View file

@ -4,7 +4,6 @@ template = require 'templates/play/level/goals'
utils = require 'lib/utils'
stateIconMap =
incomplete: 'glyphicon-minus'
success: 'glyphicon-ok'
failure: 'glyphicon-remove'
@ -61,7 +60,7 @@ module.exports = class LevelGoalsView extends CocoView
# This should really get refactored, along with GoalManager, so that goals have a standard
# representation of how many are done, how many are needed, what that means, etc.
li = $('<li></li>').addClass("status-#{state.status}").text(text)
li.prepend($('<i></i>').addClass('glyphicon').addClass(stateIconMap[state.status]))
li.prepend($('<i></i>').addClass('glyphicon').addClass(iconClass) if iconClass = stateIconMap[state.status])
list.append(li)
goals.push goal
if not firstRun and state.status is 'success' and @previousGoalStatus[goal.id] isnt 'success'

View file

@ -371,6 +371,7 @@ module.exports = class SpellView extends CocoView
onCodeReload: (e) ->
return unless e.spell is @spell
@reloadCode true
@ace.clearSelection()
reloadCode: (cast=true) ->
@updateACEText @spell.originalSource
@ -422,6 +423,7 @@ module.exports = class SpellView extends CocoView
_.throttle @notifySpellChanged, 300
_.throttle @updateLines, 500
]
onSignificantChange.push _.debounce @checkRequiredCode, 1500 if requiredCodePerLevel[@options.level.get('slug')]
@onCodeChangeMetaHandler = =>
return if @eventsSuppressed
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5
@ -564,7 +566,7 @@ module.exports = class SpellView extends CocoView
guessWhetherFinished: (aether) ->
valid = not aether.getAllProblems().length
cursorPosition = @ace.getCursorPosition()
currentLine = _.string.rtrim(@aceDoc.$lines[cursorPosition.row].replace(/[ \t]*\/\/[^"']*/g, '')) # trim // unless inside "
currentLine = _.string.rtrim(@aceDoc.$lines[cursorPosition.row].replace(@singleLineCommentRegex(), '')) # trim // unless inside "
endOfLine = cursorPosition.column >= currentLine.length # just typed a semicolon or brace, for example
beginningOfLine = not currentLine.substr(0, cursorPosition.column).trim().length # uncommenting code, for example
incompleteThis = /^(s|se|sel|self|t|th|thi|this)$/.test currentLine.trim()
@ -575,6 +577,20 @@ module.exports = class SpellView extends CocoView
else
@recompile()
singleLineCommentRegex: ->
return @_singleLineCommentRegex if @_singleLineCommentRegex
commentStarts =
javascript: '//'
python: '#'
coffeescript: '#'
clojure: ';'
lua: '--'
io: '//'
commentStart = commentStarts[@spell.language] or '//'
@_singleLineCommentRegexp ?= new RegExp "[ \t]*#{commentStart}[^\"'\n]*", 'g'
console.log 'got', @_singleLineCommentRegexp, 'from', "[ \t]*#{commentStart}[^\"']*", 'comment start is', commentStart, 'acuse lang is', @spell.language
@_singleLineCommentRegexp
preload: ->
# Send this code over to the God for preloading, but don't change the cast state.
oldSource = @spell.source
@ -829,6 +845,16 @@ module.exports = class SpellView extends CocoView
onScriptStateChange: (e) ->
@scriptRunning = if e.currentScript is null then false else true
checkRequiredCode: =>
return if @destroyed
source = @getSource().replace @singleLineCommentRegex(), ''
for requiredCodeFragment in requiredCodePerLevel[@options.level.get('slug')]
if source.indexOf(requiredCodeFragment) is -1
@warnedCodeFragments ?= {}
unless @warnedCodeFragments[requiredCodeFragment]
Backbone.Mediator.publish 'tome:required-code-fragment-deleted', codeFragment: requiredCodeFragment
@warnedCodeFragments[requiredCodeFragment] = true
destroy: ->
$(@ace?.container).find('.ace_gutter').off 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
@firepad?.dispose()
@ -840,3 +866,9 @@ module.exports = class SpellView extends CocoView
@debugView?.destroy()
$(window).off 'resize', @onWindowResize
super()
requiredCodePerLevel =
'true-names': ['Brak']
'the-first-kithmaze': ['loop']
'lowly-kithmen': ['findNearestEnemy']

View file

@ -75,7 +75,7 @@ module.exports = class PlayLevelModal extends ModalView
patchMe ||= not _.isEqual val, lastHeroConfig[key]
sessionHeroConfig[key] = val
lastHeroConfig[key] = val
if (codeLanguage = @subviews.choose_hero_view.codeLanguage) and @subviews.choose_hero_view.codeLanguageChanged
if (codeLanguage = @subviews.choose_hero_view.codeLanguage) and (@subviews.choose_hero_view.codeLanguageChanged or not me.get('aceConfig'))
patchSession ||= codeLanguage isnt @options.session.get('codeLanguage')
patchMe ||= codeLanguage isnt me.get('aceConfig')?.language
@options.session.set 'codeLanguage', codeLanguage

View file

@ -10,7 +10,7 @@ hipchat = require '../hipchat'
deltasLib = require '../../app/lib/deltas'
PROJECT = {original: 1, name: 1, version: 1, description: 1, slug: 1, kind: 1, created: 1, permissions: 1}
FETCH_LIMIT = 300
FETCH_LIMIT = 500
module.exports = class Handler
# subclasses should override these properties

View file

@ -38,6 +38,8 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
'i18nCoverage'
'i18n'
'description'
'gems'
'heroClass'
]
hasAccess: (req) ->
@ -45,7 +47,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
hasAccessToDocument: (req, document, method=null) ->
method = (method or req.method).toLowerCase()
return true if method is 'get'
return true if method is 'get'
return true if req.user?.isAdmin()
return true if method is 'put' and @isJustFillingTranslations(req, document)
return
@ -59,11 +61,11 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
if req.query.view is 'items'
query.kind = 'Item'
else if req.query.view is 'heroes'
query.kind = 'Unit'
query.original = {$in: _.values heroes} # TODO: replace with some sort of ThangType property later
#query.kind = 'Hero' # TODO: when all the heroes are tagged, just use this
query.original = {$in: _.values heroes} # TODO: when all the heroes are tagged, don't do this
else if req.query.view is 'i18n-coverage'
query.i18nCoverage = {$exists: true}
q = ThangType.find(query, projection)
skip = parseInt(req.query.skip)
if skip? and skip < 1000000