phone swipe support, tweaked gate code, fixed idle, better input cooldowns

This commit is contained in:
ad044 2021-02-27 19:39:31 +04:00
parent 2a3f2202aa
commit 20934e2f19
19 changed files with 388 additions and 346 deletions

View file

@ -14,6 +14,7 @@ import ChangeDiscScene from "./scenes/ChangeDiscScene";
import EndScene from "./scenes/EndScene";
import IdleMediaScene from "./scenes/IdleMediaScene";
import InputHandler from "./components/InputHandler";
import { Html } from "@react-three/drei";
const App = () => {
const currentScene = useStore((state) => state.currentScene);
@ -40,16 +41,16 @@ const App = () => {
);
return (
<div id="game-root" className="game">
<span className="canvas">
<Canvas concurrent>
<InputHandler />
<Suspense fallback={null}>
{/*<Preloader />*/}
{dispatchScene[currentScene as keyof typeof dispatchScene]}
</Suspense>
</Canvas>
</span>
<div className="game">
<Canvas concurrent>
<Suspense fallback={null}>
{/*<Preloader />*/}
{dispatchScene[currentScene as keyof typeof dispatchScene]}
<Html center zIndexRange={[0, 0]}>
<InputHandler />
</Html>
</Suspense>
</Canvas>
{["media", "idle_media", "tak", "end"].includes(currentScene) && (
<MediaPlayer />
)}

View file

@ -1,9 +1,8 @@
import React, { useEffect, useState } from "react";
import BlueZero from "./GateMiddleObject/BlueZero";
import BlueOne from "./GateMiddleObject/BlueOne";
import { a, useSpring, useSprings } from "@react-spring/three";
import blue_digit_positions from "../../resources/blue_digit_positions.json";
import Mirror from "./GateMiddleObject/Mirror";
import BlueDigit from "./GateMiddleObject/BlueDigit";
type GateMiddleObjectProps = {
intro: boolean;
@ -16,14 +15,12 @@ const GateMiddleObject = (props: GateMiddleObjectProps) => {
const [springs, set] = useSprings(44, (intIdx) => {
const idx = intIdx.toString();
return {
type: blue_digit_positions[idx as keyof typeof blue_digit_positions].type,
posX:
blue_digit_positions[idx as keyof typeof blue_digit_positions]
.initial_x,
posY:
blue_digit_positions[idx as keyof typeof blue_digit_positions]
.initial_y,
visibility: false,
config: { duration: 150 },
};
});
@ -40,7 +37,6 @@ const GateMiddleObject = (props: GateMiddleObjectProps) => {
.final_y,
delay:
blue_digit_positions[idx as keyof typeof blue_digit_positions].delay,
visibility: true,
};
});
@ -62,23 +58,17 @@ const GateMiddleObject = (props: GateMiddleObjectProps) => {
position-z={middleObjectGroupState.posZ}
visible={props.intro}
>
{springs.map((item, idx) =>
item.type.get() === 1 ? (
<BlueOne
posX={item.posX}
posY={item.posY}
key={idx}
visibility={item.visibility}
/>
) : (
<BlueZero
posX={item.posX}
posY={item.posY}
key={idx}
visibility={item.visibility}
/>
)
)}
{springs.map((item, idx) => (
<BlueDigit
type={
blue_digit_positions[
idx.toString() as keyof typeof blue_digit_positions
].type
}
posX={item.posX}
posY={item.posY}
/>
))}
</a.group>
<Mirror
visible={props.gateLvl === 1 ? !props.intro : props.gateLvl > 0}

View file

@ -1,29 +1,41 @@
import React, { useEffect, useMemo, useRef } from "react";
import { useLoader } from "react-three-fiber";
import * as THREE from "three";
import gateBlueBinarySingularZero from "../../../static/sprites/gate/blue_binary_singular_zero.png";
import gateBlueBinarySingularOne from "../../../static/sprites/gate/blue_binary_singular_one.png";
import { a, SpringValue } from "@react-spring/three";
import gateBlueBinarySingularZero from "../../../static/sprites/gate/blue_binary_singular_zero.png";
type BlueZeroProps = {
type BlueDigitProps = {
type: number;
posX: SpringValue<number>;
posY: SpringValue<number>;
visibility: SpringValue<boolean>;
};
const BlueZero = (props: BlueZeroProps) => {
const BlueDigit = (props: BlueDigitProps) => {
const gateBlueBinarySingularOneTex = useLoader(
THREE.TextureLoader,
gateBlueBinarySingularOne
);
const gateBlueBinarySingularZeroTex = useLoader(
THREE.TextureLoader,
gateBlueBinarySingularZero
);
const objRef = useRef<THREE.Mesh>();
const matRef = useRef<THREE.ShaderMaterial>();
const uniforms = useMemo(
() => ({
zeroTex: { type: "t", value: gateBlueBinarySingularZeroTex },
tex: {
type: "t",
value:
props.type === 1
? gateBlueBinarySingularOneTex
: gateBlueBinarySingularZeroTex,
},
brightnessMultiplier: { value: 1.5 },
}),
[gateBlueBinarySingularZeroTex]
[gateBlueBinarySingularOneTex, gateBlueBinarySingularZeroTex, props.type]
);
const vertexShader = `
@ -35,14 +47,13 @@ const BlueZero = (props: BlueZeroProps) => {
}
`;
const fragmentShaderZero = `
uniform sampler2D zeroTex;
const fragmentShader = `
uniform sampler2D tex;
uniform float brightnessMultiplier;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D(zeroTex, vUv) * brightnessMultiplier;
gl_FragColor = texture2D(tex, vUv) * brightnessMultiplier;
}
`;
@ -50,21 +61,26 @@ const BlueZero = (props: BlueZeroProps) => {
setTimeout(() => {
if (matRef.current) {
matRef.current.uniforms.brightnessMultiplier.value = 3.5;
matRef.current.uniformsNeedUpdate = true;
}
}, 1400);
setTimeout(() => {
if (objRef.current) objRef.current.visible = true;
}, 150);
}, []);
return (
<a.mesh
scale={[0.08, 0.1, 0]}
scale={[0.04, 0.1, 0]}
position-x={props.posX}
position-y={props.posY}
renderOrder={5}
visible={props.visibility}
visible={false}
ref={objRef}
>
<planeBufferGeometry attach="geometry"></planeBufferGeometry>
<planeBufferGeometry attach="geometry" />
<shaderMaterial
fragmentShader={fragmentShaderZero}
fragmentShader={fragmentShader}
vertexShader={vertexShader}
uniforms={uniforms}
attach="material"
@ -76,4 +92,4 @@ const BlueZero = (props: BlueZeroProps) => {
);
};
export default BlueZero;
export default BlueDigit;

View file

@ -1,77 +0,0 @@
import React, { useEffect, useMemo, useRef } from "react";
import { useLoader } from "react-three-fiber";
import * as THREE from "three";
import gateBlueBinarySingularOne from "../../../static/sprites/gate/blue_binary_singular_one.png";
import { a, SpringValue } from "@react-spring/three";
type BlueOneProps = {
posX: SpringValue<number>;
posY: SpringValue<number>;
visibility: SpringValue<boolean>;
};
const BlueOne = (props: BlueOneProps) => {
const gateBlueBinarySingularOneTex = useLoader(
THREE.TextureLoader,
gateBlueBinarySingularOne
);
const matRef = useRef<THREE.ShaderMaterial>();
const uniforms = useMemo(
() => ({
oneTex: { type: "t", value: gateBlueBinarySingularOneTex },
brightnessMultiplier: { value: 1.5 },
}),
[gateBlueBinarySingularOneTex]
);
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShaderOne = `
uniform sampler2D oneTex;
uniform float brightnessMultiplier;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D(oneTex, vUv) * brightnessMultiplier;
}
`;
useEffect(() => {
setTimeout(() => {
if (matRef.current)
matRef.current.uniforms.brightnessMultiplier.value = 3.5;
}, 1400);
}, []);
return (
<a.mesh
scale={[0.04, 0.1, 0]}
position-x={props.posX}
position-y={props.posY}
renderOrder={5}
visible={props.visibility}
>
<planeBufferGeometry attach="geometry"></planeBufferGeometry>
<shaderMaterial
fragmentShader={fragmentShaderOne}
vertexShader={vertexShader}
uniforms={uniforms}
attach="material"
transparent={true}
depthTest={false}
ref={matRef}
/>
</a.mesh>
);
};
export default BlueOne;

View file

@ -0,0 +1,77 @@
import { useFrame } from "react-three-fiber";
import { playAudio, useStore } from "../store";
import * as audio from "../static/audio/sfx";
import {
playIdleAudio,
playIdleVideo,
playLainIdleAnim,
} from "../core/eventTemplates";
import {
getRandomIdleLainAnim,
getRandomIdleMedia,
} from "../helpers/idle-helpers";
import handleEvent from "../core/handleEvent";
type IdleManagerProps = {
lainIdleTimerRef: any;
idleSceneTimerRef: any;
};
const IdleManager = (props: IdleManagerProps) => {
const mainSubscene = useStore((state) => state.mainSubscene);
const scene = useStore((state) => state.currentScene);
useFrame(() => {
const now = Date.now();
if (
props.lainIdleTimerRef.current !== -1 &&
props.idleSceneTimerRef.current !== -1 &&
mainSubscene !== "pause" &&
mainSubscene !== "level_selection" &&
scene === "main"
) {
if (now > props.lainIdleTimerRef.current + 10000) {
// after one idle animation plays, the second comes sooner than it would after a regular keypress
props.lainIdleTimerRef.current = now - 2500;
const [idleLainAnim, duration] = getRandomIdleLainAnim();
const event = playLainIdleAnim({
lainMoveState: idleLainAnim,
duration: duration,
});
if (event) handleEvent(event);
}
if (now > props.idleSceneTimerRef.current + 500000) {
// put it on lock until the next action, since while the idle media plays, the
// Date.now() value keeps increasing, which can result in another idle media playing right after one finishes
// one way to work around this would be to modify the value depending on the last played idle media's duration
// but i'm way too lazy for that
props.idleSceneTimerRef.current = -1;
playAudio(audio.sound32);
const data = getRandomIdleMedia();
const { type, nodeName, images, media } = data;
let event;
if (type === "audio" && images && nodeName) {
event = playIdleAudio({
idleNodeName: nodeName,
idleImages: images,
idleMedia: media,
});
} else if (type === "video") {
event = playIdleVideo({ idleMedia: media });
}
if (event) handleEvent(event);
}
}
});
return null;
};
export default IdleManager;

View file

@ -1,154 +1,138 @@
import { useCallback, useEffect, useRef } from "react";
import React, { useCallback, useEffect, useRef } from "react";
import {
getBootSceneContext,
getEndSceneContext,
getMainSceneContext,
getMediaSceneContext,
getSsknSceneContext,
playAudio,
useStore,
} from "../store";
import { getKeyCodeAssociation } from "../utils/parseUserInput";
import getKeyPress from "../utils/getKeyPress";
import handleMediaSceneInput from "../core/input-handlers/handleMediaSceneInput";
import handleSsknSceneInput from "../core/input-handlers/handleSsknSceneInput";
import handleMainSceneInput from "../core/input-handlers/handleMainSceneInput";
import handleBootSceneInput from "../core/input-handlers/handleBootSceneInput";
import { useFrame } from "react-three-fiber";
import { getRandomIdleLainAnim } from "../helpers/idle-helpers";
import * as audio from "../static/audio/sfx";
import handleEndSceneInput from "../core/input-handlers/handleEndSceneInput";
import handleEvent from "../core/handleEvent";
import { GameEvent } from "../types/types";
import { useSwipeable } from "react-swipeable";
import IdleManager from "./IdleManager";
import { Canvas } from "react-three-fiber";
const InputHandler = () => {
const scene = useStore((state) => state.currentScene);
const mainSubscene = useStore((state) => state.mainSubscene);
const inputCooldown = useStore((state) => state.inputCooldown);
const setLainMoveState = useStore((state) => state.setLainMoveState);
const timeSinceLastKeyPress = useRef(-1);
const lainIdleCounter = useRef(-1);
const idleSceneCounter = useRef(-1);
useFrame(() => {
const now = Date.now();
if (
lainIdleCounter.current > -1 &&
idleSceneCounter.current > -1 &&
mainSubscene !== "pause" &&
mainSubscene !== "level_selection" &&
scene === "main"
) {
if (now > lainIdleCounter.current + 10000) {
setLainMoveState(getRandomIdleLainAnim());
// after one idle animation plays, the second comes sooner than it would after a regular keypress
lainIdleCounter.current = now - 2500;
}
if (now > idleSceneCounter.current + 30000) {
// put it on lock until the next action, since while the idle media plays, the
// Date.now() value keeps increasing, which can result in another idle media playing right after one finishes
// one way to work around this would be to modify the value depending on the last played idle media's duration
// but i'm way too lazy for that
idleSceneCounter.current = -1;
// idleManager(getRandomIdleMedia());
playAudio(audio.sound32);
setTimeout(() => {
// useStore.setState({ event: "play_idle_media" });
}, 1200);
}
}
});
useEffect(() => {
if (scene !== "main") idleSceneCounter.current = -1;
}, [scene]);
const lainIdleTimerRef = useRef(-1);
const idleSceneTimerRef = useRef(-1);
const handleKeyPress = useCallback(
(event) => {
const { keyCode } = event;
(keyPress: string) => {
const now = Date.now();
const keyPress = getKeyCodeAssociation(keyCode);
if (scene === "main") {
timeSinceLastKeyPress.current = now;
lainIdleTimerRef.current = now;
idleSceneTimerRef.current = now;
}
const sceneFns = (() => {
switch (scene) {
case "main":
return {
contextProvider: getMainSceneContext,
keyPressHandler: handleMainSceneInput,
};
case "media":
return {
contextProvider: getMediaSceneContext,
keyPressHandler: handleMediaSceneInput,
};
case "sskn":
return {
contextProvider: getSsknSceneContext,
keyPressHandler: handleSsknSceneInput,
};
case "boot":
return {
contextProvider: getBootSceneContext,
keyPressHandler: handleBootSceneInput,
};
case "end":
return {
contextProvider: getEndSceneContext,
keyPressHandler: handleEndSceneInput,
};
case "gate":
case "polytan":
useStore.setState({ currentScene: "main" });
break;
case "idle_media":
useStore.setState({
currentScene: "main",
idleStarting: false,
});
break;
}
})();
if (sceneFns) {
const { contextProvider, keyPressHandler } = sceneFns;
const ctx = contextProvider();
const event: GameEvent | undefined = keyPressHandler(
ctx as any,
keyPress
);
if (event) handleEvent(event);
}
},
[scene]
);
const handlers = useSwipeable({
onSwiped: (eventData) => handleKeyPress(eventData.dir.toUpperCase()),
onTap: () => handleKeyPress("CIRCLE"),
});
const handleKeyBoardEvent = useCallback(
(event) => {
const key = getKeyPress(event.key);
const now = Date.now();
if (
keyPress &&
key &&
now > timeSinceLastKeyPress.current + inputCooldown &&
inputCooldown !== -1
) {
if (scene === "main") {
lainIdleCounter.current = now;
idleSceneCounter.current = now;
timeSinceLastKeyPress.current = now;
}
const sceneFns = (() => {
switch (scene) {
case "main":
return {
contextProvider: getMainSceneContext,
keyPressHandler: handleMainSceneInput,
};
case "media":
return {
contextProvider: getMediaSceneContext,
keyPressHandler: handleMediaSceneInput,
};
case "sskn":
return {
contextProvider: getSsknSceneContext,
keyPressHandler: handleSsknSceneInput,
};
case "boot":
return {
contextProvider: getBootSceneContext,
keyPressHandler: handleBootSceneInput,
};
case "end":
return {
contextProvider: getEndSceneContext,
keyPressHandler: handleEndSceneInput,
};
case "gate":
case "polytan":
useStore.setState({ currentScene: "main" });
break;
case "idle_media":
useStore.setState({
currentScene: "main",
idleStarting: false,
});
break;
}
})();
if (sceneFns) {
const { contextProvider, keyPressHandler } = sceneFns;
const ctx = contextProvider();
const event: GameEvent | undefined = keyPressHandler(
ctx as any,
keyPress
);
if (event) handleEvent(event);
}
handleKeyPress(key);
}
},
[inputCooldown, scene]
[handleKeyPress, inputCooldown]
);
useEffect(() => {
window.addEventListener("keydown", handleKeyPress);
window.addEventListener("keydown", handleKeyBoardEvent);
return () => {
window.removeEventListener("keydown", handleKeyPress);
window.removeEventListener("keydown", handleKeyBoardEvent);
};
}, [handleKeyPress]);
}, [handleKeyBoardEvent]);
return null;
return (
<>
<div {...handlers} className="swipe-handler" />
<Canvas>
<IdleManager
lainIdleTimerRef={lainIdleTimerRef}
idleSceneTimerRef={idleSceneTimerRef}
/>
</Canvas>
</>
);
};
export default InputHandler;

View file

@ -25,18 +25,6 @@ const HUD = memo(() => {
const scene = useStore((state) => state.currentScene);
const prevData = usePrevious({ siteRotY, activeLevel, subscene, scene });
const lerpObject = (
obj: THREE.Object3D,
posX: number,
initialPosX: number
) => {
obj.position.x = lerp(
obj.position.x,
activeRef.current ? posX : initialPosX,
0.12
);
};
// this part is imperative because it performs a lot better than having a toggleable spring.
useFrame(() => {
if (
@ -46,25 +34,30 @@ const HUD = memo(() => {
greenTextRef.current
) {
const hud = currentHudRef.current;
lerpObject(
longHudRef.current,
hud.long.position[0],
hud.long.initial_position[0]
longHudRef.current.position.x = lerp(
longHudRef.current.position.x,
activeRef.current ? hud.long.position[0] : hud.long.initial_position[0],
0.12
);
lerpObject(
boringHudRef.current,
hud.boring.position[0],
hud.boring.initial_position[0]
boringHudRef.current.position.x = lerp(
boringHudRef.current.position.x,
activeRef.current
? hud.boring.position[0]
: hud.boring.initial_position[0],
0.12
);
lerpObject(
bigHudRef.current,
hud.big.position[0],
hud.big.initial_position[0]
bigHudRef.current.position.x = lerp(
bigHudRef.current.position.x,
activeRef.current ? hud.big.position[0] : hud.big.initial_position[0],
0.12
);
lerpObject(
greenTextRef.current,
hud.medium_text.position[0],
hud.medium_text.initial_position[0]
greenTextRef.current.position.x = lerp(
greenTextRef.current.position.x,
activeRef.current
? hud.medium_text.position[0]
: hud.medium_text.initial_position[0],
0.12
);
}
});

View file

@ -204,7 +204,7 @@ export const changeSelectedLevel = (calculatedState: {
{
mutation: {
selectedLevel: calculatedState.selectedLevel,
inputCooldown: 300,
inputCooldown: 100,
},
},
],
@ -263,7 +263,7 @@ export const changePauseComponent = (calculatedState: {
{
mutation: {
activePauseComponent: calculatedState.activePauseComponent,
inputCooldown: 500,
inputCooldown: 700,
},
},
],
@ -279,7 +279,7 @@ export const showPermissionDenied = {
};
export const displayPrompt = {
state: [{ mutation: { promptVisible: true, inputCooldown: 500 } }],
state: [{ mutation: { promptVisible: true, inputCooldown: 0 } }],
audio: [{ sfx: [audio.sound0] }],
};
@ -311,7 +311,7 @@ export const exitPause = (calculatedState: { siteRot: number[] }) => ({
});
export const exitAbout = {
state: [{ mutation: { showingAbout: false, inputCooldown: 500 } }],
state: [{ mutation: { showingAbout: false, inputCooldown: 0 } }],
};
export const changePromptComponent = (calculatedState: {
@ -321,7 +321,7 @@ export const changePromptComponent = (calculatedState: {
{
mutation: {
activePromptComponent: calculatedState.activePromptComponent,
inputCooldown: 500,
inputCooldown: 100,
},
},
],
@ -334,7 +334,7 @@ export const exitPrompt = {
mutation: {
activePromptComponent: "no",
promptVisible: false,
inputCooldown: 500,
inputCooldown: 0,
},
},
],
@ -458,7 +458,7 @@ export const changeMediaSide = (calculatedState: {
activeMediaComponent: calculatedState.activeMediaComponent,
lastActiveMediaComponents: calculatedState.lastActiveMediaComponents,
currentMediaSide: calculatedState.currentMediaSide,
inputCooldown: 500,
inputCooldown: 0,
},
},
],
@ -497,7 +497,7 @@ export const changeRightMediaComponent = (calculatedState: {
mutation: {
activeMediaComponent: calculatedState.activeComponent,
mediaWordPosStateIdx: calculatedState.wordPosStateIdx,
inputCooldown: 500,
inputCooldown: 300,
},
},
],
@ -519,7 +519,7 @@ export const wordNotFound = {
};
export const hideWordNotFound = {
state: [{ mutation: { wordNotFound: false, inputCooldown: 300 } }],
state: [{ mutation: { wordNotFound: false, inputCooldown: 0 } }],
};
export const selectWord = (calculatedState: {
@ -550,7 +550,7 @@ export const changeSsknComponent = (calculatedState: {
{
mutation: {
activeSsknComponent: calculatedState.activeSsknComponent,
inputCooldown: 500,
inputCooldown: 100,
},
},
],
@ -595,7 +595,7 @@ export const changeEndComponent = (calculatedState: {
{
mutation: {
activeEndComponent: calculatedState.activeEndComponent,
inputCooldown: 500,
inputCooldown: 100,
},
},
],
@ -615,7 +615,7 @@ export const changeMainMenuComponent = (calculatedState: {
{
mutation: {
activeMainMenuComponent: calculatedState.activeMainMenuComponent,
inputCooldown: 500,
inputCooldown: 200,
},
},
],
@ -702,3 +702,58 @@ export const updateAuthorizeUserLetterIdx = (calculatedState: {
},
],
});
export const playIdleVideo = (calculatedState: { idleMedia: string }) => ({
state: [
{
mutation: {
idleStarting: true,
idleMedia: calculatedState.idleMedia,
inputCooldown: -1,
},
},
{ mutation: { currentScene: "idle_media" }, delay: 1200 },
],
});
export const playIdleAudio = (calculatedState: {
idleMedia: string;
idleImages: { "1": string; "2": string; "3": string };
idleNodeName: string;
}) => ({
state: [
{
mutation: {
idleStarting: true,
inputCooldown: -1,
idleMedia: calculatedState.idleMedia,
idleImages: calculatedState.idleImages,
idleNodeName: calculatedState.idleNodeName,
},
},
{ mutation: { currentScene: "idle_media" }, delay: 1200 },
],
});
export const playLainIdleAnim = (calculatedState: {
lainMoveState: string;
duration: number;
}) => ({
// todo appropriate disable-move here also
state: [
{
mutation: {
lainMoveState: calculatedState.lainMoveState,
canLainMove: false,
},
},
{
mutation: { lainMoveState: "standing", canLainMove: true },
delay: calculatedState.duration,
},
],
});
export const resetInputCooldown = {
state: [{ mutation: { inputCooldown: 0 } }],
};

View file

@ -24,6 +24,7 @@ import {
loadGame,
loadGameFail,
pauseGame,
resetInputCooldown,
ripNode,
saveGame,
selectLevel,
@ -54,6 +55,7 @@ const handleMainSceneInput = (
activePromptComponent,
siteSaveState,
wordNotFound,
canLainMove,
} = mainSceneContext;
if (promptVisible) {
@ -137,6 +139,7 @@ const handleMainSceneInput = (
};
if (nodeData.didMove) {
if (!canLainMove) return resetInputCooldown;
return siteMoveHorizontal({
lainMoveAnimation: lainMoveAnimation,
siteRot: newSiteRot,
@ -179,15 +182,18 @@ const handleMainSceneInput = (
matrixIndices: nodeData.matrixIndices,
};
if (nodeData.didMove)
if (nodeData.didMove) {
if (!canLainMove) return resetInputCooldown;
return siteMoveVertical({
lainMoveAnimation: lainMoveAnimation,
activeLevel: newLevel,
activeNode: newNode,
});
else return changeNode({ activeNode: newNode });
} else return changeNode({ activeNode: newNode });
}
case "CIRCLE":
if (!canLainMove) return resetInputCooldown;
const eventAnimation = Math.random() < 0.4 ? throwNode : ripNode;
if (
@ -207,6 +213,7 @@ const handleMainSceneInput = (
case "L2":
return enterLevelSelection({ selectedLevel: level });
case "TRIANGLE":
if (!canLainMove) return resetInputCooldown;
return pauseGame({ siteRot: [Math.PI / 2, siteRotY, 0] });
}
break;
@ -225,6 +232,8 @@ const handleMainSceneInput = (
return exitLevelSelection;
case "CIRCLE":
if (!canLainMove) return resetInputCooldown;
if (level === selectedLevel) return;
const direction = selectedLevel > level ? "up" : "down";

View file

@ -72,39 +72,39 @@ export const getRandomIdleMedia = () => {
const nodeName = siteData[level][nodeToPlay].node_name;
return {
type: "audio",
images: images,
media: media,
nodeName: nodeName,
};
} else {
return {
type: "video",
media:
idleNodes.video[Math.floor(Math.random() * idleNodes.video.length)],
nodeName: undefined,
images: undefined,
};
}
};
export const getRandomIdleLainAnim = () => {
const moves = [
"prayer",
"touch_sleeve",
"thinking",
"stretch_2",
"stretch",
"spin",
"scratch_head",
"blush",
"hands_behind_head",
"hands_on_hips",
"hands_on_hips_2",
"hands_together",
"lean_forward",
"lean_left",
"lean_right",
"look_around",
"play_with_hair",
export const getRandomIdleLainAnim = (): [string, number] => {
const moves: [string, number][] = [
["prayer", 3500],
["touch_sleeve", 3000],
["thinking", 3900],
["stretch_2", 3900],
["stretch", 3000],
["spin", 3000],
["scratch_head", 3900],
["blush", 3000],
["hands_behind_head", 2300],
["hands_on_hips", 3000],
["hands_on_hips_2", 3900],
["hands_together", 2500],
["lean_forward", 2700],
["lean_left", 2700],
["lean_right", 3500],
["look_around", 3000],
["play_with_hair", 2900],
];
return moves[Math.floor(Math.random() * moves.length)];

View file

@ -1747,31 +1747,31 @@
"is_viewed": 0,
"is_visible": 1
},
"Sskn01": {
"SSkn01": {
"is_viewed": 0,
"is_visible": 1
},
"Sskn02": {
"SSkn02": {
"is_viewed": 0,
"is_visible": 1
},
"Sskn03": {
"SSkn03": {
"is_viewed": 0,
"is_visible": 1
},
"Sskn04": {
"SSkn04": {
"is_viewed": 0,
"is_visible": 1
},
"Sskn04#": {
"SSkn04#": {
"is_viewed": 0,
"is_visible": 1
},
"Sskn05": {
"SSkn05": {
"is_viewed": 0,
"is_visible": 1
},
"Sskn06": {
"SSkn06": {
"is_viewed": 0,
"is_visible": 1
},

View file

@ -1644,7 +1644,7 @@
"3": "-1"
},
"media_file": "INS01.STR",
"node_name": "Sskn01",
"node_name": "SSkn01",
"required_final_video_viewcount": 0,
"site": "A",
"title": "mT up-date App.",
@ -2748,7 +2748,7 @@
"3": "-1"
},
"media_file": "INS02.STR",
"node_name": "Sskn02",
"node_name": "SSkn02",
"required_final_video_viewcount": 0,
"site": "A",
"title": "mT up-date App.",
@ -4892,7 +4892,7 @@
"3": "-1"
},
"media_file": "INS03.STR",
"node_name": "Sskn03",
"node_name": "SSkn03",
"required_final_video_viewcount": 0,
"site": "A",
"title": "mT up-date App.",
@ -7302,7 +7302,7 @@
"3": "-1"
},
"media_file": "INS04.STR",
"node_name": "Sskn04",
"node_name": "SSkn04",
"required_final_video_viewcount": 0,
"site": "A",
"title": "mT up-date App.",

View file

@ -8,7 +8,7 @@
"3": "-1"
},
"media_file": "INS05.STR",
"node_name": "Sskn04#",
"node_name": "SSkn04#",
"required_final_video_viewcount": 0,
"site": "B",
"title": "mT up-date App.",
@ -1510,7 +1510,7 @@
"3": "-1"
},
"media_file": "INS06.STR",
"node_name": "Sskn05",
"node_name": "SSkn05",
"required_final_video_viewcount": 0,
"site": "B",
"title": "mT up-date App.",
@ -2968,7 +2968,7 @@
"3": "-1"
},
"media_file": "INS07.STR",
"node_name": "Sskn06",
"node_name": "SSkn06",
"required_final_video_viewcount": 0,
"site": "B",
"title": "mT up-date App.",
@ -5811,4 +5811,4 @@
}
}
}
}
}

View file

@ -28,7 +28,7 @@ const ChangeDiscScene = () => {
const disc2Tex = useLoader(THREE.TextureLoader, disc2);
useEffect(() => {
// setTimeout(() => setScene("main"), 3500);
setTimeout(() => setScene("main"), 3500);
}, [activeSite, setScene]);
return (
@ -70,7 +70,10 @@ const ChangeDiscScene = () => {
</sprite>
<sprite scale={[0.4, 0.7, 0]} position={[1.4, -1.9, 0]}>
<spriteMaterial attach="material" map={disc1Tex} />
<spriteMaterial
attach="material"
map={activeSite === "a" ? disc1Tex : disc2Tex}
/>
</sprite>
</>
);

View file

@ -15,6 +15,7 @@ const IdleMediaScene = () => {
useStore.setState({
currentScene: "main",
idleStarting: false,
intro: false,
});
}, [mediaPercentageElapsed]);

View file

@ -46,32 +46,26 @@ type State = {
activeNodeRot: number[];
activeNodeAttributes: NodeAttributes;
// lain
lainMoveState: string;
canLainMove: boolean;
// site
activeSite: ActiveSite;
siteRot: number[];
oldSiteRot: number[];
// level
activeLevel: string;
oldLevel: string;
// level selection
selectedLevel: number;
// end scene
activeEndComponent: EndComponent;
endSceneSelectionVisible: boolean;
// pause
activePauseComponent: PauseComponent;
pauseExitAnimation: boolean;
showingAbout: boolean;
permissionDenied: boolean;
// media/media scene
audioAnalyser: AudioAnalyser | undefined;
mediaPercentageElapsed: number;
currentMediaSide: MediaSide;
@ -84,39 +78,30 @@ type State = {
mediaWordPosStateIdx: number;
wordSelected: boolean;
// idle scene
idleStarting: boolean;
idleMedia: string;
idleImages: { "1": string; "2": string; "3": string } | undefined;
idleNodeName: string | undefined;
// sskn scene
activeSsknComponent: SsknComponent;
ssknLoading: boolean;
// polytan scene
polytanUnlockedParts: PolytanBodyParts;
// player name
playerName: string;
// boot scene
activeMainMenuComponent: MainMenuComponent;
authorizeUserLetterIdx: number;
bootSubscene: BootSubscene;
// prompt
promptVisible: boolean;
activePromptComponent: PromptComponent;
// status notifiers
loadSuccessful: boolean | undefined;
saveSuccessful: boolean | undefined;
// word not found notification thing
wordNotFound: boolean;
// save state
siteSaveState: SiteSaveState;
inputCooldown: number;
@ -126,7 +111,7 @@ export const useStore = create(
combine(
{
// scene data
currentScene: "change_disc",
currentScene: "main",
// game progress
gameProgress: game_progress,
@ -153,6 +138,7 @@ export const useStore = create(
// lain
lainMoveState: "standing",
canLainMove: true,
// site
activeSite: "a",
@ -324,6 +310,7 @@ export const useStore = create(
gate_level: state.gameProgress.gate_level + 1,
},
})),
loadUserSaveState: (userState: UserSaveState) =>
set(() => ({
siteSaveState: userState.siteSaveState,
@ -362,6 +349,7 @@ export const getMainSceneContext = (): MainSceneContext => {
showingAbout: state.showingAbout,
siteSaveState: state.siteSaveState,
wordNotFound: state.wordNotFound,
canLainMove: state.canLainMove,
};
};

View file

@ -108,6 +108,7 @@ export interface MainSceneContext extends PromptContext {
selectedLevel: number;
wordNotFound: boolean;
siteSaveState: SiteSaveState;
canLainMove: boolean;
}
export type SsknSceneContext = {

16
src/utils/getKeyPress.ts Normal file
View file

@ -0,0 +1,16 @@
const getKeyPress = (keyCode: string) => {
const keyCodeAssocs = {
ArrowDown: "DOWN", // down arrow
ArrowLeft: "LEFT", // left arrow
ArrowUp: "UP", // up arrow
ArrowRight: "RIGHT", // right arrow
x: "CIRCLE", // x key
z: "X", // z key
d: "TRIANGLE", // d key
e: "L2", // e key
v: "START", // v key
};
return keyCodeAssocs[keyCode as keyof typeof keyCodeAssocs];
};
export default getKeyPress;

View file

@ -1,15 +0,0 @@
export const getKeyCodeAssociation = (keyCode: number) => {
const keyCodeAssocs = {
40: "DOWN", // down arrow
37: "LEFT", // left arrow
38: "UP", // up arrow
39: "RIGHT", // right arrow
88: "CIRCLE", // x key
90: "X", // z key
68: "TRIANGLE", // d key
69: "L2", // e key
86: "START", // v key
32: "SPACE",
};
return keyCodeAssocs[keyCode as keyof typeof keyCodeAssocs];
};