<commit_before>CocoClass = require 'lib/CocoClass'
{createProgressBar} = require './sprite_utils'
Camera = require './Camera'
Mark = require './Mark'
Label = require './Label'
AudioPlayer = require 'lib/AudioPlayer'
{me} = require 'lib/auth'

# We'll get rid of this once level's teams actually have colors
healthColors =
  ogres: [64, 128, 212]
  humans: [255, 0, 0]
  neutral: [64, 212, 128]

# Sprite: EaselJS-based view/controller for Thang model
module.exports = Lank = class Lank extends CocoClass
  thangType: null # ThangType instance

  sprite: null

  healthBar: null
  marks: null
  labels: null
  ranges: null

  options:
    groundLayer: null
    textLayer: null
    floatingLayer: null
    thang: null
    camera: null
    showInvisible: false

  possessed: false
  flipped: false
  flippedCount: 0
  actionQueue: null
  actions: null
  rotation: 0

  # Scale numbers
  scaleFactorX: 1 # Current scale adjustment. This can change rapidly.
  scaleFactorY: 1
  targetScaleFactorX: 1 # What the scaleFactor is going toward during a tween.
  targetScaleFactorY: 1

  # ACTION STATE
  # Actions have relations. If you say 'move', 'move_side' may play because of a direction
  # relationship, and if you say 'cast', 'cast_begin' may happen first, or 'cast_end' after.
  currentRootAction: null  # action that, in general, is playing or will play
  currentAction: null  # related action that is right now playing

  subscriptions:
    'level:sprite-dialogue': 'onDialogue'
    'level:sprite-clear-dialogue': 'onClearDialogue'
    'level:set-letterbox': 'onSetLetterbox'
    'surface:ticked': 'onSurfaceTicked'
    'sprite:move': 'onMove'

  constructor: (@thangType, options) ->
    super()
    @isMissile = @thangType.get('name') in ['Arrow', 'Spear']
    @options = _.extend($.extend(true, {}, @options), options)
    @setThang @options.thang
    if @thang?
      options = @thang?.getLankOptions?()
      @options.colorConfig = options.colorConfig if options and options.colorConfig
    console.error @toString(), 'has no ThangType!' unless @thangType

    # this is a stub, use @setSprite to swap it out for something else later
    @sprite = new createjs.Container

    @actionQueue = []
    @marks = {}
    @labels = {}
    @ranges = []
    @handledDisplayEvents = {}
    @age = 0
    @stillLoading = true
    if @thangType.isFullyLoaded() then @onThangTypeLoaded() else @listenToOnce(@thangType, 'sync', @onThangTypeLoaded)

  toString: -> "<Lank: #{@thang?.id}>"

  onThangTypeLoaded: ->
    @stillLoading = false
    for trigger, sounds of @thangType.get('soundTriggers') or {} when trigger isnt 'say'
      AudioPlayer.preloadSoundReference sound for sound in sounds when sound
    if @thangType.get('raster')
      @actions = {}
      @isRaster = true
    else
      @actions = @thangType.getActions()
      @createMarks()

    @scaleFactorX = @thang.scaleFactorX if @thang?.scaleFactorX?
    @scaleFactorX = @thang.scaleFactor if @thang?.scaleFactor?
    @scaleFactorY = @thang.scaleFactorY if @thang?.scaleFactorY?
    @scaleFactorY = @thang.scaleFactor if @thang?.scaleFactor?
    @updateAction() unless @currentAction

  setSprite: (newSprite) ->
    if @sprite
      @sprite.destroy?()
      if parent = @sprite.parent
        parent.removeChild @sprite
        parent.addChild newSprite

    # get the lank to update things
    for prop in ['lastPos', 'currentRootAction']
      delete @[prop]

    @sprite = newSprite
    if @thang and @thang.stateChanged is false
      @thang.stateChanged = true
    @configureMouse()
    @sprite.on 'animationend', @playNextAction
    @playAction(@currentAction) if @currentAction and not @stillLoading
    @trigger 'new-sprite', @sprite

  ##################################################
  # QUEUEING AND PLAYING ACTIONS

  queueAction: (action) ->
    # The normal way to have an action play
    action = @actions[action] if _.isString(action)
    action ?= @actions.idle
    @actionQueue = []
    @actionQueue.push @currentRootAction.relatedActions.end if @currentRootAction?.relatedActions?.end
    @actionQueue.push action.relatedActions.begin if action.relatedActions?.begin
    @actionQueue.push action
    if action.goesTo and nextAction = @actions[action.goesTo]
      @actionQueue.push nextAction if nextAction
    @currentRootAction = action
    @playNextAction()

  onSurfaceTicked: (e) -> @age += e.dt

  playNextAction: =>
    @playAction(@actionQueue.splice(0, 1)[0]) if @actionQueue.length

  playAction: (action) ->
    return if @isRaster
    @currentAction = action
    return @hide() unless action.animation or action.container or action.relatedActions or action.goesTo
    @show()
    return @updateActionDirection() unless action.animation or action.container or action.goesTo
    return if @sprite.placeholder
    m = if action.container then 'gotoAndStop' else 'gotoAndPlay'
    @sprite[m]?(action.name)
    @updateScale()
    @updateRotation()

  hide: ->
    @hiding = true
    @updateAlpha()

  show: ->
    @hiding = false
    @updateAlpha()

  stop: ->
    @sprite?.stop?()
    mark.stop() for name, mark of @marks
    @stopped = true

  play: ->
    @sprite?.play?()
    mark.play() for name, mark of @marks
    @stopped = false

  update: (frameChanged) ->
    # Gets the sprite to reflect what the current state of the thangs and surface are
    return false if @stillLoading
    if (frameChanged and @thang and @thang.stateChanged) or (@thang and @thang.bobHeight) or @notOfThisWorld
      @updatePosition()
    return false if @thang and @thang.stateChanged is false
    frameChanged = frameChanged or @targetScaleFactorX isnt @scaleFactorX or @targetScaleFactorY isnt @scaleFactorY
    if frameChanged
      @handledDisplayEvents = {}
      @updateScale()  # must happen before rotation
      @updateAlpha()
      @updateRotation()
      @updateAction()
      @updateStats()
      @updateGold()
      @showAreaOfEffects()
      @showTextEvents()
      @updateHealthBar()
    @updateMarks()
    @updateLabels()<commit_msg>Redid my bobbing fix b286b24 in a way that doesn't break the LankBoss position test.<commit_after>CocoClass = require 'lib/CocoClass'
{createProgressBar} = require './sprite_utils'
Camera = require './Camera'
Mark = require './Mark'
Label = require './Label'
AudioPlayer = require 'lib/AudioPlayer'
{me} = require 'lib/auth'

# We'll get rid of this once level's teams actually have colors
healthColors =
  ogres: [64, 128, 212]
  humans: [255, 0, 0]
  neutral: [64, 212, 128]

# Sprite: EaselJS-based view/controller for Thang model
module.exports = Lank = class Lank extends CocoClass
  thangType: null # ThangType instance

  sprite: null

  healthBar: null
  marks: null
  labels: null
  ranges: null

  options:
    groundLayer: null
    textLayer: null
    floatingLayer: null
    thang: null
    camera: null
    showInvisible: false

  possessed: false
  flipped: false
  flippedCount: 0
  actionQueue: null
  actions: null
  rotation: 0

  # Scale numbers
  scaleFactorX: 1 # Current scale adjustment. This can change rapidly.
  scaleFactorY: 1
  targetScaleFactorX: 1 # What the scaleFactor is going toward during a tween.
  targetScaleFactorY: 1

  # ACTION STATE
  # Actions have relations. If you say 'move', 'move_side' may play because of a direction
  # relationship, and if you say 'cast', 'cast_begin' may happen first, or 'cast_end' after.
  currentRootAction: null  # action that, in general, is playing or will play
  currentAction: null  # related action that is right now playing

  subscriptions:
    'level:sprite-dialogue': 'onDialogue'
    'level:sprite-clear-dialogue': 'onClearDialogue'
    'level:set-letterbox': 'onSetLetterbox'
    'surface:ticked': 'onSurfaceTicked'
    'sprite:move': 'onMove'

  constructor: (@thangType, options) ->
    super()
    @isMissile = @thangType.get('name') in ['Arrow', 'Spear']
    @options = _.extend($.extend(true, {}, @options), options)
    @setThang @options.thang
    if @thang?
      options = @thang?.getLankOptions?()
      @options.colorConfig = options.colorConfig if options and options.colorConfig
    console.error @toString(), 'has no ThangType!' unless @thangType

    # this is a stub, use @setSprite to swap it out for something else later
    @sprite = new createjs.Container

    @actionQueue = []
    @marks = {}
    @labels = {}
    @ranges = []
    @handledDisplayEvents = {}
    @age = 0
    @stillLoading = true
    if @thangType.isFullyLoaded() then @onThangTypeLoaded() else @listenToOnce(@thangType, 'sync', @onThangTypeLoaded)

  toString: -> "<Lank: #{@thang?.id}>"

  onThangTypeLoaded: ->
    @stillLoading = false
    for trigger, sounds of @thangType.get('soundTriggers') or {} when trigger isnt 'say'
      AudioPlayer.preloadSoundReference sound for sound in sounds when sound
    if @thangType.get('raster')
      @actions = {}
      @isRaster = true
    else
      @actions = @thangType.getActions()
      @createMarks()

    @scaleFactorX = @thang.scaleFactorX if @thang?.scaleFactorX?
    @scaleFactorX = @thang.scaleFactor if @thang?.scaleFactor?
    @scaleFactorY = @thang.scaleFactorY if @thang?.scaleFactorY?
    @scaleFactorY = @thang.scaleFactor if @thang?.scaleFactor?
    @updateAction() unless @currentAction

  setSprite: (newSprite) ->
    if @sprite
      @sprite.destroy?()
      if parent = @sprite.parent
        parent.removeChild @sprite
        parent.addChild newSprite

    # get the lank to update things
    for prop in ['lastPos', 'currentRootAction']
      delete @[prop]

    @sprite = newSprite
    if @thang and @thang.stateChanged is false
      @thang.stateChanged = true
    @configureMouse()
    @sprite.on 'animationend', @playNextAction
    @playAction(@currentAction) if @currentAction and not @stillLoading
    @trigger 'new-sprite', @sprite

  ##################################################
  # QUEUEING AND PLAYING ACTIONS

  queueAction: (action) ->
    # The normal way to have an action play
    action = @actions[action] if _.isString(action)
    action ?= @actions.idle
    @actionQueue = []
    @actionQueue.push @currentRootAction.relatedActions.end if @currentRootAction?.relatedActions?.end
    @actionQueue.push action.relatedActions.begin if action.relatedActions?.begin
    @actionQueue.push action
    if action.goesTo and nextAction = @actions[action.goesTo]
      @actionQueue.push nextAction if nextAction
    @currentRootAction = action
    @playNextAction()

  onSurfaceTicked: (e) -> @age += e.dt

  playNextAction: =>
    @playAction(@actionQueue.splice(0, 1)[0]) if @actionQueue.length

  playAction: (action) ->
    return if @isRaster
    @currentAction = action
    return @hide() unless action.animation or action.container or action.relatedActions or action.goesTo
    @show()
    return @updateActionDirection() unless action.animation or action.container or action.goesTo
    return if @sprite.placeholder
    m = if action.container then 'gotoAndStop' else 'gotoAndPlay'
    @sprite[m]?(action.name)
    @updateScale()
    @updateRotation()

  hide: ->
    @hiding = true
    @updateAlpha()

  show: ->
    @hiding = false
    @updateAlpha()

  stop: ->
    @sprite?.stop?()
    mark.stop() for name, mark of @marks
    @stopped = true

  play: ->
    @sprite?.play?()
    mark.play() for name, mark of @marks
    @stopped = false

  update: (frameChanged) ->
    # Gets the sprite to reflect what the current state of the thangs and surface are
    return false if @stillLoading
    thangUnchanged = @thang and @thang.stateChanged is false
    if (frameChanged and not thangUnchanged) or (@thang and @thang.bobHeight) or @notOfThisWorld
      @updatePosition()
    return false if thangUnchanged
    frameChanged = frameChanged or @targetScaleFactorX isnt @scaleFactorX or @targetScaleFactorY isnt @scaleFactorY
    if frameChanged
      @handledDisplayEvents = {}
      @updateScale()  # must happen before rotation
      @updateAlpha()
      @updateRotation()
      @updateAction()
      @updateStats()
      @updateGold()
      @showAreaOfEffects()
      @showTextEvents()
      @updateHealthBar()
    @updateMarks()
    @updateLabels()
{'fraction_non_alphanumeric': 0.11520395550061804, 'fraction_numerical': 0.005521219612690565, 'mean_word_length': 3.519925512104283, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6171373', 'n_tokens_mistral': 3818, 'n_tokens_neox': 3707, 'n_words': 1088}