Open Game in new Window
From f9f7d1600a1ee3146e9019c4ba50a2781f67946c Mon Sep 17 00:00:00 2001
From: wltan <53135010+wltan@users.noreply.github.com>
Date: Fri, 10 Apr 2020 16:13:47 +0800
Subject: [PATCH 18/39] Read input JSON file and set override
---
src/components/game-dev/JsonUpload.tsx | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/components/game-dev/JsonUpload.tsx b/src/components/game-dev/JsonUpload.tsx
index 49e66a8239..3f013d0c86 100644
--- a/src/components/game-dev/JsonUpload.tsx
+++ b/src/components/game-dev/JsonUpload.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { overrideStudentData } from '../academy/game/backend/game-state.js';
class JsonUpload extends React.Component {
private static onFormSubmit(e: { preventDefault: () => void; }){
@@ -7,9 +8,7 @@ class JsonUpload extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
- this.state ={
- file:null
- };
+ overrideStudentData(undefined);
JsonUpload.onFormSubmit = JsonUpload.onFormSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
@@ -17,14 +16,17 @@ class JsonUpload extends React.Component {
public render() {
return (
);
}
private onChange(e: { target: { files: any; }; }) {
- this.setState({file:e.target.files[0]});
+ const reader = new FileReader();
+ reader.onload = (event: Event) => {
+ overrideStudentData(JSON.parse(""+reader.result));
+ };
+ reader.readAsText(e.target.files[0]);
}
}
From e53e689f057899f6a3cfe56938c35adafe1cf015 Mon Sep 17 00:00:00 2001
From: wltan <53135010+wltan@users.noreply.github.com>
Date: Fri, 10 Apr 2020 17:25:19 +0800
Subject: [PATCH 19/39] Enable the JSON file to simulate customized mission
pointer and current date as well
---
src/components/academy/game/backend/game-state.js | 14 +++++++++-----
src/components/game-dev/JsonUpload.tsx | 6 +++---
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/src/components/academy/game/backend/game-state.js b/src/components/academy/game/backend/game-state.js
index 7bf82039be..4150c33df2 100644
--- a/src/components/academy/game/backend/game-state.js
+++ b/src/components/academy/game/backend/game-state.js
@@ -28,11 +28,15 @@ let studentDataOverride = undefined,
missionPointerOverride = undefined,
currentDateOverride = undefined;
// override student game data
-export function overrideStudentData(data) { studentDataOverride = data; }
-// override student's current mission
-export function overrideMissionPointer(data) { missionPointerOverride = data; }
-// override current date (to determine active missions)
-export function overrideCurrentDate(data) { currentDateOverride = data; }
+export function overrideGameState(data) {
+ if (data) {
+ studentDataOverride = data;
+ missionPointerOverride = data.missionPointer;
+ currentDateOverride = data.currentDate;
+ } else {
+ studentDataOverride = missionPointerOverride = currentDateOverride = undefined;
+ }
+}
export function getStudentData() {
// formerly create-initializer/loadFromServer
diff --git a/src/components/game-dev/JsonUpload.tsx b/src/components/game-dev/JsonUpload.tsx
index 3f013d0c86..7bd27b1c6a 100644
--- a/src/components/game-dev/JsonUpload.tsx
+++ b/src/components/game-dev/JsonUpload.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { overrideStudentData } from '../academy/game/backend/game-state.js';
+import { overrideGameState } from '../academy/game/backend/game-state.js';
class JsonUpload extends React.Component {
private static onFormSubmit(e: { preventDefault: () => void; }){
@@ -8,7 +8,7 @@ class JsonUpload extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
- overrideStudentData(undefined);
+ overrideGameState(undefined);
JsonUpload.onFormSubmit = JsonUpload.onFormSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
@@ -24,7 +24,7 @@ class JsonUpload extends React.Component {
private onChange(e: { target: { files: any; }; }) {
const reader = new FileReader();
reader.onload = (event: Event) => {
- overrideStudentData(JSON.parse(""+reader.result));
+ overrideGameState(JSON.parse(""+reader.result));
};
reader.readAsText(e.target.files[0]);
}
From 586999c4396c48d3e4c1054414296e943856a4bf Mon Sep 17 00:00:00 2001
From: travisryte
Date: Thu, 16 Apr 2020 04:09:59 +0800
Subject: [PATCH 20/39] Fixed bug where tests on PixiJS will fail due to canvas
error
Tried https://github.com/pixijs/pixi.js/issues/4769 after encountering TypeError: Cannot set property 'fillStyle' of null
It solved the problem with the game-dev component.
---
package-lock.json | 34 ++++++++++++++++++++++++++++++++++
package.json | 1 +
src/setupTests.ts | 2 +-
3 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/package-lock.json b/package-lock.json
index 103c0c176a..893ac6e23b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4766,6 +4766,12 @@
"integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
"dev": true
},
+ "cssfontparser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz",
+ "integrity": "sha1-9AIvyPlwDGgCnVQghK+69CWj8+M=",
+ "dev": true
+ },
"cssnano": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz",
@@ -9504,6 +9510,7 @@
"resolved": "https://registry.npmjs.org/jest/-/jest-22.4.2.tgz",
"integrity": "sha512-wD7dXWtfaQAgbNVsjFqzmuhg6nzwGsTRVea3FpSJ7GURhG+J536fw4mdoLB01DgiEozDDeF1ZMR/UlUszTsCrg==",
"dev": true,
+ "setupFiles": ["jest-canvas-mock"],
"requires": {
"import-local": "^1.0.0",
"jest-cli": "^22.4.2"
@@ -9774,6 +9781,16 @@
}
}
},
+ "jest-canvas-mock": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/jest-canvas-mock/-/jest-canvas-mock-2.2.0.tgz",
+ "integrity": "sha512-DcJdchb7eWFZkt6pvyceWWnu3lsp5QWbUeXiKgEMhwB3sMm5qHM1GQhDajvJgBeiYpgKcojbzZ53d/nz6tXvJw==",
+ "dev": true,
+ "requires": {
+ "cssfontparser": "^1.2.1",
+ "parse-color": "^1.0.0"
+ }
+ },
"jest-changed-files": {
"version": "22.4.3",
"resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-22.4.3.tgz",
@@ -15923,6 +15940,23 @@
"pbkdf2": "^3.0.3"
}
},
+ "parse-color": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz",
+ "integrity": "sha1-e3SLlag/A/FqlPU15S1/PZRlhhk=",
+ "dev": true,
+ "requires": {
+ "color-convert": "~0.5.0"
+ },
+ "dependencies": {
+ "color-convert": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
+ "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=",
+ "dev": true
+ }
+ }
+ },
"parse-github-url": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz",
diff --git a/package.json b/package.json
index 453837bbed..cd067ec87e 100644
--- a/package.json
+++ b/package.json
@@ -121,6 +121,7 @@
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.14.0",
"husky": "^1.3.1",
+ "jest-canvas-mock": "^2.2.0",
"local-cors-proxy": "^1.0.2",
"prettier": "^1.18.2",
"react-scripts-ts": "^2.16.0",
diff --git a/src/setupTests.ts b/src/setupTests.ts
index b8acf7f719..2b4b22ba41 100644
--- a/src/setupTests.ts
+++ b/src/setupTests.ts
@@ -1,4 +1,4 @@
import { configure } from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
-
+import 'jest-canvas-mock';
configure({ adapter: new Adapter() });
From fd88f3f729041bdf1fd39987382bf0d175b337a0 Mon Sep 17 00:00:00 2001
From: travisryte
Date: Thu, 16 Apr 2020 04:24:03 +0800
Subject: [PATCH 21/39] Fully moved completed-quests and collectibles away from
localStorage.
Saving and loading of quest and collectibles will come through the user data or from overriding data.
---
.../academy/game/backend/game-state.js | 77 +++++++++++--------
.../game/object-manager/object-manager.js | 5 +-
.../game/quest-manager/quest-manager.js | 3 +-
3 files changed, 52 insertions(+), 33 deletions(-)
diff --git a/src/components/academy/game/backend/game-state.js b/src/components/academy/game/backend/game-state.js
index 4150c33df2..50e8540dee 100644
--- a/src/components/academy/game/backend/game-state.js
+++ b/src/components/academy/game/backend/game-state.js
@@ -1,72 +1,88 @@
import { storyXMLPathTest, storyXMLPathLive } from '../constants/constants'
import { isStudent } from './user';
+var SaveManager = require('../save-manager/save-manager.js');
+
/**
* Handles data regarding the game state.
* - The student's list of completed quests and collectibles
* - The student's current story mission
* - The global list of missions that are open
+ * - The action to save user state to server.
*/
let fetched = false;
-let studentMissionPointer = undefined,
- studentData = undefined;
-export function fetchGameData(userStory, callback) {
+let studentData = undefined,
+ handleSaveData = undefined,
+ studentStory = undefined;
+
+export function fetchGameData(userStory, gameState, callback) {
// fetch only needs to be called once; if there are additional calls somehow then ignore them
if(fetched) {
callback();
return;
}
fetched = true;
- studentMissionPointer = userStory.story;
- // not implemented yet
- studentData = undefined; // userStory.data;
+ studentStory = userStory.story;
+ studentData = gameState;
fetchGlobalMissionPointer(callback);
}
// overrides
let studentDataOverride = undefined,
- missionPointerOverride = undefined,
- currentDateOverride = undefined;
+ currentDateOverride = undefined,
+ studentStoryOverride = undefined;
+
// override student game data
export function overrideGameState(data) {
if (data) {
- studentDataOverride = data;
- missionPointerOverride = data.missionPointer;
+ studentDataOverride = data.gameState;
+ studentStoryOverride = data.story;
currentDateOverride = data.currentDate;
} else {
- studentDataOverride = missionPointerOverride = currentDateOverride = undefined;
+ studentStoryOverride = studentDataOverride = missionPointerOverride = currentDateOverride = undefined;
}
}
+export function setSaveHandler(saveData) {
+ handleSaveData = saveData;
+}
+
export function getStudentData() {
// formerly create-initializer/loadFromServer
if(studentDataOverride) return studentDataOverride;
return studentData;
}
-export function saveStudentData(json) {
- // formerly create-initializer/saveToServer
- return json;
+export function saveStudentData(data) {
+ console.log('saving student data');
+ if (handleSaveData !== undefined) {
+ handleSaveData(data)
+ }
}
export function saveCollectible(collectible) {
- // currently local but we should eventually migrate to backend
- if (typeof Storage !== 'undefined') {
- localStorage.setItem(collectible, 'collected');
- }
+ studentData.collectibles[collectible] = 'completed';
+ saveStudentData(studentData);
+}
+
+export function hasCollectible(collectible) {
+ return studentData &&
+ studentData.collectibles[collectible] &&
+ studentData.collectibles[collectible] === 'completed';
}
export function saveQuest(questId) {
- // currently local but we should eventually migrate to backend
- if (typeof Storage !== 'undefined') {
- localStorage.setItem(questId, 'completed');
- }
+ studentData.completed_quests.push(questId);
+ saveStudentData(studentData);
+}
+
+export function hasCompletedQuest(questId) {
+ return studentData && studentData.completed_quests.includes(questId);
}
-function getStudentMissionPointer() {
- // placeholder
- if(missionPointerOverride) return missionPointerOverride;
- return studentMissionPointer;
+function getStudentStory() {
+ if(studentStoryOverride) return studentStoryOverride;
+ return studentStory;
}
let stories = [];
@@ -103,12 +119,13 @@ function fetchGlobalMissionPointer(callback) {
* global list of open missions, then the corresponding upper (or lower) bound will be used.
*/
export function getMissionPointer() {
- let student = getStudentMissionPointer();
+ //finds the mission id's mission pointer
+ let missionPointer = stories.find(story => story.getAttribute("id") === getStudentStory());
const newest = parseInt(stories[stories.length-1].getAttribute("key")); // the newest mission to open
const oldest = parseInt(stories[0].getAttribute("key")); // the oldest mission to open
- student = Math.min(student, newest);
- student = Math.max(student, oldest);
- const storyToLoad = stories.filter(story => story.getAttribute("key") == student)[0];
+ missionPointer = Math.min(missionPointer, newest);
+ missionPointer = Math.max(missionPointer, oldest);
+ const storyToLoad = stories.filter(story => story.getAttribute("key") == missionPointer)[0];
console.log("Now loading story " + storyToLoad.getAttribute("id")); // debug statement
return storyToLoad.getAttribute("id");
}
\ No newline at end of file
diff --git a/src/components/academy/game/object-manager/object-manager.js b/src/components/academy/game/object-manager/object-manager.js
index f464ad7a49..6fdcf6f580 100644
--- a/src/components/academy/game/object-manager/object-manager.js
+++ b/src/components/academy/game/object-manager/object-manager.js
@@ -10,6 +10,7 @@ var ExternalManager = require('../external-manager/external-manager.js');
var MapOverlay = require('../map-overlay/map-overlay.js');
var Utils = require('../utils/utils.js');
var FilterEffects = require('../filter-effects/filter-effects.js');
+var GameState = require('../backend/game-state')
var mapObjects;
var sequenceObjects;
@@ -164,8 +165,8 @@ export function processTempObject(gameLocation, node) {
}
var collectible = node.getAttribute('name');
var isInDorm = gameLocation.name == 'yourRoom';
- if ((isInDorm && !localStorage.hasOwnProperty(collectible))||
- (!isInDorm && localStorage.hasOwnProperty(collectible))) {
+ if ((isInDorm && !GameState.hasColletible(collectible))||
+ (!isInDorm && GameState.hasColletible(collectible))) {
return; //don't load the collectible in dorm if it's not collected || don't load if in hidden location and collected
}
if (isInDorm) {
diff --git a/src/components/academy/game/quest-manager/quest-manager.js b/src/components/academy/game/quest-manager/quest-manager.js
index b2e8c4d301..00ad1b56af 100644
--- a/src/components/academy/game/quest-manager/quest-manager.js
+++ b/src/components/academy/game/quest-manager/quest-manager.js
@@ -6,6 +6,7 @@ var StoryManager = require('../story-manager/story-manager.js');
var SaveManager = require('../save-manager/save-manager.js');
var Utils = require('../utils/utils.js');
var ExternalManager = require('../external-manager/external-manager.js');
+var GameState = require('../backend/game-state');
var loadedQuests = {};
var activeQuests = {};
@@ -65,7 +66,7 @@ export function unlockQuest(storyId, questId, callback) {
if (!activeQuests[storyId]) {
activeQuests[storyId] = {};
}
- if (typeof Storage !== 'undefined' && localStorage.hasOwnProperty(questId)) {
+ if (typeof Storage !== 'undefined' && GameState.hasCompletedQuest(questId)) {
// skip sequence
skipEffects(quest.children[0]);
SaveManager.saveUnlockQuest(storyId, questId);
From 9aa15e031af9c4d1f4a92d190759361cf24e7753 Mon Sep 17 00:00:00 2001
From: travisryte
Date: Thu, 16 Apr 2020 04:26:51 +0800
Subject: [PATCH 22/39] Save Manager now saves story action sequence and start
location to localStorage
---
src/components/academy/game/constants/constants.js | 2 ++
.../academy/game/save-manager/save-manager.js | 11 ++++++-----
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/components/academy/game/constants/constants.js b/src/components/academy/game/constants/constants.js
index db1135a92b..d4c3b09bf4 100644
--- a/src/components/academy/game/constants/constants.js
+++ b/src/components/academy/game/constants/constants.js
@@ -24,5 +24,7 @@ export const
avatarPath = ASSETS_HOST + 'avatars/',
uiPath = ASSETS_HOST + 'UI/',
soundPath = ASSETS_HOST + 'sounds/',
+ saveDataKey = "source_academy_save_data",
+ locationKey = "source_academy_location",
fadeTime = 0.3,
nullFunction = function() {};
\ No newline at end of file
diff --git a/src/components/academy/game/save-manager/save-manager.js b/src/components/academy/game/save-manager/save-manager.js
index d67813cdb0..d741c60dc5 100644
--- a/src/components/academy/game/save-manager/save-manager.js
+++ b/src/components/academy/game/save-manager/save-manager.js
@@ -1,4 +1,5 @@
import {saveStudentData} from '../backend/game-state';
+import {saveDataKey} from "../constants/constants";
var LocationManager = require('../location-manager/location-manager.js');
var QuestManager = require('../quest-manager/quest-manager.js');
@@ -10,14 +11,13 @@ var Utils = require('../utils/utils.js');
var actionSequence = [];
-export function init(saveData, callback) {
+// finds existing save data, which consists of action sequence and starting location
+export function init() {
+ let saveData = localStorage.getItem(saveDataKey);
if (saveData) {
- alert(saveData);
saveData = JSON.parse(saveData);
actionSequence = saveData.actionSequence;
var storyXMLs = [];
- // TODO: this is assuming that all 'loadStory' appear at the start
- // This may not be the case. Need to improve
for (var i = 0; i < actionSequence.length; i++) {
if (actionSequence[i].type == 'loadStory') {
storyXMLs.push(actionSequence[i].storyId);
@@ -110,8 +110,9 @@ export function saveLoadStories(stories) {
saveGame();
}
+// saves actionsequence and start location into local storage
function saveGame() {
- saveStudentData(
+ localStorage.setItem(saveDataKey,
JSON.stringify({
actionSequence: actionSequence,
startLocation: LocationManager.getStartLocation()
From 6a3e61d5384732670cd0433cd7cab9b844da43db Mon Sep 17 00:00:00 2001
From: travisryte
Date: Thu, 16 Apr 2020 04:31:23 +0800
Subject: [PATCH 23/39] Added new actions and changed state for the addition of
game state.
- Made new actiontypes to update game state in the backend and in session.
- Updated tests to accomodate for game state.
---
src/actions/__tests__/session.ts | 5 ++--
src/actions/actionTypes.ts | 2 ++
src/actions/game.ts | 5 +++-
src/actions/material.ts | 2 --
src/actions/session.ts | 13 +++++++---
src/mocks/backend.ts | 5 ++--
src/reducers/__tests__/session.ts | 9 +++++--
src/reducers/session.ts | 6 +++++
src/reducers/states.ts | 16 +++++++++++-
src/sagas/__tests__/backend.ts | 24 ++++++++++++++----
src/sagas/backend.ts | 42 +++++++++++++------------------
src/sagas/requests.ts | 19 ++++++++++++++
12 files changed, 105 insertions(+), 43 deletions(-)
diff --git a/src/actions/__tests__/session.ts b/src/actions/__tests__/session.ts
index 47e0c12e0f..eda44a8db6 100644
--- a/src/actions/__tests__/session.ts
+++ b/src/actions/__tests__/session.ts
@@ -1,7 +1,7 @@
import { Grading, GradingOverview } from '../../components/academy/grading/gradingShape';
import { IAssessment, IAssessmentOverview } from '../../components/assessment/assessmentShape';
import { Notification } from '../../components/notification/notificationShape';
-import { Role, Story } from '../../reducers/states';
+import { GameState, Role, Story } from '../../reducers/states';
import * as actionTypes from '../actionTypes';
import {
acknowledgeNotifications,
@@ -155,7 +155,8 @@ test('setUser generates correct action object', () => {
name: 'test student',
role: 'student' as Role,
grade: 150,
- story: {} as Story
+ story: {} as Story,
+ gameState: {} as GameState
};
const action = setUser(user);
expect(action).toEqual({
diff --git a/src/actions/actionTypes.ts b/src/actions/actionTypes.ts
index 2ef04053b8..ad4e704f7e 100755
--- a/src/actions/actionTypes.ts
+++ b/src/actions/actionTypes.ts
@@ -124,3 +124,5 @@ export const NOTIFY_CHATKIT_USERS = 'NOTIFY_CHATKIT_USERS';
/** GAMEDEV */
export const FETCH_TEST_STORIES = 'FETCH_TEST_STORIES';
+export const SAVE_USER_STATE = 'SAVE_USER_STATE';
+export const SET_GAME_STATE = 'SET_GAME_STATE';
diff --git a/src/actions/game.ts b/src/actions/game.ts
index c552f98a12..22fce7c6bc 100644
--- a/src/actions/game.ts
+++ b/src/actions/game.ts
@@ -1,5 +1,8 @@
+import { GameState } from 'src/reducers/states';
import { action } from 'typesafe-actions';
-
import * as actionTypes from './actionTypes';
+export const fetchTestStories = () => action(actionTypes.FETCH_TEST_STORIES);
export const saveCanvas = (canvas: HTMLCanvasElement) => action(actionTypes.SAVE_CANVAS, canvas);
+export const saveUserData = (gameState: GameState) =>
+ action(actionTypes.SAVE_USER_STATE, gameState);
diff --git a/src/actions/material.ts b/src/actions/material.ts
index 487b029e72..c766bf9585 100644
--- a/src/actions/material.ts
+++ b/src/actions/material.ts
@@ -21,5 +21,3 @@ export const updateMaterialIndex = (index: MaterialData[]) =>
export const uploadMaterial = (file: File, title: string, description: string) =>
action(actionTypes.UPLOAD_MATERIAL, { file, title, description });
-
-export const fetchTestStories = () => action(actionTypes.FETCH_TEST_STORIES);
diff --git a/src/actions/session.ts b/src/actions/session.ts
index 73307c76e1..aa5161a701 100755
--- a/src/actions/session.ts
+++ b/src/actions/session.ts
@@ -6,7 +6,7 @@ import {
Notification,
NotificationFilterFunction
} from '../components/notification/notificationShape';
-import { Story } from '../reducers/states';
+import { GameState, Story } from '../reducers/states';
import * as actionTypes from './actionTypes';
import { Role } from '../reducers/states';
@@ -31,6 +31,8 @@ export const fetchGradingOverviews = (filterToGroup = true) =>
export const login = () => action(actionTypes.LOGIN);
+export const setGameState = (gameState: GameState) => action(actionTypes.SET_GAME_STATE, gameState);
+
export const setTokens = ({
accessToken,
refreshToken
@@ -43,8 +45,13 @@ export const setTokens = ({
refreshToken
});
-export const setUser = (user: { name: string; role: Role; grade: number; story: Story }) =>
- action(actionTypes.SET_USER, user);
+export const setUser = (user: {
+ name: string;
+ role: Role;
+ grade: number;
+ story?: Story;
+ gameState?: GameState;
+}) => action(actionTypes.SET_USER, user);
export const submitAnswer = (id: number, answer: string | number) =>
action(actionTypes.SUBMIT_ANSWER, {
diff --git a/src/mocks/backend.ts b/src/mocks/backend.ts
index a94ee8a80d..3750557e97 100644
--- a/src/mocks/backend.ts
+++ b/src/mocks/backend.ts
@@ -15,7 +15,7 @@ import {
NotificationFilterFunction
} from '../components/notification/notificationShape';
import { store } from '../createStore';
-import { IState, Role } from '../reducers/states';
+import { GameState, IState, Role } from '../reducers/states';
import { history } from '../utils/history';
import { showSuccessMessage, showWarningMessage } from '../utils/notification';
import { mockAssessmentOverviews, mockAssessments } from './assessmentAPI';
@@ -35,7 +35,8 @@ export function* mockBackendSaga(): SagaIterator {
story: 'mission-1',
playStory: true
},
- grade: 0
+ grade: 0,
+ gameState: {} as GameState
};
store.dispatch(actions.setTokens(tokens));
store.dispatch(actions.setUser(user));
diff --git a/src/reducers/__tests__/session.ts b/src/reducers/__tests__/session.ts
index 6003431ebe..615bdf1056 100644
--- a/src/reducers/__tests__/session.ts
+++ b/src/reducers/__tests__/session.ts
@@ -20,7 +20,7 @@ import {
import { Notification } from '../../components/notification/notificationShape';
import { HistoryHelper } from '../../utils/history';
import { reducer } from '../session';
-import { defaultSession, ISessionState, Role, Story } from '../states';
+import { defaultSession, GameState, ISessionState, Role, Story } from '../states';
test('LOG_OUT works correctly on default session', () => {
const action = {
@@ -56,11 +56,16 @@ test('SET_USER works correctly', () => {
story: 'test story',
playStory: true
};
+ const gameState: GameState = {
+ collectibles: {},
+ completed_quests: []
+ };
const payload = {
name: 'test student',
role: Role.Student,
grade: 150,
- story
+ story,
+ gameState
};
const action = {
diff --git a/src/reducers/session.ts b/src/reducers/session.ts
index eb8f510355..2a4c72abfe 100644
--- a/src/reducers/session.ts
+++ b/src/reducers/session.ts
@@ -4,6 +4,7 @@ import { ActionType } from 'typesafe-actions';
import * as actions from '../actions';
import {
LOG_OUT,
+ SET_GAME_STATE,
SET_TOKENS,
SET_USER,
UPDATE_ASSESSMENT,
@@ -88,6 +89,11 @@ export const reducer: Reducer = (
...state,
notifications: action.payload
};
+ case SET_GAME_STATE:
+ return {
+ ...state,
+ gameState: action.payload
+ };
default:
return state;
}
diff --git a/src/reducers/states.ts b/src/reducers/states.ts
index 1ed4f99bde..b7816dd588 100755
--- a/src/reducers/states.ts
+++ b/src/reducers/states.ts
@@ -138,7 +138,8 @@ export interface ISessionState {
readonly maxXp: number;
readonly refreshToken?: string;
readonly role?: Role;
- readonly story?: Story;
+ readonly story: Story;
+ readonly gameState: GameState;
readonly name?: string;
readonly xp: number;
readonly notifications: Notification[];
@@ -157,6 +158,11 @@ export type Story = {
playStory: boolean;
};
+export type GameState = {
+ collectibles: { [id: string]: string };
+ completed_quests: string[];
+};
+
/**
* An output while the program is still being run in the interpreter. As a
* result, there are no return values or SourceErrors yet. However, there could
@@ -423,6 +429,14 @@ export const defaultSession: ISessionState = {
refreshToken: undefined,
role: undefined,
name: undefined,
+ story: {
+ story: '',
+ playStory: false
+ },
+ gameState: {
+ completed_quests: [],
+ collectibles: {}
+ },
xp: 0,
notifications: []
};
diff --git a/src/sagas/__tests__/backend.ts b/src/sagas/__tests__/backend.ts
index 3fd42c1d30..0a81fc7309 100644
--- a/src/sagas/__tests__/backend.ts
+++ b/src/sagas/__tests__/backend.ts
@@ -16,7 +16,7 @@ import {
mockAssessments
} from '../../mocks/assessmentAPI';
import { mockNotifications } from '../../mocks/userAPI';
-import { Role, Story } from '../../reducers/states';
+import { GameState, Role, Story } from '../../reducers/states';
import { showSuccessMessage, showWarningMessage } from '../../utils/notification';
import backendSaga from '../backend';
import {
@@ -67,8 +67,15 @@ describe('Test FETCH_AUTH Action', () => {
const user = {
name: 'user',
role: 'student' as Role,
- story: {} as Story,
- grade: 1
+ story: {
+ story: '',
+ playStory: false
+ } as Story,
+ grade: 1,
+ gameState: {
+ collectibles: {},
+ completed_quests: []
+ } as GameState
};
return expectSaga(backendSaga)
.call(postAuth, luminusCode)
@@ -85,8 +92,15 @@ describe('Test FETCH_AUTH Action', () => {
const user = {
name: 'user',
role: 'student' as Role,
- story: {} as Story,
- grade: 1
+ story: {
+ story: '',
+ playStory: false
+ } as Story,
+ grade: 1,
+ gameState: {
+ collectibles: {},
+ completed_quests: []
+ } as GameState
};
return expectSaga(backendSaga)
.provide([[call(postAuth, luminusCode), null], [call(getUser, mockTokens), user]])
diff --git a/src/sagas/backend.ts b/src/sagas/backend.ts
index 43f4447981..5240c8d4cd 100644
--- a/src/sagas/backend.ts
+++ b/src/sagas/backend.ts
@@ -22,7 +22,7 @@ import {
Notification,
NotificationFilterFunction
} from '../components/notification/notificationShape';
-import { IState, Role } from '../reducers/states';
+import { GameState, IState, Role } from '../reducers/states';
import { history } from '../utils/history';
import { showSuccessMessage, showWarningMessage } from '../utils/notification';
import * as request from './requests';
@@ -561,30 +561,6 @@ function* backendSaga(): SagaIterator {
action: ReturnType
) {
const fileName: string = 'Test Stories';
- /*
- yield put(actions.fetchMaterialIndex());
- let materialIndex = null;
- while (materialIndex === null) {
- materialIndex = yield select(
- (state: IState) => state.session.materialIndex!
- );
- }
- let storyFolder = yield materialIndex.find((x :MaterialData) => x.title === fileName);
-
- if (storyFolder === undefined) {
- yield put(actions.createMaterialFolder(fileName));
- while (yield materialIndex.find((x :MaterialData) => x.title === fileName) === undefined) {
- materialIndex = yield select(
- (state: IState) => state.session.materialIndex!
- );
- }
- storyFolder = yield materialIndex.find((x :MaterialData) => x.title === fileName);
- }
- // tslint:disable-next-line:no-console
- console.log(storyFolder.id == null ? 1 : 0);
- yield put(actions.fetchMaterialIndex(storyFolder.id));
- */
-
const tokens = yield select((state: IState) => ({
accessToken: state.session.accessToken,
refreshToken: state.session.refreshToken
@@ -619,6 +595,22 @@ function* backendSaga(): SagaIterator {
}
}
});
+
+ yield takeEvery(actionTypes.SAVE_USER_STATE, function*(
+ action: ReturnType
+ ) {
+ const tokens = yield select((state: IState) => ({
+ accessToken: state.session.accessToken,
+ refreshToken: state.session.refreshToken
+ }));
+ const gameState: GameState = action.payload;
+ const resp = yield request.putUserGameState(gameState, tokens);
+ if (!resp || !resp.ok) {
+ yield request.handleResponseError(resp);
+ return;
+ }
+ yield put(actions.setGameState(gameState));
+ });
}
export default backendSaga;
diff --git a/src/sagas/requests.ts b/src/sagas/requests.ts
index 9899982ab6..4c4c1e89f4 100644
--- a/src/sagas/requests.ts
+++ b/src/sagas/requests.ts
@@ -2,6 +2,7 @@
/*eslint-env browser*/
import { call } from 'redux-saga/effects';
+import { GameState } from 'src/reducers/states';
import * as actions from '../actions';
import {
Grading,
@@ -101,6 +102,24 @@ export async function getUser(tokens: Tokens): Promise
);
}
-
- private async getStoryOpts() {
- const defaultStory = { story: 10, playStory: true };
- const userStory = this.props.story ? this.props.story : store.getState().session.story;
- setUserRole(store.getState().session.role);
- return userStory ? userStory : defaultStory;
- }
}
export default Game;
diff --git a/src/components/academy/game/story-xml-player.js b/src/components/academy/game/story-xml-player.js
index 09a080891a..f02569ce2d 100644
--- a/src/components/academy/game/story-xml-player.js
+++ b/src/components/academy/game/story-xml-player.js
@@ -20,9 +20,10 @@ var stage;
// options contains the following properties:
// saveData, hookHandlers, wristDeviceFunc
// changeLocationHook, playerImageCanvas, playerName
-export function init(div, canvas, options, callback) {
+export function init(div, canvas, options) {
renderer = PIXI.autoDetectRenderer(
Constants.screenWidth,
+
Constants.screenHeight,
{ backgroundColor: 0x000000, view: canvas }
);
@@ -51,8 +52,8 @@ export function init(div, canvas, options, callback) {
}
animate();
- SaveManager.init(options.saveData, callback);
-
+ SaveManager.init();
+
// a pixi.container on top of everything that is exported
stage.addChild(ExternalManager.init(options.hookHandlers));
};
diff --git a/src/components/game-dev/JsonUpload.tsx b/src/components/game-dev/JsonUpload.tsx
index 7bd27b1c6a..7067880ed8 100644
--- a/src/components/game-dev/JsonUpload.tsx
+++ b/src/components/game-dev/JsonUpload.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { overrideGameState } from '../academy/game/backend/game-state.js';
class JsonUpload extends React.Component {
- private static onFormSubmit(e: { preventDefault: () => void; }){
+ private static onFormSubmit(e: { preventDefault: () => void }) {
e.preventDefault(); // Stop form submit
}
@@ -17,18 +17,17 @@ class JsonUpload extends React.Component {
return (
);
}
- private onChange(e: { target: { files: any; }; }) {
+ private onChange(e: { target: { files: any } }) {
const reader = new FileReader();
reader.onload = (event: Event) => {
- overrideGameState(JSON.parse(""+reader.result));
+ overrideGameState(JSON.parse('' + reader.result));
};
reader.readAsText(e.target.files[0]);
}
}
-
export default JsonUpload;
diff --git a/src/components/game-dev/StoryTable.tsx b/src/components/game-dev/StoryTable.tsx
index 8c8c6aa546..990844b780 100644
--- a/src/components/game-dev/StoryTable.tsx
+++ b/src/components/game-dev/StoryTable.tsx
@@ -23,7 +23,7 @@ import { getStandardDateTime } from '../../utils/dateHelpers';
import { controlButton } from '../commons';
import DeleteCell from './DeleteCell';
import DownloadCell from './DownloadCell';
-import JsonUpload from "./JsonUpload";
+import JsonUpload from './JsonUpload';
import { DirectoryData, MaterialData } from './storyShape';
/**
@@ -174,7 +174,7 @@ class StoryTable extends React.Component {
-
+
diff --git a/src/containers/GameContainer.ts b/src/containers/GameContainer.ts
index 53388adb31..940dcdf95f 100644
--- a/src/containers/GameContainer.ts
+++ b/src/containers/GameContainer.ts
@@ -1,14 +1,15 @@
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
-import { saveCanvas } from '../actions/game';
+import { saveCanvas, saveUserData } from '../actions/game';
import Game, { DispatchProps, StateProps } from '../components/academy/game';
import { IState } from '../reducers/states';
const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators(
{
- handleSaveCanvas: saveCanvas
+ handleSaveCanvas: saveCanvas,
+ handleSaveData: saveUserData
},
dispatch
);
@@ -16,7 +17,9 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis
const mapStateToProps: MapStateToProps = state => ({
canvas: state.academy.gameCanvas,
name: state.session.name!,
- story: state.session.story
+ story: state.session.story,
+ gameState: state.session.gameState,
+ role: state.session.role
});
export default connect(
diff --git a/src/sagas/requests.ts b/src/sagas/requests.ts
index 4c4c1e89f4..cf047d9e5f 100644
--- a/src/sagas/requests.ts
+++ b/src/sagas/requests.ts
@@ -106,7 +106,6 @@ export async function getUser(tokens: Tokens): Promise