diff --git a/Version b/Version
index 8cf6caf..3f4e15a 100644
--- a/Version
+++ b/Version
@@ -1 +1 @@
-3.4.1
\ No newline at end of file
+3.4.20
\ No newline at end of file
diff --git a/changelogs.yaml b/changelogs.yaml
index a75e4e2..2edccdc 100644
--- a/changelogs.yaml
+++ b/changelogs.yaml
@@ -1,4 +1,6 @@
showcase:
+ 2023/03/15:
+ - Added background music
2023/03/03:
- Performance optimization
2023/02/26:
@@ -38,10 +40,12 @@ showcase:
2021/05/26:
- First commit
directory:
+ 2023/03/15:
+ - Added background music
2023/03/03:
- - Fixed Voice and VCs issues
+ - Fixed Voice and VLs issues
2023/03/02:
- - Added Voice, VCs, Operator Logo
+ - Added Voice, VLs, Operator Logo
2023/03/01:
- Added Operator page
- Added Changelogs page
diff --git a/config/_project_json.yaml b/config/_project_json.yaml
index 66aaf81..1951376 100644
--- a/config/_project_json.yaml
+++ b/config/_project_json.yaml
@@ -34,6 +34,7 @@ localization:
ui_privacy_do_not_track: Send usage data
ui_music_title:
📝 Music
ui_music_notice: Please adjust the 'Offset' value if you notice audio cutoff
+ ui_music_notice1: 'bg_rhodes_day.png' and 'operator_bg.png' use the same music, it is not a bug.
ui_music_selection: Music
ui_music_volume: Volume
ui_music_offset: Offset
@@ -71,6 +72,7 @@ localization:
ui_privacy_do_not_track: 发送使用数据
ui_music_title: 🎵 音乐
ui_music_notice: 如若发现音频截止,请调节 '弥补' 数值
+ ui_music_notice1: 'bg_rhodes_day.png' 和 'operator_bg.png' 使用同样的音乐,并非Bug
ui_music_selection: 音乐
ui_music_volume: 音量
ui_music_offset: 弥补
@@ -233,6 +235,10 @@ properties:
value:
text: ui_music_notice
condition: music_title.value == true
+ - key: music_notice1
+ value:
+ text: ui_music_notice1
+ condition: music_title.value == true
- key: music_selection
value:
text: ui_music_selection
diff --git a/directory/.env b/directory/.env
index ede81d2..2ae0018 100644
--- a/directory/.env
+++ b/directory/.env
@@ -4,4 +4,6 @@ VITE_VOICE_FOLDERS={"main":"voice","sub":[{"name":"jp","lang":"JP"},{"name":"cn"
VITE_DIRECTORY_FOLDER="_assets"
VITE_BACKGROUND_FOLDER="background"
VITE_AVAILABLE_OPERATORS=["chen","dusk","dusk_everything_is_a_miracle","ling","nearl","nian","nian_unfettered_freedom","phatom_focus","rosmontis","skadi","skadi_sublimation","w","w_wonder","specter","gavial","surtr_colorful_wonderland","lee_trust_your_eyes","texas_the_omertosa","nearl_relight","rosmontis_become_anew","passager_dream_in_a_moment","mizuki_summer_feast","chongyue","ling_it_does_wash_the_strings","pozemka_snowy_plains_in_words"]
-VITE_ERROR_FILES={"files":[{"key":"build_char_128_plosis_epoque%233","paddings":{"left":-120,"right":150,"top":10,"bottom":0}},{"key":"build_char_128_plosis","paddings":{"left":-90,"right":100,"top":10,"bottom":0}}],"voice":"CN_034.ogg"}
\ No newline at end of file
+VITE_ERROR_FILES={"files":[{"key":"build_char_128_plosis_epoque%233","paddings":{"left":-120,"right":150,"top":10,"bottom":0}},{"key":"build_char_128_plosis","paddings":{"left":-90,"right":100,"top":10,"bottom":0}}],"voice":"CN_034.ogg"}
+VITE_MUSIC_FOLDER=music
+VITE_MUSIC_MAPPING={"operator_bg.png":{"intro":"m_sys_void_intro.ogg","loop":"m_sys_void_loop.ogg"},"bg_anniversary_1.png":{"intro":"m_dia_nightoflongmen_intro.ogg","loop":"m_dia_nightoflongmen_loop.ogg"},"bg_iberia_1.png":{"intro":"m_sys_act18d3d0_intro.ogg","loop":"m_sys_act18d3d0_loop.ogg"},"bg_kazimierz_1.png":{"intro":"m_dia_street_intro.ogg","loop":"m_dia_street_loop.ogg"},"bg_main_victoria_1.png":{"intro":"m_avg_ghosthunter_intro.ogg","loop":"m_avg_ghosthunter_loop.ogg"},"bg_rhodes_day.png":{"intro":"m_sys_void_intro.ogg","loop":"m_sys_void_loop.ogg"},"bg_rhodes_night.png":{"intro":"m_sys_tech_intro.ogg","loop":"m_sys_tech_loop.ogg"},"bg_rogue_1.png":{"intro":null,"loop":"m_avg_rglk1secretevent_loop.ogg"},"bg_siesta_1.png":{"intro":"m_sys_ddd_intro.ogg","loop":"m_sys_ddd_loop.ogg"},"bg_ursus_1.png":{"intro":"m_avg_loneliness_intro.ogg","loop":"m_avg_loneliness_loop.ogg"},"bg_yan_1.png":{"intro":null,"loop":"m_sys_bitw_loop.ogg"}}
\ No newline at end of file
diff --git a/directory/Version b/directory/Version
index c678b02..2fa3901 100644
--- a/directory/Version
+++ b/directory/Version
@@ -1 +1 @@
-1.0.18
\ No newline at end of file
+1.0.22
\ No newline at end of file
diff --git a/directory/src/component/switch.jsx b/directory/src/component/switch.jsx
index c02a531..e3998e5 100644
--- a/directory/src/component/switch.jsx
+++ b/directory/src/component/switch.jsx
@@ -16,7 +16,11 @@ export default function Switch(props) {
return (
props.handleOnClick()}
+ onClick={() => {
+ if (props.handleOnClick) {
+ props.handleOnClick(!on)
+ }
+ }}
>
{i18n(props.text)}
diff --git a/directory/src/component/voice.jsx b/directory/src/component/voice.jsx
new file mode 100644
index 0000000..c5e86ba
--- /dev/null
+++ b/directory/src/component/voice.jsx
@@ -0,0 +1,53 @@
+import React, {
+ useEffect,
+ useRef,
+} from "react"
+import PropTypes from 'prop-types';
+
+export default function VoiceElement({
+ src,
+ replay,
+ handleAduioStateChange,
+}) {
+ const audioRef = useRef(null)
+
+ useEffect(() => {
+ if (src) {
+ audioRef.current.src = src
+ audioRef.current.play()
+ } else {
+ audioRef.current.pause()
+ }
+ }, [src])
+
+ useEffect(() => {
+ if (replay) {
+ audioRef.current.currentTime = 0
+ audioRef.current.play()
+ }
+ }, [replay])
+
+ return (
+ {
+ if (handleAduioStateChange) handleAduioStateChange(e, 'ended')
+ }}
+ onPlay={(e) => {
+ if (handleAduioStateChange) handleAduioStateChange(e, 'play')
+ }}
+ onPause={(e) => {
+ if (handleAduioStateChange) handleAduioStateChange(e, 'pause')
+ }}
+ >
+
+
+ )
+}
+VoiceElement.propTypes = {
+ src: PropTypes.string,
+ handleAduioStateChange: PropTypes.func,
+ replay: PropTypes.bool,
+}
\ No newline at end of file
diff --git a/directory/src/libs/voice.jsx b/directory/src/libs/voice.jsx
deleted file mode 100644
index a7e2a5b..0000000
--- a/directory/src/libs/voice.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import {
- useState,
- useEffect,
- useRef,
-} from "react"
-import { useCallback } from "react"
-const audioEl = new Audio()
-let lastSrc = ''
-export default function useAudio() {
- const [isPlaying, _setIsPlaying] = useState(false)
- const isPlayingRef = useRef(isPlaying)
- const setIsPlaying = (data) => {
- isPlayingRef.current = data
- _setIsPlaying(data)
- }
-
- useEffect(() => {
- audioEl.addEventListener('ended', () => setIsPlaying(false))
- return () => {
- audioEl.removeEventListener('ended', () => setIsPlaying(false))
- }
- }, [])
-
- const play = useCallback(
- (
- link,
- options = {
- overwrite: false
- },
- callback = () => { }
- ) => {
- if (!options.overwrite && link === lastSrc) return
- audioEl.src = link
- let startPlayPromise = audioEl.play()
- if (startPlayPromise !== undefined) {
- setIsPlaying(true)
- startPlayPromise
- .then(() => {
- lastSrc = link
- callback()
- return
- })
- .catch((e) => {
- console.log(e)
- return
- })
- }
- }, [])
-
- const stop = useCallback(() => {
- audioEl.pause()
- setIsPlaying(false)
- }, [])
-
- const getSrc = useCallback(() => audioEl.src, [])
- const resetSrc = useCallback(() => {
- audioEl.src = ''
- }, [])
-
- return {
- play,
- stop,
- getSrc,
- resetSrc,
- isPlaying,
- isPlayingRef,
- }
-
-}
\ No newline at end of file
diff --git a/directory/src/routes/Error.jsx b/directory/src/routes/Error.jsx
index 33c46e9..66f153d 100644
--- a/directory/src/routes/Error.jsx
+++ b/directory/src/routes/Error.jsx
@@ -17,7 +17,7 @@ import Switch from '@/component/switch';
import ReturnButton from "@/component/return_button";
import { Typewriter } from 'react-simple-typewriter'
import { useHeader } from '@/state/header';
-import useAudio from '@/libs/voice';
+import VoiceElement from '@/component/voice';
import spine from '!/libs/spine-player'
import '!/libs/spine-player.css'
import useUmami from '@parcellab/react-use-umami';
@@ -27,6 +27,7 @@ const config = JSON.parse(import.meta.env.VITE_ERROR_FILES)
const obj = config.files[Math.floor((Math.random() * config.files.length))]
const filename = obj.key.replace("#", "%23")
const padding = obj.paddings
+let lastVoiceState = 'ended'
export default function Error() {
// eslint-disable-next-line no-unused-vars
@@ -40,9 +41,11 @@ export default function Error() {
const [spineDone, _setSpineDone] = useState(false)
const spineRef = useRef(null)
const [spineData, setSpineData] = useState(null)
- const { play, stop } = useAudio()
const spineDoneRef = useRef(spineDone)
const voiceOnRef = useRef(voiceOn)
+ const [voiceSrc, setVoiceSrc] = useState(null)
+ const [voiceReplay, setVoiceReplay] = useState(false)
+ const [spinePlayer, setSpinePlayer] = useState(null)
const setSpineDone = (data) => {
spineDoneRef.current = data
@@ -60,26 +63,40 @@ export default function Error() {
useEffect(() => {
setTitle(content[0])
- stop()
- }, [content, setTitle, stop])
+ }, [content, setTitle])
useEffect(() => {
if (!voiceOn) {
- stop()
+ setVoiceSrc(null)
+ } else {
+ setVoiceSrc(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/error.ogg`)
+ if (spinePlayer) {
+ spinePlayer.animationState.setAnimation(0, "Interact", false, 0);
+ spinePlayer.animationState.addAnimation(0, "Relax", true, 0);
+ }
}
- }, [voiceOn, stop])
+ }, [voiceOn])
useEffect(() => {
voiceOnRef.current = voiceOn
}, [voiceOn])
const playVoice = useCallback(() => {
- play(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/error.ogg`, { overwrite: true })
- }, [play])
+ if (lastVoiceState === 'ended' && voiceSrc !== null) {
+ setVoiceReplay(true)
+ }
+ }, [voiceSrc])
+
+ const handleAduioStateChange = useCallback((e, state) => {
+ lastVoiceState = state
+ if (state === 'ended') {
+ setVoiceReplay(false)
+ }
+ }, [])
useEffect(() => {
if (spineRef.current?.children.length === 0 && spineData) {
- new spine.SpinePlayer(spineRef.current, {
+ setSpinePlayer(new spine.SpinePlayer(spineRef.current, {
skelUrl: `./assets/${filename}.skel`,
atlasUrl: `./assets/${filename}.atlas`,
rawDataURIs: spineData,
@@ -99,7 +116,7 @@ export default function Error() {
showControls: false,
touch: false,
fps: 60,
- defaultMix: 0,
+ defaultMix: 0.3,
success: (player) => {
let isPlayingInteract = false
player.animationState.addListener({
@@ -127,7 +144,7 @@ export default function Error() {
ani()
}
}
- })
+ }))
}
}, [playVoice, spineData]);
@@ -163,6 +180,11 @@ export default function Error() {
className={`${classes.spine} ${spineDone ? classes.active : ''}`}
ref={spineRef}
/>
+
);
diff --git a/directory/src/routes/path/Home.jsx b/directory/src/routes/path/Home.jsx
index 4795f85..fef6515 100644
--- a/directory/src/routes/path/Home.jsx
+++ b/directory/src/routes/path/Home.jsx
@@ -15,7 +15,7 @@ import {
} from '@/state/language'
import { useHeader } from '@/state/header';
import { useAppbar } from '@/state/appbar';
-import useAudio from '@/libs/voice';
+import VoiceElement from '@/component/voice';
import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils';
import CharIcon from '@/component/char_icon';
@@ -24,6 +24,7 @@ import useUmami from '@parcellab/react-use-umami';
import Switch from '@/component/switch';
const voiceOnAtom = atomWithStorage('voiceOn', false)
+let lastVoiceState = 'ended'
export default function Home() {
// eslint-disable-next-line no-unused-vars
@@ -36,7 +37,9 @@ export default function Home() {
} = useHeader()
const { config } = useConfig()
const [content, setContent] = useState([])
- const { stop } = useAudio()
+ const [voiceOn] = useAtom(voiceOnAtom)
+ const [voiceSrc, setVoiceSrc] = useState(null)
+ const [voiceReplay, setVoiceReplay] = useState(false)
useEffect(() => {
setTitle('dynamic_compile')
@@ -48,15 +51,33 @@ export default function Home() {
key: 'skin'
}])
setHeaderIcon(null)
- stop()
- }, [setHeaderIcon, setTabs, setTitle, stop])
+ }, [setHeaderIcon, setTabs, setTitle])
useEffect(() => {
setContent(config?.operators || [])
}, [config])
+ const handleAduioStateChange = useCallback((e, state) => {
+ lastVoiceState = state
+ if (state === 'ended') {
+ setVoiceReplay(false)
+ }
+ }, [])
+
const isShown = useCallback((type) => currentTab === 'all' || currentTab === type, [currentTab])
+ const handleVoicePlay = useCallback((src) => {
+ if (!voiceOn) {
+ setVoiceSrc(null)
+ } else {
+ if (src === voiceSrc && lastVoiceState === 'ended') {
+ setVoiceReplay(true)
+ } else {
+ setVoiceSrc(src)
+ }
+ }
+ }, [voiceOn, voiceSrc])
+
return (
{
@@ -71,6 +92,7 @@ export default function Home() {
key={item.link}
item={item}
hidden={!isShown(item.type)}
+ handleVoicePlay={handleVoicePlay}
/>
)
})}
@@ -81,27 +103,17 @@ export default function Home() {
)
})
}
-
+
)
}
-function OperatorElement({ item, hidden }) {
+function OperatorElement({ item, hidden, handleVoicePlay }) {
const { textDefaultLang, language, alternateLang } = useLanguage()
- const { play, stop, resetSrc } = useAudio()
- const [voiceOn] = useAtom(voiceOnAtom)
-
- const playVoice = useCallback(() => {
- if (!voiceOn) return
- play(`/${item.link}/assets/${JSON.parse(import.meta.env.VITE_VOICE_FOLDERS).main}/${import.meta.env.VITE_APP_VOICE_URL}`)
- }, [voiceOn, play, item.link])
-
- useEffect(() => {
- if (!voiceOn) {
- stop()
- resetSrc()
- }
- }, [voiceOn, stop, resetSrc])
return useMemo(() => {
return (
@@ -111,7 +123,7 @@ function OperatorElement({ item, hidden }) {
hidden={hidden}
>
playVoice()}
+ onMouseEnter={() => handleVoicePlay(`/${item.link}/assets/${JSON.parse(import.meta.env.VITE_VOICE_FOLDERS).main}/${import.meta.env.VITE_APP_VOICE_URL}`)}
>
@@ -141,10 +153,10 @@ function OperatorElement({ item, hidden }) {
)
- }, [item, hidden, language, alternateLang, textDefaultLang, playVoice])
+ }, [item, hidden, language, alternateLang, textDefaultLang, handleVoicePlay])
}
-function VoiceSwitchElement() {
+function VoiceSwitchElement({ src, replay, handleAduioStateChange }) {
const [voiceOn, setVoiceOn] = useAtom(voiceOnAtom)
const {
setExtraArea,
@@ -163,7 +175,19 @@ function VoiceSwitchElement() {
])
}, [voiceOn, setExtraArea, setVoiceOn])
- return null
+ return (
+
+ )
+}
+
+VoiceSwitchElement.propTypes = {
+ src: PropTypes.string,
+ replay: PropTypes.bool,
+ handleAduioStateChange: PropTypes.func,
}
function ImageElement({ item }) {
diff --git a/directory/src/routes/path/Operator.jsx b/directory/src/routes/path/Operator.jsx
index c98fc25..b6ae9ba 100644
--- a/directory/src/routes/path/Operator.jsx
+++ b/directory/src/routes/path/Operator.jsx
@@ -18,19 +18,23 @@ import {
import { useHeader } from '@/state/header';
import { useAppbar } from '@/state/appbar';
import { useBackgrounds } from '@/state/background';
-import useAudio from '@/libs/voice';
+import VoiceElement from '@/component/voice';
import useUmami from '@parcellab/react-use-umami'
import spine from '!/libs/spine-player'
import '!/libs/spine-player.css'
import Border from '@/component/border';
import { useI18n } from '@/state/language';
+import Switch from '@/component/switch';
+import { atom, useAtom } from 'jotai'
+const musicMapping = JSON.parse(import.meta.env.VITE_MUSIC_MAPPING)
const getVoiceFoler = (lang) => {
const folderObject = JSON.parse(import.meta.env.VITE_VOICE_FOLDERS)
const voiceFolder = folderObject.sub.find(e => e.lang === lang) || folderObject.sub.find(e => e.name === 'custom')
return `${folderObject.main}/${voiceFolder.name}`
}
const defaultSpineAnimation = 'Idle'
+const backgroundAtom = atom(null)
const getTabName = (item, language) => {
if (item.type === 'operator') {
@@ -61,16 +65,18 @@ export default function Operator() {
const [spinePlayer, setSpinePlayer] = useState(null)
const [voiceLang, _setVoiceLang] = useState(null)
const { backgrounds } = useBackgrounds()
- const [currentBackground, setCurrentBackground] = useState(null)
+ const [currentBackground, setCurrentBackground] = useAtom(backgroundAtom)
const [voiceConfig, setVoiceConfig] = useState(null)
const [subtitleLang, setSubtitleLang] = useState(null)
const [hideSubtitle, setHideSubtitle] = useState(true)
- const { play, stop, getSrc, isPlaying, isPlayingRef } = useAudio()
const [subtitleObj, _setSubtitleObj] = useState(null)
const [currentVoiceId, setCurrentVoiceId] = useState(null)
const voiceLangRef = useRef(voiceLang)
const subtitleObjRef = useRef(subtitleObj)
const configRef = useRef(config)
+ const [voiceSrc, setVoiceSrc] = useState(null)
+ const [isVoicePlaying, _setIsVoicePlaying] = useState(false)
+ const isVoicePlayingRef = useRef(isVoicePlaying)
const setVoiceLang = (value) => {
voiceLangRef.current = value
@@ -82,18 +88,18 @@ export default function Operator() {
_setSubtitleObj(value)
}
+ const setIsVoicePlaying = (value) => {
+ isVoicePlayingRef.current = value
+ _setIsVoicePlaying(value)
+ }
+
useEffect(() => {
setExtraArea([])
- stop()
- }, [setExtraArea, stop])
+ }, [setExtraArea])
useEffect(() => {
- if (!voiceLang) stop()
- }, [stop, voiceLang])
-
- useEffect(() => {
- if (backgrounds) setCurrentBackground(backgrounds[0])
- }, [backgrounds])
+ if (backgrounds.length > 0) setCurrentBackground(backgrounds[0])
+ }, [backgrounds, setCurrentBackground])
useEffect(() => {
setSpineData(null)
@@ -175,7 +181,7 @@ export default function Operator() {
showControls: false,
touch: false,
fps: 60,
- defaultMix: 0,
+ defaultMix: 0.3,
success: (player) => {
let lastVoiceId = null
let currentVoiceId = null
@@ -189,17 +195,13 @@ export default function Operator() {
const id = voiceId()
currentVoiceId = id
setCurrentVoiceId(id)
- play(
- `/${configRef.current.link}/assets/${getVoiceFoler(voiceLangRef.current)}/${id}.ogg`,
- () => {
- lastVoiceId = currentVoiceId
- }
- )
+ setVoiceSrc(`/${configRef.current.link}/assets/${getVoiceFoler(voiceLangRef.current)}/${id}.ogg`)
+ lastVoiceId = currentVoiceId
}
}
}))
}
- }, [config, spineData, setSpinePlayer, spineAnimation, play]);
+ }, [config, spineData, spineAnimation]);
useEffect(() => {
if (voiceConfig && voiceLang) {
@@ -212,13 +214,24 @@ export default function Operator() {
}
}, [subtitleLang, voiceConfig, voiceLang])
+ const handleAduioStateChange = useCallback((e, state) => {
+ switch (state) {
+ case 'play':
+ setIsVoicePlaying(true)
+ break
+ default:
+ setIsVoicePlaying(false)
+ break
+ }
+ }, [])
+
useEffect(() => {
if (subtitleLang) {
- if (isPlaying) {
+ if (isVoicePlaying) {
setHideSubtitle(false)
} else {
const autoHide = () => {
- if (isPlayingRef.current) return
+ if (isVoicePlayingRef.current) return
setHideSubtitle(true)
}
setTimeout(autoHide, 5 * 1000)
@@ -229,29 +242,35 @@ export default function Operator() {
} else {
setHideSubtitle(true)
}
- }, [subtitleLang, isPlaying, isPlayingRef])
+ }, [subtitleLang, isVoicePlaying])
useEffect(() => {
- if (voiceLang && isPlaying) {
+ if (voiceLang && isVoicePlaying) {
const audioUrl = `/assets/${getVoiceFoler(voiceLang)}/${currentVoiceId}.ogg`
- if (getSrc() !== (window.location.href.replace(/\/$/g, '') + audioUrl)) {
- play(`/${config.link}${audioUrl}`)
+ if (voiceSrc !== (window.location.href.replace(/\/$/g, '') + audioUrl)) {
+ setVoiceSrc(`/${config.link}${audioUrl}`)
}
}
- }, [voiceLang, isPlaying, currentVoiceId, config, getSrc, play])
+ }, [voiceLang, isVoicePlaying, currentVoiceId, config, voiceSrc])
+
+ const playAnimationVoice = useCallback((animation) => {
+ if (voiceLangRef.current) {
+ let id = null
+ if (animation === 'Idle') id = 'CN_011'
+ if (animation === 'Interact') id = 'CN_034'
+ if (animation === 'Special') id = 'CN_042'
+ if (id) {
+ setCurrentVoiceId(id)
+ setVoiceSrc(`/${key}/assets/${getVoiceFoler(voiceLangRef.current)}/${id}.ogg`)
+ }
+ }
+ }, [key])
useEffect(() => {
- if (voiceLang && config) {
- let id = ''
- if (spineAnimation === 'Idle') id = 'CN_011'
- if (spineAnimation === 'Interact') id = 'CN_034'
- if (spineAnimation === 'Special') id = 'CN_042'
- setCurrentVoiceId(id)
- play(
- `/${config.link}/assets/${getVoiceFoler(voiceLang)}/${id}.ogg`
- )
+ if (!voiceLang) {
+ setVoiceSrc(null)
}
- }, [voiceLang, config, spineAnimation, play])
+ }, [voiceLang])
const spineSettings = [
{
@@ -260,8 +279,10 @@ export default function Operator() {
{
name: 'idle',
onClick: () => {
- spinePlayer.animationState.setAnimation(0, "Idle", true, 0)
- setSpineAnimation('Idle')
+ const animation = "Idle"
+ playAnimationVoice(animation)
+ spinePlayer.animationState.setAnimation(0, animation, true, 0)
+ setSpineAnimation(animation)
},
activeRule: () => {
return spineAnimation === 'Idle'
@@ -269,8 +290,10 @@ export default function Operator() {
}, {
name: 'interact',
onClick: () => {
- spinePlayer.animationState.setAnimation(0, "Interact", true, 0)
- setSpineAnimation('Interact')
+ const animation = "Interact"
+ playAnimationVoice(animation)
+ spinePlayer.animationState.setAnimation(0, animation, true, 0)
+ setSpineAnimation(animation)
},
activeRule: () => {
return spineAnimation === 'Interact'
@@ -278,8 +301,10 @@ export default function Operator() {
}, {
name: 'special',
onClick: () => {
- spinePlayer.animationState.setAnimation(0, "Special", true, 0)
- setSpineAnimation('Special')
+ const animation = "Special"
+ playAnimationVoice(animation)
+ spinePlayer.animationState.setAnimation(0, animation, true, 0)
+ setSpineAnimation(animation)
},
activeRule: () => {
return spineAnimation === 'Special'
@@ -297,6 +322,9 @@ export default function Operator() {
} else {
setVoiceLang(null)
}
+ if (!isVoicePlayingRef.current) {
+ playAnimationVoice(spineAnimation)
+ }
},
activeRule: () => {
return voiceLang === item
@@ -320,6 +348,9 @@ export default function Operator() {
}
}
}) || []
+ }, {
+ name: 'music',
+ el:
}, {
name: 'backgrounds',
options: backgrounds.map((item) => {
@@ -348,6 +379,13 @@ export default function Operator() {
}}>
{
spineSettings.map((item) => {
+ if (item.el) {
+ return (
+
+ )
+ }
if (item.options.length === 0) return null
return (
@@ -417,7 +455,7 @@ export default function Operator() {
{
config && (
@@ -426,7 +464,7 @@ export default function Operator() {
}
{currentVoiceId && subtitleObj && (
-
+
{subtitleObj[currentVoiceId]?.title}
{subtitleObj[currentVoiceId]?.text}
@@ -437,6 +475,80 @@ export default function Operator() {
+
+
+ )
+}
+
+function MusicElement() {
+ const [enableMusic, setEnableMusic] = useState(false)
+ const { i18n } = useI18n()
+ const musicIntroRef = useRef(null)
+ const musicLoopRef = useRef(null)
+ const [background,] = useAtom(backgroundAtom)
+
+ useEffect(() => {
+ if (musicIntroRef.current && musicIntroRef.current) {
+ musicIntroRef.current.volume = 0.5
+ musicLoopRef.current.volume = 0.5
+ }
+ }, [musicIntroRef, musicLoopRef])
+
+ useEffect(() => {
+ if (!enableMusic || background) {
+ musicIntroRef.current.pause()
+ musicLoopRef.current.pause()
+ }
+ }, [enableMusic, background])
+
+ useEffect(() => {
+ if (background && enableMusic) {
+ const introOgg = musicMapping[background].intro
+ const intro = `./chen/assets/${import.meta.env.VITE_MUSIC_FOLDER}/${introOgg}`
+ const loop = `./chen/assets/${import.meta.env.VITE_MUSIC_FOLDER}/${musicMapping[background].loop}`
+ musicLoopRef.current.src = loop
+ if (introOgg) {
+ musicIntroRef.current.src = intro || loop
+ } else {
+ musicLoopRef.current.play()
+ }
+ }
+ }, [background, enableMusic])
+
+ const handleIntroTimeUpdate = useCallback(() => {
+ if (musicIntroRef.current.currentTime >= musicIntroRef.current.duration - 0.3) {
+ musicIntroRef.current.pause()
+ musicLoopRef.current.play()
+ }
+ }, [])
+
+ const handleLoopTimeUpdate = useCallback(() => {
+ if (musicLoopRef.current.currentTime >= musicLoopRef.current.duration - 0.3) {
+ musicLoopRef.current.currentTime = 0
+ musicLoopRef.current.play()
+ }
+ }, [])
+
+ return (
+
+ setEnableMusic(!enableMusic)}
+ >
+
+
+
+ handleIntroTimeUpdate()}>
+
+
+ handleLoopTimeUpdate()}>
+
+
)
}
\ No newline at end of file
diff --git a/directory/src/scss/error/Error.module.scss b/directory/src/scss/error/Error.module.scss
index 044f340..9fe0eb3 100644
--- a/directory/src/scss/error/Error.module.scss
+++ b/directory/src/scss/error/Error.module.scss
@@ -22,6 +22,7 @@
padding-top: 10rem;
font-size: 3rem;
gap: 2rem;
+ padding-bottom: 0;
}
.spine {
@@ -38,6 +39,7 @@
@media (max-width: 768px) {
.main {
padding-top: 6rem;
+ max-height: calc(100vh - 6rem);
}
.content {
font-size: 2rem;
@@ -46,6 +48,7 @@
@media (max-width: 480px) {
.main {
padding-top: 4rem;
+ max-height: calc(100vh - 4rem);
}
.content {
font-size: 1.5rem;
diff --git a/libs/directory.js b/libs/directory.js
index b9e06fe..67944f9 100644
--- a/libs/directory.js
+++ b/libs/directory.js
@@ -5,7 +5,7 @@ import { read } from './yaml.js';
import AssetsProcessor from './assets_processor.js'
import EnvGenerator from './env_generator.js'
-export default function ({ backgrounds }) {
+export default function ({ backgrounds, musicMapping }) {
const extractedFolder = path.join(__projectRoot, __config.folder.operator, '_directory')
const targetFolder = path.join(__projectRoot, __config.folder.release, __config.folder.directory);
const sourceFolder = path.join(__projectRoot, __config.folder.operator);
@@ -78,6 +78,12 @@ export default function ({ backgrounds }) {
}, {
key: "error_files",
value: JSON.stringify(__config.directory.error).replace('#', '%23')
+ }, {
+ key: "music_folder",
+ value: __config.folder.music
+ }, {
+ key: "music_mapping",
+ value: JSON.stringify(musicMapping)
}
]), path.join(__projectRoot, 'directory', '.env'))
diff --git a/src/components/music.js b/src/components/music.js
index 5ab7809..2cbd513 100644
--- a/src/components/music.js
+++ b/src/components/music.js
@@ -76,7 +76,7 @@ export default class Music {
}
success() {
- this.changeMusic(window.settings.currentBackground)
+ if (this.#currentMusic === null) this.changeMusic(window.settings.currentBackground)
}
changeMusic(name) {
diff --git a/src/libs/wallpaper_engine.js b/src/libs/wallpaper_engine.js
index 5567b6e..6e8d358 100644
--- a/src/libs/wallpaper_engine.js
+++ b/src/libs/wallpaper_engine.js
@@ -93,10 +93,10 @@ window.wallpaperPropertyListener = {
}
if (properties.music_title) {
window.music.useMusic = properties.music_title.value
+ window.music.changeMusic(properties.music_selection.value)
window.settings.functionInsights("useMusic", Object.keys(properties) !== 1)
}
if (properties.music_selection) {
- // TODO: not working
window.music.changeMusic(properties.music_selection.value)
window.settings.functionInsights("music_selection", Object.keys(properties) !== 1)
}