mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-26 14:03:28 -04:00
Merge branch 'master' of github.com:codecombat/codecombat
This commit is contained in:
commit
038e8cf776
24 changed files with 292 additions and 48 deletions
app
server
|
@ -10,6 +10,7 @@ module.exports = LevelOptions =
|
|||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['moveRight']
|
||||
'gems-in-the-deep':
|
||||
disableSpaces: true
|
||||
hidesSubmitUntilRun: true
|
||||
|
@ -71,8 +72,10 @@ module.exports = LevelOptions =
|
|||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['Brak']
|
||||
'favorable-odds':
|
||||
disableSpaces: true
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
|
@ -82,6 +85,7 @@ module.exports = LevelOptions =
|
|||
restrictedGear: {feet: 'leather-boots'}
|
||||
'the-raised-sword':
|
||||
disableSpaces: true
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
|
@ -97,6 +101,7 @@ module.exports = LevelOptions =
|
|||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['loop']
|
||||
'haunted-kithmaze':
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
|
@ -105,6 +110,7 @@ module.exports = LevelOptions =
|
|||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['loop']
|
||||
'descending-further':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
|
@ -140,6 +146,8 @@ module.exports = LevelOptions =
|
|||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['findNearestEnemy']
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'lowly-kithmen':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
|
@ -147,6 +155,8 @@ module.exports = LevelOptions =
|
|||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['findNearestEnemy']
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'closing-the-distance':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
|
@ -154,6 +164,7 @@ module.exports = LevelOptions =
|
|||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'tactical-strike':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
|
@ -161,12 +172,14 @@ module.exports = LevelOptions =
|
|||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'the-final-kithmaze':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'the-gauntlet':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
|
@ -174,6 +187,7 @@ module.exports = LevelOptions =
|
|||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'kithgard-gates':
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
|
|
|
@ -6,6 +6,7 @@ module.exports = class CoordinateDisplay extends createjs.Container
|
|||
'surface:mouse-over': 'onMouseOver'
|
||||
'surface:stage-mouse-down': 'onMouseDown'
|
||||
'camera:zoom-updated': 'onZoomUpdated'
|
||||
'level:flag-color-selected': 'onFlagColorSelected'
|
||||
|
||||
constructor: (options) ->
|
||||
super()
|
||||
|
@ -60,6 +61,9 @@ module.exports = class CoordinateDisplay extends createjs.Container
|
|||
@hide()
|
||||
@show()
|
||||
|
||||
onFlagColorSelected: (e) ->
|
||||
@placingFlag = Boolean e.color
|
||||
|
||||
hide: ->
|
||||
return unless @label.parent
|
||||
@removeChild @label
|
||||
|
@ -154,6 +158,6 @@ module.exports = class CoordinateDisplay extends createjs.Container
|
|||
@y = sup.y
|
||||
@addChild @background
|
||||
@addChild @label
|
||||
@addChild @pointMarker
|
||||
@addChild @pointMarker unless @placingFlag
|
||||
@updateCache()
|
||||
Backbone.Mediator.publish 'surface:coordinates-shown', {}
|
||||
|
|
|
@ -664,7 +664,9 @@ module.exports = Lank = class Lank extends CocoClass
|
|||
updateLabels: ->
|
||||
return unless @thang
|
||||
blurb = if @thang.health <= 0 then null else @thang.sayMessage # Dead men tell no tales
|
||||
@addLabel 'say', Label.STYLE_SAY if blurb
|
||||
blurb = null if blurb in ['For Thoktar!', 'Bones!', 'Behead!', 'Destroy!', 'Die, humans!'] # Let's just hear, not see, these ones.
|
||||
labelStyle = if /Hero Placeholder/.test(@thang.id) then Label.STYLE_DIALOGUE else Label.STYLE_SAY
|
||||
@addLabel 'say', labelStyle if blurb
|
||||
if @labels.say?.setText blurb
|
||||
@notifySpeechUpdated blurb: blurb
|
||||
label.update() for name, label of @labels
|
||||
|
|
|
@ -50,6 +50,8 @@ class CocoModel extends Backbone.Model
|
|||
@loading = false
|
||||
@jqxhr = null
|
||||
@loadFromBackup()
|
||||
|
||||
getCreationDate: -> new Date(parseInt(@id.slice(0,8), 16)*1000)
|
||||
|
||||
getNormalizedURL: -> "#{@urlRoot}/#{@id}"
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ module.exports =
|
|||
|
||||
'tome:toggle-spell-list': c.object {title: 'Toggle Spell List', description: 'Published when you toggle the dropdown for a thang\'s spells'}
|
||||
|
||||
'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: ['spell']},
|
||||
'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: []},
|
||||
spell: {type: 'object'}
|
||||
|
||||
'tome:palette-cleared': c.object {title: 'Palette Cleared', description: 'Published when the spell palette is about to be cleared and recreated.'},
|
||||
|
@ -122,6 +122,10 @@ module.exports =
|
|||
'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'}
|
||||
|
||||
'tome:suspect-code-fragment-added': c.object {title: 'Suspect Code Fragment Added', description: 'Published when a suspect code fragment is added to the sample code.', required: ['codeFragment']},
|
||||
codeFragment: {type: 'string'}
|
||||
codeLanguage: {type: 'string'}
|
||||
|
||||
'tome:winnability-updated': c.object {title: 'Winnability Updated', description: 'When we think we can now win (or can no longer win), we may want to emphasize the submit button versus the run button (or vice versa), so this fires when we get new goal states (even preloaded goal states) suggesting success or failure change.', required: ['winnable']},
|
||||
winnable: {type: 'boolean'}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ module.exports =
|
|||
thang: {type: 'object'}
|
||||
killer: {type: 'object'}
|
||||
killerHealth: {type: ['number', 'undefined']}
|
||||
maxHealth: {type: 'number'}
|
||||
|
||||
'world:thang-touched-goal': c.object {required: ['actor', 'touched']},
|
||||
replacedNoteChain: {type: 'array'}
|
||||
|
|
|
@ -43,6 +43,13 @@ $level-resize-transition-time: 0.5s
|
|||
visibility: hidden
|
||||
#gold-view
|
||||
right: 1%
|
||||
@include box-shadow(-1px 1px 10px cyan)
|
||||
.team-gold
|
||||
font-size: 2vw
|
||||
line-height: 2vw
|
||||
img
|
||||
width: 1.8vw
|
||||
heighT: 1.8vw
|
||||
#control-bar-view .title
|
||||
left: 20%
|
||||
width: 60%
|
||||
|
|
|
@ -12,11 +12,13 @@
|
|||
padding: 19px 0px 2px 25px
|
||||
z-index: 3
|
||||
font-size: 14px
|
||||
min-width: 230px
|
||||
|
||||
&.brighter
|
||||
font-size: 18px
|
||||
font-size: 1.4vw
|
||||
border-width: 0.91vw 1.22vw 3.10vw 0.91vw
|
||||
min-width: 23vw
|
||||
|
||||
.goals-status
|
||||
margin: 5px 0 0 0
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
z-index: 6
|
||||
@include transition(box-shadow .2s linear)
|
||||
@include user-select(none)
|
||||
padding: 4px
|
||||
padding: 0.4vw
|
||||
background: transparent url(/images/level/gold_background.png) no-repeat
|
||||
background-size: 100% 100%
|
||||
border-radius: 4px
|
||||
|
@ -18,9 +18,9 @@
|
|||
box-shadow: 2px 2px 2px black
|
||||
|
||||
.team-gold
|
||||
font-size: 18px
|
||||
font-size: 1.4vw
|
||||
line-height: 1.4vw
|
||||
margin: 0
|
||||
line-height: 20px
|
||||
color: hsla(205,0%,51%,1)
|
||||
display: inline-block
|
||||
padding: 0px 4px
|
||||
|
@ -35,11 +35,12 @@
|
|||
color: hsla(116,80%,51%,1)
|
||||
|
||||
img
|
||||
width: 16px
|
||||
height: 16px
|
||||
width: 1.2vw
|
||||
height: 1.2vw
|
||||
border-radius: 2px
|
||||
padding: 1px
|
||||
margin-top: -1px
|
||||
padding: 0.1vw
|
||||
margin-top: -0.2vw
|
||||
margin-right: 0.1vw
|
||||
|
||||
.gold-amount
|
||||
display: inline-block
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
|
||||
h1:not(.not-code), h2:not(.not-code), h3:not(.not-code), h4:not(.not-code), h5:not(.not-code), h6:not(.not-code)
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace
|
||||
font-variant: normal
|
||||
|
||||
.popover-title
|
||||
background-color: transparent
|
||||
|
|
|
@ -1,4 +1,82 @@
|
|||
#play-achievements-modal
|
||||
.achievement-view
|
||||
color: black
|
||||
|
||||
.modal-header
|
||||
padding-bottom: 20px
|
||||
|
||||
img.icon
|
||||
float: left
|
||||
width: 40px
|
||||
margin-right: 10px
|
||||
|
||||
|
||||
//- Unachieved Panels
|
||||
|
||||
.panel
|
||||
margin: 5px 0
|
||||
position: relative
|
||||
border: 2px solid rgb(75,75,75)
|
||||
padding: 2px
|
||||
|
||||
h3
|
||||
margin: 0 0 0 50px
|
||||
color: rgb(75,75,75)
|
||||
|
||||
p
|
||||
margin: 0 0 0 50px
|
||||
color: rgb(75,75,75)
|
||||
|
||||
.panel-body
|
||||
padding: 5px 150px 5px 5px
|
||||
border: 2px solid rgb(150,150,150)
|
||||
|
||||
.created
|
||||
position: absolute
|
||||
right: 10px
|
||||
top: 5px
|
||||
color: rgb(75,75,75)
|
||||
font-size: 12px
|
||||
|
||||
|
||||
//- Achieved Panels
|
||||
|
||||
.panel.earned
|
||||
background: rgb(50,40,33)
|
||||
border: 5px solid rgb(26,21,17)
|
||||
padding: 0
|
||||
|
||||
h3
|
||||
color: white
|
||||
|
||||
p
|
||||
color: rgb(203,170,148)
|
||||
|
||||
.panel-body
|
||||
border: 2px solid rgb(75,62,51)
|
||||
|
||||
.created
|
||||
color: rgb(255,189,68)
|
||||
|
||||
|
||||
//- Rewards
|
||||
|
||||
.rewards
|
||||
position: absolute
|
||||
right: .2em
|
||||
//bottom: 10px
|
||||
top: 29px
|
||||
|
||||
.label
|
||||
font-size: 18px
|
||||
margin-left: 5px
|
||||
color: rgb(50,40,33)
|
||||
background: rgb(203,170,148)
|
||||
|
||||
.gems
|
||||
background: #94ccc7
|
||||
|
||||
.worth
|
||||
background: #d8c488
|
||||
|
||||
img
|
||||
width: 12px
|
||||
height: 12px
|
|
@ -3,7 +3,6 @@ h3.problem-alert-title(data-i18n="play_level.problem_alert_title") Fix Your Code
|
|||
if hint
|
||||
span.problem-title!= hint
|
||||
br
|
||||
br
|
||||
span.problem-subtitle!= message
|
||||
else
|
||||
span.problem-title!= message
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
h4
|
||||
span= doc.shortName
|
||||
span.prop-name= doc.shortName
|
||||
| -
|
||||
code.prop-type= doc.type == 'function' && doc.owner == 'this' ? 'method' : doc.type
|
||||
if doc.type != 'function'
|
||||
|
|
|
@ -4,4 +4,29 @@ block modal-header-content
|
|||
h3(data-i18n="play.achievements") Achievements
|
||||
|
||||
block modal-body-content
|
||||
p TODO: show all dem achievements
|
||||
for achievement in achievements
|
||||
.panel(class=achievement.earned ? 'earned' : '')
|
||||
.panel-body
|
||||
img.icon(src=achievement.getImageURL())
|
||||
h3= achievement.name
|
||||
p= achievement.description
|
||||
|
||||
if achievement.earnedDate
|
||||
.created=moment(achievement.earnedDate).fromNow()
|
||||
else
|
||||
.created(data-i18n="user.status_unfinished")
|
||||
|
||||
.rewards
|
||||
- rewards = achievement.get('rewards')
|
||||
- rewards = { gems: 100 }
|
||||
if rewards && rewards.gems
|
||||
span.gems.label.label-default
|
||||
span= rewards.gems
|
||||
img.gem(src="/images/common/gem.png")
|
||||
|
||||
- worth = achievement.get('worth')
|
||||
if worth
|
||||
span.worth.label.label-default
|
||||
span #{worth}xp
|
||||
// maybe add more icons/numbers for items, heroes, levels, xp?
|
||||
block modal-footer
|
|
@ -43,10 +43,10 @@
|
|||
.game-controls.header-font
|
||||
button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items")
|
||||
button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes")
|
||||
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
|
||||
if me.get('anonymous') === false
|
||||
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
|
||||
if me.isAdmin()
|
||||
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
|
||||
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
|
||||
button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
|
||||
else if me.get('anonymous', true)
|
||||
|
|
|
@ -86,7 +86,7 @@ module.exports = class InventoryModal extends ModalView
|
|||
locked = not (item.get('original') in me.items())
|
||||
locked = false if me.get('slug') is 'nick'
|
||||
|
||||
if not item.getFrontFacingStats().props.length and not _.size item.getFrontFacingStats().stats # Temp: while there are placeholder items
|
||||
if not item.getFrontFacingStats().props.length and not _.size(item.getFrontFacingStats().stats) and not locked # Temp: while there are placeholder items
|
||||
null # Don't put into a collection
|
||||
else if locked and item.get('slug') isnt 'simple-boots'
|
||||
@itemGroups.lockedItems.add(item)
|
||||
|
|
|
@ -102,7 +102,7 @@ module.exports = class WorldMapView extends RootView
|
|||
context.isIPadApp = application.isIPadApp
|
||||
context.mapType = _.string.slugify @terrain
|
||||
context.nextLevel = @nextLevel
|
||||
context.forestIsAvailable = '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or [])
|
||||
context.forestIsAvailable = @startedForestLevel or '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or [])
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
|
@ -131,8 +131,10 @@ module.exports = class WorldMapView extends RootView
|
|||
@openModalView authModal
|
||||
|
||||
onSessionsLoaded: (e) ->
|
||||
forestLevels = (f.id for f in forest)
|
||||
for session in @sessions.models
|
||||
@levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started'
|
||||
@startedForestLevel = true if session.get('levelID') in forestLevels
|
||||
if @nextLevel and @levelStatusMap[@nextLevel] is 'complete'
|
||||
@nextLevel = null
|
||||
@render()
|
||||
|
@ -771,3 +773,4 @@ if me.getKithmazeGroup() is 'the-first-kithmaze'
|
|||
_.remove dungeon, id: 'haunted-kithmaze'
|
||||
else
|
||||
_.remove dungeon, id: 'the-first-kithmaze'
|
||||
_.find(dungeon, id: 'the-raised-sword').nextLevels.continue = 'haunted-kithmaze'
|
||||
|
|
|
@ -6,4 +6,10 @@ module.exports = class ReloadLevelModal extends ModalView
|
|||
template: template
|
||||
|
||||
events:
|
||||
'click #restart-level-confirm-button': -> Backbone.Mediator.publish 'level:restart', {}
|
||||
'click #restart-level-confirm-button': 'onClickRestart'
|
||||
|
||||
onClickRestart: (e) ->
|
||||
if key.shift
|
||||
Backbone.Mediator.publish 'level:restart', {}
|
||||
else
|
||||
Backbone.Mediator.publish 'tome:reload-code', {}
|
||||
|
|
|
@ -34,7 +34,7 @@ module.exports = class ProblemAlertView extends CocoView
|
|||
getRenderData: (context={}) ->
|
||||
context = super context
|
||||
if @problem?
|
||||
format = (s) -> s?.replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>')
|
||||
format = (s) -> marked(s.replace(/</g, '<').replace(/>/g, '>')) if s?
|
||||
context.message = format @problem.aetherProblem.message
|
||||
context.hint = format @problem.aetherProblem.hint
|
||||
context
|
||||
|
|
|
@ -394,7 +394,7 @@ module.exports = class SpellView extends CocoView
|
|||
@focus() if cast
|
||||
|
||||
onCodeReload: (e) ->
|
||||
return unless e.spell is @spell
|
||||
return unless e.spell is @spell or not e.spell
|
||||
@reloadCode true
|
||||
@ace.clearSelection()
|
||||
_.delay (=> @ace?.clearSelection()), 500 # Make double sure this gets done (saw some timing issues?)
|
||||
|
@ -450,7 +450,8 @@ module.exports = class SpellView extends CocoView
|
|||
_.throttle @updateLines, 500
|
||||
_.throttle @hideProblemAlert, 500
|
||||
]
|
||||
onSignificantChange.push _.debounce @checkRequiredCode, 1500 if requiredCodePerLevel[@options.level.get('slug')]
|
||||
onSignificantChange.push _.debounce @checkRequiredCode, 750 if LevelOptions[@options.level.get('slug')]?.requiredCode
|
||||
onSignificantChange.push _.debounce @checkSuspectCode, 750 if LevelOptions[@options.level.get('slug')]?.suspectCode
|
||||
@onCodeChangeMetaHandler = =>
|
||||
return if @eventsSuppressed
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5
|
||||
|
@ -881,13 +882,26 @@ module.exports = class SpellView extends CocoView
|
|||
checkRequiredCode: =>
|
||||
return if @destroyed
|
||||
source = @getSource().replace @singleLineCommentRegex(), ''
|
||||
for requiredCodeFragment in requiredCodePerLevel[@options.level.get('slug')]
|
||||
requiredCodeFragments = LevelOptions[@options.level.get('slug')].requiredCode
|
||||
for requiredCodeFragment in requiredCodeFragments
|
||||
# Could make this obey regular expressions like suspectCode if needed
|
||||
if source.indexOf(requiredCodeFragment) is -1
|
||||
@warnedCodeFragments ?= {}
|
||||
unless @warnedCodeFragments[requiredCodeFragment]
|
||||
Backbone.Mediator.publish 'tome:required-code-fragment-deleted', codeFragment: requiredCodeFragment
|
||||
@warnedCodeFragments[requiredCodeFragment] = true
|
||||
|
||||
checkSuspectCode: =>
|
||||
return if @destroyed
|
||||
source = @getSource().replace @singleLineCommentRegex(), ''
|
||||
suspectCodeFragments = LevelOptions[@options.level.get('slug')].suspectCode
|
||||
for suspectCodeFragment in suspectCodeFragments
|
||||
if suspectCodeFragment.pattern.test source
|
||||
@warnedCodeFragments ?= {}
|
||||
unless @warnedCodeFragments[suspectCodeFragment.name]
|
||||
Backbone.Mediator.publish 'tome:suspect-code-fragment-added', codeFragment: suspectCodeFragment.name, codeLanguage: @spell.language
|
||||
@warnedCodeFragments[suspectCodeFragment] = true
|
||||
|
||||
destroy: ->
|
||||
$(@ace?.container).find('.ace_gutter').off 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
|
||||
$(@ace?.container).find('.ace_gutter').off 'click', @onGutterClick
|
||||
|
@ -900,11 +914,3 @@ module.exports = class SpellView extends CocoView
|
|||
@debugView?.destroy()
|
||||
$(window).off 'resize', @onWindowResize
|
||||
super()
|
||||
|
||||
|
||||
requiredCodePerLevel =
|
||||
'dungeons-of-kithgard': ['moveRight']
|
||||
'true-names': ['Brak']
|
||||
'the-first-kithmaze': ['loop']
|
||||
'haunted-kithmaze': ['loop']
|
||||
'lowly-kithmen': ['findNearestEnemy']
|
||||
|
|
|
@ -2,27 +2,92 @@ ModalView = require 'views/kinds/ModalView'
|
|||
template = require 'templates/play/modal/play-achievements-modal'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Achievement = require 'models/Achievement'
|
||||
#AchievementView = require 'views/game-menu/AchievementView'
|
||||
EarnedAchievement = require 'models/EarnedAchievement'
|
||||
|
||||
utils = require 'lib/utils'
|
||||
|
||||
PAGE_SIZE = 200
|
||||
|
||||
module.exports = class PlayAchievementsModal extends ModalView
|
||||
className: 'modal fade play-modal'
|
||||
template: template
|
||||
modalWidthPercent: 90
|
||||
id: 'play-achievements-modal'
|
||||
#instant: true
|
||||
|
||||
#events:
|
||||
# 'change input.select': 'onSelectionChanged'
|
||||
plain: true
|
||||
|
||||
earnedMap: {}
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
#@achievements = new CocoCollection([], {model: Achievement})
|
||||
#@achievements.url = '/db/thang.type?view=achievements&project=name,description,components,original,rasterIcon'
|
||||
#@supermodel.loadCollection(@achievements, 'achievements')
|
||||
@achievements = new Backbone.Collection()
|
||||
earnedMap = {}
|
||||
|
||||
achievementsFetcher = new CocoCollection([], {url: '/db/achievement', model: Achievement})
|
||||
achievementsFetcher.setProjection([
|
||||
'name'
|
||||
'description'
|
||||
'icon'
|
||||
'worth'
|
||||
'i18n'
|
||||
'rewards'
|
||||
'collection'
|
||||
])
|
||||
|
||||
earnedAchievementsFetcher = new CocoCollection([], {url: '/db/earned_achievement', model: EarnedAchievement})
|
||||
earnedAchievementsFetcher.setProjection([ 'achievement' ])
|
||||
|
||||
achievementsFetcher.skip = 0
|
||||
achievementsFetcher.fetch({data: {skip: 0, limit: PAGE_SIZE}})
|
||||
earnedAchievementsFetcher.skip = 0
|
||||
earnedAchievementsFetcher.fetch({data: {skip: 0, limit: PAGE_SIZE}})
|
||||
|
||||
@listenTo achievementsFetcher, 'sync', @onAchievementsLoaded
|
||||
@listenTo earnedAchievementsFetcher, 'sync', @onEarnedAchievementsLoaded
|
||||
|
||||
@supermodel.loadCollection(achievementsFetcher, 'achievement')
|
||||
@supermodel.loadCollection(earnedAchievementsFetcher, 'achievement')
|
||||
|
||||
@onEverythingLoaded = _.after(2, @onEverythingLoaded)
|
||||
|
||||
onAchievementsLoaded: (fetcher) ->
|
||||
needMore = fetcher.models.length is PAGE_SIZE
|
||||
@achievements.add(fetcher.models)
|
||||
if needMore
|
||||
fetcher.skip += PAGE_SIZE
|
||||
fetcher.fetch({data: {skip: fetcher.skip, limit: PAGE_SIZE}})
|
||||
else
|
||||
@stopListening(fetcher)
|
||||
@onEverythingLoaded()
|
||||
|
||||
onEarnedAchievementsLoaded: (fetcher) ->
|
||||
needMore = fetcher.models.length is PAGE_SIZE
|
||||
for earned in fetcher.models
|
||||
@earnedMap[earned.get('achievement')] = earned
|
||||
if needMore
|
||||
fetcher.skip += PAGE_SIZE
|
||||
fetcher.fetch({data: {skip: fetcher.skip, limit: PAGE_SIZE}})
|
||||
else
|
||||
@stopListening(fetcher)
|
||||
@onEverythingLoaded()
|
||||
|
||||
onEverythingLoaded: =>
|
||||
@achievements.set(@achievements.filter((m) -> m.get('collection') isnt 'level.sessions'))
|
||||
for achievement in @achievements.models
|
||||
if earned = @earnedMap[achievement.id]
|
||||
achievement.earned = earned
|
||||
achievement.earnedDate = earned.getCreationDate()
|
||||
achievement.earnedDate ?= ''
|
||||
@achievements.comparator = (m) -> m.earnedDate
|
||||
@achievements.sort()
|
||||
@achievements.set(@achievements.models.reverse())
|
||||
for achievement in @achievements.models
|
||||
achievement.name = utils.i18n achievement.attributes, 'name'
|
||||
achievement.description = utils.i18n achievement.attributes, 'description'
|
||||
@render()
|
||||
|
||||
getRenderData: (context={}) ->
|
||||
context = super(context)
|
||||
#context.achievements = @achievements.models
|
||||
context.achievements = @achievements.models
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
|
|
|
@ -2,12 +2,6 @@ mongoose = require 'mongoose'
|
|||
jsonschema = require '../../app/schemas/models/earned_achievement'
|
||||
|
||||
EarnedAchievementSchema = new mongoose.Schema({
|
||||
created:
|
||||
type: Date
|
||||
default: Date.now
|
||||
changed:
|
||||
type: Date
|
||||
default: Date.now
|
||||
notified:
|
||||
type: Boolean
|
||||
default: false
|
||||
|
|
|
@ -16,7 +16,26 @@ class EarnedAchievementHandler extends Handler
|
|||
|
||||
get: (req, res) ->
|
||||
return @getByAchievementIDs(req, res) if req.query.view is 'get-by-achievement-ids'
|
||||
super(arguments...)
|
||||
query = { user: req.user._id+''}
|
||||
|
||||
projection = {}
|
||||
if req.query.project
|
||||
projection[field] = 1 for field in req.query.project.split(',')
|
||||
|
||||
q = EarnedAchievement.find(query, projection)
|
||||
|
||||
skip = parseInt(req.query.skip)
|
||||
if skip? and skip < 1000000
|
||||
q.skip(skip)
|
||||
|
||||
limit = parseInt(req.query.limit)
|
||||
if limit? and limit < 1000
|
||||
q.limit(limit)
|
||||
|
||||
q.exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
documents = (@formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, documents)
|
||||
|
||||
getByAchievementIDs: (req, res) ->
|
||||
query = { user: req.user._id+''}
|
||||
|
|
|
@ -129,6 +129,10 @@ module.exports = class Handler
|
|||
term = req.query.term
|
||||
matchedObjects = []
|
||||
filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]
|
||||
|
||||
skip = parseInt(req.query.skip)
|
||||
limit = parseInt(req.query.limit)
|
||||
|
||||
if @modelClass.schema.uses_coco_permissions and req.user
|
||||
filters.push {filter: {index: req.user.get('id')}}
|
||||
projection = null
|
||||
|
@ -158,7 +162,14 @@ module.exports = class Handler
|
|||
else
|
||||
args = [filter.filter]
|
||||
args.push projection if projection
|
||||
@modelClass.find(args...).limit(FETCH_LIMIT).exec callback
|
||||
q = @modelClass.find(args...)
|
||||
if skip? and skip < 1000000
|
||||
q.skip(skip)
|
||||
if limit? and limit < FETCH_LIMIT
|
||||
q.limit(limit)
|
||||
else
|
||||
q.limit(FETCH_LIMIT)
|
||||
q.exec callback
|
||||
# if it's not a text search but the user is an admin, let him try stuff anyway
|
||||
else if req.user?.isAdmin()
|
||||
# admins can send any sort of query down the wire
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue