Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions src/celemod-ui/src/modAdoption.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const settings = {
{ name: "Copied", file: "Copied.zip" },
],
profileEnabledNames: ["Enabled"],
allProfileEnabledNames: ["Enabled"],
blacklistEnabledNames: ["Enabled", "Copied"],
knownNames: ["Enabled"],
alwaysOnNames: [],
Expand All @@ -18,9 +19,37 @@ test("adopts a Mod that was copied into the Mods folder", () => {
assert.deepEqual(selectAdoptableModNames(settings), ["Copied"]);
});

test("keeps Mods CeleMod already saw out of the adoption", () => {
test("adopts a known Mod that was enabled outside CeleMod", () => {
// The game's "Toggle Mods" menu only edits blacklist.txt, so a Mod CeleMod
// already knows can still be enabled while no profile enables it. Applying
// the profiles must not turn it off again.
assert.deepEqual(
selectAdoptableModNames({ ...settings, knownNames: ["Enabled", "Copied"] }),
selectAdoptableModNames({
...settings,
knownNames: ["Enabled", "Copied"],
}),
["Copied"],
);
});

test("adopts a known Mod enabled outside CeleMod whatever the download default is", () => {
assert.deepEqual(
selectAdoptableModNames({
...settings,
knownNames: ["Enabled", "Copied"],
enableNewMods: false,
}),
["Copied"],
);
});

test("keeps a known Mod another profile enables out of the adoption", () => {
assert.deepEqual(
selectAdoptableModNames({
...settings,
knownNames: ["Enabled", "Copied"],
allProfileEnabledNames: ["Enabled", "Copied"],
}),
[],
);
});
Expand Down
31 changes: 26 additions & 5 deletions src/celemod-ui/src/modAdoption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,22 @@ export interface ModAdoptionInput {
installed: readonly InstalledModLike[];
/** Mod names the profiles that are about to be applied enable. */
profileEnabledNames: readonly string[];
/**
* Mod names every stored profile enables, including the inactive ones. A
* Mod that is only enabled on disk because another profile owns it keeps
* that profile's state and is not adopted here.
*/
allProfileEnabledNames: readonly string[];
/**
* Mod names the current `blacklist.txt` still loads. Every other installed
* Mod was disabled by CeleMod on purpose and must stay disabled.
*/
blacklistEnabledNames: readonly string[];
/**
* Mod names CeleMod already saw. They are not newly added, so they keep the
* state the profiles and the blacklist gave them.
* state the profiles and the blacklist gave them, except for a Mod that is
* enabled on disk although no profile enables it: something outside CeleMod
* turned that one on (see the helper below).
*/
knownNames: readonly string[];
alwaysOnNames: readonly string[];
Expand All @@ -38,19 +46,26 @@ const normalize = (name: string) => name.trim().toLowerCase();

/**
* Names of the installed Mods that CeleMod should adopt into the applied
* profiles. Returns an empty list when the default is "disable" - applying
* the profiles then disables those Mods, which is exactly that setting.
* profiles. New Mods follow the "Mod 下载后默认行为" setting: an empty list
* when the default is "disable", which is exactly that setting.
*
* A Mod that CeleMod already saw is not new, so it normally keeps the state the
* profiles gave it. The exception is a Mod that `blacklist.txt` still loads
* while no profile enables it: Everest's "Toggle Mods" menu and other launchers
* only write `blacklist.txt`, so that enabled state came from outside CeleMod
* and applying the profiles would silently turn it off again.
*/
export const selectAdoptableModNames = ({
installed,
profileEnabledNames,
allProfileEnabledNames,
blacklistEnabledNames,
knownNames,
alwaysOnNames,
enableNewMods,
}: ModAdoptionInput): string[] => {
if (!enableNewMods) return [];
const profileEnabled = new Set(profileEnabledNames.map(normalize));
const anyProfileEnabled = new Set(allProfileEnabledNames.map(normalize));
const blacklistEnabled = new Set(blacklistEnabledNames.map(normalize));
const known = new Set(knownNames.map(normalize));
const alwaysOn = new Set(alwaysOnNames.map(normalize));
Expand All @@ -63,7 +78,13 @@ export const selectAdoptableModNames = ({
handled.add(key);
if (profileEnabled.has(key)) continue;
if (!blacklistEnabled.has(key)) continue;
if (known.has(key) || alwaysOn.has(key)) continue;
if (alwaysOn.has(key)) continue;
if (known.has(key)) {
if (anyProfileEnabled.has(key)) continue;
adopted.push(mod.name);
continue;
}
if (!enableNewMods) continue;
adopted.push(mod.name);
}
return adopted;
Expand Down
1 change: 1 addition & 0 deletions src/celemod-ui/src/states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ export const adoptUntrackedMods = async (
profileEnabledNames: selectedProfiles.flatMap(
(profile) => profile.enabled_mods,
),
allProfileEnabledNames: profiles.flatMap((profile) => profile.enabled_mods),
blacklistEnabledNames: direct.enabled_mods,
knownNames: state.knownMods[gamePath] ?? [],
alwaysOnNames: state.alwaysOnMods,
Expand Down
Loading