@@ -81,6 +81,22 @@ type ModDepInfo = ModInfoProbablyMissing & {
8181 optional : boolean ;
8282} ;
8383
84+ // Mods that can stand in for one another: if any one in the list is enabled,
85+ // a dependency on any *other* member is treated as satisfied. Special-case for
86+ // the multiplayer clients — CelesteNet.Client and its CN fork MiaoNet.
87+ // (MiaoNet's installed name is literally "MiaoNet"; see main.rs
88+ // get_installed_miaonet.)
89+ const CELESTENET_ALT_LIST = [ 'CelesteNet.Client' , 'MiaoNet' ] ;
90+ const MOD_FRIENDLY_NAME : Record < string , string > = {
91+ 'CelesteNet.Client' : 'CelesteNet' ,
92+ 'MiaoNet' : 'MiaoNet' ,
93+ } ;
94+ // Returns the (raw) name of an enabled alternative covering `name`, else undefined.
95+ const altCovering = ( name : string , modMap : Map < string , ModInfo > ) => {
96+ if ( ! CELESTENET_ALT_LIST . includes ( name ) ) return undefined ;
97+ return CELESTENET_ALT_LIST . find ( ( alt ) => alt !== name && modMap . get ( alt ) ?. enabled ) ;
98+ } ;
99+
84100const modListContext = createContext < {
85101 switchMod : ( id : string , enabled : boolean , recursive ?: boolean ) => void ;
86102 switchProfile : ( name : string ) => void ;
@@ -95,6 +111,7 @@ const modListContext = createContext<{
95111 showDetailed : boolean ;
96112 alwaysOnMods : string [ ] ;
97113 switchAlwaysOn : ( name : string , enabled : boolean ) => void ;
114+ isCoveredByAlt : ( name : string ) => string | undefined ;
98115 autoDisableNewMods : boolean ;
99116 hasUpdateMods : {
100117 name : string ;
@@ -252,6 +269,7 @@ const ModLocal = ({
252269 } , [ name , ctx . hasUpdateMods ] ) ;
253270
254271 const isAlwaysOn = ctx ?. alwaysOnMods . includes ( name ) ;
272+ const coveredByAlt = ctx ?. isCoveredByAlt ?.( name ) ;
255273
256274 const [ editingComment , setEditingComment ] = useState ( false ) ;
257275 const refCommentInput = useRef < HTMLInputElement > ( null ) ;
@@ -381,6 +399,20 @@ const ModLocal = ({
381399 </ ModBadge >
382400 ) }
383401
402+ { coveredByAlt && (
403+ < ModBadge
404+ bg = "#0d47a1"
405+ color = "white"
406+ title = { _i18n . t ( '{alt} 已开启,可替代本 Mod' , {
407+ alt : MOD_FRIENDLY_NAME [ coveredByAlt ] ?? coveredByAlt ,
408+ } ) }
409+ >
410+ { _i18n . t ( '{alt} 已开启' , {
411+ alt : MOD_FRIENDLY_NAME [ coveredByAlt ] ?? coveredByAlt ,
412+ } ) }
413+ </ ModBadge >
414+ ) }
415+
384416 < span
385417 className = "modName"
386418 onClick = { ( ) => setEditingComment ( true ) }
@@ -656,7 +688,9 @@ export const Manage = () => {
656688 }
657689
658690 if ( ! modMap . has ( dep . name ) ) {
659- mergeSM ( { status : 'missing' , message : '' } , dep . name ) ;
691+ if ( ! altCovering ( dep . name , modMap ) ) {
692+ mergeSM ( { status : 'missing' , message : '' } , dep . name ) ;
693+ }
660694 continue ;
661695 }
662696
@@ -671,7 +705,7 @@ export const Manage = () => {
671705 ) ;
672706 }
673707
674- if ( ! installedDep . enabled ) {
708+ if ( ! installedDep . enabled && ! altCovering ( dep . name , modMap ) ) {
675709 mergeSM (
676710 {
677711 status : 'not-enabled' ,
@@ -898,6 +932,7 @@ export const Manage = () => {
898932 if ( enabled ) setAlwaysOnMods ( [ ...alwaysOnMods , name ] ) ;
899933 else setAlwaysOnMods ( alwaysOnMods . filter ( ( v ) => v !== name ) ) ;
900934 } ,
935+ isCoveredByAlt : ( name : string ) => altCovering ( name , installedModMap ) ,
901936 alwaysOnMods,
902937 autoDisableNewMods,
903938 modComments, setModComment ( name : string , comment : string ) {
@@ -970,9 +1005,11 @@ export const Manage = () => {
9701005 }
9711006
9721007 const switchList : string [ ] = [ ] ;
1008+ // Alternatives the user must pick between — don't auto-enable them when
1009+ // a dependent is toggled on. See DEPENDENCY_ALTERNATIVES above.
9731010 const excludeFromAutoEnableList = [
9741011 'CelesteNet.Client' ,
975- 'Miao.CelesteNet.Client ' ,
1012+ 'MiaoNet ' ,
9761013 ] ;
9771014
9781015 const visited = new Set < string > ( ) ;
@@ -1165,6 +1202,7 @@ export const Manage = () => {
11651202 [
11661203 currentProfile ,
11671204 installedMods ,
1205+ installedModMap ,
11681206 gamePath ,
11691207 modPath ,
11701208 fullTree ,
0 commit comments