mirror of
https://github.com/ad044/lainTSX.git
synced 2024-10-22 23:19:06 +00:00
some sounds!
This commit is contained in:
parent
68a43c39de
commit
d2f7b1da08
11 changed files with 236 additions and 20 deletions
|
@ -73,24 +73,46 @@ const BootAuthorizeUser = (props: BootAuthorizeUserProps) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bgLettersRef.current) {
|
if (bgLettersRef.current) {
|
||||||
//down
|
//down
|
||||||
if (letterIdx === prevData!.letterIdx + 13) {
|
if (prevData!.letterIdx + 13 === letterIdx) {
|
||||||
bgLettersRef.current.position.y += 0.25;
|
bgLettersRef.current.position.y += 0.25;
|
||||||
activeLetterMap.offset.y -= 0.2;
|
activeLetterMap.offset.y -= 0.2;
|
||||||
|
// down skip
|
||||||
|
} else if (letterIdx === prevData!.letterIdx + 26) {
|
||||||
|
bgLettersRef.current.position.y += 0.5;
|
||||||
|
activeLetterMap.offset.y -= 0.4;
|
||||||
|
} else if (letterIdx === prevData!.letterIdx + 52) {
|
||||||
|
bgLettersRef.current.position.y += 1;
|
||||||
|
activeLetterMap.offset.y -= 0.8;
|
||||||
}
|
}
|
||||||
// up
|
// up
|
||||||
else if (letterIdx === prevData!.letterIdx - 13) {
|
else if (letterIdx === prevData!.letterIdx - 13) {
|
||||||
bgLettersRef.current.position.y -= 0.25;
|
bgLettersRef.current.position.y -= 0.25;
|
||||||
activeLetterMap.offset.y += 0.2;
|
activeLetterMap.offset.y += 0.2;
|
||||||
|
// up skip
|
||||||
|
} else if (letterIdx === prevData!.letterIdx - 26) {
|
||||||
|
bgLettersRef.current.position.y -= 0.5;
|
||||||
|
activeLetterMap.offset.y += 0.4;
|
||||||
|
} else if (letterIdx === prevData!.letterIdx - 52) {
|
||||||
|
bgLettersRef.current.position.y -= 1;
|
||||||
|
activeLetterMap.offset.y += 0.8;
|
||||||
}
|
}
|
||||||
// left
|
// left
|
||||||
else if (letterIdx === prevData!.letterIdx - 1) {
|
else if (letterIdx === prevData!.letterIdx - 1) {
|
||||||
bgLettersRef.current.position.x += 0.3;
|
bgLettersRef.current.position.x += 0.3;
|
||||||
activeLetterMap.offset.x -= 0.0775;
|
activeLetterMap.offset.x -= 0.0775;
|
||||||
}
|
}
|
||||||
|
// left skip
|
||||||
|
else if (letterIdx === prevData!.letterIdx - 2) {
|
||||||
|
bgLettersRef.current.position.x += 0.6;
|
||||||
|
activeLetterMap.offset.x -= 0.155;
|
||||||
|
}
|
||||||
// right
|
// right
|
||||||
else if (letterIdx === prevData!.letterIdx + 1) {
|
else if (letterIdx === prevData!.letterIdx + 1) {
|
||||||
bgLettersRef.current.position.x -= 0.3;
|
bgLettersRef.current.position.x -= 0.3;
|
||||||
activeLetterMap.offset.x += 0.0775;
|
activeLetterMap.offset.x += 0.0775;
|
||||||
|
} else if (letterIdx === prevData!.letterIdx + 2) {
|
||||||
|
bgLettersRef.current.position.x -= 0.6;
|
||||||
|
activeLetterMap.offset.x += 0.155;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [activeLetterMap.offset, letterIdx, prevData]);
|
}, [activeLetterMap.offset, letterIdx, prevData]);
|
||||||
|
|
|
@ -27,6 +27,7 @@ import promptManager from "../core/setters/promptManager";
|
||||||
import bootSubsceneManager from "../core/setters/boot/bootSubsceneManager";
|
import bootSubsceneManager from "../core/setters/boot/bootSubsceneManager";
|
||||||
import bootManager from "../core/setters/boot/bootManager";
|
import bootManager from "../core/setters/boot/bootManager";
|
||||||
import handleBootSceneKeyPress from "../core/scene-keypress-handlers/handleBootSceneKeyPress";
|
import handleBootSceneKeyPress from "../core/scene-keypress-handlers/handleBootSceneKeyPress";
|
||||||
|
import soundManager from "../core/setters/soundManager";
|
||||||
|
|
||||||
const KeyPressHandler = () => {
|
const KeyPressHandler = () => {
|
||||||
const mediaSceneSetters = useMemo(
|
const mediaSceneSetters = useMemo(
|
||||||
|
@ -59,12 +60,20 @@ const KeyPressHandler = () => {
|
||||||
gameSaver,
|
gameSaver,
|
||||||
progressManager,
|
progressManager,
|
||||||
promptManager,
|
promptManager,
|
||||||
|
soundManager,
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const bootSceneSetters = useMemo(
|
const bootSceneSetters = useMemo(
|
||||||
() => [bootSubsceneManager, bootManager, promptManager, gameLoader],
|
() => [
|
||||||
|
bootSubsceneManager,
|
||||||
|
bootManager,
|
||||||
|
promptManager,
|
||||||
|
gameLoader,
|
||||||
|
soundManager,
|
||||||
|
sceneManager,
|
||||||
|
],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -41,10 +41,17 @@ const handleBootSceneKeyPress = (bootSceneContext: any) => {
|
||||||
break;
|
break;
|
||||||
case "authorize_user":
|
case "authorize_user":
|
||||||
switch (keyPress) {
|
switch (keyPress) {
|
||||||
|
case "START":
|
||||||
|
if (playerName.length > 0) {
|
||||||
|
return {
|
||||||
|
event: "start_new_game",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "X":
|
case "X":
|
||||||
if (playerName.length > 0) {
|
if (playerName.length > 0) {
|
||||||
return {
|
return {
|
||||||
event: "update_player_name",
|
event: "remove_last_char",
|
||||||
playerName: playerName.slice(0, -1),
|
playerName: playerName.slice(0, -1),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -52,8 +59,25 @@ const handleBootSceneKeyPress = (bootSceneContext: any) => {
|
||||||
}
|
}
|
||||||
case "LEFT":
|
case "LEFT":
|
||||||
// if utmost left, break
|
// if utmost left, break
|
||||||
if ([0, 13, 26, 39, 52].includes(authorizeUserLetterIdx)) break;
|
if (
|
||||||
else {
|
[0, 13, 26, 39, 52].includes(authorizeUserLetterIdx) ||
|
||||||
|
authorizeUserLetterIdx === 15
|
||||||
|
)
|
||||||
|
break;
|
||||||
|
// skip
|
||||||
|
else if (
|
||||||
|
authorizeUserLetterIdx === 41 ||
|
||||||
|
authorizeUserLetterIdx === 17 ||
|
||||||
|
authorizeUserLetterIdx === 30 ||
|
||||||
|
authorizeUserLetterIdx === 43 ||
|
||||||
|
authorizeUserLetterIdx === 19 ||
|
||||||
|
authorizeUserLetterIdx === 45
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
event: "authorize_user_left",
|
||||||
|
authorizeUserLetterIdx: authorizeUserLetterIdx - 2,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
event: "authorize_user_left",
|
event: "authorize_user_left",
|
||||||
authorizeUserLetterIdx: authorizeUserLetterIdx - 1,
|
authorizeUserLetterIdx: authorizeUserLetterIdx - 1,
|
||||||
|
@ -62,7 +86,20 @@ const handleBootSceneKeyPress = (bootSceneContext: any) => {
|
||||||
case "RIGHT":
|
case "RIGHT":
|
||||||
// if utmost right, break
|
// if utmost right, break
|
||||||
if ([12, 25, 38, 51, 64].includes(authorizeUserLetterIdx)) break;
|
if ([12, 25, 38, 51, 64].includes(authorizeUserLetterIdx)) break;
|
||||||
else {
|
// skip empty
|
||||||
|
else if (
|
||||||
|
authorizeUserLetterIdx === 39 ||
|
||||||
|
authorizeUserLetterIdx === 41 ||
|
||||||
|
authorizeUserLetterIdx === 28 ||
|
||||||
|
authorizeUserLetterIdx === 15 ||
|
||||||
|
authorizeUserLetterIdx === 43 ||
|
||||||
|
authorizeUserLetterIdx === 17
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
event: "authorize_user_right",
|
||||||
|
authorizeUserLetterIdx: authorizeUserLetterIdx + 2,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
event: "authorize_user_right",
|
event: "authorize_user_right",
|
||||||
authorizeUserLetterIdx: authorizeUserLetterIdx + 1,
|
authorizeUserLetterIdx: authorizeUserLetterIdx + 1,
|
||||||
|
@ -74,9 +111,27 @@ const handleBootSceneKeyPress = (bootSceneContext: any) => {
|
||||||
Array.from(new Array(13), (x, i) => i + 52).includes(
|
Array.from(new Array(13), (x, i) => i + 52).includes(
|
||||||
authorizeUserLetterIdx
|
authorizeUserLetterIdx
|
||||||
)
|
)
|
||||||
)
|
) {
|
||||||
break;
|
break;
|
||||||
else {
|
// skip empty
|
||||||
|
} else if (
|
||||||
|
authorizeUserLetterIdx === 0 ||
|
||||||
|
authorizeUserLetterIdx === 1 ||
|
||||||
|
authorizeUserLetterIdx === 52 ||
|
||||||
|
authorizeUserLetterIdx === 27 ||
|
||||||
|
authorizeUserLetterIdx === 31 ||
|
||||||
|
authorizeUserLetterIdx === 5
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
event: "authorize_user_down",
|
||||||
|
authorizeUserLetterIdx: authorizeUserLetterIdx + 26,
|
||||||
|
};
|
||||||
|
} else if (authorizeUserLetterIdx === 3) {
|
||||||
|
return {
|
||||||
|
event: "authorize_user_down",
|
||||||
|
authorizeUserLetterIdx: authorizeUserLetterIdx + 52,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
event: "authorize_user_down",
|
event: "authorize_user_down",
|
||||||
authorizeUserLetterIdx: authorizeUserLetterIdx + 13,
|
authorizeUserLetterIdx: authorizeUserLetterIdx + 13,
|
||||||
|
@ -88,9 +143,26 @@ const handleBootSceneKeyPress = (bootSceneContext: any) => {
|
||||||
Array.from(new Array(13), (x, i) => i).includes(
|
Array.from(new Array(13), (x, i) => i).includes(
|
||||||
authorizeUserLetterIdx
|
authorizeUserLetterIdx
|
||||||
)
|
)
|
||||||
)
|
) {
|
||||||
break;
|
break;
|
||||||
else {
|
// skip empty
|
||||||
|
} else if (
|
||||||
|
authorizeUserLetterIdx === 26 ||
|
||||||
|
authorizeUserLetterIdx === 27 ||
|
||||||
|
authorizeUserLetterIdx === 53 ||
|
||||||
|
authorizeUserLetterIdx === 31 ||
|
||||||
|
authorizeUserLetterIdx === 57
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
event: "authorize_user_up",
|
||||||
|
authorizeUserLetterIdx: authorizeUserLetterIdx - 26,
|
||||||
|
};
|
||||||
|
} else if (authorizeUserLetterIdx === 55) {
|
||||||
|
return {
|
||||||
|
event: "authorize_user_up",
|
||||||
|
authorizeUserLetterIdx: authorizeUserLetterIdx - 52,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
event: "authorize_user_up",
|
event: "authorize_user_up",
|
||||||
authorizeUserLetterIdx: authorizeUserLetterIdx - 13,
|
authorizeUserLetterIdx: authorizeUserLetterIdx - 13,
|
||||||
|
@ -106,7 +178,7 @@ const handleBootSceneKeyPress = (bootSceneContext: any) => {
|
||||||
|
|
||||||
if (newName !== undefined)
|
if (newName !== undefined)
|
||||||
return { event: "update_player_name", playerName: newName };
|
return { event: "update_player_name", playerName: newName };
|
||||||
break;
|
else return { event: "update_player_name_denied" };
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,10 @@ const bootManager = (eventState: any) => {
|
||||||
return {
|
return {
|
||||||
action: () => setPlayerName(eventState.playerName),
|
action: () => setPlayerName(eventState.playerName),
|
||||||
};
|
};
|
||||||
|
case "remove_last_char":
|
||||||
|
return {
|
||||||
|
action: () => setPlayerName(eventState.playerName),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,11 @@ const sceneManager = (eventState: any) => {
|
||||||
useStore.setState({ currentScene: "idle_media", intro: false }),
|
useStore.setState({ currentScene: "idle_media", intro: false }),
|
||||||
delay: 0,
|
delay: 0,
|
||||||
};
|
};
|
||||||
|
case "start_new_game":
|
||||||
|
return {
|
||||||
|
action: () => useStore.setState({ currentScene: "main" }),
|
||||||
|
delay: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
86
src/core/setters/soundManager.ts
Normal file
86
src/core/setters/soundManager.ts
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import { playAudio } from "../../store";
|
||||||
|
import * as audio from "../../static/sfx";
|
||||||
|
|
||||||
|
const soundManager = (eventState: any) => {
|
||||||
|
const dispatchAction = (eventState: { event: string; scene: string }) => {
|
||||||
|
switch (eventState.event) {
|
||||||
|
case "throw_node_media":
|
||||||
|
case "throw_node_gate":
|
||||||
|
case "throw_node_sskn":
|
||||||
|
case "throw_node_tak":
|
||||||
|
case "throw_node_polytan":
|
||||||
|
return {
|
||||||
|
action: () => {
|
||||||
|
playAudio(audio.sound0);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
playAudio(audio.sound12);
|
||||||
|
}, 1600);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
playAudio(audio.sound13);
|
||||||
|
playAudio(audio.sound14);
|
||||||
|
}, 2800);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
case "update_player_name":
|
||||||
|
case "update_player_name_denied":
|
||||||
|
case "main_menu_authorize_user_select":
|
||||||
|
case "main_menu_load_data_select":
|
||||||
|
return {
|
||||||
|
action: () => playAudio(audio.sound0),
|
||||||
|
};
|
||||||
|
case "site_left":
|
||||||
|
case "site_right":
|
||||||
|
return {
|
||||||
|
action: () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
playAudio(audio.sound6);
|
||||||
|
}, 1100);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case "site_up":
|
||||||
|
case "site_down":
|
||||||
|
return {
|
||||||
|
action: () => {
|
||||||
|
playAudio(audio.sound13);
|
||||||
|
setTimeout(() => {
|
||||||
|
playAudio(audio.sound10);
|
||||||
|
playAudio(audio.sound9);
|
||||||
|
}, 1300);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
playAudio(audio.sound8);
|
||||||
|
}, 2700);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case "main_menu_down":
|
||||||
|
case "main_menu_up":
|
||||||
|
case "prompt_left":
|
||||||
|
case "prompt_right":
|
||||||
|
case "change_node":
|
||||||
|
return {
|
||||||
|
action: () => playAudio(audio.sound1),
|
||||||
|
};
|
||||||
|
case "authorize_user_back":
|
||||||
|
case "remove_last_char":
|
||||||
|
case "load_data_no":
|
||||||
|
return {
|
||||||
|
action: () => playAudio(audio.sound29),
|
||||||
|
};
|
||||||
|
case "load_data_yes":
|
||||||
|
return {
|
||||||
|
action: () => playAudio(audio.sound28),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { action } = { ...dispatchAction(eventState) };
|
||||||
|
|
||||||
|
if (action) {
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default soundManager;
|
5
src/react-app-env.d.ts
vendored
5
src/react-app-env.d.ts
vendored
|
@ -90,6 +90,11 @@ declare module '*.mp4' {
|
||||||
export default src;
|
export default src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '*.wav' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
|
|
||||||
declare module "*.ogg" {
|
declare module "*.ogg" {
|
||||||
const src: string;
|
const src: string;
|
||||||
export default src;
|
export default src;
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
|
|
||||||
"ァ": [0, 80, 16, 16, 0],
|
"ァ": [0, 80, 16, 16, 0],
|
||||||
"ィ": [16, 80, 16, 16, 0],
|
"ィ": [16, 80, 16, 16, 0],
|
||||||
"ゥ": [32, 80, 16, 16, 0],
|
"ゥ": [32, 80, 16, 16, 0],
|
||||||
"ェ": [48, 80, 16, 16, 0],
|
"ェ": [48, 80, 16, 16, 0],
|
||||||
"ォ": [64, 80, 16, 16, 0],
|
"ォ": [64, 80, 16, 16, 0],
|
||||||
"ー": [80, 80, 16, 16, 0]
|
"ー": [80, 80, 16, 16, 0]
|
||||||
|
|
|
@ -15,6 +15,8 @@ import * as THREE from "three";
|
||||||
import { useFrame } from "react-three-fiber";
|
import { useFrame } from "react-three-fiber";
|
||||||
import NotFound from "../components/MainScene/NotFound";
|
import NotFound from "../components/MainScene/NotFound";
|
||||||
import PausePopUps from "../components/MainScene/PauseSubscene/PausePopUps";
|
import PausePopUps from "../components/MainScene/PauseSubscene/PausePopUps";
|
||||||
|
import { playAudio } from "../store";
|
||||||
|
import * as audio from "../static/sfx";
|
||||||
|
|
||||||
const MainScene = () => {
|
const MainScene = () => {
|
||||||
const intro = useStore((state) => state.intro);
|
const intro = useStore((state) => state.intro);
|
||||||
|
@ -42,12 +44,10 @@ const MainScene = () => {
|
||||||
}
|
}
|
||||||
}, [setWordSelected, wordSelected]);
|
}, [setWordSelected, wordSelected]);
|
||||||
|
|
||||||
|
const introWrapperRef = useRef<THREE.Group>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (intro) {
|
if (intro) {
|
||||||
if (introWrapperRef.current) {
|
|
||||||
introWrapperRef.current.rotation.x = Math.PI / 2;
|
|
||||||
introWrapperRef.current.position.z = -10;
|
|
||||||
}
|
|
||||||
setStarfieldIntro(false);
|
setStarfieldIntro(false);
|
||||||
setLainIntroAnim(false);
|
setLainIntroAnim(false);
|
||||||
setIntroFinished(false);
|
setIntroFinished(false);
|
||||||
|
@ -57,10 +57,10 @@ const MainScene = () => {
|
||||||
const [starfieldIntro, setStarfieldIntro] = useState(false);
|
const [starfieldIntro, setStarfieldIntro] = useState(false);
|
||||||
const [lainIntroAnim, setLainIntroAnim] = useState(false);
|
const [lainIntroAnim, setLainIntroAnim] = useState(false);
|
||||||
const [introFinished, setIntroFinished] = useState(false);
|
const [introFinished, setIntroFinished] = useState(false);
|
||||||
const introWrapperRef = useRef<THREE.Group>();
|
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (intro && introWrapperRef.current) {
|
if (intro && introWrapperRef.current && !introFinished) {
|
||||||
|
if (introWrapperRef.current.position.z === -10) playAudio(audio.sound32);
|
||||||
if (
|
if (
|
||||||
Math.round(introWrapperRef.current.position.z) === -3 &&
|
Math.round(introWrapperRef.current.position.z) === -3 &&
|
||||||
!starfieldIntro
|
!starfieldIntro
|
||||||
|
@ -121,7 +121,11 @@ const MainScene = () => {
|
||||||
introFinished={intro ? introFinished : true}
|
introFinished={intro ? introFinished : true}
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group ref={introWrapperRef}>
|
<group
|
||||||
|
ref={introWrapperRef}
|
||||||
|
position-z={intro ? -10 : 0}
|
||||||
|
rotation-x={intro ? Math.PI / 2 : 0}
|
||||||
|
>
|
||||||
<Site introFinished={intro ? introFinished : true} />
|
<Site introFinished={intro ? introFinished : true} />
|
||||||
</group>
|
</group>
|
||||||
<OrbitControls />
|
<OrbitControls />
|
||||||
|
|
10
src/store.ts
10
src/store.ts
|
@ -130,7 +130,7 @@ export const useStore = create(
|
||||||
// main subscene
|
// main subscene
|
||||||
mainSubscene: "site",
|
mainSubscene: "site",
|
||||||
|
|
||||||
// whether or not to play the intro anim
|
// whether or not to play the intro anim on main scene
|
||||||
intro: true,
|
intro: true,
|
||||||
|
|
||||||
// nodes
|
// nodes
|
||||||
|
@ -544,3 +544,11 @@ export const getBootSceneContext = () => {
|
||||||
authorizeUserLetterIdx: state.authorizeUserLetterIdx,
|
authorizeUserLetterIdx: state.authorizeUserLetterIdx,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const playAudio = (audio: HTMLAudioElement) => {
|
||||||
|
audio.currentTime = 0;
|
||||||
|
audio.currentTime = 0;
|
||||||
|
audio.volume = 1;
|
||||||
|
audio.loop = false;
|
||||||
|
audio.play();
|
||||||
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const getKeyCodeAssociation = (keyCode: number) => {
|
||||||
90: "X", // z key
|
90: "X", // z key
|
||||||
68: "TRIANGLE", // d key
|
68: "TRIANGLE", // d key
|
||||||
69: "L2", // e key
|
69: "L2", // e key
|
||||||
|
86: "START", // v key
|
||||||
32: "SPACE",
|
32: "SPACE",
|
||||||
};
|
};
|
||||||
return keyCodeAssocs[keyCode as keyof typeof keyCodeAssocs];
|
return keyCodeAssocs[keyCode as keyof typeof keyCodeAssocs];
|
||||||
|
|
Loading…
Reference in a new issue