Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-11-12 18:44:53 -08:00
commit 6b92df2e41
33 changed files with 378 additions and 111 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View file

@ -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

View file

@ -310,6 +310,11 @@
restricted: "(restricted in this level)"
equip: "Equip"
unequip: "Unequip"
buy_gems:
few_gems: 'A few gems'
pile_gems: 'Pile of gems'
chest_gems: 'Chest of gems'
choose_hero:
choose_hero: "Choose Your Hero"

View file

@ -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()

View file

@ -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

View file

@ -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'

View file

@ -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' }

View 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

View file

@ -40,7 +40,7 @@ $itemSlotGridHeight: 70px
#gems-count-container
position: absolute
left: 213px
left: 374px
top: 10px
width: 160px
height: 66px
@ -49,7 +49,7 @@ $itemSlotGridHeight: 70px
#gems-count
position: absolute
left: 75px
top: 17px
top: 14px
font-size: 25px
color: rgb(1,64,91)
@ -58,8 +58,8 @@ $itemSlotGridHeight: 70px
#close-modal
position: absolute
left: 390px
top: 23px
left: 551px
top: 21px
width: 60px
height: 60px
color: white
@ -77,11 +77,9 @@ $itemSlotGridHeight: 70px
#equipped
width: 330px
background: white
border: 3px solid black
position: absolute
left: 20px
top: 112px
top: 122px
height: 450px
overflow: hidden
@ -307,14 +305,12 @@ $itemSlotGridHeight: 70px
position: absolute
#unequipped
width: 222px
width: 245px
position: absolute
left: 370px
top: 112px
height: 450px
border: 3px solid black
top: 135px
height: 435px
padding: 9px 0 9px 9px
background-color: white
#double-click-hint
margin: 20px 0 70px
@ -387,12 +383,12 @@ $itemSlotGridHeight: 70px
//- Hero/Play buttons
#choose-hero-button, #play-level-button
top: 572px
top: 582px
position: absolute
background: url(/images/pages/play/modal/confirm-button.png)
width: 209px
height: 65px
background-size: 209px 65px
height: 55px
background-size: 209px 55px
border: 0
&:disabled

View file

@ -1,8 +1,44 @@
#buy-gems-modal
button
width: 100%
margin-bottom: 10px
//- Clear modal defaults
.modal-dialog
margin: 100px auto 0 auto
padding: 0
width: 820px
height: 460px
background: none
//- Background
#buy-gems-background
position: absolute
top: -157px
left: -2px
//- Products
#products
position: absolute
left: 55px
top: 242px
width: 720px
height: 140px
index: 1
.gem
width: 20px
height: 20px
.product
width: 228px
overflow: none
float: left
text-align: center
margin-right: 12px
h4
font-size: 20px
color: rgb(22,16,5)
h3
margin-top: 10px
text-transform: uppercase
color: rgb(22,16,5)
button
width: 80%

View file

@ -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
@ -239,6 +259,8 @@ $gameControlMargin: 30px
background-position: (-3 * $gameControlSize) 0px
&.settings
background-position: (-4 * $gameControlSize) 0px
&.gems
background-position: (-5 * $gameControlSize) 0px
.tooltip
font-size: 24px

View file

@ -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

View 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

View file

@ -1,6 +1,6 @@
.modal-dialog
.modal-content
img(src="/images/pages/play/modal/items-background-narrow.png")#play-items-modal-narrow-bg
img(src="/images/pages/play/modal/inventory-background.png")#play-items-modal-narrow-bg
div#gems-count-container
span#gems-count.big-font= gems

View file

@ -1,14 +1,11 @@
extends /templates/modal/modal_base
block modal-header-content
h3(data-i18n='play.buy_gems')
block modal-body-content
for product in products
button.product.btn.btn-lg(value=product.id)
img.gem(src="/images/common/gem.png")
span x#{product.gems): #{product.price}
block modal-footer
.modal-footer
button(data-dismiss="modal", data-i18n="common.cancel").btn Cancel
.modal-dialog
.modal-content
img(src="/images/pages/play/modal/buy-gems-background.png")#buy-gems-background
#products
for product in products
.product
h4 x#{product.gems}
h3(data-i18n=product.i18n)
button.btn.btn-illustrated.btn-lg(value=product.id)
span= product.price

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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....'

View 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()

View file

@ -124,7 +124,7 @@ module.exports = class InventoryModal extends ModalView
@itemDetailsView = new ItemDetailsView()
@insertSubView(@itemDetailsView)
@requireLevelEquipment()
@$el.find('.nano').nanoScroller()
@$el.find('.nano').nanoScroller({alwaysVisible: true})
afterInsert: ->
super()

View file

@ -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') ? []

View file

@ -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) ->

View file

@ -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
}
{

View file

@ -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'

View file

@ -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)

View file

@ -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

View file

@ -7,9 +7,9 @@ module.exports = class BuyGemsModal extends ModalView
plain: true
products: [
{ price: '$4.99', gems: 5000, id: 'gems_5' }
{ price: '$9.99', gems: 11000, id: 'gems_10' }
{ price: '$19.99', gems: 25000, id: 'gems_20' }
{ price: '$4.99', gems: 5000, id: 'gems_5', i18n: 'buy_gems.few_gems' }
{ price: '$9.99', gems: 11000, id: 'gems_10', i18n: 'buy_gems.pile_gems' }
{ price: '$19.99', gems: 25000, id: 'gems_20', i18n: 'buy_gems.chest_gems' }
]
subscriptions:
@ -17,7 +17,7 @@ module.exports = class BuyGemsModal extends ModalView
'ipad:iap-complete': 'onIAPComplete'
events:
'click button.product': 'onClickProductButton'
'click .product button': 'onClickProductButton'
constructor: (options) ->
super(options)
@ -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) ->
@ -42,11 +48,14 @@ module.exports = class BuyGemsModal extends ModalView
Backbone.Mediator.publish 'buy-gems-modal:purchase-initiated', { productID: productID }
else
@$el.find('.modal-body').append($('<div class="alert alert-danger">Not implemented</div>'))
application.tracker?.trackEvent 'Started purchase', {productID:productID}, ['Google Analytics']
alert('Not yet implemented, but thanks for trying!')
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()

View file

@ -29,6 +29,7 @@ LevelHandler = class LevelHandler extends Handler
'employerDescription'
'terrain'
'i18nCoverage'
'loadingTip'
]
postEditableProperties: ['name']

View file

@ -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()