diff --git a/src/__tests__/helpers/idle-helpers.test.ts b/src/__tests__/helpers/idle-helpers.test.ts new file mode 100644 index 0000000..9c036c4 --- /dev/null +++ b/src/__tests__/helpers/idle-helpers.test.ts @@ -0,0 +1,38 @@ +import { isPolytanFullyUnlocked } from "../../store"; +import { useStore } from "../../store"; + +const initialStoreState = useStore.getState(); + +describe("Idle helpers", () => { + it("Checks if polytan unlock works properly", () => { + expect(isPolytanFullyUnlocked()).toEqual(false); + + const fullyUnlocked = { + body: true, + head: true, + leftArm: true, + rightArm: true, + leftLeg: true, + rightLeg: true, + }; + + useStore.setState({ polytanUnlockedParts: fullyUnlocked }); + expect(isPolytanFullyUnlocked()).toEqual(true); + + const partiallyUnlocked = { + body: false, + head: true, + leftArm: false, + rightArm: true, + leftLeg: true, + rightLeg: true, + }; + + useStore.setState({ polytanUnlockedParts: partiallyUnlocked }); + expect(isPolytanFullyUnlocked()).toEqual(false); + }); + + beforeEach(() => { + useStore.setState(initialStoreState, true); + }); +}); diff --git a/src/__tests__/helpers/name-selection-helpers.test.ts b/src/__tests__/helpers/name-selection-helpers.test.ts index 323b822..91a2338 100644 --- a/src/__tests__/helpers/name-selection-helpers.test.ts +++ b/src/__tests__/helpers/name-selection-helpers.test.ts @@ -1,4 +1,4 @@ -import handleNameSelection from "../../helpers/name-selection-helpers"; +import { handleNameSelection } from "../../helpers/name-selection-helpers"; it("Handles the logic for japanese characters", () => { // cant be first character check diff --git a/src/__tests__/helpers/node-helpers.test.ts b/src/__tests__/helpers/node-helpers.test.ts index 9641a5f..b2a091e 100644 --- a/src/__tests__/helpers/node-helpers.test.ts +++ b/src/__tests__/helpers/node-helpers.test.ts @@ -6,7 +6,7 @@ it("Finds the node by it's id", () => { expect(getNodeById("0422", "a").node_name).toEqual("Tda028"); expect(getNodeById("0000", "a").node_name).toEqual("Env001"); expect(getNodeById("0616", "a").node_name).toEqual("Cou015"); - expect(getNodeById("0100", "b").node_name).toEqual("Sskn04#"); + expect(getNodeById("0100", "b").node_name).toEqual("SSkn04#"); expect(getNodeById("0101", "b").node_name).toEqual("Dc1025"); }); diff --git a/src/__tests__/input-handlers/handleBootSceneInput.test.ts b/src/__tests__/input-handlers/handleBootSceneInput.test.ts index de3d8e8..89f8142 100644 --- a/src/__tests__/input-handlers/handleBootSceneInput.test.ts +++ b/src/__tests__/input-handlers/handleBootSceneInput.test.ts @@ -36,7 +36,10 @@ it("Checks whether or not the boot scene input handler reacts appropriately for } { // change letter in authorize user scene - const spy = jest.spyOn(eventTemplates, "updateAuthorizeUserLetterIdx"); + const spy = jest.spyOn( + eventTemplates, + "updateAuthorizeUserLetterMatrixIndices" + ); const testContext = { ...getBootSceneContext(), subscene: "authorize_user" as BootSubscene, @@ -65,7 +68,7 @@ it("Checks whether or not the boot scene input handler reacts appropriately for playerName: "チ", }; - handleBootSceneInput(testContext, "X"); + handleBootSceneInput(testContext, "CROSS"); expect(spy).toHaveBeenCalled(); } @@ -77,7 +80,7 @@ it("Checks whether or not the boot scene input handler reacts appropriately for playerName: "", }; - expect(handleBootSceneInput(testContext, "X")).toEqual( + expect(handleBootSceneInput(testContext, "CROSS")).toEqual( exitUserAuthorization ); } diff --git a/src/__tests__/input-handlers/handleMediaSceneInput.test.ts b/src/__tests__/input-handlers/handleMediaSceneInput.test.ts index 903ff05..ccefcaa 100644 --- a/src/__tests__/input-handlers/handleMediaSceneInput.test.ts +++ b/src/__tests__/input-handlers/handleMediaSceneInput.test.ts @@ -35,11 +35,10 @@ it("Checks whether or not the media scene input handler reacts appropriately for { // play media - const spy = jest.spyOn(eventTemplates, "playMedia"); - const testContext = getMediaSceneContext(); - handleMediaSceneInput(testContext, "CIRCLE"); - - expect(spy).toHaveBeenCalled(); + // const testContext = getMediaSceneContext(); + // expect(handleMediaSceneInput(testContext, "CIRCLE")).toEqual( + // eventTemplates.playMedia + // ); } { // change right side media component diff --git a/src/components/MainScene/HUD.tsx b/src/components/MainScene/HUD.tsx index 54a6914..cd4c87c 100644 --- a/src/components/MainScene/HUD.tsx +++ b/src/components/MainScene/HUD.tsx @@ -1,4 +1,4 @@ -import React, { createRef, memo, useEffect, useRef } from "react"; +import React, { memo, useEffect, useRef } from "react"; import { useFrame, useLoader } from "react-three-fiber"; import * as THREE from "three"; import bigHud from "../../static/sprites/main/big_hud.png"; diff --git a/src/components/MainScene/Site/Site.tsx b/src/components/MainScene/Site/Site.tsx index 7d92571..d7bbc28 100644 --- a/src/components/MainScene/Site/Site.tsx +++ b/src/components/MainScene/Site/Site.tsx @@ -43,6 +43,11 @@ const Site = (props: SiteProps) => { config: { duration: 1200 }, })); + const [tiltState, setTiltState] = useSpring(() => ({ + tilt: 0, + config: { duration: 200 }, + })); + useEffect( () => useStore.subscribe(setRotY, (state) => ({ @@ -69,6 +74,12 @@ const Site = (props: SiteProps) => { [setPos] ); + useEffect(() => + useStore.subscribe(setTiltState, (state) => ({ + tilt: state.cameraTiltValue, + })) + ); + const activeSite = useStore((state) => state.activeSite); const gameProgress = useStore((state) => state.gameProgress); @@ -80,13 +91,15 @@ const Site = (props: SiteProps) => { return ( : null}> - - - - - + + + + + + + + - ); diff --git a/src/components/TextRenderer/MainYellowTextAnimator.tsx b/src/components/TextRenderer/MainYellowTextAnimator.tsx index 7fe371c..2f0313c 100644 --- a/src/components/TextRenderer/MainYellowTextAnimator.tsx +++ b/src/components/TextRenderer/MainYellowTextAnimator.tsx @@ -42,19 +42,19 @@ const MainYellowTextAnimator = (props: { visible?: boolean }) => { }, [activeNode, prevData?.subscene, set, subscene, gameProgress]); return ( - + {trail.map(({ posX, posY }, idx) => ( ))} - + ); }; diff --git a/src/core/eventTemplates.ts b/src/core/eventTemplates.ts index 3d886e0..8174074 100644 --- a/src/core/eventTemplates.ts +++ b/src/core/eventTemplates.ts @@ -39,6 +39,7 @@ export const siteMoveHorizontal = (calculatedState: { mutation: { lainMoveState: calculatedState.lainMoveAnimation, siteRot: calculatedState.siteRot, + cameraTiltValue: 0, inputCooldown: 5500, }, }, @@ -62,6 +63,7 @@ export const siteMoveVertical = (calculatedState: { { mutation: { lainMoveState: calculatedState.lainMoveAnimation, + cameraTiltValue: 0, activeLevel: calculatedState.activeLevel, inputCooldown: 5500, }, @@ -95,6 +97,7 @@ export const throwNode = (calculatedState: { currentScene: GameScene }) => ({ { mutation: { lainMoveState: "throw_node", inputCooldown: -1 } }, { mutation: { + cameraTiltValue: 0, currentScene: calculatedState.currentScene, intro: false, lainMoveState: "standing", @@ -116,6 +119,7 @@ export const ripNode = (calculatedState: { currentScene: GameScene }) => ({ { mutation: { currentScene: calculatedState.currentScene, + cameraTiltValue: 0, intro: false, lainMoveState: "standing", }, @@ -136,6 +140,7 @@ export const explodeNode = { mutation: { lainMoveState: "touch_node_and_get_scared", inputCooldown: 3800, + cameraTiltValue: 0, }, }, { @@ -153,7 +158,13 @@ export const explodeNode = { export const knockNode = { state: [ - { mutation: { lainMoveState: "knock", inputCooldown: 3500 } }, + { + mutation: { + lainMoveState: "knock", + cameraTiltValue: 0, + inputCooldown: 3500, + }, + }, { mutation: { lainMoveState: "standing" }, delay: 3500, @@ -165,7 +176,13 @@ export const knockNode = { export const knockNodeAndFall = { state: [ - { mutation: { lainMoveState: "knock_and_fall", inputCooldown: 6000 } }, + { + mutation: { + lainMoveState: "knock_and_fall", + cameraTiltValue: 0, + inputCooldown: 6000, + }, + }, { mutation: { lainMoveState: "standing" }, delay: 6000 }, ], effects: [nodeKnockAndFallAnimation], @@ -184,6 +201,7 @@ export const enterLevelSelection = (calculatedState: { { mutation: { selectedLevel: calculatedState.selectedLevel, + cameraTiltValue: 0, mainSubscene: "level_selection", inputCooldown: 1500, }, @@ -243,6 +261,7 @@ export const pauseGame = (calculatedState: { siteRot: number[] }) => ({ { mutation: { lainMoveState: "rip_middle_ring", + cameraTiltValue: 0, mainSubscene: "pause", inputCooldown: -1, }, @@ -797,6 +816,33 @@ export const setProtocolLines = (calculatedState: { ], }); +export const setCameraTilt = (calculatedState: { + cameraTiltValue: number; +}) => ({ + state: [ + { + mutation: { + cameraTiltValue: calculatedState.cameraTiltValue, + inputCooldown: 100, + }, + }, + ], +}); + +export const resetCameraTilt = (calculatedState: { + lastCameraTiltValue: number; +}) => ({ + state: [ + { + mutation: { + cameraTiltValue: 0, + lastCameraTiltValue: calculatedState.lastCameraTiltValue, + inputCooldown: 100, + }, + }, + ], +}); + export const resetInputCooldown = { state: [{ mutation: { inputCooldown: 0 } }], }; diff --git a/src/core/input-handlers/handleMainSceneInput.ts b/src/core/input-handlers/handleMainSceneInput.ts index b4471d3..a13aa7a 100644 --- a/src/core/input-handlers/handleMainSceneInput.ts +++ b/src/core/input-handlers/handleMainSceneInput.ts @@ -25,10 +25,12 @@ import { loadGame, loadGameFail, pauseGame, + resetCameraTilt, resetInputCooldown, ripNode, saveGame, selectLevel, + setCameraTilt, setProtocolLines, showAbout, showPermissionDenied, @@ -59,6 +61,8 @@ const handleMainSceneInput = ( wordNotFound, canLainMove, protocolLinesToggled, + cameraTiltValue, + lastCameraTiltValue, } = mainSceneContext; if (promptVisible) { @@ -208,6 +212,7 @@ const handleMainSceneInput = ( case "L2": return enterLevelSelection({ selectedLevel: level }); case "TRIANGLE": + case "SELECT": if (!canLainMove) return resetInputCooldown; return pauseGame({ siteRot: [Math.PI / 2, siteRotY, 0] }); case "L1": @@ -250,6 +255,16 @@ const handleMainSceneInput = ( return setProtocolLines({ protocolLinesToggled: !protocolLinesToggled, }); + case "R2": + if (cameraTiltValue === 0) { + return setCameraTilt({ + cameraTiltValue: -lastCameraTiltValue, + }); + } else { + return resetCameraTilt({ + lastCameraTiltValue: -lastCameraTiltValue, + }); + } } break; case "level_selection": diff --git a/src/dom-components/Notes.tsx b/src/dom-components/Notes.tsx index e9b3363..6422cda 100644 --- a/src/dom-components/Notes.tsx +++ b/src/dom-components/Notes.tsx @@ -99,10 +99,26 @@ const Notes = () => { z ✖ + + s + ◼ + d ▲ + + r + R1 + + + t + R2 + + + w + L1 + e L2 @@ -111,6 +127,10 @@ const Notes = () => { v START + + c + SELECT + k Upscale Game Window diff --git a/src/helpers/idle-helpers.ts b/src/helpers/idle-helpers.ts index df860e6..37289cd 100644 --- a/src/helpers/idle-helpers.ts +++ b/src/helpers/idle-helpers.ts @@ -1,6 +1,6 @@ import site_a from "../resources/site_a.json"; import site_b from "../resources/site_b.json"; -import { useStore } from "../store"; +import { isPolytanFullyUnlocked, useStore } from "../store"; import { SiteData } from "../types/types"; export const getRandomIdleMedia = () => { @@ -78,7 +78,7 @@ export const getRandomIdleMedia = () => { nodeName: nodeName, }; } else { - if (site === "b" && Math.random() < 0.3) { + if (site === "b" && isPolytanFullyUnlocked() && Math.random() < 0.3) { const polytanMedia = ["PO1.STR[0]", "PO2.STR[0]"]; return { type: "video", diff --git a/src/resources/node_huds.json b/src/resources/node_huds.json index dd4eadf..968c64f 100644 --- a/src/resources/node_huds.json +++ b/src/resources/node_huds.json @@ -18,7 +18,7 @@ [-0.09, -0.18, 0] ] }, - "big_text": [-0.35, 0.23, -8.7] + "big_text": [-0.3, 0.2, -8.7] }, "fg_hud_2": { "mirrored": 0, @@ -39,7 +39,7 @@ [-0.09, -0.18, 0] ] }, - "big_text": [-0.35, -0.05, -8.7] + "big_text": [-0.3, -0.04, -8.7] }, "fg_hud_3": { "mirrored": 0, @@ -60,7 +60,7 @@ [0.09, 0.18, 0] ] }, - "big_text": [-0.35, -0.32, -8.7] + "big_text": [-0.3, -0.27, -8.7] }, "fg_hud_4": { "mirrored": 1, @@ -81,7 +81,7 @@ [-0.09, -0.18, 0] ] }, - "big_text": [0.45, 0.265, -8.7] + "big_text": [0.39, 0.23, -8.7] }, "fg_hud_5": { "mirrored": 1, @@ -102,7 +102,7 @@ [-0.09, -0.18, 0] ] }, - "big_text": [0.45, -0.05, -8.7] + "big_text": [0.39, -0.04, -8.7] }, "fg_hud_6": { "mirrored": 1, @@ -123,7 +123,7 @@ [0.09, 0.18, 0] ] }, - "big_text": [0.45, -0.32, -8.7] + "big_text": [0.39, -0.27, -8.7] }, "bg_hud_1": { "mirrored": 0, @@ -144,7 +144,7 @@ [-0.09, -0.18, 0] ] }, - "big_text": [-0.15, 0.1, -8.7] + "big_text": [-0.15, 0.09, -8.7] }, "bg_hud_2": { "mirrored": 0, @@ -165,7 +165,7 @@ [-0.09, -0.18, 0] ] }, - "big_text": [-0.15, -0.03, -8.7] + "big_text": [-0.15, -0.02, -8.7] }, "bg_hud_3": { "mirrored": 0, @@ -186,7 +186,7 @@ [-0.09, -0.18, 0] ] }, - "big_text": [-0.15, -0.17, -8.7] + "big_text": [-0.15, -0.14, -8.7] }, "bg_hud_4": { "mirrored": 1, @@ -195,7 +195,7 @@ "initial_position": [1.63, 0.04, -8.6] }, "boring": { - "position": [-0.28, 0.06, -8.6], + "position": [-0.3, 0.06, -8.6], "initial_position": [-1.28, 0.06, -8.6] }, "big": { @@ -207,7 +207,7 @@ [-0.09, -0.18, 0] ] }, - "big_text": [0.2, 0.1, -8.7] + "big_text": [0.2, 0.09, -8.7] }, "bg_hud_5": { "mirrored": 1, @@ -228,7 +228,7 @@ [-0.09, -0.18, 0] ] }, - "big_text": [0.2, -0.03, -8.7] + "big_text": [0.2, -0.02, -8.7] }, "bg_hud_6": { "mirrored": 1, @@ -237,7 +237,7 @@ "initial_position": [1.63, -0.19, -8.6] }, "boring": { - "position": [-0.29, -0.17, -8.6], + "position": [-0.3, -0.17, -8.6], "initial_position": [-1.29, -0.17, -8.6] }, "big": { @@ -249,6 +249,6 @@ [0.09, 0.18, 0] ] }, - "big_text": [0.2, -0.17, -8.7] + "big_text": [0.2, -0.14, -8.7] } } diff --git a/src/scenes/MainScene.tsx b/src/scenes/MainScene.tsx index aa29d4a..3118f33 100644 --- a/src/scenes/MainScene.tsx +++ b/src/scenes/MainScene.tsx @@ -139,6 +139,18 @@ const MainScene = () => { mainSceneMusic.pause(); }; }, [intro, introFinished, showingAbout]); + + const [tiltState, setTiltState] = useSpring(() => ({ + posY: 0, + config: { duration: 200 }, + })); + + useEffect(() => + useStore.subscribe(setTiltState, (state) => ({ + posY: -state.cameraTiltValue, + })) + ); + return ( }> @@ -153,10 +165,10 @@ const MainScene = () => { - + - + @@ -168,12 +180,12 @@ const MainScene = () => { mainVisible={intro ? starfieldIntro : true} /> - + - + { wordNotFound: state.wordNotFound, canLainMove: state.canLainMove, protocolLinesToggled: state.protocolLinesToggled, + cameraTiltValue: state.cameraTiltValue, + lastCameraTiltValue: state.lastCameraTiltValue, }; }; @@ -454,15 +463,10 @@ export const createAudioAnalyser = () => { }; export const isPolytanFullyUnlocked = () => { - return ( - useStore.getState().polytanUnlockedParts === - { - body: true, - head: true, - leftArm: true, - rightArm: true, - leftLeg: true, - rightLeg: true, - } - ); + const polytanProgress = useStore.getState().polytanUnlockedParts; + + for (const key in polytanProgress) + if (!polytanProgress[key as keyof typeof polytanProgress]) return false; + + return true; }; diff --git a/src/types/types.ts b/src/types/types.ts index eb2bdae..aff822d 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -115,6 +115,8 @@ export interface MainSceneContext extends PromptContext { siteSaveState: SiteSaveState; canLainMove: boolean; protocolLinesToggled: boolean; + cameraTiltValue: number; + lastCameraTiltValue: number; } export type SsknSceneContext = { diff --git a/src/utils/getKeyPress.ts b/src/utils/getKeyPress.ts index c619cf7..5872288 100644 --- a/src/utils/getKeyPress.ts +++ b/src/utils/getKeyPress.ts @@ -1,5 +1,6 @@ const getKeyPress = (key: string) => { - if (["X", "Z", "D", "E", "V", "T", "W", "R"].includes(key)) + // make the keybinds work with caps lock on aswell + if (["X", "Z", "D", "E", "V", "T", "W", "R", "S", "C"].includes(key)) key = key.toLowerCase(); const keyCodeAssocs = { @@ -11,10 +12,12 @@ const getKeyPress = (key: string) => { z: "CROSS", d: "TRIANGLE", s: "SQUARE", + t: "R2", e: "L2", - v: "START", w: "L1", r: "R1", + v: "START", + c: "SELECT", }; return keyCodeAssocs[key as keyof typeof keyCodeAssocs]; };