engagementgamelab/CivicSeed

View on GitHub
client/code/game/game.input.js

Summary

Maintainability
F
1 wk
Test Coverage
'use strict'
/* global ss, $, $game */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

    input.js

    - Handles on-screen buttons, HUD elements, generic gameboard
      interactions, keypresses, and codes/effects triggered in chat.
    - Also handles various UI interactions, like show / hide / toggle of
      overlays.

 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

var $input = module.exports = (function () {
  var $body

  // Remember if any keys are held down, to prevent
  // repeated firings of certain keys
  var keysHeld = {}

  function isKeyInputAccepted () {
    // TODO: There may be other conditions to test whether keypad input should be accepted.
    return (!$('input, textarea').is(':focus')) ? true : false
  }

  function isKeyHeldDown (keycode) {
    return (keycode in keysHeld)
  }

  function recordHeldKey (keycode) {
    if (keycode) {
      keysHeld[keycode] = true
    }
  }

  function deleteHeldKey (keycode) {
    delete keysHeld[keycode]
  }

  // Decide if we should or should not let buttons be clicked based on state
  function isNewActionAllowed () {
    // Check all the game states (if windows are open ,in transit, etc.) to begin a new action
    return (
      $game.running &&
      !$game.flags.check('screen-transition') &&
      !$game.flags.check('botanist-chatting') &&
      !$game.flags.check('visible-seedventory') &&
      !$game.flags.check('visible-skinventory') &&
      !$game.flags.check('visible-help') &&
      !$game.flags.check('visible-progress') &&
      !$game.flags.check('visible-resource-overlay') &&
      !$game.flags.check('visible-botanist-overlay') &&
      !$game.flags.check('visible-boss-overlay') &&
      !$game.flags.check('playing-cutscene')
    ) ? true : false
  }

  // Inputs for game activities
  function trigger (input) {
    switch (input) {
      case 'FOREST':
        if ($game.flags.check('teleport-forest')) {
          outfitLog('Teleporting to ' + $game.world.northwest.name + '!')
          $game.$player.beam({x: 15, y: 22})
        } else {
          return false
        }
        break
      case 'TOWN':
        if ($game.flags.check('teleport-town')) {
          outfitLog('Teleporting to ' + $game.world.northeast.name + '!')
          $game.$player.beam({x: 98, y: 31})
        } else {
          return false
        }
        break
      case 'RANCH':
        if ($game.flags.check('teleport-ranch')) {
          outfitLog('Teleporting to ' + $game.world.southeast.name + '!')
          $game.$player.beam({x: 131, y: 96})
        } else {
          return false
        }
        break
      case 'PORT':
        if ($game.flags.check('teleport-port')) {
          outfitLog('Teleporting to ' + $game.world.southwest.name + '!')
          $game.$player.beam({x: 33, y: 99})
        } else {
          return false
        }
        break
      case 'kazaam':
        outfitLog('Starting collaborative challenge.')
        ss.rpc('game.player.collaborativeChallenge', function (err) {
          // Not implemented.
          if (err) {
            $game.log('Whoops: you came alone, you get no bone(us).')
          }
        })
        break
      default:
        return false
    }
  }

  // Cheats only
  function cheat (input) {
    switch (input.toLowerCase()) {
      case 'beam me up scotty':
      case 'beam me up, Scotty!':   // Legacy cheat with punctuation
        cheatLog('Teleporting to botanist.')
        $game.$player.beam({x: 70, y: 74})
        break
      case 'show me the money':
        cheatLog('Adding 50 seeds.')
        $game.$player.addSeeds('regular', 50)
        break
      case 'like one of your french girls':
        cheatLog('Adding 50 paint seeds.')
        $game.$player.addSeeds('draw', 50)
        break
      case 'loki':
        cheatLog('Debug seed amount.')
        $game.$player.setSeeds('regular', 0)
        $game.$player.setSeeds('draw', 3)
        break
      case 'ding me':
        cheatLog('Leveling up!')
        $game.$player.nextLevel()
        break
      case 'suit alors':
        cheatLog('All suits unlocked!')
        var sets = $game.$skins.getSetsList()
        for (var i in sets) {
          $game.$skins.unlockSkin(sets[i])
        }
        break
      case 'birthday suit':
        cheatLog('All suits removed!')
        $game.$skins.resetSkinventory()
        break
      case 'pleasantville':
        cheatLog('Welcome to Pleasantville!')
        $game.bossModeUnlocked = true
        $game.flags.set('boss-mode-unlocked')
        $game.flags.set('boss-mode-ready')
        $game.$player.currentLevel = 4
        $game.toBossLevel()
        break
      default:
        return false
    }
  }

  function log (color, tag, message) {
    $game.log('<span class="color-' + color + '">[' + tag + ']</span>' + ' ' + message)
  }

  function outfitLog (message) {
    log('lightpurple', 'Outfit effect', message)
  }

  function cheatLog (message) {
    log('yellow', 'Cheat code activated', message)
  }

  return {
    init: function () {
      $body = $(document.body)

      /* * * * * * * *       GENERIC GAMEBOARD INTERACTION       * * * * * * * */

      // Update cursor on mouse move
      $body.on('mousemove', '#gameboard', function (e) {
        if (!$game.flags.check('screen-transition') && $game.running) {
          $game.$mouse.onMove({
            event: e,
            x: e.pageX,
            y: e.pageY,
            offX: this.offsetLeft,
            offY: this.offsetTop
          })
        }
      })

      // Decide what to do on mouse click
      $body.on('click', '#gameboard', function (e) {
        if (isNewActionAllowed() === true) {
          $game.$mouse.onClick({
            event: e,
            x: e.pageX,
            y: e.pageY,
            offX: this.offsetLeft,
            offY: this.offsetTop
          })
        }
      })

      /* * * * * * * *                HUD BUTTONS                * * * * * * * */

      // Prevent clicking of any HUD button if resource or botanist overlays are shown
      $body.on('click', '.hud-button', function (e) {
        if ($game.flags.check('visible-resource-overlay') || $game.flags.check('visible-botanist-overlay')) {
          e.stopImmediatePropagation()
        }
      })

      // Toggle display of Inventory
      $body.on('click', '.hud-inventory, #inventory button', function () {
        $game.inventory.toggle()
      })

      // Toggle display of Changing Room (skinventory)
      $body.on('click', '.hud-skinventory', function () {
        $input.toggleSkinventory()
      })

      // Toggle display of Seed inventory (seedventory)
      $body.on('click', '.hud-seed', function () {
        $input.toggleSeedMode()
      })

      // Toggle display of Game log
      $body.on('click', '.hud-log', function () {
        $input.toggleGameLog()
      })

      // Toggle display of Progress window
      $body.on('click', '.hud-progress', function () {
        $input.toggleProgress()
      })

      // Toggle Audio on/off
      $body.on('click', '.hud-mute', function () {
        $input.toggleMute()
      })

      // Toggle display of Help window
      $body.on('click', '.hud-help', function () {
        $input.toggleHelp()
      })

      // Display a tooltip when player hovers over HUD controls
      $body.on('mouseenter', '.hud-button a', function () {
        $(this).tooltip('show')
      })

      // When player clicks a highlighted HUD button, remove the highlight
      $body.on('click', '.hud-button-highlight', function () {
        $input.unhighlightHUDButton(this)
      })

      /* * * * * * * *      SEEDVENTORY WINDOW INTERACTIONS      * * * * * * * */

      // When a seed button is clicked, clear all seed selected (highlight) classes
      $body.on('click', '#seedventory .seed', function () {
        $('#seedventory .seed').removeClass('selected')
      })

      // Select regular seed
      $body.on('click', '#seedventory .regular-button', function () {
        $('.regular-button').addClass('selected')
        $game.$player.startSeeding('regular')
      })

      // Select draw seed
      $body.on('click', '#seedventory .draw-button', function () {
        $('.draw-button').addClass('selected')

        $game.$player.startSeeding('draw')

        $body.on('mousedown touchstart', '#gameboard', function () {
          $game.$player.drawFirstSeed()
          $game.flags.set('draw-mode')
        })
        $body.on('mouseup touchend', '#gameboard', function () {
          $game.flags.unset('draw-mode')
        })
      })

      // Close Seed inventory
      $body.on('click', '#seedventory .close-button', function () {
        $input.endSeedMode()
      })

      /* * * * * * * *       PROGRESS WINDOW INTERACTIONS        * * * * * * * */

      // You vs Everyone progress map tabs
      $body.on('click', '.tab-you', function () {
        // '.active' class is based on Bootstrap's 'tabbable'
        $('#tab-you-pane').addClass('active')
        $('#tab-everyone-pane').removeClass('active')
      })

      $body.on('click', '.tab-everyone', function () {
        // '.active' class is based on Bootstrap 'tabbable'
        $('#tab-everyone-pane').addClass('active')
        $('#tab-you-pane').removeClass('active')
      })

      // View all player resource answers
      $body.on('click', '.collected-button', function () {
        $('#collected-resources').show()
      })

      // Go back to progress menu from player resource answers
      $body.on('click', '#collected-resources .back-button', function () {
        $('#collected-resources').hide()
      })

      // Close Progress window
      $body.on('click', '#progress-area .close-overlay', function (e) {
        e.preventDefault()
        $input.closeProgress()
        return false
      })

      /* * * * * * * *      SKINVENTORY WINDOW INTERACTIONS      * * * * * * * */

      // Click to equip a new skin part
      $body.on('click', '#skinventory .outer', function () {
        if (!$(this).hasClass('locked')) {
          var name = $(this).data('name')
          var part = $(this).parent().data('part')

          // Set highlight class
          $(this).parent().children().removeClass('equipped')
          $(this).addClass('equipped')

          // Set suit
          $game.$skins.changePlayerSkin(name, part)
        }
      })

      // Close skinventory
      $body.on('click', '#skinventory .close-button', function (e) {
        e.preventDefault()
        $input.closeSkinventory()
        return false
      })

      /* * * * * * * *            HELP WINDOW OVERLAY            * * * * * * * */

      // Close Help window
      $body.on('click', '#help-area a i, #help-area .close-button', function (e) {
        e.preventDefault()
        $input.closeHelp()
        return false
      })

      /* * * * * * * *        RESOURCE WINDOW INTERACTIONS       * * * * * * * *

      NOTE:   Interactions bound to some buttons in the resource overlay are
              programatically bound when needed in resources.js.

       * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

      // Close the resource area
      $body.on('click', '#resource-area .close-overlay', function (e) {
        e.preventDefault()
        $game.$resources.hideResource()
        return false
      })

      // Make your comment public
      $body.on('click', '.public-button button', function () {
        $game.$player.makePublic($(this).attr('data-resource'))
        // Toggle state of button
        // TODO: place this presentation logic elsewhere
        // This toggling does not reflect success / error conditions from server
        $(this).parent().removeClass('public-button').addClass('private-button')
        $(this).parent().find('i').removeClass('fa-lock').addClass('fa-unlock-alt')
        $(this).text('Make Private')
      })

      // Make your comment private
      $body.on('click', '.private-button button', function () {
        $game.$player.makePrivate($(this).attr('data-resource'))
        // Toggle state of button
        // TODO: place this presentation logic elsewhere
        // This toggling does not reflect success / error conditions from server
        $(this).parent().removeClass('private-button').addClass('public-button')
        $(this).parent().find('i').removeClass('fa-unlock-alt').addClass('fa-lock')
        $(this).text('Make Public')
      })

      // Pledge a seed to a comment
      $body.on('click', '.pledge-button button', function () {
        var info = {
          id: $(this).attr('data-player'),
          pledger: $game.$player.firstName,
          resourceId: $(this).attr('data-resource')
        }

        var pledges = $game.$player.getPledges()

        if (pledges > 0) {
          ss.rpc('game.player.pledgeSeed', info, function (r) {
            $game.$player.updatePledges(-1)
            $game.$resources.showCheckMessage('Thanks! (they will say). You can seed ' + (pledges - 1) + ' more answers this level.')
            if ($game.flags.check('pledge-reward')) {
              $game.$player.addSeeds('draw', 10)
              outfitLog('You gained 10 paintbrush seeds for seeding another player’s response.')
            }
          })
        } else {
          $game.$resources.showCheckMessage('You cannot seed any more answers this level.')
        }
      })

      /* * * * * * * *       BOTANIST OVERLAY INTERACTIONS       * * * * * * * *

      NOTE:   Interactions bound to buttons in the botanist overlay are
              programatically bound when needed in botanist.js.

       * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

      // Close botanist window
      $body.on('click', '#botanist-area .close-overlay', function (e) {
        e.preventDefault()
        $game.$botanist.hideOverlay()
        return false
      })

      /* * * * * * * *        OTHER GAMEBOARD HUD ELEMENTS       * * * * * * * */

      $body.on('click', '#speech-bubble, #inventory, #botanist-area', function (e) {
        // Prevent clicking on interface elements from interacting with gameboard below
        e.stopImmediatePropagation()
      })

      // When scrolling article or log content, prevent page from scrolling also
      $body.on('mouseenter', '.scrollable, .content-box', function () {
        $(this).scroll(function () {
          $('body').css('overflow', 'hidden')
        })
      })
      $body.on('mouseleave', '.scrollable, .content-box', function () {
        $('body').css('overflow', 'auto')
      })

      // Send a chat message when submitted from the chat input field
      $body.on('click', '#chat-submit', function (e) {
        e.preventDefault()
        var el = document.getElementById('chat-input')
        var message = el.value

        // Stop chatting if player has tried to submit a blank message
        if (message === '') {
          el.blur()
          return false
        }

        $game.$audio.playTriggerFx('chatSend')

        // Check for chat triggers (e.g. cheat codes)
        if (trigger(message) === false && cheat(message) === false) {
          $game.$chat.send(message)
        }

        // Reset input box
        el.value = ''
        return true
      })

      $body.on('click', '#game-log', function () {
        $game.$log.clearUnread()
      })

      // Pause menu if we want it
      // This is currently disabled / unimplemented
      // $(window).blur(function (e) {
      //  $game.pause()
      // })

      // $('.unpause').click(function () {
      //  $game.resume()
      // })

      /* * * * * * * *                KEYBINDINGS                * * * * * * * */

      // Clear key from being held, if it is
      $body.keyup(function (e) {
        deleteHeldKey(e.which)

        switch (e.which) {
          case 87:  // 'w'
          case 38:  // 'up arrow', 'numpad 2' (numlock on)
          case 104: // 'numpad 2' (numlock off)
          case 56:  // 'numpad 2' (numlock off/opera)
          case 65:  // 'a'
          case 37:  // 'left arrow', 'numpad 4'
          case 100: // 'numpad 4' (numlock off)
          case 52:  // 'numpad 4' (numlock off/opera)
          case 83:  // 's'
          case 40:  // 'down arrow', 'numpad 8'
          case 98:  // 'numpad 8' (numlock off)
          case 50:  // 'numpad 8' (numlock off/opera)
          case 68:  // 'd'
          case 39:  // 'right arrow', 'numpad 6'
          case 102: // 'numpad 6' (numlock off)
          case 54:  // 'numpad 6' (numlock off/opera)
            e.preventDefault()
            // Any of these, if unheld, immediately stops movement
            $game.$player.moveStop()
            // And then remove scroll prevention
            $('body').css('overflow', 'auto')
            break
          default:
            // Nothing
            break
        }
      })

      $body.keydown(function (e) {
        // If escape is pressed, cancels any current action and returns to default gameboard view
        if (e.which === 27) {
          $input.resetUI()
        }

        // Allow keyboard inputs only when gameboard is active.
        if (!isNewActionAllowed()) return
        if (!isKeyInputAccepted()) return

        // Refuse inputs if Ctrl or Command is pressed so that the game doesn't overwrite other system/client command keys
        // This does not cover Mac's fn' key
        if (e.ctrlKey === true || e.metaKey === true || e.altKey === true) return

        // Attach keys to actions
        switch (e.which) {
          // **** MOVEMENT ****
          // Each movement event has .preventDefault() to prevent it from scrolling browser window
          case 87:  // 'w'
          case 38:  // 'up arrow', 'numpad 2' (numlock on)
          case 104: // 'numpad 2' (numlock off)
          case 56:  // 'numpad 2' (numlock off/opera)
            // Move player character up.
            e.preventDefault()
            // Disallow event from firing repeatedly on hold
            if (!isKeyHeldDown(e.which)) {
              $game.$player.moveStraight('up')
              recordHeldKey(e.which)
              // Prevent window scroll
              $('body').css('overflow', 'hidden')
            }
            break
          case 65:  // 'a'
          case 37:  // 'left arrow', 'numpad 4'
          case 100: // 'numpad 4' (numlock off)
          case 52:  // 'numpad 4' (numlock off/opera)
            // Move player character to the left.
            e.preventDefault()
            // Disallow event from firing repeatedly on hold
            if (!isKeyHeldDown(e.which)) {
              $game.$player.moveStraight('left')
              recordHeldKey(e.which)
              // Prevent window scroll
              $('body').css('overflow', 'hidden')
            }
            break
          case 83:  // 's'
          case 40:  // 'down arrow', 'numpad 8'
          case 98:  // 'numpad 8' (numlock off)
          case 50:  // 'numpad 8' (numlock off/opera)
            // Move player character down.
            e.preventDefault()
            // Disallow event from firing repeatedly on hold
            if (!isKeyHeldDown(e.which)) {
              $game.$player.moveStraight('down')
              recordHeldKey(e.which)
              // Prevent window scroll
              $('body').css('overflow', 'hidden')
            }
            break
          case 68:  // 'd'
          case 39:  // 'right arrow', 'numpad 6'
          case 102: // 'numpad 6' (numlock off)
          case 54:  // 'numpad 6' (numlock off/opera)
            // Move player character to the right.
            e.preventDefault()
            // Disallow event from firing repeatedly on hold
            if (!isKeyHeldDown(e.which)) {
              $game.$player.moveStraight('right')
              recordHeldKey(e.which)
              // Prevent window scroll
              $('body').css('overflow', 'hidden')
            }
            break
          // **** CHAT ****
          case 84:  // 't'
          case 13:  // 'enter'
            // Focus chat input field.
            e.preventDefault() // prevent 't' from appearing in the input.
            $input.focusChatInput()
            break
          // **** DISPLAY HUD OVERLAYS & WINDOWS ****
          case 73:  // 'i'
            // Display inventory overlay.
            $game.inventory.toggle()
            break
          case 69:  // 'e'
            // Seedventory
            $input.toggleSeedMode()
            break
          case 77:  // 'm'
            // Toggles minimap.
            // Currently disabled because there's currently no way for the player to know how to get this back in the UI.
            // $input.toggleMinimap()
            break
          case 67:  // 'c'
            // Changing room
            $input.toggleSkinventory()
            break
          case 76:  // 'l'
            // Game log
            $input.toggleGameLog()
            break
          case 80:  // 'p'
            // Progress
            $input.toggleProgress()
            break
          case 86:  // 'v'
            // Mute audio
            $input.toggleMute()
            break
          case 72:  // 'h'
          case 191: // 'question mark' (?)
            // Help
            $input.toggleHelp()
            break
          // Default switch: all other key presses, no action.
          default:
            break
        }
      })
    },

    focusChatInput: function () {
      document.getElementById('chat-input').focus()
    },

    // Toggle minimap
    // Currently disabled in UI.
    // TODO: Consider moving logic to $game.minimap module
    toggleMinimap: function () {
      if ($game.flags.check('first-time') === true) return // Disables if it's player's first time in the game.
      $('.minimap').toggle()
    },

    // Toggles Seed Mode.
    toggleSeedMode: function () {
      if ($game.$player.seedMode === true || $game.flags.check('seed-mode') === true) {
        $input.endSeedMode()
      } else {
        $input.startSeedMode()
      }
    },

    startSeedMode: function () {
      var seeds = $game.$player.getSeeds()

      $input.resetUI()

      $game.$player.seedMode = true
      $game.flags.set('seed-mode')
      $input.activeHUDButton('.hud-seed')

      // Force update of mouse cursor
      $game.$mouse.updateCursor()

      // Special controls for boss mode
      if ($game.flags.check('boss-mode')) {
        $game.$boss.startSeedMode()
      } else if (seeds.draw > 0) {
        // If player has multiple types of seeds, open up the seed inventory
        $input.openSeedventory(seeds)
      } else if (seeds.regular > 0) {
        // Otherwise, go straight to regular seed planting mode
        $game.$player.seedPlanting = true
        $game.$player.seedMode = 'regular'
        $game.alert('Click anywhere to plant a seed and watch color bloom there')
      } else {
        // No seeds, cancel out of seed mode.
        $game.alert('You have no seeds!')
        $input.endSeedMode()
      }
    },

    endSeedMode: function () {
      $game.$player.seedMode = false
      $game.flags.unset('seed-mode')
      if ($game.flags.check('visible-seedventory') === true) {
        $input.closeSeedventory()
      }
      document.getElementById('graffiti').style.display = 'none'
      $body.off('mousedown touchend', '#gameboard')
      $body.off('mouseup touchend', '#gameboard')
      $game.flags.unset('draw-mode')
      $game.$player.seedPlanting = false
      $input.inactiveHUDButton('.hud-seed')

      // Force update of mouse cursor
      $game.$mouse.updateCursor()

      $game.$player.saveMapImage()
      $game.$player.saveSeeds()
    },

    openSeedventory: function (seeds) {
      $game.alert('Choose a seed to plant')
      $('#seedventory').slideDown(300, function () {
        if (seeds.regular > 0) $('.regular-button').addClass('active')
        if (seeds.draw > 0) $('.draw-button').addClass('active')
        $game.flags.set('visible-seedventory')
      })
    },

    closeSeedventory: function () {
      $('#seedventory').slideUp(300, function () {
        $game.flags.unset('visible-seedventory')
      })
    },

    toggleSkinventory: function () {
      return ($game.flags.check('visible-skinventory') === true) ? $input.closeSkinventory() : $input.openSkinventory()
    },

    openSkinventory: function () {
      $input.resetUI()
      $input.activeHUDButton('.hud-skinventory')
      $game.flags.set('visible-skinventory')
      $('#skinventory').show()

      // Reset badge count
      $game.setBadgeCount('.hud-skinventory', 0)
    },

    closeSkinventory: function () {
      $game.flags.unset('visible-skinventory')
      $input.inactiveHUDButton('.hud-skinventory')
      $('#skinventory').hide()
    },

    toggleGameLog: function () {
      $input.resetUI()
      $game.$log.clearUnread()

      if ($('#game-log').is(':visible')) {
        $input.hideGameLog()
        $input.showGameLogOverlay()
      } else {
        $input.hideGameLogOverlay()
        $input.showGameLog()
      }
    },

    showGameLogOverlay: function () {
      $('#game-log-overlay').fadeIn(200)

      // Display a highlight HUD button for this
      $input.activeHUDButton('.hud-log')
    },

    hideGameLogOverlay: function () {
      $('#game-log-overlay').hide()
      $input.inactiveHUDButton('.hud-log')
    },

    showGameLog: function () {
      var height = $(window).height()
      $('html, body').stop().animate({
        scrollTop: height
      }, 250)
      $('#game-log').show()
    },

    hideGameLog: function () {
      $('#game-log').hide()
    },

    toggleProgress: function () {
      return ($game.flags.check('visible-progress') === true) ? $input.closeProgress() : $input.openProgress()
    },

    openProgress: function () {
      $input.resetUI()
      $input.activeHUDButton('.hud-progress')
      $game.flags.set('visible-progress')
      $game.updateProgressOverlay()
      $('#progress-area').show()
    },

    closeProgress: function () {
      $game.flags.unset('visible-progress')
      $input.inactiveHUDButton('.hud-progress')
      $('#progress-area').hide()
    },

    toggleHelp: function () {
      return ($game.flags.check('visible-help') === true) ? $input.closeHelp() : $input.openHelp()
    },

    openHelp: function () {
      $input.resetUI()
      $input.activeHUDButton('.hud-help')
      $game.flags.set('visible-help')
      $('#help-area').show()
    },

    closeHelp: function () {
      $game.flags.unset('visible-help')
      $input.inactiveHUDButton('.hud-help')
      $('#help-area').hide()
    },

    toggleMute: function () {
      return ($game.$audio.toggleMute() === true) ? $input.muteAudio() : $input.unmuteAudio()
    },

    muteAudio: function () {
      $('.hud-mute .fa').removeClass('fa-volume-up').addClass('fa-volume-off')
    },

    unmuteAudio: function () {
      $('.hud-mute .fa').removeClass('fa-volume-off').addClass('fa-volume-up')
    },

    // Add a highlight to a HUD button
    highlightHUDButton: function (target) {
      $('#hud .hud-button').removeClass('hud-button-highlight') // Removes all previous HUD highlights
      $(target).addClass('hud-button-highlight')
    },

    // Remove a highlight to a HUD button
    unhighlightHUDButton: function (target) {
      $(target).removeClass('hud-button-highlight')
    },

    // Highlight an active HUD button
    activeHUDButton: function (target) {
      $('#hud .hud-button').removeClass('hud-button-active') // Removes all previous HUD highlights
      $(target).addClass('hud-button-active')
    },

    // Remove highlight to active HUD button
    inactiveHUDButton: function (target) {
      $(target).removeClass('hud-button-active')
    },

    // Clears UI and sets everything into the most defaultest mode we can
    resetUI: function () {
      // Close any overlays
      $game.inventory.close()
      $input.closeSkinventory()
      $input.closeProgress()
      $input.closeHelp()
      $input.endSeedMode()

      // Also simultaneously cancel out of resources, botanist, and speechbubbles.
      $game.$npc.hideSpeechBubble()
      if ($game.flags.check('botanist-chatting') || $game.flags.check('visible-botanist-overlay')) {
        $game.$botanist.hideOverlay()
      }

      // Unfocus chat input box
      document.getElementById('chat-input').blur()

      // TODO: Set cursor to walk action
    }
  }
}())