diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 66416dc08..afcffb0f8 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -301,10 +301,13 @@ choose_inventory: "Equip Items" equipped_item: "Equipped" available_item: "Available" + restricted_title: "Restricted" should_equip: "(double-click to equip)" equipped: "(equipped)" locked: "(locked)" restricted: "(restricted in this level)" + equip: "Equip" + unequip: "Unequip" choose_hero: choose_hero: "Choose Your Hero" diff --git a/app/styles/game-menu/inventory-modal.sass b/app/styles/game-menu/inventory-modal.sass index e3d022a80..be8bd00cd 100644 --- a/app/styles/game-menu/inventory-modal.sass +++ b/app/styles/game-menu/inventory-modal.sass @@ -1,18 +1,8 @@ @import "app/styles/mixins" -$totalWidth: 706px - 2 * 20px -$inventoryHeight: 445px -$equippedWidth: 450px -$itemSlotMargin: 5px -$itemSlotSize: ($equippedWidth - 6 * 2 * $itemSlotMargin) / 6 -$itemSlotSizeWithMargin: $itemSlotSize + 2 * $itemSlotMargin -$itemSlotBorderWidth: 2px -$itemSlotInnerWidth: $itemSlotSize - 2 * $itemSlotBorderWidth -$heroContainerWidth: 4 * $itemSlotSizeWithMargin -$heroContainerHeight: $inventoryHeight - $itemSlotSizeWithMargin -$selectedAreaHeight: 150px -$stashMargin: 20px -$stashWidth: $totalWidth - $equippedWidth - $stashMargin +$itemSlotSize: 55px +$itemSlotInnerWidth: $itemSlotSize - 4 +$itemSlotGridHeight: 70px .ui-effects-transfer outline: 2px solid #28f @@ -20,318 +10,434 @@ $stashWidth: $totalWidth - $equippedWidth - $stashMargin z-index: 9001 #inventory-modal + + //- Overall modal structure .modal-dialog margin: 30px auto 0 auto - width: 720px + width: 1017px + height: 660px + + .modal-content + height: 100% + width: 100% - .modal-body - height: 450px - margin: 0 - - +user-select(none) - - h3 + .modal-body + height: 450px margin: 0 - .draggable-item - width: $itemSlotSize - height: $itemSlotSize + +user-select(none) + + //- Background + + #play-items-modal-narrow-bg + position: absolute + top: -69px + left: -8px + + + //- Gems count + + #gems-count-container + position: absolute + left: 213px + top: 10px + width: 160px + height: 66px + @include rotate(5deg) + + #gems-count + position: absolute + left: 75px + top: 17px + font-size: 25px + color: rgb(1,64,91) + + + //- Close modal button + + #close-modal + position: absolute + left: 390px + top: 23px + width: 60px + height: 60px + color: white + text-align: center + font-size: 30px + padding-top: 7px + cursor: pointer + @include rotate(-3deg) + + &:hover + color: yellow + + + //- Equipped area + #equipped - width: $equippedWidth + width: 330px + background: white + border: 3px solid black position: absolute left: 20px - top: 0 - bottom: 0 - //bottom: $selectedAreaHeight + 10 - right: 0 + top: 112px + height: 450px overflow: hidden - .item-slot-row - //background-color: rgba(35, 112, 124, 0.5) - height: $itemSlotSizeWithMargin - clear: both - margin: 0px auto + #hero-image + @include filter(contrast(0%) brightness(0%)) + opacity: 0.4 + width: 225px + height: 410px + position: absolute + left: 10px + top: 20px - &.row-4 - width: 4 * $itemSlotSizeWithMargin - - .item-slot-column - //background-color: rgba(112, 124, 35, 0.5) - width: $itemSlotSizeWithMargin - height: 5 * $itemSlotSizeWithMargin - float: left - //margin-top: 30px - .item-slot width: $itemSlotSize height: $itemSlotSize - margin: $itemSlotMargin - background-color: white - float: left + margin: 5px + background-color: rgba(255,255,255,0.4) + border: 2px dashed rgb(100,100,150) position: relative cursor: pointer @include transition(0.5s ease) - - &.selected - .placeholder, .item-container .item-view img - border-color: #28f - @include box-shadow(0 0 10px #28f) + //&.disabled + // opacity: 0.5 + + &.selected + .placeholder, img.item + border-color: rgb(81,153,236) + background-color: rgb(81,153,236) + @include box-shadow(0 0 10px rgb(81,153,236)) + img.item + background: rgb(81,153,236) + &.should-equip background-color: #8fa outline: 2px solid #8af @include box-shadow(2px 2px 4px black) - + &.droppable outline: 2px solid blue @include box-shadow(4px 4px 6px black) - + &.droppable-hover outline: 4px solid blue @include box-shadow(6px 6px 8px black) - + .placeholder width: 100% height: 100% background-size: cover - border: $itemSlotBorderWidth solid #888 - @include opacity(0.4) + @include opacity(0.7) background-image: url(/images/pages/game-menu/slot-icons.png) + - &[data-slot="misc-1"] .placeholder - background-position: (-0 * $itemSlotInnerWidth) 0px + // A terrible awful bit of styling, but will be gone or messed around with soon anyway - &[data-slot="minion"] .placeholder - background-position: (-1 * $itemSlotInnerWidth) 0px + &[data-slot="misc-1"] + display: none // hiding for now + position: absolute + left: 250px + top: 20px + ($itemSlotGridHeight * 3) + .placeholder + background-position: (-0 * $itemSlotInnerWidth) 0px + + &[data-slot="misc-0"] + display: none // hiding for now + position: absolute + left: 250px + top: 20px + ($itemSlotGridHeight * 2) + .placeholder + background-position: (-4 * $itemSlotInnerWidth) 0px - &[data-slot="programming-book"] .placeholder - background-position: (-3 * $itemSlotInnerWidth) 0px + &[data-slot="minion"] + position: absolute + left: 250px + top: 20px + ($itemSlotGridHeight * 1) + .placeholder + background-position: (-1 * $itemSlotInnerWidth) 0px + + &[data-slot="programming-book"] + position: absolute + left: 250px + top: 20px + ($itemSlotGridHeight * 4) + .placeholder + background-position: (-3 * $itemSlotInnerWidth) 0px + // Only for wizards... //&[data-slot="spellbook"] .placeholder // background-position: (-2 * $itemSlotInnerWidth) 0px - - &[data-slot="misc-0"] .placeholder - background-position: (-4 * $itemSlotInnerWidth) 0px - - &[data-slot="wrists"] .placeholder - background-position: (-5 * $itemSlotInnerWidth) 0px - - &[data-slot="left-ring"] .placeholder - background-position: (-6 * $itemSlotInnerWidth) 0px - - &[data-slot="right-ring"] .placeholder - background-position: (-7 * $itemSlotInnerWidth) 0px - - &[data-slot="torso"] .placeholder - background-position: (-8 * $itemSlotInnerWidth) 0px - - &[data-slot="feet"] .placeholder - background-position: (-9 * $itemSlotInnerWidth) 0px - - &[data-slot="neck"] .placeholder - background-position: (-10 * $itemSlotInnerWidth) 0px - - &[data-slot="waist"] .placeholder - background-position: (-11 * $itemSlotInnerWidth) 0px - - &[data-slot="eyes"] .placeholder - background-position: (-12 * $itemSlotInnerWidth) 0px - - &[data-slot="head"] .placeholder - background-position: (-13 * $itemSlotInnerWidth) 0px - - &[data-slot="pet"] .placeholder - background-position: (-14 * $itemSlotInnerWidth) 0px - - &[data-slot="gloves"] .placeholder - background-position: (-15 * $itemSlotInnerWidth) 0px - - &[data-slot="left-hand"] .placeholder - background-position: (-16 * $itemSlotInnerWidth) 0px - - &[data-slot="right-hand"] .placeholder - background-position: (-17 * $itemSlotInnerWidth) 0px - - &[data-slot="flag"] .placeholder - //background-position: (-18 * $itemSlotInnerWidth) 0px - background-position: (-2 * $itemSlotInnerWidth) 0px - - .item-container + + &[data-slot="wrists"] position: absolute - left: 0 - top: 0 + left: 20px + top: 20px + ($itemSlotGridHeight * 2.5) + .placeholder + background-position: (-5 * $itemSlotInnerWidth) 0px + + &[data-slot="left-ring"] + position: absolute + left: 250px + top: 20px + ($itemSlotGridHeight * 2) + .placeholder + background-position: (-6 * $itemSlotInnerWidth) 0px + + &[data-slot="right-ring"] + position: absolute + left: 250px + top: 20px + ($itemSlotGridHeight * 3) + .placeholder + background-position: (-7 * $itemSlotInnerWidth) 0px + + &[data-slot="torso"] + position: absolute + left: 90px + top: 20px + ($itemSlotGridHeight * 3) + .placeholder + background-position: (-8 * $itemSlotInnerWidth) 0px + + &[data-slot="feet"] + position: absolute + left: 90px + top: 20px + ($itemSlotGridHeight * 5) + .placeholder + background-position: (-9 * $itemSlotInnerWidth) 0px + + &[data-slot="neck"] + position: absolute + left: 90px + top: 20px + ($itemSlotGridHeight * 2) + .placeholder + background-position: (-10 * $itemSlotInnerWidth) 0px + + &[data-slot="waist"] + position: absolute + left: 90px + top: 20px + ($itemSlotGridHeight * 4) + .placeholder + background-position: (-11 * $itemSlotInnerWidth) 0px + + &[data-slot="eyes"] + position: absolute + left: 90px + top: 20px + $itemSlotGridHeight + .placeholder + background-position: (-12 * $itemSlotInnerWidth) 0px + + &[data-slot="head"] + position: absolute + left: 90px + top: 20px + .placeholder + background-position: (-13 * $itemSlotInnerWidth) 0px + + &[data-slot="pet"] + position: absolute + left: 250px + top: 20px + .placeholder + background-position: (-14 * $itemSlotInnerWidth) 0px + + &[data-slot="gloves"] + position: absolute + left: 160px + top: 20px + ($itemSlotGridHeight * 2.5) + .placeholder + background-position: (-15 * $itemSlotInnerWidth) 0px + + &[data-slot="left-hand"] + position: absolute + left: 160px + top: 20px + ($itemSlotGridHeight * 3.5) + .placeholder + background-position: (-16 * $itemSlotInnerWidth) 0px + + &[data-slot="right-hand"] + position: absolute + left: 20px + top: 20px + ($itemSlotGridHeight * 3.5) + .placeholder + background-position: (-17 * $itemSlotInnerWidth) 0px + + &[data-slot="flag"] + position: absolute + left: 250px + top: 20px + ($itemSlotGridHeight * 5) + .placeholder + background-position: (-2 * $itemSlotInnerWidth) 0px + + img.item + position: absolute + left: -2px + top: -2px - .item-view - img - width: $itemSlotSize - height: $itemSlotSize - border: 2px solid black - background-color: white + width: $itemSlotSize + height: $itemSlotSize + border: 2px solid black + background-color: white - .item-info - display: none + + //- dragging styling + + #equipped - .item-slot.disabled - opacity: 0.5 + &.droppable + outline: 2px solid blue + @include box-shadow(4px 4px 6px black) - .hero-container - //background-color: rgba(31, 0, 200, 0.25) - float: left - position: relative - @include transition(0.5s ease) + &.droppable-hover + outline: 4px solid blue + @include box-shadow(6px 6px 8px black) + + .draggable-item + width: $itemSlotSize * 1.2 + height: $itemSlotSize * 1.2 - &.droppable - outline: 2px solid blue - @include box-shadow(4px 4px 6px black) - - &.droppable-hover - outline: 4px solid blue - @include box-shadow(6px 6px 8px black) - - .equipped-hero-canvas, .hero-feature-image - width: $heroContainerWidth - height: $heroContainerHeight - - .hero-feature-image - text-align: center - - img - height: $heroContainerHeight - - #available-equipment - width: $stashWidth - position: absolute - right: 20px - top: 0 - bottom: 0 - overflow-y: scroll - border: 2px solid #ccc - padding: 4px - background-color: white - - &.Warrior .list-group-item:not(.Warrior), &.Ranger .list-group-item:not(.Ranger), &.Wizard .list-group-item:not(.Wizard) + + //- Available equipment + + &.Warrior #unequipped img.item:not(.Warrior), &.Ranger #unequipped img.item:not(.Ranger), &.Wizard #unequipped img.item:not(.Wizard) // Our code hides and shows (modifies display), but we can be invisible this other way. visibility: hidden position: absolute - .list-group-item - padding: 4px 0 - @include transition(0.5s ease) - - &.active - background-color: #e0f0f5 - - .status-message .should-equip-message - display: inline - - &.should-equip - background-color: #8fa - outline: 2px solid #8af - @include box-shadow(4px 4px 6px black) - z-index: 1 - - .status-message .should-equip-message - display: inline - font-weight: bold - - &.equipped - background-color: #ff5 - - .item-view - cursor: default - - .status-message .equipped-message - display: inline - - &.restricted - background-color: rgba(255, 80, 67, 0.25) - - .item-view - cursor: default - - .status-message .restricted-message - display: inline - - &.locked - background-image: url(/images/pages/game-menu/lock.png) - background-size: 25px 25px - background-repeat: no-repeat - background-position: 98% 90% - - .item-view - cursor: default - - h4, img - //@include filter(contrast(50%) brightness(65%)) - @include opacity(0.6) - - .status-message .locked-message - display: inline - - &.silhouette - h4 - visibility: hidden - position: absolute - h4:before - content: '???' - visibility: visible - .item-view .status-message .locked-message - display: none - img - @include filter(contrast(25%) brightness(25%)) - - .item-view - cursor: pointer - - #selected-items - $selectedItemsContainerMargin: 20px - $selectedItemMargin: 10px - $selectedItemImageSize: 75px - + #unequipped + width: 222px position: absolute - top: $selectedItemsContainerMargin - right: $selectedItemsContainerMargin - bottom: $selectedItemsContainerMargin - left: $selectedItemsContainerMargin - text-align: center + left: 370px + top: 112px + height: 450px + border: 3px solid black + padding: 9px 0 9px 9px + background-color: white - #selected-equipped-item, #selected-available-item - text-align: left - overflow-y: scroll - margin: 0 - height: 48.4% - height: -webkit-calc(50% - $selectedItemMargin / 2) - height: calc(50% - $selectedItemMargin / 2) - width: 100% - padding: 10px 5px 10px 10px - position: relative - display: none + #double-click-hint + margin: 20px 0 70px + + h4 + clear: both + margin-bottom: 10px + margin-top: 20px + font-size: 24px + text-transform: uppercase + font-weight: bold + + img.item + float: left + border: 1px solid black + margin: 3px + padding: 1px + width: 60px + height: 60px + cursor: pointer + + &.active + background-color: rgb(81,153,236) + + //.status-message .should-equip-message + // display: inline - img - margin-top: 21px - width: $selectedItemImageSize - height: $selectedItemImageSize - margin-right: 10px + &.should-equip + background-color: #8fa + outline: 2px solid #8af + @include box-shadow(4px 4px 6px black) + z-index: 1 + + //.status-message .should-equip-message + // display: inline + // font-weight: bold - .item-info - width: 110px - width: -webkit-calc(100% - 75px - 10px) - width: calc(100% - 75px - 10px) + &.equipped + background-color: #ff5 + display: none + cursor: default + + //.item-view + // cursor: default + // + //.status-message .equipped-message + // display: inline + + &.restricted + background-color: rgba(255, 80, 67, 0.25) + cursor: default + + //.item-view + // cursor: default + // + //.status-message .restricted-message + // display: inline + + &.locked + cursor: default + //background-color: gray + + &.silhouette + cursor: default + pointer-events: none + @include filter(contrast(25%) brightness(25%)) + opacity: 0.5 + + + //- Hero/Play buttons - > h3 - position: absolute - left: 0px - top: 0px - padding: 5px + #choose-hero-button, #play-level-button + top: 572px + position: absolute + background: url(/images/pages/play/modal/confirm-button.png) + width: 209px + height: 65px + background-size: 209px 65px + border: 0 - #selected-equipped-item - margin-bottom: $selectedItemMargin - padding-bottom: 20px - background-color: #ff5 + &:disabled + opacity: 1 + @include filter(grayscale(100%)) + + + #choose-hero-button + left: 20px + + #play-level-button + right: 414px - #selected-available-item - padding-top: 15px - background-color: #e0f0f5 - bottom: 0 + + //- Item details. Non-specific item-details-view styling is in item-details-view.sass. + + #item-details-view + + #item-title + left: 698px + top: 56px + + #item-details-body + left: 650px + + #selected-item-unlock-button + left: 646px + + + //- Equip/unequip/extra + + #item-details-extra + position: absolute + left: 644px + top: 589px + + & > * + width: 338px + height: 50px + + .alert + text-align: center + font-weight: bold + + button + border: 3px solid rgb(46,46,46) + background: white + font-size: 16px diff --git a/app/styles/play/modal/item-details-view.sass b/app/styles/play/modal/item-details-view.sass new file mode 100644 index 000000000..0d523e518 --- /dev/null +++ b/app/styles/play/modal/item-details-view.sass @@ -0,0 +1,82 @@ +#item-details-view + + .nano-content + padding: 10px + + #item-title + position: absolute + width: 228px + height: 50px + left: 910px + top: 60px + z-index: 2 + + h2 + font-size: 20px + margin: 12px 20px + text-align: center + color: rgb(53,40,25) + + #item-details-body + position: absolute + left: 860px + top: 126px + width: 330px + height: 449px + //background: rgba(100,100,100,0.5) + + #item-container + height: 163px + width: 100% + + .item-img, .item-shadow + width: 130px + height: 130px + + .item-img + top: 15px + + .item-shadow + top: 25px + + img.hr + width: 80% + margin: 0 10% -3px + + &.faded + opacity: 0.4 + + .stat-row + height: 24px + position: relative + font-size: 20px + font-weight: bold + + .stat-label + position: absolute + left: 54px + color: rgb(93,73,52) + + .stat + position: absolute + left: 150px + color: rgb(42,38,28) + + #skills + margin: 25px + + h3 + color: rgb(41,35,25) + + strong + color: rgb(50,50,30) + + #selected-item-unlock-button + left: 856px + top: 594px + width: 337px + height: 41px + font-size: 16px + + img + height: 16px diff --git a/app/styles/play/modal/play-items-modal.sass b/app/styles/play/modal/play-items-modal.sass index 8c798c988..19e5dd548 100644 --- a/app/styles/play/modal/play-items-modal.sass +++ b/app/styles/play/modal/play-items-modal.sass @@ -123,8 +123,8 @@ .tab-pane height: 100% - .nano-content - padding: 26px 51px 26px 26px + .nano-content + padding: 26px 51px 26px 26px //- Item box @@ -222,6 +222,23 @@ background: url(/images/pages/play/modal/item-box-background-selected.png) + //- Item details. Non-specific item-details-view styling is in item-details-view.sass. + + #item-details-view + + #item-title + left: 910px + top: 60px + + #item-details-body + left: 860px + + #selected-item-unlock-button + left: 856px + + +#play-items-modal, #inventory-modal + //- Item list scrollbar .nano-pane @@ -236,86 +253,6 @@ margin-left: -3px margin-right: -3px - // color: red - - - //- Item details - - #item-title - position: absolute - width: 228px - height: 50px - left: 910px - top: 60px - z-index: 2 - - h2 - font-size: 20px - margin: 12px 20px - text-align: center - color: rgb(53,40,25) - - #item-details-body - position: absolute - left: 860px - top: 126px - width: 330px - height: 449px - //background: rgba(100,100,100,0.5) - - #item-container - height: 163px - width: 100% - - .item-img, .item-shadow - width: 130px - height: 130px - - .item-img - top: 15px - - .item-shadow - top: 25px - - img.hr - width: 80% - margin: 0 10% -3px - - &.faded - opacity: 0.4 - - .stat-row - height: 24px - position: relative - font-size: 20px - font-weight: bold - - .stat-label - position: absolute - left: 54px - color: rgb(93,73,52) - - .stat - position: absolute - left: 150px - color: rgb(42,38,28) - - #skills - margin: 25px - - h3 - color: rgb(41,35,25) - - strong - color: rgb(50,50,30) - - #selected-item-unlock-button - left: 856px - top: 594px - width: 337px - height: 41px - font-size: 16px - //- Item icons w/shadows (both in list and details areas) @@ -355,6 +292,9 @@ opacity: 1 color: rgba(255,255,255, 0.4) + +//- Use the two-column layout and background image if we are on a narrow screen. + @media only screen and (max-width: 1300px) #play-items-modal overflow-x: hidden diff --git a/app/templates/game-menu/inventory-modal.jade b/app/templates/game-menu/inventory-modal.jade index 9bd81e4a8..846d23173 100644 --- a/app/templates/game-menu/inventory-modal.jade +++ b/app/templates/game-menu/inventory-modal.jade @@ -1,68 +1,52 @@ -extends /templates/modal/modal_base +.modal-dialog + .modal-content + img(src="/images/pages/play/modal/items-background-narrow.png")#play-items-modal-narrow-bg -block modal-header-content - h1#choose-inventory-header.choose-inventory-active(data-i18n="inventory.choose_inventory") Equip Items + div#gems-count-container + span#gems-count.big-font= gems -block modal-body-content - #equipped - .item-slot-row - for slot in ['left-ring', 'neck', 'eyes', 'head', 'wrists', 'right-ring'] + div#close-modal + span.glyphicon.glyphicon-remove + + #equipped + if selectedHero + img(src="/file/"+selectedHero.get('featureImage'))#hero-image + + for slot in ['head', 'eyes', 'neck', 'torso', 'gloves', 'wrists', 'left-hand', 'right-hand', 'waist', 'feet', 'left-ring', 'right-ring', 'minion', 'flag', 'pet', 'programming-book', 'misc-0', 'misc-1'] .item-slot(data-slot=slot) .placeholder - .item-container - if equipment[slot] - .replace-me(data-item-id=equipment[slot].get('original')) - - .item-slot-column.pull-left - // TODO: add in 'misc-0' again somehow? Used to be where 'flag' is now. - for slot in ['minion', 'torso', 'gloves', 'left-hand', 'flag'] - .item-slot(data-slot=slot) - .placeholder - .item-container - if equipment[slot] - .replace-me(data-item-id=equipment[slot].get('original')) - - .hero-container - canvas.equipped-hero-canvas - .hero-feature-image - img - #selected-items - #selected-equipped-item.well - h3(data-i18n="inventory.equipped_item") Equipped - .item-view-stub - #selected-available-item.well - h3(data-i18n="inventory.available_item") Available - .item-view-stub - - .item-slot-column.pull-right - for slot in ['pet', 'waist', 'feet', 'right-hand', 'programming-book'] - .item-slot(data-slot=slot) - .placeholder - .item-container - if equipment[slot] - .replace-me(data-item-id=equipment[slot].get('original')) - - // TODO: work in misc 1 again - //hr.slot-row-separator - // - //.item-slot-row.row-4 - // for slot in ['misc-1'] - // .item-slot(data-slot=slot) - // .placeholder - // .item-container - // if equipment[slot] - // .replace-me(data-item-id=equipment[slot].get('original')) - - #available-equipment - h4#unlocked-description - ul.list-group - for item in unlockedItems - li.list-group-item(class=item.classes, data-item-id=item.get('original')) - h4#locked-description - ul.list-group - for item in lockedItems - li.list-group-item(class=item.classes, data-item-id=item.get('original'), style="display: none") + if equipment[slot] + img.item(src=equipment[slot].getPortraitURL(), data-item-id=equipment[slot].id) -block modal-footer-content - button#choose-hero-button.btn.btn-lg.btn-primary.choose-inventory-active.pull-left(data-i18n="play.change_hero") Change Hero - button#play-level-button.btn.btn-lg.btn-success.choose-inventory-active(data-i18n="common.play") Play + #unequipped + .nano + .nano-content + if itemGroups + if itemGroups.availableItems.models.length + h4#available-description(data-i18n="inventory.available_item") + for item in itemGroups.availableItems.models + img.item(src=item.getPortraitURL(), class=item.classes, data-item-id=item.id) + .clearfix + + #double-click-hint.alert.alert-info.secret(data-i18n="inventory.should_equip") + + if itemGroups.restrictedItems.models.length + h4#restricted-description(data-i18n="inventory.restricted_title") + for item in itemGroups.restrictedItems.models + img.item(src=item.getPortraitURL(), class=item.classes, data-item-id=item.id) + .clearfix + + if itemGroups.lockedItems.models.length + h4#locked-description(data-i18n="play.locked") + for item in itemGroups.lockedItems.models + img.item(src=item.getPortraitURL(), class=item.classes, data-item-id=item.id) + .clearfix + + #item-details-view + #item-details-extra + button#equip-item-viewed.btn.secret(data-i18n="inventory.equip") + button#unequip-item-viewed.btn.secret(data-i18n="inventory.unequip") + .alert.alert-danger#restricted-item-viewed.secret(data-i18n="inventory.restricted") + + button#choose-hero-button.btn.btn-lg.btn-primary.choose-inventory-active(data-i18n="play.change_hero") Change Hero + button#play-level-button.btn.btn-lg.btn-success.choose-inventory-active(data-i18n="common.play") Play diff --git a/app/templates/play/modal/item-details-view.jade b/app/templates/play/modal/item-details-view.jade index 7b30b32b8..9d7f4c598 100644 --- a/app/templates/play/modal/item-details-view.jade +++ b/app/templates/play/modal/item-details-view.jade @@ -33,4 +33,9 @@ p Only admins will see it for now. if item && !item.owned - button#selected-item-unlock-button.btn.big-font.unlock-button(disabled=!item.affordable, data-item-id=item.id, data-i18n="play.unlock") Unlock \ No newline at end of file + button#selected-item-unlock-button.btn.big-font.unlock-button(disabled=!item.affordable, data-item-id=item.id) + span(data-i18n="play.unlock") Unlock + span.spl= '('+item.get('gems') + img(src="/images/common/gem.png") + span ) + \ No newline at end of file diff --git a/app/views/HomeView.coffee b/app/views/HomeView.coffee index 725905bed..7b5064e10 100644 --- a/app/views/HomeView.coffee +++ b/app/views/HomeView.coffee @@ -4,6 +4,8 @@ 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 @@ -34,4 +36,8 @@ module.exports = class HomeView extends RootView e.preventDefault() e.stopImmediatePropagation() window.tracker?.trackEvent 'Homepage', Action: 'Play' - window.open '/play', '_blank' \ No newline at end of file + window.open '/play', '_blank' + + 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 cb6bfe6d8..3da689a25 100644 --- a/app/views/game-menu/InventoryModal.coffee +++ b/app/views/game-menu/InventoryModal.coffee @@ -5,6 +5,8 @@ ThangType = require 'models/ThangType' CocoCollection = require 'collections/CocoCollection' ItemView = require './ItemView' SpriteBuilder = require 'lib/sprites/SpriteBuilder' +ItemDetailsView = require 'views/play/modal/ItemDetailsView' +Purchase = require 'models/Purchase' LevelOptions = require 'lib/LevelOptions' hasGoneFullScreenOnce = false @@ -14,101 +16,130 @@ module.exports = class InventoryModal extends ModalView className: 'modal fade play-modal' template: template slots: ['head', 'eyes', 'neck', 'torso', 'wrists', 'gloves', 'left-ring', 'right-ring', 'right-hand', 'left-hand', 'waist', 'feet', 'programming-book', 'pet', 'minion', 'flag'] #, 'misc-0', 'misc-1'] # TODO: bring in misc slot(s) again when we have space + closesOnClickOutside: false # because draggable somehow triggers hide when you don't drag onto a draggable events: 'click .item-slot': 'onItemSlotClick' - 'click #available-equipment .list-group-item:not(.equipped)': 'onAvailableItemClick' - 'dblclick #available-equipment .list-group-item:not(.equipped)': 'onAvailableItemDoubleClick' - 'doubletap #available-equipment .list-group-item:not(.equipped)': 'onAvailableItemDoubleClick' - 'dblclick .item-slot .item-view': 'onEquippedItemDoubleClick' - 'doubletap .item-slot .item-view': 'onEquippedItemDoubleClick' + 'click #unequipped img.item': 'onUnequippedItemClick' + 'doubletap #unequipped img.item': 'onUnequippedItemDoubleClick' + 'doubletap .item-slot img.item': 'onEquippedItemDoubleClick' 'shown.bs.modal': 'onShown' 'click #choose-hero-button': 'onClickChooseHero' 'click #play-level-button': 'onClickPlayLevel' + 'click .unlock-button': 'onUnlockButtonClicked' + 'click #equip-item-viewed': 'onClickEquipItemViewed' + 'click #unequip-item-viewed': 'onClickUnequipItemViewed' + 'click #close-modal': 'hide' shortcuts: 'esc': 'clearSelection' 'enter': 'onClickPlayLevel' + + #- Setup + initialize: (options) -> super(arguments...) @items = new CocoCollection([], {model: ThangType}) - @items.url = '/db/thang.type?view=items&project=name,slug,components,original,rasterIcon,gems,description,heroClass' + # TODO: switch to item store loading system? + @items.url = '/db/thang.type?view=items' + @items.setProjection [ + 'name', + 'slug', + 'components', + 'original', + 'rasterIcon', + 'gems', + 'description', + 'heroClass', + 'i18n' + ] @supermodel.loadCollection(@items, 'items') - @equipment = {} # Assign for real when we have loaded the session + @equipment = {} # Assign for real when we have loaded the session and items. - destroy: -> - @stage?.removeAllChildren() - super() - - onLoaded: -> + onItemsLoaded: -> item.notInLevel = true for item in @items.models @equipment = @options.equipment or @options.session?.get('heroConfig')?.inventory or me.get('heroConfig')?.inventory or {} @equipment = $.extend true, {}, @equipment @requireLevelEquipment() + @itemGroups = {} + @itemGroups.availableItems = new Backbone.Collection() + @itemGroups.restrictedItems = new Backbone.Collection() + @itemGroups.lockedItems = new Backbone.Collection() + itemGroup.comparator = 'gems' for itemGroup in _.values @itemGroups + + equipped = _.values(@equipment) + @sortItem(item, equipped) for item in @items.models + + sortItem: (item, equipped) -> + equipped ?= _.values(@equipment) + + # general starting classes + item.classes = _.clone(item.getAllowedSlots()) + for heroClass in item.getAllowedHeroClasses() + item.classes.push heroClass + item.classes.push 'equipped' if item.get('original') in equipped + + # sort into one of the four groups + locked = not (item.get('original') in me.items()) + + if locked and item.get('slug') isnt 'simple-boots' + @itemGroups.lockedItems.add(item) + item.classes.push 'locked' + item.classes.push 'silhouette' if item.isSilhouettedItem() + else if item.get('slug') in _.values(LevelOptions[@options.levelID]?.restrictedGear ? {}) + @itemGroups.restrictedItems.add(item) + item.classes.push 'restricted' + else + @itemGroups.availableItems.add(item) + + onLoaded: -> + # Both items and session have been loaded. + @onItemsLoaded() super() getRenderData: (context={}) -> context = super(context) context.equipped = _.values(@equipment) context.items = @items.models - - for item in @items.models - item.classes = item.getAllowedSlots() - item.classes.push 'equipped' if item.get('original') in context.equipped - locked = not (item.get('original') in me.items()) - item.classes.push 'locked' if locked and item.get('slug') isnt 'simple-boots' - for heroClass in item.getAllowedHeroClasses() - item.classes.push heroClass - item.classes.push 'silhouette' if item.isSilhouettedItem() - item.classes.push 'restricted' if item.get('slug') in _.values(LevelOptions[@options.levelID]?.restrictedGear ? {}) - - @items.models.sort (a, b) -> - lockScore = 90019001 * (('locked' in a.classes) - ('locked' in b.classes)) - gemScore = a.get('gems') - b.get('gems') - lockScore + gemScore - - context.unlockedItems = [] - context.lockedItems = [] - for item in @items.models - (if 'locked' in item.classes then context.lockedItems else context.unlockedItems).push item - + context.itemGroups = @itemGroups context.slots = @slots + context.selectedHero = @selectedHero context.equipment = _.clone @equipment - for slot, itemOriginal of context.equipment - item = _.find @items.models, (item) -> item.get('original') is itemOriginal - context.equipment[slot] = item + context.equipment[slot] = @items.findWhere {original: itemOriginal} for slot, itemOriginal of context.equipment + context.gems = me.gems() context afterRender: -> super() - @$el.find('.modal-footer button').css('visibility', 'hidden') + @$el.find('#play-level-button').css('visibility', 'hidden') return unless @supermodel.finished() - @$el.find('.modal-footer button').css('visibility', 'visible') + @$el.find('#play-level-button').css('visibility', 'visible') - keys = (item.get('original') for item in @items.models) - itemMap = _.zipObject keys, @items.models + @setUpDraggableEventsForAvailableEquipment() + @setUpDraggableEventsForEquippedArea() + @delegateEvents() + @itemDetailsView = new ItemDetailsView() + @insertSubView(@itemDetailsView) + @requireLevelEquipment() + @$el.find('.nano').nanoScroller() - # Fill in equipped items - for slottedItemStub in @$el.find('.replace-me') - itemID = $(slottedItemStub).data('item-id') - item = itemMap[itemID] - itemView = new ItemView({item: item, includes: {}}) - itemView.render() - $(slottedItemStub).replaceWith(itemView.$el) - @registerSubView(itemView) + afterInsert: -> + super() + @canvasWidth = @$el.find('canvas').innerWidth() + @canvasHeight = @$el.find('canvas').innerHeight() + @inserted = true + @requireLevelEquipment() - for availableItemEl in @$el.find('#available-equipment .list-group-item') - itemID = $(availableItemEl).data('item-id') - item = itemMap[itemID] - itemView = new ItemView({item: item, includes: {name: true}}) - itemView.render() - $(availableItemEl).append(itemView.$el) - @registerSubView(itemView) - continue if $(availableItemEl).hasClass('locked') or $(availableItemEl).hasClass('restricted') - dragHelper = itemView.$el.find('img').clone().addClass('draggable-item') - do (dragHelper, itemView) => - itemView.$el.draggable + #- Draggable logic + + setUpDraggableEventsForAvailableEquipment: -> + for availableItemEl in @$el.find('#unequipped img.item') + availableItemEl = $(availableItemEl) + continue if availableItemEl.hasClass('locked') or availableItemEl.hasClass('restricted') + dragHelper = availableItemEl.clone().addClass('draggable-item') + do (dragHelper, availableItemEl) => + availableItemEl.draggable revert: 'invalid' appendTo: @$el cursorAt: {left: 35.5, top: 35.5} @@ -116,43 +147,32 @@ module.exports = class InventoryModal extends ModalView revertDuration: 200 distance: 10 scroll: false - zIndex: 10000 - itemView.$el.on 'dragstart', => - @onAvailableItemClick target: itemView.$el.parent() unless itemView.$el.parent().hasClass 'active' + zIndex: 1100 + availableItemEl.on 'dragstart', => @selectUnequippedItem(availableItemEl) + setUpDraggableEventsForEquippedArea: -> for itemSlot in @$el.find '.item-slot' slot = $(itemSlot).data 'slot' do (slot, itemSlot) => $(itemSlot).droppable - drop: (e, ui) => @onAvailableItemDoubleClick() + drop: (e, ui) => @equipSelectedItem() accept: (el) -> $(el).parent().hasClass slot activeClass: 'droppable' hoverClass: 'droppable-hover' tolerance: 'touch' @makeEquippedSlotDraggable $(itemSlot) - @$el.find('.hero-container').droppable - drop: (e, ui) => @onAvailableItemDoubleClick() + @$el.find('#equipped').droppable + drop: (e, ui) => @equipSelectedItem() accept: (el) -> true activeClass: 'droppable' hoverClass: 'droppable-hover' tolerance: 'pointer' - @$el.find('#selected-items').hide() # Hide until one is selected - @delegateEvents() - - if @selectedHero and not @startedLoadingFirstHero - @loadHero() - @requireLevelEquipment() - - afterInsert: -> - super() - @canvasWidth = @$el.find('canvas').innerWidth() - @canvasHeight = @$el.find('canvas').innerHeight() - @inserted = true - makeEquippedSlotDraggable: (slot) -> - unequip = => @unequipItemFromSlot slot + unequip = => + @unequipItemFromSlot slot + @requireLevelEquipment() shouldStayEquippedWhenDropped = (isValidDrop) -> pos = $(@).position() revert = Math.abs(pos.left) < $(@).outerWidth() and Math.abs(pos.top) < $(@).outerHeight() @@ -166,179 +186,158 @@ module.exports = class InventoryModal extends ModalView revertDuration: 200 distance: 10 scroll: false - zIndex: 10000 + zIndex: 100 + slot.on 'dragstart', => @selectItemSlot(slot) - clearSelection: -> - @$el.find('.item-slot.selected').removeClass 'selected' - @$el.find('.list-group-item').removeClass('active') - @onSelectionChanged() + + #- Select/equip event handlers onItemSlotClick: (e) -> return if @remainingRequiredEquipment?.length # Don't let them select a slot if we need them to first equip some require gear. - slot = $(e.target).closest('.item-slot') - wasActive = slot.hasClass('selected') - @unselectAllSlots() - @unselectAllAvailableEquipment() if slot.hasClass('disabled') - if wasActive - @hideSelectedSlotItem() - @unselectAllAvailableEquipment() + @selectItemSlot($(e.target).closest('.item-slot')) + + onUnequippedItemClick: (e) -> + return if @justDoubleClicked + itemEl = $(e.target).closest('img.item') + @selectUnequippedItem(itemEl) + + onUnequippedItemDoubleClick: (e) -> + item = $(e.target).closest('img.item') + return if item.hasClass('locked') or item.hasClass('restricted') + @equipSelectedItem() + @justDoubleClicked = true + _.defer => @justDoubleClicked = false + + onEquippedItemDoubleClick: -> @unequipSelectedItem() + onClickEquipItemViewed: -> @equipSelectedItem() + onClickUnequipItemViewed: -> @unequipSelectedItem() + + 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 + @requireLevelEquipment() + @itemGroups.lockedItems.remove(item) + @sortItem(item) + @renderSelectors("#unequipped", "#gems-count") + @delegateEvents() + @setUpDraggableEventsForAvailableEquipment() + @itemDetailsView.setItem(item) else - @selectSlot(slot) + 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) -> + @clearSelection() + slotEl.addClass('selected') + selectedSlotItemID = slotEl.find('img.item').data('item-id') + item = @items.get(selectedSlotItemID) + if item then @showItemDetails(item, 'unequip') @onSelectionChanged() - onAvailableItemClick: (e) -> - itemContainer = $(e.target).closest('.list-group-item') - return if itemContainer.hasClass('locked') or itemContainer.hasClass('restricted') - wasActive = itemContainer.hasClass 'active' - @unselectAllAvailableEquipment() - @selectAvailableItem(itemContainer) unless wasActive + selectUnequippedItem: (itemEl) -> + @clearSelection() + itemEl.addClass('active') + showExtra = if itemEl.hasClass('restricted') then 'restricted' else if not itemEl.hasClass('locked') then 'equip' else '' + @showItemDetails(@items.get(itemEl.data('item-id')), showExtra) @onSelectionChanged() - onAvailableItemDoubleClick: (e) -> - if e - itemContainer = $(e.target).closest('.list-group-item') - return if itemContainer.hasClass('locked') or itemContainer.hasClass('restricted') - @selectAvailableItem itemContainer - @onSelectionChanged() - slot = @getSelectedSlot() - slot = @$el.find('.item-slot:not(.disabled):first') if not slot.length - $(e.target).effect('transfer', to: slot, duration: 500, easing: 'easeOutCubic') if e - @unequipItemFromSlot(slot) - @equipSelectedItemToSlot(slot) + equipSelectedItem: -> + selectedItemEl = @getSelectedUnequippedItem() + selectedItem = @items.get(selectedItemEl.data('item-id')) + return unless selectedItem + allowedSlots = selectedItem.getAllowedSlots() + slotEl = @$el.find(".item-slot[data-slot='#{allowedSlots[0]}']") + selectedItemEl.effect('transfer', to: slotEl, duration: 500, easing: 'easeOutCubic') + unequipped = @unequipItemFromSlot(slotEl) + selectedItemEl.addClass('equipped') + slotEl.append(selectedItemEl.clone()) + @clearSelection() + @showItemDetails(selectedItem, 'unequip') + slotEl.addClass('selected') + selectedItem.classes.push 'equipped' + @makeEquippedSlotDraggable slotEl + @requireLevelEquipment() @onSelectionChanged() - onEquippedItemDoubleClick: (e) -> - @unselectAllAvailableEquipment() - slot = $(e.target).closest('.item-slot') - @selectAvailableItem(@unequipItemFromSlot(slot)) + unequipSelectedItem: -> + slotEl = @getSelectedSlot() + @clearSelection() + itemEl = @unequipItemFromSlot(slotEl) + return unless itemEl + itemEl.addClass('active') + slotEl.effect('transfer', to: itemEl, duration: 500, easing: 'easeOutCubic') + selectedSlotItemID = itemEl.data('item-id') + item = @items.get(selectedSlotItemID) + item.classes = _.without item.classes, 'equipped' + @showItemDetails(item, 'equip') + @requireLevelEquipment() @onSelectionChanged() - getSelectedSlot: -> - @$el.find('#equipped .item-slot.selected') - unselectAllAvailableEquipment: -> - @$el.find('#available-equipment .list-group-item').removeClass('active') + #- Select/equip helpers - unselectAllSlots: -> + clearSelection: -> + @deselectAllSlots() + @deselectAllUnequippedItems() + @hideItemDetails() + + unequipItemFromSlot: (slotEl) -> + itemEl = slotEl.find('img.item') + itemIDToUnequip = itemEl.data('item-id') + return unless itemIDToUnequip + itemEl.remove() + @$el.find("#unequipped img.item[data-item-id=#{itemIDToUnequip}]").removeClass('equipped') + + deselectAllSlots: -> @$el.find('#equipped .item-slot.selected').removeClass('selected') - selectSlot: (slot) -> - slot.addClass('selected') + deselectAllUnequippedItems: -> + @$el.find('#unequipped img.item').removeClass('active') getSlot: (name) -> @$el.find(".item-slot[data-slot=#{name}]") - getSelectedAvailableItemContainer: -> - @$el.find('#available-equipment .list-group-item.active') + getSelectedSlot: -> + @$el.find('#equipped .item-slot.selected') - getAvailableItemContainer: (itemID) -> - @$el.find("#available-equipment .list-group-item[data-item-id='#{itemID}']") - - selectAvailableItem: (itemContainer) -> - itemContainer?.addClass('active') - - unequipItemFromSlot: (slot) -> - itemIDToUnequip = slot.find('.item-view').data('item-id') - return unless itemIDToUnequip - slot.find('.item-view').detach() - for el in @$el.find('#available-equipment .list-group-item') - itemID = $(el).find('.item-view').data('item-id') - if itemID is itemIDToUnequip - unequipped = $(el).removeClass('equipped') - break - if unequipped - @clearSelection() - @requireLevelEquipment() - return unequipped - - equipSelectedItemToSlot: (slot) -> - selectedItemContainer = @getSelectedAvailableItemContainer() - newItemHTML = selectedItemContainer.html() - selectedItemContainer.addClass('equipped') - slotContainer = slot.find('.item-container') - slotContainer.html(newItemHTML) - slotContainer.find('.item-view').data('item-id', selectedItemContainer.find('.item-view').data('item-id')) - @$el.find('.list-group-item').removeClass('active') - @makeEquippedSlotDraggable slot - @requireLevelEquipment() + getSelectedUnequippedItem: -> + @$el.find('#unequipped img.item.active') onSelectionChanged: -> - @$el.find('.item-slot').show() - - selectedSlot = @$el.find('.item-slot.selected') - selectedItem = @$el.find('#available-equipment .list-group-item.active') - - if selectedSlot.length - @$el.find('#available-equipment .list-group-item').hide() - unlockedCount = @$el.find("#available-equipment .list-group-item.#{selectedSlot.data('slot')}:not(.locked)").show().length - lockedCount = @$el.find("#available-equipment .list-group-item.#{selectedSlot.data('slot')}.locked").show().length - @$el.find('#unlocked-description').text("#{unlockedCount} #{selectedSlot.data('slot')} items owned").toggle unlockedCount > 0 - @$el.find('#locked-description').text("#{lockedCount} #{selectedSlot.data('slot')} items locked").toggle lockedCount > 0 - selectedSlotItemID = selectedSlot.find('.item-view').data('item-id') - if selectedSlotItemID - item = _.find @items.models, {id: selectedSlotItemID} - @showSelectedSlotItem(item) - else - @hideSelectedSlotItem() - else - unlockedCount = @$el.find('#available-equipment .list-group-item:not(.locked)').show().length - @$el.find('#available-equipment .list-group-item.locked').hide() - @$el.find('#unlocked-description').text("#{unlockedCount} items owned").toggle unlockedCount > 0 - @$el.find('#locked-description').text("#{lockedCount} items locked").hide() - #@$el.find('#available-equipment .list-group-item.equipped').hide() - - @$el.find('.item-slot').removeClass('disabled') - if selectedItem.length - item = _.find @items.models, {id:selectedItem.find('.item-view').data('item-id')} - # update which slots are enabled - allowedSlots = item.getAllowedSlots() - for slotEl in @$el.find('.item-slot') - slotName = $(slotEl).data('slot') - if slotName not in allowedSlots - $(slotEl).addClass('disabled') - @showSelectedAvailableItem(item) - else - @hideSelectedAvailableItem() - @delegateEvents() - showSelectedSlotItem: (item) -> - if not @selectedEquippedItemView - @selectedEquippedItemView = new ItemView({ - item: item, includes: {name: true, stats: true, props: true}}) - @insertSubView(@selectedEquippedItemView, @$el.find('#selected-equipped-item .item-view-stub')) - else - @selectedEquippedItemView.$el.show() - @selectedEquippedItemView.item = item - @selectedEquippedItemView.render() - @$el.find('#selected-items').show() - @$el.find('#selected-equipped-item').show() - hideSelectedSlotItem: -> - @selectedEquippedItemView?.$el.hide().parent().hide() - @$el.find('#selected-items').hide() unless @selectedEquippedItemView?.$el?.is(':visible') + showItemDetails: (item, showExtra) -> + @itemDetailsView.setItem(item) + @$el.find('#item-details-extra > *').addClass('secret') + @$el.find("##{showExtra}-item-viewed").removeClass('secret') - showSelectedAvailableItem: (item) -> - if not @selectedAvailableItemView - @selectedAvailableItemView = new ItemView({ - item: item, includes: {name: true, stats: true, props: true}}) - @insertSubView(@selectedAvailableItemView, @$el.find('#selected-available-item .item-view-stub')) - else - @selectedAvailableItemView.$el.show() - @selectedAvailableItemView.item = item - @selectedAvailableItemView.render() - @$el.find('#selected-items').show() - @$el.find('#selected-available-item').show() - - hideSelectedAvailableItem: -> - @selectedAvailableItemView?.$el.hide().parent().hide() - @$el.find('#selected-items').hide() unless @selectedEquippedItemView?.$el?.is(':visible') + hideItemDetails: -> + @itemDetailsView.setItem(null) + @$el.find('#item-details-extra > *').addClass('secret') getCurrentEquipmentConfig: -> config = {} for slot in @$el.find('.item-slot') slotName = $(slot).data('slot') - slotItemID = $(slot).find('.item-view').data('item-id') + slotItemID = $(slot).find('img.item').data('item-id') continue unless slotItemID item = _.find @items.models, {id:slotItemID} config[slotName] = item.get('original') @@ -371,10 +370,13 @@ module.exports = class InventoryModal extends ModalView (item is 'leather-boots' and equipped is gear['simple-boots']) or (item is 'simple-boots' and equipped is gear['leather-boots']) ) - availableSlotSelector = "#available-equipment li[data-item-id='#{gear[item]}']" + itemModel = @items.findWhere {slug: item} + continue unless itemModel + availableSlotSelector = "#unequipped .item[data-item-id='#{itemModel.id}']" @highlightElement availableSlotSelector, delay: 500, sides: ['right'], rotation: Math.PI / 2 @$el.find(availableSlotSelector).addClass 'should-equip' @$el.find("#equipped div[data-slot='#{slot}']").addClass 'should-equip' + @$el.find('#double-click-hint').removeClass('secret') @remainingRequiredEquipment.push slot: slot, item: gear[item] if hadRequired and not @remainingRequiredEquipment.length @endHighlight() @@ -382,54 +384,12 @@ module.exports = class InventoryModal extends ModalView $('#play-level-button').prop('disabled', @remainingRequiredEquipment.length > 0) setHero: (@selectedHero) -> - @loadHero() @$el.removeClass('Warrior Ranger Wizard').addClass(@selectedHero.get('heroClass')) - - loadHero: -> - return unless @supermodel.finished() and @selectedHero and not @$el.hasClass 'secret' - @startedLoadingFirstHero = true - @stage?.removeAllChildren() - if featureImage = @selectedHero.get 'featureImage' - @$el.find(".equipped-hero-canvas").hide() - @$el.find(".hero-feature-image").show().find('img').prop('src', '/file/' + featureImage) - return - if @selectedHero.loaded and movieClip = @movieClips?[@selectedHero.get('original')] - @stage.addChild(movieClip) - @stage.update() - return - onLoaded = => - return unless canvas = @$el.find(".equipped-hero-canvas") - @canvasWidth ||= canvas.width() - @canvasHeight ||= canvas.height() - canvas.prop width: @canvasWidth, height: @canvasHeight - builder = new SpriteBuilder(@selectedHero) - movieClip = builder.buildMovieClip(@selectedHero.get('actions').attack?.animation ? @selectedHero.get('actions').idle.animation) - movieClip.scaleX = movieClip.scaleY = canvas.prop('height') / 120 # Average hero height is ~110px at normal resolution - if @selectedHero.get('name') in ['Knight', 'Robot Walker'] # These are too big, so shrink them. - movieClip.scaleX *= 0.7 - movieClip.scaleY *= 0.7 - movieClip.regX = -@selectedHero.get('positions').registration.x - movieClip.regY = -@selectedHero.get('positions').registration.y - movieClip.x = canvas.prop('width') * 0.5 - movieClip.y = canvas.prop('height') * 0.95 # This is where the feet go. - movieClip.gotoAndPlay 0 - @stage ?= new createjs.Stage(canvas[0]) - @stage.addChild movieClip - @stage.update() - @movieClips ?= {} - @movieClips[@selectedHero.get('original')] = movieClip - if @selectedHero.loaded - if @selectedHero.isFullyLoaded() - _.defer onLoaded - else - console.error 'Hmm, trying to render a hero we have not loaded...?', @selectedHero - else - @listenToOnce @selectedHero, 'sync', onLoaded + @render() onShown: -> # Called when we switch tabs to this within the modal @requireLevelEquipment() - @loadHero() onHidden: -> # Called when the modal itself is dismissed @@ -471,6 +431,13 @@ module.exports = class InventoryModal extends ModalView else callback?() + destroy: -> + @stage?.removeAllChildren() + super() + + + + gear = 'simple-boots': '53e237bf53457600003e3f05' 'simple-sword': '53e218d853457600003e3ebe' diff --git a/app/views/play/modal/ItemDetailsView.coffee b/app/views/play/modal/ItemDetailsView.coffee new file mode 100644 index 000000000..422a86185 --- /dev/null +++ b/app/views/play/modal/ItemDetailsView.coffee @@ -0,0 +1,76 @@ +CocoView = require 'views/kinds/CocoView' +template = require 'templates/play/modal/item-details-view' +CocoCollection = require 'collections/CocoCollection' +LevelComponent = require 'models/LevelComponent' + +utils = require 'lib/utils' + +module.exports = class ItemDetailsView extends CocoView + id: "item-details-view" + template: template + + constructor: -> + super(arguments...) + @propDocs = {} + + setItem: (@item) -> + if @item + @item.name = utils.i18n @item.attributes, 'name' + @item.affordable = me.gems() >= @item.get('gems') + @item.owned = me.ownsItem @item.get('original') + @item.comingSoon = not @item.getFrontFacingStats().props.length and not _.size @item.getFrontFacingStats().stats # Temp: while there are placeholder items + + stats = @item.getFrontFacingStats() + props = (p for p in stats.props when not @propDocs[p]) + if props.length > 0 + + docs = new CocoCollection([], { + url: '/db/level.component?view=prop-doc-lookup' + model: LevelComponent + project: [ + 'propertyDocumentation.name' + 'propertyDocumentation.description' + 'propertyDocumentation.i18n' + ] + }) + + docs.fetch({ data: { + componentOriginals: [c.original for c in @item.get('components')].join(',') + propertyNames: props.join(',') + }}) + @listenToOnce docs, 'sync', @onDocsLoaded + + @render() + @$el.find('.nano:visible').nanoScroller() + + onDocsLoaded: (levelComponents) -> + for component in levelComponents.models + for propDoc in component.get('propertyDocumentation') + @propDocs[propDoc.name] = propDoc + @render() + + getRenderData: -> + c = super() + c.item = @item + if @item + stats = @item.getFrontFacingStats() + c.stats = _.values(stats.stats) + _.last(c.stats).isLast = true if c.stats.length + c.props = [] + progLang = (me.get('aceConfig') ? {}).language or 'python' + for prop in stats.props + description = utils.i18n @propDocs[prop] ? {}, 'description' + + if _.isObject description + description = description[progLang] or _.values(description)[0] + if _.isString description + description = description.replace(/#{spriteName}/g, 'hero') + if fact = stats.stats.shieldDefenseFactor + description = description.replace(/#{shieldDefensePercent}%/g, fact.display) + description = $(marked(description)).html() + + c.props.push { + name: prop + description: description or '...' + } + c \ No newline at end of file diff --git a/app/views/play/modal/PlayItemsModal.coffee b/app/views/play/modal/PlayItemsModal.coffee index 6fac5fc35..2295ed2ad 100644 --- a/app/views/play/modal/PlayItemsModal.coffee +++ b/app/views/play/modal/PlayItemsModal.coffee @@ -1,8 +1,6 @@ ModalView = require 'views/kinds/ModalView' -CocoView = require 'views/kinds/CocoView' - template = require 'templates/play/modal/play-items-modal' -itemDetailsTemplate = require 'templates/play/modal/item-details-view' +ItemDetailsView = require './ItemDetailsView' CocoCollection = require 'collections/CocoCollection' ThangType = require 'models/ThangType' @@ -139,7 +137,7 @@ module.exports = class PlayItemsModal extends ModalView $($(e.target).attr('href')).find('.nano').nanoScroller({alwaysVisible: true}) onUnlockButtonClicked: (e) -> - button = $(e.target) + button = $(e.target).closest('button') if button.hasClass('confirm') item = @idToItem[$(e.target).data('item-id')] purchase = Purchase.makeFor(item) @@ -161,67 +159,3 @@ module.exports = class PlayItemsModal extends ModalView @$el.one 'click', (e) -> button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0] -class ItemDetailsView extends CocoView - id: "item-details-view" - template: itemDetailsTemplate - - constructor: -> - super(arguments...) - @propDocs = {} - - setItem: (@item) -> - @render() - - if @item - stats = @item.getFrontFacingStats() - props = (p for p in stats.props when not @propDocs[p]) - return if props.length is 0 - - docs = new CocoCollection([], { - url: '/db/level.component?view=prop-doc-lookup' - model: LevelComponent - project: [ - 'propertyDocumentation.name' - 'propertyDocumentation.description' - 'propertyDocumentation.i18n' - ] - }) - - docs.fetch({ data: { - componentOriginals: [c.original for c in @item.get('components')].join(',') - propertyNames: props.join(',') - }}) - @listenToOnce docs, 'sync', @onDocsLoaded - @$el.find('.nano:visible').nanoScroller() - - onDocsLoaded: (levelComponents) -> - for component in levelComponents.models - for propDoc in component.get('propertyDocumentation') - @propDocs[propDoc.name] = propDoc - @render() - - getRenderData: -> - c = super() - c.item = @item - if @item - stats = @item.getFrontFacingStats() - c.stats = _.values(stats.stats) - _.last(c.stats).isLast = true if c.stats.length - c.props = [] - progLang = (me.get('aceConfig') ? {}).language or 'python' - for prop in stats.props - description = utils.i18n @propDocs[prop] ? {}, 'description' - - if _.isObject description - description = description[progLang] or _.values(description)[0] - if _.isString description - description = description.replace(/#{spriteName}/g, 'hero') - if fact = stats.stats.shieldDefenseFactor - description = description.replace(/#{shieldDefensePercent}%/g, fact.display) - description = $(marked(description)).html() - - c.props.push { - name: prop - description: description or '...' - } - c diff --git a/server/purchases/Purchase.coffee b/server/purchases/Purchase.coffee index b1f34459a..600abd1af 100644 --- a/server/purchases/Purchase.coffee +++ b/server/purchases/Purchase.coffee @@ -4,6 +4,6 @@ log = require 'winston' {handlers} = require '../commons/mapping' PurchaseSchema = new mongoose.Schema({status: String}, {strict: false}) -PurchaseSchema.index({recipient: 1, 'purchase.original': 1}, {unique: true, name: 'unique purchase'}) +PurchaseSchema.index({recipient: 1, 'purchased.original': 1}, {unique: true, name: 'unique purchase'}) module.exports = mongoose.model('purchase', PurchaseSchema) diff --git a/server/purchases/purchase_handler.coffee b/server/purchases/purchase_handler.coffee index 7b0e1a7ef..5f1f34560 100644 --- a/server/purchases/purchase_handler.coffee +++ b/server/purchases/purchase_handler.coffee @@ -40,7 +40,7 @@ PurchaseHandler = class PurchaseHandler extends Handler return @sendDatabaseError(res, err) if err return @sendNotFoundError(res) unless purchasedItem return @sendBadInputError(res, 'This cannot be purchased.') if not cost = purchasedItem.get('gems') - return @sendForbiddenError(res, 'Not enough gems.') if cost > req.user.get('gems') + return @sendForbiddenError(res, 'Not enough gems.') if cost > req.user.gems() req.purchasedItem = purchasedItem # for safekeeping criteria = { @@ -69,7 +69,7 @@ PurchaseHandler = class PurchaseHandler extends Handler when 'Hero' then 'heroes' else 'levels' - original = item.get('original') + original = item.get('original') + '' purchased[group] ?= [] unless original in purchased[group] #- add the purchase to the list of purchases