added main page and guide, refactored some functions

This commit is contained in:
ad044 2021-03-23 18:23:25 +04:00
parent a2ebca2e53
commit 862f2ed0e8
21 changed files with 506 additions and 72 deletions

83
package-lock.json generated
View file

@ -2109,6 +2109,11 @@
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz",
"integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA=="
},
"@tokenizer/token": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.1.1.tgz",
"integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w=="
},
"@types/anymatch": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@ -2156,6 +2161,11 @@
"@babel/types": "^7.3.0"
}
},
"@types/debug": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
"integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ=="
},
"@types/eslint": {
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz",
@ -2307,6 +2317,15 @@
"@types/react-router": "*"
}
},
"@types/readable-stream": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.9.tgz",
"integrity": "sha512-sqsgQqFT7HmQz/V5jH1O0fvQQnXAJO46Gg9LRO/JPfjmVmGUlcx831TZZO3Y3HtWhIkzf3kTsNT0Z0kzIhIvZw==",
"requires": {
"@types/node": "*",
"safe-buffer": "*"
}
},
"@types/resolve": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
@ -6595,6 +6614,17 @@
}
}
},
"file-type": {
"version": "16.3.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.3.0.tgz",
"integrity": "sha512-ZA0hV64611vJT42ltw0T9IDwHApQuxRdrmQZWTeDmeAUtZBBVSQW3nSQqhhW1cAgpXgqcJvm410BYHXJQ9AymA==",
"requires": {
"readable-web-to-node-stream": "^3.0.0",
"strtok3": "^6.0.3",
"token-types": "^2.0.0",
"typedarray-to-buffer": "^3.1.5"
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@ -9963,6 +9993,26 @@
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
},
"music-metadata": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-7.8.1.tgz",
"integrity": "sha512-Q4PhR788jp2VDh/JgPvEUZ3NCqxgW4+hV0H2XrYKfGwq5aGagm1ek9kbCJvUCvejVnmyn6Rb+ggIPxrIVPfzww==",
"requires": {
"content-type": "^1.0.4",
"debug": "^4.3.1",
"file-type": "^16.2.0",
"media-typer": "^1.1.0",
"strtok3": "^6.0.8",
"token-types": "^2.1.1"
},
"dependencies": {
"media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="
}
}
},
"nan": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
@ -10679,6 +10729,11 @@
"sha.js": "^2.4.8"
}
},
"peek-readable": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-3.1.3.tgz",
"integrity": "sha512-mpAcysyRJxmICBcBa5IXH7SZPvWkcghm6Fk8RekoS3v+BpbSzlZzuWbMx+GXrlUwESi9qHar4nVEZNMKylIHvg=="
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@ -12571,6 +12626,15 @@
"util-deprecate": "^1.0.1"
}
},
"readable-web-to-node-stream": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.1.tgz",
"integrity": "sha512-4zDC6CvjUyusN7V0QLsXVB7pJCD9+vtrM9bYDRv6uBQ+SKfx36rp5AFNPRgh9auKRul/a1iFZJYXcCbwRL+SaA==",
"requires": {
"@types/readable-stream": "^2.3.9",
"readable-stream": "^3.6.0"
}
},
"readdirp": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
@ -14406,6 +14470,16 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
},
"strtok3": {
"version": "6.0.8",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.0.8.tgz",
"integrity": "sha512-QLgv+oiXwXgCgp2PdPPa+Jpp4D9imK9e/0BsyfeFMr6QL6wMVqoVn9+OXQ9I7MZbmUzN6lmitTJ09uwS2OmGcw==",
"requires": {
"@tokenizer/token": "^0.1.1",
"@types/debug": "^4.1.5",
"peek-readable": "^3.1.3"
}
},
"style-loader": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz",
@ -14859,6 +14933,15 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"token-types": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-2.1.1.tgz",
"integrity": "sha512-wnQcqlreS6VjthyHO3Y/kpK/emflxDBNhlNUPfh7wE39KnuDdOituXomIbyI79vBtF0Ninpkh72mcuRHo+RG3Q==",
"requires": {
"@tokenizer/token": "^0.1.1",
"ieee754": "^1.2.1"
}
},
"tough-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",

View file

@ -13,6 +13,7 @@
"@types/react-dom": "^16.9.8",
"@types/react-router-dom": "^5.1.7",
"@types/three": "^0.126.0",
"music-metadata": "^7.8.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",

View file

@ -0,0 +1,33 @@
import { readdirSync } from "fs";
import { spawnSync } from "child_process";
import { join } from "path";
import * as mm from "music-metadata";
// stub implementation of upping the pitch of the sfx, still wip
export async function convert_sfx() {
let i = 0;
const dir = join("..", "..", "src", "static", "sfx");
for (let file of readdirSync(dir)) {
if (file.includes("snd")) {
console.log(file);
const metaData = await mm.parseFile(`${join(dir, file)}`);
const sampleRate = metaData.format.sampleRate;
spawnSync(
"ffmpeg",
[
"-i",
join(dir, file),
"-filter_complex",
`asetrate=${sampleRate}*2^(6/12),atempo=1/2^(6/12)`,
"-n",
join(dir, "..", "t", file),
],
{ stdio: "inherit" }
);
}
}
}
convert_sfx();

View file

@ -3,13 +3,17 @@ import "./static/css/page.css";
import Game from "./dom-components/Game";
import { HashRouter, Route, Switch } from "react-router-dom";
import Notes from "./dom-components/Notes";
import MainPage from "./dom-components/MainPage";
import Guide from "./dom-components/Guide";
const App = () => {
return (
<HashRouter>
<Switch>
<Route path={"/"} exact component={Notes} />
<Route path={"/"} exact component={MainPage} />
<Route path={"/notes"} exact component={Notes} />
<Route path={"/game"} exact component={Game} />
<Route path={"/guide"} exact component={Guide} />
</Switch>
</HashRouter>
);

View file

@ -1,5 +1,6 @@
import { useFrame } from "react-three-fiber";
import { playAudio, useStore } from "../store";
import { useStore } from "../store";
import playAudio from "../utils/playAudio";
import * as audio from "../static/sfx";
import {
playIdleAudio,

View file

@ -1,4 +1,5 @@
import { playAudio, useStore } from "../store";
import { useStore } from "../store";
import playAudio from "../utils/playAudio";
import sleep from "../utils/sleep";
import { GameEvent } from "../types/types";

View file

@ -130,14 +130,6 @@ const handleMainSceneInput = (
if (!nodeData) return resetInputCooldown;
const lainMoveAnimation = `move_${direction}`;
const newSiteRot = [
0,
direction === "left"
? siteRotY + Math.PI / 4
: siteRotY - Math.PI / 4,
0,
];
const newNode = {
...(nodeData.node !== "unknown"
? getNodeById(nodeData.node, activeSite)
@ -147,6 +139,16 @@ const handleMainSceneInput = (
if (nodeData.didMove) {
if (!canLainMove) return resetInputCooldown;
const lainMoveAnimation = `move_${direction}`;
const newSiteRot = [
0,
direction === "left"
? siteRotY + Math.PI / 4
: siteRotY - Math.PI / 4,
0,
];
return siteMoveHorizontal({
lainMoveAnimation: lainMoveAnimation,
siteRot: newSiteRot,
@ -169,11 +171,6 @@ const handleMainSceneInput = (
if (!nodeData) return resetInputCooldown;
const lainMoveAnimation = `jump_${direction}`;
const newLevel = (direction === "up" ? level + 1 : level - 1)
.toString()
.padStart(2, "0");
const newNode = {
...(nodeData.node !== "unknown"
? getNodeById(nodeData.node, activeSite)
@ -183,6 +180,12 @@ const handleMainSceneInput = (
if (nodeData.didMove) {
if (!canLainMove) return resetInputCooldown;
const lainMoveAnimation = `jump_${direction}`;
const newLevel = (direction === "up" ? level + 1 : level - 1)
.toString()
.padStart(2, "0");
return siteMoveVertical({
lainMoveAnimation: lainMoveAnimation,
activeLevel: newLevel,

View file

@ -0,0 +1,16 @@
import React from "react";
type CreditProps = {
name: string;
credit: string;
};
const Credit = (props: CreditProps) => (
<>
<span className="cool-text">{props.name}</span> - {props.credit}
<br />
<br />
</>
);
export default Credit;

View file

@ -20,6 +20,7 @@ import { Canvas } from "react-three-fiber";
import Preloader from "../components/Preloader";
import InputHandler from "../components/InputHandler";
import MediaPlayer from "../components/MediaPlayer";
import Header from "./Header";
const Game = () => {
const currentScene = useStore((state) => state.currentScene);
@ -68,29 +69,40 @@ const Game = () => {
};
}, [handleGameResize]);
useEffect(() => {
document.body.style.overflowY = "hidden";
return () => {
document.body.style.overflowY = "visible";
};
}, []);
return (
<div
className="game"
style={{ width: Math.round(width), height: Math.round(height) }}
>
<Canvas
concurrent
gl={{ antialias: false }}
pixelRatio={window.devicePixelRatio}
className="main-canvas"
<>
<Header />
<div
className="game"
style={{ width: Math.round(width), height: Math.round(height) }}
>
<Suspense fallback={null}>
<Preloader />
{dispatchScene[currentScene as keyof typeof dispatchScene]}
<InputHandler />
</Suspense>
</Canvas>
{["media", "idle_media", "tak", "end"].includes(currentScene) && (
<div style={{ marginTop: -height }}>
<MediaPlayer />
</div>
)}
</div>
<Canvas
concurrent
gl={{ antialias: false }}
pixelRatio={window.devicePixelRatio}
className="main-canvas"
>
<Suspense fallback={null}>
<Preloader />
{dispatchScene[currentScene as keyof typeof dispatchScene]}
<InputHandler />
</Suspense>
</Canvas>
{["media", "idle_media", "tak", "end"].includes(currentScene) && (
<div style={{ marginTop: -height }}>
<MediaPlayer />
</div>
)}
</div>
</>
);
};

View file

@ -0,0 +1,100 @@
import React from "react";
import Header from "./Header";
import "../static/css/guide.css";
const Guide = () => {
return (
<>
<Header />
<p className="guide-paragraph">
<br /> A bit of a disclaimer - as stated on the main page, these are
entirely my thoughts from observations while developing the game, some
of the information may be innacurate. <br /> <br />
First, let's get this out of the way - Serial Experiments Lain PSX isn't
a "game" in a traditional sense, it's more like a visual novel which you
piece together yourself. The story revolves around Lain and her
interactions with her psychiatrist - Touko.
<br /> <br />A common misconception about the game is that there's no
specific order, and that you just randomly watch stuff and come up with
an explanation yourself. From what I've noticed, this is not entirely
true. Let me explain:
<br />
<br />
The blue orbs that you navigate through (we'll call those blue orbs
"nodes" from now on) contain either:
<br />
<br />
A. Media (audio/video)
<br />
B. Collectibles
<br />
C. Upgrades
<br />
<br />
There are also multiple "types" of nodes. You can tell them apart by
their names and icons.
<br />
Here's a basic list of these nodes according to their names in their
respective categories:
<br />
<br />A category (Media) - Tda, Lda, Dia, Cou, TaK, Dc
<br />B category (Collectibles) - P2
<br />C category (Upgrades) - SSkn, GaTE
<br /> <br />
Let's step through each of these one by one:
<br />
<br />
Category A - Media:
<br /> Tda - Touko's diary - Touko's personal thoughts.
<br />
Lda - Lain's diary - Lain's personal thoughts.
<br />
Dia - Diagnosis - Lain's diagnosis after their interactions, provided by
Touko.
<br />
Cou - Counseling - Lain and Touko's interactions.
<br />
TaK - Not sure what TaK stands for - Random quotes by Lain.
<br />
Dc - Videos.
<br />
<br />
This is where the no-real-order issue comes into play - each of these
separate types are put in chronological order, meaning After Lda001
comes Lda002, then Lda003, etc. This is identical for every other node
type I mentioned. <br /> <br />
What might lead some people to believe that there is no real order is
that the way you UNLOCK them may not be entirely chronological, for
example, the first node you'll most likely interact with from Tda is
Tda028, since that's the closest to where you start from at the
beginning of the game.
<br /> <br />
There's also another issue - despite these separate types being put in a
specific order, it is unclear how they interact with each other. For
example, there is no way (to my knowledge) to tell which Cou comes after
which Lda judging by their names alone. What might help here are the
"words" you select while you play them (on the right hand side there are
3 floating things on each audio node which you can select).
<br /> <br />
Category B - Collectibles: <br />
P2 - Polytan - You collect parts of Lain's bear, after you collect all
the pieces, 2 extra idle media files become available.
<br /> <br />
Category C - Upgrades:
<br />
SSkn - Not sure what SSkn stands for - The main upgrade inside the game.
Some nodes have an "upgrade requirement" that you need to meet to be
able to view them, the way you do that is by collecting these. So, the
next time you see Lain try to knock on a node and fall over, know that
you need to collect more SSkn nodes.
<br />
GaTE - Gate (I guess) - A "gate pass" as the game calls it - after
collecting all of them, you unlock Site B, which contains
more nodes and is the place where you continue the story.
<br /> <br />
</p>
</>
);
};
export default Guide;

View file

@ -0,0 +1,17 @@
import React from "react";
import { Link } from "react-router-dom";
import "../static/css/header.css";
const Header = () => {
return (
<div className="header">
<Link to="/">main</Link>
<Link to="/notes">notes</Link>
<Link to="/game">start</Link>
<Link to="/guide">guide</Link>
<a href="https://discord.com/invite/W22Ga2R">discord</a>
</div>
);
};
export default Header;

View file

@ -0,0 +1,116 @@
import React from "react";
import { Link } from "react-router-dom";
import "../static/css/mainpage.css";
import Credit from "./Credit";
import Header from "./Header";
import QA from "./QA";
const MainPage = () => {
return (
<>
<Header />
<p className="header-paragraph">
This is a web reimplementation of the Serial Experiments Lain PSX game
with the aim to provide multi-language support.
<br />
Please make sure to read the <Link to="/notes">notes</Link> before you
start playing.
<br />
<br />
</p>
<p className="faq">
FAQ:
<br /> <br />
<QA
question={"I'm confused about the game"}
answer={"Amazing! That means the game is working properly."}
/>
<QA
question={
"I'm extremely confused about the game and I'm not sure what I'm doing"
}
answer={`Read the <a href="/#/guide">guide</a>. Keep in mind though that this is only my interpretation of the game and what I pieced together while developing it, it could be wrong.`}
/>
<QA
question={"Source code?"}
answer={`<a href="https://github.com/ad044/lain-psx-ts">On my github.</a>`}
/>
<QA
question={"I found an issue/have a suggestion/etc."}
answer={`Please join our <a href="https://discord.com/invite/W22Ga2R">discord server</a> and tell us about it!`}
/>
</p>
<h2 className="mainpage-header">credits</h2>
<p className="credits">
<Credit
name={"m35"}
credit={"Created jPSXdec/laintools, reverse engineering."}
/>
<Credit name={"ad"} credit={"Main dev/project lead."} />
<Credit
name={"elliotcraft79"}
credit={
"Programming help, reverse engineering, voice/sound effect extraction, extraction automation script, subtitle timing."
}
/>
<Credit
name={"Yukkuri"}
credit={
"Programming/GLSL help, asset extraction, reverse engineering."
}
/>
<Credit
name={"spaztron64"}
credit={"Reverse engineering, sound extraction."}
/>
<Credit name={"Popcorn"} credit={"Programming help."} />
<Credit
name={"lelenium"}
credit={"Helped with literally everything."}
/>
<Credit name={"Bunbuns"} credit={"Fonts, help with japanese."} />
<Credit name={"Phenomenal"} credit={"Help with 3D stuff, fonts."} />
<Credit name={"oo"} credit={"Help with japanese."} />
<Credit name={"JToke"} credit={"Help with shaders."} />
<Credit name={"retard"} credit={"Made 3D models."} />
<Credit name={"knobluch"} credit={"Made 3D models."} />
<Credit name={"ridderhoff"} credit={"Help with 3D stuff."} />
<Credit
name={"claire"}
credit={"Helped with japanese and Lain's voice."}
/>
<Credit name={"Lorenzo"} credit={"Majority of the subtitle timing."} />
<Credit
name={"mutronics"}
credit={"Subtitle timing, translation, owner of laingame."}
/>
<Credit name={"Shuji"} credit={"Translation."} />
<Credit
name={"Magikarp"}
credit={"Provided initial models for the rings."}
/>
<Credit
name={
"mutronics, lelenium, Lorenzo, elliotcraft79, CosmicKiwii, Mikix, shemishtameshel, espilya, Yokuba, oo, Shuji, Bunbuns, claire, Eternofímero, Cal, Cena"
}
credit={"Subtitle timing team."}
/>{" "}
<Credit
name={"psx.lain.pl team"}
credit={"providing the base translation."}
/>{" "}
<Credit
name={"INITIATE"}
credit={"helping the project gain recognition initially."}
/>{" "}
Special thanks to
<a href="https://twitter.com/pmndrs"> Poimandres</a> for answering all
the dumb questions I had while programming and creating the amazing
libraries used in this project.
</p>
<br />
</>
);
};
export default MainPage;

View file

@ -1,6 +1,7 @@
import React, { useEffect } from "react";
import "../static/css/notes.css";
import { Link } from "react-router-dom";
import Header from "./Header";
const Notes = () => {
useEffect(() => {
@ -9,6 +10,7 @@ const Notes = () => {
return (
<>
<Header />
<table className="main-table">
<tbody>
<tr>
@ -23,6 +25,25 @@ const Notes = () => {
This is especially true if you're using a bad setup, and even
more true if you're using Linux on a bad setup, since Firefox's
WebGL implementation on it has had issues for a while now.
<br />
<br />
If it's your first time playing the game, the first time loading
it might take a while depending on the factors mentioned above.
If you're seeing a black screen for a bit, just wait it out.
Subsequent website visits will be much faster once the browser
caches all the assets.
</p>
</td>
</tr>
<tr>
<td>
<p>Sounds</p>
</td>
<td>
<p>
Browsers require user permission to autoplay audio. If you're
not hearing any sound effects, just click somewhere around the
page.
</p>
</td>
</tr>
@ -57,19 +78,17 @@ const Notes = () => {
<p>Browser Settings</p>
</td>
<td>
<p>
<span className="text-center">Firefox</span>
<div className="browser-notes">
privacy.resistFingerprinting should be set to false (it should
be by default). Otherwise, it limits the maximum WebGL texture
size to 2048, resulting in poor sprite quality.
<br />
<br />
Picture-In-Picture functionality should not be used (you most
likely have it disabled already). Just having it enabled won't
break anything, but actually using it might lead to some funny
visual bugs with media files.
</div>
<span className="text-center">Firefox</span>
<p className="browser-notes">
privacy.resistFingerprinting should be set to false (it should
be by default). Otherwise, it limits the maximum WebGL texture
size to 2048, resulting in poor sprite quality.
<br />
<br />
Picture-In-Picture functionality should not be used (you most
likely have it disabled already). Just having it enabled won't
break anything, but actually using it might lead to some funny
visual bugs with media files.
</p>
</td>
</tr>

21
src/dom-components/QA.tsx Normal file
View file

@ -0,0 +1,21 @@
import React from "react";
type QAProps = {
question: string;
answer: string;
};
const QA = (props: QAProps) => (
<>
Q:{" "}
<span
className="cool-text"
dangerouslySetInnerHTML={{ __html: props.question }}
></span>
<br />
A: <span dangerouslySetInnerHTML={{ __html: props.answer }}></span>
<br /> <br />
</>
);
export default QA;

View file

@ -1,7 +1,8 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import * as THREE from "three";
import { useFrame } from "react-three-fiber";
import { createAudioAnalyser, useStore } from "../store";
import { useStore } from "../store";
import createAudioAnalyser from "../utils/createAudioAnalyser";
import EndSelectionScreen from "../components/EndScene/EndSelectionScreen";
import introSpeech from "../static/media/audio/LAIN21.XA[31].mp4";
import outroSpeech from "../static/media/audio/LAIN21.XA[16].mp4";

View file

@ -1,5 +1,6 @@
import React, { Suspense, useEffect, useRef, useState } from "react";
import { playAudio, useStore } from "../store";
import { useStore } from "../store";
import playAudio from "../utils/playAudio";
import LevelSelection from "../components/MainScene/LevelSelection";
import HUD from "../components/MainScene/HUD";
import MainYellowTextAnimator from "../components/TextRenderer/MainYellowTextAnimator";

View file

@ -1,5 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { createAudioAnalyser, useStore } from "../store";
import { useStore } from "../store";
import createAudioAnalyser from "../utils/createAudioAnalyser";
import LeftSide from "../components/MediaScene/Selectables/LeftSide";
import RightSide from "../components/MediaScene/Selectables/RightSide";
import AudioVisualizer from "../components/MediaScene/AudioVisualizer/AudioVisualizer";

View file

@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import LainSpeak from "../components/LainSpeak";
import { createAudioAnalyser, useStore } from "../store";
import { useStore } from "../store";
import createAudioAnalyser from "../utils/createAudioAnalyser";
const TaKScene = () => {
const setScene = useStore((state) => state.setScene);

View file

@ -1,6 +1,5 @@
import create from "zustand";
import { combine } from "zustand/middleware";
import * as THREE from "three";
import { AudioAnalyser } from "three";
import game_progress from "./resources/initial_progress.json";
import { getNodeById } from "./helpers/node-helpers";
@ -445,23 +444,6 @@ export const getCurrentUserState = (): UserSaveState => {
export const saveUserProgress = (state: UserSaveState) =>
localStorage.setItem("lainSaveState", JSON.stringify(state));
export const playAudio = (audio: HTMLAudioElement) => {
audio.currentTime = 0;
audio.volume = 0.5;
audio.loop = false;
audio.play();
};
export const createAudioAnalyser = () => {
const mediaElement = document.getElementById("media") as HTMLMediaElement;
const listener = new THREE.AudioListener();
const audio = new THREE.Audio(listener);
audio.setMediaElementSource(mediaElement);
return new THREE.AudioAnalyser(audio, 2048);
};
export const isPolytanFullyUnlocked = () => {
const polytanProgress = useStore.getState().polytanUnlockedParts;

View file

@ -0,0 +1,13 @@
import * as THREE from "three";
const createAudioAnalyser = () => {
const mediaElement = document.getElementById("media") as HTMLMediaElement;
const listener = new THREE.AudioListener();
const audio = new THREE.Audio(listener);
audio.setMediaElementSource(mediaElement);
return new THREE.AudioAnalyser(audio, 2048);
};
export default createAudioAnalyser;

8
src/utils/playAudio.ts Normal file
View file

@ -0,0 +1,8 @@
const playAudio = (audio: HTMLAudioElement) => {
audio.currentTime = 0;
audio.volume = 0.5;
audio.loop = false;
audio.play();
};
export default playAudio;