Files
junisaber/src/state/index.js

395 lines
11 KiB
JavaScript
Raw Normal View History

/* global localStorage */
2018-07-20 21:16:50 +02:00
var utils = require('../utils');
2018-07-21 11:55:44 +02:00
const challengeDataStore = {};
const SEARCH_PER_PAGE = 6;
const SONG_NAME_TRUNCATE = 24;
const SONG_SUB_NAME_TRUNCATE = 32;
2018-07-20 19:58:09 +02:00
const DAMAGE_DECAY = 0.25;
const DAMAGE_MAX = 10;
/**
* State handler.
*
* 1. `handlers` is an object of events that when emitted to the scene will run the handler.
*
* 2. The handler function modifies the state.
*
* 3. Entities and components that are `bind`ed automatically update:
* `bind__<componentName>="<propertyName>: some.item.in.state"`
*/
2018-07-18 20:48:45 +02:00
AFRAME.registerState({
initialState: {
activeHand: localStorage.getItem('hand') || 'right',
2018-07-18 20:48:45 +02:00
challenge: {
2018-07-20 23:57:18 +02:00
author: '',
2018-07-20 20:47:42 +02:00
difficulty: '',
2018-07-18 20:48:45 +02:00
id: AFRAME.utils.getUrlParameter('challenge'),
2018-07-21 10:21:30 +02:00
image: '',
2018-07-20 23:57:18 +02:00
isLoading: false,
isBeatsPreloaded: false,
2018-07-20 23:57:18 +02:00
songName: '',
2018-07-21 10:21:30 +02:00
songSubName: ''
2018-07-18 20:48:45 +02:00
},
damage: 0,
2018-07-18 20:48:45 +02:00
inVR: false,
isGameOver: false, // Game over screen.
2018-10-03 18:58:26 -07:00
isPaused: false, // Playing, but paused. Not active during menu.
isPlaying: false, // Actively playing (slicing beats).
isSongFetching: false, // Fetching stage.
isSongLoading: false, // Either fetching or decoding.
isVictory: false, // Victory screen.
keyboardActive: false, // Whether search is open.
2018-10-15 16:44:38 -07:00
menuActive: true,
2018-07-20 20:47:42 +02:00
menuDifficulties: [],
2018-07-20 19:58:09 +02:00
menuSelectedChallenge: {
2018-07-21 10:21:30 +02:00
author: '',
difficulty: '',
downloads: '',
downloadsText: '',
2018-07-20 19:58:09 +02:00
id: '',
index: -1,
2018-07-21 10:21:30 +02:00
image: '',
songName: '',
songSubName: ''
},
multiplierText: '1x',
2018-07-21 10:21:30 +02:00
score: {
2018-10-16 02:57:07 -07:00
accuracy: '',
beatsHit: 0,
beatsMissed: 0,
2018-10-02 15:04:51 -07:00
combo: 0,
2018-10-16 02:57:07 -07:00
maxCombo: 0,
multiplier: 1,
rank: '',
score: 0
2018-07-20 19:58:09 +02:00
},
2018-07-21 11:55:44 +02:00
search: {
2018-09-18 03:08:18 -07:00
active: true,
2018-07-21 11:55:44 +02:00
page: 0,
hasNext: false,
hasPrev: false,
results: [],
songNameTexts: '',
songSubNameTexts: ''
2018-07-21 11:55:44 +02:00
},
searchResultsPage: []
2018-07-18 20:48:45 +02:00
},
handlers: {
/**
* Swap left-handed or right-handed mode.
*/
activehandswap: state => {
state.activeHand = state.activeHand === 'right' ? 'left' : 'right';
localStorage.setItem('activeHand', state.activeHand);
},
beathit: state => {
if (state.damage > DAMAGE_DECAY) {
state.damage -= DAMAGE_DECAY;
}
2018-10-16 02:57:07 -07:00
state.score.beatsHit++;
state.score.score++;
state.score.combo++;
if (state.score.combo > state.score.maxCombo) {
state.score.maxCombo = state.score.combo;
}
state.score.multiplier = state.score.combo >= 8
? 8
: 2 * Math.floor(Math.log2(state.score.combo));
},
beatmiss: state => {
2018-10-16 02:57:07 -07:00
state.score.beatsMissed++;
takeDamage(state);
},
beatwrong: state => {
2018-10-16 02:57:07 -07:00
state.score.beatsMissed++;
takeDamage(state);
},
2018-07-21 10:21:30 +02:00
beatloaderfinish: (state) => {
2018-07-18 20:48:45 +02:00
state.challenge.isLoading = false;
},
2018-10-10 00:33:28 -10:00
beatloaderpreloadfinish: (state) => {
state.challenge.isBeatsPreloaded = true;
2018-10-10 00:33:28 -10:00
},
2018-07-21 10:21:30 +02:00
beatloaderstart: (state) => {
state.challenge.isBeatsPreloaded = false;
2018-07-18 20:48:45 +02:00
state.challenge.isLoading = true;
},
2018-10-15 16:34:47 -07:00
gamemenuresume: (state) => {
state.isPaused = false;
},
gamemenurestart: (state) => {
resetScore(state);
state.isBeatsPreloaded = false;
state.isGameOver = false;
state.isPaused = false;
},
gamemenuexit: (state) => {
resetScore(state);
state.isBeatsPreloaded = false;
state.isGameOver = false;
state.isPaused = false;
state.isVictory = false;
2018-10-15 16:44:38 -07:00
state.menuActive = true;
2018-10-15 16:34:47 -07:00
state.challenge.id = '';
},
keyboardclose: (state) => {
state.keyboardActive = false;
},
keyboardopen: (state) => {
state.keyboardActive = true;
2018-10-04 03:24:43 +02:00
},
2018-07-20 19:58:09 +02:00
/**
* Song clicked from menu.
*/
2018-07-21 10:21:30 +02:00
menuchallengeselect: (state, id) => {
// Copy from challenge store populated from search results.
2018-07-20 19:58:09 +02:00
let challengeData = challengeDataStore[id];
2018-07-20 23:57:18 +02:00
Object.assign(state.menuSelectedChallenge, challengeData);
2018-07-21 10:21:30 +02:00
2018-07-21 12:02:37 +02:00
// Populate difficulty options.
2018-07-20 20:47:42 +02:00
state.menuDifficulties.length = 0;
2018-07-20 19:58:09 +02:00
for (let i = 0; i < challengeData.difficulties.length; i++) {
state.menuDifficulties.unshift(challengeData.difficulties[i]);
2018-07-20 19:58:09 +02:00
}
2018-07-21 12:02:37 +02:00
state.menuDifficulties.sort(difficultyComparator);
// Default to easiest difficulty.
state.menuSelectedChallenge.difficulty = state.menuDifficulties[0];
2018-07-21 10:21:30 +02:00
2018-07-20 21:16:50 +02:00
state.menuSelectedChallenge.image = utils.getS3FileUrl(id, 'image.jpg');
2018-07-20 23:57:18 +02:00
state.menuSelectedChallenge.downloadsText = `${challengeData.downloads} Plays`;
computeMenuSelectedChallengeIndex(state);
2018-10-16 01:33:23 -07:00
state.keyboardActive = false;
2018-07-20 19:58:09 +02:00
},
menuchallengeunselect: state => {
state.menuSelectedChallenge.id = '';
},
2018-07-21 10:21:30 +02:00
menudifficultyselect: (state, difficulty) => {
2018-07-20 20:47:42 +02:00
state.menuSelectedChallenge.difficulty = difficulty;
},
2018-09-19 05:54:05 -07:00
pausegame: (state) => {
2018-10-08 17:30:13 -07:00
if (!state.isPlaying) { return; }
state.isPaused = true;
2018-07-22 23:58:29 +02:00
},
2018-07-21 10:21:30 +02:00
/**
* Start challenge.
* Transfer staged challenge to the active challenge.
*/
playbuttonclick: (state) => {
resetScore(state);
2018-07-21 10:21:30 +02:00
// Set challenge. `beat-loader` is listening.
Object.assign(state.challenge, state.menuSelectedChallenge);
// Reset menu.
2018-10-15 16:44:38 -07:00
state.menuActive = false;
2018-07-21 10:21:30 +02:00
state.menuSelectedChallenge.id = '';
2018-10-05 03:17:40 -07:00
state.keyboardActive = false;
state.challenge.isLoading = true;
state.isSongLoading = true;
2018-07-18 20:48:45 +02:00
},
2018-07-21 11:55:44 +02:00
searchprevpage: function (state) {
if (state.search.page === 0) { return; }
state.search.page--;
computeSearchPagination(state);
},
searchnextpage: function (state) {
if (state.search.page > Math.floor(state.search.results.length / SEARCH_PER_PAGE)) {
return;
}
state.search.page++;
computeSearchPagination(state);
},
2018-07-20 15:42:47 +02:00
/**
* Update search results. Will automatically render using `bind-for` (menu.html).
*/
2018-07-21 10:21:30 +02:00
searchresults: (state, payload) => {
2018-07-20 15:42:47 +02:00
var i;
2018-07-21 11:55:44 +02:00
state.search.page = 0;
state.search.results = payload.results;
for (i = 0; i < payload.results.length; i++) {
let result = payload.results[i];
2018-07-26 02:32:15 +02:00
result.songSubName = result.songSubName || 'Unknown Artist';
result.shortSongName = truncate(result.songName, SONG_NAME_TRUNCATE).toUpperCase();
result.shortSongSubName = truncate(result.songSubName, SONG_SUB_NAME_TRUNCATE);
2018-10-13 17:19:36 -07:00
challengeDataStore[result.id] = result;
2018-07-20 15:42:47 +02:00
}
2018-07-21 11:55:44 +02:00
computeSearchPagination(state);
computeMenuSelectedChallengeIndex(state);
2018-07-18 20:48:45 +02:00
},
songfetchfinish: (state) => {
state.isSongFetching = false;
},
songloadfinish: (state) => {
state.isSongFetching = false;
state.isSongLoading = false;
},
songloadstart: (state) => {
state.isSongFetching = true;
state.isSongLoading = true;
},
2018-07-21 10:21:30 +02:00
'enter-vr': (state) => {
2018-07-18 20:48:45 +02:00
state.inVR = true;
},
2018-07-21 10:21:30 +02:00
'exit-vr': (state) => {
2018-07-18 20:48:45 +02:00
state.inVR = false;
2018-10-08 16:24:31 -07:00
},
victory: function (state) {
state.isVictory = true;
2018-10-16 02:57:07 -07:00
const accuracy = state.beatsHit / (state.beatsMissed + state.beatsHit);
state.score.accuracy = `${(accuracy * 100).toFixed()}%`;
if (accuracy === 1) {
state.rank = 'S';
} else if (accuracy >= .90) {
state.rank = 'A';
} else if (accuracy >= .80) {
state.rank = 'B';
} else if (accuracy >= .70) {
state.rank = 'C';
} else if (accuracy >= .60) {
state.rank = 'D';
} else {
state.rank = 'F';
}
2018-10-13 09:42:11 -07:00
},
wallhitstart: function (state) {
takeDamage(state);
2018-07-18 20:48:45 +02:00
}
},
2018-07-21 10:21:30 +02:00
/**
* Post-process the state after each action.
*/
2018-09-20 18:05:32 -07:00
computeState: (state) => {
state.isPlaying =
2018-10-15 16:44:38 -07:00
!state.menuActive && !state.isPaused && !state.isVictory && !state.isGameOver &&
!state.challenge.isLoading && !state.isSongLoading;
2018-10-15 14:54:04 -07:00
2018-10-15 16:44:38 -07:00
const anyMenuOpen = state.menuActive || state.isPaused || state.isVictory || state.isGameOver;
2018-10-15 14:54:04 -07:00
state.leftRaycasterActive = anyMenuOpen && state.activeHand === 'left' && state.inVR;
state.rightRaycasterActive = anyMenuOpen && state.activeHand === 'right' && state.inVR;
// Song is decoding if it is loading, but not fetching.
if (state.isSongLoading) {
state.loadingText = state.isSongFetching ? 'Downloading song...' : 'Processing song...';
} else {
state.loadingText = '';
}
state.multiplierText = `${state.score.multiplier}x`;
2018-09-20 18:05:32 -07:00
}
2018-07-18 20:48:45 +02:00
});
2018-07-20 15:42:47 +02:00
2018-07-21 11:55:44 +02:00
function computeSearchPagination (state) {
let numPages = Math.ceil(state.search.results.length / SEARCH_PER_PAGE);
state.search.hasPrev = state.search.page > 0;
state.search.hasNext = state.search.page < numPages - 1;
state.search.songNameTexts = '';
state.search.songSubNameTexts = '';
2018-07-21 11:55:44 +02:00
state.searchResultsPage.length = 0;
state.searchResultsPage.__dirty = true;
for (let i = state.search.page * SEARCH_PER_PAGE;
2018-07-21 11:55:44 +02:00
i < state.search.page * SEARCH_PER_PAGE + SEARCH_PER_PAGE; i++) {
if (!state.search.results[i]) { break; }
state.searchResultsPage.push(state.search.results[i]);
state.search.songNameTexts +=
truncate(state.search.results[i].songName, SONG_NAME_TRUNCATE).toUpperCase() + '\n';
state.search.songSubNameTexts +=
truncate(state.search.results[i].songSubName, SONG_SUB_NAME_TRUNCATE) + '\n';
2018-07-21 11:55:44 +02:00
}
for (let i = 0; i < state.searchResultsPage.length; i++) {
state.searchResultsPage[i].index = i;
}
computeMenuSelectedChallengeIndex(state);
2018-07-21 11:55:44 +02:00
}
function truncate (str, length) {
if (!str) { return ''; }
if (str.length >= length) {
return str.substring(0, length - 3) + '...';
}
return str;
}
2018-07-21 12:02:37 +02:00
const DIFFICULTIES = ['Easy', 'Normal', 'Hard', 'Expert', 'ExpertPlus'];
function difficultyComparator (a, b) {
const aIndex = DIFFICULTIES.indexOf(a);
const bIndex = DIFFICULTIES.indexOf(b);
if (aIndex < bIndex) { return -1; }
if (aIndex > bIndex) { return 1; }
return 0;
}
function takeDamage (state) {
2018-10-15 14:39:36 -07:00
if (AFRAME.utils.getUrlParameter('godmode')) { return; }
2018-10-13 09:41:37 -07:00
if (!state.isPlaying) { return; }
state.damage++;
state.score.combo = 0;
2018-10-15 17:41:32 -07:00
state.score.multiplier = 1;
checkGameOver(state);
}
function checkGameOver (state) {
if (state.damage >= DAMAGE_MAX) {
state.damage = 0;
state.isGameOver = true;
}
}
function resetScore (state) {
state.damage = 0;
2018-10-16 02:57:07 -07:00
state.score.beatsHit = 0;
state.score.beatsMissed = 0;
state.score.combo = 0;
2018-10-16 02:57:07 -07:00
state.score.maxCombo = 0;
state.score.score = 0;
state.score.multiplier = 1;
}
function computeMenuSelectedChallengeIndex (state) {
state.menuSelectedChallenge.index = -1;
for (let i = 0; i < state.searchResultsPage.length; i++) {
if (state.searchResultsPage[i].id === state.menuSelectedChallenge.id) {
state.menuSelectedChallenge.index = i;
break;
}
}
}