mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 08:08:15 -05:00
Merge branch 'master' of https://github.com/codecombat/codecombat
This commit is contained in:
commit
50b4e79c7b
25 changed files with 329 additions and 82 deletions
|
@ -40,12 +40,17 @@ module.exports = class SpriteParser
|
|||
if movieClips.length
|
||||
# First movie clip is root, so do it last
|
||||
movieClips = movieClips[1 ... movieClips.length].concat([movieClips[0]])
|
||||
else if containers.length
|
||||
# First container is root, so do it last
|
||||
containers = containers[1 ... containers.length].concat([containers[0]])
|
||||
|
||||
# first container isn't necessarily root... actually the last one is root in blue-cart
|
||||
# else if containers.length
|
||||
# # First container is root, so do it last
|
||||
# containers = containers[1 ... containers.length].concat([containers[0]])
|
||||
mainClip = _.last(movieClips) ? _.last(containers)
|
||||
@animationName = mainClip.name
|
||||
for container in containers
|
||||
for container, index in containers
|
||||
if index is containers.length - 1 and not movieClips.length and container.bounds?.length
|
||||
container.bounds[0] -= @width / 2
|
||||
container.bounds[1] -= @height / 2
|
||||
[shapeKeys, localShapes] = @getShapesFromBlock container, source
|
||||
localContainers = @getContainersFromMovieClip container, source
|
||||
addChildArgs = @getAddChildCallArguments container, source
|
||||
|
@ -62,6 +67,7 @@ module.exports = class SpriteParser
|
|||
if c.bn is bn
|
||||
instructions.push {t: c.t, gn: c.gn}
|
||||
break
|
||||
continue unless container.bounds and instructions.length
|
||||
@addContainer {c: instructions, b: container.bounds}, container.name
|
||||
for movieClip, index in movieClips
|
||||
if index is 0
|
||||
|
|
|
@ -240,7 +240,6 @@ module.exports = class ThangType extends CocoModel
|
|||
return if _.isString spriteSheet
|
||||
return unless spriteSheet
|
||||
canvas = $("<canvas width='#{size}' height='#{size}'></canvas>")
|
||||
console.log 'made canvas', canvas, 'with size', size unless canvas[0]
|
||||
stage = new createjs.Stage(canvas[0])
|
||||
sprite = new createjs.Sprite(spriteSheet)
|
||||
pt = @actions.portrait?.positions?.registration
|
||||
|
@ -261,6 +260,29 @@ module.exports = class ThangType extends CocoModel
|
|||
createjs.Ticker.removeEventListener 'tick', @tick
|
||||
@tick = null
|
||||
stage
|
||||
|
||||
getVectorPortraitStage: (size=100) ->
|
||||
return unless @actions
|
||||
canvas = $("<canvas width='#{size}' height='#{size}'></canvas>")
|
||||
stage = new createjs.Stage(canvas[0])
|
||||
portrait = @actions.portrait
|
||||
return unless portrait and (portrait.animation or portrait.container)
|
||||
scale = portrait.scale or 1
|
||||
|
||||
vectorParser = new SpriteBuilder(@, {})
|
||||
if portrait.animation
|
||||
sprite = vectorParser.buildMovieClip portrait.animation
|
||||
sprite.gotoAndStop(0)
|
||||
else if portrait.container
|
||||
sprite = vectorParser.buildContainerFromStore(portrait.container)
|
||||
|
||||
pt = portrait.positions?.registration
|
||||
sprite.regX = pt?.x or 0
|
||||
sprite.regY = pt?.y or 0
|
||||
sprite.scaleX = sprite.scaleY = scale * size / 100
|
||||
stage.addChild(sprite)
|
||||
stage.update()
|
||||
stage
|
||||
|
||||
uploadGenericPortrait: (callback, src) ->
|
||||
src ?= @getPortraitSource()
|
||||
|
@ -339,9 +361,10 @@ module.exports = class ThangType extends CocoModel
|
|||
unless itemConfig = _.find(components, original: LevelComponent.ItemID)?.config
|
||||
console.warn @get('name'), 'is not an item, but you are asking for its stats.'
|
||||
return props: [], stats: {}
|
||||
stats = {}
|
||||
props = itemConfig.programmableProperties ? []
|
||||
props = props.concat itemConfig.moreProgrammableProperties ? []
|
||||
stats = {}
|
||||
props = _.without props, 'canCast', 'spellNames', 'spells'
|
||||
for stat, modifiers of itemConfig.stats ? {}
|
||||
stats[stat] = @formatStatDisplay stat, modifiers
|
||||
for stat in itemConfig.extraHUDProperties ? []
|
||||
|
|
|
@ -224,6 +224,7 @@ LevelSchema = c.object {
|
|||
c.extendNamedProperties LevelSchema # let's have the name be the first property
|
||||
_.extend LevelSchema.properties,
|
||||
description: {title: 'Description', description: 'A short explanation of what this level is about.', type: 'string', maxLength: 65536, format: 'markdown'}
|
||||
loadingTip: { type: 'string', title: 'Loading Tip', description: 'What to show for this level while it\'s loading.' }
|
||||
documentation: c.object {title: 'Documentation', description: 'Documentation articles relating to this level.', required: ['specificArticles', 'generalArticles'], 'default': {specificArticles: [], generalArticles: []}},
|
||||
specificArticles: c.array {title: 'Specific Articles', description: 'Specific documentation articles that live only in this level.', uniqueItems: true }, SpecificArticleSchema
|
||||
generalArticles: c.array {title: 'General Articles', description: 'General documentation articles that can be linked from multiple levels.', uniqueItems: true}, GeneralArticleSchema
|
||||
|
@ -243,7 +244,7 @@ _.extend LevelSchema.properties,
|
|||
body: {type: 'string', format: 'markdown', title: 'Body Text', description: 'Inserted into the Victory Modal once this level is complete. Tell the player they did a good job and what they accomplished!'},
|
||||
i18n: {type: 'object', format: 'i18n', props: ['body'], description: 'Help translate this victory message'}
|
||||
}
|
||||
i18n: {type: 'object', format: 'i18n', props: ['name', 'description'], description: 'Help translate this level'}
|
||||
i18n: {type: 'object', format: 'i18n', props: ['name', 'description', 'loadingTip'], description: 'Help translate this level'}
|
||||
icon: {type: 'string', format: 'image-file', title: 'Icon'}
|
||||
banner: {type: 'string', format: 'image-file', title: 'Banner'}
|
||||
goals: c.array {title: 'Goals', description: 'An array of goals which are visible to the player and can trigger scripts.'}, GoalSchema
|
||||
|
|
|
@ -130,6 +130,7 @@ _.extend ThangTypeSchema.properties,
|
|||
positions: PositionsSchema
|
||||
raster: {type: 'string', format: 'image-file', title: 'Raster Image'}
|
||||
rasterIcon: { type: 'string', format: 'image-file', title: 'Raster Image Icon' }
|
||||
containerIcon: { type: 'string' }
|
||||
featureImage: { type: 'string', format: 'image-file', title: 'Feature Image' }
|
||||
colorGroups: c.object
|
||||
title: 'Color Groups'
|
||||
|
|
|
@ -4,9 +4,8 @@ module.exports =
|
|||
'ipad:products': c.object {required: ['products']},
|
||||
products: c.array {},
|
||||
c.object {},
|
||||
gems: { type: 'integer' }
|
||||
price: { type: 'string' }
|
||||
id: { type: 'string' }
|
||||
|
||||
'ipad:iap-complete': c.object {},
|
||||
gems: { type: 'integer' }
|
||||
productID: { type: 'string' }
|
||||
|
|
9
app/styles/editor/thang/vector-icon-setup-modal.sass
Normal file
9
app/styles/editor/thang/vector-icon-setup-modal.sass
Normal file
|
@ -0,0 +1,9 @@
|
|||
#vector-icon-setup-modal
|
||||
select
|
||||
margin-bottom: 20px
|
||||
|
||||
canvas
|
||||
background: lightblue
|
||||
border: 3px solid black
|
||||
margin: 20px auto
|
||||
display: block
|
|
@ -194,6 +194,26 @@ $gameControlMargin: 30px
|
|||
display: block
|
||||
margin: 10px auto 0 auto
|
||||
width: 200px
|
||||
|
||||
.campaign-switch
|
||||
color: purple
|
||||
position: absolute
|
||||
z-index: 1
|
||||
font-size: 2vw
|
||||
text-shadow: 0 0 0.3vw white, 0 0 0.3vw white
|
||||
|
||||
&:hover
|
||||
text-decoration: none
|
||||
|
||||
&#forest-link
|
||||
left: 94.5%
|
||||
top: 7%
|
||||
transform: rotate(-35deg)
|
||||
|
||||
&#dungeon-link
|
||||
left: 26.6%
|
||||
top: 43%
|
||||
transform: rotate(180deg)
|
||||
|
||||
.game-controls
|
||||
position: absolute
|
||||
|
|
|
@ -81,12 +81,15 @@ block outer_content
|
|||
div#settings-col.well
|
||||
img#portrait.img-thumbnail
|
||||
div.file-controls
|
||||
button(disabled=authorized === true ? undefined : "true").btn.btn-small.btn-info#upload-button
|
||||
button(disabled=authorized === true ? undefined : "true").btn.btn-sm.btn-info#upload-button
|
||||
span.glyphicon.glyphicon-upload
|
||||
span.spl Upload Animation
|
||||
button(disabled=authorized === true ? undefined : "true").btn.btn-small.btn-danger#clear-button
|
||||
button(disabled=authorized === true ? undefined : "true").btn.btn-sm.btn-danger#clear-button
|
||||
span.glyphicon.glyphicon-remove
|
||||
span.spl Clear Data
|
||||
button#set-vector-icon(disabled=authorized === true ? undefined : "true").btn.btn-sm
|
||||
span.glyphicon.glyphicon-gbp
|
||||
span.spl Vector Icon Setup
|
||||
input#real-upload-button(type="file")
|
||||
#thang-type-file-size= fileSizeString
|
||||
div#thang-type-treema
|
||||
|
|
24
app/templates/editor/thang/vector-icon-setup-modal.jade
Normal file
24
app/templates/editor/thang/vector-icon-setup-modal.jade
Normal file
|
@ -0,0 +1,24 @@
|
|||
extends /templates/modal/modal_base
|
||||
|
||||
block modal-header-content
|
||||
h3 Choose Container for Vector Icon
|
||||
|
||||
block modal-body-content
|
||||
if chosenContainer
|
||||
form.form
|
||||
.form-group
|
||||
select#container-select.form-control
|
||||
for container in containers
|
||||
option(value=container, selected=container === chosenContainer)= container
|
||||
|
||||
canvas(width=demoSize height=demoSize)#resulting-icon
|
||||
|
||||
.alert.alert-info Arrow keys to move, Shift-Plus/Minus to scale.
|
||||
else
|
||||
div forgetting something?
|
||||
|
||||
block modal-footer-content
|
||||
button.btn.pull-left.btn-info#center
|
||||
span.glyphicon.glyphicon-cutlery
|
||||
span.spl Center
|
||||
button.btn.btn-primary#done-button Done
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
if props.length
|
||||
#skills
|
||||
h3.big-font(data-i18n="play.skills-granted")
|
||||
h3.big-font(data-i18n="play.skills_granted")
|
||||
for prop in props
|
||||
p
|
||||
strong.big-font= prop.name
|
||||
|
|
|
@ -35,12 +35,17 @@
|
|||
.campaign-label(style="color: #{campaign.color}")= campaign.name
|
||||
if isIPadApp && !level.disabled && !level.locked
|
||||
button.btn.btn-success.btn-lg.start-level(data-i18n="common.play") Play
|
||||
if mapType === 'dungeon' && forestIsAvailable
|
||||
a#forest-link.glyphicon.glyphicon-share-alt.campaign-switch(href="/play/forest", data-i18n="[title]play.campaign_forest")
|
||||
if mapType === 'forest'
|
||||
a#dungeon-link.glyphicon.glyphicon-share-alt.campaign-switch(href="/play/dungeon", data-i18n="[title]play.campaign_dungeon")
|
||||
|
||||
.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")
|
||||
if me.isAdmin()
|
||||
if me.isAdmin() || isIPadApp
|
||||
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")
|
||||
|
@ -52,13 +57,6 @@
|
|||
// a.btn.account(href="/user/#{me.getSlugOrID()}", data-i18n="[title]play.account")
|
||||
// a.btn.settings(href='/account', data-i18n="[title]play.settings")
|
||||
|
||||
if mapType === 'forest'
|
||||
a.btn.campaign-switch(href="/play/dungeon", data-i18n="[title]play.campaign_dungeon")
|
||||
img(src="/images/pages/play/map_dungeon_icon.jpg").img-thumbnail
|
||||
if mapType === 'dungeon'
|
||||
a.btn.campaign-switch(href="/play/forest", data-i18n="[title]play.campaign_forest")
|
||||
img(src="/images/pages/play/map_forest_icon.jpg").img-thumbnail
|
||||
|
||||
.old-levels
|
||||
a(href="/play-old", data-i18n="play.older_campaigns").header-font Older Campaigns
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = class SettingsTabView extends CocoView
|
|||
# not thangs or scripts or the backend stuff
|
||||
editableSettings: [
|
||||
'name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals',
|
||||
'type', 'terrain', 'showsGuide', 'banner', 'employerDescription'
|
||||
'type', 'terrain', 'showsGuide', 'banner', 'employerDescription', 'loadingTip'
|
||||
]
|
||||
|
||||
subscriptions:
|
||||
|
|
|
@ -12,6 +12,7 @@ ThangTypeVersionsModal = require './ThangTypeVersionsModal'
|
|||
ThangTypeColorsTabView = require './ThangTypeColorsTabView'
|
||||
PatchesView = require 'views/editor/PatchesView'
|
||||
ForkModal = require 'views/editor/ForkModal'
|
||||
VectorIconSetupModal = require 'views/editor/thang/VectorIconSetupModal'
|
||||
SaveVersionModal = require 'views/modal/SaveVersionModal'
|
||||
template = require 'templates/editor/thang/thang-type-edit-view'
|
||||
storage = require 'lib/storage'
|
||||
|
@ -33,6 +34,7 @@ module.exports = class ThangTypeEditView extends RootView
|
|||
events:
|
||||
'click #clear-button': 'clearRawData'
|
||||
'click #upload-button': -> @$el.find('input#real-upload-button').click()
|
||||
'click #set-vector-icon': 'onClickSetVectorIcon'
|
||||
'change #real-upload-button': 'animationFileChosen'
|
||||
'change #animations-select': 'showAnimation'
|
||||
'click #marker-button': 'toggleDots'
|
||||
|
@ -47,6 +49,12 @@ module.exports = class ThangTypeEditView extends RootView
|
|||
'keyup .play-with-level-input': 'onPlayLevelKeyUp'
|
||||
'click #pop-level-i18n-button': 'onPopulateLevelI18N'
|
||||
|
||||
|
||||
onClickSetVectorIcon: ->
|
||||
modal = new VectorIconSetupModal({}, @thangType)
|
||||
@openModalView modal
|
||||
modal.once 'done', => @treema.set('/', @getThangData())
|
||||
|
||||
subscriptions:
|
||||
'editor:thang-type-color-groups-changed': 'onColorGroupsChanged'
|
||||
'editor:save-new-version': 'saveNewThangType'
|
||||
|
@ -234,7 +242,7 @@ module.exports = class ThangTypeEditView extends RootView
|
|||
# animation select
|
||||
|
||||
refreshAnimation: =>
|
||||
@thangType.buildActions()
|
||||
@thangType.resetSpriteSheetCache()
|
||||
return @showRasterImage() if @thangType.get('raster')
|
||||
options = @getLankOptions()
|
||||
console.log 'refresh animation....'
|
||||
|
|
115
app/views/editor/thang/VectorIconSetupModal.coffee
Normal file
115
app/views/editor/thang/VectorIconSetupModal.coffee
Normal file
|
@ -0,0 +1,115 @@
|
|||
ModalView = require 'views/kinds/ModalView'
|
||||
template = require 'templates/editor/thang/vector-icon-setup-modal'
|
||||
|
||||
module.exports = class VectorIconSetupModal extends ModalView
|
||||
id: "vector-icon-setup-modal"
|
||||
template: template
|
||||
demoSize: 400
|
||||
plain: true
|
||||
|
||||
events:
|
||||
'change #container-select': 'onChangeContainer'
|
||||
'click #center': 'onClickCenter'
|
||||
'click #zero-bounds': 'onClickZeroBounds'
|
||||
'click #done-button': 'onClickDone'
|
||||
|
||||
shortcuts:
|
||||
'shift+-': -> @incrScale(-0.02)
|
||||
'shift+=': -> @incrScale(0.02)
|
||||
'up': -> @incrRegY(1)
|
||||
'down': -> @incrRegY(-1)
|
||||
'left': -> @incrRegX(1)
|
||||
'right': -> @incrRegX(-1)
|
||||
|
||||
constructor: (options, @thangType) ->
|
||||
portrait = @thangType.get('actions')?.portrait
|
||||
@containers = _.keys(@thangType.get('raw')?.containers or {})
|
||||
@container = portrait?.container or _.last @containers
|
||||
@scale = portrait?.scale or 1
|
||||
@regX = portrait?.positions?.registration?.x or 0
|
||||
@regY = portrait?.positions?.registration?.y or 0
|
||||
@saveChanges()
|
||||
super(options)
|
||||
|
||||
saveChanges: ->
|
||||
actions = _.cloneDeep (@thangType.get('actions') ? {})
|
||||
actions.portrait ?= {}
|
||||
actions.portrait.scale = @scale
|
||||
actions.portrait.positions ?= {}
|
||||
actions.portrait.positions.registration = { x: @regX, y: @regY }
|
||||
actions.portrait.container = @container
|
||||
@thangType.set('actions', actions)
|
||||
@thangType.buildActions()
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.containers = @containers
|
||||
c.chosenContainer = @container
|
||||
c.demoSize = @demoSize
|
||||
c
|
||||
|
||||
afterRender: ->
|
||||
@initStage()
|
||||
super()
|
||||
|
||||
initStage: ->
|
||||
return unless @containers and @container
|
||||
@stage = @thangType.getVectorPortraitStage(@demoSize)
|
||||
@sprite = @stage.children[0]
|
||||
canvas = $(@stage.canvas)
|
||||
canvas.attr('id', 'resulting-icon')
|
||||
@$el.find('#resulting-icon').replaceWith(canvas)
|
||||
@updateSpriteProperties()
|
||||
|
||||
onChangeContainer: (e) ->
|
||||
@container = $(e.target).val()
|
||||
@saveChanges()
|
||||
@initStage()
|
||||
|
||||
refreshSprite: ->
|
||||
return unless @stage
|
||||
stage = @thangType.getVectorPortraitStage(@demoSize)
|
||||
@stage.removeAllChildren()
|
||||
@stage.addChild(@sprite = stage.children[0])
|
||||
@updateSpriteProperties()
|
||||
@stage.update()
|
||||
|
||||
updateSpriteProperties: ->
|
||||
@sprite.scaleX = @sprite.scaleY = @scale * @demoSize / 100
|
||||
@sprite.regX = @regX
|
||||
@sprite.regY = @regY
|
||||
console.log 'set to', @scale, @regX, @regY
|
||||
|
||||
onClickCenter: ->
|
||||
containerInfo = @thangType.get('raw').containers[@container]
|
||||
b = containerInfo.b
|
||||
@regX = b[0]
|
||||
@regY = b[1]
|
||||
maxDimension = Math.max(b[2], b[3])
|
||||
@scale = 100 / maxDimension
|
||||
if b[2] > b[3]
|
||||
@regY += (b[3] - b[2]) / 2
|
||||
else
|
||||
@regX += (b[2] - b[3]) / 2
|
||||
@updateSpriteProperties()
|
||||
@stage.update()
|
||||
|
||||
incrScale: (amount) ->
|
||||
@scale += amount
|
||||
@updateSpriteProperties()
|
||||
@stage.update()
|
||||
|
||||
incrRegX: (amount) ->
|
||||
@regX += amount
|
||||
@updateSpriteProperties()
|
||||
@stage.update()
|
||||
|
||||
incrRegY: (amount) ->
|
||||
@regY += amount
|
||||
@updateSpriteProperties()
|
||||
@stage.update()
|
||||
|
||||
onClickDone: ->
|
||||
@saveChanges()
|
||||
@trigger 'done'
|
||||
@hide()
|
|
@ -15,6 +15,8 @@ module.exports = class I18NEditLevelView extends I18NEditModelView
|
|||
@wrapRow "Level name", ['name'], name, i18n[lang]?.name, []
|
||||
if description = @model.get('description')
|
||||
@wrapRow "Level description", ['description'], description, i18n[lang]?.description, []
|
||||
if loadingTip = @model.get('loadingTip')
|
||||
@wrapRow "Loading tip", ['loadingTip'], loadingTip, i18n[lang]?.loadingTip, []
|
||||
|
||||
# goals
|
||||
for goal, index in @model.get('goals') ? []
|
||||
|
|
|
@ -34,6 +34,7 @@ module.exports = class RootView extends CocoView
|
|||
'achievements:new': 'handleNewAchievements'
|
||||
|
||||
showNewAchievement: (achievement, earnedAchievement) ->
|
||||
return if achievement.get('collection') is 'level.sessions'
|
||||
popup = new AchievementPopup achievement: achievement, earnedAchievement: earnedAchievement
|
||||
|
||||
handleNewAchievements: (e) ->
|
||||
|
|
|
@ -100,6 +100,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
|
||||
|
||||
afterRender: ->
|
||||
|
@ -596,7 +597,7 @@ forest = [
|
|||
description: 'Protect the peasants from the pursuing ogres.'
|
||||
nextLevels:
|
||||
continue: 'winding-trail'
|
||||
x: 29.63
|
||||
x: 32.63
|
||||
y: 53.69
|
||||
}
|
||||
{
|
||||
|
|
|
@ -18,7 +18,7 @@ module.exports = class LevelLoadingView extends CocoView
|
|||
@$el.find('.tip.rare').remove() if _.random(1, 10) < 9
|
||||
tips = @$el.find('.tip').addClass('to-remove')
|
||||
tip = _.sample(tips)
|
||||
$(tip).removeClass('to-remove')
|
||||
$(tip).removeClass('to-remove').addClass('secret')
|
||||
@$el.find('.to-remove').remove()
|
||||
@onLevelLoaded level: @options.level if @options.level?.get('goals') # If Level was already loaded.
|
||||
|
||||
|
@ -47,6 +47,11 @@ module.exports = class LevelLoadingView extends CocoView
|
|||
goalContainer.removeClass('secret')
|
||||
if goalCount is 1
|
||||
goalContainer.find('.panel-heading').text $.i18n.t 'play_level.goal' # Not plural
|
||||
tip = @$el.find('.tip')
|
||||
if @level.get('loadingTip')
|
||||
loadingTip = utils.i18n @level.attributes, 'loadingTip'
|
||||
tip.text(loadingTip)
|
||||
tip.removeClass('secret')
|
||||
|
||||
showReady: ->
|
||||
return if @shownReady
|
||||
|
@ -59,8 +64,6 @@ module.exports = class LevelLoadingView extends CocoView
|
|||
@startUnveiling()
|
||||
@unveil()
|
||||
else
|
||||
ready = $.i18n.t('play_level.loading_ready', defaultValue: 'Ready!')
|
||||
@$el.find('#tip-wrapper .tip').addClass('ready').text ready
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'level_loaded', volume: 0.75 # old: loading_ready
|
||||
@$el.find('.progress').addClass 'active progress-striped'
|
||||
@$el.find('.start-level-button').removeClass 'secret'
|
||||
|
|
|
@ -427,6 +427,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
application.tracker?.trackTiming victoryTime, 'Level Victory Time', @levelID, @levelID, 100
|
||||
|
||||
showVictory: ->
|
||||
@endHighlight()
|
||||
options = {level: @level, supermodel: @supermodel, session: @session}
|
||||
ModalClass = if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'] then HeroVictoryModal else VictoryModal
|
||||
victoryModal = new ModalClass(options)
|
||||
|
|
|
@ -129,6 +129,7 @@ module.exports = class SpellPaletteView extends CocoView
|
|||
storages = [storages] if _.isString storages
|
||||
for storage in storages
|
||||
props = _.reject @thang[storage] ? [], (prop) -> prop[0] is '_' # no private properties
|
||||
props = _.uniq props
|
||||
added = _.sortBy(props).slice()
|
||||
propGroups[owner] = (propGroups[owner] ? []).concat added
|
||||
count += added.length
|
||||
|
@ -181,14 +182,18 @@ module.exports = class SpellPaletteView extends CocoView
|
|||
propsByItem = {}
|
||||
propCount = 0
|
||||
itemsByProp = {}
|
||||
for slot, thangTypeName of @thang.inventoryThangTypeNames ? {}
|
||||
# Make sure that we get the spellbook first, then the primary hand, then anything else.
|
||||
slots = _.sortBy _.keys(@thang.inventoryThangTypeNames ? {}), (slot) ->
|
||||
if slot is 'left-hand' then 0 else if slot is 'right-hand' then 1 else 2
|
||||
for slot in slots
|
||||
thangTypeName = @thang.inventoryThangTypeNames[slot]
|
||||
if item = itemThangTypes[thangTypeName]
|
||||
unless item.get('components')
|
||||
console.error 'Item', item, 'did not have any components when we went to assemble docs.'
|
||||
for component in item.get('components') ? [] when component.config
|
||||
for owner, storages of propStorage
|
||||
if props = component.config[storages]
|
||||
for prop in _.sortBy(props) when prop[0] isnt '_' # no private properties
|
||||
for prop in _.sortBy(props) when prop[0] isnt '_' and not itemsByProp[prop] # no private properties
|
||||
propsByItem[item.get('name')] ?= []
|
||||
propsByItem[item.get('name')].push owner: owner, prop: prop, item: item
|
||||
itemsByProp[prop] = item
|
||||
|
|
|
@ -31,7 +31,13 @@ module.exports = class BuyGemsModal extends ModalView
|
|||
return c
|
||||
|
||||
onIPadProducts: (e) ->
|
||||
@products = e.products
|
||||
newProducts = []
|
||||
for iapProduct in e.products
|
||||
localProduct = _.find @products, { id: iapProduct.id }
|
||||
continue unless localProduct
|
||||
localProduct.price = iapProduct.price
|
||||
newProducts.push localProduct
|
||||
@products = newProducts
|
||||
@render()
|
||||
|
||||
onClickProductButton: (e) ->
|
||||
|
@ -45,8 +51,10 @@ module.exports = class BuyGemsModal extends ModalView
|
|||
@$el.find('.modal-body').append($('<div class="alert alert-danger">Not implemented</div>'))
|
||||
|
||||
onIAPComplete: (e) ->
|
||||
product = _.find @products, { id: e.productID }
|
||||
purchased = me.get('purchased') ? {}
|
||||
purchased = _.clone purchased
|
||||
purchased.gems ?= 0
|
||||
purchased.gems += e.gems
|
||||
purchased.gems += product.gems
|
||||
me.set('purchased', purchased)
|
||||
@hide()
|
|
@ -12,41 +12,47 @@ module.exports = class ItemDetailsView extends CocoView
|
|||
constructor: ->
|
||||
super(arguments...)
|
||||
@propDocs = {}
|
||||
@spellDocs = {}
|
||||
|
||||
setItem: (@item) ->
|
||||
if @item
|
||||
@spellDocs = {}
|
||||
@item.name = utils.i18n @item.attributes, 'name'
|
||||
@item.affordable = me.gems() >= @item.get('gems')
|
||||
@item.owned = me.ownsItem @item.get('original')
|
||||
@item.comingSoon = not @item.getFrontFacingStats().props.length and not _.size @item.getFrontFacingStats().stats # Temp: while there are placeholder items
|
||||
|
||||
|
||||
stats = @item.getFrontFacingStats()
|
||||
props = (p for p in stats.props when not @propDocs[p])
|
||||
if props.length > 0
|
||||
if props.length > 0 or ('cast' in stats.props)
|
||||
|
||||
docs = new CocoCollection([], {
|
||||
url: '/db/level.component?view=prop-doc-lookup'
|
||||
model: LevelComponent
|
||||
project: [
|
||||
'name'
|
||||
'propertyDocumentation.name'
|
||||
'propertyDocumentation.description'
|
||||
'propertyDocumentation.i18n'
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
docs.fetch({ data: {
|
||||
componentOriginals: [c.original for c in @item.get('components')].join(',')
|
||||
propertyNames: props.join(',')
|
||||
}})
|
||||
@listenToOnce docs, 'sync', @onDocsLoaded
|
||||
|
||||
|
||||
@render()
|
||||
@$el.find('.nano:visible').nanoScroller()
|
||||
|
||||
onDocsLoaded: (levelComponents) ->
|
||||
for component in levelComponents.models
|
||||
for propDoc in component.get('propertyDocumentation')
|
||||
@propDocs[propDoc.name] = propDoc
|
||||
if /^cast.+/.test propDoc.name
|
||||
@spellDocs[propDoc.name] = propDoc
|
||||
else
|
||||
@propDocs[propDoc.name] = propDoc
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
|
@ -57,20 +63,27 @@ module.exports = class ItemDetailsView extends CocoView
|
|||
c.stats = _.values(stats.stats)
|
||||
_.last(c.stats).isLast = true if c.stats.length
|
||||
c.props = []
|
||||
progLang = (me.get('aceConfig') ? {}).language or 'python'
|
||||
stats.props = stats.props.concat _.keys @spellDocs
|
||||
codeLanguage = (me.get('aceConfig') ? {}).language or 'python'
|
||||
for prop in stats.props
|
||||
description = utils.i18n @propDocs[prop] ? {}, 'description'
|
||||
doc = @propDocs[prop] ? @spellDocs[prop] ? {}
|
||||
description = utils.i18n doc, 'description'
|
||||
|
||||
if _.isObject description
|
||||
description = description[progLang] or _.values(description)[0]
|
||||
description = description[codeLanguage] or _.values(description)[0]
|
||||
if _.isString description
|
||||
description = description.replace(/#{spriteName}/g, 'hero')
|
||||
if fact = stats.stats.shieldDefenseFactor
|
||||
description = description.replace(/#{shieldDefensePercent}%/g, fact.display)
|
||||
## We don't have the full components loaded here, so we can't really get most of these values.
|
||||
#description = description.replace /#{([^.]+?)}/g, (match, keyChain) ->
|
||||
# console.log 'gotta find', keyChain, 'from', match, 'and have', stats
|
||||
# match
|
||||
description = description.replace(/#{(.+?)}/g, '`$1`')
|
||||
description = $(marked(description)).html()
|
||||
|
||||
c.props.push {
|
||||
name: prop
|
||||
name: _.string.humanize prop
|
||||
description: description or '...'
|
||||
}
|
||||
c
|
||||
c
|
||||
|
|
|
@ -37,9 +37,12 @@ LevelComponentHandler = class LevelComponentHandler extends Handler
|
|||
properties = req.query.propertyNames.split(',')
|
||||
catch e
|
||||
return @sendBadInputError(res, 'Could not parse componentOriginals or propertyNames.')
|
||||
|
||||
|
||||
query['original'] = {$in: components}
|
||||
query['propertyDocumentation.name'] = {$in: properties}
|
||||
query.$or = [
|
||||
{'propertyDocumentation.name': {$in: properties}}
|
||||
{'propertyDocumentation.name': {$regex: /^cast.+/}}
|
||||
]
|
||||
|
||||
q = LevelComponent.find(query, projection)
|
||||
q.exec (err, documents) =>
|
||||
|
|
|
@ -29,6 +29,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
'employerDescription'
|
||||
'terrain'
|
||||
'i18nCoverage'
|
||||
'loadingTip'
|
||||
]
|
||||
|
||||
postEditableProperties: ['name']
|
||||
|
|
|
@ -1,41 +1,43 @@
|
|||
InventoryView = require 'views/game-menu/InventoryView'
|
||||
# TODO: rework this spec for the InventoryModal
|
||||
|
||||
thangTypes = [
|
||||
{"_id":"boots-id","name":"Boots","original":"boots","components":[{"original":"524b85837fc0f6d519000020","majorVersion":0},{"original":"524b7b857fc0f6d519000012","majorVersion":0},{"original":"524b4150ff92f1f4f8000024","majorVersion":0},{"original":"53e12043b82921000051cdf9","majorVersion":0,"config":{"slots":["feet"],"programmableProperties":["move","targetPos"],"moreProgrammableProperties":[],"extraHUDProperties":["maxSpeed"],"stats":{"maxSpeed":{"factor":1}}}},{"original":"524b7b8c7fc0f6d519000013","majorVersion":0,"config":{"locomotionType":"running","maxSpeed":5,"maxAcceleration":100}},{"original":"524b75ad7fc0f6d519000001","majorVersion":0,"config":{"pos":{"x":39.08,"y":20.72,"z":0.5},"width":1,"height":1,"depth":1,"shape":"ellipsoid"}},{"original":"524b7b7c7fc0f6d519000011","majorVersion":0}]},
|
||||
{"_id":"boots-of-leaping-id","name":"Boots of Leaping","original":"boots-of-leaping","components":[{"original":"524b85837fc0f6d519000020","majorVersion":0},{"original":"524b7b857fc0f6d519000012","majorVersion":0},{"original":"524b4150ff92f1f4f8000024","majorVersion":0},{"original":"53e12043b82921000051cdf9","majorVersion":0,"config":{"ownerID":"Tharin","slots":["feet"],"programmableProperties":["move","targetPos","jumpTo"],"moreProgrammableProperties":["jump"],"extraHUDProperties":["maxSpeed"],"stats":{"maxSpeed":{"factor":1.2}}}},{"original":"524b7b8c7fc0f6d519000013","majorVersion":0,"config":{"locomotionType":"running","maxSpeed":6,"maxAcceleration":100}},{"original":"524b1f54d768d916b5000001","majorVersion":0,"config":{"jumpHeight":3}},{"original":"5275392d69abdcb12401441e","majorVersion":0,"config":{"jumpSpeedFactor":1.5}},{"original":"524b75ad7fc0f6d519000001","majorVersion":0,"config":{"pos":{"x":39.08,"y":20.72,"z":0.5},"width":1,"height":1,"depth":1,"shape":"ellipsoid"}},{"original":"524b7b7c7fc0f6d519000011","majorVersion":0}]},
|
||||
{"_id":"crossbow-id","name":"Crossbow","original":"crossbow","components":[{"original":"524b85837fc0f6d519000020","majorVersion":0},{"original":"524b517fff92f1f4f8000046","majorVersion":0},{"original":"524b7b747fc0f6d519000010","majorVersion":0,"config":{"team":"humans"}},{"original":"524b7bc67fc0f6d51900001a","majorVersion":0,"config":{"missileThangID":"Arrow"}},{"original":"524b7ba57fc0f6d519000016","majorVersion":0,"config":{"attackDamage":5,"attackRange":20,"cooldown":0.6,"chasesWhenAttackingOutOfRange":true}},{"original":"524b3e3fff92f1f4f800000d","majorVersion":0},{"original":"524cbdc03ea855e0ab0000bb","majorVersion":0},{"original":"524b4150ff92f1f4f8000024","majorVersion":0},{"original":"53e12043b82921000051cdf9","majorVersion":0,"config":{"slots":["right-hand"],"programmableProperties":["attack","target","attackRange"],"moreProgrammableProperties":["attackXY","targetPos"],"extraHUDProperties":["attackDamage","attackRange"]}},{"original":"524b75ad7fc0f6d519000001","majorVersion":0,"config":{"pos":{"x":41.105000000000004,"y":31.6,"z":0.125},"width":1.5,"height":0.75,"depth":0.25,"shape":"box"}},{"original":"524b7b7c7fc0f6d519000011","majorVersion":0},{"original":"524b457bff92f1f4f8000031","majorVersion":0}]},
|
||||
{"_id":"crude-glasses-id","name":"Crude Glasses","original":"crude-glasses","components":[{"original":"524b7b747fc0f6d519000010","majorVersion":0,"config":{"team":"humans"}},{"original":"524b4150ff92f1f4f8000024","majorVersion":0},{"original":"53e12043b82921000051cdf9","majorVersion":0,"config":{"slots":["eyes"],"programmableProperties":["pos","getEnemies"],"moreProgrammableProperties":["getItems","getFriends"]}},{"original":"524b75ad7fc0f6d519000001","majorVersion":0,"config":{"pos":{"x":33.230000000000004,"y":20.75,"z":2},"width":1,"height":2,"depth":1,"shape":"ellipsoid"}},{"original":"524b457bff92f1f4f8000031","majorVersion":0,"config":{"visualRange":50}}]}
|
||||
]
|
||||
|
||||
describe 'InventoryView', ->
|
||||
inventoryView = null
|
||||
|
||||
beforeEach (done) ->
|
||||
equipment = { 'feet':'boots', 'eyes': 'crude-glasses' }
|
||||
inventoryView = new InventoryView({ equipment: equipment })
|
||||
responses =
|
||||
'/db/thang.type?view=items': thangTypes
|
||||
jasmine.Ajax.requests.sendResponses(responses)
|
||||
_.defer ->
|
||||
inventoryView.render()
|
||||
done()
|
||||
|
||||
it 'selects a slot when you click it', ->
|
||||
inventoryView.getSlot('eyes').click()
|
||||
expect(inventoryView.getSelectedSlot().data('slot')).toBe('eyes')
|
||||
|
||||
it 'unselects a selected slot when you click it', ->
|
||||
inventoryView.getSlot('eyes').click().click()
|
||||
expect(inventoryView.getSelectedSlot().data('slot')).toBeUndefined()
|
||||
|
||||
it 'selects an available item when you click it', ->
|
||||
inventoryView.getAvailableItemContainer('boots-of-leaping').click()
|
||||
expect(inventoryView.getSelectedAvailableItemContainer().data('item-id')).toBe('boots-of-leaping')
|
||||
|
||||
it 'equips an available item when you double click it', ->
|
||||
inventoryView.getAvailableItemContainer('crossbow').click().dblclick()
|
||||
expect(inventoryView.getCurrentEquipmentConfig()['right-hand']).toBeTruthy()
|
||||
|
||||
it 'unequips an item when you double click it', ->
|
||||
inventoryView.getSlot('eyes').find('.item-view').click().dblclick()
|
||||
expect(inventoryView.getCurrentEquipmentConfig().eyes).toBeUndefined()
|
||||
#InventoryView = require 'views/game-menu/InventoryView'
|
||||
#
|
||||
#thangTypes = [
|
||||
# {"_id":"boots-id","name":"Boots","original":"boots","components":[{"original":"524b85837fc0f6d519000020","majorVersion":0},{"original":"524b7b857fc0f6d519000012","majorVersion":0},{"original":"524b4150ff92f1f4f8000024","majorVersion":0},{"original":"53e12043b82921000051cdf9","majorVersion":0,"config":{"slots":["feet"],"programmableProperties":["move","targetPos"],"moreProgrammableProperties":[],"extraHUDProperties":["maxSpeed"],"stats":{"maxSpeed":{"factor":1}}}},{"original":"524b7b8c7fc0f6d519000013","majorVersion":0,"config":{"locomotionType":"running","maxSpeed":5,"maxAcceleration":100}},{"original":"524b75ad7fc0f6d519000001","majorVersion":0,"config":{"pos":{"x":39.08,"y":20.72,"z":0.5},"width":1,"height":1,"depth":1,"shape":"ellipsoid"}},{"original":"524b7b7c7fc0f6d519000011","majorVersion":0}]},
|
||||
# {"_id":"boots-of-leaping-id","name":"Boots of Leaping","original":"boots-of-leaping","components":[{"original":"524b85837fc0f6d519000020","majorVersion":0},{"original":"524b7b857fc0f6d519000012","majorVersion":0},{"original":"524b4150ff92f1f4f8000024","majorVersion":0},{"original":"53e12043b82921000051cdf9","majorVersion":0,"config":{"ownerID":"Tharin","slots":["feet"],"programmableProperties":["move","targetPos","jumpTo"],"moreProgrammableProperties":["jump"],"extraHUDProperties":["maxSpeed"],"stats":{"maxSpeed":{"factor":1.2}}}},{"original":"524b7b8c7fc0f6d519000013","majorVersion":0,"config":{"locomotionType":"running","maxSpeed":6,"maxAcceleration":100}},{"original":"524b1f54d768d916b5000001","majorVersion":0,"config":{"jumpHeight":3}},{"original":"5275392d69abdcb12401441e","majorVersion":0,"config":{"jumpSpeedFactor":1.5}},{"original":"524b75ad7fc0f6d519000001","majorVersion":0,"config":{"pos":{"x":39.08,"y":20.72,"z":0.5},"width":1,"height":1,"depth":1,"shape":"ellipsoid"}},{"original":"524b7b7c7fc0f6d519000011","majorVersion":0}]},
|
||||
# {"_id":"crossbow-id","name":"Crossbow","original":"crossbow","components":[{"original":"524b85837fc0f6d519000020","majorVersion":0},{"original":"524b517fff92f1f4f8000046","majorVersion":0},{"original":"524b7b747fc0f6d519000010","majorVersion":0,"config":{"team":"humans"}},{"original":"524b7bc67fc0f6d51900001a","majorVersion":0,"config":{"missileThangID":"Arrow"}},{"original":"524b7ba57fc0f6d519000016","majorVersion":0,"config":{"attackDamage":5,"attackRange":20,"cooldown":0.6,"chasesWhenAttackingOutOfRange":true}},{"original":"524b3e3fff92f1f4f800000d","majorVersion":0},{"original":"524cbdc03ea855e0ab0000bb","majorVersion":0},{"original":"524b4150ff92f1f4f8000024","majorVersion":0},{"original":"53e12043b82921000051cdf9","majorVersion":0,"config":{"slots":["right-hand"],"programmableProperties":["attack","target","attackRange"],"moreProgrammableProperties":["attackXY","targetPos"],"extraHUDProperties":["attackDamage","attackRange"]}},{"original":"524b75ad7fc0f6d519000001","majorVersion":0,"config":{"pos":{"x":41.105000000000004,"y":31.6,"z":0.125},"width":1.5,"height":0.75,"depth":0.25,"shape":"box"}},{"original":"524b7b7c7fc0f6d519000011","majorVersion":0},{"original":"524b457bff92f1f4f8000031","majorVersion":0}]},
|
||||
# {"_id":"crude-glasses-id","name":"Crude Glasses","original":"crude-glasses","components":[{"original":"524b7b747fc0f6d519000010","majorVersion":0,"config":{"team":"humans"}},{"original":"524b4150ff92f1f4f8000024","majorVersion":0},{"original":"53e12043b82921000051cdf9","majorVersion":0,"config":{"slots":["eyes"],"programmableProperties":["pos","getEnemies"],"moreProgrammableProperties":["getItems","getFriends"]}},{"original":"524b75ad7fc0f6d519000001","majorVersion":0,"config":{"pos":{"x":33.230000000000004,"y":20.75,"z":2},"width":1,"height":2,"depth":1,"shape":"ellipsoid"}},{"original":"524b457bff92f1f4f8000031","majorVersion":0,"config":{"visualRange":50}}]}
|
||||
#]
|
||||
#
|
||||
#describe 'InventoryView', ->
|
||||
# inventoryView = null
|
||||
#
|
||||
# beforeEach (done) ->
|
||||
# equipment = { 'feet':'boots', 'eyes': 'crude-glasses' }
|
||||
# inventoryView = new InventoryView({ equipment: equipment })
|
||||
# responses =
|
||||
# '/db/thang.type?view=items': thangTypes
|
||||
# jasmine.Ajax.requests.sendResponses(responses)
|
||||
# _.defer ->
|
||||
# inventoryView.render()
|
||||
# done()
|
||||
#
|
||||
# it 'selects a slot when you click it', ->
|
||||
# inventoryView.getSlot('eyes').click()
|
||||
# expect(inventoryView.getSelectedSlot().data('slot')).toBe('eyes')
|
||||
#
|
||||
# it 'unselects a selected slot when you click it', ->
|
||||
# inventoryView.getSlot('eyes').click().click()
|
||||
# expect(inventoryView.getSelectedSlot().data('slot')).toBeUndefined()
|
||||
#
|
||||
# it 'selects an available item when you click it', ->
|
||||
# inventoryView.getAvailableItemContainer('boots-of-leaping').click()
|
||||
# expect(inventoryView.getSelectedAvailableItemContainer().data('item-id')).toBe('boots-of-leaping')
|
||||
#
|
||||
# it 'equips an available item when you double click it', ->
|
||||
# inventoryView.getAvailableItemContainer('crossbow').click().dblclick()
|
||||
# expect(inventoryView.getCurrentEquipmentConfig()['right-hand']).toBeTruthy()
|
||||
#
|
||||
# it 'unequips an item when you double click it', ->
|
||||
# inventoryView.getSlot('eyes').find('.item-view').click().dblclick()
|
||||
# expect(inventoryView.getCurrentEquipmentConfig().eyes).toBeUndefined()
|
||||
|
|
Loading…
Reference in a new issue