mirror of
https://github.com/ad044/lainTSX.git
synced 2024-10-22 23:19:06 +00:00
fixed most typing, more refactoring on starfield
This commit is contained in:
parent
6f5af7dbef
commit
42e73badde
8 changed files with 111 additions and 97 deletions
|
@ -25,6 +25,7 @@ import OrthoCamera from "./OrthoCamera";
|
||||||
import Preloader from "./Preloader";
|
import Preloader from "./Preloader";
|
||||||
import Starfield from "./Starfield";
|
import Starfield from "./Starfield";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
|
import Orb from "./Orb";
|
||||||
|
|
||||||
type KeyCodeAssociations = {
|
type KeyCodeAssociations = {
|
||||||
[keyCode: number]: string;
|
[keyCode: number]: string;
|
||||||
|
@ -312,6 +313,19 @@ const Game = () => {
|
||||||
[isLainMoving, currentSprite, moveDispatcher]
|
[isLainMoving, currentSprite, moveDispatcher]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const doIntro = useCallback(() => {
|
||||||
|
setLainMoving(true);
|
||||||
|
setLainMoveState(<LainIntro />);
|
||||||
|
updateHUD();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setLainMoving(false);
|
||||||
|
setLainMoveState(<LainStanding />);
|
||||||
|
setIsIntro(false);
|
||||||
|
updateHUD();
|
||||||
|
}, (lain_animations as LainAnimations)["intro"]["duration"]);
|
||||||
|
}, [updateHUD]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("keydown", handleKeyPress);
|
window.addEventListener("keydown", handleKeyPress);
|
||||||
|
|
||||||
|
@ -323,37 +337,27 @@ const Game = () => {
|
||||||
};
|
};
|
||||||
}, [handleKeyPress]);
|
}, [handleKeyPress]);
|
||||||
|
|
||||||
const groupRef = useRef();
|
const groupRef = useRef<THREE.Object3D>();
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (isIntro) {
|
if (isIntro) {
|
||||||
if ((groupRef.current as any).rotation.x > 0) {
|
if (groupRef.current!.rotation.x > 0) {
|
||||||
if ((groupRef.current as any).position.z > -1) {
|
if (groupRef.current!.position.z > -1) {
|
||||||
(groupRef.current as any).rotation.x -= 0.015;
|
groupRef.current!.rotation.x -= 0.015;
|
||||||
} else {
|
} else {
|
||||||
(groupRef.current as any).rotation.x -= 0.01;
|
groupRef.current!.rotation.x -= 0.01;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((groupRef.current as any).position.y > 0) {
|
if (groupRef.current!.position.y > 0) {
|
||||||
(groupRef.current as any).position.y -= 0.015;
|
groupRef.current!.position.y -= 0.015;
|
||||||
}
|
}
|
||||||
if ((groupRef.current as any).position.z < 0) {
|
if (groupRef.current!.position.z < 0) {
|
||||||
(groupRef.current as any).position.z += 0.04;
|
groupRef.current!.position.z += 0.04;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(doIntro, []);
|
||||||
setLainMoving(true);
|
|
||||||
setLainMoveState(<LainIntro />);
|
|
||||||
updateHUD();
|
|
||||||
setTimeout(() => {
|
|
||||||
setLainMoving(false);
|
|
||||||
setLainMoveState(<LainStanding />);
|
|
||||||
setIsIntro(false);
|
|
||||||
updateHUD();
|
|
||||||
}, (lain_animations as LainAnimations)["intro"]["duration"]);
|
|
||||||
}, [updateHUD]);
|
|
||||||
|
|
||||||
// pos-z ? => 3
|
// pos-z ? => 3
|
||||||
// rot-x 1.5 => 0
|
// rot-x 1.5 => 0
|
||||||
|
@ -366,7 +370,6 @@ const Game = () => {
|
||||||
>
|
>
|
||||||
<group rotation={[2.3, 0, 0]} position={[0, 1.5, -7.5]} ref={groupRef}>
|
<group rotation={[2.3, 0, 0]} position={[0, 1.5, -7.5]} ref={groupRef}>
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<OrbitControls />
|
|
||||||
<Preloader />
|
<Preloader />
|
||||||
<Hub currentSprite={currentSprite} />
|
<Hub currentSprite={currentSprite} />
|
||||||
<OrthoCamera
|
<OrthoCamera
|
||||||
|
@ -397,6 +400,7 @@ const Game = () => {
|
||||||
/>
|
/>
|
||||||
<Starfield starfieldPosY={starfieldPosY} />
|
<Starfield starfieldPosY={starfieldPosY} />
|
||||||
<Lights />
|
<Lights />
|
||||||
|
<OrbitControls />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</group>
|
</group>
|
||||||
<Lain
|
<Lain
|
||||||
|
|
|
@ -16,16 +16,16 @@ export type HUDElementProps = {
|
||||||
bigHUDType: string;
|
bigHUDType: string;
|
||||||
|
|
||||||
longHUDPosYZ: [number, number];
|
longHUDPosYZ: [number, number];
|
||||||
longHUDPosX: Interpolation<number, any>;
|
longHUDPosX: Interpolation<number, number>;
|
||||||
longHUDScale: PositionAndScaleProps;
|
longHUDScale: PositionAndScaleProps;
|
||||||
|
|
||||||
// boringHudPosition: PositionAndScaleProps;
|
// boringHudPosition: PositionAndScaleProps;
|
||||||
boringHUDPosX: Interpolation<number, any>;
|
boringHUDPosX: Interpolation<number, number>;
|
||||||
boringHUDPosYZ: [number, number];
|
boringHUDPosYZ: [number, number];
|
||||||
boringHUDScale: PositionAndScaleProps;
|
boringHUDScale: PositionAndScaleProps;
|
||||||
|
|
||||||
// bigHudPosition: PositionAndScaleProps;
|
// bigHudPosition: PositionAndScaleProps;
|
||||||
bigHUDPosX: Interpolation<number, any>;
|
bigHUDPosX: Interpolation<number, number>;
|
||||||
bigHUDPosYZ: [number, number];
|
bigHUDPosYZ: [number, number];
|
||||||
bigHUDScale: PositionAndScaleProps;
|
bigHUDScale: PositionAndScaleProps;
|
||||||
};
|
};
|
||||||
|
@ -58,17 +58,17 @@ const HUDElement = memo((props: HUDElementProps) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const longHudTexture: any = useLoader(
|
const longHudTexture = useLoader(
|
||||||
THREE.TextureLoader,
|
THREE.TextureLoader,
|
||||||
spriteTypeToSprite(props.longHUDType, "long")!
|
spriteTypeToSprite(props.longHUDType, "long")!
|
||||||
);
|
);
|
||||||
|
|
||||||
const longHudBoringTexture: any = useLoader(
|
const longHudBoringTexture = useLoader(
|
||||||
THREE.TextureLoader,
|
THREE.TextureLoader,
|
||||||
spriteTypeToSprite(props.boringHUDType, "boring")!
|
spriteTypeToSprite(props.boringHUDType, "boring")!
|
||||||
);
|
);
|
||||||
|
|
||||||
const bigHudTexture: any = useLoader(
|
const bigHudTexture = useLoader(
|
||||||
THREE.TextureLoader,
|
THREE.TextureLoader,
|
||||||
spriteTypeToSprite(props.bigHUDType, "big")!
|
spriteTypeToSprite(props.bigHUDType, "big")!
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,11 @@ import PurpleRing from "./PurpleRing";
|
||||||
export type PositionAndScaleProps = [number, number, number];
|
export type PositionAndScaleProps = [number, number, number];
|
||||||
export type RotationProps = [number, number, number, (string | undefined)?];
|
export type RotationProps = [number, number, number, (string | undefined)?];
|
||||||
|
|
||||||
const Hub = (props: any) => {
|
type HubProps = {
|
||||||
|
currentSprite: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Hub = (props: HubProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Suspense fallback={<>loading...</>}>
|
<Suspense fallback={<>loading...</>}>
|
||||||
|
|
|
@ -46,7 +46,11 @@ const FirstMarq: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Intro = (props: any) => {
|
type IntroProps = {
|
||||||
|
setMoveToGame: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Intro = (props: IntroProps) => {
|
||||||
const [looping, setLooping] = useState(true);
|
const [looping, setLooping] = useState(true);
|
||||||
const [isArrowUp, setIsArrowUp] = useState(true);
|
const [isArrowUp, setIsArrowUp] = useState(true);
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ const LevelSprite = memo((props: LevelSpriteConstructorProps) => {
|
||||||
} as SpriteToPath)[sprite];
|
} as SpriteToPath)[sprite];
|
||||||
};
|
};
|
||||||
|
|
||||||
const materialRef = useRef();
|
const materialRef = useRef<THREE.ShaderMaterial>();
|
||||||
|
|
||||||
const spriteSheet = spriteToPath(props.sprite);
|
const spriteSheet = spriteToPath(props.sprite);
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ const LevelSprite = memo((props: LevelSpriteConstructorProps) => {
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (materialRef.current) {
|
if (materialRef.current) {
|
||||||
(materialRef.current! as any).uniforms.timeMSeconds.value =
|
materialRef.current.uniforms.timeMSeconds.value =
|
||||||
(Date.now() % (Math.PI * 2000)) / 1000.0;
|
(Date.now() % (Math.PI * 2000)) / 1000.0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ type OrbProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Orb = memo((props: OrbProps) => {
|
const Orb = memo((props: OrbProps) => {
|
||||||
const orbRef = useRef();
|
const orbRef = useRef<THREE.Object3D>();
|
||||||
const [orbDirection, setOrbDirection] = useState("left");
|
const [orbDirection, setOrbDirection] = useState("left");
|
||||||
const [currentCurve, setCurrentCurve] = useState("first");
|
const [currentCurve, setCurrentCurve] = useState("first");
|
||||||
|
|
||||||
|
@ -97,11 +97,11 @@ const Orb = memo((props: OrbProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentCurve === "first") {
|
if (currentCurve === "first") {
|
||||||
(orbRef.current as any).position.x = orbPosFirst.x;
|
orbRef.current!.position.x = orbPosFirst.x;
|
||||||
(orbRef.current as any).position.y = orbPosFirst.y;
|
orbRef.current!.position.y = orbPosFirst.y;
|
||||||
} else {
|
} else {
|
||||||
(orbRef.current as any).position.x = orbPosSecond.x;
|
orbRef.current!.position.x = orbPosSecond.x;
|
||||||
(orbRef.current as any).position.y = orbPosSecond.y;
|
orbRef.current!.position.y = orbPosSecond.y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { draco } from "drei";
|
import { draco } from "drei";
|
||||||
import React, { memo, useRef } from "react";
|
import React, { memo, RefObject, useRef } from "react";
|
||||||
import { useFrame, useLoader } from "react-three-fiber";
|
import { useFrame, useLoader } from "react-three-fiber";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||||
|
@ -16,7 +16,7 @@ type GLTFResult = GLTF & {
|
||||||
};
|
};
|
||||||
|
|
||||||
const PurpleRing = memo((props: PurpleRingProps) => {
|
const PurpleRing = memo((props: PurpleRingProps) => {
|
||||||
const purpleRingRef = useRef();
|
const purpleRingRef = useRef<THREE.Object3D>();
|
||||||
|
|
||||||
const { nodes } = useLoader<GLTFResult>(
|
const { nodes } = useLoader<GLTFResult>(
|
||||||
GLTFLoader,
|
GLTFLoader,
|
||||||
|
@ -25,7 +25,7 @@ const PurpleRing = memo((props: PurpleRingProps) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
(purpleRingRef.current as any).rotation.y += 0.01;
|
purpleRingRef.current!.rotation.y += 0.01;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import { Interpolation, a } from "@react-spring/three";
|
import { a, Interpolation } from "@react-spring/three";
|
||||||
import React, { createRef, memo, useMemo, useRef } from "react";
|
import React, { createRef, memo, RefObject, useMemo, useRef } from "react";
|
||||||
import { useFrame } from "react-three-fiber";
|
import { useFrame } from "react-three-fiber";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
type StarRefsAndIncrementors = [
|
||||||
|
React.MutableRefObject<React.RefObject<THREE.Object3D>[]>,
|
||||||
|
number
|
||||||
|
][];
|
||||||
|
|
||||||
type StarfieldProps = {
|
type StarfieldProps = {
|
||||||
starfieldPosY: Interpolation<number, number>;
|
starfieldPosY: Interpolation<number, number>;
|
||||||
};
|
};
|
||||||
|
@ -79,53 +84,50 @@ const Starfield = memo((props: StarfieldProps) => {
|
||||||
posesCyanFromRight,
|
posesCyanFromRight,
|
||||||
posesCyanFromLeft,
|
posesCyanFromLeft,
|
||||||
posesWhiteFromLeft,
|
posesWhiteFromLeft,
|
||||||
].map((poses) => useRef(poses.map(() => createRef())));
|
].map((poses) =>
|
||||||
|
useRef<RefObject<THREE.Object3D>[]>(
|
||||||
|
poses.map(() => createRef<THREE.Object3D>())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// these arrays contain refs to the 3d planes and the increment values that they should move with across
|
||||||
|
// the screen
|
||||||
|
const fromRightStarRefsAndIncrementors: StarRefsAndIncrementors = [
|
||||||
|
[blueFromRightRef, 7.3],
|
||||||
|
[cyanFromRightRef, 4.3],
|
||||||
|
];
|
||||||
|
|
||||||
|
const fromLeftStarRefsAndIncrementors: StarRefsAndIncrementors = [
|
||||||
|
[blueFromLeftRef, 8.3],
|
||||||
|
[cyanFromLeftRef, 3.3],
|
||||||
|
[whiteFromLeftRef, 3.3],
|
||||||
|
];
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
blueFromRightRef.current.forEach((ref) => {
|
// planes (stars) coming from right move to positive X and negative Z direction
|
||||||
if ((ref.current as any).position.x < -1) {
|
fromRightStarRefsAndIncrementors.forEach((el) => {
|
||||||
(ref.current as any).position.x += 7.3;
|
el[0].current.forEach((posRef: RefObject<THREE.Object3D>) => {
|
||||||
(ref.current as any).position.z -= 7.3;
|
if (posRef.current!.position.x < -1) {
|
||||||
|
posRef.current!.position.x += el[1];
|
||||||
|
posRef.current!.position.z -= el[1];
|
||||||
} else {
|
} else {
|
||||||
(ref.current as any).position.x -= 0.03;
|
posRef.current!.position.x -= 0.03;
|
||||||
(ref.current as any).position.z += 0.03;
|
posRef.current!.position.z += 0.03;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
blueFromLeftRef.current.forEach((ref) => {
|
});
|
||||||
if ((ref.current as any).position.x > 3) {
|
|
||||||
(ref.current as any).position.x -= 8.3;
|
// the ones that are coming from left move to negative X and Z
|
||||||
(ref.current as any).position.z -= 8.3;
|
fromLeftStarRefsAndIncrementors.forEach((el) => {
|
||||||
|
el[0].current.forEach((posRef: RefObject<THREE.Object3D>) => {
|
||||||
|
if (posRef.current!.position.x > 3) {
|
||||||
|
posRef.current!.position.x -= el[1];
|
||||||
|
posRef.current!.position.z -= el[1];
|
||||||
} else {
|
} else {
|
||||||
(ref.current as any).position.x += 0.03;
|
posRef.current!.position.x += 0.03;
|
||||||
(ref.current as any).position.z += 0.03;
|
posRef.current!.position.z += 0.03;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cyanFromRightRef.current.forEach((ref) => {
|
|
||||||
if ((ref.current as any).position.x < -1) {
|
|
||||||
(ref.current as any).position.x += 4.3;
|
|
||||||
(ref.current as any).position.z -= 4.3;
|
|
||||||
} else {
|
|
||||||
(ref.current as any).position.x -= 0.03;
|
|
||||||
(ref.current as any).position.z += 0.03;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cyanFromLeftRef.current.forEach((ref) => {
|
|
||||||
if ((ref.current as any).position.x > 3) {
|
|
||||||
(ref.current as any).position.x -= 3.3;
|
|
||||||
(ref.current as any).position.z -= 3.3;
|
|
||||||
} else {
|
|
||||||
(ref.current as any).position.x += 0.03;
|
|
||||||
(ref.current as any).position.z += 0.03;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
whiteFromLeftRef.current.forEach((ref) => {
|
|
||||||
if ((ref.current as any).position.x > 3) {
|
|
||||||
(ref.current as any).position.x -= 3.3;
|
|
||||||
(ref.current as any).position.z -= 3.3;
|
|
||||||
} else {
|
|
||||||
(ref.current as any).position.x += 0.02;
|
|
||||||
(ref.current as any).position.z += 0.02;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -135,10 +137,10 @@ const Starfield = memo((props: StarfieldProps) => {
|
||||||
rotation={[0, 0, 0]}
|
rotation={[0, 0, 0]}
|
||||||
position-y={props.starfieldPosY}
|
position-y={props.starfieldPosY}
|
||||||
>
|
>
|
||||||
{posesBlueFromRight.map((pos: any, idx: number) => {
|
{posesBlueFromRight.map((pos: number[], idx: number) => {
|
||||||
return (
|
return (
|
||||||
<mesh
|
<mesh
|
||||||
ref={(blueFromRightRef.current as any)[idx]}
|
ref={blueFromRightRef.current[idx]}
|
||||||
scale={[0.01, 2, 1]}
|
scale={[0.01, 2, 1]}
|
||||||
rotation={[1.7, 0, 0.9]}
|
rotation={[1.7, 0, 0.9]}
|
||||||
position={[pos[0], pos[1], pos[2]]}
|
position={[pos[0], pos[1], pos[2]]}
|
||||||
|
@ -158,10 +160,10 @@ const Starfield = memo((props: StarfieldProps) => {
|
||||||
</mesh>
|
</mesh>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{posesBlueFromLeft.map((pos: any, idx: number) => {
|
{posesBlueFromLeft.map((pos: number[], idx: number) => {
|
||||||
return (
|
return (
|
||||||
<mesh
|
<mesh
|
||||||
ref={(blueFromLeftRef.current as any)[idx]}
|
ref={blueFromLeftRef.current[idx]}
|
||||||
scale={[0.01, 2, 1]}
|
scale={[0.01, 2, 1]}
|
||||||
rotation={[1.7, 0, -0.9]}
|
rotation={[1.7, 0, -0.9]}
|
||||||
position={[pos[0] - 2.4, pos[1] - 0.5, pos[2]]}
|
position={[pos[0] - 2.4, pos[1] - 0.5, pos[2]]}
|
||||||
|
@ -181,10 +183,10 @@ const Starfield = memo((props: StarfieldProps) => {
|
||||||
</mesh>
|
</mesh>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{posesCyanFromRight.map((pos: any, idx: number) => {
|
{posesCyanFromRight.map((pos: number[], idx: number) => {
|
||||||
return (
|
return (
|
||||||
<mesh
|
<mesh
|
||||||
ref={(cyanFromRightRef.current as any)[idx]}
|
ref={cyanFromRightRef.current[idx]}
|
||||||
scale={[0.01, 0.9, 1]}
|
scale={[0.01, 0.9, 1]}
|
||||||
position={[pos[0] - 1.3, pos[1], pos[2] + 1.5]}
|
position={[pos[0] - 1.3, pos[1], pos[2] + 1.5]}
|
||||||
rotation={[1.7, 0, 0.9]}
|
rotation={[1.7, 0, 0.9]}
|
||||||
|
@ -204,10 +206,10 @@ const Starfield = memo((props: StarfieldProps) => {
|
||||||
</mesh>
|
</mesh>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{posesCyanFromLeft.map((pos: any, idx: number) => {
|
{posesCyanFromLeft.map((pos: number[], idx: number) => {
|
||||||
return (
|
return (
|
||||||
<mesh
|
<mesh
|
||||||
ref={(cyanFromLeftRef.current as any)[idx]}
|
ref={cyanFromLeftRef.current[idx]}
|
||||||
scale={[0.01, 0.9, 1]}
|
scale={[0.01, 0.9, 1]}
|
||||||
position={[pos[0] - 1.3, pos[1], pos[2] + 1.5]}
|
position={[pos[0] - 1.3, pos[1], pos[2] + 1.5]}
|
||||||
rotation={[1.7, 0, -0.9]}
|
rotation={[1.7, 0, -0.9]}
|
||||||
|
@ -227,10 +229,10 @@ const Starfield = memo((props: StarfieldProps) => {
|
||||||
</mesh>
|
</mesh>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{posesWhiteFromLeft.map((pos: any, idx: number) => {
|
{posesWhiteFromLeft.map((pos: number[], idx: number) => {
|
||||||
return (
|
return (
|
||||||
<mesh
|
<mesh
|
||||||
ref={(whiteFromLeftRef.current as any)[idx]}
|
ref={whiteFromLeftRef.current[idx]}
|
||||||
scale={[0.01, 0.9, 1]}
|
scale={[0.01, 0.9, 1]}
|
||||||
position={[pos[0] - 1.3, pos[1] + 0.5, pos[2] + 1.5]}
|
position={[pos[0] - 1.3, pos[1] + 0.5, pos[2] + 1.5]}
|
||||||
rotation={[1.7, 0, -0.9]}
|
rotation={[1.7, 0, -0.9]}
|
||||||
|
|
Loading…
Reference in a new issue