codecombat/app/lib/surface/WizardSprite.coffee

274 lines
9.5 KiB
CoffeeScript
Raw Normal View History

2014-01-03 13:32:13 -05:00
IndieSprite = require 'lib/surface/IndieSprite'
{me} = require 'lib/auth'
module.exports = class WizardSprite extends IndieSprite
# Wizard targets are constantly changing, so a simple tween doesn't work.
# Instead, the wizard stores its origin point and the (possibly) moving target.
# Then it figures out its current position based on tween percentage and
# those two points.
tweenPercentage: 1.0
originPos: null
targetPos: null
targetSprite: null
reachedTarget: true
spriteXOffset: 4 # meters from target sprite
spriteYOffset: 0 # meters from target sprite
subscriptions:
'bus:player-states-changed': 'onPlayerStatesChanged'
'auth:me-synced': 'onMeSynced'
2014-01-31 13:21:32 -05:00
'surface:sprite-selected': 'onSpriteSelected'
'sprite:echo-all-wizard-sprites': 'onEchoAllWizardSprites'
shortcuts:
'up': 'onMoveKey'
'down': 'onMoveKey'
'left': 'onMoveKey'
'right': 'onMoveKey'
2014-01-03 13:32:13 -05:00
constructor: (thangType, options) ->
if @isSelf = options.isSelf
options.colorConfig = $.extend(true, {}, me.get('wizard')?.colorConfig) or {}
2014-01-03 13:32:13 -05:00
super thangType, options
@targetPos = @thang.pos
if @isSelf
@setNameLabel me.displayName()
else if options.name
@setNameLabel options.name
Backbone.Mediator.publish 'self-wizard:created', sprite: @
2014-01-03 13:32:13 -05:00
2014-08-24 19:09:06 -04:00
makeIndieThang: (thangType, options) ->
thang = super thangType, options
2014-01-03 13:32:13 -05:00
thang.isSelectable = false
thang.bobHeight = 0.75
2014-01-31 13:21:32 -05:00
thang.bobTime = 2
2014-02-01 14:02:20 -05:00
thang.pos.z += thang.bobHeight
2014-01-03 13:32:13 -05:00
thang
2014-05-20 15:32:26 -04:00
finishSetup: ->
@updateBaseScale()
@scaleFactor = @thang.scaleFactor if @thang?.scaleFactor
@updateScale()
@updateRotation()
# Don't call general update() because Thang isn't built yet
setNameLabel: (name) ->
if @options.codeLanguage and @options.codeLanguage isnt 'javascript' and not @isSelf
name += " (#{@options.codeLanguage})" # TODO: move on second line, capitalize properly
super name
2014-08-23 22:00:35 -04:00
toggle: (to) ->
@imageObject?.visible = to
label[if to then 'show' else 'hide']() for name, label of @labels
mark.mark?.visible = to for name, mark of @marks
2014-08-23 22:00:35 -04:00
2014-01-03 13:32:13 -05:00
onPlayerStatesChanged: (e) ->
for playerID, state of e.states
continue unless playerID is @thang.id
@setEditing state.wizard?.editing
continue if playerID is me.id # ignore changes for self wizard sprite
@setNameLabel state.name
continue unless state.wizard?
if targetID = state.wizard.targetSprite
2014-06-30 22:16:26 -04:00
return console.warn 'Wizard Sprite couldn\'t find target sprite', targetID unless targetID of @options.sprites
2014-01-03 13:32:13 -05:00
@setTarget @options.sprites[targetID]
else
@setTarget state.wizard.targetPos
onMeSynced: (e) ->
return unless @isSelf
@setNameLabel me.displayName() if @imageObject.visible # not if we hid the wiz
2014-02-24 15:52:35 -05:00
newColorConfig = me.get('wizard')?.colorConfig or {}
shouldUpdate = not _.isEqual(newColorConfig, @options.colorConfig)
@options.colorConfig = $.extend(true, {}, newColorConfig)
2014-02-24 15:52:35 -05:00
if shouldUpdate
2014-08-29 20:03:02 -04:00
@setUpSprite()
@playAction(@currentAction) if @currentAction
2014-01-03 13:32:13 -05:00
onSpriteSelected: (e) ->
return unless @isSelf
@setTarget e.sprite or e.worldPos
animateIn: ->
@imageObject.scaleX = @imageObject.scaleY = @imageObject.alpha = 0
createjs.Tween.get(@imageObject)
2014-01-03 13:32:13 -05:00
.to({scaleX: 1, scaleY: 1, alpha: 1}, 1000, createjs.Ease.getPowInOut(2.2))
@labels.name?.show()
2014-01-03 13:32:13 -05:00
animateOut: (callback) ->
tween = createjs.Tween.get(@imageObject)
2014-01-03 13:32:13 -05:00
.to({scaleX: 0, scaleY: 0, alpha: 0}, 1000, createjs.Ease.getPowInOut(2.2))
tween.call(callback) if callback
@labels.name?.hide()
2014-01-03 13:32:13 -05:00
setEditing: (@editing) ->
if @editing
@thang.actionActivated = @thang.action isnt 'cast'
@thang.action = 'cast'
else
@thang.action = 'idle' if @thang.action is 'cast'
setInitialState: (targetPos, @targetSprite) ->
@targetPos = @getPosFromTarget(@targetSprite or targetPos)
@endMoveTween()
onEchoAllWizardSprites: (e) -> e.payload.push @
2014-02-01 14:02:20 -05:00
defaultPos: -> x: 35, y: 24, z: @thang.depth / 2 + @thang.bobHeight
2014-01-03 13:32:13 -05:00
move: (pos, duration) -> @setTarget(pos, duration)
2014-03-01 15:15:49 -05:00
setTarget: (newTarget, duration, isLinear=false) ->
2014-01-03 13:32:13 -05:00
# ignore targets you're already heading for
targetPos = @getPosFromTarget(newTarget)
return if @targetPos and @targetPos.x is targetPos.x and @targetPos.y is targetPos.y
# ignore selecting sprites you can't control
isSprite = newTarget?.thang?
return if isSprite and not newTarget.thang.isProgrammable
return if isSprite and newTarget is @targetSprite
@shoveOtherWizards(true) if @targetSprite
@targetSprite = if isSprite then newTarget else null
@targetPos = @boundWizard targetPos
2014-03-01 15:15:49 -05:00
@beginMoveTween(duration, isLinear)
2014-01-03 13:32:13 -05:00
@shoveOtherWizards()
Backbone.Mediator.publish('self-wizard:target-changed', {sprite: @}) if @isSelf
2014-01-03 13:32:13 -05:00
boundWizard: (target) ->
# Passed an {x, y} in world coordinates, returns {x, y} within world bounds
return target unless @options.camera.bounds
@bounds = @options.camera.bounds
surfaceTarget = @options.camera.worldToSurface target
x = Math.min(Math.max(surfaceTarget.x, @bounds.x), @bounds.x + @bounds.width)
y = Math.min(Math.max(surfaceTarget.y, @bounds.y), @bounds.y + @bounds.height)
return @options.camera.surfaceToWorld {x: x, y: y}
2014-01-03 13:32:13 -05:00
getPosFromTarget: (target) ->
"""
Could be null, a vector, or sprite object. Get the position from any of these.
"""
return @defaultPos() unless target?
return target if target.x?
return target.thang.pos
2014-03-01 15:15:49 -05:00
beginMoveTween: (duration=1000, isLinear=false) ->
2014-01-03 13:32:13 -05:00
# clear the old tween
createjs.Tween.removeTweens(@)
# create a new tween to go from the current location to the new location
@originPos = _.clone(@thang.pos)
@tweenPercentage = 1.0
@thang.action = 'move'
@pointToward(@targetPos)
if duration is 0
@updatePosition()
@endMoveTween()
return
2014-03-01 15:15:49 -05:00
if isLinear
ease = createjs.Ease.linear
else
ease = createjs.Ease.getPowInOut(3.0)
2014-01-03 13:32:13 -05:00
createjs.Tween
.get(@)
2014-06-30 22:16:26 -04:00
.to({tweenPercentage: 0.0}, duration, ease)
2014-01-03 13:32:13 -05:00
.call(@endMoveTween)
@reachedTarget = false
@update true
2014-01-03 13:32:13 -05:00
shoveOtherWizards: (removeMe) ->
return unless @targetSprite
allWizards = []
Backbone.Mediator.publish 'sprite:echo-all-wizard-sprites', payload: allWizards
2014-01-03 13:32:13 -05:00
allOfUs = (wizard for wizard in allWizards when wizard.targetSprite is @targetSprite)
allOfUs = (wizard for wizard in allOfUs when wizard isnt @) if removeMe
# diagonal lineup pattern
# wizardPosition = [[4, 0], [5,1], [3,-1], [6,2], [2,-2]]
# step = 3
# for wizard, i in allOfUs
# [x,y] = wizardPositions[i%@wizardPositions.length]
# wizard.spriteXOffset = x
# wizard.spriteYOffset = y
# wizard.beginMoveTween()
# circular pattern
step = Math.PI * 2 / allOfUs.length
for wizard, i in allOfUs
wizard.spriteXOffset = 5*Math.cos(step*i)
wizard.spriteYOffset = 4*Math.sin(step*i)
wizard.beginMoveTween()
endMoveTween: =>
@thang.action = if @editing then 'cast' else 'idle'
@thang.actionActivated = @thang.action is 'cast'
@reachedTarget = true
@faceTarget()
@update true
2014-01-03 13:32:13 -05:00
2014-08-29 20:03:02 -04:00
updatePosition: (whileLoading=false) ->
return if whileLoading or not @options.camera
2014-01-03 13:32:13 -05:00
@thang.pos = @getCurrentPosition()
@faceTarget()
2014-02-01 14:02:20 -05:00
sup = @options.camera.worldToSurface x: @thang.pos.x, y: @thang.pos.y, z: @thang.pos.z - @thang.depth / 2
@imageObject.x = sup.x
@imageObject.y = sup.y
2014-01-03 13:32:13 -05:00
getCurrentPosition: ->
"""
Takes into account whether the wizard is in transit or not, and the bobbing up and down.
Eventually will also adjust based on where other wizards are.
"""
2014-02-14 17:10:28 -05:00
@targetPos = @targetSprite.thang.pos if @targetSprite?.thang
2014-01-03 13:32:13 -05:00
pos = _.clone(@targetPos)
2014-02-01 14:02:20 -05:00
pos.z = @defaultPos().z + @getBobOffset()
2014-01-03 13:32:13 -05:00
@adjustPositionToSideOfTarget(pos) if @targetSprite # be off to the side depending on placement in world
return pos if @reachedTarget # stick like glue
# if here, then the wizard is in transit. Calculate the diff!
pos =
x: pos.x + ((@originPos.x - pos.x) * @tweenPercentage)
y: pos.y + ((@originPos.y - pos.y) * @tweenPercentage)
z: pos.z
return pos
adjustPositionToSideOfTarget: (targetPos) ->
targetPos.x += @spriteXOffset
return
# doesn't work when you're zoomed in on the target, so disabling
center = @options.camera.surfaceToWorld(@options.camera.currentTarget).x
distanceFromCenter = Math.abs(targetPos.x - center)
if @spriteXOffset
distanceFromTarget = Math.abs(@spriteXOffset) - (1 / (distanceFromCenter + (1/Math.abs(@spriteXOffset))))
else
distanceFromTarget = 0
@onLeftSide = targetPos.x > center
@onLeftSide = not @onLeftSide if @spriteXOffset < 0
distanceFromTarget *= -1 if @onLeftSide
targetPos.x += distanceFromTarget # adjusted
targetPos.y += @spriteYOffset
faceTarget: ->
2014-02-14 17:10:28 -05:00
if @targetSprite?.thang
2014-01-03 13:32:13 -05:00
@pointToward(@targetSprite.thang.pos)
updateMarks: ->
super() if @imageObject.visible # not if we hid the wiz
2014-03-01 15:15:49 -05:00
onMoveKey: (e) ->
return unless @isSelf
e?.preventDefault()
yMovement = 0
xMovement = 0
yMovement += 2 if key.isPressed('up')
yMovement -= 2 if key.isPressed('down')
xMovement += 2 if key.isPressed('right')
xMovement -= 2 if key.isPressed('left')
@moveWizard xMovement, yMovement
moveWizard: (x, y) ->
2014-03-02 03:32:30 -05:00
interval = 500
position = {x: @targetPos.x + x, y: @targetPos.y + y}
@setTarget(position, interval, true)
2014-03-01 15:15:49 -05:00
@updatePosition()
Backbone.Mediator.publish 'camera:zoom-to', pos: position, duration: interval