Skip to content

Commit

Permalink
Postponed collection Load Order setting and Mod Settings overriding t…
Browse files Browse the repository at this point in the history
…o deployment instead of installation

This allows us to react to collection enable/disable
  • Loading branch information
Aragas committed Oct 6, 2024
1 parent c35ebc0 commit 259df1e
Show file tree
Hide file tree
Showing 12 changed files with 297 additions and 86 deletions.
34 changes: 34 additions & 0 deletions src/collections/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createAction } from 'redux-act';
import { ICollectionGeneralData, ICollectionSettingsData } from './types';
import { EXTENSION_BASE_ID } from '../common';

export type SetCollectionDataPayload = {
collectionSlug: string;
collectionData: ICollectionGeneralData;
};

export type SetCollectionModOptionsPayload = {
collectionSlug: string;
collectionData: ICollectionSettingsData;
};

const setCollectionGeneralData = createAction<string, ICollectionGeneralData, SetCollectionDataPayload>(
`${EXTENSION_BASE_ID}_SET_COLLECTION_GENERAL_DATA`,
(collectionSlug: string, collectionData: ICollectionGeneralData) => ({
collectionSlug,
collectionData,
})
);

const setCollectionModOptions = createAction<string, ICollectionSettingsData, SetCollectionModOptionsPayload>(
`${EXTENSION_BASE_ID}_SET_COLLECTION_MOD_OPTIONS`,
(collectionSlug: string, collectionData: ICollectionSettingsData) => ({
collectionSlug,
collectionData,
})
);

export const actionsCollections = {
setCollectionGeneralData,
setCollectionModOptions,
};
110 changes: 106 additions & 4 deletions src/collections/events.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,115 @@
import { types } from 'vortex-api';
import { selectors, types } from 'vortex-api';
import { collectionInstallBLSE } from './utils';
import { GAME_ID } from '../common';
import { hasBackupModOptions, removeOriginalModOptions, restoreOriginalModOptions } from '../modoptions';
import {
hasBackupModOptions,
overrideModOptions,
removeOriginalModOptions,
restoreOriginalModOptions,
} from '../modoptions';
import { LocalizationManager } from '../localization';
import { hasPersistentBannerlord, hasPersistentBannerlordMods } from '../vortex';
import { actionsLoadOrder, orderCurrentLoadOrderByExternalLoadOrder } from '../loadOrder';
import { VortexLauncherManager } from '../launcher';
import { IBannerlordMod } from '../types';

/**
* Event function, be careful
* We're a bit inconsistent - when the mod is enabled, we don't react
* We're waiting for the deployment to trigger
* On disable, we're reacting immediately
*/
export const willRemoveModCollections = async (api: types.IExtensionApi, modId: string): Promise<void> => {
const mod = api.getState().persistent.mods[GAME_ID]?.[modId];
export const didDeployCollection = async (api: types.IExtensionApi, profileId: string): Promise<void> => {
const state = api.getState();

const profile: types.IProfile | undefined = selectors.profileById(state, profileId);
if (profile === undefined || profile.gameId !== GAME_ID) {
return;
}

if (!hasPersistentBannerlordMods(state.persistent)) {
return;
}

const enabledCollections = Object.values(state.persistent.mods.mountandblade2bannerlord)
.filter((mod) => mod.type === 'collection')
.filter((mod) => profile.modState[mod.id]?.enabled);

for (const collection of enabledCollections) {
await modEnabledCollection(api, profileId, collection);
}
};

const modEnabledCollection = async (
api: types.IExtensionApi,
profileId: string,
mod: IBannerlordMod
): Promise<void> => {
const state = api.getState();

if (!hasPersistentBannerlord(state.persistent)) {
return;
}

const collectionSlug = mod.attributes?.['collectionSlug'];
if (collectionSlug === undefined || typeof collectionSlug !== 'string') {
return;
}

const collectionGeneralData = state.persistent.mountandblade2bannerlord.collectionGeneralData[collectionSlug];
if (collectionGeneralData !== undefined) {
const { hasBLSE, suggestedLoadOrder } = collectionGeneralData;

if (hasBLSE) {
await collectionInstallBLSE(api);
}

const launcherManager = VortexLauncherManager.getInstance(api);
const modules = launcherManager.getAllModules();
const loadOrder = await orderCurrentLoadOrderByExternalLoadOrder(api, modules, suggestedLoadOrder);
api.store?.dispatch(actionsLoadOrder.setFBLoadOrder(profileId, loadOrder));
}

const collectionModOptions = state.persistent.mountandblade2bannerlord.collectionModOptions[collectionSlug];
if (collectionModOptions !== undefined) {
const { includedModOptions } = collectionModOptions;

if (includedModOptions !== undefined && includedModOptions.length) {
const localizationManager = LocalizationManager.getInstance(api);
const { localize: t } = localizationManager;

const no = t('No');
const yes = t('Yes');
const result = await api.showDialog?.(
'question',
t('Override Mod Options'),
{
message: t(
`This collection contains custom Mod Options (MCM)!
Do you want to override your Mod Options with the custom Mod Options?
A backup of your original Mod Options will be kept and will be restored on collection removal.`
),
},
[{ label: no }, { label: yes }]
);

if (result && result.action === yes) {
await overrideModOptions(mod, includedModOptions);
}
}
}
};

/**
* Event function, be careful
* We're a bit inconsistent - when the mod is enabled, we don't react
* We're waiting for the deployment to trigger
* On disable, we're reacting immediately
*/
export const modDisabledCollections = async (api: types.IExtensionApi, modId: string): Promise<void> => {
const state = api.getState();

const mod = state.persistent.mods[GAME_ID]?.[modId];
if (!mod) {
return;
}
Expand Down
21 changes: 12 additions & 9 deletions src/collections/generalData.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { selectors, types } from 'vortex-api';
import { ICollectionData, ICollectionDataWithGeneralData, ICollectionGeneralData } from './types';
import { genCollectionGeneralLoadOrder, parseCollectionGeneralLoadOrder } from './loadOrder';
import { genCollectionGeneralLoadOrder } from './loadOrder';
import { CollectionParseError } from './errors';
import { collectionInstallBLSE } from './utils';
import { actionsCollections } from './actions';
import { GAME_ID } from '../common';
import { isModActive } from '../vortex';
import { findBLSEMod } from '../blse';
Expand Down Expand Up @@ -34,28 +35,30 @@ export const genCollectionGeneralData = (
*/
export const parseCollectionGeneralData = async (
api: types.IExtensionApi,
collection: ICollectionData
collection: ICollectionData,
mod: types.IMod
): Promise<void> => {
if (!hasGeneralData(collection)) {
return;
}

const state = api.getState();

const profileId: string | undefined = selectors.lastActiveProfileForGame(state, GAME_ID);
const profile: types.IProfile | undefined = selectors.profileById(state, profileId ?? '');
if (profile?.gameId !== GAME_ID) {
const collectionName = collection.info.name !== undefined ? collection.info.name : 'Bannerlord Collection';
throw new CollectionParseError(collectionName, 'Last active profile is missing');
}
const { hasBLSE } = collection;

const launcherManager = VortexLauncherManager.getInstance(api);
const modules = launcherManager.getAllModules();
await parseCollectionGeneralLoadOrder(api, modules, collection);
api.store?.dispatch(
actionsCollections.setCollectionGeneralData(mod.attributes?.['collectionSlug'], {
hasBLSE: collection.hasBLSE,
suggestedLoadOrder: collection.suggestedLoadOrder,
})
);

if (hasBLSE) {
await collectionInstallBLSE(api);
}
await Promise.resolve();
};

/**
Expand Down
18 changes: 13 additions & 5 deletions src/collections/legacyData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { selectors, types } from 'vortex-api';
import { ICollectionData, ICollectionDataWithLegacyData } from './types';
import { CollectionParseError } from './errors';
import { actionsCollections } from './actions';
import { GAME_ID, SUB_MODS_IDS } from '../common';
import { actionsLoadOrder, orderCurrentLoadOrderByExternalLoadOrder } from '../loadOrder';
import { VortexLauncherManager } from '../launcher';
Expand All @@ -9,18 +10,20 @@ import { hasPersistentBannerlordMods } from '../vortex';

export const parseCollectionLegacyData = async (
api: types.IExtensionApi,
collection: ICollectionData
collection: ICollectionData,
mod: types.IMod
): Promise<void> => {
if (!hasLegacyData(collection)) {
return;
}

await parseLegacyLoadOrder(api, collection);
await parseLegacyLoadOrder(api, collection, mod);
};

const parseLegacyLoadOrder = async (
api: types.IExtensionApi,
collection: ICollectionDataWithLegacyData
collection: ICollectionDataWithLegacyData,
mod: types.IMod
): Promise<void> => {
const state = api.getState();

Expand Down Expand Up @@ -58,9 +61,14 @@ const parseLegacyLoadOrder = async (
return arr;
}, []);

const loadOrder = await orderCurrentLoadOrderByExternalLoadOrder(api, allModules, suggestedLoadOrder);
api.store?.dispatch(
actionsCollections.setCollectionGeneralData(mod.attributes?.['collectionSlug'], {
hasBLSE: false,
suggestedLoadOrder: suggestedLoadOrder,
})
);

api.store?.dispatch(actionsLoadOrder.setFBLoadOrder(profileId, loadOrder));
await Promise.resolve();
};

const hasLegacyData = (collection: ICollectionData): collection is ICollectionDataWithLegacyData => {
Expand Down
27 changes: 2 additions & 25 deletions src/collections/loadOrder.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { selectors, types, util } from 'vortex-api';
import { ICollectionDataWithGeneralData } from './types';
import { CollectionParseError } from './errors';
import { GAME_ID } from '../common';
import { IBannerlordMod, IModuleCache, VortexLoadOrderStorage } from '../types';
import { actionsLoadOrder, orderCurrentLoadOrderByExternalLoadOrder } from '../loadOrder';
import { types, util } from 'vortex-api';
import { IBannerlordMod, VortexLoadOrderStorage } from '../types';

const isValidMod = (mod: types.IMod): boolean => {
return mod !== undefined && mod.type !== 'collection';
Expand Down Expand Up @@ -49,22 +45,3 @@ export const genCollectionGeneralLoadOrder = (
}, []);
return filteredLoadOrder;
};

export const parseCollectionGeneralLoadOrder = async (
api: types.IExtensionApi,
modules: Readonly<IModuleCache>,
collection: ICollectionDataWithGeneralData
): Promise<void> => {
const state = api.getState();

const profileId: string | undefined = selectors.lastActiveProfileForGame(state, GAME_ID);
if (profileId === undefined) {
throw new CollectionParseError(collection.info.name ?? '', 'Invalid profile id');
}

const suggestedLoadOrder = collection.suggestedLoadOrder;

const loadOrder = await orderCurrentLoadOrderByExternalLoadOrder(api, modules, suggestedLoadOrder);

api.store?.dispatch(actionsLoadOrder.setFBLoadOrder(profileId, loadOrder));
};
31 changes: 6 additions & 25 deletions src/collections/modOptionsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IncludedModOptions,
} from './types';
import { hasIncludedModOptions, hasModAttributeCollection } from './utils';
import { actionsCollections } from './actions';
import { nameof } from '../nameof';
import { getGlobalSettings, getSpecialSettings, overrideModOptions } from '../modoptions';
import { LocalizationManager } from '../localization';
Expand Down Expand Up @@ -80,33 +81,13 @@ export const parseCollectionModOptionsData = async (

const includedModOptions = collection.includedModOptions;

if (includedModOptions === undefined || !includedModOptions.length) {
return;
}

const localizationManager = LocalizationManager.getInstance(api);
const { localize: t } = localizationManager;

const no = t('No');
const yes = t('Yes');
const result = await api.showDialog?.(
'question',
t('Override Mod Options'),
{
message: t(
`This collection contains custom Mod Options (MCM)!
Do you want to override your Mod Options with the custom Mod Options?
A backup of your original Mod Options will be kept and will be restored on collection removal.`
),
},
[{ label: no }, { label: yes }]
api.store?.dispatch(
actionsCollections.setCollectionModOptions(mod.attributes?.['collectionSlug'], {
includedModOptions: includedModOptions,
})
);

if (!result || result.action === no) {
return;
}

await overrideModOptions(mod, includedModOptions);
await Promise.resolve();
};

const hasModOptionsData = (collection: ICollectionData): collection is ICollectionDataWithSettingsData => {
Expand Down
Loading

0 comments on commit 259df1e

Please sign in to comment.