From ce04541d3a7d5f5abe21953c4f3972df8d22c8ac Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Mon, 10 Nov 2014 22:07:55 -0800 Subject: [PATCH] Working on tying item purchasability to player level, plus other small tweaks to the item store. --- app/locale/en.coffee | 1 + app/models/ThangType.coffee | 12 ++++++++++-- app/models/User.coffee | 15 +++++++++++++++ app/schemas/models/thang_type.coffee | 1 + app/styles/play/modal/play-items-modal.sass | 17 ++++++++++++++++- app/templates/play/modal/play-items-modal.jade | 7 ++++++- app/templates/play/world-map-view.jade | 2 ++ app/views/HomeView.coffee | 3 --- app/views/game-menu/InventoryModal.coffee | 17 +++++++++-------- app/views/play/modal/PlayItemsModal.coffee | 13 +++++++------ server/levels/thangs/thang_type_handler.coffee | 3 ++- 11 files changed, 69 insertions(+), 22 deletions(-) diff --git a/app/locale/en.coffee b/app/locale/en.coffee index afcffb0f8..08db78355 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -167,6 +167,7 @@ medium: "Medium" hard: "Hard" player: "Player" + player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard units: second: "second" diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee index 848c82f29..789abab76 100644 --- a/app/models/ThangType.coffee +++ b/app/models/ThangType.coffee @@ -396,8 +396,16 @@ module.exports = class ThangType extends CocoModel name: name, display: display isSilhouettedItem: -> - # TODO: have items have actual levels instead of just going by their gem count return console.error "Trying to determine whether #{@get('name')} should be a silhouetted item, but it has no gem cost." unless @get 'gems' + console.info "Add (or make sure you have fetched) a tier for #{@get('name')} to more accurately determine whether it is silhouetted." unless @get('tier')? + tier = @get 'tier' + if tier? + return @levelRequiredForItem() > me.level() points = me.get('points') expectedTotalGems = (points ? 0) * 1.5 # Not actually true, but roughly kinda close for tier 0, kinda tier 1 - @get('gems') > (100 + expectedTotalGems) * 2 + @get('gems') > (100 + expectedTotalGems) * 1.2 + + levelRequiredForItem: -> + return console.error "Trying to determine what level is required for #{@get('name')}, but it has no tier." unless @get('tier')? + tier = @get 'tier' + me.constructor.levelForTier(Math.pow(tier, 0.7)) diff --git a/app/models/User.coffee b/app/models/User.coffee index 9e760cc17..7fab3e260 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -63,9 +63,22 @@ module.exports = class User extends CocoModel @expForLevel: (level) -> if level > 1 then Math.ceil Math.exp((level - 1)/ a) * b - c else 0 + @tierFromLevel: (level) -> + # TODO: math + # For now, just eyeball it. + tiersByLevel[Math.min(level, tiersByLevel.length - 1)] + + @levelForTier: (tier) -> + # TODO: math + for tierThreshold, level in tiersByLevel + return level if tierThreshold >= tier + level: -> User.levelFromExp(@get('points')) + tier: -> + User.tierFromLevel @level() + gems: -> gemsEarned = @get('earned')?.gems ? 0 gemsPurchased = @get('purchased')?.gems ? 0 @@ -146,3 +159,5 @@ module.exports = class User extends CocoModel @kithmazeGroup = 'haunted-kithmaze' if me.isAdmin() application.tracker.identify kithmazeGroup: @kithmazeGroup unless me.isAdmin() @kithmazeGroup + +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/schemas/models/thang_type.coffee b/app/schemas/models/thang_type.coffee index 08db1e67b..7d74b0ef9 100644 --- a/app/schemas/models/thang_type.coffee +++ b/app/schemas/models/thang_type.coffee @@ -109,6 +109,7 @@ _.extend ThangTypeSchema.properties, terrains: c.array {title: 'Terrains', description: 'If specified, limits this ThangType to levels with matching terrains.', uniqueItems: true}, c.terrainString gems: {type: 'integer', minimum: 0, title: 'Gem Cost', description: 'How many gems this item or hero costs.'} heroClass: {type: 'string', enum: ['Warrior', 'Ranger', 'Wizard'], title: 'Hero Class', description: 'What class this is (if a hero) or is restricted to (if an item). Leave undefined for most items.'} + tier: {type: 'number', minimum: 0, title: 'Tier', description: 'What tier (fractional) this item or hero is in.'} actions: c.object {title: 'Actions', additionalProperties: {$ref: '#/definitions/action'}} soundTriggers: c.object {title: 'Sound Triggers', additionalProperties: c.array({}, {$ref: '#/definitions/sound'})}, say: c.object {format: 'slug-props', additionalProperties: {$ref: '#/definitions/sound'}}, diff --git a/app/styles/play/modal/play-items-modal.sass b/app/styles/play/modal/play-items-modal.sass index 19e5dd548..a8a55e1c4 100644 --- a/app/styles/play/modal/play-items-modal.sass +++ b/app/styles/play/modal/play-items-modal.sass @@ -139,6 +139,9 @@ margin: 4px text-align: center position: relative + + &.silhouetted + cursor: default strong position: absolute @@ -275,7 +278,19 @@ .item-silhouette @include filter(contrast(0%) brightness(0%)) opacity: 0.3 - + + .required-level + position: absolute + left: 0 + right: 5px + top: 70px + font-size: 20px + line-height: 20px + font-family: Open Sans Condensed + text-transform: uppercase + font-weight: bold + z-index: 2 + //- Unlock buttons (both in list and details areas) diff --git a/app/templates/play/modal/play-items-modal.jade b/app/templates/play/modal/play-items-modal.jade index 38e4be7d9..ec6b2d21d 100644 --- a/app/templates/play/modal/play-items-modal.jade +++ b/app/templates/play/modal/play-items-modal.jade @@ -26,11 +26,16 @@ .nano-content for item in itemCategoryCollections[category].models - var hidden = item.comingSoon && !me.isAdmin() - div(class="item" + (hidden ? " hide" : ""), data-item-id=item.id) + - hidden = hidden || (!item.get('gems') && !item.owned) + div(class="item" + (hidden ? " hide" : "") + (item.silhouetted && !item.owned ? " silhouetted" : ""), data-item-id=item.id) if item.silhouetted && !item.owned span.glyphicon.glyphicon-lock.bolder span.glyphicon.glyphicon-lock img.item-silhouette(src=item.getPortraitURL()) + if item.level + .required-level + div(data-i18n="general.player_level") + div= item.level else strong.big-font= item.name img.item-img(src=item.getPortraitURL()) diff --git a/app/templates/play/world-map-view.jade b/app/templates/play/world-map-view.jade index 74d0ca9e1..3290ea6d5 100644 --- a/app/templates/play/world-map-view.jade +++ b/app/templates/play/world-map-view.jade @@ -64,6 +64,8 @@ .user-status.header-font span.gem.gem-20 span.spr= me.gems() + span.spl.spr(data-i18n="general.player_level") + span.spr= me.level() if me.get('anonymous') span.spr(data-i18n="play.anonymous_player") Anonymous Player button.btn.btn-default.btn-flat.btn-sm(data-toggle='coco-modal', data-target='modal/AuthModal', data-i18n="login.log_in") diff --git a/app/views/HomeView.coffee b/app/views/HomeView.coffee index 7b5064e10..7bc31c09e 100644 --- a/app/views/HomeView.coffee +++ b/app/views/HomeView.coffee @@ -4,8 +4,6 @@ WizardLank = require 'lib/surface/WizardLank' ThangType = require 'models/ThangType' Simulator = require 'lib/simulator/Simulator' -InventoryModal = require 'views/game-menu/InventoryModal' - {me} = require '/lib/auth' module.exports = class HomeView extends RootView @@ -40,4 +38,3 @@ module.exports = class HomeView extends RootView afterInsert: -> super(arguments...) - @openModalView(new InventoryModal({levelID: 'the-raised-sword'})) \ No newline at end of file diff --git a/app/views/game-menu/InventoryModal.coffee b/app/views/game-menu/InventoryModal.coffee index 3da689a25..dc1931d87 100644 --- a/app/views/game-menu/InventoryModal.coffee +++ b/app/views/game-menu/InventoryModal.coffee @@ -44,14 +44,15 @@ module.exports = class InventoryModal extends ModalView # TODO: switch to item store loading system? @items.url = '/db/thang.type?view=items' @items.setProjection [ - 'name', - 'slug', - 'components', - 'original', - 'rasterIcon', - 'gems', - 'description', - 'heroClass', + 'name' + 'slug' + 'components' + 'original' + 'rasterIcon' + 'gems' + 'tier' + 'description' + 'heroClass' 'i18n' ] @supermodel.loadCollection(@items, 'items') diff --git a/app/views/play/modal/PlayItemsModal.coffee b/app/views/play/modal/PlayItemsModal.coffee index 2295ed2ad..88805a346 100644 --- a/app/views/play/modal/PlayItemsModal.coffee +++ b/app/views/play/modal/PlayItemsModal.coffee @@ -62,6 +62,7 @@ module.exports = class PlayItemsModal extends ModalView 'original' 'rasterIcon' 'gems' + 'tier' 'i18n' 'heroClass' ] @@ -77,7 +78,8 @@ module.exports = class PlayItemsModal extends ModalView gemsOwned = me.gems() needMore = itemFetcher.models.length is PAGE_SIZE for model in itemFetcher.models - continue unless cost = model.get('gems') + model.owned = me.ownsItem model.get('original') + continue unless (cost = model.get('gems')) or model.owned category = slotToCategory[model.getAllowedSlots()[0]] or 'misc' @itemCategoryCollections[category] ?= new Backbone.Collection() collection = @itemCategoryCollections[category] @@ -85,10 +87,10 @@ module.exports = class PlayItemsModal extends ModalView collection.add(model) model.name = utils.i18n model.attributes, 'name' model.affordable = cost <= gemsOwned - model.owned = me.ownsItem model.get('original') - model.silhouetted = model.isSilhouettedItem() + model.silhouetted = not model.owned and model.isSilhouettedItem() + model.level = model.levelRequiredForItem() if model.get('tier')? model.equippable = 'Warrior' in model.getAllowedHeroClasses() # Temp: while there are no wizards/rangers - model.comingSoon = not model.getFrontFacingStats().props.length and not _.size model.getFrontFacingStats().stats # Temp: while there are placeholder items + 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 if needMore @@ -127,7 +129,7 @@ module.exports = class PlayItemsModal extends ModalView item = null else item = @idToItem[itemEl.data('item-id')] - if item.silhouetted + if item.silhouetted and not item.owned item = null else itemEl.addClass('selected') unless wasSelected @@ -158,4 +160,3 @@ 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] - diff --git a/server/levels/thangs/thang_type_handler.coffee b/server/levels/thangs/thang_type_handler.coffee index 490a1492e..833129c13 100644 --- a/server/levels/thangs/thang_type_handler.coffee +++ b/server/levels/thangs/thang_type_handler.coffee @@ -40,6 +40,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler 'description' 'gems' 'heroClass' + 'tier' 'extendedName' ] @@ -61,7 +62,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler query = slug: {$exists: true} if req.query.view is 'items' query.kind = 'Item' - query.gems = {$exists: true} # Items without gems don't show up anywhere + query.tier = {$exists: true} # Items without a tier don't show up anywhere, whereas items without gems don't show up in the store else if req.query.view is 'heroes' #query.kind = 'Hero' # TODO: when ChooseHeroView is refactored, just use this query.original = {$in: _.values heroes} # TODO: when ChooseHeroView is refactored, don't do this