diff --git a/app/assets/images/pages/about/matt_small.png b/app/assets/images/pages/about/matt_small.png new file mode 100644 index 000000000..660b2e0db Binary files /dev/null and b/app/assets/images/pages/about/matt_small.png differ diff --git a/app/assets/images/pages/play/menu_icons.png b/app/assets/images/pages/play/menu_icons.png index 7c19bde65..53028d44b 100644 Binary files a/app/assets/images/pages/play/menu_icons.png and b/app/assets/images/pages/play/menu_icons.png differ diff --git a/app/assets/images/pages/play/modal/buy-gems-background.png b/app/assets/images/pages/play/modal/buy-gems-background.png new file mode 100644 index 000000000..563124aeb Binary files /dev/null and b/app/assets/images/pages/play/modal/buy-gems-background.png differ diff --git a/app/assets/images/pages/play/modal/inventory-background.png b/app/assets/images/pages/play/modal/inventory-background.png new file mode 100644 index 000000000..9217b51d9 Binary files /dev/null and b/app/assets/images/pages/play/modal/inventory-background.png differ diff --git a/app/lib/sprites/SpriteParser.coffee b/app/lib/sprites/SpriteParser.coffee index b48c58bba..408664220 100644 --- a/app/lib/sprites/SpriteParser.coffee +++ b/app/lib/sprites/SpriteParser.coffee @@ -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 diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 8ca59d54d..8e7f2115b 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -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" diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee index 0555eaff9..0f7f57abd 100644 --- a/app/models/ThangType.coffee +++ b/app/models/ThangType.coffee @@ -240,7 +240,6 @@ module.exports = class ThangType extends CocoModel return if _.isString spriteSheet return unless spriteSheet 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 = $("") + 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() diff --git a/app/schemas/models/level.coffee b/app/schemas/models/level.coffee index 090cf473c..b9c72fff4 100644 --- a/app/schemas/models/level.coffee +++ b/app/schemas/models/level.coffee @@ -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 diff --git a/app/schemas/models/thang_type.coffee b/app/schemas/models/thang_type.coffee index 7d74b0ef9..5f9183745 100644 --- a/app/schemas/models/thang_type.coffee +++ b/app/schemas/models/thang_type.coffee @@ -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' diff --git a/app/schemas/subscriptions/ipad.coffee b/app/schemas/subscriptions/ipad.coffee index 6edf898d2..c4013e52d 100644 --- a/app/schemas/subscriptions/ipad.coffee +++ b/app/schemas/subscriptions/ipad.coffee @@ -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' } diff --git a/app/styles/editor/thang/vector-icon-setup-modal.sass b/app/styles/editor/thang/vector-icon-setup-modal.sass new file mode 100644 index 000000000..3514dbe32 --- /dev/null +++ b/app/styles/editor/thang/vector-icon-setup-modal.sass @@ -0,0 +1,9 @@ +#vector-icon-setup-modal + select + margin-bottom: 20px + + canvas + background: lightblue + border: 3px solid black + margin: 20px auto + display: block diff --git a/app/styles/game-menu/inventory-modal.sass b/app/styles/game-menu/inventory-modal.sass index be8bd00cd..3dfc2df37 100644 --- a/app/styles/game-menu/inventory-modal.sass +++ b/app/styles/game-menu/inventory-modal.sass @@ -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 diff --git a/app/styles/play/modal/buy-gems-modal.sass b/app/styles/play/modal/buy-gems-modal.sass index b7edc5092..52fd3bc02 100644 --- a/app/styles/play/modal/buy-gems-modal.sass +++ b/app/styles/play/modal/buy-gems-modal.sass @@ -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 \ No newline at end of file + .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% diff --git a/app/styles/play/world-map-view.sass b/app/styles/play/world-map-view.sass index 2bbe21bc1..c412b6376 100644 --- a/app/styles/play/world-map-view.sass +++ b/app/styles/play/world-map-view.sass @@ -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 diff --git a/app/templates/editor/thang/thang-type-edit-view.jade b/app/templates/editor/thang/thang-type-edit-view.jade index 742c41bd3..eb6eeecad 100644 --- a/app/templates/editor/thang/thang-type-edit-view.jade +++ b/app/templates/editor/thang/thang-type-edit-view.jade @@ -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 diff --git a/app/templates/editor/thang/vector-icon-setup-modal.jade b/app/templates/editor/thang/vector-icon-setup-modal.jade new file mode 100644 index 000000000..cdfcb8268 --- /dev/null +++ b/app/templates/editor/thang/vector-icon-setup-modal.jade @@ -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 diff --git a/app/templates/game-menu/inventory-modal.jade b/app/templates/game-menu/inventory-modal.jade index 846d23173..0503d2664 100644 --- a/app/templates/game-menu/inventory-modal.jade +++ b/app/templates/game-menu/inventory-modal.jade @@ -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 diff --git a/app/templates/play/modal/buy-gems-modal.jade b/app/templates/play/modal/buy-gems-modal.jade index 6833949f6..f06470d88 100644 --- a/app/templates/play/modal/buy-gems-modal.jade +++ b/app/templates/play/modal/buy-gems-modal.jade @@ -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 \ No newline at end of file diff --git a/app/templates/play/modal/item-details-view.jade b/app/templates/play/modal/item-details-view.jade index 9d7f4c598..6f999a9fc 100644 --- a/app/templates/play/modal/item-details-view.jade +++ b/app/templates/play/modal/item-details-view.jade @@ -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 diff --git a/app/templates/play/world-map-view.jade b/app/templates/play/world-map-view.jade index b482edccf..7f3d729bc 100644 --- a/app/templates/play/world-map-view.jade +++ b/app/templates/play/world-map-view.jade @@ -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 diff --git a/app/views/editor/level/settings/SettingsTabView.coffee b/app/views/editor/level/settings/SettingsTabView.coffee index 26ef6948b..204258d66 100644 --- a/app/views/editor/level/settings/SettingsTabView.coffee +++ b/app/views/editor/level/settings/SettingsTabView.coffee @@ -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: diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index b6cdd2b44..af812cd3d 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -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....' diff --git a/app/views/editor/thang/VectorIconSetupModal.coffee b/app/views/editor/thang/VectorIconSetupModal.coffee new file mode 100644 index 000000000..ef23bc88a --- /dev/null +++ b/app/views/editor/thang/VectorIconSetupModal.coffee @@ -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() \ No newline at end of file diff --git a/app/views/game-menu/InventoryModal.coffee b/app/views/game-menu/InventoryModal.coffee index 9b89b7e67..d23be3c8d 100644 --- a/app/views/game-menu/InventoryModal.coffee +++ b/app/views/game-menu/InventoryModal.coffee @@ -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() diff --git a/app/views/i18n/I18NEditLevelView.coffee b/app/views/i18n/I18NEditLevelView.coffee index 475aef524..307c7413b 100644 --- a/app/views/i18n/I18NEditLevelView.coffee +++ b/app/views/i18n/I18NEditLevelView.coffee @@ -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') ? [] diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index e7fdd7e2b..a66eb004e 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -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) -> diff --git a/app/views/play/WorldMapView.coffee b/app/views/play/WorldMapView.coffee index c5aeeb8a5..96588f0e8 100644 --- a/app/views/play/WorldMapView.coffee +++ b/app/views/play/WorldMapView.coffee @@ -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 } { diff --git a/app/views/play/level/LevelLoadingView.coffee b/app/views/play/level/LevelLoadingView.coffee index 65f251b8d..396123da9 100644 --- a/app/views/play/level/LevelLoadingView.coffee +++ b/app/views/play/level/LevelLoadingView.coffee @@ -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' diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index a761e5a21..fa4d2bfa1 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -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) diff --git a/app/views/play/level/tome/SpellPaletteView.coffee b/app/views/play/level/tome/SpellPaletteView.coffee index 45620381d..9d3fd3a0a 100644 --- a/app/views/play/level/tome/SpellPaletteView.coffee +++ b/app/views/play/level/tome/SpellPaletteView.coffee @@ -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 diff --git a/app/views/play/modal/BuyGemsModal.coffee b/app/views/play/modal/BuyGemsModal.coffee index 71b0ff9d7..24ad18bb6 100644 --- a/app/views/play/modal/BuyGemsModal.coffee +++ b/app/views/play/modal/BuyGemsModal.coffee @@ -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($('