mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-28 18:15:52 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
f396114a47
19 changed files with 356 additions and 88 deletions
|
@ -119,7 +119,7 @@ module.exports = class Angel extends CocoClass
|
|||
return if @aborting
|
||||
# Toggle BOX2D_ENABLED during deserialization so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment.
|
||||
window.BOX2D_ENABLED = false
|
||||
@streamingWorld = World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), startFrame, endFrame, streamingWorld
|
||||
@streamingWorld = World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), startFrame, endFrame, @work.level, streamingWorld
|
||||
window.BOX2D_ENABLED = true
|
||||
@shared.lastSerializedWorldFrames = serialized.frames
|
||||
|
||||
|
@ -253,7 +253,7 @@ module.exports = class Angel extends CocoClass
|
|||
work.testWorld.goalManager.worldGenerationEnded() if work.testWorld.ended
|
||||
serialized = testWorld.serialize()
|
||||
window.BOX2D_ENABLED = false
|
||||
World.deserialize serialized.serializedWorld, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), serialized.startFrame, serialized.endFrame
|
||||
World.deserialize serialized.serializedWorld, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), serialized.startFrame, work.level, serialized.endFrame
|
||||
window.BOX2D_ENABLED = true
|
||||
@shared.lastSerializedWorldFrames = serialized.serializedWorld.frames
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ module.exports = class MusicPlayer extends CocoClass
|
|||
'audio-player:loaded': 'onAudioLoaded'
|
||||
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
||||
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
||||
'music-player:enter-menu': 'onEnterMenu'
|
||||
'music-player:exit-menu': 'onExitMenu'
|
||||
|
||||
constructor: ->
|
||||
super arguments...
|
||||
|
@ -35,9 +37,10 @@ module.exports = class MusicPlayer extends CocoClass
|
|||
@standingBy = e
|
||||
return
|
||||
|
||||
delay = e.delay ? 0
|
||||
@standingBy = null
|
||||
@fadeOutCurrentMusic()
|
||||
@startNewMusic(src) if e.play
|
||||
@startNewMusic(src, delay) if e.play
|
||||
|
||||
restartCurrentMusic: ->
|
||||
return unless @currentMusic
|
||||
|
@ -46,15 +49,16 @@ module.exports = class MusicPlayer extends CocoClass
|
|||
|
||||
fadeOutCurrentMusic: ->
|
||||
return unless @currentMusic
|
||||
createjs.Tween.removeTweens(@currentMusic)
|
||||
f = -> @stop()
|
||||
createjs.Tween.get(@currentMusic).to({volume: 0.0}, CROSSFADE_LENGTH).call(f)
|
||||
|
||||
startNewMusic: (src) ->
|
||||
startNewMusic: (src, delay) ->
|
||||
@currentMusic = createjs.Sound.play(src, 'none', 0, 0, -1, 0.3) if src
|
||||
return unless @currentMusic
|
||||
@currentMusic.volume = 0.0
|
||||
if me.get('music', true)
|
||||
createjs.Tween.get(@currentMusic).to({volume: MUSIC_VOLUME}, CROSSFADE_LENGTH)
|
||||
createjs.Tween.get(@currentMusic).wait(delay).to({volume: MUSIC_VOLUME}, CROSSFADE_LENGTH)
|
||||
|
||||
onMusicSettingChanged: ->
|
||||
@updateMusicVolume()
|
||||
|
@ -74,6 +78,24 @@ module.exports = class MusicPlayer extends CocoClass
|
|||
if @previousMusic
|
||||
@currentMusic = @previousMusic
|
||||
@restartCurrentMusic()
|
||||
if @currentMusic.volume
|
||||
createjs.Tween.get(@currentMusic).wait(5000).to({volume: MUSIC_VOLUME}, CROSSFADE_LENGTH)
|
||||
|
||||
onEnterMenu: (e) ->
|
||||
return if @inMenu
|
||||
@inMenu = true
|
||||
@previousMusic = @currentMusic
|
||||
terrain = (e.terrain ? 'Dungeon').toLowerCase()
|
||||
file = "/music/music-menu-#{terrain}"
|
||||
Backbone.Mediator.publish 'music-player:play-music', file: file, play: true, delay: 1000
|
||||
|
||||
onExitMenu: (e) ->
|
||||
return unless @inMenu
|
||||
@inMenu = false
|
||||
@fadeOutCurrentMusic()
|
||||
if @previousMusic
|
||||
@currentMusic = @previousMusic
|
||||
@restartCurrentMusic()
|
||||
|
||||
destroy: ->
|
||||
me.off 'change:music', @onMusicSettingChanged, @
|
||||
|
|
|
@ -151,10 +151,14 @@ module.exports = class Thang
|
|||
o.unusedTrackedPropertyKeys = (@trackedPropertiesKeys[propIndex] for used, propIndex in @trackedPropertiesUsed when not used)
|
||||
o
|
||||
|
||||
@deserialize: (o, world, classMap) ->
|
||||
@deserialize: (o, world, classMap, levelComponents) ->
|
||||
t = new Thang world, o.spriteName, o.id
|
||||
for [componentClassName, componentConfig] in o.components
|
||||
componentClass = classMap[componentClassName]
|
||||
unless componentClass = classMap[componentClassName]
|
||||
console.debug 'Compiling new Component while deserializing:', componentClassName
|
||||
componentModel = _.find levelComponents, name: componentClassName
|
||||
componentClass = world.loadClassFromCode componentModel.js, componentClassName, 'component'
|
||||
world.classMap[componentClassName] = componentClass
|
||||
t.addComponents [componentClass, componentConfig]
|
||||
t.unusedTrackedPropertyKeys = o.unusedTrackedPropertyKeys
|
||||
t.unusedTrackedPropertyValues = (t[prop] for prop in o.unusedTrackedPropertyKeys)
|
||||
|
|
|
@ -436,7 +436,7 @@ module.exports = class World
|
|||
console.log 'Whoa, serializing a lot of WorldScriptNotes here:', o.scriptNotes.length
|
||||
{serializedWorld: o, transferableObjects: [o.storageBuffer], startFrame: startFrame, endFrame: endFrame}
|
||||
|
||||
@deserialize: (o, classMap, oldSerializedWorldFrames, finishedWorldCallback, startFrame, endFrame, streamingWorld) ->
|
||||
@deserialize: (o, classMap, oldSerializedWorldFrames, finishedWorldCallback, startFrame, endFrame, level, streamingWorld) ->
|
||||
# Code hotspot; optimize it
|
||||
#console.log 'Deserializing', o, 'length', JSON.stringify(o).length
|
||||
#console.log JSON.stringify(o)
|
||||
|
@ -461,10 +461,10 @@ module.exports = class World
|
|||
perf.t1 = now()
|
||||
if w.thangs.length
|
||||
for thangConfig in o.thangs when not w.thangMap[thangConfig.id]
|
||||
w.thangs.push thang = Thang.deserialize(thangConfig, w, classMap)
|
||||
w.thangs.push thang = Thang.deserialize(thangConfig, w, classMap, level.levelComponents)
|
||||
w.setThang thang
|
||||
else
|
||||
w.thangs = (Thang.deserialize(thang, w, classMap) for thang in o.thangs)
|
||||
w.thangs = (Thang.deserialize(thang, w, classMap, level.levelComponents) for thang in o.thangs)
|
||||
w.setThang thang for thang in w.thangs
|
||||
w.scriptNotes = (WorldScriptNote.deserialize(sn, w, classMap) for sn in o.scriptNotes)
|
||||
perf.t2 = now()
|
||||
|
|
|
@ -202,8 +202,13 @@
|
|||
victory_sign_up_poke: "Want to save your code? Create a free account!"
|
||||
victory_rate_the_level: "Rate the level: " # Only in old-style levels.
|
||||
victory_return_to_ladder: "Return to Ladder"
|
||||
victory_play_next_level: "Play Next Level" # Only in old-style levels.
|
||||
victory_play_continue: "Continue"
|
||||
victory_play_skip: "Skip Ahead"
|
||||
victory_play_next_level: "Play Next Level"
|
||||
victory_play_more_practice: "More Practice"
|
||||
victory_play_too_easy: "Too Easy"
|
||||
victory_play_just_right: "Just Right"
|
||||
victory_play_too_hard: "Too Hard"
|
||||
victory_saving_progress: "Saving Progress"
|
||||
victory_go_home: "Go Home" # Only in old-style levels.
|
||||
victory_review: "Tell us more!" # Only in old-style levels.
|
||||
|
|
|
@ -61,10 +61,10 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
achievements: "Достижения" # Tooltip on achievement list button from /play
|
||||
account: "Аккаунт" # Tooltip on account button from /play
|
||||
settings: "Настройки" # Tooltip on settings button from /play
|
||||
next: "Следующий" # Go from choose hero to choose inventory before playing a level
|
||||
next: "Выбрать" # Go from choose hero to choose inventory before playing a level
|
||||
change_hero: "Выбрать героя" # Go back from choose inventory to choose hero
|
||||
choose_inventory: "Выбрать предметы"
|
||||
older_campaigns: "Старые компании"
|
||||
older_campaigns: "Старые кампании"
|
||||
anonymous: "Неизвестный игрок"
|
||||
level_difficulty: "Сложность: "
|
||||
campaign_beginner: "Кампания для новичков"
|
||||
|
@ -72,7 +72,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
adventurer_prefix: "Вы можете зайти на любой из этих уровней, а также обсудить уровни на "
|
||||
adventurer_forum: "форуме Искателей приключений"
|
||||
adventurer_suffix: "."
|
||||
campaign_old_beginner: "Старые компании для новичков"
|
||||
campaign_old_beginner: "Старые кампании для новичков"
|
||||
campaign_old_beginner_description: "... в которой вы познакомитесь с магией программирования."
|
||||
campaign_dev: "Случайные уровни потруднее"
|
||||
campaign_dev_description: "... в которых вы изучите интерфейс и научитесь делать кое-что посложнее."
|
||||
|
@ -227,7 +227,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
tome_available_spells: "Доступные заклинания"
|
||||
tome_your_skills: "Ваши навыки"
|
||||
hud_continue: "Продолжить (Shift+Пробел)"
|
||||
# code_saved: "Code Saved"
|
||||
code_saved: "Код сохранен"
|
||||
skip_tutorial: "Пропуск (Esc)"
|
||||
keyboard_shortcuts: "Горячие клавиши"
|
||||
loading_ready: "Готово!"
|
||||
|
@ -279,12 +279,12 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
inventory_caption: "Оденьте своего героя"
|
||||
choose_hero_caption: "Выбор героя, языка"
|
||||
# save_load_caption: "... and view history"
|
||||
# options_caption: "Configure settings"
|
||||
options_caption: "Выбор настроек"
|
||||
# guide_caption: "Docs and tips"
|
||||
multiplayer_caption: "Играй с друзьями!"
|
||||
|
||||
# inventory:
|
||||
# choose_inventory: "Equip Items"
|
||||
inventory:
|
||||
choose_inventory: "Выбрать предметы"
|
||||
|
||||
choose_hero:
|
||||
choose_hero: "Выберите героя"
|
||||
|
@ -333,10 +333,10 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
why_paragraph_2_italic_caps: "НЕТ, МАМ, Я ДОЛЖЕН ПРОЙТИ УРОВЕНЬ!"
|
||||
why_paragraph_2_suffix: "Вот, почему CodeCombat - мультиплеерная игра, а не курс уроков в игровой форме. Мы не остановимся, пока вы не потеряете голову - в данном случае, это хорошо."
|
||||
why_paragraph_3: "Если вы собираетесь увлечься какой-нибудь игрой, увлекитесь этой и станьте одним из волшебников века информационных технологий."
|
||||
# press_title: "Bloggers/Press"
|
||||
# press_paragraph_1_prefix: "Want to write about us? Feel free to download and use all of the resources included in our"
|
||||
# press_paragraph_1_link: "press packet"
|
||||
# press_paragraph_1_suffix: ". All logos and images may be used without contacting us directly."
|
||||
press_title: "Блогерам/Прессе"
|
||||
press_paragraph_1_prefix: "Хотите написать о нас? Скачивайте и используйте все ресурсы, включенный в наш"
|
||||
press_paragraph_1_link: "пресс-пакет"
|
||||
press_paragraph_1_suffix: ". Все изображения могут быть использованы без предварительного уведомления."
|
||||
# team: "Team"
|
||||
# george_title: "CEO"
|
||||
# george_blurb: "Businesser"
|
||||
|
@ -494,7 +494,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
level_tab_thangs_add: "Добавить объект"
|
||||
delete: "Удалить"
|
||||
duplicate: "Дублировать"
|
||||
# rotate: "Rotate"
|
||||
rotate: "Повернуть"
|
||||
level_settings_title: "Настройки"
|
||||
level_component_tab_title: "Текущие компоненты"
|
||||
level_component_btn_new: "Создать новый компонент"
|
||||
|
|
|
@ -76,3 +76,15 @@ module.exports = class User extends CocoModel
|
|||
earnedHero: (heroOriginal) -> heroOriginal in (me.get('earned')?.heroes ? [])
|
||||
earnedItem: (itemOriginal) -> itemOriginal in (me.get('earned')?.items ? [])
|
||||
earnedLevel: (levelOriginal) -> levelOriginal in (me.get('earned')?.levels ? [])
|
||||
|
||||
getBranchingGroup: ->
|
||||
return @branchingGroup if @branchingGroup
|
||||
group = me.get('testGroupNumber') % 4
|
||||
@branchingGroup = switch group
|
||||
when 0 then 'no-practice'
|
||||
when 1 then 'all-practice'
|
||||
when 2 then 'choice-explicit'
|
||||
when 3 then 'choice-implicit'
|
||||
@branchingGroup = 'choice-explicit' if me.isAdmin()
|
||||
application.tracker.identify branchingGroup: @branchingGroup
|
||||
@branchingGroup
|
||||
|
|
|
@ -18,6 +18,12 @@ module.exports =
|
|||
'music-player:play-music': c.object {required: ['play']},
|
||||
play: {type: 'boolean'}
|
||||
file: {type: 'string'}
|
||||
delay: {type: 'integer', minimum: 0, format: 'milliseconds'}
|
||||
|
||||
'music-player:enter-menu': c.object {required: []},
|
||||
terrain: {type: 'string'}
|
||||
|
||||
'music-player:exit-menu': c.object {}
|
||||
|
||||
'modal:opened': c.object {}
|
||||
|
||||
|
|
|
@ -168,43 +168,49 @@
|
|||
|
||||
//- Footer
|
||||
|
||||
.modal-content
|
||||
padding-bottom: 50px // so the footer appears at the bottom
|
||||
|
||||
&.with-sign-up .modal-content
|
||||
padding-bottom: 100px // need more space for signup poke
|
||||
|
||||
.modal-footer
|
||||
position: absolute
|
||||
bottom: -20px
|
||||
left: 20px
|
||||
right: 20px
|
||||
padding-bottom: 0
|
||||
|
||||
#totals
|
||||
color: white
|
||||
|
||||
p.sign-up-poke
|
||||
position: absolute
|
||||
bottom: 60px
|
||||
right: 20px
|
||||
color: white
|
||||
|
||||
.sign-up-button
|
||||
float: right
|
||||
margin-left: 10px
|
||||
margin: 2px 10px
|
||||
|
||||
.ladder-submission-view
|
||||
display: inline-block
|
||||
#totals
|
||||
color: white
|
||||
|
||||
.rank-button.btn-block
|
||||
display: inline-block
|
||||
width: initial
|
||||
padding-left: 19px
|
||||
padding-right: 19px
|
||||
.next-level-buttons
|
||||
float: right
|
||||
|
||||
.last-submitted
|
||||
float: none
|
||||
.next-level-button
|
||||
display: block
|
||||
margin: 8px 10px
|
||||
width: 150px
|
||||
|
||||
.ladder-submission-view
|
||||
display: inline-block
|
||||
color: white
|
||||
|
||||
.rank-button.btn-block
|
||||
display: inline-block
|
||||
width: initial
|
||||
padding-left: 19px
|
||||
padding-right: 19px
|
||||
|
||||
.last-submitted
|
||||
float: none
|
||||
|
||||
.next-levels-prompt
|
||||
display: none
|
||||
margin: 30px -21px
|
||||
|
||||
.btn
|
||||
width: 30%
|
||||
width: -webkit-calc(33.333333% - 10px)
|
||||
width: calc(33.333333% - 10px)
|
||||
margin: 5px
|
||||
|
||||
|
||||
html.no-borderimage
|
||||
|
|
|
@ -41,8 +41,17 @@ block modal-body-content
|
|||
img(src=item.getPortraitURL())
|
||||
.reward-text= animate ? 'New Item' : item.get('name')
|
||||
|
||||
.next-levels-prompt
|
||||
for button in continueButtons
|
||||
- var enabled = Boolean(button.link != '/play' || me.getBranchingGroup() == 'choice-implicit' || button.key == 'continue');
|
||||
a.btn.btn-success.btn-lg.world-map-button.next-level-branch-button(href=button.link, disabled=!enabled, data-dismiss="modal", data-i18n="play_level.victory_play_" + button[me.getBranchingGroup()], data-branch-key=button.key)
|
||||
|
||||
block modal-footer-content
|
||||
if me.get('anonymous')
|
||||
p.sign-up-poke.hide
|
||||
button.btn.btn-success.sign-up-button.btn-large(data-toggle="coco-modal", data-target="modal/SignupModal", data-i18n="play_level.victory_sign_up") Sign Up to Save Progress
|
||||
span(data-i18n="play_level.victory_sign_up_poke") Want to save your code? Create a free account!
|
||||
|
||||
div#totals.pull-left
|
||||
span.spr Experience Gained:
|
||||
span#xp-total +0
|
||||
|
@ -52,15 +61,10 @@ block modal-footer-content
|
|||
|
||||
button.btn.btn-warning.hide#saving-progress-label(disabled, data-i18n="play_level.victory_saving_progress") Saving Progress
|
||||
|
||||
if readyToRank
|
||||
.ladder-submission-view
|
||||
else if level.get('type') === 'hero-ladder'
|
||||
a.btn.btn-primary(href="/play/ladder/#{level.get('slug')}#my-matches", data-dismiss="modal", data-i18n="play_level.victory_return_to_ladder") Return to Ladder
|
||||
else
|
||||
a.btn.btn-success.world-map-button.hide#continue-button(href="/play-hero", data-dismiss="modal", data-i18n="play_level.victory_play_continue") Continue
|
||||
|
||||
if me.get('anonymous')
|
||||
p.sign-up-poke.hide
|
||||
button.btn.btn-success.sign-up-button.btn-large(data-toggle="coco-modal", data-target="modal/SignupModal", data-i18n="play_level.victory_sign_up") Sign Up to Save Progress
|
||||
span(data-i18n="play_level.victory_sign_up_poke") Want to save your code? Create a free account!
|
||||
|
||||
.next-level-buttons
|
||||
if readyToRank
|
||||
.ladder-submission-view
|
||||
else if level.get('type') === 'hero-ladder'
|
||||
a.btn.btn-primary(href="/play/ladder/#{level.get('slug')}#my-matches", data-dismiss="modal", data-i18n="play_level.victory_return_to_ladder") Return to Ladder
|
||||
else
|
||||
button.btn.btn-success.world-map-button.next-level-button.hide#continue-button(data-i18n="play_level.victory_play_continue") Continue
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
.gradient.vertical-gradient.left-gradient
|
||||
img.map-background(src="/images/pages/play/map_" + mapType + ".jpg", alt="")
|
||||
|
||||
- var seenNext = false;
|
||||
- var seenNext = nextLevel;
|
||||
each campaign in campaigns
|
||||
each level in campaign.levels
|
||||
- var next = !seenNext && levelStatusMap[level.id] != "complete";
|
||||
if level.hidden
|
||||
continue;
|
||||
- var next = level.id == nextLevel || (!seenNext && levelStatusMap[level.id] != "complete" && !level.locked && !level.disabled);
|
||||
- seenNext = seenNext || next;
|
||||
div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{campaign.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name)
|
||||
div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{level.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name)
|
||||
a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled, data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
||||
div(style="left: #{level.x}%; bottom: #{level.y}%", class="level-shadow" + (next ? " next" : "") + " " + levelStatusMap[level.id] || "")
|
||||
.level-info-container(data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
||||
|
|
|
@ -52,7 +52,7 @@ module.exports = class ThangsTabView extends CocoView
|
|||
'click #thangs-palette-toggle': 'toggleThangsPalette'
|
||||
# 'click .add-thang-palette-icon': 'toggleThangsPalette'
|
||||
'click #rotation-menu-item button': 'onClickRotationButton'
|
||||
|
||||
|
||||
shortcuts:
|
||||
'esc': 'selectAddThang'
|
||||
'delete, del, backspace': 'deleteSelectedExtantThang'
|
||||
|
@ -501,7 +501,7 @@ module.exports = class ThangsTabView extends CocoView
|
|||
# foldered thangs -> array of thangs
|
||||
flattened = []
|
||||
for key, value of thangs
|
||||
if value.id and value.thangType
|
||||
if value.id? and value.thangType
|
||||
flattened.push value
|
||||
else
|
||||
flattened = flattened.concat @flattenThangs(value)
|
||||
|
@ -621,7 +621,7 @@ module.exports = class ThangsTabView extends CocoView
|
|||
$('#contextmenu').show()
|
||||
|
||||
#- Context menu callbacks
|
||||
|
||||
|
||||
onDeleteClicked: (e) ->
|
||||
$('#contextmenu').hide()
|
||||
@deleteSelectedExtantThang e
|
||||
|
@ -634,7 +634,7 @@ module.exports = class ThangsTabView extends CocoView
|
|||
$('#contextmenu').hide()
|
||||
rotation = parseFloat($(e.target).closest('button').data('rotation'))
|
||||
@rotateSelectedThangBy rotation * Math.PI
|
||||
|
||||
|
||||
rotateSelectedThangBy: (radians) ->
|
||||
return unless @selectedExtantThang
|
||||
@hush = true
|
||||
|
@ -645,7 +645,7 @@ module.exports = class ThangsTabView extends CocoView
|
|||
@thangsTreema.set(@pathForThang(thangData), thangData)
|
||||
@hush = false
|
||||
@onThangsChanged()
|
||||
|
||||
|
||||
toggleThangsContainer: (e) ->
|
||||
$('#all-thangs').toggleClass('hide')
|
||||
|
||||
|
@ -675,10 +675,10 @@ class ThangsFolderNode extends TreemaNode.nodeMap.object
|
|||
valEl.append(el)
|
||||
|
||||
countThangs: (data) ->
|
||||
return 0 if data.thangType and data.id
|
||||
return 0 if data.thangType and data.id?
|
||||
num = 0
|
||||
for key, value of data
|
||||
if value.thangType and value.id
|
||||
if value.thangType and value.id?
|
||||
num += 1
|
||||
else
|
||||
num += @countThangs(value)
|
||||
|
|
|
@ -24,6 +24,7 @@ module.exports = class GameMenuModal extends ModalView
|
|||
@options.showInventory = @options.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
|
||||
@options.levelID = @options.level.get('slug')
|
||||
@options.startingSessionHeroConfig = $.extend {}, true, (@options.session.get('heroConfig') ? {})
|
||||
Backbone.Mediator.publish 'music-player:enter-menu', terrain: @options.level.get('terrain', true)
|
||||
|
||||
getRenderData: (context={}) ->
|
||||
context = super(context)
|
||||
|
@ -50,6 +51,7 @@ module.exports = class GameMenuModal extends ModalView
|
|||
patchingMe = @updateConfig()
|
||||
me.patch() unless patchingMe # Might need to patch for options menu, too
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-close', volume: 1
|
||||
Backbone.Mediator.publish 'music-player:exit-menu', {}
|
||||
|
||||
updateConfig: ->
|
||||
sessionHeroConfig = @options.startingSessionHeroConfig
|
||||
|
|
|
@ -32,6 +32,7 @@ module.exports = class WorldMapView extends RootView
|
|||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
@nextLevel = @getQueryVariable 'next'
|
||||
@levelStatusMap = {}
|
||||
@levelPlayCountMap = {}
|
||||
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', null, 0).model
|
||||
|
@ -83,11 +84,17 @@ module.exports = class WorldMapView extends RootView
|
|||
level.x ?= 10 + 80 * Math.random()
|
||||
level.y ?= 10 + 80 * Math.random()
|
||||
level.locked = index > 0 and not me.earnedLevel level.original
|
||||
level.locked = false if window.levelUnlocksNotWorking
|
||||
window.levelUnlocksNotWorking = true if level.locked and level.id is @nextLevel # Temporary
|
||||
level.locked = false if window.levelUnlocksNotWorking # Temporary; also possible in HeroVictoryModal
|
||||
level.color = campaign.color
|
||||
if level.practice
|
||||
level.color = 'rgb(80, 130, 200)' unless me.getBranchingGroup() is 'all-practice'
|
||||
level.hidden = true if me.getBranchingGroup() is 'no-practice'
|
||||
context.levelStatusMap = @levelStatusMap
|
||||
context.levelPlayCountMap = @levelPlayCountMap
|
||||
context.isIPadApp = application.isIPadApp
|
||||
context.mapType = _.string.slugify @terrain
|
||||
context.nextLevel = @nextLevel
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
|
@ -599,6 +606,9 @@ hero = [
|
|||
description: 'Grab the gem, but touch nothing else. Start here.'
|
||||
x: 14
|
||||
y: 15.5
|
||||
nextLevels:
|
||||
continue: 'gems-in-the-deep'
|
||||
skip_ahead: 'shadow-guard'
|
||||
}
|
||||
{
|
||||
name: 'Gems in the Deep'
|
||||
|
@ -609,6 +619,24 @@ hero = [
|
|||
description: 'Quickly collect the gems; you will need them.'
|
||||
x: 32
|
||||
y: 15.5
|
||||
nextLevels:
|
||||
more_practice: 'gem-grabber'
|
||||
continue: 'shadow-guard'
|
||||
skip_ahead: 'true-names'
|
||||
}
|
||||
{
|
||||
name: 'Gem Grabber'
|
||||
type: 'hero'
|
||||
difficulty: 1
|
||||
id: 'gem-grabber'
|
||||
original: '54174347844506ae0195a0b8'
|
||||
description: 'Grab even more gems as you practice moving.'
|
||||
x: 35.49
|
||||
y: 24.61
|
||||
nextLevels:
|
||||
continue: 'shadow-guard'
|
||||
practice: true
|
||||
disabled: true
|
||||
}
|
||||
{
|
||||
name: 'Shadow Guard'
|
||||
|
@ -619,6 +647,23 @@ hero = [
|
|||
description: 'Evade the Kithgard minion.'
|
||||
x: 54
|
||||
y: 9
|
||||
nextLevels:
|
||||
more_practice: 'munchkin-dodger'
|
||||
continue: 'true-names'
|
||||
}
|
||||
{
|
||||
name: 'Munchkin Dodger'
|
||||
type: 'hero'
|
||||
difficulty: 1
|
||||
id: 'munchkin-dodger'
|
||||
original: '541875da4c16460000ab990f'
|
||||
description: 'Practice your evasion skills with more guards.'
|
||||
x: 61.19
|
||||
y: 13.80
|
||||
nextLevels:
|
||||
continue: 'true-names'
|
||||
practice: true
|
||||
disabled: true
|
||||
}
|
||||
{
|
||||
name: 'True Names'
|
||||
|
@ -629,6 +674,23 @@ hero = [
|
|||
description: 'Learn an enemy\'s true name to defeat it.'
|
||||
x: 74
|
||||
y: 12
|
||||
nextLevels:
|
||||
more_practice: 'munchkin-slayer'
|
||||
continue: 'the-raised-sword'
|
||||
}
|
||||
{
|
||||
name: 'Munchkin Slayer'
|
||||
type: 'hero'
|
||||
difficulty: 1
|
||||
id: 'munchkin-slayer'
|
||||
original: '5418aec24c16460000ab9aa6'
|
||||
description: 'Test out your battle skills by defeating more munchkins.'
|
||||
x: 80.85
|
||||
y: 11.85
|
||||
nextLevels:
|
||||
continue: 'the-raised-sword'
|
||||
practice: true
|
||||
disabled: true
|
||||
}
|
||||
{
|
||||
name: 'The Raised Sword'
|
||||
|
@ -639,6 +701,8 @@ hero = [
|
|||
description: 'Learn to equip yourself for combat.'
|
||||
x: 85
|
||||
y: 20
|
||||
nextLevels:
|
||||
continue: 'the-first-kithmaze'
|
||||
}
|
||||
{
|
||||
name: 'The First Kithmaze'
|
||||
|
@ -649,6 +713,24 @@ hero = [
|
|||
description: 'The builders of Kith constructed many mazes to confuse travelers.'
|
||||
x: 70
|
||||
y: 28
|
||||
nextLevels:
|
||||
more_practice: 'the-one-point-fifth-kithmaze'
|
||||
continue: 'the-second-kithmaze'
|
||||
skip_ahead: 'new-sight'
|
||||
}
|
||||
{
|
||||
name: 'The One-Point-Fifth Kithmaze'
|
||||
type: 'hero'
|
||||
difficulty: 1
|
||||
id: 'the-one-point-fifth-kithmaze'
|
||||
original: '5418cf256bae62f707c7e1c3'
|
||||
description: 'Another day, another maze.'
|
||||
x: 78.47
|
||||
y: 34.38
|
||||
nextLevels:
|
||||
continue: 'the-second-kithmaze'
|
||||
practice: true
|
||||
disabled: true
|
||||
}
|
||||
{
|
||||
name: 'The Second Kithmaze'
|
||||
|
@ -659,6 +741,23 @@ hero = [
|
|||
description: 'Many have tried, few have found their way through this maze.'
|
||||
x: 55.54
|
||||
y: 26.96
|
||||
nextLevels:
|
||||
more_practice: 'the-two-point-fifth-kithmaze'
|
||||
continue: 'new-sight'
|
||||
}
|
||||
{
|
||||
name: 'The Two-Point-Fifth Kithmaze'
|
||||
type: 'hero'
|
||||
difficulty: 1
|
||||
id: 'the-two-point-fifth-kithmaze'
|
||||
original: '5418d40f4c16460000ab9ac2'
|
||||
description: 'You must really like doing these mazes!'
|
||||
x: 49.02
|
||||
y: 25.78
|
||||
nextLevels:
|
||||
continue: 'new-sight'
|
||||
practice: true
|
||||
disabled: true
|
||||
}
|
||||
{
|
||||
name: 'New Sight'
|
||||
|
@ -669,6 +768,8 @@ hero = [
|
|||
description: 'A true name can only be seen with the correct lenses.'
|
||||
x: 67
|
||||
y: 41
|
||||
nextLevels:
|
||||
continue: 'lowly-kithmen'
|
||||
}
|
||||
{
|
||||
name: 'Lowly Kithmen'
|
||||
|
@ -679,6 +780,24 @@ hero = [
|
|||
description: 'Use your glasses to seek out and attack the Kithmen.'
|
||||
x: 74
|
||||
y: 48
|
||||
nextLevels:
|
||||
more_practice: 'still-pretty-low-kithmen'
|
||||
continue: 'closing-the-distance'
|
||||
skip_ahead: 'the-final-kithmaze'
|
||||
}
|
||||
{
|
||||
name: 'Still-Pretty-Low Kithmen'
|
||||
type: 'hero'
|
||||
difficulty: 1
|
||||
id: 'still-pretty-low-kithmen'
|
||||
original: '541b288e1ccc8eaae19f3c25'
|
||||
description: 'Now that you can see them, they\'re everywhere!'
|
||||
x: 80.17
|
||||
y: 45.31
|
||||
nextLevels:
|
||||
continue: 'closing-the-distance'
|
||||
practice: true
|
||||
disabled: true
|
||||
}
|
||||
{
|
||||
name: 'Closing the Distance'
|
||||
|
@ -689,6 +808,23 @@ hero = [
|
|||
description: 'Kithmen are not the only ones to stand in your way.'
|
||||
x: 76
|
||||
y: 60
|
||||
nextLevels:
|
||||
more_practice: 'assassinating-more-kithmen'
|
||||
continue: 'the-final-kithmaze'
|
||||
}
|
||||
{
|
||||
name: 'Assassinating More Kithmen'
|
||||
type: 'hero'
|
||||
difficulty: 1
|
||||
id: 'assassinating-more-kithmen'
|
||||
original: '541b434e1ccc8eaae19f3c33'
|
||||
description: 'They\'re, uh, coming right for us?'
|
||||
x: 80.34
|
||||
y: 55.60
|
||||
nextLevels:
|
||||
continue: 'the-final-kithmaze'
|
||||
practice: true
|
||||
disabled: true
|
||||
}
|
||||
{
|
||||
name: 'The Final Kithmaze'
|
||||
|
@ -699,6 +835,23 @@ hero = [
|
|||
description: 'To escape you must find your way through an Elder Kithman\'s maze.'
|
||||
x: 82
|
||||
y: 70
|
||||
nextLevels:
|
||||
more_practice: 'okay-one-more-kithmaze'
|
||||
continue: 'kithgard-gates'
|
||||
}
|
||||
{
|
||||
name: 'Okay, One More Kithmaze'
|
||||
type: 'hero'
|
||||
difficulty: 1
|
||||
id: 'okay-one-more-kithmaze'
|
||||
original: '541c9a30c6362edfb0f34479'
|
||||
description: 'But you really gotta get outside after this, okay?'
|
||||
x: 76.94
|
||||
y: 74.22
|
||||
nextLevels:
|
||||
continue: 'kithgard-gates'
|
||||
practice: true
|
||||
disabled: true
|
||||
}
|
||||
{
|
||||
name: 'Kithgard Gates'
|
||||
|
@ -707,9 +860,10 @@ hero = [
|
|||
id: 'kithgard-gates'
|
||||
original: '541c9a30c6362edfb0f34479'
|
||||
description: 'Escape the Kithgard dungeons and don\'t let the guardians get you.'
|
||||
disabled: true
|
||||
x: 89
|
||||
y: 82
|
||||
nextLevels:
|
||||
continue: 'defence-of-plainswood'
|
||||
}
|
||||
{
|
||||
name: 'Defence of Plainswood'
|
||||
|
@ -718,7 +872,6 @@ hero = [
|
|||
id: 'defence-of-plainswood'
|
||||
original: '541b67f71ccc8eaae19f3c62'
|
||||
description: 'Protect the peasants from the pursuing ogres.'
|
||||
disabled: true
|
||||
x: 95.31
|
||||
y: 88.26
|
||||
}
|
||||
|
@ -781,7 +934,7 @@ hero = [
|
|||
|
||||
]
|
||||
|
||||
campaigns = [
|
||||
WorldMapView.campaigns = campaigns = [
|
||||
#{id: 'beginner', name: 'Beginner Campaign', description: '... in which you learn the wizardry of programming.', levels: tutorials, color: "rgb(255, 80, 60)"}
|
||||
#{id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas, color: "rgb(80, 5, 60)"}
|
||||
#{id: 'dev', name: 'Random Harder Levels', description: '... in which you learn the interface while doing something a little harder.', levels: experienced, color: "rgb(80, 60, 255)"}
|
||||
|
|
|
@ -18,6 +18,10 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
subscriptions:
|
||||
'ladder:game-submitted': 'onGameSubmitted'
|
||||
|
||||
events:
|
||||
'click #continue-button': 'onClickContinue'
|
||||
'click .next-level-branch-button': 'onClickNextLevelBranch'
|
||||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
@session = options.session
|
||||
|
@ -33,6 +37,14 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
@waitingToContinueSince = new Date()
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'victory'
|
||||
|
||||
destroy: ->
|
||||
clearInterval @sequentialAnimationInterval
|
||||
super()
|
||||
|
||||
onHidden: ->
|
||||
Backbone.Mediator.publish 'music-player:exit-menu', {}
|
||||
super()
|
||||
|
||||
onAchievementsLoaded: ->
|
||||
thangTypeOriginals = []
|
||||
achievementIDs = []
|
||||
|
@ -102,13 +114,20 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
c.me = me
|
||||
c.readyToRank = @level.get('type', true) is 'hero-ladder' and @session.readyToRank()
|
||||
c.level = @level
|
||||
@continueLevelLink = @getNextLevelLink 'continue'
|
||||
@morePracticeLevelLink = me.isAdmin() and @getNextLevelLink 'more_practice'
|
||||
@skipAheadLevelLink = me.isAdmin() and @getNextLevelLink 'skip_ahead'
|
||||
c.continueButtons = [
|
||||
{key: 'skip_ahead', link: @skipAheadLevelLink, 'choice-explicit': 'skip', 'choice-implicit': 'too_easy'}
|
||||
{key: 'continue', link: @continueLevelLink, 'choice-explicit': 'next_level', 'choice-implicit': 'just_right'}
|
||||
{key: 'more_practice', link: @morePracticeLevelLink, 'choice-explicit': 'more_practice', 'choice-implicit': 'too_hard'}
|
||||
]
|
||||
return c
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
return unless @supermodel.finished()
|
||||
@playSelectionSound hero, true for original, hero of @thangTypes # Preload them
|
||||
@$el.addClass 'with-sign-up' if me.get('anonymous')
|
||||
@updateSavingProgressStatus()
|
||||
@$el.find('#victory-header').delay(250).queue(->
|
||||
$(@).removeClass('out').dequeue()
|
||||
|
@ -134,6 +153,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
panel.delay(500)
|
||||
panel.queue(-> complete())
|
||||
@animationComplete = not @animatedPanels.length
|
||||
complete() if @animationComplete
|
||||
if @level.get('type', true) is 'hero-ladder'
|
||||
@ladderSubmissionView = new LadderSubmissionView session: @session, level: @level
|
||||
@insertSubView @ladderSubmissionView, @$el.find('.ladder-submission-view')
|
||||
|
@ -216,16 +236,18 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
clearInterval @sequentialAnimationInterval
|
||||
@animationComplete = true
|
||||
@updateSavingProgressStatus()
|
||||
Backbone.Mediator.publish 'music-player:enter-menu', terrain: @level.get('terrain', true)
|
||||
|
||||
updateSavingProgressStatus: ->
|
||||
return unless @animationComplete
|
||||
@$el.find('#saving-progress-label').toggleClass('hide', @readyToContinue)
|
||||
@$el.find('#continue-button').toggleClass('hide', not @readyToContinue)
|
||||
@$el.find('.next-level-button').toggleClass('hide', not @readyToContinue)
|
||||
@$el.find('.sign-up-poke').toggleClass('hide', not @readyToContinue)
|
||||
|
||||
onGameSubmitted: (e) ->
|
||||
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches"
|
||||
Backbone.Mediator.publish 'router:navigate', route: ladderURL
|
||||
# Preserve the supermodel as we navigate back to the ladder.
|
||||
Backbone.Mediator.publish 'router:navigate', route: ladderURL, viewClass: require('views/play/ladder/LadderView'), viewArgs: [{supermodel: @supermodel}]
|
||||
|
||||
playSelectionSound: (hero, preload=false) ->
|
||||
return unless sounds = hero.get('soundTriggers')?.selected
|
||||
|
@ -236,8 +258,36 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
else
|
||||
AudioPlayer.playSound name, 1
|
||||
|
||||
# TODO: award heroes/items and play an awesome sound when you get one
|
||||
# Branching group testing
|
||||
|
||||
destroy: ->
|
||||
clearInterval @sequentialAnimationInterval
|
||||
super()
|
||||
getNextLevel: (type) ->
|
||||
for campaign in require('views/play/WorldMapView').campaigns
|
||||
break if levelInfo
|
||||
for level in campaign.levels
|
||||
if level.id is @level.get 'slug'
|
||||
levelInfo = level
|
||||
break
|
||||
levelInfo?.nextLevels?[type] # 'more_practice', 'skip_ahead', 'continue'
|
||||
|
||||
getNextLevelLink: (type) ->
|
||||
return '/play' unless nextLevel = @getNextLevel type
|
||||
"play?next=#{nextLevel}"
|
||||
|
||||
onClickContinue: (e) ->
|
||||
nextLevelLink = @continueLevelLink
|
||||
if me.getBranchingGroup() is 'all-practice' and @morePracticeLevelLink
|
||||
nextLevelLink = @morePracticeLevelLink
|
||||
skipPrompt = me.getBranchingGroup() in ['no-practice', 'all-practice']
|
||||
skipPrompt ||= not (@skipAheadLevelLink or @morePractiveLevelLink) and me.getBranchingGroup() is 'choice-explicit'
|
||||
if skipPrompt
|
||||
# Preserve the supermodel as we navigate back to the world map.
|
||||
Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: @supermodel}]
|
||||
else
|
||||
# Hide everything except the buttons prompting them for which kind of next level to do
|
||||
@$el.find('.modal-footer, .modal-body > *').hide()
|
||||
@$el.find('.next-levels-prompt').show()
|
||||
|
||||
onClickNextLevelBranch: (e) ->
|
||||
application.tracker?.trackEvent 'Branch Selected', level: @level.get('slug'), label: @level.get('slug'), branch: $(e.target).data('branch-key'), branchingGroup: me.getBranchingGroup()
|
||||
# Preserve the supermodel as we navigate back to world map.
|
||||
Backbone.Mediator.publish 'router:navigate', route: '/play', viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: @supermodel}]
|
||||
|
|
|
@ -47,7 +47,7 @@ module.exports = class SpellPaletteEntryView extends CocoView
|
|||
).on 'show.bs.popover', =>
|
||||
Backbone.Mediator.publish 'tome:palette-hovered', thang: @thang, prop: @doc.name, entry: @
|
||||
soundIndex = Math.floor(Math.random() * 4)
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: "spell-palette-entry-open-{soundIndex}", volume: 0.75
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: "spell-palette-entry-open-#{soundIndex}", volume: 0.75
|
||||
|
||||
onMouseEnter: (e) ->
|
||||
# Make sure the doc has the updated Thang so it can regenerate its prop value
|
||||
|
|
|
@ -75,7 +75,7 @@ module.exports = class SpellPaletteView extends CocoView
|
|||
columns = ({items: [], nEntries: 0} for i in [0 ... nColumns])
|
||||
nRows = 0
|
||||
for group, entries of @entryGroups
|
||||
shortestColumn = _.sortBy(columns, (column) -> column.nEntries)[0]
|
||||
continue unless shortestColumn = _.sortBy(columns, (column) -> column.nEntries)[0]
|
||||
shortestColumn.nEntries += Math.max 2, entries.length
|
||||
shortestColumn.items.push @entryGroupElements[group]
|
||||
nRows = Math.max nRows, shortestColumn.nEntries
|
||||
|
|
|
@ -197,13 +197,15 @@ module.exports = class SpellView extends CocoView
|
|||
@zatanna = new Zatanna @ace,
|
||||
basic: false
|
||||
liveCompletion: false
|
||||
snippets: @autocomplete
|
||||
snippetsLangDefaults: false
|
||||
completers:
|
||||
keywords: false
|
||||
text: false
|
||||
snippets: @autocomplete
|
||||
text: @autocomplete
|
||||
autoLineEndings:
|
||||
javascript: ';'
|
||||
popupFontSizePx: 16
|
||||
popupWidthPx: 380
|
||||
|
||||
updateAutocomplete: (@autocomplete) ->
|
||||
@zatanna?.set 'snippets', @autocomplete
|
||||
|
@ -639,7 +641,7 @@ module.exports = class SpellView extends CocoView
|
|||
# TODO: move this whole thing into SpellDebugView or somewhere?
|
||||
@highlightComments() unless @destroyed
|
||||
flow ?= @spellThang?.castAether?.flow
|
||||
return unless flow
|
||||
return unless flow and @thang
|
||||
executed = []
|
||||
executedRows = {}
|
||||
matched = false
|
||||
|
|
|
@ -32,7 +32,7 @@ setupExpressMiddleware = (app) ->
|
|||
express.logger.format('prod', productionLogging)
|
||||
app.use(express.logger('prod'))
|
||||
app.use express.compress filter: (req, res) ->
|
||||
return false if req.headers.host is 'codecombat.com' # Cloudflare will gzip it for us on codecombat.com
|
||||
#return false if req.headers.host is 'codecombat.com' # CloudFlare will gzip it for us on codecombat.com # But now it's disabled.
|
||||
compressible res.getHeader('Content-Type')
|
||||
else
|
||||
app.use(express.logger('dev'))
|
||||
|
|
Loading…
Reference in a new issue