mirror of
https://github.com/ad044/lainTSX.git
synced 2024-10-22 23:19:06 +00:00
added main page and guide, refactored some functions
This commit is contained in:
parent
a2ebca2e53
commit
862f2ed0e8
21 changed files with 506 additions and 72 deletions
83
package-lock.json
generated
83
package-lock.json
generated
|
@ -2109,6 +2109,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz",
|
||||||
"integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA=="
|
"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": {
|
"@types/anymatch": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
|
||||||
|
@ -2156,6 +2161,11 @@
|
||||||
"@babel/types": "^7.3.0"
|
"@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": {
|
"@types/eslint": {
|
||||||
"version": "7.2.6",
|
"version": "7.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz",
|
||||||
|
@ -2307,6 +2317,15 @@
|
||||||
"@types/react-router": "*"
|
"@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": {
|
"@types/resolve": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
|
"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": {
|
"file-uri-to-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
|
||||||
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
|
"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": {
|
"nan": {
|
||||||
"version": "2.14.2",
|
"version": "2.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||||
|
@ -10679,6 +10729,11 @@
|
||||||
"sha.js": "^2.4.8"
|
"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": {
|
"performance-now": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
|
@ -12571,6 +12626,15 @@
|
||||||
"util-deprecate": "^1.0.1"
|
"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": {
|
"readdirp": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
|
"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": {
|
"style-loader": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
"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": {
|
"tough-cookie": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"@types/react-dom": "^16.9.8",
|
"@types/react-dom": "^16.9.8",
|
||||||
"@types/react-router-dom": "^5.1.7",
|
"@types/react-router-dom": "^5.1.7",
|
||||||
"@types/three": "^0.126.0",
|
"@types/three": "^0.126.0",
|
||||||
|
"music-metadata": "^7.8.1",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
|
|
33
scripts/extract/convert_sfx.mjs
Normal file
33
scripts/extract/convert_sfx.mjs
Normal 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();
|
|
@ -3,13 +3,17 @@ import "./static/css/page.css";
|
||||||
import Game from "./dom-components/Game";
|
import Game from "./dom-components/Game";
|
||||||
import { HashRouter, Route, Switch } from "react-router-dom";
|
import { HashRouter, Route, Switch } from "react-router-dom";
|
||||||
import Notes from "./dom-components/Notes";
|
import Notes from "./dom-components/Notes";
|
||||||
|
import MainPage from "./dom-components/MainPage";
|
||||||
|
import Guide from "./dom-components/Guide";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Switch>
|
<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={"/game"} exact component={Game} />
|
||||||
|
<Route path={"/guide"} exact component={Guide} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useFrame } from "react-three-fiber";
|
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 * as audio from "../static/sfx";
|
||||||
import {
|
import {
|
||||||
playIdleAudio,
|
playIdleAudio,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { playAudio, useStore } from "../store";
|
import { useStore } from "../store";
|
||||||
|
import playAudio from "../utils/playAudio";
|
||||||
import sleep from "../utils/sleep";
|
import sleep from "../utils/sleep";
|
||||||
import { GameEvent } from "../types/types";
|
import { GameEvent } from "../types/types";
|
||||||
|
|
||||||
|
|
|
@ -130,14 +130,6 @@ const handleMainSceneInput = (
|
||||||
|
|
||||||
if (!nodeData) return resetInputCooldown;
|
if (!nodeData) return resetInputCooldown;
|
||||||
|
|
||||||
const lainMoveAnimation = `move_${direction}`;
|
|
||||||
const newSiteRot = [
|
|
||||||
0,
|
|
||||||
direction === "left"
|
|
||||||
? siteRotY + Math.PI / 4
|
|
||||||
: siteRotY - Math.PI / 4,
|
|
||||||
0,
|
|
||||||
];
|
|
||||||
const newNode = {
|
const newNode = {
|
||||||
...(nodeData.node !== "unknown"
|
...(nodeData.node !== "unknown"
|
||||||
? getNodeById(nodeData.node, activeSite)
|
? getNodeById(nodeData.node, activeSite)
|
||||||
|
@ -147,6 +139,16 @@ const handleMainSceneInput = (
|
||||||
|
|
||||||
if (nodeData.didMove) {
|
if (nodeData.didMove) {
|
||||||
if (!canLainMove) return resetInputCooldown;
|
if (!canLainMove) return resetInputCooldown;
|
||||||
|
|
||||||
|
const lainMoveAnimation = `move_${direction}`;
|
||||||
|
const newSiteRot = [
|
||||||
|
0,
|
||||||
|
direction === "left"
|
||||||
|
? siteRotY + Math.PI / 4
|
||||||
|
: siteRotY - Math.PI / 4,
|
||||||
|
0,
|
||||||
|
];
|
||||||
|
|
||||||
return siteMoveHorizontal({
|
return siteMoveHorizontal({
|
||||||
lainMoveAnimation: lainMoveAnimation,
|
lainMoveAnimation: lainMoveAnimation,
|
||||||
siteRot: newSiteRot,
|
siteRot: newSiteRot,
|
||||||
|
@ -169,11 +171,6 @@ const handleMainSceneInput = (
|
||||||
|
|
||||||
if (!nodeData) return resetInputCooldown;
|
if (!nodeData) return resetInputCooldown;
|
||||||
|
|
||||||
const lainMoveAnimation = `jump_${direction}`;
|
|
||||||
const newLevel = (direction === "up" ? level + 1 : level - 1)
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0");
|
|
||||||
|
|
||||||
const newNode = {
|
const newNode = {
|
||||||
...(nodeData.node !== "unknown"
|
...(nodeData.node !== "unknown"
|
||||||
? getNodeById(nodeData.node, activeSite)
|
? getNodeById(nodeData.node, activeSite)
|
||||||
|
@ -183,6 +180,12 @@ const handleMainSceneInput = (
|
||||||
|
|
||||||
if (nodeData.didMove) {
|
if (nodeData.didMove) {
|
||||||
if (!canLainMove) return resetInputCooldown;
|
if (!canLainMove) return resetInputCooldown;
|
||||||
|
|
||||||
|
const lainMoveAnimation = `jump_${direction}`;
|
||||||
|
const newLevel = (direction === "up" ? level + 1 : level - 1)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
|
||||||
return siteMoveVertical({
|
return siteMoveVertical({
|
||||||
lainMoveAnimation: lainMoveAnimation,
|
lainMoveAnimation: lainMoveAnimation,
|
||||||
activeLevel: newLevel,
|
activeLevel: newLevel,
|
||||||
|
|
16
src/dom-components/Credit.tsx
Normal file
16
src/dom-components/Credit.tsx
Normal 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;
|
|
@ -20,6 +20,7 @@ import { Canvas } from "react-three-fiber";
|
||||||
import Preloader from "../components/Preloader";
|
import Preloader from "../components/Preloader";
|
||||||
import InputHandler from "../components/InputHandler";
|
import InputHandler from "../components/InputHandler";
|
||||||
import MediaPlayer from "../components/MediaPlayer";
|
import MediaPlayer from "../components/MediaPlayer";
|
||||||
|
import Header from "./Header";
|
||||||
|
|
||||||
const Game = () => {
|
const Game = () => {
|
||||||
const currentScene = useStore((state) => state.currentScene);
|
const currentScene = useStore((state) => state.currentScene);
|
||||||
|
@ -68,29 +69,40 @@ const Game = () => {
|
||||||
};
|
};
|
||||||
}, [handleGameResize]);
|
}, [handleGameResize]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.style.overflowY = "hidden";
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflowY = "visible";
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className="game"
|
<Header />
|
||||||
style={{ width: Math.round(width), height: Math.round(height) }}
|
<div
|
||||||
>
|
className="game"
|
||||||
<Canvas
|
style={{ width: Math.round(width), height: Math.round(height) }}
|
||||||
concurrent
|
|
||||||
gl={{ antialias: false }}
|
|
||||||
pixelRatio={window.devicePixelRatio}
|
|
||||||
className="main-canvas"
|
|
||||||
>
|
>
|
||||||
<Suspense fallback={null}>
|
<Canvas
|
||||||
<Preloader />
|
concurrent
|
||||||
{dispatchScene[currentScene as keyof typeof dispatchScene]}
|
gl={{ antialias: false }}
|
||||||
<InputHandler />
|
pixelRatio={window.devicePixelRatio}
|
||||||
</Suspense>
|
className="main-canvas"
|
||||||
</Canvas>
|
>
|
||||||
{["media", "idle_media", "tak", "end"].includes(currentScene) && (
|
<Suspense fallback={null}>
|
||||||
<div style={{ marginTop: -height }}>
|
<Preloader />
|
||||||
<MediaPlayer />
|
{dispatchScene[currentScene as keyof typeof dispatchScene]}
|
||||||
</div>
|
<InputHandler />
|
||||||
)}
|
</Suspense>
|
||||||
</div>
|
</Canvas>
|
||||||
|
{["media", "idle_media", "tak", "end"].includes(currentScene) && (
|
||||||
|
<div style={{ marginTop: -height }}>
|
||||||
|
<MediaPlayer />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
100
src/dom-components/Guide.tsx
Normal file
100
src/dom-components/Guide.tsx
Normal 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;
|
17
src/dom-components/Header.tsx
Normal file
17
src/dom-components/Header.tsx
Normal 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;
|
116
src/dom-components/MainPage.tsx
Normal file
116
src/dom-components/MainPage.tsx
Normal 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;
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import "../static/css/notes.css";
|
import "../static/css/notes.css";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import Header from "./Header";
|
||||||
|
|
||||||
const Notes = () => {
|
const Notes = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -9,6 +10,7 @@ const Notes = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Header />
|
||||||
<table className="main-table">
|
<table className="main-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -23,6 +25,25 @@ const Notes = () => {
|
||||||
This is especially true if you're using a bad setup, and even
|
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
|
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.
|
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>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -57,19 +78,17 @@ const Notes = () => {
|
||||||
<p>Browser Settings</p>
|
<p>Browser Settings</p>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<p>
|
<span className="text-center">Firefox</span>
|
||||||
<span className="text-center">Firefox</span>
|
<p className="browser-notes">
|
||||||
<div className="browser-notes">
|
privacy.resistFingerprinting should be set to false (it should
|
||||||
privacy.resistFingerprinting should be set to false (it should
|
be by default). Otherwise, it limits the maximum WebGL texture
|
||||||
be by default). Otherwise, it limits the maximum WebGL texture
|
size to 2048, resulting in poor sprite quality.
|
||||||
size to 2048, resulting in poor sprite quality.
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
Picture-In-Picture functionality should not be used (you most
|
||||||
Picture-In-Picture functionality should not be used (you most
|
likely have it disabled already). Just having it enabled won't
|
||||||
likely have it disabled already). Just having it enabled won't
|
break anything, but actually using it might lead to some funny
|
||||||
break anything, but actually using it might lead to some funny
|
visual bugs with media files.
|
||||||
visual bugs with media files.
|
|
||||||
</div>
|
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
21
src/dom-components/QA.tsx
Normal file
21
src/dom-components/QA.tsx
Normal 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;
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useFrame } from "react-three-fiber";
|
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 EndSelectionScreen from "../components/EndScene/EndSelectionScreen";
|
||||||
import introSpeech from "../static/media/audio/LAIN21.XA[31].mp4";
|
import introSpeech from "../static/media/audio/LAIN21.XA[31].mp4";
|
||||||
import outroSpeech from "../static/media/audio/LAIN21.XA[16].mp4";
|
import outroSpeech from "../static/media/audio/LAIN21.XA[16].mp4";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { Suspense, useEffect, useRef, useState } from "react";
|
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 LevelSelection from "../components/MainScene/LevelSelection";
|
||||||
import HUD from "../components/MainScene/HUD";
|
import HUD from "../components/MainScene/HUD";
|
||||||
import MainYellowTextAnimator from "../components/TextRenderer/MainYellowTextAnimator";
|
import MainYellowTextAnimator from "../components/TextRenderer/MainYellowTextAnimator";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
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 LeftSide from "../components/MediaScene/Selectables/LeftSide";
|
||||||
import RightSide from "../components/MediaScene/Selectables/RightSide";
|
import RightSide from "../components/MediaScene/Selectables/RightSide";
|
||||||
import AudioVisualizer from "../components/MediaScene/AudioVisualizer/AudioVisualizer";
|
import AudioVisualizer from "../components/MediaScene/AudioVisualizer/AudioVisualizer";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import LainSpeak from "../components/LainSpeak";
|
import LainSpeak from "../components/LainSpeak";
|
||||||
import { createAudioAnalyser, useStore } from "../store";
|
import { useStore } from "../store";
|
||||||
|
import createAudioAnalyser from "../utils/createAudioAnalyser";
|
||||||
|
|
||||||
const TaKScene = () => {
|
const TaKScene = () => {
|
||||||
const setScene = useStore((state) => state.setScene);
|
const setScene = useStore((state) => state.setScene);
|
||||||
|
|
18
src/store.ts
18
src/store.ts
|
@ -1,6 +1,5 @@
|
||||||
import create from "zustand";
|
import create from "zustand";
|
||||||
import { combine } from "zustand/middleware";
|
import { combine } from "zustand/middleware";
|
||||||
import * as THREE from "three";
|
|
||||||
import { AudioAnalyser } from "three";
|
import { AudioAnalyser } from "three";
|
||||||
import game_progress from "./resources/initial_progress.json";
|
import game_progress from "./resources/initial_progress.json";
|
||||||
import { getNodeById } from "./helpers/node-helpers";
|
import { getNodeById } from "./helpers/node-helpers";
|
||||||
|
@ -445,23 +444,6 @@ export const getCurrentUserState = (): UserSaveState => {
|
||||||
export const saveUserProgress = (state: UserSaveState) =>
|
export const saveUserProgress = (state: UserSaveState) =>
|
||||||
localStorage.setItem("lainSaveState", JSON.stringify(state));
|
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 = () => {
|
export const isPolytanFullyUnlocked = () => {
|
||||||
const polytanProgress = useStore.getState().polytanUnlockedParts;
|
const polytanProgress = useStore.getState().polytanUnlockedParts;
|
||||||
|
|
||||||
|
|
13
src/utils/createAudioAnalyser.ts
Normal file
13
src/utils/createAudioAnalyser.ts
Normal 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
8
src/utils/playAudio.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const playAudio = (audio: HTMLAudioElement) => {
|
||||||
|
audio.currentTime = 0;
|
||||||
|
audio.volume = 0.5;
|
||||||
|
audio.loop = false;
|
||||||
|
audio.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default playAudio;
|
Loading…
Reference in a new issue