bemusic/bemuse

View on GitHub
bemuse/src/game/display/player-display.js

Summary

Maintainability
F
3 days
Test Coverage
import * as touch3d from './touch3d'
import NoteArea from './note-area'
import { MISSED, breaksCombo } from '../judgments'
import { getGauge } from './Gauge'

export class PlayerDisplay {
  constructor(player, skinData) {
    this._currentSpeed = 1
    this._player = player
    this._noteArea = new NoteArea(
      player.notechart.notes,
      player.notechart.barLines
    )
    this._stateful = {}
    this._defaultData = {
      placement: player.options.placement,
      scratch: player.options.scratch,
      key_mode: player.notechart.getKeyMode(player.options.scratch),
      lane_lift: Math.max(0, -player.options.laneCover),
      lane_press: Math.max(0, player.options.laneCover),
    }
    this._gauge = getGauge(player.options.gauge)
    this._touch3dMode = skinData.displayMode === 'touch3d'
  }

  update(time, gameTime, playerState) {
    const touch3dMode = this._touch3dMode
    const player = this._player
    const noteArea = this._noteArea
    const stateful = this._stateful
    const beat = player.notechart.secondsToBeat(gameTime)
    const position = player.notechart.beatToPosition(beat)
    const spacing = player.notechart.spacingAtBeat(beat)
    const data = Object.assign({}, this._defaultData)
    const push = (key, value) => (data[key] || (data[key] = [])).push(value)
    const gauge = this._gauge

    this._currentSpeed += (playerState.speed - this._currentSpeed) / 3
    const speed = this._currentSpeed * spacing

    updateBeat()
    updateVisibleNotes()
    updateBarLines()
    updateInput()
    updateJudgment()
    updateGauge()
    updateExplode()

    data['speed'] = playerState.speed.toFixed(1) + 'x'
    data['stat_1'] = getCount(1)
    data['stat_2'] = getCount(2)
    data['stat_3'] = getCount(3)
    data['stat_4'] = getCount(4)
    data['stat_missed'] = getCount(MISSED)
    data['stat_acc'] = getAccuracy()
    const bpm = player.notechart.bpmAtBeat(beat)
    data['bpm'] = bpm < 1 ? '' : Math.round(bpm) % 10000 || ''

    Object.assign(data, stateful)
    return data

    function updateBeat() {
      data.beat = beat
    }

    function getCount(judgment) {
      return playerState.stats.counts && playerState.stats.counts[judgment]
    }

    function getAccuracy() {
      return ((playerState.stats.currentAccuracy || 0) * 100).toFixed(2) + '%'
    }

    function updateVisibleNotes() {
      const entities = noteArea.getVisibleNotes(position, getUpperBound(), 1)
      if (touch3dMode) {
        const putNote = (id, noteY, column, scale = 1) => {
          const row = touch3d.getRow(noteY - 0.01)
          const columnIndex = +column || -1
          const areaWidth = touch3d.PLAY_AREA_WIDTH
          const xOffset =
            row.projection * areaWidth * ((2 * (columnIndex - 0.5)) / 7 - 1)
          const desiredWidth = (row.projection * scale * areaWidth * 2) / 7
          push(`note3d_${column}`, {
            key: id,
            y: row.y,
            x: xOffset + 1280 / 2,
            width: desiredWidth,
          })
        }
        const longNoteStep = 3 / 128
        for (const entity of entities) {
          const note = entity.note
          const column = note.column
          if (entity.height) {
            let c = 0
            const start = entity.y + entity.height
            for (
              let i =
                start -
                Math.max(
                  0,
                  Math.floor((start - 1) / longNoteStep) * longNoteStep
                );
              i >= 0 && i >= entity.y;
              i -= longNoteStep
            ) {
              putNote(note.id + 'x' + c++, i, column, 0.8)
            }
            putNote(note.id, entity.y + entity.height, column)
          } else {
            if (playerState.getNoteStatus(note) !== 'judged') {
              putNote(note.id, entity.y, column)
            }
          }
        }
      } else {
        for (const entity of entities) {
          const note = entity.note
          const column = note.column
          if (entity.height) {
            const judgment = playerState.getNoteJudgment(note)
            const status = playerState.getNoteStatus(note)
            push(`longnote_${column}`, {
              key: note.id,
              y: entity.y,
              height: entity.height,
              active: judgment !== 0 && judgment !== MISSED,
              missed: status === 'judged' && judgment === MISSED,
            })
          } else {
            if (playerState.getNoteStatus(note) !== 'judged') {
              push(`note_${column}`, {
                key: note.id,
                y: entity.y,
              })
            }
          }
        }
      }
    }

    function updateBarLines() {
      const entities = noteArea.getVisibleBarLines(position, getUpperBound(), 1)
      for (const entity of entities) {
        if (touch3dMode) {
          const row = touch3d.getRow(entity.y - 0.01)
          push('barlines3d', {
            key: entity.id,
            y: row.y,
            x: row.projection * -touch3d.PLAY_AREA_WIDTH + 1280 / 2,
            width: row.projection * touch3d.PLAY_AREA_WIDTH * 2,
          })
        } else {
          push('barlines', { key: entity.id, y: entity.y })
        }
      }
    }

    function updateInput() {
      const input = playerState.input
      for (const column of player.columns) {
        const control = input.get(column)
        data[`${column}_active`] = control.value !== 0 ? 1 : 0
        if (control.changed) {
          if (control.value !== 0) {
            stateful[`${column}_down`] = time
          } else {
            stateful[`${column}_up`] = time
          }
        }
      }
    }

    function updateJudgment() {
      const notifications = playerState.notifications.judgments
      const notification = notifications[notifications.length - 1]
      if (notification) {
        const name =
          notification.judgment === -1 ? 'missed' : `${notification.judgment}`
        stateful[`judge_${name}`] = time
        const deviationMode =
          notification.judgment === -1 || notification.judgment === 1
            ? 'none'
            : notification.delta > 0
            ? 'late'
            : notification.delta < 0
            ? 'early'
            : 'none'
        stateful[`judge_deviation_${deviationMode}`] = time
        stateful['combo'] = notification.combo
      }
      data['score'] = playerState.stats.score
    }

    function updateGauge() {
      gauge.update(playerState)
      if (gauge.shouldDisplay()) {
        if (!stateful['gauge_enter']) stateful['gauge_enter'] = time
      } else {
        if (stateful['gauge_enter']) {
          if (!stateful['gauge_exit']) stateful['gauge_exit'] = time
        }
      }
      data['gauge_primary'] = gauge.getPrimary()
      data['gauge_secondary'] = gauge.getSecondary()
      data['gauge_extra'] = gauge.getExtra()
    }

    function updateExplode() {
      const notifications = playerState.notifications.judgments
      for (let i = 0; i < notifications.length; i++) {
        const notification = notifications[i]
        if (!breaksCombo(notification.judgment)) {
          stateful[`${notification.column}_explode`] = time
        }
      }
    }

    function getUpperBound() {
      return position + 5 / speed
    }
  }
}

export default PlayerDisplay