mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-04 17:19:47 -04:00
Mostly done with the victory modal.
This commit is contained in:
parent
70e20efcc7
commit
6ff7cd12cc
11 changed files with 333 additions and 38 deletions
app
assets/images/pages/play/level/modal
achievement_plate.pngreward_icon_gems.pngreward_icon_xp.pngreward_plate.pngvictory_modal_background.pngvictory_word.png
styles/play/level/modal
templates/play/level/modal
views/play/level
server/achievements
BIN
app/assets/images/pages/play/level/modal/achievement_plate.png
Normal file
BIN
app/assets/images/pages/play/level/modal/achievement_plate.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 9.1 KiB |
BIN
app/assets/images/pages/play/level/modal/reward_icon_gems.png
Normal file
BIN
app/assets/images/pages/play/level/modal/reward_icon_gems.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 3.6 KiB |
BIN
app/assets/images/pages/play/level/modal/reward_icon_xp.png
Normal file
BIN
app/assets/images/pages/play/level/modal/reward_icon_xp.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 4.1 KiB |
BIN
app/assets/images/pages/play/level/modal/reward_plate.png
Normal file
BIN
app/assets/images/pages/play/level/modal/reward_plate.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.1 KiB |
Binary file not shown.
After ![]() (image error) Size: 20 KiB |
BIN
app/assets/images/pages/play/level/modal/victory_word.png
Normal file
BIN
app/assets/images/pages/play/level/modal/victory_word.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 9 KiB |
|
@ -1,2 +1,188 @@
|
|||
#hero-victory-modal
|
||||
color: red
|
||||
|
||||
//- Header
|
||||
|
||||
.background-wrapper
|
||||
background: url("/images/pages/play/level/modal/victory_modal_background.png")
|
||||
height: 650px
|
||||
width: 550px
|
||||
|
||||
#victory-header
|
||||
display: block
|
||||
margin: 40px auto 0
|
||||
|
||||
.modal-header
|
||||
height: 110px
|
||||
border: none
|
||||
|
||||
|
||||
//- Achievement panels
|
||||
|
||||
.modal-body
|
||||
padding: 0 20px
|
||||
|
||||
.achievement-panel
|
||||
background: url("/images/pages/play/level/modal/achievement_plate.png")
|
||||
width: 451px
|
||||
height: 144px
|
||||
margin: 5px auto
|
||||
position: relative
|
||||
|
||||
-webkit-transition-duration: 1s
|
||||
-moz-transition-duration: 1s
|
||||
-o-transition-duration: 1s
|
||||
transition-duration: 1s
|
||||
|
||||
-webkit-filter: grayscale(100%)
|
||||
-moz-filter: grayscale(100%)
|
||||
-o-filter: grayscale(100%)
|
||||
filter: grayscale(100%)
|
||||
|
||||
&.earned
|
||||
-webkit-filter: none
|
||||
-moz-filter: none
|
||||
-o-filter: none
|
||||
filter: none
|
||||
|
||||
.achievement-description
|
||||
position: absolute
|
||||
text-align: center
|
||||
left: 95px
|
||||
right: 98px
|
||||
top: 10px
|
||||
color: white
|
||||
white-space: nowrap
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
|
||||
.achievement-rewards
|
||||
position: absolute
|
||||
left: 25px
|
||||
right: 23px
|
||||
top: 41px
|
||||
bottom: 18px
|
||||
|
||||
|
||||
//- Reward panels
|
||||
|
||||
.reward-panel
|
||||
background: url("/images/pages/play/level/modal/reward_plate.png")
|
||||
background: url("/images/pages/play/level/modal/reward_plate.png")
|
||||
width: 77px
|
||||
height: 85px
|
||||
float: left
|
||||
margin: 0 1.8px
|
||||
position: relative
|
||||
|
||||
.reward-image-container
|
||||
top: 8px
|
||||
left: 11px
|
||||
height: 55px
|
||||
width: 56px
|
||||
position: relative
|
||||
|
||||
-webkit-transform: scale(0, 0)
|
||||
-moz-transform: scale(0, 0)
|
||||
-o-transform: scale(0, 0)
|
||||
transform: scale(0, 0)
|
||||
|
||||
-webkit-transition-duration: 0.5s
|
||||
-moz-transition-duration: 0.5s
|
||||
-o-transition-duration: 0.5s
|
||||
transition-duration: 0.5s
|
||||
|
||||
&.show
|
||||
-webkit-transform: scale(1, 1)
|
||||
-moz-transform: scale(1, 1)
|
||||
-o-transform: scale(1, 1)
|
||||
transform: scale(1, 1)
|
||||
|
||||
img
|
||||
margin: 0
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: 50%
|
||||
margin-right: -50%
|
||||
|
||||
-webkit-transition-duration: 0.5s
|
||||
-moz-transition-duration: 0.5s
|
||||
-o-transition-duration: 0.5s
|
||||
transition-duration: 0.5s
|
||||
|
||||
-webkit-transform: translate(-50%, -50%)
|
||||
-moz-transform: translate(-50%, -50%)
|
||||
-o-transform: translate(-50%, -50%)
|
||||
transform: translate(-50%, -50%)
|
||||
|
||||
max-width: 56px
|
||||
max-height: 55px
|
||||
|
||||
.reward-text
|
||||
position: absolute
|
||||
bottom: 6px
|
||||
left: 4px
|
||||
right: 3px
|
||||
height: 15px
|
||||
text-align: center
|
||||
color: white
|
||||
font-weight: bold
|
||||
font-size: 12px
|
||||
white-space: nowrap
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
|
||||
|
||||
//- Pulse effect
|
||||
|
||||
@-webkit-keyframes pulse
|
||||
from
|
||||
-webkit-transform: translate(-50%, -50%) scale(1.0)
|
||||
50%
|
||||
-webkit-transform: translate(-50%, -50%) scale(1.3)
|
||||
to
|
||||
-webkit-transform: translate(-50%, -50%) scale(1.0)
|
||||
|
||||
@-moz-keyframes pulse
|
||||
from
|
||||
-moz-transform: translate(-50%, -50%) scale(1.0)
|
||||
50%
|
||||
-moz-transform: translate(-50%, -50%) scale(1.3)
|
||||
to
|
||||
-moz-transform: translate(-50%, -50%) scale(1.0)
|
||||
|
||||
@-o-keyframes pulse
|
||||
from
|
||||
-o-transform: translate(-50%, -50%) scale(1.0)
|
||||
50%
|
||||
-o-transform: translate(-50%, -50%) scale(1.3)
|
||||
to
|
||||
-o-transform: translate(-50%, -50%) scale(1.0)
|
||||
|
||||
@keyframes pulse
|
||||
from
|
||||
transform: translate(-50%, -50%) scale(1.0)
|
||||
50%
|
||||
transform: translate(-50%, -50%) scale(1.3)
|
||||
to
|
||||
transform: translate(-50%, -50%) scale(1.0)
|
||||
|
||||
.pulse
|
||||
-webkit-animation: pulse 0.5s infinite
|
||||
-moz-animation: pulse 0.5s infinite
|
||||
-o-animation: pulse 0.5s infinite
|
||||
animation: pulse 0.5s infinite
|
||||
|
||||
|
||||
//- Footer
|
||||
|
||||
.modal-content
|
||||
height: 650px // so the footer appears at the bottom
|
||||
|
||||
.modal-footer
|
||||
position: absolute
|
||||
bottom: 20px
|
||||
left: 20px
|
||||
right: 20px
|
||||
|
||||
#totals
|
||||
color: white
|
|
@ -1,34 +1,53 @@
|
|||
extends /templates/modal/modal_base
|
||||
block modal-header-content
|
||||
h3
|
||||
span(data-i18n="play_level.victory_title_prefix")
|
||||
span= levelName
|
||||
span(data-i18n="play_level.victory_title_suffix") Complete
|
||||
img(src="/images/pages/play/level/modal/victory_word.png")#victory-header
|
||||
|
||||
block modal-body-content
|
||||
h4 Achievements Unlocked
|
||||
|
||||
|
||||
for achievement in achievements
|
||||
.panel
|
||||
img(src=achievement.getImageURL())
|
||||
h5= achievement.get('name')
|
||||
p= achievement.get('description')
|
||||
if achievement.completed
|
||||
strong Completed
|
||||
else
|
||||
strong Incomplete
|
||||
p Earned #{achievement.get('worth')} xp.
|
||||
- var animate = achievement.completed && !achievement.completedAWhileAgo
|
||||
.achievement-panel(class=achievement.completedAWhileAgo ? 'earned' : '' data-achievement-id=achievement.id data-animate=animate)
|
||||
- var rewards = achievement.get('rewards') || {};
|
||||
if rewards.gems
|
||||
p Earned #{achievement.get('rewards').gems} gems.
|
||||
if rewards.heroes
|
||||
for hero in rewards.heroes
|
||||
- var hero = thangTypes[hero];
|
||||
img(src=hero.getPortraitURL())
|
||||
if rewards.items
|
||||
for item in rewards.items
|
||||
- var item = thangTypes[item];
|
||||
img(src=item.getPortraitURL())
|
||||
|
||||
div.achievement-description= achievement.get('description')
|
||||
|
||||
div.achievement-rewards
|
||||
- var worth = achievement.get('worth', true);
|
||||
if worth
|
||||
.reward-panel.numerical(data-number=worth, data-number-unit='xp')
|
||||
.reward-image-container(class=animate?'':'show')
|
||||
img(src="/images/pages/play/level/modal/reward_icon_xp.png")
|
||||
.reward-text= animate ? 'x0' : '+'+worth
|
||||
|
||||
if rewards.gems
|
||||
.reward-panel.numerical(data-number=rewards.gems, data-number-unit='gem')
|
||||
.reward-image-container(class=animate?'':'show')
|
||||
img(src="/images/pages/play/level/modal/reward_icon_gems.png")
|
||||
.reward-text= animate ? 'x0' : '+'+rewards.gems
|
||||
|
||||
if rewards.heroes
|
||||
for hero in rewards.heroes
|
||||
- var hero = thangTypes[hero];
|
||||
.reward-panel
|
||||
.reward-image-container(class=animate?'':'show')
|
||||
img(src=hero.getPortraitURL())
|
||||
.reward-text= hero.get('name')
|
||||
|
||||
if rewards.items
|
||||
for item in rewards.items
|
||||
- var item = thangTypes[item];
|
||||
.reward-panel
|
||||
.reward-image-container(class=animate?'':'show')
|
||||
img(src=item.getPortraitURL())
|
||||
.reward-text= item.get('name')
|
||||
|
||||
|
||||
block modal-footer-content
|
||||
| footer
|
||||
div#totals.pull-left
|
||||
span.spr Experience Gained:
|
||||
span#xp-total +0
|
||||
br
|
||||
span.spr Gems Gained:
|
||||
span#gem-total +0
|
||||
|
||||
button.btn.btn-primary.pull-right#continue-button Continue
|
|
@ -206,10 +206,6 @@ module.exports = class PlayLevelView extends RootView
|
|||
@initVolume()
|
||||
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
|
||||
|
||||
# testing
|
||||
# modal = new HeroVictoryModal({session: @session, level: @level})
|
||||
# @openModalView(modal)
|
||||
|
||||
@originalSessionState = $.extend(true, {}, @session.get('state'))
|
||||
@register()
|
||||
@controlBar.setBus(@bus)
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
ModalView = require 'views/kinds/ModalView'
|
||||
template = require 'templates/play/level/modal/hero-victory-modal'
|
||||
Achievement = require 'models/Achievement'
|
||||
EarnedAchievement = require 'models/EarnedAchievement'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
LocalMongo = require 'lib/LocalMongo'
|
||||
utils = require 'lib/utils'
|
||||
ThangType = require 'models/ThangType'
|
||||
|
||||
module.exports = class UnnamedView extends ModalView
|
||||
module.exports = class HeroVictoryModal extends ModalView
|
||||
id: 'hero-victory-modal'
|
||||
template: template
|
||||
closeButton: false
|
||||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
|
@ -24,24 +26,96 @@ module.exports = class UnnamedView extends ModalView
|
|||
|
||||
onAchievementsLoaded: ->
|
||||
thangTypeOriginals = []
|
||||
achievementIDs = []
|
||||
for achievement in @achievements.models
|
||||
rewards = achievement.get('rewards')
|
||||
console.log 'rewards', rewards
|
||||
thangTypeOriginals.push rewards.heroes or []
|
||||
thangTypeOriginals.push rewards.items or []
|
||||
achievementIDs.push(achievement.id)
|
||||
thangTypeOriginals = _.uniq _.flatten thangTypeOriginals
|
||||
console.log 'thang type originals?', thangTypeOriginals
|
||||
for thangTypeOriginal in thangTypeOriginals
|
||||
thangType = new ThangType()
|
||||
thangType.url = "/db/thang.type/#{thangTypeOriginal}/version"
|
||||
thangType.project = ['original', 'rasterIcon']
|
||||
thangType.project = ['original', 'rasterIcon', 'name']
|
||||
@thangTypes[thangTypeOriginal] = @supermodel.loadModel(thangType, 'thang').model
|
||||
|
||||
url = "/db/earned_achievement?view=get-by-achievement-ids&achievementIDs=#{achievementIDs.join(',')}"
|
||||
earnedAchievements = new CocoCollection([], {
|
||||
url: url
|
||||
model: EarnedAchievement
|
||||
})
|
||||
res = @supermodel.loadCollection(earnedAchievements, 'earned_achievements')
|
||||
@earnedAchievements = res.model
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.levelName = utils.i18n @level.attributes, 'name'
|
||||
for achievement in @achievements.models
|
||||
earnedAchievementMap = _.indexBy(@earnedAchievements?.models or [], (ea) -> ea.get('achievement'))
|
||||
for achievement, index in @achievements.models
|
||||
achievement.completed = LocalMongo.matchesQuery(@session.attributes, achievement.get('query'))
|
||||
earnedAchievement = earnedAchievementMap[achievement.id]
|
||||
if earnedAchievement
|
||||
achievement.completedAWhileAgo = new Date() - Date.parse(earnedAchievement.get('created')) > 30 * 1000
|
||||
c.achievements = @achievements.models
|
||||
|
||||
# for testing the three states
|
||||
# if c.achievements.length
|
||||
# c.achievements = [c.achievements[0].clone(), c.achievements[0].clone(), c.achievements[0].clone()]
|
||||
# for achievement, index in c.achievements
|
||||
# achievement.completed = index > 0
|
||||
# achievement.completedAWhileAgo = index > 1
|
||||
|
||||
c.thangTypes = @thangTypes
|
||||
return c
|
||||
return c
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
return unless @supermodel.finished()
|
||||
complete = _.once(_.bind(@beginAnimateNumbers, @))
|
||||
@animatedPanels = $()
|
||||
panels = @$el.find('.achievement-panel')
|
||||
for panel in panels
|
||||
panel = $(panel)
|
||||
continue unless panel.data('animate')
|
||||
@animatedPanels = @animatedPanels.add(panel)
|
||||
panel.delay(500)
|
||||
panel.queue(->
|
||||
$(this).addClass('earned') # animate out the grayscale
|
||||
$(this).dequeue()
|
||||
)
|
||||
panel.delay(500)
|
||||
panel.queue(->
|
||||
$(this).find('.reward-image-container').addClass('show')
|
||||
$(this).dequeue()
|
||||
)
|
||||
panel.delay(500)
|
||||
panel.queue(-> complete())
|
||||
|
||||
beginAnimateNumbers: ->
|
||||
@numericalItemPanels = _.map(@animatedPanels.find('.numerical'), (panel) -> {
|
||||
number: $(panel).data('number')
|
||||
textEl: $(panel).find('.reward-text')
|
||||
rootEl: $(panel)
|
||||
unit: $(panel).data('number-unit')
|
||||
})
|
||||
|
||||
# TODO: mess with this more later. Doesn't seem to work, often times will pulse background red rather than animate
|
||||
# itemPanel.rootEl.find('.reward-image-container img').addClass('pulse') for itemPanel in @numericalItemPanels
|
||||
@numberAnimationStart = new Date()
|
||||
@totalXP = 0
|
||||
@totalXP += panel.number for panel in @numericalItemPanels when panel.unit is 'xp'
|
||||
@totalGems = 0
|
||||
@totalGems += panel.number for panel in @numericalItemPanels when panel.unit is 'gem'
|
||||
@gemEl = $('#gem-total')
|
||||
@XPEl = $('#xp-total')
|
||||
@numberAnimationInterval = setInterval(@tickNumberAnimation, 15 / 1000)
|
||||
|
||||
tickNumberAnimation: =>
|
||||
pct = Math.min(1, (new Date() - @numberAnimationStart) / 1500)
|
||||
panel.textEl.text('+'+parseInt(panel.number*pct)) for panel in @numericalItemPanels
|
||||
@XPEl.text('+'+parseInt(@totalXP * pct))
|
||||
@gemEl.text('+'+parseInt(@totalGems * pct))
|
||||
@endAnimateNumbers() if pct is 1
|
||||
|
||||
endAnimateNumbers: ->
|
||||
@$el.find('.pulse').removeClass('pulse')
|
||||
clearInterval(@numberAnimationInterval)
|
||||
|
|
|
@ -14,6 +14,26 @@ class EarnedAchievementHandler extends Handler
|
|||
hasAccess: (req) ->
|
||||
req.method is 'GET' # or req.user.isAdmin()
|
||||
|
||||
get: (req, res) ->
|
||||
return @getByAchievementIDs(req, res) if req.query.view is 'get-by-achievement-ids'
|
||||
super(arguments...)
|
||||
|
||||
getByAchievementIDs: (req, res) ->
|
||||
query = { user: req.user._id+''}
|
||||
ids = req.query.achievementIDs
|
||||
if (not ids) or (ids.length is 0)
|
||||
return @sendBadInputError(res, 'For a get-by-achievement-ids request, need to provide ids.')
|
||||
|
||||
ids = ids.split(',')
|
||||
for id in ids
|
||||
if not Handler.isID(id)
|
||||
return @sendBadInputError(res, "Not a MongoDB ObjectId: #{id}")
|
||||
|
||||
query.achievement = {$in: ids}
|
||||
EarnedAchievement.find query, (err, earnedAchievements) ->
|
||||
return @sendDatabaseError(res, err) if err
|
||||
res.send(earnedAchievements)
|
||||
|
||||
recalculate: (req, res) ->
|
||||
onSuccess = (data) => log.debug 'Finished recalculating achievements'
|
||||
if 'achievements' of req.body # Support both slugs and IDs separated by commas
|
||||
|
|
Loading…
Add table
Reference in a new issue