Skip to content

Commit 28302d8

Browse files
committed
- Added GameDetails.multipleDataRoots and GameDetails.pluginDataRoot fields.
- Mod import options now allows for selecting installation directory when game has `multipleDataRoots` enabled. - Mod installation dir is deduced automatically when possible. - Improved Oblivion Remastered support.
1 parent a306b9e commit 28302d8

15 files changed

+479
-120
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ Now, only the files in `standard/Data` directory will be added for this mod.
230230

231231
**Tip:** Multiple directories can be marked as **root data dir** if needed.
232232

233+
Some games will use multiple directories for mods. In this case, Stellar will attempt to figure out where to install mods if possible, but some mods may be packaged in a way where this is not possible. If Stellar cannot deduce the installation directory, you will need to choose which folder to install the mod to:
234+
235+
![Add Mod Example 2](/docs/mod-add-3.png)
236+
237+
Select the installation directory on the left. If no folder is selected, mod will be installed to the top level data directory.
238+
233239
### FOMOD installers
234240

235241
Some mods are packaged with special metadata known as FOMOD that allows for customizing the installation through a guided flow. Stellar supports FOMOD and will automatically show the installation wizard for FOMOD-compatible mods, as shown in the example below:
@@ -375,6 +381,10 @@ The default game installation paths for this game. These are typically the defau
375381
- **Game Plugin List Path** - (Optional) The location where the plugin list should be saved.
376382
- **Steam IDs** - (Optional) Any Steam game IDs associated with this installation (if applicable).
377383

384+
#### Multiple Mod Data Roots
385+
386+
Whether or not the game uses multiple subdirectories for mod data.
387+
378388
#### Game Binaries
379389

380390
(Optional) The names of binaries (i.e. the `exe` of the game). Also include the names of any possible mod loader binaries for the game here.
@@ -391,6 +401,10 @@ The default game installation paths for this game. These are typically the defau
391401

392402
Whether or not external plugin file management is required for this game.
393403

404+
#### Plugin Data Directory
405+
406+
(Optional) The directory where plugin files are located, relative to the **Game Data Directory**.
407+
394408
#### Plugin List Type
395409

396410
(Optional) The type of plugin list for the game, if applicable.

docs/mod-add-3.png

44.1 KB
Loading

game-db.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,38 +100,40 @@
100100
{
101101
"steamId": ["2623190"],
102102
"rootDir": "%PROGRAMFILES%\\Steam\\steamapps\\common\\Oblivion Remastered",
103-
"modDir": "OblivionRemastered\\Content\\Dev\\ObvData\\Data",
103+
"modDir": ".",
104104
"pluginListPath": "OblivionRemastered\\Content\\Dev\\ObvData\\Data\\Plugins.txt",
105105
"configFilePath": "%USERPROFILE%\\Documents\\My Games\\Oblivion Remastered\\Saved\\Config\\Windows",
106106
"saveFolderPath": "%USERPROFILE%\\Documents\\My Games\\Oblivion Remastered\\Saved\\SaveGames"
107107
},
108108
{
109109
"steamId": ["2623190"],
110110
"rootDir": "%PROGRAMFILES(X86)%\\Steam\\steamapps\\common\\Oblivion Remastered",
111-
"modDir": "OblivionRemastered\\Content\\Dev\\ObvData\\Data",
111+
"modDir": ".",
112112
"pluginListPath": "OblivionRemastered\\Content\\Dev\\ObvData\\Data\\Plugins.txt",
113113
"configFilePath": "%USERPROFILE%\\Documents\\My Games\\Oblivion Remastered\\Saved\\Config\\Windows",
114114
"saveFolderPath": "%USERPROFILE%\\Documents\\My Games\\Oblivion Remastered\\Saved\\SaveGames"
115115
},
116116
{
117117
"rootDir": "C:\\XboxGames\\Oblivion Remastered\\Content",
118-
"modDir": "OblivionRemastered\\Content\\Dev\\ObvData\\Data",
118+
"modDir": ".",
119119
"pluginListPath": "OblivionRemastered\\Content\\Dev\\ObvData\\Data\\Plugins.txt",
120120
"configFilePath": "%USERPROFILE%\\Documents\\My Games\\Oblivion Remastered\\Saved\\Config\\Windows",
121121
"saveFolderPath": "%USERPROFILE%\\Documents\\My Games\\Oblivion Remastered\\Saved\\SaveGames"
122122
},
123123
{
124124
"steamId": ["2623190"],
125125
"rootDir": "~/.local/share/Steam/steamapps/common/Oblivion Remastered",
126-
"modDir": "OblivionRemastered/Content/Dev/ObvData/Data",
126+
"modDir": ".",
127127
"pluginListPath": "OblivionRemastered/Content/Dev/ObvData/Data/Plugins.txt",
128128
"configFilePath": "$/pfx/drive_c/users/steamuser/Documents/My Games/Oblivion Remastered/Saved/Config/Windows",
129129
"saveFolderPath": "$/pfx/drive_c/users/steamuser/Documents/My Games/Oblivion Remastered/Saved/SaveGames"
130130
}
131131
],
132+
"multipleDataRoots": true,
132133
"gameBinary": ["OblivionRemastered.exe"],
133134
"pluginListType": "Gamebryo",
134135
"pluginFormats": ["esm", "esp"],
136+
"pluginDataRoot": "OblivionRemastered/Content/Dev/ObvData/Data",
135137
"saveFormats": ["sav"],
136138
"pinnedPlugins": [
137139
{

game-db.schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,18 @@
3838
},
3939
"type": "array"
4040
},
41+
"multipleDataRoots": {
42+
"type": "boolean"
43+
},
4144
"pinnedPlugins": {
4245
"items": {
4346
"$ref": "#/definitions/GameDetails.PinnedPlugin"
4447
},
4548
"type": "array"
4649
},
50+
"pluginDataRoot": {
51+
"type": "string"
52+
},
4753
"pluginFormats": {
4854
"items": {
4955
"type": "string"

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"author": "Mychal Thompson <mychal.r.thompson@gmail.com>",
44
"repository": "https://github.com/lVlyke/stellar-mod-loader",
55
"license": "GPL-3.0",
6-
"version": "0.12.1",
6+
"version": "0.13.0",
77
"main": "electron.js",
88
"scripts": {
99
"app:build-dist-debug": "npm run build && npm run package",

src/app/components/game-manager/game-manager.component.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@
131131
</button>
132132
}
133133

134+
<!-- Multiple Data Roots -->
135+
<mat-checkbox name="multipleDataRoots"
136+
[disabled]="!isCustomGameDetails"
137+
[(ngModel)]="activeGameDetails.multipleDataRoots">
138+
Multiple Mod Data Roots
139+
</mat-checkbox>
140+
134141
<!-- Game Binaries -->
135142
<app-select-edit name="gameBinaries"
136143
label="Game Binaries"
@@ -164,6 +171,23 @@
164171
</mat-checkbox>
165172
}
166173

174+
@if (activeGameDetails.pluginDataRoot !== undefined) {
175+
<!-- Plugin Data Root -->
176+
<mat-form-field appearance="fill" [attr.subscript]="false">
177+
<mat-label>Plugin Data Directory</mat-label>
178+
<input matInput
179+
name="pluginDataRot"
180+
type="text"
181+
[disabled]="!isCustomGameDetails"
182+
[(ngModel)]="activeGameDetails.pluginDataRoot">
183+
</mat-form-field>
184+
} @else if (isCustomGameDetails && activeGameDetails.pluginFormats.length > 0) {
185+
<button mat-raised-button (click)="activeGameDetails.pluginDataRoot = ''">
186+
<mat-icon color="accent">add</mat-icon>
187+
Add Plugin Data Directory
188+
</button>
189+
}
190+
167191
@if (!!activeGameDetails.pluginListType) {
168192
<!-- Plugin List Type -->
169193
<mat-form-field appearance="fill" [attr.subscript]="false">

src/app/components/mod-import-options/mod-import-options.component.html

Lines changed: 94 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,54 +12,104 @@
1212
(ngModelChange)="importRequestChange$.emit(importRequest)">
1313
</mat-form-field>
1414

15-
<mat-label class="files-label">Mod Files</mat-label>
16-
17-
<div class="mod-file-tree-container">
18-
<mat-tree class="file-tree" [dataSource]="filesDataSource" [treeControl]="treeControl">
19-
<!-- File node -->
20-
<mat-tree-node *matTreeNodeDef="let node"
21-
matTreeNodePadding
22-
[matTreeNodePaddingIndent]="24"
23-
[attr.mod-enabled]="isNodeEnabled$(node) | async">
24-
<button type="button" mat-icon-button disabled></button>
25-
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: node }" />
26-
</mat-tree-node>
15+
@if (importRequest.modFilePrefix && !showInstallFolderPicker) {
16+
<mat-form-field appearance="fill">
17+
<mat-label>Installation Directory</mat-label>
18+
<input matInput
19+
name="modFilePrefix"
20+
type="text"
21+
[disabled]="true"
22+
[ngModel]="importRequest.modFilePrefix">
23+
</mat-form-field>
24+
}
2725

28-
<!-- Directory node -->
29-
<mat-tree-node *matTreeNodeDef="let node; when: nodeIsDir"
30-
matTreeNodePadding
31-
[matTreeNodePaddingIndent]="24"
32-
[attr.mod-enabled]="!isRootDirNode(node) && (isNodeEnabled$(node) | async)">
33-
<button type="button" mat-icon-button matTreeNodeToggle>
34-
<mat-icon class="mat-icon-rtl-mirror">
35-
{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}
36-
</mat-icon>
37-
</button>
26+
<div class="editor-pane">
27+
@if (showInstallFolderPicker) {
28+
<div>
29+
<mat-label class="files-label">Installation Directory</mat-label>
3830

39-
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: node }" />
31+
<div class="mod-file-tree-container">
32+
<mat-radio-group name="installationDir"
33+
[ngModel]="importRequest.modFilePrefix"
34+
(ngModelChange)="setModFilePrefix($event)">
35+
<mat-tree class="file-tree" [dataSource]="gameDirDataSource" [treeControl]="treeControl">
36+
<mat-tree-node *matTreeNodeDef="let node"
37+
matTreeNodePadding
38+
[matTreeNodePaddingIndent]="24">
39+
40+
@if (!node.terminal) {
41+
<button type="button" mat-icon-button matTreeNodeToggle>
42+
<mat-icon class="mat-icon-rtl-mirror">
43+
{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}
44+
</mat-icon>
45+
</button>
46+
} @else {
47+
<button type="button" mat-icon-button disabled></button>
48+
}
49+
50+
<mat-radio-button [value]="node.fullPath" />
51+
52+
<span class="mod-path-part">
53+
{{ node.pathPart }}
54+
</span>
55+
</mat-tree-node>
56+
</mat-tree>
57+
</mat-radio-group>
58+
</div>
59+
</div>
60+
}
4061

41-
@if (!isRootDirNode(node)) {
42-
<a mat-button
43-
class="path-info-badge root-dir-action root-dir-select-action"
44-
(click)="addModRootSubdir(node.fullPath)">
45-
&lt;set data dir&gt;
46-
</a>
47-
} @else {
48-
<span class="path-info-badge root-dir-marker"
49-
matColor="primary"
50-
matTooltip="Only files inside this directory will be added">
51-
&lt;data dir&gt;
52-
</span>
62+
<div>
63+
<mat-label class="files-label">Mod Files</mat-label>
5364

54-
<mat-icon color="warn"
55-
class="path-info-badge root-dir-action root-dir-clear-action"
56-
matTooltip="Clear data dir"
57-
(click)="removeModRootSubdir(node.fullPath)">
58-
close
59-
</mat-icon>
60-
}
61-
</mat-tree-node>
62-
</mat-tree>
65+
<div class="mod-file-tree-container">
66+
<mat-tree class="file-tree" [dataSource]="modFileTreeDataSource" [treeControl]="treeControl">
67+
<!-- File node -->
68+
<mat-tree-node *matTreeNodeDef="let node"
69+
matTreeNodePadding
70+
[matTreeNodePaddingIndent]="24"
71+
[attr.mod-enabled]="isNodeEnabled$(node) | async">
72+
<button type="button" mat-icon-button disabled></button>
73+
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: node }" />
74+
</mat-tree-node>
75+
76+
<!-- Directory node -->
77+
<mat-tree-node *matTreeNodeDef="let node; when: nodeIsDir"
78+
matTreeNodePadding
79+
[matTreeNodePaddingIndent]="24"
80+
[attr.mod-enabled]="!isRootDirNode(node) && (isNodeEnabled$(node) | async)">
81+
<button type="button" mat-icon-button matTreeNodeToggle>
82+
<mat-icon class="mat-icon-rtl-mirror">
83+
{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}
84+
</mat-icon>
85+
</button>
86+
87+
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: node }" />
88+
89+
@if (!isRootDirNode(node)) {
90+
<a mat-button
91+
class="path-info-badge root-dir-action root-dir-select-action"
92+
(click)="addModRootSubdir(node.fullPath)">
93+
&lt;set data dir&gt;
94+
</a>
95+
} @else {
96+
<span class="path-info-badge root-dir-marker"
97+
matColor="primary"
98+
matTooltip="Only files inside this directory will be added">
99+
&lt;data dir&gt;
100+
</span>
101+
102+
<mat-icon color="warn"
103+
class="path-info-badge root-dir-action root-dir-clear-action"
104+
matTooltip="Clear data dir"
105+
(click)="removeModRootSubdir(node.fullPath)">
106+
close
107+
</mat-icon>
108+
}
109+
</mat-tree-node>
110+
</mat-tree>
111+
</div>
112+
</div>
63113
</div>
64114
}
65115
</form>

src/app/components/mod-import-options/mod-import-options.component.scss

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,31 @@
77
display: block;
88
}
99

10+
.editor-pane {
11+
display: flex;
12+
flex-direction: row;
13+
gap: 1rem;
14+
15+
> * {
16+
display: flex;
17+
flex-direction: column;
18+
flex: 1 1 100%;
19+
20+
> .mod-file-tree-container {
21+
display: flex;
22+
flex-direction: column;
23+
flex: 1 1 100%;
24+
}
25+
}
26+
}
27+
28+
mat-radio-group {
29+
display: contents;
30+
}
31+
1032
mat-tree {
33+
flex: 1 1 100%;
34+
border-radius: 1rem;
1135

1236
[mod-enabled] {
1337

0 commit comments

Comments
 (0)