Set up the PlayHeroesModal, and hooked it into the world map.

This commit is contained in:
Scott Erickson 2014-11-05 19:03:05 -08:00
parent c48b155413
commit c660053dea
8 changed files with 570 additions and 24 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View file

@ -55,6 +55,7 @@
confirm: "Confirm"
owned: "Owned" # For items you own
locked: "Locked"
available: "Available"
skills_granted: "Skills Granted" # Property documentation details
heroes: "Heroes" # Tooltip on hero shop button from /play
achievements: "Achievements" # Tooltip on achievement list button from /play
@ -312,6 +313,9 @@
io_blurb: "Simple but obscure."
status: "Status"
weapons: "Weapons"
weapons_warrior: "Swords - Short Range, No Magic"
weapons_ranger: "Crossbows, Guns - Long Range, No Magic"
weapons_wizard: "Wands, Staffs - Long Range, Magic"
attack: "Damage" # Can also translate as "Attack"
health: "Health"
speed: "Speed"

View file

@ -1,4 +1,328 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
$heroCanvasHeight: 265px
#play-heroes-modal
.hero-view
color: black
//- Clear modal defaults
.modal-dialog
padding: 0
width: 820px
height: 658px
//- Background
#play-heroes-background
position: absolute
top: -59px
left: -20px
//- Header
h1
position: absolute
left: 154px
top: 25px
margin: 0
width: 450px
text-align: center
color: rgb(254,188,68)
font-size: 38px
text-shadow: black 4px 4px 0, black -4px -4px 0, black 4px -4px 0, black -4px 4px 0, black 4px 0px 0, black 0px -4px 0, black -4px 0px 0, black 0px 4px 0
//- Close modal button
#close-modal
position: absolute
left: 615px
top: 17px
width: 60px
height: 60px
color: white
text-align: center
font-size: 30px
padding-top: 15px
cursor: pointer
@include rotate(-3deg)
&:hover
color: yellow
//- Carousel character portraits
#hero-carousel
width: 750px
height: 386px
position: absolute
left: 34px
top: 117px
.carousel-indicator-container
position: relative
z-index: 1
.carousel-indicators
position: static
width: 100%
margin-left: 0
.hero-indicator
width: 104px
height: 98px
margin: 0 -11px
position: relative
background: url(/images/pages/play/modal/hero_portrait_picker_inactive.png)
border: none
&.active
background: url(/images/pages/play/modal/hero_portrait_picker_active.png)
z-index: 5
.hero-avatar
width: 61px
height: 61px
background-size: contain
position: relative
left: 21px
top: 18px
&.locked
.hero-avatar
@include filter(contrast(50%) brightness(65%))
.lock-indicator
position: absolute
width: 40%
left: 30%
top: 30%
@include filter(invert(90%))
//- Small transformations to jumble the hero icons a little
.hero-index-0
transform: rotate(-5deg)
z-index: 2
.hero-index-1
top: -3px
z-index: 1
.hero-index-2
top: -3px
transform: rotate(5deg)
z-index: 1
.hero-index-3
transform: rotate(-1deg)
z-index: 0
.hero-index-4
transform: rotate(3deg)
.hero-index-5
z-index: 0
.hero-index-6
transform: rotate(6deg)
top: -8px
z-index: 1
.hero-index-8
transform: rotate(4deg)
//- Carousel panel
#hero-carousel
.hero-item
&.locked
@include opacity(0.6)
canvas, .hero-feature-image
width: 334px
height: $heroCanvasHeight
float: left
.hero-stats
width: 384px
height: $heroCanvasHeight
float: left
.hero-feature-image
display: none
text-align: center
img
height: $heroCanvasHeight
.hero-stats
color: white
h2
margin-top: 0px
color: white
.hero-description
margin-bottom: 10px
.hero-stat-row
margin: 5px 0
.stat-label
float: left
width: 100px
color: rgb(203,170,148)
.stat-value
display: inline-block
width: 280px
color: rgb(244,189,68)
.stat-progress
background: rgb(32,27,22)
height: 15px
padding: 4px 5px
border-radius: 16px
position: relative
top: 2px
left: -3px
width: 70%
.stat-progress-bar
height: 7px
border-radius: 7px
&.attack .stat-progress-bar
background: purple
&.health .stat-progress-bar
background: red
&.speed .stat-progress-bar
background: green
//- Carousel switch buttons
a.left, a.right
color: rgb(74,61,51)
position: absolute
top: 195px
width: 40px
height: 84px
font-size: 24px
.glyphicon
position: relative
top: 27px
left: 8px
&:hover, &:active
color: rgb(126,105,88)
a.right
right: -49px
a.left
left: -46px
.glyphicon
@include scaleXY(-1, 1)
//- Programming select box
.form
position: absolute
left: 32px
top: 527px
width: 541px
height: 102px
padding: 10px 40px
.help-block
color: rgb(51,51,51)
font-size: 14px
font-weight: bold
select
font-size: 18px
.fancy-select
display: inline-block
width: 100%
.options
text-transform: none
.trigger, .options
background-color: rgb(239,232,217)
width: 100%
color: black
.trigger
text-transform: uppercase
border: 3px solid black
font-size: 16px
padding: 5px 10px
//- the little triangle on the right side of the fancy select box
&:after
border: 8px solid transparent
border-top-color: black
top: 13px
right: 11px
.options
padding-left: 5px
.selected
color: black
.hover
color: black
background-color: #abc
.options
li
padding-left: 40px
background: transparent url(/images/common/code_languages/javascript_small.png) no-repeat left center
background-size: 32px 32px
&[data-value="python"]
background-image: url(/images/common/code_languages/python_small.png)
&[data-value="coffeescript"]
background-image: url(/images/common/code_languages/coffeescript_small.png)
&[data-value="clojure"]
background-image: url(/images/common/code_languages/clojure_small.png)
&[data-value="lua"]
background-image: url(/images/common/code_languages/lua_small.png)
&[data-value="io"]
background-image: url(/images/common/code_languages/io_small.png)
#confirm-button
background: url(/images/pages/play/modal/confirm-button.png)
width: 209px
height: 110px
position: absolute
left: 588px
top: 522px
padding: 36px 0
text-align: center
text-transform: uppercase
font-size: 26px
font-family: Open Sans Condensed
color: white
body.ipad #play-heroes-modal
// iPad is Python-only for now, and has its own reset button.
.form
display: none

View file

@ -1,7 +1,63 @@
extends /templates/modal/modal_base
.modal-dialog
.modal-content
block modal-header-content
h3(data-i18n="play.heroes") Heroes
img(src="/images/pages/play/modal/play-heroes-background.png")#play-heroes-background
h1(data-i18n="choose_hero.choose_hero")
block modal-body-content
p TODO: show all dem heroes
div#close-modal
span.glyphicon.glyphicon-remove
#hero-carousel.carousel.slide(data-interval=0)
.carousel-indicator-container
ol.carousel-indicators
for hero, index in heroes
li(data-hero-id=hero.get('original'), title=hero.get('name'), data-slide-to=index, data-target="#hero-carousel", class="hero-indicator hero-index-" + index + (hero.locked ? " locked" : ""))
.hero-avatar
if hero.locked
img.lock-indicator(src="/images/pages/game-menu/lock.png")
.carousel-inner
for hero in heroes
div(class="item hero-item" + (hero.locked ? " locked" : ""), data-hero-id=hero.get('original'))
canvas.hero-canvas
.hero-feature-image
img
.hero-stats
h2= hero.name
.hero-description= hero.description
.hero-stat-row
.stat-label(data-i18n='choose_hero.status')
.stat-value(data-i18n=hero.locked ? 'play.locked' : 'play.available')
.hero-stat-row
.stat-label(data-i18n='choose_hero.weapons')
.stat-value(data-i18n='choose_hero.weapons_'+hero.class)
if hero.stats
if hero.stats.skills.length
.hero-stat-row
.stat-label(data-i18n='choose_hero.skills')
.stat-value= hero.stats.skills.join(', ')
for stat in ['attack', 'health', 'speed']
.hero-stat-row(class=stat)
.stat-label(data-i18n='choose_hero.'+stat)
.stat-value
.stat-progress
.stat-progress-bar(style="width: " + (parseInt(hero.stats[stat]*100)) + "%")
a.left(role="button", data-slide="prev", href="#hero-carousel")
span.glyphicon.glyphicon-play
a.right(role="button", data-slide="next", href="#hero-carousel")
span.glyphicon.glyphicon-play
.form
.form-group.select-group
span.help-block(data-i18n="choose_hero.programming_language_description") Which programming language do you want to use?
//label.control-label(for="option-code-language", data-i18n="choose_hero.programming_language") Programming Language
select#option-code-language(name="code-language")
for option in codeLanguages
option(value=option.id, selected=codeLanguage === option.id)= option.name
a#confirm-button(data-i18n=confirmButtonI18N)

View file

@ -2,46 +2,208 @@ ModalView = require 'views/kinds/ModalView'
template = require 'templates/play/modal/play-heroes-modal'
CocoCollection = require 'collections/CocoCollection'
ThangType = require 'models/ThangType'
#HeroView = require 'views/game-menu/HeroView'
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
AudioPlayer = require 'lib/AudioPlayer'
utils = require 'lib/utils'
module.exports = class PlayHeroesModal extends ModalView
className: 'modal fade play-modal'
template: template
modalWidthPercent: 90
id: 'play-heroes-modal'
#instant: true
#events:
# 'change input.select': 'onSelectionChanged'
events:
'slide.bs.carousel #hero-carousel': 'onHeroChanged'
'change #option-code-language': 'onCodeLanguageChanged'
'click #close-modal': 'hide'
'click #confirm-button': 'saveAndHide'
shortcuts:
'left': -> @$el.find('#hero-carousel').carousel('prev') if @heroes.models.length and not @$el.hasClass 'secret'
'right': -> @$el.find('#hero-carousel').carousel('next') if @heroes.models.length and not @$el.hasClass 'secret'
constructor: (options) ->
super options
options ?= {}
@confirmButtonI18N = options.confirmButtonI18N ? "common.save"
@heroes = new CocoCollection([], {model: ThangType})
@heroes.url = '/db/thang.type?view=heroes&project=name,description,components,original,rasterIcon'
@heroes.url = '/db/thang.type?view=heroes'
@heroes.setProjection ['original','name','slug','soundTriggers','featureImage','gems','heroClass','description']
@listenToOnce @heroes, 'sync', @onHeroesLoaded
@supermodel.loadCollection(@heroes, 'heroes')
@stages = {}
@session = options.session
onHeroesLoaded: ->
for hero in @heroes.models
hero.name = utils.i18n hero.attributes, 'extendedName' # or whatever the property name ends up being
hero.name ?= utils.i18n hero.attributes, 'name'
hero.description = utils.i18n hero.attributes, 'description'
original = hero.get('original')
hero.locked = original not in [ThangType.heroes.captain, ThangType.heroes.knight] and not me.ownsHero(original)
hero.class = (hero.get('heroClass') or 'warrior').toLowerCase()
hero.stats = hero.getHeroStats()
getRenderData: (context={}) ->
context = super(context)
context.heroes = @heroes.models
context.level = @options.level
context.codeLanguages = [
{id: 'python', name: 'Python (Default)'}
{id: 'javascript', name: 'JavaScript'}
{id: 'coffeescript', name: 'CoffeeScript'}
{id: 'clojure', name: 'Clojure (Experimental)'}
{id: 'lua', name: 'Lua (Experimental)'}
{id: 'io', name: 'Io (Experimental)'}
]
context.codeLanguage = @codeLanguage = @options?.session?.get('codeLanguage') ? me.get('aceConfig')?.language ? 'python'
context.confirmButtonI18N = @confirmButtonI18N
context
afterRender: ->
super()
return unless @supermodel.finished()
heroes = @heroes.models
@$el.find('.hero-indicator').each ->
heroID = $(@).data('hero-id')
hero = _.find heroes, (hero) -> hero.get('original') is heroID
$(@).find('.hero-avatar').css('background-image', "url(#{hero.getPortraitURL()})").tooltip()
@canvasWidth = 313 # @$el.find('canvas').width() # unreliable, whatever
@canvasHeight = @$el.find('canvas').height()
heroConfig = @options?.session?.get('heroConfig') ? me.get('heroConfig') ? {}
heroIndex = Math.max 0, _.findIndex(heroes, ((hero) -> hero.get('original') is heroConfig.thangType))
@$el.find(".hero-item:nth-child(#{heroIndex + 1}), .hero-indicator:nth-child(#{heroIndex + 1})").addClass('active')
@onHeroChanged direction: null, relatedTarget: @$el.find('.hero-item')[heroIndex]
@$el.find('.hero-stat').tooltip()
@buildCodeLanguages()
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-open', volume: 1
#@addHeroViews()
onHeroChanged: (e) ->
direction = e.direction # 'left' or 'right'
heroItem = $(e.relatedTarget)
hero = _.find @heroes.models, (hero) -> hero.get('original') is heroItem.data('hero-id')
return console.error "Couldn't find hero from heroItem:", heroItem unless hero
heroIndex = heroItem.index()
hero = @loadHero hero, heroIndex
@preloadHero heroIndex + 1
@preloadHero heroIndex - 1
@selectedHero = hero unless hero.locked
Backbone.Mediator.publish 'level:hero-selection-updated', hero: @selectedHero
$('#choose-inventory-button').prop 'disabled', hero.locked
getFullHero: (original) ->
url = "/db/thang.type/#{original}/version"
if fullHero = @supermodel.getModel url
return fullHero
fullHero = new ThangType()
fullHero.setURL url
fullHero = (@supermodel.loadModel fullHero, 'thang').model
fullHero
preloadHero: (heroIndex) ->
return unless hero = @heroes.models[heroIndex]
@loadHero hero, heroIndex, true
loadHero: (hero, heroIndex, preloading=false) ->
createjs.Ticker.removeEventListener 'tick', stage for stage in _.values @stages
if featureImage = hero.get 'featureImage'
$(".hero-item[data-hero-id='#{hero.get('original')}'] canvas").hide()
$(".hero-item[data-hero-id='#{hero.get('original')}'] .hero-feature-image").show().find('img').prop('src', '/file/' + featureImage)
@playSelectionSound hero unless preloading
return hero
createjs.Ticker.setFPS 30 # In case we paused it from being inactive somewhere else
if stage = @stages[heroIndex]
unless preloading
_.defer -> createjs.Ticker.addEventListener 'tick', stage # Deferred, otherwise it won't start updating for some reason.
@playSelectionSound hero
return hero
fullHero = @getFullHero hero.get 'original'
onLoaded = =>
return unless canvas = $(".hero-item[data-hero-id='#{fullHero.get('original')}'] canvas")
canvas.show().prop width: @canvasWidth, height: @canvasHeight
builder = new SpriteBuilder(fullHero)
movieClip = builder.buildMovieClip(fullHero.get('actions').attack?.animation ? fullHero.get('actions').idle.animation)
movieClip.scaleX = movieClip.scaleY = canvas.prop('height') / 120 # Average hero height is ~110px tall at normal resolution
if fullHero.get('name') in ['Knight', 'Robot Walker'] # These are too big, so shrink them.
movieClip.scaleX *= 0.7
movieClip.scaleY *= 0.7
movieClip.regX = -fullHero.get('positions').registration.x
movieClip.regY = -fullHero.get('positions').registration.y
movieClip.x = canvas.prop('width') * 0.5
movieClip.y = canvas.prop('height') * 0.925 # This is where the feet go.
stage = new createjs.Stage(canvas[0])
@stages[heroIndex] = stage
stage.addChild movieClip
stage.update()
movieClip.gotoAndPlay 0
unless preloading
createjs.Ticker.addEventListener 'tick', stage
@playSelectionSound hero
if fullHero.loaded
_.defer onLoaded
else
@listenToOnce fullHero, 'sync', onLoaded
fullHero
playSelectionSound: (hero) ->
return if @$el.hasClass 'secret'
@currentSoundInstance?.stop()
return unless sounds = hero.get('soundTriggers')?.selected
return unless sound = sounds[Math.floor Math.random() * sounds.length]
name = AudioPlayer.nameForSoundReference sound
AudioPlayer.preloadSoundReference sound
@currentSoundInstance = AudioPlayer.playSound name, 1
@currentSoundInstance
buildCodeLanguages: ->
$select = @$el.find('#option-code-language')
$select.fancySelect().parent().find('.options li').each ->
languageName = $(@).text()
languageID = $(@).data('value')
blurb = $.i18n.t("choose_hero.#{languageID}_blurb")
$(@).text("#{languageName} - #{blurb}")
onCodeLanguageChanged: (e) ->
@codeLanguage = @$el.find('#option-code-language').val()
@codeLanguageChanged = true
saveAndHide: ->
hero = @selectedHero.get('original')
if @session
changed = @updateHeroConfig(@session, hero)
if @session.get('codeLanguage') isnt @codeLanguage
@session.set('codeLanguage', @codeLanguage)
changed = true
@session.patch() if changed
changed = @updateHeroConfig(me, hero)
aceConfig = _.clone(me.get('aceConfig'))
if @codeLanguage isnt aceConfig.language
aceConfig.language = @codeLanguage
me.set 'aceConfig', aceConfig
changed = true
me.patch() if changed
@hide()
updateHeroConfig: (model, hero) ->
heroConfig = _.clone(model.get('heroConfig')) or {}
if heroConfig.thangType isnt hero
heroConfig.thangType = hero
model.set('heroConfig', heroConfig)
return true
onHidden: ->
super()
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-close', volume: 1
#addHeroViews: ->
# keys = (hero.id for hero in @heroes.models)
# heroMap = _.zipObject keys, @heroes.models
# for heroStub in @$el.find('.replace-me')
# heroID = $(heroStub).data('hero-id')
# hero = heroMap[heroID]
# heroView = new HeroView({hero: hero, includes: {name: true, stats: true, props: true}})
# heroView.render()
# $(heroStub).replaceWith(heroView.$el)
# @registerSubView(heroView)
destroy: ->
for heroIndex, stage of @stages
createjs.Ticker.removeEventListener "tick", stage
stage.removeAllChildren()
super()