diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 987e29bdb..8f0069d6f 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -96,10 +96,10 @@ logging_in: "Logging In" log_out: "Log Out" recover: "recover account" - authenticate_gplus: 'Authenticate G+' - load_profile: 'Load G+ Profile' - load_email: 'Load G+ Email' - finishing: 'Finishing' + authenticate_gplus: "Authenticate G+" + load_profile: "Load G+ Profile" + load_email: "Load G+ Email" + finishing: "Finishing" signup: create_account_title: "Create Account to Save Progress" @@ -319,12 +319,15 @@ unequip: "Unequip" buy_gems: - few_gems: 'A few gems' - pile_gems: 'Pile of gems' - chest_gems: 'Chest of gems' - purchasing: 'Purchasing...' - declined: 'Your card was declined' - retrying: 'Server error, retrying.' + few_gems: "A few gems" + pile_gems: "Pile of gems" + chest_gems: "Chest of gems" + purchasing: "Purchasing..." + declined: "Your card was declined" + retrying: "Server error, retrying." + prompt_title: "Not Enough Gems" + prompt_body: "Do you want to get more?" + prompt_button: "Enter Shop" choose_hero: choose_hero: "Choose Your Hero" diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee index 31218bf26..1191eb16a 100644 --- a/app/models/ThangType.coffee +++ b/app/models/ThangType.coffee @@ -19,6 +19,10 @@ module.exports = class ThangType extends CocoModel librarian: '52fbf74b7e01835453bd8d8e' 'potion-master': '52e9adf7427172ae56002172' sorcerer: '52fd1524c7e6cf99160e7bc9' + @heroClasses: + Warrior: ['captain', 'knight', 'samurai'] + Ranger: ['ninja', 'forest-archer', 'trapper'] + Wizard: ['librarian', 'potion-master', 'sorcerer'] @items: 'simple-boots': '53e237bf53457600003e3f05' urlRoot: '/db/thang.type' @@ -447,7 +451,7 @@ module.exports = class ThangType extends CocoModel levelRequiredForItem: -> return console.error "Trying to determine what level is required for #{@get('name')}, but it has no tier." unless @get('tier')? itemTier = @get 'tier' - playerTier = itemTier / 2 + playerTier = itemTier / 2.5 playerLevel = me.constructor.levelForTier playerTier #console.log 'Level required for', @get('name'), 'is', playerLevel, 'player tier', playerTier, 'because it is itemTier', itemTier, 'which is normally level', me.constructor.levelForTier(itemTier) playerLevel diff --git a/app/models/User.coffee b/app/models/User.coffee index 07d1b83de..4896c98bb 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -95,6 +95,13 @@ module.exports = class User extends CocoModel ownsItem: (itemOriginal) -> itemOriginal in @items() ownsLevel: (levelOriginal) -> levelOriginal in @levels() + getHeroClasses: -> + idsToSlugs = _.invert ThangType.heroes + myHeroSlugs = (idsToSlugs[id] for id in @heroes()) + myHeroClasses = [] + myHeroClasses.push heroClass for heroClass, heroSlugs of ThangType.heroClasses when _.intersection(myHeroSlugs, heroSlugs).length + myHeroClasses + getBranchingGroup: -> return @branchingGroup if @branchingGroup group = me.get('testGroupNumber') % 4 @@ -107,4 +114,14 @@ module.exports = class User extends CocoModel application.tracker.identify branchingGroup: @branchingGroup unless me.isAdmin() @branchingGroup + getGemPromptGroup: -> + return @gemPromptGroup if @gemPromptGroup + group = me.get('testGroupNumber') % 8 + @gemPromptGroup = switch group + when 0, 1, 2, 3 then 'prompt' + when 4, 5, 6, 7 then 'no-prompt' + @gemPromptGroup = 'prompt' if me.isAdmin() + application.tracker.identify gemPromptGroup: @gemPromptGroup unless me.isAdmin() + @gemPromptGroup + tiersByLevel = [-1, 0, 0.05, 0.14, 0.18, 0.32, 0.41, 0.5, 0.64, 0.82, 0.91, 1.04, 1.22, 1.35, 1.48, 1.65, 1.78, 1.96, 2.1, 2.24, 2.38, 2.55, 2.69, 2.86, 3.03, 3.16, 3.29, 3.42, 3.58, 3.74, 3.89, 4.04, 4.19, 4.32, 4.47, 4.64, 4.79, 4.96] diff --git a/app/styles/common/common.sass b/app/styles/common/common.sass index 9c70cd67f..c97aea9a3 100644 --- a/app/styles/common/common.sass +++ b/app/styles/common/common.sass @@ -228,6 +228,9 @@ kbd border-width: 15px 20px .arrow display: none + .btn + font-size: 20px + width: 100% .btn.btn-illustrated background: 0 @@ -242,6 +245,9 @@ kbd font-weight: bold color: rgb(248, 197, 146) + &:hover + color: lighten(rgb(248, 197, 146), 5%) + &:active border-image: url(/images/common/button-background-pressed-border.png) 14 16 16 20 fill round padding: 2px 0 0 2px diff --git a/app/styles/play/modal/play-items-modal.sass b/app/styles/play/modal/play-items-modal.sass index eddc2b276..ab7a3c5ca 100644 --- a/app/styles/play/modal/play-items-modal.sass +++ b/app/styles/play/modal/play-items-modal.sass @@ -307,6 +307,13 @@ opacity: 1 color: rgba(255,255,255, 0.4) +// Make sure this shows up above our modals. +.popover.buy-gems-prompt + z-index: 1050 + text-align: center + + button + margin-top: 20px //- Use the two-column layout and background image if we are on a narrow screen. diff --git a/app/templates/play/modal/buy-gems-prompt.jade b/app/templates/play/modal/buy-gems-prompt.jade new file mode 100644 index 000000000..366f986e1 --- /dev/null +++ b/app/templates/play/modal/buy-gems-prompt.jade @@ -0,0 +1,6 @@ +.popover.buy-gems-prompt(role="tooltip") + .arrow + h2(data-i18n="buy_gems.prompt_title") Not Enough Gems + p(data-i18n="buy_gems.prompt_body") Do you want to get more? + button.btn.btn-success.btn-illustrated.btn-lg.buy-gems-prompt-button(data-i18n="buy_gems.prompt_button") Enter Shop + \ 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 3da543a79..ad2609aa1 100644 --- a/app/templates/play/modal/item-details-view.jade +++ b/app/templates/play/modal/item-details-view.jade @@ -37,7 +37,7 @@ if item && !item.owned // Temp, while we only have Warriors: prevent them from buying non-Warrior stuff button.btn.big-font.disabled.unequippable #{item.get('heroClass')} Only else - button#selected-item-unlock-button.btn.big-font.unlock-button(disabled=!item.affordable, data-item-id=item.id) + button#selected-item-unlock-button.btn.big-font.unlock-button(data-item-id=item.id) span(data-i18n="play.unlock") Unlock span.spl= '('+item.get('gems') img(src="/images/common/gem.png", draggable="false") diff --git a/app/templates/play/modal/play-items-modal.jade b/app/templates/play/modal/play-items-modal.jade index 08d0d52ba..05656e783 100644 --- a/app/templates/play/modal/play-items-modal.jade +++ b/app/templates/play/modal/play-items-modal.jade @@ -53,7 +53,7 @@ // Temp, while we only have Warriors: prevent them from buying non-Warrior stuff span.big-font.unequippable= item.get('heroClass') else - button.btn.unlock-button.big-font(data-i18n="play.unlock", disabled=!item.affordable, data-item-id=item.id) + button.btn.unlock-button.big-font(data-i18n="play.unlock", data-item-id=item.id) .clearfix #item-details-view diff --git a/app/views/game-menu/InventoryModal.coffee b/app/views/game-menu/InventoryModal.coffee index ba4b1cbf6..22f4c5764 100644 --- a/app/views/game-menu/InventoryModal.coffee +++ b/app/views/game-menu/InventoryModal.coffee @@ -1,5 +1,6 @@ ModalView = require 'views/kinds/ModalView' template = require 'templates/game-menu/inventory-modal' +buyGemsPromptTemplate = require 'templates/play/modal/buy-gems-prompt' {me} = require 'lib/auth' ThangType = require 'models/ThangType' CocoCollection = require 'collections/CocoCollection' @@ -8,6 +9,7 @@ SpriteBuilder = require 'lib/sprites/SpriteBuilder' ItemDetailsView = require 'views/play/modal/ItemDetailsView' Purchase = require 'models/Purchase' LevelOptions = require 'lib/LevelOptions' +BuyGemsModal = require 'views/play/modal/BuyGemsModal' hasGoneFullScreenOnce = false @@ -31,6 +33,8 @@ module.exports = class InventoryModal extends ModalView 'click #equip-item-viewed': 'onClickEquipItemViewed' 'click #unequip-item-viewed': 'onClickUnequipItemViewed' 'click #close-modal': 'hide' + 'click .buy-gems-prompt-button': 'onBuyGemsPromptButtonClicked' + 'click': 'onClickedSomewhere' shortcuts: 'esc': 'clearSelection' @@ -224,40 +228,6 @@ module.exports = class InventoryModal extends ModalView @selectUnequippedItem(itemEl) @equipSelectedItem() - onUnlockButtonClicked: (e) -> - button = $(e.target).closest('button') - if button.hasClass('confirm') - item = @items.get($(e.target).data('item-id')) - purchase = Purchase.makeFor(item) - purchase.save() - - #- set local changes to mimic what should happen on the server... - purchased = me.get('purchased') ? {} - purchased.items ?= [] - purchased.items.push(item.get('original')) - - me.set('purchased', purchased) - me.set('spent', (me.get('spent') ? 0) + item.get('gems')) - - #- ...then rerender key bits - @itemGroups.lockedItems.remove(item) - # Redo all item sorting to make sure that we don't clobber state changes since last render. - equipped = _.values @getCurrentEquipmentConfig() - @sortItem(item, equipped) for item in @items.models - @renderSelectors('#unequipped', '#gems-count') - - @requireLevelEquipment() - @delegateEvents() - @setUpDraggableEventsForAvailableEquipment() - @itemDetailsView.setItem(item) - - Backbone.Mediator.publish 'store:item-purchased', item: item, itemSlug: item.get('slug') - else - button.addClass('confirm').text($.i18n.t('play.confirm')) - @$el.one 'click', (e) -> - button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0] - - #- Select/equip higher-level, all encompassing methods the callbacks all use selectItemSlot: (slotEl) -> @@ -470,7 +440,70 @@ module.exports = class InventoryModal extends ModalView else callback?() + #- TODO: DRY this between PlayItemsModal and InventoryModal + + onUnlockButtonClicked: (e) -> + e.stopPropagation() + button = $(e.target).closest('button') + item = @items.get(button.data('item-id')) + affordable = item.affordable + if not affordable + @askToBuyGems button + else if button.hasClass('confirm') + purchase = Purchase.makeFor(item) + purchase.save() + + #- set local changes to mimic what should happen on the server... + purchased = me.get('purchased') ? {} + purchased.items ?= [] + purchased.items.push(item.get('original')) + + me.set('purchased', purchased) + me.set('spent', (me.get('spent') ? 0) + item.get('gems')) + + #- ...then rerender key bits + @itemGroups.lockedItems.remove(item) + # Redo all item sorting to make sure that we don't clobber state changes since last render. + equipped = _.values @getCurrentEquipmentConfig() + @sortItem(item, equipped) for item in @items.models + @renderSelectors('#unequipped', '#gems-count') + + @requireLevelEquipment() + @delegateEvents() + @setUpDraggableEventsForAvailableEquipment() + @itemDetailsView.setItem(item) + + Backbone.Mediator.publish 'store:item-purchased', item: item, itemSlug: item.get('slug') + else + button.addClass('confirm').text($.i18n.t('play.confirm')) + @$el.one 'click', (e) -> + button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0] + + askToBuyGems: (unlockButton) -> + if me.getGemPromptGroup() is 'no-prompt' + return @openModalView new BuyGemsModal() + @$el.find('.unlock-button').popover 'destroy' + popoverTemplate = buyGemsPromptTemplate {} + unlockButton.popover( + animation: true + trigger: 'manual' + placement: 'top' + content: ' ' # template has it + container: @$el + template: popoverTemplate + ).popover 'show' + popover = unlockButton.data('bs.popover') + popover?.$tip?.i18n() + + onBuyGemsPromptButtonClicked: (e) -> + @openModalView new BuyGemsModal() + + onClickedSomewhere: (e) -> + return if @destroyed + @$el.find('.unlock-button').popover 'destroy' + destroy: -> + @$el.find('.unlock-button').popover 'destroy' @stage?.removeAllChildren() super() diff --git a/app/views/play/modal/PlayItemsModal.coffee b/app/views/play/modal/PlayItemsModal.coffee index fd4b9c6a5..74b17fcdf 100644 --- a/app/views/play/modal/PlayItemsModal.coffee +++ b/app/views/play/modal/PlayItemsModal.coffee @@ -1,6 +1,8 @@ ModalView = require 'views/kinds/ModalView' template = require 'templates/play/modal/play-items-modal' +buyGemsPromptTemplate = require 'templates/play/modal/buy-gems-prompt' ItemDetailsView = require './ItemDetailsView' +BuyGemsModal = require 'views/play/modal/BuyGemsModal' CocoCollection = require 'collections/CocoCollection' ThangType = require 'models/ThangType' @@ -46,7 +48,9 @@ module.exports = class PlayItemsModal extends ModalView 'click .item': 'onItemClicked' 'shown.bs.tab': 'onTabClicked' 'click .unlock-button': 'onUnlockButtonClicked' + 'click .buy-gems-prompt-button': 'onBuyGemsPromptButtonClicked' 'click #close-modal': 'hide' + 'click': 'onClickedSomewhere' constructor: (options) -> super options @@ -89,7 +93,7 @@ module.exports = class PlayItemsModal extends ModalView model.affordable = cost <= gemsOwned model.silhouetted = not model.owned and model.isSilhouettedItem() model.level = model.levelRequiredForItem() if model.get('tier')? - model.unequippable = not ('Warrior' in model.getAllowedHeroClasses()) # Temp: while there are no wizards/rangers + model.unequippable = not _.intersection(me.getHeroClasses(), model.getAllowedHeroClasses()).length model.comingSoon = not model.getFrontFacingStats().props.length and not _.size(model.getFrontFacingStats().stats) and not model.owned # Temp: while there are placeholder items @idToItem[model.id] = model @@ -140,9 +144,14 @@ module.exports = class PlayItemsModal extends ModalView $($(e.target).attr('href')).find('.nano').nanoScroller({alwaysVisible: true}) onUnlockButtonClicked: (e) -> + e.stopPropagation() button = $(e.target).closest('button') - if button.hasClass('confirm') - item = @idToItem[$(e.target).data('item-id')] + item = @idToItem[button.data('item-id')] + affordable = item.affordable + if not affordable + @askToBuyGems button + else if button.hasClass('confirm') + purchase = Purchase.makeFor(item) purchase.save() @@ -163,3 +172,29 @@ module.exports = class PlayItemsModal extends ModalView button.addClass('confirm').text($.i18n.t('play.confirm')) @$el.one 'click', (e) -> button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0] + + askToBuyGems: (unlockButton) -> + if me.getGemPromptGroup() is 'no-prompt' + return @openModalView new BuyGemsModal() + @$el.find('.unlock-button').popover 'destroy' + popoverTemplate = buyGemsPromptTemplate {} + unlockButton.popover( + animation: true + trigger: 'manual' + content: ' ' # template has it + container: @$el + template: popoverTemplate + ).popover 'show' + popover = unlockButton.data('bs.popover') + popover?.$tip?.i18n() + + onBuyGemsPromptButtonClicked: (e) -> + @openModalView new BuyGemsModal() + + onClickedSomewhere: (e) -> + return if @destroyed + @$el.find('.unlock-button').popover 'destroy' + + destroy: -> + @$el.find('.unlock-button').popover 'destroy' + super()