Skip to content

Commit

Permalink
Merge pull request #126 from BUTR/collections-wip
Browse files Browse the repository at this point in the history
Collection Support
  • Loading branch information
Aragas authored Jun 29, 2024
2 parents f14495e + 6561476 commit 3dc1b8c
Show file tree
Hide file tree
Showing 146 changed files with 4,645 additions and 2,754 deletions.
4 changes: 4 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"ghcr.io/devcontainers/features/node:1": {
"version": "18.17.1",
"nodeGypDependencies": "true"
},
"ghcr.io/devcontainers/features/python:1": {

},
"ghcr.io/devcontainers/features/powershell:1": {
"version": "latest"
Expand All @@ -30,6 +33,7 @@
"ghcr.io/devcontainers/features/git",
"ghcr.io/butr/devcontainer/7z",
"ghcr.io/devcontainers/features/node",
"ghcr.io/devcontainers/features/python",
"ghcr.io/devcontainers/features/powershell"
],
"mounts": [
Expand Down
3 changes: 1 addition & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Project-specific editor formatting options
[*]
charset = utf-8
insert_final_newline = true
indent_style = space
indent_size = 2
indent_size = 2
61 changes: 59 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"project": "./tsconfig.json"
},
"env": {
"browser": true,
Expand All @@ -24,7 +25,7 @@
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended"
],
"plugins": ["prettier", "react", "react-hooks", "@typescript-eslint"],
"plugins": ["prettier", "react", "react-hooks", "@typescript-eslint", "import"],
"rules": {
"eqeqeq": "error",
"no-console": "warn",
Expand All @@ -38,6 +39,50 @@
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",

"require-await": "off",
"@typescript-eslint/require-await": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/strict-boolean-expressions":
[
"error",
{
"allowString": true,
"allowNumber": true,
"allowNullableObject": true,
"allowNullableBoolean": false,
"allowNullableString": false,
"allowNullableNumber": false,
"allowNullableEnum": false,
"allowAny": false
}
],

"sort-imports":
[
"error",
{
"ignoreCase": true,
"ignoreDeclarationSort": true
}
],

"import/order": [
1,
{
"groups":
[
"external",
"builtin",
"internal",
"sibling",
"parent",
"index"
]
}
],

"@typescript-eslint/naming-convention": [
"warn",
{
Expand All @@ -48,7 +93,19 @@
"match": true
}
}
// ,
// {
// "selector": "method",
// "format": ["camelCase"],
// "leadingUnderscore": "allow",
// "trailingUnderscore": "allow",
// "custom": {
// "regex": "^.*Async$",
// "match": true
// }
// }
],

"no-restricted-imports": [
"warn",
{
Expand Down
5 changes: 5 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
---------------------------------------------------------------------------------------------------
Version: 1.1.0
* Added back collection support
* Old collections will now set the Load Order on install
* New collections will be able to include MCM settings
---------------------------------------------------------------------------------------------------
Version: 1.0.13
* Added mandatory folders for manual game path hint
* Fixed undefined tools bug
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"copy-webpack-plugin": "^12.0.1",
"eslint": "^8.34.0",
"eslint-config-prettier": "^9",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.2",
Expand All @@ -55,6 +55,7 @@
"redux-act": "^1.7.7",
"ts-loader": "^9.2.6",
"ts-node": "^10.7.0",
"turbowalk": "git+https://github.com/Nexus-Mods/node-turbowalk",
"typescript": "^5.3.3",
"vortex-api": "git+https://github.com/Nexus-Mods/vortex-api",
"vortex-ext-common": "^0.4.0",
Expand Down
47 changes: 47 additions & 0 deletions src/blse/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { actions, types } from 'vortex-api';
import { findBLSEMod } from './utils';
import { hasSettingsInterfacePrimaryTool } from '../vortex';
import { GAME_ID } from '../common';

export const didDeployBLSE = (api: types.IExtensionApi): Promise<void> => {
const state = api.getState();

if (!hasSettingsInterfacePrimaryTool(state.settings.interface)) {
return Promise.resolve();
}

const primaryTool = state.settings.interface.primaryTool.mountandblade2bannerlord;

const blseMod = findBLSEMod(state);
if (blseMod && primaryTool === undefined) {
api.store?.dispatch(actions.setPrimaryTool(GAME_ID, 'blse-cli'));
}
if (!blseMod && primaryTool === 'blse-cli') {
api.store?.dispatch(actions.setPrimaryTool(GAME_ID, undefined!));
}

return Promise.resolve();
};

/**
* Event function, be careful
*/
export const didPurgeBLSE = (api: types.IExtensionApi): Promise<void> => {
const state = api.getState();

if (!hasSettingsInterfacePrimaryTool(state.settings.interface)) {
return Promise.resolve();
}

const primaryTool = state.settings.interface.primaryTool.mountandblade2bannerlord;
if (primaryTool !== 'blse-cli') {
return Promise.resolve();
}

const blseMod = findBLSEMod(state);
if (blseMod) {
api.store?.dispatch(actions.setPrimaryTool(GAME_ID, undefined!));
}

return Promise.resolve();
};
1 change: 1 addition & 0 deletions src/utils/blse/index.ts → src/blse/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './events';
export * from './installer';
export * from './modType';
export * from './utils';
export * from './vortex';
18 changes: 9 additions & 9 deletions src/utils/blse/installer.ts → src/blse/installer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import path from 'path';
import { selectors, types } from 'vortex-api';
import { isStoreXbox } from '..';
import { GAME_ID, BLSE_CLI_EXE, BINARY_FOLDER_XBOX, BINARY_FOLDER_STANDARD } from '../../common';
import path from 'path';
import { isStoreXbox } from '../vortex';
import { BINARY_FOLDER_STANDARD, BINARY_FOLDER_XBOX, BLSE_CLI_EXE, GAME_ID } from '../common';

export const installBLSE = async (api: types.IExtensionApi, files: string[]): Promise<types.IInstallResult> => {
const discovery = selectors.currentGameDiscovery(api.getState());
if (!discovery) {
export const installBLSE = (api: types.IExtensionApi, files: string[]): Promise<types.IInstallResult> => {
const discovery: types.IDiscoveryResult | undefined = selectors.currentGameDiscovery(api.getState());
if (discovery === undefined) {
return Promise.resolve({
instructions: [],
});
Expand All @@ -21,13 +21,13 @@ export const installBLSE = async (api: types.IExtensionApi, files: string[]): Pr
source: file,
destination: file,
}));
return {
return Promise.resolve({
instructions: instructions,
};
});
};

export const testBLSE = (files: string[], gameId: string): Promise<types.ISupportedResult> => {
const supported = gameId === GAME_ID && !!files.find((file) => path.basename(file) === BLSE_CLI_EXE);
const supported = gameId === GAME_ID && files.find((file) => path.basename(file) === BLSE_CLI_EXE) !== undefined;
return Promise.resolve({
supported,
requiredFiles: [],
Expand Down
6 changes: 4 additions & 2 deletions src/utils/blse/modType.ts → src/blse/modType.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { selectors, types } from 'vortex-api';
import { BLSE_CLI_EXE } from '../../common';
import { BLSE_CLI_EXE } from '../common';

export const getInstallPathBLSE = (api: types.IExtensionApi, game: types.IGame): string => {
const discovery: types.IDiscoveryResult | undefined = selectors.discoveryByGame(api.getState(), game.id);
return discovery?.path ?? ``;
};

export const isModTypeBLSE = (instructions: types.IInstruction[]): boolean => {
return instructions.some((inst) => inst.type === 'copy' && inst.source && inst.source.endsWith(BLSE_CLI_EXE));
return instructions.some(
(inst) => inst.type === 'copy' && inst.source !== undefined && inst.source.endsWith(BLSE_CLI_EXE)
);
};
47 changes: 23 additions & 24 deletions src/utils/blse/shared.ts → src/blse/utils.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import { gte } from 'semver';
import { actions, selectors, types, util } from 'vortex-api';
import { IFileInfo } from '@nexusmods/nexus-api/lib';
import { GAME_ID, BLSE_MOD_ID, BLSE_URL } from '../../common';
import { GetLocalizationManager, IBannerlordMod, IBannerlordModStorage } from '../../types';
import { BLSE_MOD_ID, BLSE_URL, GAME_ID } from '../common';
import { hasPersistentBannerlordMods } from '../vortex';
import { LocalizationManager } from '../localization';
import { IBannerlordMod } from '../types';

export const isModActive = (profile: types.IProfile, mod: IBannerlordMod): boolean => {
return profile.modState[mod.id]?.enabled ?? false;
};
const isModBLSE = (mod: IBannerlordMod) => {
const isModBLSE = (mod: IBannerlordMod): boolean => {
return mod.type === `bannerlord-blse` || (mod.attributes?.modId === 1 && mod.attributes?.source === `nexus`);
};

export const findBLSEMod = (api: types.IExtensionApi): IBannerlordMod | undefined => {
const state = api.getState();
const mods = (state.persistent.mods[GAME_ID] as IBannerlordModStorage) ?? {};
export const findBLSEMod = (state: types.IState): IBannerlordMod | undefined => {
if (hasPersistentBannerlordMods(state.persistent) === false) return undefined;

const mods = state.persistent.mods.mountandblade2bannerlord ?? {};
const blseMods: IBannerlordMod[] = Object.values(mods).filter((mod: IBannerlordMod) => isModBLSE(mod));

if (blseMods.length === 0) return undefined;

if (blseMods.length === 1) return blseMods[0];

return blseMods.reduce<IBannerlordMod | undefined>((prev: IBannerlordMod | undefined, iter: IBannerlordMod) => {
if (prev === undefined) {
if (!prev) {
return iter;
}
return gte(iter.attributes?.version ?? '0.0.0', prev.attributes?.version ?? '0.0.0') ? iter : prev;
Expand All @@ -31,7 +34,7 @@ export const findBLSEMod = (api: types.IExtensionApi): IBannerlordMod | undefine
export const findBLSEDownload = (api: types.IExtensionApi): string | undefined => {
const state = api.getState();
const downloadedFiles = state.persistent.downloads.files;
if (!downloadedFiles) {
if (downloadedFiles === undefined) {
return undefined;
}

Expand All @@ -51,53 +54,49 @@ export const findBLSEDownload = (api: types.IExtensionApi): string | undefined =

export const isActiveBLSE = (api: types.IExtensionApi): boolean => {
const state = api.getState();
const mods = (state.persistent.mods[GAME_ID] as IBannerlordModStorage) ?? {};

if (hasPersistentBannerlordMods(state.persistent) === false) return false;

const mods = state.persistent.mods.mountandblade2bannerlord ?? {};
const blseMods: IBannerlordMod[] = Object.values(mods).filter((mod: IBannerlordMod) => isModBLSE(mod));

if (blseMods.length === 0) {
return false;
}

const profile = selectors.activeProfile(state);
const profile: types.IProfile | undefined = selectors.activeProfile(state);
return blseMods.filter((x) => isModActive(profile, x)).length >= 1;
};

export const deployBLSE = async (api: types.IExtensionApi): Promise<void> => {
await util.toPromise((cb) => api.events.emit('deploy-mods', cb));
await util.toPromise((cb) => api.events.emit('start-quick-discovery', () => cb(null)));

const discovery = selectors.currentGameDiscovery(api.getState());
const discovery: types.IDiscoveryResult | undefined = selectors.currentGameDiscovery(api.getState());
const tool = discovery?.tools?.['blse-cli'];
if (tool) {
api.store?.dispatch(actions.setPrimaryTool(GAME_ID, tool.id));
}
};

export const downloadBLSE = async (
api: types.IExtensionApi,
getLocalizationManager: GetLocalizationManager,
update?: boolean
): Promise<void> => {
const localizationManager = getLocalizationManager();
const t = localizationManager.localize;
export const downloadBLSE = async (api: types.IExtensionApi, shouldUpdate: boolean = false): Promise<void> => {
const { localize: t } = LocalizationManager.getInstance(api);

api.dismissNotification?.('blse-missing');
api.sendNotification?.({
id: 'blse-installing',
message: update ? t('Updating BLSE') : t('Installing BLSE'),
message: shouldUpdate ? t('Updating BLSE') : t('Installing BLSE'),
type: 'activity',
noDismiss: true,
allowSuppress: false,
});

if (api.ext?.ensureLoggedIn) {
await api.ext.ensureLoggedIn();
}
await api.ext?.ensureLoggedIn?.();

try {
const modFiles = (await api.ext.nexusGetModFiles?.(GAME_ID, BLSE_MOD_ID)) ?? [];

const fileTime = (input: IFileInfo) => Number.parseInt(input.uploaded_time, 10);
const fileTime = (input: IFileInfo): number => Number.parseInt(input.uploaded_time, 10);

const file = modFiles.filter((file) => file.category_id === 1).sort((lhs, rhs) => fileTime(lhs) - fileTime(rhs))[0];

Expand All @@ -117,7 +116,7 @@ export const downloadBLSE = async (
const modId = await util.toPromise<string>((cb) =>
api.events.emit('start-install-download', dlId, { allowAutoEnable: false }, cb)
);
const profile = selectors.activeProfile(api.getState());
const profile: types.IProfile | undefined = selectors.activeProfile(api.getState());
await actions.setModsEnabled(api, profile.id, [modId], true, {
allowAutoDeploy: false,
installed: true,
Expand Down
Loading

0 comments on commit 3dc1b8c

Please sign in to comment.