mirror of
https://github.com/ad044/lainTSX.git
synced 2024-10-22 23:19:06 +00:00
prompts for pause
This commit is contained in:
parent
8f303e455a
commit
8a1eedb63d
12 changed files with 258 additions and 98 deletions
|
@ -1,15 +1,18 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import * as THREE from "three";
|
||||
import PauseSquare from "./PauseSquare";
|
||||
import StaticBigLetter from "../../TextRenderer/StaticBigLetter";
|
||||
import PauseBigLetter from "../../TextRenderer/PauseBigLetter";
|
||||
import { useStore } from "../../../store";
|
||||
import { useLoader } from "react-three-fiber";
|
||||
import About from "./About";
|
||||
import Prompt from "../../Prompt";
|
||||
import PermissionDenied from "./PermissionDenied";
|
||||
|
||||
const Pause = () => {
|
||||
const exit = useStore((state) => state.pauseExitAnimation);
|
||||
const showingAbout = useStore((state) => state.showingAbout);
|
||||
const promptVisible = useStore((state) => state.promptVisible);
|
||||
const permissionDenied = useStore((state) => state.permissionDenied);
|
||||
const [showActiveComponent, setShowActiveComponent] = useState(false);
|
||||
const [animation, setAnimation] = useState(false);
|
||||
const [intro, setIntro] = useState(true);
|
||||
|
@ -85,7 +88,7 @@ const Pause = () => {
|
|||
if (rowIdx === 5 && col > 0 && col < 5) {
|
||||
return col === 1 ? (
|
||||
<React.Fragment key={`Lfragment`}>
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={"white"}
|
||||
letter={"L"}
|
||||
letterIdx={col}
|
||||
|
@ -119,7 +122,7 @@ const Pause = () => {
|
|||
} else if (rowIdx === 4 && col > 4 && col < 7) {
|
||||
return col === 5 ? (
|
||||
<React.Fragment key={"AFragment"}>
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={"white"}
|
||||
letter={"A"}
|
||||
letterIdx={col}
|
||||
|
@ -153,7 +156,7 @@ const Pause = () => {
|
|||
} else if (rowIdx === 3 && col > 2 && col < 7) {
|
||||
return col === 3 ? (
|
||||
<React.Fragment key={"CFragment"}>
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={"white"}
|
||||
letter={"C"}
|
||||
letterIdx={col}
|
||||
|
@ -187,7 +190,7 @@ const Pause = () => {
|
|||
} else if (rowIdx === 1 && col > 0 && col < 5) {
|
||||
return col === 1 ? (
|
||||
<React.Fragment key={"Sfragment"}>
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={"white"}
|
||||
letter={"S"}
|
||||
letterIdx={col}
|
||||
|
@ -221,7 +224,7 @@ const Pause = () => {
|
|||
} else if (rowIdx === 0 && col > 4 && col < 7) {
|
||||
return col === 5 ? (
|
||||
<React.Fragment key={"Efragment"}>
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={"white"}
|
||||
letter={"E"}
|
||||
letterIdx={col}
|
||||
|
@ -267,7 +270,7 @@ const Pause = () => {
|
|||
})
|
||||
)}
|
||||
{"Load".split("").map((letter, idx) => (
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={idx > 0 ? "yellow" : "orange"}
|
||||
letter={letter}
|
||||
letterIdx={idx}
|
||||
|
@ -278,7 +281,7 @@ const Pause = () => {
|
|||
/>
|
||||
))}
|
||||
{"About".split("").map((letter, idx) => (
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={idx > 0 ? "yellow" : "orange"}
|
||||
letter={letter}
|
||||
letterIdx={idx}
|
||||
|
@ -289,7 +292,7 @@ const Pause = () => {
|
|||
/>
|
||||
))}
|
||||
{"Change".split("").map((letter, idx) => (
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={idx > 0 ? "yellow" : "orange"}
|
||||
letter={letter}
|
||||
letterIdx={idx}
|
||||
|
@ -300,7 +303,7 @@ const Pause = () => {
|
|||
/>
|
||||
))}
|
||||
{"Save".split("").map((letter, idx) => (
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={idx > 0 ? "yellow" : "orange"}
|
||||
letter={letter}
|
||||
letterIdx={idx}
|
||||
|
@ -311,7 +314,7 @@ const Pause = () => {
|
|||
/>
|
||||
))}
|
||||
{"Exit".split("").map((letter, idx) => (
|
||||
<StaticBigLetter
|
||||
<PauseBigLetter
|
||||
color={idx > 0 ? "yellow" : "orange"}
|
||||
letter={letter}
|
||||
letterIdx={idx}
|
||||
|
@ -352,9 +355,16 @@ const Pause = () => {
|
|||
</mesh>
|
||||
</group>
|
||||
{showingAbout && <About />}
|
||||
<group position={[1, 0.6, 0]} scale={[1.2, 1.2, 0]}>
|
||||
<Prompt />
|
||||
</group>
|
||||
{promptVisible && (
|
||||
<group position={[1, 0.6, 0]} scale={[1.2, 1.2, 0]}>
|
||||
<Prompt />
|
||||
</group>
|
||||
)}
|
||||
{permissionDenied && (
|
||||
<group position={[1, 0.6, 0]} scale={[1.2, 1.2, 0]}>
|
||||
<PermissionDenied />
|
||||
</group>
|
||||
)}
|
||||
</group>
|
||||
)}
|
||||
</>
|
||||
|
|
36
src/components/MainScene/PauseSubscene/PermissionDenied.tsx
Normal file
36
src/components/MainScene/PauseSubscene/PermissionDenied.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import React, { memo } from "react";
|
||||
import headerContainer from "../../../static/sprite/prompt_question_container.png";
|
||||
import { useLoader } from "react-three-fiber";
|
||||
import * as THREE from "three";
|
||||
import StaticOrangeLetter from "../../TextRenderer/StaticOrangeLetter";
|
||||
|
||||
const PermissionDenied = memo(() => {
|
||||
const headerContainerTex = useLoader(THREE.TextureLoader, headerContainer);
|
||||
|
||||
return (
|
||||
<>
|
||||
<sprite scale={[4.1, 0.3, 0]} renderOrder={200} position={[0, 0.2, 0]}>
|
||||
<spriteMaterial
|
||||
map={headerContainerTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
opacity={0.6}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
<group scale={[0.08, 0.7, 0]} position={[-1, 0.19, 0]}>
|
||||
{"Permission denied".split("").map((letter, idx) => (
|
||||
<StaticOrangeLetter
|
||||
color={"orange"}
|
||||
letter={letter}
|
||||
letterIdx={idx}
|
||||
scale={[1.5, 0.25, 0.25]}
|
||||
key={idx}
|
||||
/>
|
||||
))}
|
||||
</group>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default PermissionDenied;
|
|
@ -9,8 +9,6 @@ import * as THREE from "three";
|
|||
import { useStore } from "../store";
|
||||
|
||||
const Prompt = () => {
|
||||
const promptVisible = useStore((state) => state.promptVisible);
|
||||
|
||||
const questionContainerTex = useLoader(
|
||||
THREE.TextureLoader,
|
||||
questionContainer
|
||||
|
@ -27,82 +25,65 @@ const Prompt = () => {
|
|||
)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(promptVisible);
|
||||
}, [promptVisible]);
|
||||
return (
|
||||
<>
|
||||
{promptVisible && (
|
||||
<>
|
||||
<sprite
|
||||
scale={[4.1, 0.3, 0]}
|
||||
renderOrder={200}
|
||||
position={[0, 0.2, 0]}
|
||||
>
|
||||
<spriteMaterial
|
||||
map={questionContainerTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
opacity={0.6}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
<sprite scale={[4.1, 0.3, 0]} renderOrder={200} position={[0, 0.2, 0]}>
|
||||
<spriteMaterial
|
||||
map={questionContainerTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
opacity={0.6}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
|
||||
<sprite
|
||||
scale={[2, 0.24, 0]}
|
||||
renderOrder={200}
|
||||
position={[0, 0.19, 0]}
|
||||
>
|
||||
<spriteMaterial
|
||||
map={questionTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
<sprite scale={[2, 0.24, 0]} renderOrder={200} position={[0, 0.19, 0]}>
|
||||
<spriteMaterial
|
||||
map={questionTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
|
||||
<sprite
|
||||
scale={[0.5, 0.19, 0]}
|
||||
renderOrder={200}
|
||||
position={[-1.2, -0.2, 0]}
|
||||
>
|
||||
<spriteMaterial
|
||||
map={yesTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
<sprite
|
||||
scale={[0.5, 0.19, 0]}
|
||||
renderOrder={200}
|
||||
position={[-1.2, -0.2, 0]}
|
||||
>
|
||||
<spriteMaterial
|
||||
map={yesTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
|
||||
<sprite
|
||||
scale={[0.7, 0.3, 0]}
|
||||
renderOrder={199}
|
||||
position={
|
||||
activeComponent === "yes" ? [-1.2, -0.2, 0] : [1.2, -0.2, 0]
|
||||
}
|
||||
>
|
||||
<spriteMaterial
|
||||
map={answerContainerTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
<sprite
|
||||
scale={[0.7, 0.3, 0]}
|
||||
renderOrder={199}
|
||||
position={activeComponent === "yes" ? [-1.2, -0.2, 0] : [1.2, -0.2, 0]}
|
||||
>
|
||||
<spriteMaterial
|
||||
map={answerContainerTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
|
||||
<sprite
|
||||
scale={[0.4, 0.19, 0]}
|
||||
renderOrder={200}
|
||||
position={[1.2, -0.2, 0]}
|
||||
>
|
||||
<spriteMaterial
|
||||
map={noTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
</>
|
||||
)}
|
||||
<sprite
|
||||
scale={[0.4, 0.19, 0]}
|
||||
renderOrder={200}
|
||||
position={[1.2, -0.2, 0]}
|
||||
>
|
||||
<spriteMaterial
|
||||
map={noTex}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
depthTest={false}
|
||||
/>
|
||||
</sprite>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { memo, useCallback, useEffect, useState } from "react";
|
||||
import { useStore } from "../../store";
|
||||
import { a, useTrail } from "@react-spring/three";
|
||||
import BigLetter from "./BigLetter";
|
||||
import SiteBigLetter from "./SiteBigLetter";
|
||||
|
||||
const MediaYellowTextAnimator = memo(() => {
|
||||
const [lastLeftComponent, setLastLeftComponent] = useState("play");
|
||||
|
@ -66,7 +66,7 @@ const MediaYellowTextAnimator = memo(() => {
|
|||
position-z={-8.7}
|
||||
scale={[0.04, 0.06, 0.04]}
|
||||
>
|
||||
<BigLetter letter={textArr[idx]} letterIdx={idx} key={idx} />
|
||||
<SiteBigLetter letter={textArr[idx]} letterIdx={idx} key={idx} />
|
||||
</a.group>
|
||||
))}
|
||||
</group>
|
||||
|
|
|
@ -8,14 +8,14 @@ import { a, useSpring } from "@react-spring/three";
|
|||
import React, { useMemo, memo } from "react";
|
||||
import { useStore } from "../../store";
|
||||
|
||||
const StaticBigLetter = memo(
|
||||
const PauseBigLetter = memo(
|
||||
(props: {
|
||||
color: string;
|
||||
letter: string;
|
||||
letterIdx: number;
|
||||
position: number[];
|
||||
scale: number[];
|
||||
active: boolean;
|
||||
active?: boolean;
|
||||
rowIdx?: number;
|
||||
colIdx?: number;
|
||||
intro?: boolean;
|
||||
|
@ -155,4 +155,4 @@ const StaticBigLetter = memo(
|
|||
}
|
||||
);
|
||||
|
||||
export default StaticBigLetter;
|
||||
export default PauseBigLetter;
|
|
@ -8,7 +8,7 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from "react";
|
|||
import { useStore } from "../../store";
|
||||
import usePrevious from "../../hooks/usePrevious";
|
||||
|
||||
const BigLetter = memo((props: { letter: string; letterIdx: number }) => {
|
||||
const SiteBigLetter = memo((props: { letter: string; letterIdx: number }) => {
|
||||
const [color, setColor] = useState("yellow");
|
||||
|
||||
const tex = useMemo(
|
||||
|
@ -158,4 +158,4 @@ const BigLetter = memo((props: { letter: string; letterIdx: number }) => {
|
|||
);
|
||||
});
|
||||
|
||||
export default BigLetter;
|
||||
export default SiteBigLetter;
|
94
src/components/TextRenderer/StaticOrangeLetter.tsx
Normal file
94
src/components/TextRenderer/StaticOrangeLetter.tsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
import orangeFont from "../../static/sprite/orange_font_texture.png";
|
||||
import * as THREE from "three";
|
||||
import { useLoader } from "react-three-fiber";
|
||||
import orange_font_json from "../../resources/font_data/big_font.json";
|
||||
import React, { memo, useMemo } from "react";
|
||||
|
||||
const StaticOrangeLetter = memo(
|
||||
(props: {
|
||||
color: string;
|
||||
letter: string;
|
||||
letterIdx: number;
|
||||
scale: number[];
|
||||
}) => {
|
||||
const colorTexture: THREE.Texture = useLoader(
|
||||
THREE.TextureLoader,
|
||||
orangeFont
|
||||
);
|
||||
|
||||
const lineYOffset = useMemo(() => {
|
||||
const lineOne = "ABCDEFGHIJKLMNOPQ";
|
||||
const lineTwo = "RSTUVWXYZ01234567";
|
||||
const lineThree = "89abcdefghijklmnopqrs";
|
||||
|
||||
let lineNum: number;
|
||||
if (props.letter === " ") {
|
||||
lineNum = 5;
|
||||
} else {
|
||||
if (lineOne.includes(props.letter)) {
|
||||
lineNum = 1;
|
||||
} else if (lineTwo.includes(props.letter)) {
|
||||
lineNum = 2;
|
||||
} else if (lineThree.includes(props.letter)) {
|
||||
lineNum = 3;
|
||||
} else {
|
||||
lineNum = 4;
|
||||
}
|
||||
}
|
||||
|
||||
const offsets = {
|
||||
1: 0.884,
|
||||
2: 0.765,
|
||||
3: 0.648,
|
||||
4: 0.47,
|
||||
5: 1,
|
||||
};
|
||||
return offsets[lineNum as keyof typeof offsets];
|
||||
}, [props.letter]);
|
||||
|
||||
const letterData = useMemo(
|
||||
() =>
|
||||
orange_font_json.glyphs[
|
||||
props.letter as keyof typeof orange_font_json.glyphs
|
||||
],
|
||||
[props.letter]
|
||||
);
|
||||
|
||||
const geom = useMemo(() => {
|
||||
const geometry = new THREE.PlaneBufferGeometry();
|
||||
|
||||
const uvAttribute = geometry.attributes.uv;
|
||||
|
||||
for (let i = 0; i < uvAttribute.count; i++) {
|
||||
let u = uvAttribute.getX(i);
|
||||
let v = uvAttribute.getY(i);
|
||||
|
||||
u = (u * letterData[2]) / 256 + letterData[0] / 256;
|
||||
|
||||
v = (v * letterData[3]) / 136 + lineYOffset - letterData[4] / 136;
|
||||
|
||||
uvAttribute.setXY(i, u, v);
|
||||
}
|
||||
return geometry;
|
||||
}, [letterData, lineYOffset]);
|
||||
|
||||
return (
|
||||
<mesh
|
||||
position={[props.letterIdx * 1.6, -letterData[4] / 50, 0]}
|
||||
scale={props.scale as [number, number, number]}
|
||||
geometry={geom}
|
||||
renderOrder={205}
|
||||
>
|
||||
<meshBasicMaterial
|
||||
map={colorTexture}
|
||||
attach="material"
|
||||
transparent={true}
|
||||
side={THREE.FrontSide}
|
||||
depthTest={false}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default StaticOrangeLetter;
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useStore } from "../../store";
|
||||
import { a, useTrail } from "@react-spring/three";
|
||||
import BigLetter from "./BigLetter";
|
||||
import SiteBigLetter from "./SiteBigLetter";
|
||||
import usePrevious from "../../hooks/usePrevious";
|
||||
import { getNodeHud } from "../../utils/node-utils";
|
||||
|
||||
|
@ -52,7 +52,7 @@ const YellowTextRenderer = (props: { visible?: boolean }) => {
|
|||
position-z={-8.7}
|
||||
scale={[0.04, 0.06, 0.04]}
|
||||
>
|
||||
<BigLetter letter={text[idx]} letterIdx={idx} key={idx} />
|
||||
<SiteBigLetter letter={text[idx]} letterIdx={idx} key={idx} />
|
||||
</a.group>
|
||||
))}
|
||||
</a.group>
|
||||
|
|
|
@ -21,6 +21,7 @@ const handleMainSceneEvent = (mainSceneContext: any) => {
|
|||
showingAbout,
|
||||
promptVisible,
|
||||
activePromptComponent,
|
||||
gateLvl,
|
||||
} = mainSceneContext;
|
||||
|
||||
if (promptVisible) {
|
||||
|
@ -310,9 +311,24 @@ const handleMainSceneEvent = (mainSceneContext: any) => {
|
|||
};
|
||||
case "CIRCLE":
|
||||
if (activePauseComponent === "change") {
|
||||
if (gateLvl < 4) {
|
||||
return { event: "show_permission_denied" };
|
||||
} else {
|
||||
return {
|
||||
event: "display_prompt",
|
||||
};
|
||||
}
|
||||
} else if (
|
||||
activePauseComponent === "save" ||
|
||||
activePauseComponent === "load"
|
||||
) {
|
||||
return {
|
||||
event: "display_prompt",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
event: `pause_${activePauseComponent}_select`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ const pauseManager = (eventState: any) => {
|
|||
const setComponentMatrixIdx = useStore.getState().setPauseComponentMatrixIdx;
|
||||
const setExitAnimation = useStore.getState().setPauseExitAnimation;
|
||||
const setShowingAbout = useStore.getState().setShowingAbout;
|
||||
const setPermissionDenied = useStore.getState().setPermissionDenied;
|
||||
|
||||
const dispatchAction = (eventState: PauseManagerProps) => {
|
||||
switch (eventState.event) {
|
||||
|
@ -16,7 +17,19 @@ const pauseManager = (eventState: any) => {
|
|||
};
|
||||
case "pause_exit_select":
|
||||
return {
|
||||
action: () => setExitAnimation(true),
|
||||
action: () => {
|
||||
setExitAnimation(true);
|
||||
setComponentMatrixIdx(2);
|
||||
},
|
||||
};
|
||||
case "show_permission_denied":
|
||||
return {
|
||||
action: () => {
|
||||
setPermissionDenied(true);
|
||||
setTimeout(() => {
|
||||
setPermissionDenied(false);
|
||||
}, 1200);
|
||||
},
|
||||
};
|
||||
case "pause_about_select":
|
||||
return {
|
||||
|
|
|
@ -4,6 +4,11 @@ const promptManager = (eventState: any) => {
|
|||
const setComponentMatrixIdx = useStore.getState().setPromptComponentMatrixIdx;
|
||||
const setPromptVisible = useStore.getState().setPromptVisible;
|
||||
|
||||
const exitAndResetPrompt = () => {
|
||||
setPromptVisible(false);
|
||||
setComponentMatrixIdx(1);
|
||||
};
|
||||
|
||||
const dispatchAction = (eventState: { event: string; scene: string }) => {
|
||||
switch (eventState.event) {
|
||||
case "display_prompt": {
|
||||
|
@ -16,9 +21,9 @@ const promptManager = (eventState: any) => {
|
|||
case "prompt_left":
|
||||
return { action: () => setComponentMatrixIdx(0) };
|
||||
case "pause_change_select":
|
||||
return { action: () => setPromptVisible(false) };
|
||||
return { action: () => exitAndResetPrompt() };
|
||||
case "exit_prompt":
|
||||
return { action: () => setPromptVisible(false) };
|
||||
return { action: () => exitAndResetPrompt() };
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ type State = {
|
|||
pauseComponentMatrixIdx: number;
|
||||
pauseExitAnimation: boolean;
|
||||
showingAbout: boolean;
|
||||
permissionDenied: boolean;
|
||||
|
||||
// media/media scene
|
||||
audioAnalyser: undefined | THREE.AudioAnalyser;
|
||||
|
@ -195,6 +196,7 @@ export const useStore = create(
|
|||
pauseComponentMatrixIdx: 2,
|
||||
pauseExitAnimation: false,
|
||||
showingAbout: false,
|
||||
permissionDenied: false,
|
||||
|
||||
// media / media scene
|
||||
audioAnalyser: undefined,
|
||||
|
@ -338,6 +340,8 @@ export const useStore = create(
|
|||
setPauseExitAnimation: (to: boolean) =>
|
||||
set(() => ({ pauseExitAnimation: to })),
|
||||
setShowingAbout: (to: boolean) => set(() => ({ showingAbout: to })),
|
||||
setPermissionDenied: (to: boolean) =>
|
||||
set(() => ({ permissionDenied: to })),
|
||||
|
||||
// media/media scene setters
|
||||
toggleMediaSide: () =>
|
||||
|
@ -489,6 +493,7 @@ export const getMainSceneContext = () => {
|
|||
promptVisible: state.promptVisible,
|
||||
activePromptComponent:
|
||||
state.promptComponentMatrix[state.promptComponentMatrixIdx],
|
||||
gateLvl: state.gateLvl,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue