diff --git a/public/assets/mockChapter0.txt b/public/assets/mockChapter0.txt index 0c5f33437a..7a985d9fb0 100644 --- a/public/assets/mockChapter0.txt +++ b/public/assets/mockChapter0.txt @@ -4,8 +4,9 @@ objectives finish gameStartActions - show_dialogue(welcome) if userstate.assessments.301 - show_dialogue(unwelcome) if !userstate.assessments.301 + // replace 173 with assessment id + show_dialogue(welcome) if userstate.assessments.173 + show_dialogue(unwelcome) if !userstate.assessments.173 checkpointCompleteActions show_dialogue(done) @@ -92,16 +93,27 @@ dialogues update_character(scottie, happy) unwelcome + 1 @scottie, sad You haven't finished assessment - Finish assessment 301 first and come back? - navigate_to_assessment(405) - Anyways, welcome. - @you - Can I still go to places? - @scottie, happy - Yes, you can remain here. - move_character(scottie, hallway, right) + Finish assessment 173 first and come back? + navigate_to_assessment(173) + goto 2 + 2 + @scottie + Have you finished the assignment? + prompt: Have you finished? + Yes -> goto 4 + I don't want to -> goto 3 + 3 + @scottie + OK + 4 + @scottie + Hmmm + update_assessment_status*() + Let me check + goto 2 if !userstate.assessments.173 else 3 what, What should I do now, Scottie? @you diff --git a/src/features/game/action/GameActionExecuter.ts b/src/features/game/action/GameActionExecuter.ts index 7545569aeb..d660c9c53d 100644 --- a/src/features/game/action/GameActionExecuter.ts +++ b/src/features/game/action/GameActionExecuter.ts @@ -90,6 +90,9 @@ export default class GameActionExecuter { case GameActionType.NavigateToAssessment: await globalAPI.promptNavigateToAssessment(actionParams.assessmentId); return; + case GameActionType.UpdateAssessmentStatus: + await globalAPI.updateAssessmentState(); + return; case GameActionType.Delay: await sleep(actionParams.duration); return; @@ -115,6 +118,7 @@ export default class GameActionExecuter { case GameActionType.UpdateCharacter: return true; case GameActionType.NavigateToAssessment: + case GameActionType.UpdateAssessmentStatus: case GameActionType.PreviewLocation: case GameActionType.ChangeBackground: case GameActionType.StartAnimation: diff --git a/src/features/game/action/GameActionTypes.ts b/src/features/game/action/GameActionTypes.ts index ac7a43cdec..209a92494b 100644 --- a/src/features/game/action/GameActionTypes.ts +++ b/src/features/game/action/GameActionTypes.ts @@ -22,6 +22,7 @@ export enum GameActionType { PlaySFX = 'PlaySFX', ShowObjectLayer = 'ShowObjectLayer', NavigateToAssessment = 'NavigateToAssessment', + UpdateAssessmentStatus = 'UpdateAssessmentStatus', Delay = 'Delay' } diff --git a/src/features/game/dialogue/GameDialogueGenerator.ts b/src/features/game/dialogue/GameDialogueGenerator.ts index fe85f483eb..5faa7cce79 100644 --- a/src/features/game/dialogue/GameDialogueGenerator.ts +++ b/src/features/game/dialogue/GameDialogueGenerator.ts @@ -1,3 +1,4 @@ +import GameActionConditionChecker from '../action/GameActionConditionChecker'; import { DialogueLine, DialogueObject, PartName } from './GameDialogueTypes'; /** @@ -18,18 +19,28 @@ export default class DialogueGenerator { } /** - * @returns {DialogueLine} returns the dialgoueLine that is played next, + * @returns {Promise} returns the dialgoueLine that is played next, * based on what is dictated by the dialogueContent */ - public generateNextLine(): DialogueLine { + public async generateNextLine(): Promise { const dialogueLine = this.dialogueContent.get(this.currPart)![this.currLineNum]; if (!dialogueLine || !dialogueLine.line) { return { line: '' }; } if (dialogueLine.goto) { - if (this.dialogueContent.get(dialogueLine.goto)) { - this.currPart = dialogueLine.goto; + let currPart: string | null = dialogueLine.goto.part; + const conditionCheck = await GameActionConditionChecker.checkAllConditionsSatisfied( + dialogueLine.goto.conditions + ); + if (!conditionCheck) { + currPart = dialogueLine.goto.altPart; + } + + if (!currPart) { + this.currLineNum++; + } else if (this.dialogueContent.get(currPart)) { + this.currPart = currPart; this.currLineNum = 0; } else { return { line: '' }; diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index 6d4218cd67..462808a68b 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -52,7 +52,7 @@ export default class DialogueManager { private async showNextLine(resolve: () => void) { GameGlobalAPI.getInstance().playSound(SoundAssets.dialogueAdvance.key); const { line, speakerDetail, actionIds, prompt } = - this.getDialogueGenerator().generateNextLine(); + await this.getDialogueGenerator().generateNextLine(); const lineWithName = line.replace('{name}', this.getUsername()); this.getDialogueRenderer().changeText(lineWithName); this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); diff --git a/src/features/game/dialogue/GameDialogueTypes.ts b/src/features/game/dialogue/GameDialogueTypes.ts index c715ca7d76..ea5539cd22 100644 --- a/src/features/game/dialogue/GameDialogueTypes.ts +++ b/src/features/game/dialogue/GameDialogueTypes.ts @@ -1,4 +1,4 @@ -import { IGameActionable } from '../action/GameActionTypes'; +import { ActionCondition, IGameActionable } from '../action/GameActionTypes'; import { SpeakerDetail } from '../character/GameCharacterTypes'; /** @@ -24,7 +24,11 @@ export type Choice = string; export type DialogueLine = IGameActionable & { line: string; speakerDetail?: SpeakerDetail | null; - goto?: PartName; + goto?: { + conditions: ActionCondition[]; + part: PartName; + altPart: PartName | null; + }; prompt?: Prompt; }; diff --git a/src/features/game/parser/DialogueParser.ts b/src/features/game/parser/DialogueParser.ts index ff0e192aff..118eacd6c1 100644 --- a/src/features/game/parser/DialogueParser.ts +++ b/src/features/game/parser/DialogueParser.ts @@ -3,6 +3,7 @@ import { GameItemType } from '../location/GameMapTypes'; import { mapValues } from '../utils/GameUtils'; import StringUtils from '../utils/StringUtils'; import ActionParser from './ActionParser'; +import ConditionParser from './ConditionParser'; import Parser from './Parser'; import PromptParser from './PromptParser'; import SpeakerParser from './SpeakerParser'; @@ -92,7 +93,20 @@ export default class DialogueParser { const rawStr = lines[currIndex]; switch (true) { case isGotoLabel(rawStr): - dialogueLines[dialogueLines.length - 1].goto = rawStr.split(' ')[1]; + const [gotoString, postpend] = StringUtils.splitByChar(rawStr, 'if'); + const [conditionalsString, altPart] = postpend + ? StringUtils.splitByChar(postpend, 'else') + : [null, null]; + const conditions = conditionalsString + ? StringUtils.splitByChar(conditionalsString, 'AND').map(condition => + ConditionParser.parse(condition) + ) + : []; + dialogueLines[dialogueLines.length - 1].goto = { + conditions, + part: gotoString.split(' ')[1], + altPart: altPart + }; break; case isPrompt(rawStr): const rawTitle = rawStr; @@ -129,7 +143,7 @@ export default class DialogueParser { } const isInteger = (line: string) => new RegExp(/^[0-9]+$/).test(line); -const isGotoLabel = (line: string) => new RegExp(/^goto [0-9]+$/).test(line); +const isGotoLabel = (line: string) => new RegExp(/^goto [0-9]+.*$/).test(line); const isActionLabel = (line: string) => line && (line[0] === '\t' || line.slice(0, 4) === ' '); const isSpeaker = (line: string) => line && line[0] === '@'; const isPrompt = (line: string) => line.trim().startsWith('prompt:'); diff --git a/src/features/game/parser/ParserConverter.ts b/src/features/game/parser/ParserConverter.ts index ee0bda4a0c..356b0bc3ee 100644 --- a/src/features/game/parser/ParserConverter.ts +++ b/src/features/game/parser/ParserConverter.ts @@ -56,6 +56,7 @@ const stringToActionTypeMap = { preview_location: GameActionType.PreviewLocation, show_object_layer: GameActionType.ShowObjectLayer, navigate_to_assessment: GameActionType.NavigateToAssessment, + update_assessment_status: GameActionType.UpdateAssessmentStatus, delay: GameActionType.Delay }; diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index 2706485be2..c0faeaa9fc 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -413,6 +413,10 @@ class GameGlobalAPI { } } + public async updateAssessmentState() { + await SourceAcademyGame.getInstance().getUserStateManager().loadAssessments(); + } + ///////////////////// // Characters // ///////////////////// diff --git a/src/features/game/scenes/roomPreview/RoomPreview.ts b/src/features/game/scenes/roomPreview/RoomPreview.ts index 74c0c58c35..1ee6107b73 100644 --- a/src/features/game/scenes/roomPreview/RoomPreview.ts +++ b/src/features/game/scenes/roomPreview/RoomPreview.ts @@ -4,7 +4,9 @@ import { createContext } from 'src/commons/utils/JsSlangHelper'; import ImageAssets from '../../assets/ImageAssets'; import { getAwardProp } from '../../awards/GameAwardsHelper'; import GameAwardsManager from '../../awards/GameAwardsManager'; +import CommonBackButton from '../../commons/CommonBackButton'; import { Constants, screenCenter, screenSize } from '../../commons/CommonConstants'; +import CommonTextHover from '../../commons/CommonTextHover'; import { ItemId } from '../../commons/CommonTypes'; import { addLoadingScreen } from '../../effects/LoadingScreen'; import GameEscapeManager from '../../escape/GameEscapeManager'; @@ -14,10 +16,11 @@ import { Layer } from '../../layer/GameLayerTypes'; import GamePhaseManager from '../../phase/GamePhaseManager'; import { GamePhaseType } from '../../phase/GamePhaseTypes'; import SourceAcademyGame from '../../SourceAcademyGame'; +import { createButton } from '../../utils/ButtonUtils'; import { mandatory } from '../../utils/GameUtils'; import { loadImage, loadSound, loadSpritesheet } from '../../utils/LoaderUtils'; import { resizeOverflow } from '../../utils/SpriteUtils'; -import { roomDefaultCode } from './RoomPreviewConstants'; +import { RoomConstants, roomDefaultCode } from './RoomPreviewConstants'; import { createCMRGamePhases, createVerifiedHoverContainer } from './RoomPreviewHelper'; /** @@ -108,6 +111,30 @@ export default class RoomPreview extends Phaser.Scene { }) ); + const roomRefreshHover = new CommonTextHover( + this, + RoomConstants.refreshButton.x - 200, + RoomConstants.refreshButton.y - 30, + 'Refresh Room' + ); + + const backButton = new CommonBackButton(this, () => { + this.getLayerManager().clearAllLayers(); + this.scene.start('MainMenu'); + }); + + const refreshButton = createButton(this, { + assetKey: ImageAssets.chapterRepeatButton.key, + onUp: async () => { + await SourceAcademyGame.getInstance().loadRoomCode(); + this.studentCode = SourceAcademyGame.getInstance().getRoomCode(); + this.getLayerManager().clearAllLayers(); + this.scene.restart(); + }, + onHover: () => roomRefreshHover.setVisible(true), + onOut: () => roomRefreshHover.setVisible(false) + }).setPosition(RoomConstants.refreshButton.x, RoomConstants.refreshButton.y); + // Execute create await this.eval(`create();`); SourceAcademyGame.getInstance().getSoundManager().playBgMusic(Constants.nullInteractionId); @@ -122,6 +149,9 @@ export default class RoomPreview extends Phaser.Scene { // Add verified tag this.getLayerManager().addToLayer(Layer.UI, this.getVerifCont()); + this.getLayerManager().addToLayer(Layer.UI, backButton); + this.getLayerManager().addToLayer(Layer.UI, refreshButton); + this.getLayerManager().addToLayer(Layer.UI, roomRefreshHover); } public update() { diff --git a/src/features/game/scenes/roomPreview/RoomPreviewConstants.ts b/src/features/game/scenes/roomPreview/RoomPreviewConstants.ts index 706d3e8d40..5cd2a9a982 100644 --- a/src/features/game/scenes/roomPreview/RoomPreviewConstants.ts +++ b/src/features/game/scenes/roomPreview/RoomPreviewConstants.ts @@ -1,4 +1,5 @@ import FontAssets from '../../assets/FontAssets'; +import { screenSize } from '../../commons/CommonConstants'; import { BitmapFontStyle } from '../../commons/CommonTypes'; export const roomDefaultCode = ` @@ -41,5 +42,6 @@ export const RoomConstants = { assessmentNumber: 'MYROOM', verifiedText: 'VERIFIED', tag: { width: 128, height: 50 }, - hoverTagTextConfig: { x: 64, y: 0, oriX: 0.5, oriY: 0.55 } + hoverTagTextConfig: { x: 64, y: 0, oriX: 0.5, oriY: 0.55 }, + refreshButton: { x: 0.95 * screenSize.x, y: 0.92 * screenSize.y } };