1- import { defineComponent , PropType } from 'vue' ;
1+ import { computed , defineComponent , PropType } from 'vue' ;
22import { IEntryState , ISectionState , Section } from "@/client/apiGen" ;
3- import { Select , NumberInput } from '@munet/ui' ;
3+ import { Select , NumberInput , WhateverNaviBar } from '@munet/ui' ;
44import { useI18n } from 'vue-i18n' ;
55import { getNameForPath } from '../../utils' ;
66import ConfigEntry from '../../ConfigEntry' ;
77import { ENTRY_GROUP_PADDING , ENTRY_LABEL_CLASS } from '../../constants' ;
88
9+ const ROUTING_PREFIX = 'GameSystem.SoundRouting.' ;
10+ const SOUND_PREFIX = 'GameSystem.Sound.' ;
11+ const ENABLE_8CH_PATH = SOUND_PREFIX + 'Enable8Channel' ;
12+
13+ const routeKeys = [
14+ 'RouteP1SpeakerLeftTo' , 'RouteP1SpeakerRightTo' ,
15+ 'RouteP1HeadphoneLeftTo' , 'RouteP1HeadphoneRightTo' ,
16+ 'RouteP2SpeakerLeftTo' , 'RouteP2SpeakerRightTo' ,
17+ 'RouteP2HeadphoneLeftTo' , 'RouteP2HeadphoneRightTo' ,
18+ ] as const ;
19+
20+ interface Preset {
21+ name : string ;
22+ routes : Record < typeof routeKeys [ number ] , string > ;
23+ enable8Channel : boolean ;
24+ }
25+
26+ const presets : Preset [ ] = [
27+ {
28+ name : '2ch外放' ,
29+ routes : {
30+ RouteP1SpeakerLeftTo : 'P1SpeakerLeft' ,
31+ RouteP1SpeakerRightTo : 'P1SpeakerRight' ,
32+ RouteP1HeadphoneLeftTo : 'None' ,
33+ RouteP1HeadphoneRightTo : 'None' ,
34+ RouteP2SpeakerLeftTo : 'None' ,
35+ RouteP2SpeakerRightTo : 'None' ,
36+ RouteP2HeadphoneLeftTo : 'None' ,
37+ RouteP2HeadphoneRightTo : 'None' ,
38+ } ,
39+ enable8Channel : false ,
40+ } ,
41+ {
42+ name : '2ch耳机' ,
43+ routes : {
44+ RouteP1SpeakerLeftTo : 'None' ,
45+ RouteP1SpeakerRightTo : 'None' ,
46+ RouteP1HeadphoneLeftTo : 'P1SpeakerLeft' ,
47+ RouteP1HeadphoneRightTo : 'P1SpeakerRight' ,
48+ RouteP2SpeakerLeftTo : 'None' ,
49+ RouteP2SpeakerRightTo : 'None' ,
50+ RouteP2HeadphoneLeftTo : 'None' ,
51+ RouteP2HeadphoneRightTo : 'None' ,
52+ } ,
53+ enable8Channel : false ,
54+ } ,
55+ {
56+ name : '8ch' ,
57+ routes : {
58+ RouteP1SpeakerLeftTo : 'P1SpeakerLeft' ,
59+ RouteP1SpeakerRightTo : 'P1SpeakerRight' ,
60+ RouteP1HeadphoneLeftTo : 'P1HeadphoneLeft' ,
61+ RouteP1HeadphoneRightTo : 'P1HeadphoneRight' ,
62+ RouteP2SpeakerLeftTo : 'P2SpeakerLeft' ,
63+ RouteP2SpeakerRightTo : 'P2SpeakerRight' ,
64+ RouteP2HeadphoneLeftTo : 'P2HeadphoneLeft' ,
65+ RouteP2HeadphoneRightTo : 'P2HeadphoneRight' ,
66+ } ,
67+ enable8Channel : true ,
68+ } ,
69+ ] ;
70+
971export default defineComponent ( {
1072 props : {
1173 section : { type : Object as PropType < Section > , required : true } ,
1274 entryStates : { type : Object as PropType < Record < string , IEntryState > > , required : true } ,
1375 sectionState : { type : Object as PropType < ISectionState > , required : true } ,
76+ allSectionStates : { type : Object as PropType < Record < string , ISectionState > > } ,
1477 } ,
1578 setup ( props ) {
1679 const { t } = useI18n ( ) ;
17- const PREFIX = 'GameSystem.SoundRouting.' ;
1880
1981 const soundChannelOptions = [ 'None' , 'P1SpeakerLeft' , 'P1SpeakerRight' , 'P1HeadphoneLeft' , 'P1HeadphoneRight' , 'P2SpeakerLeft' , 'P2SpeakerRight' , 'P2HeadphoneLeft' , 'P2HeadphoneRight' ]
2082 . map ( channel => ( { label : t ( 'mod.soundChannel.' + channel ) , value : channel } ) ) ;
@@ -23,16 +85,55 @@ export default defineComponent({
2385 const p2Routes = [ 'RouteP2SpeakerLeftTo' , 'RouteP2SpeakerRightTo' , 'RouteP2HeadphoneLeftTo' , 'RouteP2HeadphoneRightTo' ] ;
2486 const p1Volumes = [ 'VolumeP1Speaker' , 'VolumeP1Headphone' ] ;
2587 const p2Volumes = [ 'VolumeP2Speaker' , 'VolumeP2Headphone' ] ;
26- const knownPaths = [ ...p1Routes , ...p2Routes , ...p1Volumes , ...p2Volumes ] . map ( it => PREFIX + it ) ;
88+ const knownPaths = [ ...p1Routes , ...p2Routes , ...p1Volumes , ...p2Volumes ] . map ( it => ROUTING_PREFIX + it ) ;
2789
28- const findEntry = ( key : string ) => props . section . entries ?. find ( it => it . path === PREFIX + key ) ;
90+ const findEntry = ( key : string ) => props . section . entries ?. find ( it => it . path === ROUTING_PREFIX + key ) ;
91+
92+ // 检测当前配置匹配哪个预设
93+ const activePreset = computed ( ( ) => {
94+ const enable8ChEntry = props . entryStates [ ENABLE_8CH_PATH ] ;
95+ if ( ! enable8ChEntry ) return null ;
96+
97+ for ( const preset of presets ) {
98+ const routesMatch = routeKeys . every ( key => {
99+ const entry = props . entryStates [ ROUTING_PREFIX + key ] ;
100+ return entry && entry . value === preset . routes [ key ] ;
101+ } ) ;
102+ const channelMatch = ! ! enable8ChEntry . value === preset . enable8Channel ;
103+ if ( routesMatch && channelMatch ) return preset . name ;
104+ }
105+ return null ;
106+ } ) ;
107+
108+ // 应用预设:只修改路由和 Enable8Channel,其他选项不动
109+ const applyPreset = ( preset : Preset ) => {
110+ for ( const key of routeKeys ) {
111+ const entry = props . entryStates [ ROUTING_PREFIX + key ] ;
112+ if ( entry ) entry . value = preset . routes [ key ] ;
113+ }
114+ const enable8ChEntry = props . entryStates [ ENABLE_8CH_PATH ] ;
115+ if ( enable8ChEntry ) enable8ChEntry . value = preset . enable8Channel ;
116+
117+ // 确保 GameSystem.Sound 和 GameSystem.SoundRouting 都开着
118+ if ( props . allSectionStates ) {
119+ const soundState = props . allSectionStates [ 'GameSystem.Sound' ] ;
120+ if ( soundState ) soundState . enabled = true ;
121+ }
122+ props . sectionState . enabled = true ;
123+ } ;
124+
125+ const naviItems = computed ( ( ) => presets . map ( preset => ( {
126+ name : preset . name ,
127+ selected : activePreset . value === preset . name ,
128+ onClick : ( ) => applyPreset ( preset ) ,
129+ } ) ) ) ;
29130
30131 const renderSelect = ( key : string ) => {
31132 const entry = findEntry ( key ) ;
32133 if ( ! entry ) return null ;
33134 return < div class = "flex gap-2 items-start" >
34135 < div class = { ENTRY_LABEL_CLASS } > { getNameForPath ( entry . path ! , entry . name ! , entry . attribute ?. comment ?. nameZh ) } </ div >
35- < Select class = "w-full" v-model :value = { props . entryStates [ PREFIX + key ] . value } options = { soundChannelOptions } />
136+ < Select class = "w-full" v-model :value = { props . entryStates [ ROUTING_PREFIX + key ] . value } options = { soundChannelOptions } />
36137 </ div > ;
37138 } ;
38139
@@ -41,11 +142,15 @@ export default defineComponent({
41142 if ( ! entry ) return null ;
42143 return < div class = "flex gap-2 items-start" >
43144 < div class = { ENTRY_LABEL_CLASS } > { getNameForPath ( entry . path ! , entry . name ! , entry . attribute ?. comment ?. nameZh ) } </ div >
44- < NumberInput innerClass = "h-42px!" v-model :value = { props . entryStates [ PREFIX + key ] . value } step = { 0.1 } decimal = { 2 } />
145+ < NumberInput innerClass = "h-42px!" v-model :value = { props . entryStates [ ROUTING_PREFIX + key ] . value } step = { 0.1 } decimal = { 2 } />
45146 </ div > ;
46147 } ;
47148
48149 return ( ) => < div class = "flex flex-col gap-2" >
150+ < div class = "pl-40 flex items-center gap-2" >
151+ 预设:
152+ < WhateverNaviBar items = { naviItems . value } />
153+ </ div >
49154 < div class = { [ "grid grid-cols-1 min-[500px]:grid-cols-2 gap-y-12px gap-x-16px" , ENTRY_GROUP_PADDING ] } >
50155 < div class = "flex flex-col gap-2" >
51156 { p1Routes . map ( renderSelect ) }
0 commit comments