mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-22 02:45:29 -04:00
Merge branch 'master' into production
This commit is contained in:
commit
7e389d4084
16 changed files with 81 additions and 25 deletions
app
lib
schemas
templates/play/level/tome
views
editor
game-menu
play
server
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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'}},
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 = {}
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -11,7 +11,7 @@ module.exports = class OptionsView extends CocoView
|
|||
template: template
|
||||
aceConfig: {}
|
||||
defaultConfig:
|
||||
language: 'javascript'
|
||||
language: 'python'
|
||||
keyBindings: 'default'
|
||||
invisibles: false
|
||||
indentGuides: false
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue