返回提交历史
Modified
src/controllers/custom/addItemsController.ts
+7
-3
Modified
src/controllers/custom/manageQuestsController.ts
+8
-4
Modified
src/services/questService.ts
+5
-2
Modified
static/webui/index.html
+3
-2
Modified
static/webui/script.js
+41
-114
Modified
static/webui/translations/de.js
+1
-0
Modified
static/webui/translations/en.js
+1
-0
Modified
static/webui/translations/es.js
+1
-0
Modified
static/webui/translations/fr.js
+1
-0
Modified
static/webui/translations/ru.js
+1
-0
Modified
static/webui/translations/uk.js
+1
-0
Modified
static/webui/translations/zh.js
+1
-0
XFEstudio/SpaceNinjaServer
fix(webui): cleanup state tracking (#3072)
In general, all options should always be available to avoid needless complexity and confusion. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3072 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
4a04e635
代码差异
12 个文件
+71
-125
@@ -10,9 +10,13 @@ export const addItemsController: RequestHandler = async (req, res) => {
10
10
for (const request of requests) {
11
11
await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, request.Fingerprint, true);
12
12
}
13
await inventory.save();
14
res.end();
15
broadcastInventoryUpdate(req);
13
if (inventory.isModified()) {
14
await inventory.save();
15
res.json(true);
16
broadcastInventoryUpdate(req);
17
} else {
18
res.json(false);
19
}
16
20
};
17
21
18
22
interface IAddItemRequest {
@@ -51,7 +51,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
51
51
break;
52
52
}
53
53
case "giveAll": {
54
allQuestKeys.forEach(questKey => addQuestKey(inventory, { ItemType: questKey }));
54
allQuestKeys.forEach(questKey => addQuestKey(inventory, { ItemType: questKey }, true));
55
55
break;
56
56
}
57
57
case "deleteKey": {
@@ -169,7 +169,11 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
169
169
break;
170
170
}
171
171
172
await inventory.save();
173
res.status(200).end();
174
broadcastInventoryUpdate(req);
172
if (inventory.isModified()) {
173
await inventory.save();
174
res.json(true);
175
broadcastInventoryUpdate(req);
176
} else {
177
res.json(false);
178
}
175
179
};
@@ -124,10 +124,13 @@ export const resetQuestKeyToStage = (
124
124
125
125
export const addQuestKey = (
126
126
inventory: TInventoryDatabaseDocument,
127
questKey: IQuestKeyDatabase
127
questKey: IQuestKeyDatabase,
128
silentFailure?: boolean
128
129
): IQuestKeyClient | undefined => {
129
130
if (inventory.QuestKeys.some(q => q.ItemType === questKey.ItemType)) {
130
logger.warn(`Quest key ${questKey.ItemType} already exists. It will not be added`);
131
if (!silentFailure) {
132
logger.warn(`Quest key ${questKey.ItemType} already exists. It will not be added`);
133
}
131
134
return;
132
135
}
133
136
@@ -71,7 +71,7 @@
71
71
<li class="nav-item">
72
72
<a class="nav-link" href="/webui/import" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_import"></a>
73
73
</li>
74
<li class="nav-item d-none" id="nav-guildView">
74
<li class="nav-item" id="nav-guildView">
75
75
<a class="nav-link" href="/webui/guildView" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_guildView"></a>
76
76
</li>
77
77
</ul>
@@ -573,7 +573,8 @@
573
573
</div>
574
574
</div>
575
575
<div id="guild-route" data-route="/webui/guildView" data-title="Guild | OpenWF WebUI">
576
<h3 id="guildView-loading" class="mb-0" data-loc="general_loading"></h3>
576
<h3 id="guildView-loading" data-loc="general_loading"></h3>
577
<h3 id="guildView-na" class="d-none" data-loc="guildView_na"></h3>
577
578
<h3 id="guildView-title" class="mb-0"></h3>
578
579
<p id="guildView-tier" class="text-body-secondary mb-0"></p>
579
580
<p id="guildView-class" class="text-body-secondary mb-0"></p>
@@ -61,6 +61,8 @@ function openWebSocket() {
61
61
if (single.getCurrentPath() == "/webui/") {
62
62
single.loadRoute("/webui/inventory");
63
63
}
64
$("#email").val("");
65
$("#password").val("");
64
66
$(".displayname").text(data.DisplayName);
65
67
window.accountId = data.id;
66
68
window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce + "&wsid=" + wsid;
@@ -268,9 +270,6 @@ function setLanguage(lang) {
268
270
// Not in prelogin state?
269
271
fetchItemList();
270
272
updateInventory();
271
if (single.getCurrentPath().startsWith("/webui/guildView")) {
272
updateInventory();
273
}
274
273
}
275
274
}
276
275
@@ -316,8 +315,6 @@ function fetchItemList() {
316
315
window.itemListPromise = new Promise(resolve => {
317
316
const req = $.get("/custom/getItemLists?lang=" + window.lang);
318
317
req.done(async data => {
319
window.allQuestKeys = data.QuestKeys;
320
321
318
await dictPromise;
322
319
323
320
document.querySelectorAll('[id^="datalist-"]').forEach(datalist => {
@@ -760,12 +757,7 @@ function updateInventory() {
760
757
req.done(data => {
761
758
window.itemListPromise.then(itemMap => {
762
759
window.didInitialInventoryUpdate = true;
763
if (data.GuildId) {
764
window.guildId = data.GuildId.$oid;
765
document.getElementById("nav-guildView").classList.remove("d-none");
766
} else {
767
document.getElementById("nav-guildView").classList.add("d-none");
768
}
760
window.guildId = data?.GuildId?.$oid;
769
761
770
762
const modularWeapons = [
771
763
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
@@ -1003,11 +995,6 @@ function updateInventory() {
1003
995
1004
996
document.getElementById("EvolutionProgress-list").innerHTML = "";
1005
997
data.EvolutionProgress?.forEach(item => {
1006
const datalist = document.getElementById("datalist-EvolutionProgress");
1007
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
1008
if (optionToRemove) {
1009
datalist.removeChild(optionToRemove);
1010
}
1011
998
const tr = document.createElement("tr");
1012
999
tr.setAttribute("data-item-type", item.ItemType);
1013
1000
{
@@ -1134,15 +1121,6 @@ function updateInventory() {
1134
1121
1135
1122
document.getElementById("FlavourItems-list").innerHTML = "";
1136
1123
data.FlavourItems.forEach(item => {
1137
const datalist = document.getElementById("datalist-FlavourItems");
1138
if (!data.FlavourItems.some(x => x.ItemType == item.ItemType)) {
1139
if (!datalist.querySelector(`option[data-key="${item.ItemType}"]`)) {
1140
reAddToItemList(itemMap, "FlavourItems", item.ItemType);
1141
}
1142
}
1143
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
1144
optionToRemove?.remove();
1145
1146
1124
const tr = document.createElement("tr");
1147
1125
tr.setAttribute("data-item-type", item.ItemType);
1148
1126
{
@@ -1159,7 +1137,6 @@ function updateInventory() {
1159
1137
a.href = "#";
1160
1138
a.onclick = function (event) {
1161
1139
event.preventDefault();
1162
reAddToItemList(itemMap, "FlavourItems", item.ItemType);
1163
1140
removeCustomization(item.ItemType);
1164
1141
};
1165
1142
a.title = loc("code_remove");
@@ -1175,11 +1152,6 @@ function updateInventory() {
1175
1152
document.getElementById("WeaponSkins-list").innerHTML = "";
1176
1153
data.WeaponSkins.forEach(item => {
1177
1154
if (item.ItemId.$oid.startsWith("ca70ca70ca70ca70")) return;
1178
const datalist = document.getElementById("datalist-WeaponSkins");
1179
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
1180
if (optionToRemove) {
1181
datalist.removeChild(optionToRemove);
1182
}
1183
1155
const tr = document.createElement("tr");
1184
1156
{
1185
1157
const td = document.createElement("td");
@@ -1196,7 +1168,6 @@ function updateInventory() {
1196
1168
a.onclick = function (event) {
1197
1169
event.preventDefault();
1198
1170
document.getElementById("WeaponSkins-list").removeChild(tr);
1199
reAddToItemList(itemMap, "WeaponSkins", item.ItemType);
1200
1171
disposeOfGear("WeaponSkins", item.ItemId.$oid);
1201
1172
};
1202
1173
a.title = loc("code_remove");
@@ -1210,15 +1181,6 @@ function updateInventory() {
1210
1181
document.getElementById("WeaponSkins-list").appendChild(tr);
1211
1182
});
1212
1183
1213
const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
1214
const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
1215
1216
if (datalistEvolutionProgress.length === 0) {
1217
formEvolutionProgress.classList.add("disabled");
1218
formEvolutionProgress.querySelector("input").disabled = true;
1219
formEvolutionProgress.querySelector("button").disabled = true;
1220
}
1221
1222
1184
if (data.CrewShipHarnesses?.length) {
1223
1185
window.plexus = {
1224
1186
id: data.CrewShipHarnesses[0].ItemId.$oid,
@@ -1262,26 +1224,12 @@ function updateInventory() {
1262
1224
1263
1225
// Populate quests route
1264
1226
document.getElementById("QuestKeys-list").innerHTML = "";
1265
window.allQuestKeys.forEach(questKey => {
1266
if (!data.QuestKeys.some(x => x.ItemType == questKey.uniqueName)) {
1267
const datalist = document.getElementById("datalist-QuestKeys");
1268
if (!datalist.querySelector(`option[data-key="${questKey.uniqueName}"]`)) {
1269
reAddToItemList(itemMap, "QuestKeys", questKey.uniqueName);
1270
}
1271
}
1272
});
1273
1227
data.QuestKeys.forEach(item => {
1274
1228
const tr = document.createElement("tr");
1275
1229
tr.setAttribute("data-item-type", item.ItemType);
1276
1230
const run = item.Progress[0]?.c ?? 0;
1277
1231
const stage = run == 0 ? item.Progress.length : item.Progress.map(p => p.c ?? 0).lastIndexOf(run);
1278
1232
1279
const datalist = document.getElementById("datalist-QuestKeys");
1280
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
1281
if (optionToRemove) {
1282
datalist.removeChild(optionToRemove);
1283
}
1284
1285
1233
{
1286
1234
const td = document.createElement("td");
1287
1235
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
@@ -1368,7 +1316,6 @@ function updateInventory() {
1368
1316
a.href = "#";
1369
1317
a.onclick = function (event) {
1370
1318
event.preventDefault();
1371
reAddToItemList(itemMap, "QuestKeys", item.ItemType);
1372
1319
debounce(doQuestUpdate, "deleteKey", item.ItemType);
1373
1320
};
1374
1321
a.title = loc("code_remove");
@@ -1380,22 +1327,6 @@ function updateInventory() {
1380
1327
document.getElementById("QuestKeys-list").appendChild(tr);
1381
1328
});
1382
1329
1383
const datalistQuestKeys = document.querySelectorAll("#datalist-QuestKeys option");
1384
const formQuestKeys = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]");
1385
const giveAllQuestButton = document.querySelector("button[onclick*=\"doBulkQuestUpdate('giveAll')\"]");
1386
1387
if (datalistQuestKeys.length === 0) {
1388
formQuestKeys.classList.add("disabled");
1389
formQuestKeys.querySelector("input").disabled = true;
1390
formQuestKeys.querySelector("button").disabled = true;
1391
giveAllQuestButton.disabled = true;
1392
} else {
1393
formQuestKeys.classList.remove("disabled");
1394
formQuestKeys.querySelector("input").disabled = false;
1395
formQuestKeys.querySelector("button").disabled = false;
1396
giveAllQuestButton.disabled = false;
1397
}
1398
1399
1330
// Populate mods route
1400
1331
document.getElementById("riven-list").innerHTML = "";
1401
1332
document.getElementById("mods-list").innerHTML = "";
@@ -1854,6 +1785,7 @@ function updateInventory() {
1854
1785
guildReq.done(guildData => {
1855
1786
window.itemListPromise.then(itemMap => {
1856
1787
document.getElementById("guildView-loading").classList.add("d-none");
1788
$("#guild-route > .row").removeClass("d-none");
1857
1789
1858
1790
document.getElementById("guildView-title").textContent = guildData.Name;
1859
1791
document.getElementById("guildView-tier").textContent = loc("guildView_tierDisplay")
@@ -1920,11 +1852,6 @@ function updateInventory() {
1920
1852
document.getElementById("TechProjects-list").innerHTML = "";
1921
1853
guildData.TechProjects ??= [];
1922
1854
guildData.TechProjects.forEach(item => {
1923
const datalist = document.getElementById("datalist-TechProjects");
1924
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
1925
if (optionToRemove) {
1926
datalist.removeChild(optionToRemove);
1927
}
1928
1855
const tr = document.createElement("tr");
1929
1856
tr.setAttribute("data-item-type", item.ItemType);
1930
1857
{
@@ -1975,7 +1902,6 @@ function updateInventory() {
1975
1902
a.href = "#";
1976
1903
a.onclick = function (event) {
1977
1904
event.preventDefault();
1978
reAddToItemList(itemMap, "TechProjects", item.ItemType);
1979
1905
debounce(removeGuildTechProject, item.ItemType);
1980
1906
};
1981
1907
a.title = loc("code_remove");
@@ -1992,16 +1918,6 @@ function updateInventory() {
1992
1918
["VaultDecoRecipes", "VaultMiscItems", "VaultShipDecorations"].forEach(vaultKey => {
1993
1919
document.getElementById(vaultKey + "-list").innerHTML = "";
1994
1920
(guildData[vaultKey] ??= []).forEach(item => {
1995
if (vaultKey == "VaultDecoRecipes") {
1996
const datalist = document.getElementById("datalist-VaultDecoRecipes");
1997
const optionToRemove = datalist.querySelector(
1998
`option[data-key="${item.ItemType}"]`
1999
);
2000
if (optionToRemove) {
2001
datalist.removeChild(optionToRemove);
2002
}
2003
}
2004
2005
1921
const tr = document.createElement("tr");
2006
1922
tr.setAttribute("data-item-type", item.ItemType);
2007
1923
{
@@ -2025,9 +1941,6 @@ function updateInventory() {
2025
1941
a.title = loc("code_remove");
2026
1942
a.onclick = e => {
2027
1943
e.preventDefault();
2028
if (vaultKey == "VaultDecoRecipes") {
2029
reAddToItemList(itemMap, vaultKey, item.ItemType);
2030
}
2031
1944
removeVaultItem(vaultKey, item.ItemType, item.ItemCount * -1);
2032
1945
};
2033
1946
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
@@ -2169,7 +2082,8 @@ function updateInventory() {
2169
2082
});
2170
2083
2171
2084
guildReq.fail(() => {
2172
single.loadRoute("/webui/inventory");
2085
document.getElementById("guildView-loading").classList.add("d-none");
2086
document.getElementById("guildView-na").classList.remove("d-none");
2173
2087
});
2174
2088
}
2175
2089
@@ -2252,9 +2166,13 @@ function doAcquireEquipment(category) {
2252
2166
}
2253
2167
])
2254
2168
});
2255
req.done(() => {
2169
req.done(didAnything => {
2256
2170
document.getElementById("acquire-type-" + category).value = "";
2257
updateInventory();
2171
if (didAnything) {
2172
updateInventory();
2173
} else {
2174
toast(loc("code_nothingToDo"));
2175
}
2258
2176
});
2259
2177
});
2260
2178
}
@@ -2419,8 +2337,11 @@ function doAcquireEvolution() {
2419
2337
$("#acquire-type-EvolutionProgress").addClass("is-invalid").focus();
2420
2338
return;
2421
2339
}
2422
2423
setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
2340
if (!document.querySelector("#EvolutionProgress-list [data-item-type='" + uniqueName + "']")) {
2341
setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
2342
} else {
2343
toast(loc("code_nothingToDo"));
2344
}
2424
2345
}
2425
2346
2426
2347
$(document).on("input", "input", function () {
@@ -2747,10 +2668,14 @@ function addMissingEvolutionProgress() {
2747
2668
const requests = [];
2748
2669
document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {
2749
2670
const uniqueName = elm.getAttribute("data-key");
2750
requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
2671
if (!document.querySelector("#EvolutionProgress-list [data-item-type='" + uniqueName + "']")) {
2672
requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
2673
}
2751
2674
});
2752
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
2753
return setEvolutionProgress(requests);
2675
if (requests.length == 0) {
2676
toast(loc("code_nothingToDo"));
2677
} else if (window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
2678
setEvolutionProgress(requests);
2754
2679
}
2755
2680
}
2756
2681
@@ -2770,7 +2695,9 @@ function maxRankAllEvolutions() {
2770
2695
});
2771
2696
2772
2697
if (Object.keys(requests).length > 0) {
2773
return setEvolutionProgress(requests);
2698
return setEvolutionProgress(requests).then(() => {
2699
toast(loc("code_succRankUp"));
2700
});
2774
2701
}
2775
2702
2776
2703
toast(loc("code_noEquipmentToRankUp"));
@@ -3572,6 +3499,7 @@ single.getRoute("#detailedView-route").on("beforeload", function () {
3572
3499
3573
3500
single.getRoute("#guild-route").on("beforeload", function () {
3574
3501
document.getElementById("guildView-loading").classList.remove("d-none");
3502
document.getElementById("guildView-na").classList.add("d-none");
3575
3503
document.getElementById("guildView-title").textContent = "";
3576
3504
document.getElementById("guildView-tier").textContent = "";
3577
3505
document.getElementById("guildView-class").textContent = "";
@@ -3597,6 +3525,7 @@ single.getRoute("#guild-route").on("beforeload", function () {
3597
3525
document.querySelectorAll("#guild-actions button").forEach(btn => {
3598
3526
btn.disabled = true;
3599
3527
});
3528
$("#guild-route > .row").addClass("d-none");
3600
3529
if (window.didInitialInventoryUpdate) {
3601
3530
updateInventory();
3602
3531
}
@@ -3686,23 +3615,17 @@ function doAddCurrency(currency) {
3686
3615
});
3687
3616
}
3688
3617
3689
function reAddToItemList(itemMap, datalist, itemType) {
3690
const item = itemMap[itemType];
3691
if (!item?.alwaysAvailable) {
3692
const option = document.createElement("option");
3693
option.setAttribute("data-key", itemType);
3694
option.value = item?.name ?? itemType;
3695
document.getElementById("datalist-" + datalist).appendChild(option);
3696
}
3697
}
3698
3699
3618
function doQuestUpdate(operation, itemType) {
3700
3619
revalidateAuthz().then(() => {
3701
3620
$.post({
3702
3621
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
3703
3622
contentType: "application/json"
3704
}).then(function () {
3705
updateInventory();
3623
}).then(function (didAnything) {
3624
if (didAnything) {
3625
updateInventory();
3626
} else {
3627
toast(loc("code_nothingToDo"));
3628
}
3706
3629
});
3707
3630
});
3708
3631
}
@@ -3712,8 +3635,12 @@ function doBulkQuestUpdate(operation) {
3712
3635
$.post({
3713
3636
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
3714
3637
contentType: "application/json"
3715
}).then(function () {
3716
updateInventory();
3638
}).then(function (didAnything) {
3639
if (didAnything) {
3640
updateInventory();
3641
} else {
3642
toast(loc("code_nothingToDo"));
3643
}
3717
3644
});
3718
3645
});
3719
3646
}
@@ -439,6 +439,7 @@ dict = {
439
439
theme_dark: `Dunkles Design`,
440
440
theme_light: `Helles Design`,
441
441
442
guildView_na: `[UNTRANSLATED] You're not in a clan.`,
442
443
guildView_cheats: `Clan-Cheats`,
443
444
guildView_techProjects: `Forschung`,
444
445
guildView_vaultDecoRecipes: `Dojo-Deko-Baupläne`,
@@ -438,6 +438,7 @@ dict = {
438
438
theme_dark: `Dark Theme`,
439
439
theme_light: `Light Theme`,
440
440
441
guildView_na: `You're not in a clan.`,
441
442
guildView_cheats: `Clan Cheats`,
442
443
guildView_techProjects: `Research`,
443
444
guildView_vaultDecoRecipes: `Dojo Deco Recipes`,
@@ -439,6 +439,7 @@ dict = {
439
439
theme_dark: `Tema Oscuro`,
440
440
theme_light: `Tema Claro`,
441
441
442
guildView_na: `[UNTRANSLATED] You're not in a clan.`,
442
443
guildView_cheats: `Trucos de Clan`,
443
444
guildView_techProjects: `Investigación`,
444
445
guildView_vaultDecoRecipes: `Planos de Decoración de Dojo`,
@@ -439,6 +439,7 @@ dict = {
439
439
theme_dark: `Thème sombre`,
440
440
theme_light: `Thème clair`,
441
441
442
guildView_na: `[UNTRANSLATED] You're not in a clan.`,
442
443
guildView_cheats: `Clan`,
443
444
guildView_techProjects: `Recherche`,
444
445
guildView_vaultDecoRecipes: `Schémas de décorations de dojo`,
@@ -439,6 +439,7 @@ dict = {
439
439
theme_dark: `Темная тема`,
440
440
theme_light: `Светлая тема`,
441
441
442
guildView_na: `[UNTRANSLATED] You're not in a clan.`,
442
443
guildView_cheats: `Читы Клана`,
443
444
guildView_techProjects: `Исследовения`,
444
445
guildView_vaultDecoRecipes: `Рецепты декораций Додзё`,
@@ -439,6 +439,7 @@ dict = {
439
439
theme_dark: `Темна тема`,
440
440
theme_light: `Світла тема`,
441
441
442
guildView_na: `[UNTRANSLATED] You're not in a clan.`,
442
443
guildView_cheats: `Кланові чити`,
443
444
guildView_techProjects: `Дослідження`,
444
445
guildView_vaultDecoRecipes: `Рецепти оздоблень Доджьо`,
@@ -439,6 +439,7 @@ dict = {
439
439
theme_dark: `暗色主题`,
440
440
theme_light: `亮色主题`,
441
441
442
guildView_na: `[UNTRANSLATED] You're not in a clan.`,
442
443
guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
443
444
guildView_techProjects: `研究`,
444
445
guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`,