forked from OpenWF/SpaceNinjaServer
Compare commits
70 Commits
1a2d8ab19a
...
f796f9a851
Author | SHA1 | Date | |
---|---|---|---|
f796f9a851 | |||
e18b8e09ea | |||
0d8044b87c | |||
a109ea6c5d | |||
7eb95c995c | |||
dc8f32d4d8 | |||
ba70ba88dd | |||
08d4a03c50 | |||
45feff682b | |||
65be1083ce | |||
07e7c9e897 | |||
dcb26471c9 | |||
5a75d88385 | |||
a35572e306 | |||
c46c43f143 | |||
98ed2b5ee4 | |||
7aa1b12306 | |||
b410f6b554 | |||
1dffcf979f | |||
c86bba017b | |||
2c499cec3d | |||
d6145561fd | |||
1545cdb8ce | |||
80b5e2df7f | |||
76e61129bf | |||
ea3e299861 | |||
3d8c1d036a | |||
773f96ebbc | |||
2a80307c26 | |||
a40ff27fea | |||
c9a4359714 | |||
280ed8bef1 | |||
9c89e907b1 | |||
b54fd96098 | |||
c7c7fd4ea0 | |||
a75e6d6b95 | |||
5089f67146 | |||
0416221d15 | |||
26729ce21a | |||
ee4adc7d55 | |||
29aadf4e78 | |||
0b32bc21be | |||
e09e5ebec2 | |||
b2de8608c6 | |||
2b23db1433 | |||
a45bacc388 | |||
46d37d3688 | |||
41686aea88 | |||
61ac2f8b72 | |||
d2ab894c01 | |||
8c85cdcd1d | |||
aa6191f033 | |||
e26d2635fb | |||
dd6ae8898f | |||
499ca23ffb | |||
d3102acb7c | |||
363028c9ce | |||
1d60745f18 | |||
a9b3b16d31 | |||
fd1d72a1cf | |||
75832afdbe | |||
aa916d2820 | |||
5a5f6106a3 | |||
24d9dc27e2 | |||
5e05a15743 | |||
545b949202 | |||
0c9b27a29b | |||
cfa750b6f7 | |||
049baa4313 | |||
e267ca8f55 |
18
.eslintrc
18
.eslintrc
@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"plugins": ["@typescript-eslint", "prettier", "import"],
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
|
"plugin:import/recommended",
|
||||||
|
"plugin:import/typescript"
|
||||||
],
|
],
|
||||||
"plugins": ["@typescript-eslint", "prettier"],
|
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
@ -26,11 +28,19 @@
|
|||||||
"no-case-declarations": "error",
|
"no-case-declarations": "error",
|
||||||
"prettier/prettier": "error",
|
"prettier/prettier": "error",
|
||||||
"no-mixed-spaces-and-tabs": "error",
|
"no-mixed-spaces-and-tabs": "error",
|
||||||
"require-await": "off",
|
"@typescript-eslint/require-await": "error",
|
||||||
"@typescript-eslint/require-await": "error"
|
"import/no-named-as-default-member": "off",
|
||||||
|
"import/no-cycle": "warn"
|
||||||
},
|
},
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"import/extensions": [ ".ts" ],
|
||||||
|
"import/resolver": {
|
||||||
|
"typescript": true,
|
||||||
|
"node": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -19,6 +19,7 @@ jobs:
|
|||||||
- run: npm run lint:ci
|
- run: npm run lint:ci
|
||||||
- run: npm run prettier
|
- run: npm run prettier
|
||||||
- run: npm run update-translations
|
- run: npm run update-translations
|
||||||
|
- run: npm run fix-imports
|
||||||
- name: Fail if there are uncommitted changes
|
- name: Fail if there are uncommitted changes
|
||||||
run: |
|
run: |
|
||||||
if [[ -n "$(git status --porcelain)" ]]; then
|
if [[ -n "$(git status --porcelain)" ]]; then
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
"unlockAllSimarisResearchEntries": false,
|
"unlockAllSimarisResearchEntries": false,
|
||||||
"disableDailyTribute": false,
|
"disableDailyTribute": false,
|
||||||
"spoofMasteryRank": -1,
|
"spoofMasteryRank": -1,
|
||||||
|
"relicRewardItemCountMultiplier": 1,
|
||||||
"nightwaveStandingMultiplier": 1,
|
"nightwaveStandingMultiplier": 1,
|
||||||
"unfaithfulBugFixes": {
|
"unfaithfulBugFixes": {
|
||||||
"ignore1999LastRegionPlayed": false,
|
"ignore1999LastRegionPlayed": false,
|
||||||
@ -77,7 +78,9 @@
|
|||||||
"nightwaveOverride": "",
|
"nightwaveOverride": "",
|
||||||
"allTheFissures": "",
|
"allTheFissures": "",
|
||||||
"circuitGameModes": null,
|
"circuitGameModes": null,
|
||||||
"darvoStockMultiplier": 1
|
"darvoStockMultiplier": 1,
|
||||||
|
"varziaOverride": "",
|
||||||
|
"varziaFullyStocked": false
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"keepVendorsExpired": false
|
"keepVendorsExpired": false
|
||||||
|
2196
package-lock.json
generated
2196
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,9 @@
|
|||||||
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
||||||
"lint:fix": "eslint --fix --ext .ts .",
|
"lint:fix": "eslint --fix --ext .ts .",
|
||||||
"prettier": "prettier --write .",
|
"prettier": "prettier --write .",
|
||||||
"update-translations": "cd scripts && node update-translations.js"
|
"update-translations": "cd scripts && node update-translations.js",
|
||||||
|
"fix-imports": "cd scripts && node fix-imports.js",
|
||||||
|
"fix": "npm run update-translations && npm run fix-imports && npm run prettier"
|
||||||
},
|
},
|
||||||
"license": "GNU",
|
"license": "GNU",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -37,7 +39,7 @@
|
|||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"typescript": "^5.5",
|
"typescript": "^5.5",
|
||||||
"undici": "^7.10.0",
|
"undici": "^7.10.0",
|
||||||
"warframe-public-export-plus": "^0.5.74",
|
"warframe-public-export-plus": "^0.5.78",
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0",
|
"winston-daily-rotate-file": "^5.0.0",
|
||||||
@ -47,6 +49,8 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||||
"@typescript-eslint/parser": "^8.28.0",
|
"@typescript-eslint/parser": "^8.28.0",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
|
"eslint-import-resolver-typescript": "^4.4.4",
|
||||||
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-prettier": "^5.2.5",
|
"eslint-plugin-prettier": "^5.2.5",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"tree-kill": "^1.2.2"
|
"tree-kill": "^1.2.2"
|
||||||
|
46
scripts/fix-imports.js
Normal file
46
scripts/fix-imports.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const root = path.join(process.cwd(), "..");
|
||||||
|
|
||||||
|
function listFiles(dir) {
|
||||||
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
|
let results = [];
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = path.join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
results = results.concat(listFiles(fullPath));
|
||||||
|
} else {
|
||||||
|
results.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = listFiles(path.join(root, "src"));
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
let content;
|
||||||
|
try {
|
||||||
|
content = fs.readFileSync(file, "utf8");
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const dir = path.dirname(file);
|
||||||
|
const fixedContent = content.replaceAll(/} from "([^"]+)";/g, (sub, importPath) => {
|
||||||
|
if (!importPath.startsWith("@/")) {
|
||||||
|
const fullImportPath = path.resolve(dir, importPath);
|
||||||
|
if (fs.existsSync(fullImportPath + ".ts")) {
|
||||||
|
const relative = path.relative(root, fullImportPath).replace(/\\/g, "/");
|
||||||
|
const fixedPath = "@/" + relative;
|
||||||
|
console.log(`${importPath} -> ${fixedPath}`);
|
||||||
|
return sub.split(importPath).join(fixedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sub;
|
||||||
|
});
|
||||||
|
if (content != fixedContent) {
|
||||||
|
fs.writeFileSync(file, fixedContent, "utf8");
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
|
|||||||
const strings = extractStrings(line);
|
const strings = extractStrings(line);
|
||||||
if (Object.keys(strings).length > 0) {
|
if (Object.keys(strings).length > 0) {
|
||||||
Object.entries(strings).forEach(([key, value]) => {
|
Object.entries(strings).forEach(([key, value]) => {
|
||||||
if (targetStrings.hasOwnProperty(key)) {
|
if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED] ")) {
|
||||||
fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`);
|
fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`);
|
||||||
} else {
|
} else {
|
||||||
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
||||||
|
@ -24,7 +24,6 @@ export const artifactsController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
if (itemIndex !== -1) {
|
if (itemIndex !== -1) {
|
||||||
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
|
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
|
||||||
inventory.markModified(`Upgrades.${itemIndex}.UpgradeFingerprint`);
|
|
||||||
} else {
|
} else {
|
||||||
itemIndex =
|
itemIndex =
|
||||||
Upgrades.push({
|
Upgrades.push({
|
||||||
|
@ -14,15 +14,17 @@ import {
|
|||||||
addRecipes,
|
addRecipes,
|
||||||
occupySlot,
|
occupySlot,
|
||||||
combineInventoryChanges,
|
combineInventoryChanges,
|
||||||
addKubrowPetPrint
|
addKubrowPetPrint,
|
||||||
|
addPowerSuit,
|
||||||
|
addEquipment
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { InventorySlot, IPendingRecipeDatabase, Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
|
||||||
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { IRecipe } from "warframe-public-export-plus";
|
import { IRecipe } from "warframe-public-export-plus";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
|
import { EquipmentFeatures, IEquipmentClient, Status } from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
interface IClaimCompletedRecipeRequest {
|
interface IClaimCompletedRecipeRequest {
|
||||||
RecipeIds: IOid[];
|
RecipeIds: IOid[];
|
||||||
@ -124,17 +126,122 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
|
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
|
||||||
addKubrowPetPrint(inventory, pet, InventoryChanges);
|
addKubrowPetPrint(inventory, pet, InventoryChanges);
|
||||||
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
||||||
InventoryChanges = {
|
if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
|
||||||
...InventoryChanges,
|
// Quite the special case here...
|
||||||
...(await addItem(
|
// We don't just get Umbra, but also Skiajati and Umbra Mods. Both items are max rank, potatoed, and with the mods are pre-installed.
|
||||||
|
// Source: https://wiki.warframe.com/w/The_Sacrifice, https://wiki.warframe.com/w/Excalibur/Umbra, https://wiki.warframe.com/w/Skiajati
|
||||||
|
|
||||||
|
const umbraModA = (
|
||||||
|
await addItem(
|
||||||
|
inventory,
|
||||||
|
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
`{"lvl":5}`
|
||||||
|
)
|
||||||
|
).Upgrades![0];
|
||||||
|
const umbraModB = (
|
||||||
|
await addItem(
|
||||||
|
inventory,
|
||||||
|
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
`{"lvl":5}`
|
||||||
|
)
|
||||||
|
).Upgrades![0];
|
||||||
|
const umbraModC = (
|
||||||
|
await addItem(
|
||||||
|
inventory,
|
||||||
|
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
`{"lvl":5}`
|
||||||
|
)
|
||||||
|
).Upgrades![0];
|
||||||
|
const sacrificeModA = (
|
||||||
|
await addItem(
|
||||||
|
inventory,
|
||||||
|
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
`{"lvl":5}`
|
||||||
|
)
|
||||||
|
).Upgrades![0];
|
||||||
|
const sacrificeModB = (
|
||||||
|
await addItem(
|
||||||
|
inventory,
|
||||||
|
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
`{"lvl":5}`
|
||||||
|
)
|
||||||
|
).Upgrades![0];
|
||||||
|
InventoryChanges.Upgrades ??= [];
|
||||||
|
InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
|
||||||
|
|
||||||
|
await addPowerSuit(
|
||||||
inventory,
|
inventory,
|
||||||
recipe.resultType,
|
"/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
|
||||||
recipe.num,
|
{
|
||||||
false,
|
Configs: [
|
||||||
undefined,
|
{
|
||||||
pendingRecipe.TargetFingerprint
|
Upgrades: [
|
||||||
))
|
"",
|
||||||
};
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
umbraModA.ItemId.$oid,
|
||||||
|
umbraModB.ItemId.$oid,
|
||||||
|
umbraModC.ItemId.$oid
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
XP: 900_000,
|
||||||
|
Features: EquipmentFeatures.DOUBLE_CAPACITY
|
||||||
|
},
|
||||||
|
InventoryChanges
|
||||||
|
);
|
||||||
|
inventory.XPInfo.push({
|
||||||
|
ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
|
||||||
|
XP: 900_000
|
||||||
|
});
|
||||||
|
|
||||||
|
addEquipment(
|
||||||
|
inventory,
|
||||||
|
"Melee",
|
||||||
|
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
|
||||||
|
{
|
||||||
|
Configs: [
|
||||||
|
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
|
||||||
|
],
|
||||||
|
XP: 450_000,
|
||||||
|
Features: EquipmentFeatures.DOUBLE_CAPACITY
|
||||||
|
},
|
||||||
|
InventoryChanges
|
||||||
|
);
|
||||||
|
inventory.XPInfo.push({
|
||||||
|
ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
|
||||||
|
XP: 450_000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
InventoryChanges = {
|
||||||
|
...InventoryChanges,
|
||||||
|
...(await addItem(
|
||||||
|
inventory,
|
||||||
|
recipe.resultType,
|
||||||
|
recipe.num,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
pendingRecipe.TargetFingerprint
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
config.claimingBlueprintRefundsIngredients &&
|
config.claimingBlueprintRefundsIngredients &&
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { checkCalendarChallengeCompletion, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
|
import { checkCalendarAutoAdvance, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import { getWorldState } from "@/src/services/worldStateService";
|
import { getWorldState } from "@/src/services/worldStateService";
|
||||||
@ -28,7 +28,7 @@ export const completeCalendarEventController: RequestHandler = async (req, res)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex;
|
calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex;
|
||||||
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
|
checkCalendarAutoAdvance(inventory, currentSeason);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: inventoryChanges,
|
InventoryChanges: inventoryChanges,
|
||||||
|
@ -21,7 +21,8 @@ import {
|
|||||||
updateCurrency
|
updateCurrency
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { ITypeCount } from "@/src/types/commonTypes";
|
||||||
|
import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const contributeToVaultController: RequestHandler = async (req, res) => {
|
export const contributeToVaultController: RequestHandler = async (req, res) => {
|
||||||
|
@ -4,9 +4,15 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
export const creditsController: RequestHandler = async (req, res) => {
|
export const creditsController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const inventory = (
|
||||||
|
await Promise.all([
|
||||||
const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
|
getAccountIdForRequest(req),
|
||||||
|
getInventory(
|
||||||
|
req.query.accountId as string,
|
||||||
|
"RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits"
|
||||||
|
)
|
||||||
|
])
|
||||||
|
)[1];
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
RegularCredits: inventory.RegularCredits,
|
RegularCredits: inventory.RegularCredits,
|
||||||
|
@ -12,7 +12,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { getRandomInt } from "@/src/services/rngService";
|
import { getRandomInt } from "@/src/services/rngService";
|
||||||
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
|
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
|
||||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentDatabase } from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
|
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountForRequest, getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
|
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
|
||||||
const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
|
const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
|
||||||
const guild = (await Guild.findById(data.g, "DojoComponents"))!;
|
const guild = (await Guild.findById(data.g, "DojoComponents Ranks"))!;
|
||||||
const component = guild.DojoComponents.id(data.c)!;
|
const component = guild.DojoComponents.id(data.c)!;
|
||||||
if (req.query.act == "f") {
|
if (req.query.act == "f") {
|
||||||
res.json({
|
res.json({
|
||||||
@ -34,6 +37,19 @@ export const customObstacleCourseLeaderboardController: RequestHandler = async (
|
|||||||
entry.r = ++r;
|
entry.r = ++r;
|
||||||
}
|
}
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
res.status(200).end();
|
||||||
|
} else if (req.query.act == "c") {
|
||||||
|
// TOVERIFY: What clan permission is actually needed for this?
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||||
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.Leaderboard = undefined;
|
||||||
|
await guild.save();
|
||||||
|
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
} else {
|
} else {
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
|
@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
|
import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
|
||||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
export const evolveWeaponController: RequestHandler = async (req, res) => {
|
export const evolveWeaponController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
@ -4,7 +4,6 @@ import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/serv
|
|||||||
import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ExportFocusUpgrades } from "warframe-public-export-plus";
|
import { ExportFocusUpgrades } from "warframe-public-export-plus";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
|
||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
export const focusController: RequestHandler = async (req, res) => {
|
export const focusController: RequestHandler = async (req, res) => {
|
||||||
@ -116,7 +115,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
});
|
});
|
||||||
occupySlot(inventory, InventorySlot.AMPS, false);
|
occupySlot(inventory, InventorySlot.AMPS, false);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
|
res.json(inventoryChanges.OperatorAmps![0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FocusOperation.UnbindUpgrade: {
|
case FocusOperation.UnbindUpgrade: {
|
||||||
|
@ -6,9 +6,8 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
import { IMongoDate } from "@/src/types/commonTypes";
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { IMissionReward } from "@/src/types/missionTypes";
|
import { IMissionReward } from "@/src/types/missionTypes";
|
||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
import { IGardeningClient, IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { IGardeningClient } from "@/src/types/shipTypes";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { dict_en, ExportResources } from "warframe-public-export-plus";
|
import { dict_en, ExportResources } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { generateRewardSeed } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { generateRewardSeed } from "@/src/services/rngService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||||
|
@ -3,10 +3,9 @@ import { config } from "@/src/services/configService";
|
|||||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { IGetShipResponse, IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
|
||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
|
||||||
import { getLoadout } from "@/src/services/loadoutService";
|
import { getLoadout } from "@/src/services/loadoutService";
|
||||||
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
|
||||||
export const getShipController: RequestHandler = async (req, res) => {
|
export const getShipController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -26,15 +25,7 @@ export const getShipController: RequestHandler = async (req, res) => {
|
|||||||
LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
|
LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
|
||||||
Ship: {
|
Ship: {
|
||||||
...personalRooms.Ship,
|
...personalRooms.Ship,
|
||||||
ShipId: toOid(personalRoomsDb.activeShipId),
|
ShipId: toOid(personalRoomsDb.activeShipId)
|
||||||
ShipInterior: {
|
|
||||||
Colors: personalRooms.ShipInteriorColors,
|
|
||||||
ShipAttachments: { HOOD_ORNAMENT: "" },
|
|
||||||
SkinFlavourItem: ""
|
|
||||||
},
|
|
||||||
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
|
||||||
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
|
||||||
: undefined
|
|
||||||
},
|
},
|
||||||
Apartment: personalRooms.Apartment,
|
Apartment: personalRooms.Apartment,
|
||||||
TailorShop: personalRooms.TailorShop
|
TailorShop: personalRooms.TailorShop
|
||||||
|
@ -3,9 +3,10 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { ExportRecipes } from "warframe-public-export-plus";
|
import { ExportRecipes } from "warframe-public-export-plus";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { EquipmentFeatures, IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
interface IGildWeaponRequest {
|
interface IGildWeaponRequest {
|
||||||
ItemName: string;
|
ItemName: string;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { ITypeCount } from "@/src/types/commonTypes";
|
||||||
|
import { ILoreFragmentScan } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
|
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
getGuildVault,
|
getGuildVault,
|
||||||
hasAccessToDojo,
|
hasAccessToDojo,
|
||||||
hasGuildPermission,
|
hasGuildPermission,
|
||||||
|
processCompletedGuildTechProject,
|
||||||
processFundedGuildTechProject,
|
processFundedGuildTechProject,
|
||||||
processGuildTechProjectContributionsUpdate,
|
processGuildTechProjectContributionsUpdate,
|
||||||
removePigmentsFromGuildMembers,
|
removePigmentsFromGuildMembers,
|
||||||
@ -51,8 +52,12 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
};
|
};
|
||||||
if (project.CompletionDate) {
|
if (project.CompletionDate) {
|
||||||
techProject.CompletionDate = toMongoDate(project.CompletionDate);
|
techProject.CompletionDate = toMongoDate(project.CompletionDate);
|
||||||
if (Date.now() >= project.CompletionDate.getTime()) {
|
if (
|
||||||
needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
|
Date.now() >= project.CompletionDate.getTime() &&
|
||||||
|
setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate)
|
||||||
|
) {
|
||||||
|
processCompletedGuildTechProject(guild, project.ItemType);
|
||||||
|
needSave = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
techProjects.push(techProject);
|
techProjects.push(techProject);
|
||||||
|
@ -5,15 +5,25 @@ import { config } from "@/src/services/configService";
|
|||||||
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
||||||
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
||||||
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IPolarity, ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
|
import {
|
||||||
|
eFaction,
|
||||||
|
ExportCustoms,
|
||||||
|
ExportFlavour,
|
||||||
|
ExportResources,
|
||||||
|
ExportVirtuals,
|
||||||
|
ICountedItem
|
||||||
|
} from "warframe-public-export-plus";
|
||||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
||||||
import {
|
import {
|
||||||
|
addEmailItem,
|
||||||
|
addItem,
|
||||||
addMiscItems,
|
addMiscItems,
|
||||||
allDailyAffiliationKeys,
|
allDailyAffiliationKeys,
|
||||||
|
checkCalendarAutoAdvance,
|
||||||
cleanupInventory,
|
cleanupInventory,
|
||||||
createLibraryDailyTask,
|
createLibraryDailyTask,
|
||||||
generateRewardSeed
|
getCalendarProgress
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { addString, catBreadHash } from "@/src/helpers/stringHelpers";
|
import { addString, catBreadHash } from "@/src/helpers/stringHelpers";
|
||||||
@ -26,6 +36,10 @@ import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelp
|
|||||||
import { Inbox } from "@/src/models/inboxModel";
|
import { Inbox } from "@/src/models/inboxModel";
|
||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { DailyDeal } from "@/src/models/worldStateModel";
|
import { DailyDeal } from "@/src/models/worldStateModel";
|
||||||
|
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
|
||||||
|
import { generateRewardSeed } from "@/src/services/rngService";
|
||||||
|
import { getInvasionByOid, getWorldState } from "@/src/services/worldStateService";
|
||||||
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
|
|
||||||
export const inventoryController: RequestHandler = async (request, response) => {
|
export const inventoryController: RequestHandler = async (request, response) => {
|
||||||
const account = await getAccountForRequest(request);
|
const account = await getAccountForRequest(request);
|
||||||
@ -108,6 +122,64 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Setup CalendarProgress as part of 1999 mission completion?
|
||||||
|
|
||||||
|
const previousYearIteration = inventory.CalendarProgress?.Iteration;
|
||||||
|
|
||||||
|
// We need to do the following to ensure the in-game calendar does not break:
|
||||||
|
getCalendarProgress(inventory); // Keep the CalendarProgress up-to-date (at least for the current year iteration) (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2364)
|
||||||
|
checkCalendarAutoAdvance(inventory, getWorldState().KnownCalendarSeasons[0]); // Skip birthday events for characters if we do not have them unlocked yet (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2424)
|
||||||
|
|
||||||
|
// also handle sending of kiss cinematic at year rollover
|
||||||
|
if (
|
||||||
|
inventory.CalendarProgress!.Iteration != previousYearIteration &&
|
||||||
|
inventory.DialogueHistory &&
|
||||||
|
inventory.DialogueHistory.Dialogues
|
||||||
|
) {
|
||||||
|
let kalymos = false;
|
||||||
|
for (const { dialogueName, kissEmail } of [
|
||||||
|
{
|
||||||
|
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
|
||||||
|
kissEmail: "/Lotus/Types/Items/EmailItems/ArthurKissEmailItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
|
||||||
|
kissEmail: "/Lotus/Types/Items/EmailItems/EleanorKissEmailItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
|
||||||
|
kissEmail: "/Lotus/Types/Items/EmailItems/LettieKissEmailItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
|
||||||
|
kissEmail: "/Lotus/Types/Items/EmailItems/AmirKissEmailItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
|
||||||
|
kissEmail: "/Lotus/Types/Items/EmailItems/AoiKissEmailItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue",
|
||||||
|
kissEmail: "/Lotus/Types/Items/EmailItems/QuincyKissEmailItem"
|
||||||
|
}
|
||||||
|
]) {
|
||||||
|
const dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == dialogueName);
|
||||||
|
if (dialogue) {
|
||||||
|
if (dialogue.Rank == 7) {
|
||||||
|
await addEmailItem(inventory, kissEmail);
|
||||||
|
kalymos = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (dialogue.Rank == 6) {
|
||||||
|
kalymos = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kalymos) {
|
||||||
|
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cleanupInventory(inventory);
|
cleanupInventory(inventory);
|
||||||
|
|
||||||
inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
|
inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
|
||||||
@ -123,6 +195,63 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
//await inventory.save();
|
//await inventory.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i != inventory.QualifyingInvasions.length; ) {
|
||||||
|
const qi = inventory.QualifyingInvasions[i];
|
||||||
|
const invasion = getInvasionByOid(qi.invasionId.toString());
|
||||||
|
if (!invasion) {
|
||||||
|
logger.debug(`removing QualifyingInvasions entry for unknown invasion: ${qi.invasionId.toString()}`);
|
||||||
|
inventory.QualifyingInvasions.splice(i, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (invasion.Completed) {
|
||||||
|
let factionSidedWith: string | undefined;
|
||||||
|
let battlePay: ICountedItem[] | undefined;
|
||||||
|
if (qi.AttackerScore >= 3) {
|
||||||
|
factionSidedWith = invasion.Faction;
|
||||||
|
battlePay = invasion.AttackerReward.countedItems;
|
||||||
|
logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
|
||||||
|
} else if (qi.DefenderScore >= 3) {
|
||||||
|
factionSidedWith = invasion.DefenderFaction;
|
||||||
|
battlePay = invasion.DefenderReward.countedItems;
|
||||||
|
logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
|
||||||
|
}
|
||||||
|
if (factionSidedWith) {
|
||||||
|
if (battlePay) {
|
||||||
|
// Decoupling rewards from the inbox message because it may delete itself without being read
|
||||||
|
for (const item of battlePay) {
|
||||||
|
await addItem(inventory, item.ItemType, item.ItemCount);
|
||||||
|
}
|
||||||
|
await createMessage(account._id, [
|
||||||
|
{
|
||||||
|
sndr: eFaction.find(x => x.tag == factionSidedWith)?.name ?? factionSidedWith, // TOVERIFY
|
||||||
|
msg: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageBody`,
|
||||||
|
sub: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageSubject`,
|
||||||
|
countedAtt: battlePay,
|
||||||
|
attVisualOnly: true,
|
||||||
|
icon:
|
||||||
|
factionSidedWith == "FC_GRINEER"
|
||||||
|
? "/Lotus/Interface/Icons/Npcs/EliteRifleLancerAvatar.png" // Source: https://www.reddit.com/r/Warframe/comments/1aj4usx/battle_pay_worth_10_plat/, https://www.youtube.com/watch?v=XhNZ6ai6BOY
|
||||||
|
: "/Lotus/Interface/Icons/Npcs/CrewmanNormal.png", // My best source for this is https://www.youtube.com/watch?v=rxrCCFm73XE around 1:37
|
||||||
|
// TOVERIFY: highPriority?
|
||||||
|
endDate: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's not clear if this is correct.
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (invasion.Faction != "FC_INFESTATION") {
|
||||||
|
// Sided with grineer -> opposed corpus -> send zanuka (harvester)
|
||||||
|
// Sided with corpus -> opposed grineer -> send g3 (death squad)
|
||||||
|
inventory[factionSidedWith != "FC_GRINEER" ? "DeathSquadable" : "Harvestable"] = true;
|
||||||
|
// TOVERIFY: Should this happen earlier?
|
||||||
|
// TOVERIFY: Should this send an (ephemeral) email?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug(`removing QualifyingInvasions entry for completed invasion: ${qi.invasionId.toString()}`);
|
||||||
|
inventory.QualifyingInvasions.splice(i, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
if (inventory.LastInventorySync) {
|
if (inventory.LastInventorySync) {
|
||||||
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
|
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
|
||||||
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
|
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { getInventory, updateCurrency, updateSlots } from "@/src/services/inventoryService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { updateSlots } from "@/src/services/inventoryService";
|
|
||||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { exhaustive } from "@/src/utils/ts-utils";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
loadout slots are additionally purchased slots only
|
loadout slots are additionally purchased slots only
|
||||||
@ -23,13 +22,44 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
|
|||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
|
const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
|
||||||
|
|
||||||
if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) {
|
let price;
|
||||||
logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
|
let amount;
|
||||||
|
switch (body.Bin) {
|
||||||
|
case InventorySlot.SUITS:
|
||||||
|
case InventorySlot.MECHSUITS:
|
||||||
|
case InventorySlot.PVE_LOADOUTS:
|
||||||
|
case InventorySlot.CREWMEMBERS:
|
||||||
|
price = 20;
|
||||||
|
amount = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InventorySlot.SPACESUITS:
|
||||||
|
price = 12;
|
||||||
|
amount = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InventorySlot.WEAPONS:
|
||||||
|
case InventorySlot.SPACEWEAPONS:
|
||||||
|
case InventorySlot.SENTINELS:
|
||||||
|
case InventorySlot.RJ_COMPONENT_AND_ARMAMENTS:
|
||||||
|
case InventorySlot.AMPS:
|
||||||
|
price = 12;
|
||||||
|
amount = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InventorySlot.RIVENS:
|
||||||
|
price = 60;
|
||||||
|
amount = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
exhaustive(body.Bin);
|
||||||
|
throw new Error(`unexpected slot purchase of type ${body.Bin as string}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const currencyChanges = updateCurrency(inventory, 20, true);
|
const currencyChanges = updateCurrency(inventory, price, true);
|
||||||
updateSlots(inventory, body.Bin, 1, 1);
|
updateSlots(inventory, body.Bin, amount, amount);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({ InventoryChanges: currencyChanges });
|
res.json({ InventoryChanges: currencyChanges });
|
||||||
|
@ -8,7 +8,7 @@ import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } f
|
|||||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
|
|
||||||
export const loginController: RequestHandler = async (request, response) => {
|
export const loginController: RequestHandler = async (request, response) => {
|
||||||
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from "@/src/services/loginRewardService";
|
} from "@/src/services/loginRewardService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
|
|
||||||
export const loginRewardsController: RequestHandler = async (req, res) => {
|
export const loginRewardsController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
@ -47,10 +48,10 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
|
|||||||
response.DailyTributeInfo.HasChosenReward = true;
|
response.DailyTributeInfo.HasChosenReward = true;
|
||||||
response.DailyTributeInfo.ChosenReward = randomRewards[0];
|
response.DailyTributeInfo.ChosenReward = randomRewards[0];
|
||||||
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
|
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
setAccountGotLoginRewardToday(account);
|
setAccountGotLoginRewardToday(account);
|
||||||
await account.save();
|
await Promise.all([inventory.save(), account.save()]);
|
||||||
|
|
||||||
|
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||||
}
|
}
|
||||||
res.json(response);
|
res.json(response);
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
} from "@/src/services/loginRewardService";
|
} from "@/src/services/loginRewardService";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
@ -34,11 +35,10 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
|
|||||||
chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
|
chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
|
||||||
inventoryChanges = await claimLoginReward(inventory, chosenReward);
|
inventoryChanges = await claimLoginReward(inventory, chosenReward);
|
||||||
}
|
}
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
setAccountGotLoginRewardToday(account);
|
setAccountGotLoginRewardToday(account);
|
||||||
await account.save();
|
await Promise.all([inventory.save(), account.save()]);
|
||||||
|
|
||||||
|
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||||
res.json({
|
res.json({
|
||||||
DailyTributeInfo: {
|
DailyTributeInfo: {
|
||||||
NewInventory: inventoryChanges,
|
NewInventory: inventoryChanges,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
|
|
||||||
export const logoutController: RequestHandler = async (req, res) => {
|
export const logoutController: RequestHandler = async (req, res) => {
|
||||||
if (!req.query.accountId) {
|
if (!req.query.accountId) {
|
||||||
|
@ -3,11 +3,12 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
|
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
|
||||||
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
|
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
|
||||||
import { generateRewardSeed, getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getInventoryResponse } from "./inventoryController";
|
import { getInventoryResponse } from "@/src/controllers/api/inventoryController";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
|
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
|
import { generateRewardSeed } from "@/src/services/rngService";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
**** INPUT ****
|
**** INPUT ****
|
||||||
|
@ -15,10 +15,9 @@ import {
|
|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
||||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
||||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
|
||||||
import { getRandomInt } from "@/src/services/rngService";
|
import { getRandomInt } from "@/src/services/rngService";
|
||||||
import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
|
import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
|
||||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IEquipmentDatabase, Status } from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
interface IModularCraftRequest {
|
interface IModularCraftRequest {
|
||||||
WeaponType: string;
|
WeaponType: string;
|
||||||
|
@ -3,7 +3,7 @@ import { ExportWeapons } from "warframe-public-export-plus";
|
|||||||
import { IMongoDate } from "@/src/types/commonTypes";
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { SRng } from "@/src/services/rngService";
|
import { SRng } from "@/src/services/rngService";
|
||||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import {
|
import {
|
||||||
addEquipment,
|
addEquipment,
|
||||||
@ -17,6 +17,7 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
||||||
const partTypeToParts: Record<string, string[]> = {};
|
const partTypeToParts: Record<string, string[]> = {};
|
||||||
|
@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
|
|
||||||
interface INameWeaponRequest {
|
interface INameWeaponRequest {
|
||||||
ItemName: string;
|
ItemName: string;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
import {
|
import {
|
||||||
antivirusMods,
|
antivirusMods,
|
||||||
consumeModCharge,
|
|
||||||
decodeNemesisGuess,
|
decodeNemesisGuess,
|
||||||
encodeNemesisGuess,
|
encodeNemesisGuess,
|
||||||
getInfNodes,
|
getInfNodes,
|
||||||
@ -17,12 +16,13 @@ import {
|
|||||||
parseUpgrade
|
parseUpgrade
|
||||||
} from "@/src/helpers/nemesisHelpers";
|
} from "@/src/helpers/nemesisHelpers";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
import { addMods, freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { SRng } from "@/src/services/rngService";
|
import { SRng } from "@/src/services/rngService";
|
||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||||
import {
|
import {
|
||||||
IInnateDamageFingerprint,
|
IInnateDamageFingerprint,
|
||||||
IInventoryClient,
|
IInventoryClient,
|
||||||
@ -36,6 +36,7 @@ import {
|
|||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
export const nemesisController: RequestHandler = async (req, res) => {
|
export const nemesisController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
@ -391,3 +392,54 @@ interface IKnife {
|
|||||||
AttachedUpgrades: IUpgradeClient[];
|
AttachedUpgrades: IUpgradeClient[];
|
||||||
HiddenWhenHolstered: boolean;
|
HiddenWhenHolstered: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const consumeModCharge = (
|
||||||
|
response: IKnifeResponse,
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
upgrade: { ItemId: IOid; ItemType: string },
|
||||||
|
dataknifeUpgrades: string[]
|
||||||
|
): void => {
|
||||||
|
response.UpgradeIds ??= [];
|
||||||
|
response.UpgradeTypes ??= [];
|
||||||
|
response.UpgradeFingerprints ??= [];
|
||||||
|
response.UpgradeNew ??= [];
|
||||||
|
response.HasKnife = true;
|
||||||
|
|
||||||
|
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
||||||
|
const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
|
||||||
|
const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
|
||||||
|
fingerprint.lvl += 1;
|
||||||
|
dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
|
||||||
|
|
||||||
|
response.UpgradeIds.push(upgrade.ItemId.$oid);
|
||||||
|
response.UpgradeTypes.push(upgrade.ItemType);
|
||||||
|
response.UpgradeFingerprints.push(fingerprint);
|
||||||
|
response.UpgradeNew.push(false);
|
||||||
|
} else {
|
||||||
|
const id = new Types.ObjectId();
|
||||||
|
inventory.Upgrades.push({
|
||||||
|
_id: id,
|
||||||
|
ItemType: upgrade.ItemType,
|
||||||
|
UpgradeFingerprint: `{"lvl":1}`
|
||||||
|
});
|
||||||
|
|
||||||
|
addMods(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: upgrade.ItemType,
|
||||||
|
ItemCount: -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType);
|
||||||
|
if (dataknifeRawUpgradeIndex != -1) {
|
||||||
|
dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString();
|
||||||
|
} else {
|
||||||
|
logger.warn(`${upgrade.ItemType} not found in dataknife config`);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.UpgradeIds.push(id.toString());
|
||||||
|
response.UpgradeTypes.push(upgrade.ItemType);
|
||||||
|
response.UpgradeFingerprints.push({ lvl: 1 });
|
||||||
|
response.UpgradeNew.push(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -57,8 +57,12 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
|
|||||||
component.DecoCapacity -= meta.capacityCost;
|
component.DecoCapacity -= meta.capacityCost;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
|
const entry = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type);
|
||||||
if (!itemType || meta.dojoCapacityCost === undefined) {
|
if (!entry) {
|
||||||
|
throw new Error(`unknown deco type: ${deco.Type}`);
|
||||||
|
}
|
||||||
|
const [itemType, meta] = entry;
|
||||||
|
if (meta.dojoCapacityCost === undefined) {
|
||||||
throw new Error(`unknown deco type: ${deco.Type}`);
|
throw new Error(`unknown deco type: ${deco.Type}`);
|
||||||
}
|
}
|
||||||
component.DecoCapacity -= meta.dojoCapacityCost;
|
component.DecoCapacity -= meta.dojoCapacityCost;
|
||||||
@ -75,7 +79,13 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
|
|||||||
if (meta) {
|
if (meta) {
|
||||||
processDojoBuildMaterialsGathered(guild, meta);
|
processDojoBuildMaterialsGathered(guild, meta);
|
||||||
}
|
}
|
||||||
} else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
|
} else if (
|
||||||
|
deco.Type.startsWith("/Lotus/Objects/Tenno/Dojo/NpcPlaceables/") ||
|
||||||
|
(guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems)
|
||||||
|
) {
|
||||||
|
if (!guild.VaultRegularCredits || !guild.VaultMiscItems) {
|
||||||
|
throw new Error(`dojo visitor placed without anything in vault?!`);
|
||||||
|
}
|
||||||
if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
||||||
let enoughMiscItems = true;
|
let enoughMiscItems = true;
|
||||||
for (const ingredient of meta.ingredients) {
|
for (const ingredient of meta.ingredients) {
|
||||||
|
@ -1,25 +1,39 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { addConsumables, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const playerSkillsController: RequestHandler = async (req, res) => {
|
export const playerSkillsController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId, "PlayerSkills");
|
const inventory = await getInventory(accountId, "PlayerSkills Consumables");
|
||||||
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
|
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
|
||||||
|
|
||||||
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
|
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
|
||||||
const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000;
|
const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000;
|
||||||
inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost;
|
inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost;
|
||||||
inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
|
inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
if (request.Skill == "LPS_COMMAND" && inventory.PlayerSkills.LPS_COMMAND == 9) {
|
||||||
|
const consumablesChanges = [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Restoratives/Consumable/CrewmateBall",
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addConsumables(inventory, consumablesChanges);
|
||||||
|
inventoryChanges.Consumables = consumablesChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
Pool: request.Pool,
|
Pool: request.Pool,
|
||||||
PoolInc: -cost,
|
PoolInc: -cost,
|
||||||
Skill: request.Skill,
|
Skill: request.Skill,
|
||||||
Rank: oldRank + 1
|
Rank: oldRank + 1,
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { IPurchaseRequest } from "@/src/types/purchaseTypes";
|
import { IPurchaseRequest } from "@/src/types/purchaseTypes";
|
||||||
import { handlePurchase } from "@/src/services/purchaseService";
|
import { handlePurchase } from "@/src/services/purchaseService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
|
|
||||||
export const purchaseController: RequestHandler = async (req, res) => {
|
export const purchaseController: RequestHandler = async (req, res) => {
|
||||||
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
|
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
5
src/controllers/api/resetQuestProgressController.ts
Normal file
5
src/controllers/api/resetQuestProgressController.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const resetQuestProgressController: RequestHandler = (_req, res) => {
|
||||||
|
res.send("1").end();
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { Status } from "@/src/types/equipmentTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const retrievePetFromStasisController: RequestHandler = async (req, res) => {
|
export const retrievePetFromStasisController: RequestHandler = async (req, res) => {
|
||||||
|
@ -2,7 +2,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ISettings } from "../../types/inventoryTypes/inventoryTypes";
|
import { ISettings } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
interface ISaveSettingsRequest {
|
interface ISaveSettingsRequest {
|
||||||
Settings: ISettings;
|
Settings: ISettings;
|
||||||
|
@ -15,10 +15,11 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
|||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
|
|
||||||
export const sellController: RequestHandler = async (req, res) => {
|
export const sellController: RequestHandler = async (req, res) => {
|
||||||
const payload = JSON.parse(String(req.body)) as ISellRequest;
|
const payload = JSON.parse(String(req.body)) as ISellRequest;
|
||||||
|
//console.log(JSON.stringify(payload, null, 2));
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
|
const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
|
||||||
if (payload.SellCurrency == "SC_RegularCredits") {
|
if (payload.SellCurrency == "SC_RegularCredits") {
|
||||||
@ -58,6 +59,9 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
if (payload.Items.Hoverboards) {
|
if (payload.Items.Hoverboards) {
|
||||||
requiredFields.add(InventorySlot.SPACESUITS);
|
requiredFields.add(InventorySlot.SPACESUITS);
|
||||||
}
|
}
|
||||||
|
if (payload.Items.CrewMembers) {
|
||||||
|
requiredFields.add(InventorySlot.CREWMEMBERS);
|
||||||
|
}
|
||||||
if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
|
if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
|
||||||
requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
||||||
requiredFields.add("CrewShipRawSalvage");
|
requiredFields.add("CrewShipRawSalvage");
|
||||||
@ -181,6 +185,17 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
inventory.Drones.pull({ _id: sellItem.String });
|
inventory.Drones.pull({ _id: sellItem.String });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (payload.Items.KubrowPetPrints) {
|
||||||
|
payload.Items.KubrowPetPrints.forEach(sellItem => {
|
||||||
|
inventory.KubrowPetPrints.pull({ _id: sellItem.String });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (payload.Items.CrewMembers) {
|
||||||
|
payload.Items.CrewMembers.forEach(sellItem => {
|
||||||
|
inventory.CrewMembers.pull({ _id: sellItem.String });
|
||||||
|
freeUpSlot(inventory, InventorySlot.CREWMEMBERS);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (payload.Items.CrewShipWeapons) {
|
if (payload.Items.CrewShipWeapons) {
|
||||||
payload.Items.CrewShipWeapons.forEach(sellItem => {
|
payload.Items.CrewShipWeapons.forEach(sellItem => {
|
||||||
if (sellItem.String[0] == "/") {
|
if (sellItem.String[0] == "/") {
|
||||||
@ -303,6 +318,8 @@ interface ISellRequest {
|
|||||||
OperatorAmps?: ISellItem[];
|
OperatorAmps?: ISellItem[];
|
||||||
Hoverboards?: ISellItem[];
|
Hoverboards?: ISellItem[];
|
||||||
Drones?: ISellItem[];
|
Drones?: ISellItem[];
|
||||||
|
KubrowPetPrints?: ISellItem[];
|
||||||
|
CrewMembers?: ISellItem[];
|
||||||
CrewShipWeapons?: ISellItem[];
|
CrewShipWeapons?: ISellItem[];
|
||||||
CrewShipWeaponSkins?: ISellItem[];
|
CrewShipWeaponSkins?: ISellItem[];
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
import { TBootLocation } from "@/src/types/shipTypes";
|
import { TBootLocation } from "@/src/types/personalRoomsTypes";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
export const setBootLocationController: RequestHandler = async (req, res) => {
|
export const setBootLocationController: RequestHandler = async (req, res) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
|
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/personalRoomsTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
|
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { setShipCustomizations } from "@/src/services/shipCustomizationsService";
|
import { setShipCustomizations } from "@/src/services/shipCustomizationsService";
|
||||||
import { ISetShipCustomizationsRequest } from "@/src/types/shipTypes";
|
import { ISetShipCustomizationsRequest } from "@/src/types/personalRoomsTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { RequestHandler } from "express";
|
|||||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/shipTypes";
|
import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/personalRoomsTypes";
|
||||||
|
|
||||||
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
|
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
@ -2,7 +2,7 @@ import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers";
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const setSuitInfectionController: RequestHandler = async (req, res) => {
|
export const setSuitInfectionController: RequestHandler = async (req, res) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IShipDecorationsRequest } from "@/src/types/shipTypes";
|
import { IShipDecorationsRequest } from "@/src/types/personalRoomsTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
|
import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
|
||||||
|
@ -6,7 +6,7 @@ import { IOid } from "@/src/types/commonTypes";
|
|||||||
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
|
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
|
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
@ -2,7 +2,7 @@ import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers";
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const umbraController: RequestHandler = async (req, res) => {
|
export const umbraController: RequestHandler = async (req, res) => {
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { IUpgradesRequest } from "@/src/types/requestTypes";
|
import { IUpgradesRequest } from "@/src/types/requestTypes";
|
||||||
import {
|
import { ArtifactPolarity, IAbilityOverride } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
ArtifactPolarity,
|
|
||||||
IEquipmentDatabase,
|
|
||||||
EquipmentFeatures,
|
|
||||||
IAbilityOverride
|
|
||||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
|
||||||
import { IInventoryClient, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInventoryClient, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
@ -13,6 +8,8 @@ import { getRecipeByResult } from "@/src/services/itemDataService";
|
|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
|
import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||||
|
import { EquipmentFeatures, IEquipmentDatabase } from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
export const upgradesController: RequestHandler = async (req, res) => {
|
export const upgradesController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -120,6 +117,7 @@ export const upgradesController: RequestHandler = async (req, res) => {
|
|||||||
setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
|
setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
|
||||||
item.Polarized ??= 0;
|
item.Polarized ??= 0;
|
||||||
item.Polarized += 1;
|
item.Polarized += 1;
|
||||||
|
sendWsBroadcastTo(accountId, { update_inventory: true }); // webui may need to to re-add "max rank" button
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {
|
case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {
|
||||||
|
@ -7,7 +7,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
|
|||||||
const requests = req.body as IAddItemRequest[];
|
const requests = req.body as IAddItemRequest[];
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
for (const request of requests) {
|
for (const request of requests) {
|
||||||
await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, undefined, true);
|
await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, request.Fingerprint, true);
|
||||||
}
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.end();
|
res.end();
|
||||||
@ -16,4 +16,5 @@ export const addItemsController: RequestHandler = async (req, res) => {
|
|||||||
interface IAddItemRequest {
|
interface IAddItemRequest {
|
||||||
ItemType: string;
|
ItemType: string;
|
||||||
ItemCount: number;
|
ItemCount: number;
|
||||||
|
Fingerprint?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
|
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportMisc } from "warframe-public-export-plus";
|
import { ExportMisc } from "warframe-public-export-plus";
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
||||||
import { saveConfig } from "@/src/services/configWatcherService";
|
import { saveConfig } from "@/src/services/configWriterService";
|
||||||
import { sendWsBroadcastExcept } from "@/src/services/webService";
|
import { sendWsBroadcastExcept } from "@/src/services/wsService";
|
||||||
|
|
||||||
export const getConfigController: RequestHandler = async (req, res) => {
|
export const getConfigController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
@ -37,6 +37,8 @@ const configIdToIndexable = (id: string): [Record<string, boolean | string | num
|
|||||||
let obj = config as unknown as Record<string, never>;
|
let obj = config as unknown as Record<string, never>;
|
||||||
const arr = id.split(".");
|
const arr = id.split(".");
|
||||||
while (arr.length > 1) {
|
while (arr.length > 1) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
obj[arr[0]] ??= {} as never;
|
||||||
obj = obj[arr[0]];
|
obj = obj[arr[0]];
|
||||||
arr.splice(0, 1);
|
arr.splice(0, 1);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
TRelicQuality
|
TRelicQuality
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
|
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
|
||||||
|
import varzia from "@/static/fixed_responses/worldState/varzia.json";
|
||||||
|
|
||||||
interface ListedItem {
|
interface ListedItem {
|
||||||
uniqueName: string;
|
uniqueName: string;
|
||||||
@ -55,6 +56,7 @@ interface ItemLists {
|
|||||||
EvolutionProgress: ListedItem[];
|
EvolutionProgress: ListedItem[];
|
||||||
mods: ListedItem[];
|
mods: ListedItem[];
|
||||||
Boosters: ListedItem[];
|
Boosters: ListedItem[];
|
||||||
|
VarziaOffers: ListedItem[];
|
||||||
//circuitGameModes: ListedItem[];
|
//circuitGameModes: ListedItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +93,8 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
KubrowPets: [],
|
KubrowPets: [],
|
||||||
EvolutionProgress: [],
|
EvolutionProgress: [],
|
||||||
mods: [],
|
mods: [],
|
||||||
Boosters: []
|
Boosters: [],
|
||||||
|
VarziaOffers: []
|
||||||
/*circuitGameModes: [
|
/*circuitGameModes: [
|
||||||
{
|
{
|
||||||
uniqueName: "Survival",
|
uniqueName: "Survival",
|
||||||
@ -338,6 +341,13 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const item of Object.values(varzia.primeDualPacks)) {
|
||||||
|
res.VarziaOffers.push({
|
||||||
|
uniqueName: item.ItemType,
|
||||||
|
name: getString(getItemName(item.ItemType) || "", lang)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
response.json(res);
|
response.json(res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest, isAdministrator, isNameTaken } from "@/src/services/loginService";
|
import { getAccountForRequest, isAdministrator, isNameTaken } from "@/src/services/loginService";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { saveConfig } from "@/src/services/configWatcherService";
|
import { saveConfig } from "@/src/services/configWriterService";
|
||||||
|
|
||||||
export const renameAccountController: RequestHandler = async (req, res) => {
|
export const renameAccountController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { args } from "@/src/helpers/commandLineArguments";
|
import { args } from "@/src/helpers/commandLineArguments";
|
||||||
import { sendWsBroadcast } from "@/src/services/webService";
|
import { sendWsBroadcast } from "@/src/services/wsService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const webuiFileChangeDetectedController: RequestHandler = (req, res) => {
|
export const webuiFileChangeDetectedController: RequestHandler = (req, res) => {
|
||||||
|
@ -6,13 +6,11 @@ import { Account } from "@/src/models/loginModel";
|
|||||||
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
|
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
|
||||||
import { allDailyAffiliationKeys } from "@/src/services/inventoryService";
|
import { allDailyAffiliationKeys } from "@/src/services/inventoryService";
|
||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
|
||||||
import {
|
import {
|
||||||
IAffiliation,
|
IAffiliation,
|
||||||
IAlignment,
|
IAlignment,
|
||||||
IChallengeProgress,
|
IChallengeProgress,
|
||||||
IDailyAffiliations,
|
IDailyAffiliations,
|
||||||
ILoadoutConfigClient,
|
|
||||||
IMission,
|
IMission,
|
||||||
IPlayerSkills,
|
IPlayerSkills,
|
||||||
ITypeXPItem
|
ITypeXPItem
|
||||||
@ -23,6 +21,8 @@ import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
|
|||||||
import { IStatsClient } from "@/src/types/statTypes";
|
import { IStatsClient } from "@/src/types/statTypes";
|
||||||
import { toStoreItem } from "@/src/services/itemDataService";
|
import { toStoreItem } from "@/src/services/itemDataService";
|
||||||
import { FlattenMaps } from "mongoose";
|
import { FlattenMaps } from "mongoose";
|
||||||
|
import { IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||||
|
import { ILoadoutConfigClient } from "@/src/types/saveLoadoutTypes";
|
||||||
|
|
||||||
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
|
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
|
||||||
const account = await Account.findById(playerId, "DisplayName");
|
const account = await Account.findById(playerId, "DisplayName");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { IAccountCreation } from "@/src/types/customTypes";
|
import { IAccountCreation } from "@/src/types/customTypes";
|
||||||
import { IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
|
import { IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { isString, parseEmail, parseString } from "../general";
|
import { isString, parseEmail, parseString } from "@/src/helpers/general";
|
||||||
|
|
||||||
const getWhirlpoolHash = (rawPassword: string): string => {
|
const getWhirlpoolHash = (rawPassword: string): string => {
|
||||||
const whirlpool = crypto.createHash("whirlpool");
|
const whirlpool = crypto.createHash("whirlpool");
|
||||||
|
@ -9,11 +9,11 @@ export const isEmptyObject = (obj: object): boolean => {
|
|||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const isString = (text: unknown): text is string => {
|
export const isString = (text: unknown): text is string => {
|
||||||
return typeof text === "string" || text instanceof String;
|
return typeof text === "string" || text instanceof String;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseString = (data: unknown): string => {
|
export const parseString = (data: unknown): string => {
|
||||||
if (!isString(data)) {
|
if (!isString(data)) {
|
||||||
throw new Error("data is not a string");
|
throw new Error("data is not a string");
|
||||||
}
|
}
|
||||||
@ -21,11 +21,11 @@ const parseString = (data: unknown): string => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isNumber = (number: unknown): number is number => {
|
export const isNumber = (number: unknown): number is number => {
|
||||||
return typeof number === "number" && !isNaN(number);
|
return typeof number === "number" && !isNaN(number);
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseNumber = (data: unknown): number => {
|
export const parseNumber = (data: unknown): number => {
|
||||||
if (!isNumber(data)) {
|
if (!isNumber(data)) {
|
||||||
throw new Error("data is not a number");
|
throw new Error("data is not a number");
|
||||||
}
|
}
|
||||||
@ -33,11 +33,11 @@ const parseNumber = (data: unknown): number => {
|
|||||||
return Number(data);
|
return Number(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDate = (date: string): boolean => {
|
export const isDate = (date: string): boolean => {
|
||||||
return Date.parse(date) != 0;
|
return Date.parse(date) != 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseDateNumber = (date: unknown): string => {
|
export const parseDateNumber = (date: unknown): string => {
|
||||||
if (!isString(date) || !isDate(date)) {
|
if (!isString(date) || !isDate(date)) {
|
||||||
throw new Error("date could not be parsed");
|
throw new Error("date could not be parsed");
|
||||||
}
|
}
|
||||||
@ -45,18 +45,18 @@ const parseDateNumber = (date: unknown): string => {
|
|||||||
return date;
|
return date;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseEmail = (email: unknown): string => {
|
export const parseEmail = (email: unknown): string => {
|
||||||
if (!isString(email)) {
|
if (!isString(email)) {
|
||||||
throw new Error("incorrect email");
|
throw new Error("incorrect email");
|
||||||
}
|
}
|
||||||
return email;
|
return email;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isBoolean = (booleanCandidate: unknown): booleanCandidate is boolean => {
|
export const isBoolean = (booleanCandidate: unknown): booleanCandidate is boolean => {
|
||||||
return typeof booleanCandidate === "boolean";
|
return typeof booleanCandidate === "boolean";
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseBoolean = (booleanCandidate: unknown): boolean => {
|
export const parseBoolean = (booleanCandidate: unknown): boolean => {
|
||||||
if (!isBoolean(booleanCandidate)) {
|
if (!isBoolean(booleanCandidate)) {
|
||||||
throw new Error("argument was not a boolean");
|
throw new Error("argument was not a boolean");
|
||||||
}
|
}
|
||||||
@ -70,5 +70,3 @@ export const isObject = (objectCandidate: unknown): objectCandidate is Record<st
|
|||||||
!Array.isArray(objectCandidate)
|
!Array.isArray(objectCandidate)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { isString, isNumber, parseString, parseNumber, parseDateNumber, parseBoolean, parseEmail };
|
|
||||||
|
@ -51,6 +51,11 @@ export const fromMongoDate = (date: IMongoDate): Date => {
|
|||||||
return new Date(parseInt(date.$date.$numberLong));
|
return new Date(parseInt(date.$date.$numberLong));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TTraitsPool = Record<
|
||||||
|
"Colors" | "EyeColors" | "FurPatterns" | "BodyTypes" | "Heads" | "Tails",
|
||||||
|
{ type: string; rarity: TRarity }[]
|
||||||
|
>;
|
||||||
|
|
||||||
export const kubrowWeights: Record<TRarity, number> = {
|
export const kubrowWeights: Record<TRarity, number> = {
|
||||||
COMMON: 6,
|
COMMON: 6,
|
||||||
UNCOMMON: 4,
|
UNCOMMON: 4,
|
||||||
@ -65,126 +70,126 @@ export const kubrowFurPatternsWeights: Record<TRarity, number> = {
|
|||||||
LEGENDARY: 1
|
LEGENDARY: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
export const catbrowDetails = {
|
export const catbrowDetails: TTraitsPool = {
|
||||||
Colors: [
|
Colors: [
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" },
|
||||||
|
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" },
|
||||||
|
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" },
|
||||||
|
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" as TRarity }
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" }
|
||||||
],
|
],
|
||||||
|
|
||||||
EyeColors: [
|
EyeColors: [
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" as TRarity }
|
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" }
|
||||||
],
|
],
|
||||||
|
|
||||||
FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" as TRarity }],
|
FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" }],
|
||||||
|
|
||||||
BodyTypes: [
|
BodyTypes: [
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" as TRarity }
|
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" }
|
||||||
],
|
],
|
||||||
|
|
||||||
Heads: [
|
Heads: [
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" as TRarity }
|
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" }
|
||||||
],
|
],
|
||||||
|
|
||||||
Tails: [
|
Tails: [
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" as TRarity }
|
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const kubrowDetails = {
|
export const kubrowDetails: TTraitsPool = {
|
||||||
Colors: [
|
Colors: [
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" },
|
||||||
|
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" },
|
||||||
|
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" as TRarity }
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" }
|
||||||
],
|
],
|
||||||
|
|
||||||
EyeColors: [
|
EyeColors: [
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" as TRarity }
|
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" }
|
||||||
],
|
],
|
||||||
|
|
||||||
FurPatterns: [
|
FurPatterns: [
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" },
|
||||||
|
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" },
|
||||||
|
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" as TRarity }
|
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" }
|
||||||
],
|
],
|
||||||
|
|
||||||
BodyTypes: [
|
BodyTypes: [
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" as TRarity },
|
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" },
|
||||||
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" as TRarity }
|
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" }
|
||||||
],
|
],
|
||||||
|
|
||||||
Heads: [],
|
Heads: [],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { TEquipmentKey } from "../types/inventoryTypes/inventoryTypes";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
export const modularWeaponTypes: Record<string, TEquipmentKey> = {
|
export const modularWeaponTypes: Record<string, TEquipmentKey> = {
|
||||||
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns",
|
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns",
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
|
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
|
||||||
import { IInfNode, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInfNode, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
|
import { generateRewardSeed, getRewardAtPercentage, SRng } from "@/src/services/rngService";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { logger } from "../utils/logger";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { IOid } from "../types/commonTypes";
|
import { isArchwingMission } from "@/src/services/worldStateService";
|
||||||
import { Types } from "mongoose";
|
|
||||||
import { addMods, generateRewardSeed } from "../services/inventoryService";
|
|
||||||
import { isArchwingMission } from "../services/worldStateService";
|
|
||||||
|
|
||||||
type TInnateDamageTag =
|
type TInnateDamageTag =
|
||||||
| "InnateElectricityDamage"
|
| "InnateElectricityDamage"
|
||||||
@ -364,57 +361,6 @@ export const parseUpgrade = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const consumeModCharge = (
|
|
||||||
response: IKnifeResponse,
|
|
||||||
inventory: TInventoryDatabaseDocument,
|
|
||||||
upgrade: { ItemId: IOid; ItemType: string },
|
|
||||||
dataknifeUpgrades: string[]
|
|
||||||
): void => {
|
|
||||||
response.UpgradeIds ??= [];
|
|
||||||
response.UpgradeTypes ??= [];
|
|
||||||
response.UpgradeFingerprints ??= [];
|
|
||||||
response.UpgradeNew ??= [];
|
|
||||||
response.HasKnife = true;
|
|
||||||
|
|
||||||
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
|
||||||
const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
|
|
||||||
const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
|
|
||||||
fingerprint.lvl += 1;
|
|
||||||
dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
|
|
||||||
|
|
||||||
response.UpgradeIds.push(upgrade.ItemId.$oid);
|
|
||||||
response.UpgradeTypes.push(upgrade.ItemType);
|
|
||||||
response.UpgradeFingerprints.push(fingerprint);
|
|
||||||
response.UpgradeNew.push(false);
|
|
||||||
} else {
|
|
||||||
const id = new Types.ObjectId();
|
|
||||||
inventory.Upgrades.push({
|
|
||||||
_id: id,
|
|
||||||
ItemType: upgrade.ItemType,
|
|
||||||
UpgradeFingerprint: `{"lvl":1}`
|
|
||||||
});
|
|
||||||
|
|
||||||
addMods(inventory, [
|
|
||||||
{
|
|
||||||
ItemType: upgrade.ItemType,
|
|
||||||
ItemCount: -1
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType);
|
|
||||||
if (dataknifeRawUpgradeIndex != -1) {
|
|
||||||
dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString();
|
|
||||||
} else {
|
|
||||||
logger.warn(`${upgrade.ItemType} not found in dataknife config`);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.UpgradeIds.push(id.toString());
|
|
||||||
response.UpgradeTypes.push(upgrade.ItemType);
|
|
||||||
response.UpgradeFingerprints.push({ lvl: 1 });
|
|
||||||
response.UpgradeNew.push(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
|
export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
|
||||||
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,8 @@ import { getRandomWeightedReward, IRngResult } from "@/src/services/rngService";
|
|||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { addMiscItems, combineInventoryChanges } from "@/src/services/inventoryService";
|
import { addMiscItems, combineInventoryChanges } from "@/src/services/inventoryService";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import { IInventoryChanges } from "../types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
|
||||||
export const crackRelic = async (
|
export const crackRelic = async (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
@ -13,12 +14,25 @@ export const crackRelic = async (
|
|||||||
inventoryChanges: IInventoryChanges = {}
|
inventoryChanges: IInventoryChanges = {}
|
||||||
): Promise<IRngResult> => {
|
): Promise<IRngResult> => {
|
||||||
const relic = ExportRelics[participant.VoidProjection];
|
const relic = ExportRelics[participant.VoidProjection];
|
||||||
const weights = refinementToWeights[relic.quality];
|
let weights = refinementToWeights[relic.quality];
|
||||||
|
if (relic.quality == "VPQ_SILVER" && config.exceptionalRelicsAlwaysGiveBronzeReward) {
|
||||||
|
weights = { COMMON: 1, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 };
|
||||||
|
} else if (relic.quality == "VPQ_GOLD" && config.flawlessRelicsAlwaysGiveSilverReward) {
|
||||||
|
weights = { COMMON: 0, UNCOMMON: 1, RARE: 0, LEGENDARY: 0 };
|
||||||
|
} else if (relic.quality == "VPQ_PLATINUM" && config.radiantRelicsAlwaysGiveGoldReward) {
|
||||||
|
weights = { COMMON: 0, UNCOMMON: 0, RARE: 1, LEGENDARY: 0 };
|
||||||
|
}
|
||||||
logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
|
logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
|
||||||
const reward = getRandomWeightedReward(
|
let reward = getRandomWeightedReward(
|
||||||
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
|
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
|
||||||
weights
|
weights
|
||||||
)!;
|
)!;
|
||||||
|
if (config.relicRewardItemCountMultiplier !== undefined && (config.relicRewardItemCountMultiplier ?? 1) != 1) {
|
||||||
|
reward = {
|
||||||
|
...reward,
|
||||||
|
itemCount: reward.itemCount * config.relicRewardItemCountMultiplier
|
||||||
|
};
|
||||||
|
}
|
||||||
logger.debug(`relic rolled`, reward);
|
logger.debug(`relic rolled`, reward);
|
||||||
participant.Reward = reward.type;
|
participant.Reward = reward.type;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IUpgrade } from "warframe-public-export-plus";
|
import { IUpgrade } from "warframe-public-export-plus";
|
||||||
import { getRandomElement, getRandomInt, getRandomReward } from "../services/rngService";
|
import { getRandomElement, getRandomInt, getRandomReward } from "@/src/services/rngService";
|
||||||
|
|
||||||
export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint;
|
export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint;
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ logger.info("Starting up...");
|
|||||||
// Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP.
|
// Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP.
|
||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { JSONStringify } from "json-with-bigint";
|
import { JSONStringify } from "json-with-bigint";
|
||||||
import { startWebServer } from "./services/webService";
|
import { startWebServer } from "@/src/services/webService";
|
||||||
|
|
||||||
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
|
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
|
||||||
import { updateWorldStateCollections } from "./services/worldStateService";
|
import { updateWorldStateCollections } from "@/src/services/worldStateService";
|
||||||
|
|
||||||
// Patch JSON.stringify to work flawlessly with Bigints.
|
// Patch JSON.stringify to work flawlessly with Bigints.
|
||||||
JSON.stringify = JSONStringify;
|
JSON.stringify = JSONStringify;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
|
|
||||||
export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => {
|
export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => {
|
||||||
if (err.message == "Invalid accountId-nonce pair") {
|
if (err.message == "Invalid accountId-nonce pair") {
|
||||||
|
25
src/models/commonModel.ts
Normal file
25
src/models/commonModel.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Schema } from "mongoose";
|
||||||
|
import { IColor, IShipCustomization } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
|
||||||
|
export const colorSchema = new Schema<IColor>(
|
||||||
|
{
|
||||||
|
t0: Number,
|
||||||
|
t1: Number,
|
||||||
|
t2: Number,
|
||||||
|
t3: Number,
|
||||||
|
en: Number,
|
||||||
|
e1: Number,
|
||||||
|
m0: Number,
|
||||||
|
m1: Number
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
export const shipCustomizationSchema = new Schema<IShipCustomization>(
|
||||||
|
{
|
||||||
|
SkinFlavourItem: String,
|
||||||
|
Colors: colorSchema,
|
||||||
|
ShipAttachments: { HOOD_ORNAMENT: String }
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
@ -17,8 +17,8 @@ import {
|
|||||||
GuildPermission
|
GuildPermission
|
||||||
} from "@/src/types/guildTypes";
|
} from "@/src/types/guildTypes";
|
||||||
import { Document, Model, model, Schema, Types } from "mongoose";
|
import { Document, Model, model, Schema, Types } from "mongoose";
|
||||||
import { fusionTreasuresSchema, typeCountSchema } from "./inventoryModels/inventoryModel";
|
import { fusionTreasuresSchema, typeCountSchema } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { pictureFrameInfoSchema } from "./personalRoomsModel";
|
import { pictureFrameInfoSchema } from "@/src/models/personalRoomsModel";
|
||||||
|
|
||||||
const dojoDecoSchema = new Schema<IDojoDecoDatabase>({
|
const dojoDecoSchema = new Schema<IDojoDecoDatabase>({
|
||||||
Type: String,
|
Type: String,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { model, Schema, Types } from "mongoose";
|
import { model, Schema, Types } from "mongoose";
|
||||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { typeCountSchema } from "@/src/models/inventoryModels/inventoryModel";
|
import { typeCountSchema } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
import { IMongoDate, IOid, ITypeCount } from "@/src/types/commonTypes";
|
||||||
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
|
||||||
|
|
||||||
export interface IMessageClient
|
export interface IMessageClient
|
||||||
extends Omit<IMessageDatabase, "_id" | "date" | "startDate" | "endDate" | "ownerId" | "attVisualOnly" | "expiry"> {
|
extends Omit<IMessageDatabase, "_id" | "date" | "startDate" | "endDate" | "ownerId" | "attVisualOnly" | "expiry"> {
|
||||||
@ -23,7 +22,9 @@ export interface IMessageDatabase extends IMessage {
|
|||||||
export interface IMessage {
|
export interface IMessage {
|
||||||
sndr: string;
|
sndr: string;
|
||||||
msg: string;
|
msg: string;
|
||||||
|
cinematic?: string;
|
||||||
sub: string;
|
sub: string;
|
||||||
|
customData?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
highPriority?: boolean;
|
highPriority?: boolean;
|
||||||
lowPrioNewPlayers?: boolean;
|
lowPrioNewPlayers?: boolean;
|
||||||
@ -102,7 +103,9 @@ const messageSchema = new Schema<IMessageDatabase>(
|
|||||||
ownerId: Schema.Types.ObjectId,
|
ownerId: Schema.Types.ObjectId,
|
||||||
sndr: String,
|
sndr: String,
|
||||||
msg: String,
|
msg: String,
|
||||||
|
cinematic: String,
|
||||||
sub: String,
|
sub: String,
|
||||||
|
customData: String,
|
||||||
icon: String,
|
icon: String,
|
||||||
highPriority: Boolean,
|
highPriority: Boolean,
|
||||||
lowPrioNewPlayers: Boolean,
|
lowPrioNewPlayers: Boolean,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Document, Model, Schema, Types, model } from "mongoose";
|
import { Document, Model, Schema, Types, model } from "mongoose";
|
||||||
import {
|
import {
|
||||||
IFlavourItem,
|
|
||||||
IRawUpgrade,
|
IRawUpgrade,
|
||||||
IMiscItem,
|
IMiscItem,
|
||||||
IInventoryDatabase,
|
IInventoryDatabase,
|
||||||
@ -10,7 +9,6 @@ import {
|
|||||||
IDuviriInfo,
|
IDuviriInfo,
|
||||||
IPendingRecipeDatabase,
|
IPendingRecipeDatabase,
|
||||||
IPendingRecipeClient,
|
IPendingRecipeClient,
|
||||||
ITypeCount,
|
|
||||||
IFocusXP,
|
IFocusXP,
|
||||||
IFocusUpgrade,
|
IFocusUpgrade,
|
||||||
ITypeXPItem,
|
ITypeXPItem,
|
||||||
@ -39,25 +37,15 @@ import {
|
|||||||
IEvolutionProgress,
|
IEvolutionProgress,
|
||||||
IEndlessXpProgressDatabase,
|
IEndlessXpProgressDatabase,
|
||||||
IEndlessXpProgressClient,
|
IEndlessXpProgressClient,
|
||||||
ICrewShipCustomization,
|
|
||||||
ICrewShipWeapon,
|
|
||||||
ICrewShipWeaponEmplacements,
|
|
||||||
IShipExterior,
|
|
||||||
IHelminthFoodRecord,
|
IHelminthFoodRecord,
|
||||||
ICrewShipMembersDatabase,
|
|
||||||
IDialogueHistoryDatabase,
|
IDialogueHistoryDatabase,
|
||||||
IDialogueDatabase,
|
IDialogueDatabase,
|
||||||
IDialogueGift,
|
IDialogueGift,
|
||||||
ICompletedDialogue,
|
ICompletedDialogue,
|
||||||
IDialogueClient,
|
IDialogueClient,
|
||||||
IUpgradeDatabase,
|
IUpgradeDatabase,
|
||||||
ICrewShipMemberDatabase,
|
|
||||||
ICrewShipMemberClient,
|
|
||||||
TEquipmentKey,
|
TEquipmentKey,
|
||||||
equipmentKeys,
|
equipmentKeys,
|
||||||
IKubrowPetDetailsDatabase,
|
|
||||||
ITraits,
|
|
||||||
IKubrowPetDetailsClient,
|
|
||||||
IKubrowPetEggDatabase,
|
IKubrowPetEggDatabase,
|
||||||
IKubrowPetEggClient,
|
IKubrowPetEggClient,
|
||||||
ICustomMarkers,
|
ICustomMarkers,
|
||||||
@ -96,27 +84,39 @@ import {
|
|||||||
IInvasionProgressClient,
|
IInvasionProgressClient,
|
||||||
IAccolades,
|
IAccolades,
|
||||||
IHubNpcCustomization,
|
IHubNpcCustomization,
|
||||||
ILotusCustomization,
|
|
||||||
IEndlessXpReward,
|
IEndlessXpReward,
|
||||||
IPersonalGoalProgressDatabase,
|
IPersonalGoalProgressDatabase,
|
||||||
IPersonalGoalProgressClient,
|
IPersonalGoalProgressClient,
|
||||||
IKubrowPetPrintClient,
|
IKubrowPetPrintClient,
|
||||||
IKubrowPetPrintDatabase
|
IKubrowPetPrintDatabase
|
||||||
} from "../../types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IOid } from "../../types/commonTypes";
|
import { IOid, ITypeCount } from "@/src/types/commonTypes";
|
||||||
import {
|
import {
|
||||||
IAbilityOverride,
|
IAbilityOverride,
|
||||||
IColor,
|
ICrewShipCustomization,
|
||||||
|
IFlavourItem,
|
||||||
IItemConfig,
|
IItemConfig,
|
||||||
|
ILotusCustomization,
|
||||||
IOperatorConfigDatabase,
|
IOperatorConfigDatabase,
|
||||||
IPolarity,
|
IPolarity
|
||||||
IEquipmentDatabase,
|
|
||||||
IArchonCrystalUpgrade,
|
|
||||||
IEquipmentClient
|
|
||||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { EquipmentSelectionSchema, oidSchema } from "./loadoutModel";
|
import { EquipmentSelectionSchema, oidSchema } from "@/src/models/inventoryModels/loadoutModel";
|
||||||
import { ICountedStoreItem } from "warframe-public-export-plus";
|
import { ICountedStoreItem } from "warframe-public-export-plus";
|
||||||
|
import { colorSchema, shipCustomizationSchema } from "@/src/models/commonModel";
|
||||||
|
import {
|
||||||
|
IArchonCrystalUpgrade,
|
||||||
|
ICrewShipMemberClient,
|
||||||
|
ICrewShipMemberDatabase,
|
||||||
|
ICrewShipMembersDatabase,
|
||||||
|
ICrewShipWeapon,
|
||||||
|
ICrewShipWeaponEmplacements,
|
||||||
|
IEquipmentClient,
|
||||||
|
IEquipmentDatabase,
|
||||||
|
IKubrowPetDetailsClient,
|
||||||
|
IKubrowPetDetailsDatabase,
|
||||||
|
ITraits
|
||||||
|
} from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
|
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
|
||||||
|
|
||||||
@ -166,20 +166,6 @@ const abilityOverrideSchema = new Schema<IAbilityOverride>(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
export const colorSchema = new Schema<IColor>(
|
|
||||||
{
|
|
||||||
t0: Number,
|
|
||||||
t1: Number,
|
|
||||||
t2: Number,
|
|
||||||
t3: Number,
|
|
||||||
en: Number,
|
|
||||||
e1: Number,
|
|
||||||
m0: Number,
|
|
||||||
m1: Number
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const operatorConfigSchema = new Schema<IOperatorConfigDatabase>(
|
const operatorConfigSchema = new Schema<IOperatorConfigDatabase>(
|
||||||
{
|
{
|
||||||
Skins: [String],
|
Skins: [String],
|
||||||
@ -896,18 +882,9 @@ const crewShipWeaponSchema = new Schema<ICrewShipWeapon>(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
const shipExteriorSchema = new Schema<IShipExterior>(
|
|
||||||
{
|
|
||||||
SkinFlavourItem: String,
|
|
||||||
Colors: colorSchema,
|
|
||||||
ShipAttachments: { HOOD_ORNAMENT: String }
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const crewShipCustomizationSchema = new Schema<ICrewShipCustomization>(
|
const crewShipCustomizationSchema = new Schema<ICrewShipCustomization>(
|
||||||
{
|
{
|
||||||
CrewshipInterior: shipExteriorSchema
|
CrewshipInterior: shipCustomizationSchema
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
@ -1239,8 +1216,8 @@ const calenderProgressSchema = new Schema<ICalendarProgress>(
|
|||||||
},
|
},
|
||||||
SeasonProgress: {
|
SeasonProgress: {
|
||||||
SeasonType: { type: String, required: true },
|
SeasonType: { type: String, required: true },
|
||||||
LastCompletedDayIdx: { type: Number, default: 0 },
|
LastCompletedDayIdx: { type: Number, default: -1 },
|
||||||
LastCompletedChallengeDayIdx: { type: Number, default: 0 },
|
LastCompletedChallengeDayIdx: { type: Number, default: -1 },
|
||||||
ActivatedChallenges: { type: [String], default: [] }
|
ActivatedChallenges: { type: [String], default: [] }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { IEquipmentSelection } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentSelection } from "@/src/types/equipmentTypes";
|
||||||
import { ILoadoutConfigDatabase, ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
import { ILoadoutConfigDatabase, ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
||||||
import { Document, Model, Schema, Types, model } from "mongoose";
|
import { Document, Model, Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Document, model, Schema, Types } from "mongoose";
|
import { Document, model, Schema, Types } from "mongoose";
|
||||||
import { ILeaderboardEntryDatabase } from "../types/leaderboardTypes";
|
import { ILeaderboardEntryDatabase } from "@/src/types/leaderboardTypes";
|
||||||
|
|
||||||
const leaderboardEntrySchema = new Schema<ILeaderboardEntryDatabase>(
|
const leaderboardEntrySchema = new Schema<ILeaderboardEntryDatabase>(
|
||||||
{
|
{
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { colorSchema } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { IOrbiter, IPersonalRoomsDatabase, PersonalRoomsModelType } from "@/src/types/personalRoomsTypes";
|
|
||||||
import {
|
import {
|
||||||
|
IApartmentDatabase,
|
||||||
IFavouriteLoadoutDatabase,
|
IFavouriteLoadoutDatabase,
|
||||||
IGardeningDatabase,
|
IGardeningDatabase,
|
||||||
IPlacedDecosDatabase,
|
IOrbiterClient,
|
||||||
|
IOrbiterDatabase,
|
||||||
|
IPersonalRoomsDatabase,
|
||||||
IPictureFrameInfo,
|
IPictureFrameInfo,
|
||||||
|
IPlacedDecosDatabase,
|
||||||
|
IPlantClient,
|
||||||
|
IPlantDatabase,
|
||||||
|
IPlanterDatabase,
|
||||||
IRoom,
|
IRoom,
|
||||||
ITailorShopDatabase,
|
ITailorShopDatabase,
|
||||||
IApartmentDatabase,
|
PersonalRoomsModelType
|
||||||
IPlanterDatabase,
|
} from "@/src/types/personalRoomsTypes";
|
||||||
IPlantDatabase,
|
|
||||||
IPlantClient
|
|
||||||
} from "@/src/types/shipTypes";
|
|
||||||
import { Schema, Types, model } from "mongoose";
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
import { colorSchema, shipCustomizationSchema } from "@/src/models/commonModel";
|
||||||
|
|
||||||
export const pictureFrameInfoSchema = new Schema<IPictureFrameInfo>(
|
export const pictureFrameInfoSchema = new Schema<IPictureFrameInfo>(
|
||||||
{
|
{
|
||||||
@ -137,10 +140,11 @@ const apartmentDefault: IApartmentDatabase = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const orbiterSchema = new Schema<IOrbiter>(
|
const orbiterSchema = new Schema<IOrbiterDatabase>(
|
||||||
{
|
{
|
||||||
Features: [String],
|
Features: [String],
|
||||||
Rooms: [roomSchema],
|
Rooms: [roomSchema],
|
||||||
|
ShipInterior: shipCustomizationSchema,
|
||||||
VignetteFish: { type: [String], default: undefined },
|
VignetteFish: { type: [String], default: undefined },
|
||||||
FavouriteLoadoutId: Schema.Types.ObjectId,
|
FavouriteLoadoutId: Schema.Types.ObjectId,
|
||||||
Wallpaper: String,
|
Wallpaper: String,
|
||||||
@ -150,7 +154,18 @@ const orbiterSchema = new Schema<IOrbiter>(
|
|||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
const orbiterDefault: IOrbiter = {
|
orbiterSchema.set("toJSON", {
|
||||||
|
virtuals: true,
|
||||||
|
transform(_doc, obj) {
|
||||||
|
const db = obj as IOrbiterDatabase;
|
||||||
|
const client = obj as IOrbiterClient;
|
||||||
|
|
||||||
|
if (db.FavouriteLoadoutId) {
|
||||||
|
client.FavouriteLoadoutId = toOid(db.FavouriteLoadoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const orbiterDefault: IOrbiterDatabase = {
|
||||||
Features: ["/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem"], //TODO: potentially remove after missionstarting gear
|
Features: ["/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem"], //TODO: potentially remove after missionstarting gear
|
||||||
Rooms: [
|
Rooms: [
|
||||||
{ Name: "AlchemyRoom", MaxCapacity: 1600 },
|
{ Name: "AlchemyRoom", MaxCapacity: 1600 },
|
||||||
@ -197,7 +212,6 @@ const tailorShopDefault: ITailorShopDatabase = {
|
|||||||
export const personalRoomsSchema = new Schema<IPersonalRoomsDatabase>({
|
export const personalRoomsSchema = new Schema<IPersonalRoomsDatabase>({
|
||||||
personalRoomsOwnerId: Schema.Types.ObjectId,
|
personalRoomsOwnerId: Schema.Types.ObjectId,
|
||||||
activeShipId: Schema.Types.ObjectId,
|
activeShipId: Schema.Types.ObjectId,
|
||||||
ShipInteriorColors: colorSchema,
|
|
||||||
Ship: { type: orbiterSchema, default: orbiterDefault },
|
Ship: { type: orbiterSchema, default: orbiterDefault },
|
||||||
Apartment: { type: apartmentSchema, default: apartmentDefault },
|
Apartment: { type: apartmentSchema, default: apartmentDefault },
|
||||||
TailorShop: { type: tailorShopSchema, default: tailorShopDefault }
|
TailorShop: { type: tailorShopSchema, default: tailorShopDefault }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
import { IShipDatabase } from "../types/shipTypes";
|
import { IShipDatabase } from "@/src/types/shipTypes";
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { colorSchema } from "@/src/models/inventoryModels/inventoryModel";
|
import { colorSchema } from "@/src/models/commonModel";
|
||||||
import { IShipInventory } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IShipInventory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
const shipSchema = new Schema<IShipDatabase>(
|
const shipSchema = new Schema<IShipDatabase>(
|
||||||
|
@ -112,6 +112,7 @@ import { removeFromGuildController } from "@/src/controllers/api/removeFromGuild
|
|||||||
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
|
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
|
||||||
import { renamePetController } from "@/src/controllers/api/renamePetController";
|
import { renamePetController } from "@/src/controllers/api/renamePetController";
|
||||||
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
|
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
|
||||||
|
import { resetQuestProgressController } from "@/src/controllers/api/resetQuestProgressController";
|
||||||
import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
|
import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
|
||||||
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
|
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
|
||||||
import { saveLoadoutController } from "@/src/controllers/api/saveLoadoutController";
|
import { saveLoadoutController } from "@/src/controllers/api/saveLoadoutController";
|
||||||
@ -209,6 +210,7 @@ apiRouter.get("/questControl.php", questControlController);
|
|||||||
apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController);
|
apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController);
|
||||||
apiRouter.get("/removeFriend.php", removeFriendGetController);
|
apiRouter.get("/removeFriend.php", removeFriendGetController);
|
||||||
apiRouter.get("/removeFromAlliance.php", removeFromAllianceController);
|
apiRouter.get("/removeFromAlliance.php", removeFromAllianceController);
|
||||||
|
apiRouter.get("/resetQuestProgress.php", resetQuestProgressController);
|
||||||
apiRouter.get("/setActiveQuest.php", setActiveQuestController);
|
apiRouter.get("/setActiveQuest.php", setActiveQuestController);
|
||||||
apiRouter.get("/setActiveShip.php", setActiveShipController);
|
apiRouter.get("/setActiveShip.php", setActiveShipController);
|
||||||
apiRouter.get("/setAllianceGuildPermissions.php", setAllianceGuildPermissionsController);
|
apiRouter.get("/setAllianceGuildPermissions.php", setAllianceGuildPermissionsController);
|
||||||
|
@ -64,9 +64,13 @@ export interface IConfig {
|
|||||||
noDojoResearchTime?: boolean;
|
noDojoResearchTime?: boolean;
|
||||||
fastClanAscension?: boolean;
|
fastClanAscension?: boolean;
|
||||||
missionsCanGiveAllRelics?: boolean;
|
missionsCanGiveAllRelics?: boolean;
|
||||||
|
exceptionalRelicsAlwaysGiveBronzeReward?: boolean;
|
||||||
|
flawlessRelicsAlwaysGiveSilverReward?: boolean;
|
||||||
|
radiantRelicsAlwaysGiveGoldReward?: boolean;
|
||||||
unlockAllSimarisResearchEntries?: boolean;
|
unlockAllSimarisResearchEntries?: boolean;
|
||||||
disableDailyTribute?: boolean;
|
disableDailyTribute?: boolean;
|
||||||
spoofMasteryRank?: number;
|
spoofMasteryRank?: number;
|
||||||
|
relicRewardItemCountMultiplier?: number;
|
||||||
nightwaveStandingMultiplier?: number;
|
nightwaveStandingMultiplier?: number;
|
||||||
unfaithfulBugFixes?: {
|
unfaithfulBugFixes?: {
|
||||||
ignore1999LastRegionPlayed?: boolean;
|
ignore1999LastRegionPlayed?: boolean;
|
||||||
@ -85,6 +89,8 @@ export interface IConfig {
|
|||||||
allTheFissures?: string;
|
allTheFissures?: string;
|
||||||
circuitGameModes?: string[];
|
circuitGameModes?: string[];
|
||||||
darvoStockMultiplier?: number;
|
darvoStockMultiplier?: number;
|
||||||
|
varziaOverride?: string;
|
||||||
|
varziaFullyStocked?: boolean;
|
||||||
};
|
};
|
||||||
dev?: {
|
dev?: {
|
||||||
keepVendorsExpired?: boolean;
|
keepVendorsExpired?: boolean;
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import chokidar from "chokidar";
|
import chokidar from "chokidar";
|
||||||
import fsPromises from "fs/promises";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { logger } from "../utils/logger";
|
import { config, configPath, loadConfig } from "@/src/services/configService";
|
||||||
import { config, configPath, loadConfig } from "./configService";
|
import { saveConfig, shouldReloadConfig } from "@/src/services/configWriterService";
|
||||||
import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService";
|
import { getWebPorts, startWebServer, stopWebServer } from "@/src/services/webService";
|
||||||
import { Inbox } from "../models/inboxModel";
|
import { sendWsBroadcast } from "@/src/services/wsService";
|
||||||
|
import { Inbox } from "@/src/models/inboxModel";
|
||||||
|
import varzia from "@/static/fixed_responses/worldState/varzia.json";
|
||||||
|
|
||||||
let amnesia = false;
|
|
||||||
chokidar.watch(configPath).on("change", () => {
|
chokidar.watch(configPath).on("change", () => {
|
||||||
if (amnesia) {
|
if (shouldReloadConfig()) {
|
||||||
amnesia = false;
|
|
||||||
} else {
|
|
||||||
logger.info("Detected a change to config file, reloading its contents.");
|
logger.info("Detected a change to config file, reloading its contents.");
|
||||||
try {
|
try {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
@ -57,17 +56,19 @@ export const validateConfig = (): void => {
|
|||||||
config.worldState.galleonOfGhouls = 0;
|
config.worldState.galleonOfGhouls = 0;
|
||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
config.worldState?.varziaOverride &&
|
||||||
|
!varzia.primeDualPacks.some(p => p.ItemType === config.worldState?.varziaOverride)
|
||||||
|
) {
|
||||||
|
config.worldState.varziaOverride = "";
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
if (modified) {
|
if (modified) {
|
||||||
logger.info(`Updating config file to fix some issues with it.`);
|
logger.info(`Updating config file to fix some issues with it.`);
|
||||||
void saveConfig();
|
void saveConfig();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const saveConfig = async (): Promise<void> => {
|
|
||||||
amnesia = true;
|
|
||||||
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const syncConfigWithDatabase = (): void => {
|
export const syncConfigWithDatabase = (): void => {
|
||||||
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
|
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
|
||||||
if (!config.worldState?.galleonOfGhouls) {
|
if (!config.worldState?.galleonOfGhouls) {
|
||||||
|
17
src/services/configWriterService.ts
Normal file
17
src/services/configWriterService.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import fsPromises from "fs/promises";
|
||||||
|
import { config, configPath } from "@/src/services/configService";
|
||||||
|
|
||||||
|
let amnesia = false;
|
||||||
|
|
||||||
|
export const saveConfig = async (): Promise<void> => {
|
||||||
|
amnesia = true;
|
||||||
|
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shouldReloadConfig = (): boolean => {
|
||||||
|
if (amnesia) {
|
||||||
|
amnesia = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
@ -1,10 +1,10 @@
|
|||||||
import { IFriendInfo } from "../types/friendTypes";
|
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||||
import { getInventory } from "./inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { config } from "./configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { Account } from "../models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Friendship } from "../models/friendModel";
|
import { Friendship } from "@/src/models/friendModel";
|
||||||
import { fromOid, toMongoDate } from "../helpers/inventoryHelpers";
|
import { fromOid, toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
|
|
||||||
export const addAccountDataToFriendInfo = async (info: IFriendInfo): Promise<void> => {
|
export const addAccountDataToFriendInfo = async (info: IFriendInfo): Promise<void> => {
|
||||||
const account = (await Account.findById(fromOid(info._id), "DisplayName LastLogin"))!;
|
const account = (await Account.findById(fromOid(info._id), "DisplayName LastLogin"))!;
|
||||||
|
@ -22,16 +22,17 @@ import {
|
|||||||
import { toMongoDate, toOid, toOid2 } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate, toOid, toOid2 } from "@/src/helpers/inventoryHelpers";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { ExportDojoRecipes, ExportResources, IDojoBuild, IDojoResearch } from "warframe-public-export-plus";
|
import { ExportDojoRecipes, ExportResources, IDojoBuild, IDojoResearch } from "warframe-public-export-plus";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { config } from "./configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { getRandomInt } from "./rngService";
|
import { getRandomInt } from "@/src/services/rngService";
|
||||||
import { Inbox } from "../models/inboxModel";
|
import { Inbox } from "@/src/models/inboxModel";
|
||||||
import { IFusionTreasure, ITypeCount } from "../types/inventoryTypes/inventoryTypes";
|
import { IFusionTreasure } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IInventoryChanges } from "../types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { parallelForeach } from "../utils/async-utils";
|
import { parallelForeach } from "@/src/utils/async-utils";
|
||||||
import allDecoRecipes from "@/static/fixed_responses/allDecoRecipes.json";
|
import allDecoRecipes from "@/static/fixed_responses/allDecoRecipes.json";
|
||||||
import { createMessage } from "./inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "./friendService";
|
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
|
||||||
|
import { ITypeCount } from "@/src/types/commonTypes";
|
||||||
|
|
||||||
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
|
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -550,6 +551,19 @@ export const processFundedGuildTechProject = (
|
|||||||
guild.XP += recipe.guildXpValue;
|
guild.XP += recipe.guildXpValue;
|
||||||
}
|
}
|
||||||
setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
|
setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
|
||||||
|
if (config.noDojoResearchTime) {
|
||||||
|
processCompletedGuildTechProject(guild, techProject.ItemType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const processCompletedGuildTechProject = (guild: TGuildDatabaseDocument, type: string): void => {
|
||||||
|
if (type.startsWith("/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/")) {
|
||||||
|
guild.VaultDecoRecipes ??= [];
|
||||||
|
guild.VaultDecoRecipes.push({
|
||||||
|
ItemType: type,
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setGuildTechLogState = (
|
export const setGuildTechLogState = (
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
IEquipmentClient,
|
|
||||||
IEquipmentDatabase,
|
|
||||||
IItemConfig,
|
IItemConfig,
|
||||||
IOperatorConfigClient,
|
IOperatorConfigClient,
|
||||||
IOperatorConfigDatabase
|
IOperatorConfigDatabase
|
||||||
} from "../types/inventoryTypes/commonInventoryTypes";
|
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { IMongoDate } from "../types/commonTypes";
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import {
|
import {
|
||||||
equipmentKeys,
|
equipmentKeys,
|
||||||
ICrewShipMemberClient,
|
|
||||||
ICrewShipMemberDatabase,
|
|
||||||
ICrewShipMembersClient,
|
|
||||||
ICrewShipMembersDatabase,
|
|
||||||
IDialogueClient,
|
IDialogueClient,
|
||||||
IDialogueDatabase,
|
IDialogueDatabase,
|
||||||
IDialogueHistoryClient,
|
IDialogueHistoryClient,
|
||||||
@ -20,10 +14,6 @@ import {
|
|||||||
IInfestedFoundryClient,
|
IInfestedFoundryClient,
|
||||||
IInfestedFoundryDatabase,
|
IInfestedFoundryDatabase,
|
||||||
IInventoryClient,
|
IInventoryClient,
|
||||||
IKubrowPetDetailsClient,
|
|
||||||
IKubrowPetDetailsDatabase,
|
|
||||||
ILoadoutConfigClient,
|
|
||||||
ILoadOutPresets,
|
|
||||||
INemesisClient,
|
INemesisClient,
|
||||||
INemesisDatabase,
|
INemesisDatabase,
|
||||||
IPendingRecipeClient,
|
IPendingRecipeClient,
|
||||||
@ -35,10 +25,25 @@ import {
|
|||||||
IUpgradeDatabase,
|
IUpgradeDatabase,
|
||||||
IWeaponSkinClient,
|
IWeaponSkinClient,
|
||||||
IWeaponSkinDatabase
|
IWeaponSkinDatabase
|
||||||
} from "../types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { ILoadoutConfigDatabase, ILoadoutDatabase } from "../types/saveLoadoutTypes";
|
import {
|
||||||
import { slotNames } from "../types/purchaseTypes";
|
ILoadoutConfigClient,
|
||||||
|
ILoadoutConfigDatabase,
|
||||||
|
ILoadoutDatabase,
|
||||||
|
ILoadOutPresets
|
||||||
|
} from "@/src/types/saveLoadoutTypes";
|
||||||
|
import { slotNames } from "@/src/types/purchaseTypes";
|
||||||
|
import {
|
||||||
|
ICrewShipMemberClient,
|
||||||
|
ICrewShipMemberDatabase,
|
||||||
|
ICrewShipMembersClient,
|
||||||
|
ICrewShipMembersDatabase,
|
||||||
|
IEquipmentClient,
|
||||||
|
IEquipmentDatabase,
|
||||||
|
IKubrowPetDetailsClient,
|
||||||
|
IKubrowPetDetailsDatabase
|
||||||
|
} from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
const convertDate = (value: IMongoDate): Date => {
|
const convertDate = (value: IMongoDate): Date => {
|
||||||
return new Date(parseInt(value.$date.$numberLong));
|
return new Date(parseInt(value.$date.$numberLong));
|
||||||
|
@ -2,8 +2,8 @@ import { IMessageDatabase, Inbox } from "@/src/models/inboxModel";
|
|||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { HydratedDocument, Types } from "mongoose";
|
import { HydratedDocument, Types } from "mongoose";
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import { unixTimesInMs } from "../constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { config } from "./configService";
|
import { config } from "@/src/services/configService";
|
||||||
|
|
||||||
export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => {
|
export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => {
|
||||||
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });
|
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { ExportRecipes } from "warframe-public-export-plus";
|
import { ExportRecipes } from "warframe-public-export-plus";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { IInfestedFoundryClient, IInfestedFoundryDatabase, ITypeCount } from "../types/inventoryTypes/inventoryTypes";
|
import { IInfestedFoundryClient, IInfestedFoundryDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { addRecipes } from "./inventoryService";
|
import { addRecipes } from "@/src/services/inventoryService";
|
||||||
import { config } from "./configService";
|
import { config } from "@/src/services/configService";
|
||||||
|
import { ITypeCount } from "@/src/types/commonTypes";
|
||||||
|
|
||||||
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => {
|
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => {
|
||||||
const recipeChanges: ITypeCount[] = [];
|
const recipeChanges: ITypeCount[] = [];
|
||||||
|
@ -4,12 +4,10 @@ import { Types } from "mongoose";
|
|||||||
import { SlotNames, IInventoryChanges, IBinChanges, slotNames, IAffiliationMods } from "@/src/types/purchaseTypes";
|
import { SlotNames, IInventoryChanges, IBinChanges, slotNames, IAffiliationMods } from "@/src/types/purchaseTypes";
|
||||||
import {
|
import {
|
||||||
IChallengeProgress,
|
IChallengeProgress,
|
||||||
IFlavourItem,
|
|
||||||
IMiscItem,
|
IMiscItem,
|
||||||
IMission,
|
IMission,
|
||||||
IRawUpgrade,
|
IRawUpgrade,
|
||||||
ISeasonChallenge,
|
ISeasonChallenge,
|
||||||
ITypeCount,
|
|
||||||
InventorySlot,
|
InventorySlot,
|
||||||
IWeaponSkinClient,
|
IWeaponSkinClient,
|
||||||
TEquipmentKey,
|
TEquipmentKey,
|
||||||
@ -23,25 +21,17 @@ import {
|
|||||||
TPartialStartingGear,
|
TPartialStartingGear,
|
||||||
ILoreFragmentScan,
|
ILoreFragmentScan,
|
||||||
ICrewMemberClient,
|
ICrewMemberClient,
|
||||||
Status,
|
|
||||||
IKubrowPetDetailsDatabase,
|
|
||||||
ITraits,
|
|
||||||
ICalendarProgress,
|
ICalendarProgress,
|
||||||
INemesisWeaponTargetFingerprint,
|
INemesisWeaponTargetFingerprint,
|
||||||
INemesisPetTargetFingerprint,
|
INemesisPetTargetFingerprint,
|
||||||
IDialogueDatabase,
|
IDialogueDatabase,
|
||||||
IKubrowPetPrintClient
|
IKubrowPetPrintClient
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
|
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "@/src/types/genericUpdate";
|
||||||
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
|
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { convertInboxMessage, fromStoreItem, getKeyChainItems } from "@/src/services/itemDataService";
|
import { convertInboxMessage, fromStoreItem, getKeyChainItems } from "@/src/services/itemDataService";
|
||||||
import {
|
import { IFlavourItem, IItemConfig } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
EquipmentFeatures,
|
|
||||||
IEquipmentClient,
|
|
||||||
IEquipmentDatabase,
|
|
||||||
IItemConfig
|
|
||||||
} from "../types/inventoryTypes/commonInventoryTypes";
|
|
||||||
import {
|
import {
|
||||||
ExportArcanes,
|
ExportArcanes,
|
||||||
ExportBoosters,
|
ExportBoosters,
|
||||||
@ -69,7 +59,7 @@ import {
|
|||||||
ISentinel,
|
ISentinel,
|
||||||
TStandingLimitBin
|
TStandingLimitBin
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
import { createShip } from "./shipService";
|
import { createShip } from "@/src/services/shipService";
|
||||||
import {
|
import {
|
||||||
catbrowDetails,
|
catbrowDetails,
|
||||||
fromMongoDate,
|
fromMongoDate,
|
||||||
@ -77,20 +67,36 @@ import {
|
|||||||
kubrowDetails,
|
kubrowDetails,
|
||||||
kubrowFurPatternsWeights,
|
kubrowFurPatternsWeights,
|
||||||
kubrowWeights,
|
kubrowWeights,
|
||||||
toOid
|
toOid,
|
||||||
} from "../helpers/inventoryHelpers";
|
TTraitsPool
|
||||||
|
} from "@/src/helpers/inventoryHelpers";
|
||||||
import { addQuestKey, completeQuest } from "@/src/services/questService";
|
import { addQuestKey, completeQuest } from "@/src/services/questService";
|
||||||
import { handleBundleAcqusition } from "./purchaseService";
|
import { handleBundleAcqusition } from "@/src/services/purchaseService";
|
||||||
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
||||||
import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService";
|
import {
|
||||||
import { createMessage } from "./inboxService";
|
generateRewardSeed,
|
||||||
|
getRandomElement,
|
||||||
|
getRandomInt,
|
||||||
|
getRandomWeightedReward,
|
||||||
|
SRng
|
||||||
|
} from "@/src/services/rngService";
|
||||||
|
import { createMessage, IMessageCreationTemplate } from "@/src/services/inboxService";
|
||||||
import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper";
|
import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper";
|
||||||
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
|
import { getNightwaveSyndicateTag, getWorldState } from "@/src/services/worldStateService";
|
||||||
import { ICalendarSeason } from "@/src/types/worldStateTypes";
|
import { ICalendarSeason } from "@/src/types/worldStateTypes";
|
||||||
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
|
import { generateNemesisProfile, INemesisProfile } from "@/src/helpers/nemesisHelpers";
|
||||||
import { TAccountDocument } from "./loginService";
|
import { TAccountDocument } from "@/src/services/loginService";
|
||||||
import { unixTimesInMs } from "../constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { addString } from "../helpers/stringHelpers";
|
import { addString } from "@/src/helpers/stringHelpers";
|
||||||
|
import {
|
||||||
|
EquipmentFeatures,
|
||||||
|
IEquipmentClient,
|
||||||
|
IEquipmentDatabase,
|
||||||
|
IKubrowPetDetailsDatabase,
|
||||||
|
ITraits,
|
||||||
|
Status
|
||||||
|
} from "@/src/types/equipmentTypes";
|
||||||
|
import { ITypeCount } from "@/src/types/commonTypes";
|
||||||
|
|
||||||
export const createInventory = async (
|
export const createInventory = async (
|
||||||
accountOwnerId: Types.ObjectId,
|
accountOwnerId: Types.ObjectId,
|
||||||
@ -132,17 +138,6 @@ export const createInventory = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateRewardSeed = (): bigint => {
|
|
||||||
const hiDword = getRandomInt(0, 0x7fffffff);
|
|
||||||
const loDword = getRandomInt(0, 0xffffffff);
|
|
||||||
let seed = (BigInt(hiDword) << 32n) | BigInt(loDword);
|
|
||||||
if (Math.random() < 0.5) {
|
|
||||||
seed *= -1n;
|
|
||||||
seed -= 1n;
|
|
||||||
}
|
|
||||||
return seed;
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO: RawUpgrades might need to return a LastAdded
|
//TODO: RawUpgrades might need to return a LastAdded
|
||||||
const awakeningRewards = [
|
const awakeningRewards = [
|
||||||
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
|
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
|
||||||
@ -483,6 +478,19 @@ export const addItem = async (
|
|||||||
return addCustomization(inventory, typeName);
|
return addCustomization(inventory, typeName);
|
||||||
}
|
}
|
||||||
if (typeName in ExportUpgrades || typeName in ExportArcanes) {
|
if (typeName in ExportUpgrades || typeName in ExportArcanes) {
|
||||||
|
if (targetFingerprint) {
|
||||||
|
if (quantity != 1) {
|
||||||
|
logger.warn(`adding 1 of ${typeName} ${targetFingerprint} even tho quantity ${quantity} was requested`);
|
||||||
|
}
|
||||||
|
const upgrade =
|
||||||
|
inventory.Upgrades[
|
||||||
|
inventory.Upgrades.push({
|
||||||
|
ItemType: typeName,
|
||||||
|
UpgradeFingerprint: targetFingerprint
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
return { Upgrades: [upgrade.toJSON<IUpgradeClient>()] };
|
||||||
|
}
|
||||||
const changes = [
|
const changes = [
|
||||||
{
|
{
|
||||||
ItemType: typeName,
|
ItemType: typeName,
|
||||||
@ -807,7 +815,7 @@ export const addItem = async (
|
|||||||
if (!seed) {
|
if (!seed) {
|
||||||
throw new Error(`Expected crew member to have a seed`);
|
throw new Error(`Expected crew member to have a seed`);
|
||||||
}
|
}
|
||||||
seed |= 0x33b81en << 32n;
|
seed |= BigInt(Math.trunc(inventory.Created.getTime() / 1000) & 0xffffff) << 32n;
|
||||||
return {
|
return {
|
||||||
...addCrewMember(inventory, typeName, seed),
|
...addCrewMember(inventory, typeName, seed),
|
||||||
...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase)
|
...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase)
|
||||||
@ -1044,6 +1052,21 @@ export const addSpaceSuit = (
|
|||||||
return inventoryChanges;
|
return inventoryChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createRandomTraits = (kubrowPetName: string, traitsPool: TTraitsPool): ITraits => {
|
||||||
|
return {
|
||||||
|
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
||||||
|
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
||||||
|
TertiaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
||||||
|
AccentColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
||||||
|
EyeColor: getRandomWeightedReward(traitsPool.EyeColors, kubrowWeights)!.type,
|
||||||
|
FurPattern: getRandomWeightedReward(traitsPool.FurPatterns, kubrowFurPatternsWeights)!.type,
|
||||||
|
Personality: kubrowPetName,
|
||||||
|
BodyType: getRandomWeightedReward(traitsPool.BodyTypes, kubrowWeights)!.type,
|
||||||
|
Head: traitsPool.Heads.length ? getRandomWeightedReward(traitsPool.Heads, kubrowWeights)!.type : undefined,
|
||||||
|
Tail: traitsPool.Tails.length ? getRandomWeightedReward(traitsPool.Tails, kubrowWeights)!.type : undefined
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const addKubrowPet = (
|
export const addKubrowPet = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
kubrowPetName: string,
|
kubrowPetName: string,
|
||||||
@ -1060,7 +1083,6 @@ export const addKubrowPet = (
|
|||||||
addSpecialItem(inventory, specialItem, inventoryChanges);
|
addSpecialItem(inventory, specialItem, inventoryChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
||||||
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
|
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
|
||||||
|
|
||||||
if (!details) {
|
if (!details) {
|
||||||
@ -1070,9 +1092,10 @@ export const addKubrowPet = (
|
|||||||
"/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit"
|
"/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit"
|
||||||
].includes(kubrowPetName);
|
].includes(kubrowPetName);
|
||||||
|
|
||||||
let traits: ITraits;
|
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
|
||||||
|
let dominantTraits: ITraits;
|
||||||
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
|
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
|
||||||
traits = {
|
dominantTraits = {
|
||||||
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
|
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
|
||||||
SecondaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryVampire",
|
SecondaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryVampire",
|
||||||
TertiaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryVampire",
|
TertiaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryVampire",
|
||||||
@ -1085,19 +1108,35 @@ export const addKubrowPet = (
|
|||||||
Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
|
Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
|
dominantTraits = createRandomTraits(kubrowPetName, traitsPool);
|
||||||
traits = {
|
if (kubrowPetName == "/Lotus/Types/Game/KubrowPet/ChargerKubrowPetPowerSuit") {
|
||||||
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
dominantTraits.BodyType = "/Lotus/Types/Game/KubrowPet/BodyTypes/ChargerKubrowPetBodyType";
|
||||||
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
dominantTraits.FurPattern = "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternInfested";
|
||||||
TertiaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
}
|
||||||
AccentColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
}
|
||||||
EyeColor: getRandomWeightedReward(traitsPool.EyeColors, kubrowWeights)!.type,
|
|
||||||
FurPattern: getRandomWeightedReward(traitsPool.FurPatterns, kubrowFurPatternsWeights)!.type,
|
const recessiveTraits: ITraits = createRandomTraits(
|
||||||
Personality: kubrowPetName,
|
getRandomElement(
|
||||||
BodyType: getRandomWeightedReward(traitsPool.BodyTypes, kubrowWeights)!.type,
|
isCatbrow
|
||||||
Head: isCatbrow ? getRandomWeightedReward(traitsPool.Heads, kubrowWeights)!.type : undefined,
|
? [
|
||||||
Tail: isCatbrow ? getRandomWeightedReward(traitsPool.Tails, kubrowWeights)!.type : undefined
|
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
|
||||||
};
|
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit"
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
"/Lotus/Types/Game/KubrowPet/AdventurerKubrowPetPowerSuit",
|
||||||
|
"/Lotus/Types/Game/KubrowPet/FurtiveKubrowPetPowerSuit",
|
||||||
|
"/Lotus/Types/Game/KubrowPet/GuardKubrowPetPowerSuit",
|
||||||
|
"/Lotus/Types/Game/KubrowPet/HunterKubrowPetPowerSuit",
|
||||||
|
"/Lotus/Types/Game/KubrowPet/RetrieverKubrowPetPowerSuit"
|
||||||
|
]
|
||||||
|
)!,
|
||||||
|
traitsPool
|
||||||
|
);
|
||||||
|
for (const key of Object.keys(recessiveTraits) as (keyof ITraits)[]) {
|
||||||
|
// My heurstic approximation is a 20% chance for a dominant trait to be copied into the recessive traits. TODO: A more scientific statistical analysis maybe?
|
||||||
|
if (Math.random() < 0.2) {
|
||||||
|
recessiveTraits[key] = dominantTraits[key]!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
details = {
|
details = {
|
||||||
@ -1109,8 +1148,8 @@ export const addKubrowPet = (
|
|||||||
HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
|
HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
|
||||||
IsMale: !!getRandomInt(0, 1),
|
IsMale: !!getRandomInt(0, 1),
|
||||||
Size: getRandomInt(70, 100) / 100,
|
Size: getRandomInt(70, 100) / 100,
|
||||||
DominantTraits: traits,
|
DominantTraits: dominantTraits,
|
||||||
RecessiveTraits: traits
|
RecessiveTraits: recessiveTraits
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1256,8 +1295,8 @@ export const addStanding = (
|
|||||||
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
||||||
if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing;
|
if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing;
|
||||||
|
|
||||||
if (syndicate.Title == -2 && syndicate.Standing + gainedStanding < -71000) {
|
if (syndicate.Standing + gainedStanding < -71000) {
|
||||||
gainedStanding = -71000 + syndicate.Standing;
|
gainedStanding = -71000 - syndicate.Standing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
|
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
|
||||||
@ -1385,7 +1424,11 @@ export const addSkin = (
|
|||||||
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) {
|
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) {
|
||||||
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
|
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
|
||||||
} else {
|
} else {
|
||||||
const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1;
|
const index =
|
||||||
|
inventory.WeaponSkins.push({
|
||||||
|
ItemType: typeName,
|
||||||
|
IsNew: typeName.startsWith("/Lotus/Upgrades/Skins/RailJack/") ? undefined : true // railjack skins are incompatible with this flag
|
||||||
|
}) - 1;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
inventoryChanges.WeaponSkins ??= [];
|
inventoryChanges.WeaponSkins ??= [];
|
||||||
(inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push(
|
(inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push(
|
||||||
@ -1563,7 +1606,22 @@ export const addEmailItem = async (
|
|||||||
const meta = ExportEmailItems[typeName];
|
const meta = ExportEmailItems[typeName];
|
||||||
const emailItem = inventory.EmailItems.find(x => x.ItemType == typeName);
|
const emailItem = inventory.EmailItems.find(x => x.ItemType == typeName);
|
||||||
if (!emailItem || !meta.sendOnlyOnce) {
|
if (!emailItem || !meta.sendOnlyOnce) {
|
||||||
await createMessage(inventory.accountOwnerId, [convertInboxMessage(meta.message)]);
|
const msg: IMessageCreationTemplate = convertInboxMessage(meta.message);
|
||||||
|
if (msg.cinematic == "/Lotus/Levels/1999/PlayerHomeBalconyCinematics.level") {
|
||||||
|
msg.customData = JSON.stringify({
|
||||||
|
Tag: msg.customData + "KissCin",
|
||||||
|
CinLoadout: {
|
||||||
|
Skins: inventory.AdultOperatorLoadOuts[0].Skins,
|
||||||
|
Upgrades: inventory.AdultOperatorLoadOuts[0].Upgrades,
|
||||||
|
attcol: inventory.AdultOperatorLoadOuts[0].attcol,
|
||||||
|
cloth: inventory.AdultOperatorLoadOuts[0].cloth,
|
||||||
|
eyecol: inventory.AdultOperatorLoadOuts[0].eyecol,
|
||||||
|
pricol: inventory.AdultOperatorLoadOuts[0].pricol,
|
||||||
|
syancol: inventory.AdultOperatorLoadOuts[0].syancol
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await createMessage(inventory.accountOwnerId, [msg]);
|
||||||
|
|
||||||
if (emailItem) {
|
if (emailItem) {
|
||||||
emailItem.ItemCount += 1;
|
emailItem.ItemCount += 1;
|
||||||
@ -1577,6 +1635,15 @@ export const addEmailItem = async (
|
|||||||
return inventoryChanges;
|
return inventoryChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const xpEarningParts: readonly string[] = [
|
||||||
|
"LWPT_BLADE",
|
||||||
|
"LWPT_GUN_BARREL",
|
||||||
|
"LWPT_AMP_OCULUS",
|
||||||
|
"LWPT_MOA_HEAD",
|
||||||
|
"LWPT_ZANUKA_HEAD",
|
||||||
|
"LWPT_HB_DECK"
|
||||||
|
];
|
||||||
|
|
||||||
export const applyClientEquipmentUpdates = (
|
export const applyClientEquipmentUpdates = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
gearArray: IEquipmentClient[],
|
gearArray: IEquipmentClient[],
|
||||||
@ -1595,13 +1662,26 @@ export const applyClientEquipmentUpdates = (
|
|||||||
item.XP ??= 0;
|
item.XP ??= 0;
|
||||||
item.XP += XP;
|
item.XP += XP;
|
||||||
|
|
||||||
const xpinfoIndex = inventory.XPInfo.findIndex(x => x.ItemType == item.ItemType);
|
let xpItemType = item.ItemType;
|
||||||
|
if (item.ModularParts) {
|
||||||
|
for (const part of item.ModularParts) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
const partType = ExportWeapons[part]?.partType;
|
||||||
|
if (partType !== undefined && xpEarningParts.indexOf(partType) != -1) {
|
||||||
|
xpItemType = part;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug(`adding xp to ${xpItemType} for modular item ${fromOid(ItemId)} (${item.ItemType})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const xpinfoIndex = inventory.XPInfo.findIndex(x => x.ItemType == xpItemType);
|
||||||
if (xpinfoIndex !== -1) {
|
if (xpinfoIndex !== -1) {
|
||||||
const xpinfo = inventory.XPInfo[xpinfoIndex];
|
const xpinfo = inventory.XPInfo[xpinfoIndex];
|
||||||
xpinfo.XP += XP;
|
xpinfo.XP += XP;
|
||||||
} else {
|
} else {
|
||||||
inventory.XPInfo.push({
|
inventory.XPInfo.push({
|
||||||
ItemType: item.ItemType,
|
ItemType: xpItemType,
|
||||||
XP: XP
|
XP: XP
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1874,7 +1954,7 @@ export const addCalendarProgress = (inventory: TInventoryDatabaseDocument, value
|
|||||||
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
|
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
|
||||||
day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge
|
day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge
|
||||||
);
|
);
|
||||||
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
|
checkCalendarAutoAdvance(inventory, currentSeason);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => {
|
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => {
|
||||||
@ -2059,8 +2139,8 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
|
|||||||
},
|
},
|
||||||
SeasonProgress: {
|
SeasonProgress: {
|
||||||
SeasonType: currentSeason.Season,
|
SeasonType: currentSeason.Season,
|
||||||
LastCompletedDayIdx: 0,
|
LastCompletedDayIdx: -1,
|
||||||
LastCompletedChallengeDayIdx: 0,
|
LastCompletedChallengeDayIdx: -1,
|
||||||
ActivatedChallenges: []
|
ActivatedChallenges: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -2081,16 +2161,44 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
|
|||||||
return inventory.CalendarProgress;
|
return inventory.CalendarProgress;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkCalendarChallengeCompletion = (
|
export const checkCalendarAutoAdvance = (
|
||||||
calendarProgress: ICalendarProgress,
|
inventory: TInventoryDatabaseDocument,
|
||||||
currentSeason: ICalendarSeason
|
currentSeason: ICalendarSeason
|
||||||
): void => {
|
): void => {
|
||||||
const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
|
const calendarProgress = inventory.CalendarProgress!;
|
||||||
if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx >= dayIndex) {
|
for (
|
||||||
|
let dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
|
||||||
|
dayIndex != currentSeason.Days.length;
|
||||||
|
++dayIndex
|
||||||
|
) {
|
||||||
const day = currentSeason.Days[dayIndex];
|
const day = currentSeason.Days[dayIndex];
|
||||||
if (day.events.length != 0 && day.events[0].type == "CET_CHALLENGE") {
|
if (day.events.length == 0) {
|
||||||
|
// birthday
|
||||||
|
if (day.day == 1) {
|
||||||
|
// kaya
|
||||||
|
if ((inventory.Affiliations.find(x => x.Tag == "HexSyndicate")?.Title || 0) >= 4) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logger.debug(`cannot talk to kaya, skipping birthday`);
|
||||||
|
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
|
||||||
|
} else if (day.day == 74 || day.day == 355) {
|
||||||
|
// minerva, velimir
|
||||||
|
if ((inventory.Affiliations.find(x => x.Tag == "HexSyndicate")?.Title || 0) >= 5) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logger.debug(`cannot talk to minerva/velimir, skipping birthday`);
|
||||||
|
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (day.events[0].type == "CET_CHALLENGE") {
|
||||||
|
if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx < dayIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
//logger.debug(`already completed the challenge, skipping ahead`);
|
//logger.debug(`already completed the challenge, skipping ahead`);
|
||||||
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
|
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
dict_zh,
|
dict_zh,
|
||||||
ExportArcanes,
|
ExportArcanes,
|
||||||
ExportBoosters,
|
ExportBoosters,
|
||||||
|
ExportBundles,
|
||||||
ExportCustoms,
|
ExportCustoms,
|
||||||
ExportDrones,
|
ExportDrones,
|
||||||
ExportGear,
|
ExportGear,
|
||||||
@ -32,7 +33,7 @@ import {
|
|||||||
IRecipe,
|
IRecipe,
|
||||||
TReward
|
TReward
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
import { IMessage } from "../models/inboxModel";
|
import { IMessage } from "@/src/models/inboxModel";
|
||||||
|
|
||||||
export type WeaponTypeInternal =
|
export type WeaponTypeInternal =
|
||||||
| "LongGuns"
|
| "LongGuns"
|
||||||
@ -45,6 +46,39 @@ export type WeaponTypeInternal =
|
|||||||
| "SpecialItems";
|
| "SpecialItems";
|
||||||
|
|
||||||
export const getRecipe = (uniqueName: string): IRecipe | undefined => {
|
export const getRecipe = (uniqueName: string): IRecipe | undefined => {
|
||||||
|
// Handle crafting of archwing summon for versions prior to 39.0.0 as this blueprint was removed then.
|
||||||
|
if (uniqueName == "/Lotus/Types/Recipes/EidolonRecipes/OpenArchwingSummonBlueprint") {
|
||||||
|
return {
|
||||||
|
resultType: "/Lotus/Types/Restoratives/OpenArchwingSummon",
|
||||||
|
buildPrice: 7500,
|
||||||
|
buildTime: 1800,
|
||||||
|
skipBuildTimePrice: 10,
|
||||||
|
consumeOnUse: false,
|
||||||
|
num: 1,
|
||||||
|
codexSecret: false,
|
||||||
|
alwaysAvailable: true,
|
||||||
|
ingredients: [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/IraditeItem",
|
||||||
|
ItemCount: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem",
|
||||||
|
ItemCount: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Items/Fish/Eidolon/FishParts/EidolonFishOilItem",
|
||||||
|
ItemCount: 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Items/MiscItems/Circuits",
|
||||||
|
ItemCount: 600
|
||||||
|
}
|
||||||
|
],
|
||||||
|
excludeFromMarket: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return ExportRecipes[uniqueName];
|
return ExportRecipes[uniqueName];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,6 +118,9 @@ export const getItemName = (uniqueName: string): string | undefined => {
|
|||||||
if (uniqueName in ExportArcanes) {
|
if (uniqueName in ExportArcanes) {
|
||||||
return ExportArcanes[uniqueName].name;
|
return ExportArcanes[uniqueName].name;
|
||||||
}
|
}
|
||||||
|
if (uniqueName in ExportBundles) {
|
||||||
|
return ExportBundles[uniqueName].name;
|
||||||
|
}
|
||||||
if (uniqueName in ExportCustoms) {
|
if (uniqueName in ExportCustoms) {
|
||||||
return ExportCustoms[uniqueName].name;
|
return ExportCustoms[uniqueName].name;
|
||||||
}
|
}
|
||||||
@ -218,7 +255,9 @@ export const convertInboxMessage = (message: IInboxMessage): IMessage => {
|
|||||||
return {
|
return {
|
||||||
sndr: message.sender,
|
sndr: message.sender,
|
||||||
msg: message.body,
|
msg: message.body,
|
||||||
|
cinematic: message.cinematic,
|
||||||
sub: message.title,
|
sub: message.title,
|
||||||
|
customData: message.customData,
|
||||||
att: message.attachments.length > 0 ? message.attachments : undefined,
|
att: message.attachments.length > 0 ? message.attachments : undefined,
|
||||||
countedAtt: message.countedAttachments.length > 0 ? message.countedAttachments : undefined,
|
countedAtt: message.countedAttachments.length > 0 ? message.countedAttachments : undefined,
|
||||||
icon: message.icon ?? "",
|
icon: message.icon ?? "",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Guild } from "../models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { Leaderboard, TLeaderboardEntryDocument } from "../models/leaderboardModel";
|
import { Leaderboard, TLeaderboardEntryDocument } from "@/src/models/leaderboardModel";
|
||||||
import { ILeaderboardEntryClient } from "../types/leaderboardTypes";
|
import { ILeaderboardEntryClient } from "@/src/types/leaderboardTypes";
|
||||||
|
|
||||||
export const submitLeaderboardScore = async (
|
export const submitLeaderboardScore = async (
|
||||||
schedule: "weekly" | "daily",
|
schedule: "weekly" | "daily",
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import randomRewards from "@/static/fixed_responses/loginRewards/randomRewards.json";
|
import randomRewards from "@/static/fixed_responses/loginRewards/randomRewards.json";
|
||||||
import { IInventoryChanges } from "../types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { TAccountDocument } from "./loginService";
|
import { TAccountDocument } from "@/src/services/loginService";
|
||||||
import { mixSeeds, SRng } from "./rngService";
|
import { mixSeeds, SRng } from "@/src/services/rngService";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { addBooster, updateCurrency } from "./inventoryService";
|
import { addBooster, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { handleStoreItemAcquisition } from "./purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import {
|
import {
|
||||||
ExportBoosterPacks,
|
ExportBoosterPacks,
|
||||||
ExportBoosters,
|
ExportBoosters,
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
ExportWarframes,
|
ExportWarframes,
|
||||||
ExportWeapons
|
ExportWeapons
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
import { toStoreItem } from "./itemDataService";
|
import { toStoreItem } from "@/src/services/itemDataService";
|
||||||
|
|
||||||
export interface ILoginRewardsReponse {
|
export interface ILoginRewardsReponse {
|
||||||
DailyTributeInfo: {
|
DailyTributeInfo: {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { createInventory } from "@/src/services/inventoryService";
|
import { createInventory } from "@/src/services/inventoryService";
|
||||||
import { IDatabaseAccountJson, IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
|
import { IDatabaseAccountJson, IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
|
||||||
import { createShip } from "./shipService";
|
import { createShip } from "@/src/services/shipService";
|
||||||
import { Document, Types } from "mongoose";
|
import { Document, Types } from "mongoose";
|
||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||||
import { PersonalRooms } from "@/src/models/personalRoomsModel";
|
import { PersonalRooms } from "@/src/models/personalRoomsModel";
|
||||||
|
@ -8,10 +8,10 @@ import {
|
|||||||
IRegion,
|
IRegion,
|
||||||
IReward
|
IReward
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
import { IMissionInventoryUpdateRequest, IRewardInfo } from "@/src/types/requestTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
|
import { IRngResult, SRng, generateRewardSeed, getRandomElement, getRandomReward } from "@/src/services/rngService";
|
||||||
import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { equipmentKeys, IMission, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import {
|
import {
|
||||||
addBooster,
|
addBooster,
|
||||||
addCalendarProgress,
|
addCalendarProgress,
|
||||||
@ -35,7 +35,6 @@ import {
|
|||||||
addStanding,
|
addStanding,
|
||||||
applyClientEquipmentUpdates,
|
applyClientEquipmentUpdates,
|
||||||
combineInventoryChanges,
|
combineInventoryChanges,
|
||||||
generateRewardSeed,
|
|
||||||
getDialogue,
|
getDialogue,
|
||||||
giveNemesisPetRecipe,
|
giveNemesisPetRecipe,
|
||||||
giveNemesisWeaponRecipe,
|
giveNemesisWeaponRecipe,
|
||||||
@ -48,11 +47,10 @@ import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
|
|||||||
import { fromStoreItem, getLevelKeyRewards, isStoreItem, toStoreItem } from "@/src/services/itemDataService";
|
import { fromStoreItem, getLevelKeyRewards, isStoreItem, toStoreItem } from "@/src/services/itemDataService";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
|
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import { handleStoreItemAcquisition } from "./purchaseService";
|
import { IMissionCredits, IMissionReward } from "@/src/types/missionTypes";
|
||||||
import { IMissionCredits, IMissionReward } from "../types/missionTypes";
|
|
||||||
import { crackRelic } from "@/src/helpers/relicHelper";
|
import { crackRelic } from "@/src/helpers/relicHelper";
|
||||||
import { createMessage } from "./inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json";
|
import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json";
|
||||||
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
|
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
|
||||||
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
|
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
|
||||||
@ -65,8 +63,8 @@ import {
|
|||||||
getNemesisManifest,
|
getNemesisManifest,
|
||||||
getNemesisPasscode
|
getNemesisPasscode
|
||||||
} from "@/src/helpers/nemesisHelpers";
|
} from "@/src/helpers/nemesisHelpers";
|
||||||
import { Loadout } from "../models/inventoryModels/loadoutModel";
|
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||||
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
|
import { ILoadoutConfigDatabase } from "@/src/types/saveLoadoutTypes";
|
||||||
import {
|
import {
|
||||||
getLiteSortie,
|
getLiteSortie,
|
||||||
getSortie,
|
getSortie,
|
||||||
@ -75,12 +73,14 @@ import {
|
|||||||
idToDay,
|
idToDay,
|
||||||
idToWeek,
|
idToWeek,
|
||||||
pushClassicBounties
|
pushClassicBounties
|
||||||
} from "./worldStateService";
|
} from "@/src/services/worldStateService";
|
||||||
import { config } from "./configService";
|
import { config } from "@/src/services/configService";
|
||||||
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
||||||
import { ISyndicateMissionInfo } from "../types/worldStateTypes";
|
import { ISyndicateMissionInfo } from "@/src/types/worldStateTypes";
|
||||||
import { fromOid } from "../helpers/inventoryHelpers";
|
import { fromOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { TAccountDocument } from "./loginService";
|
import { TAccountDocument } from "@/src/services/loginService";
|
||||||
|
import { ITypeCount } from "@/src/types/commonTypes";
|
||||||
|
import { IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||||
|
|
||||||
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
|
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
|
||||||
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
||||||
@ -558,6 +558,7 @@ export const addMissionInventoryUpdates = async (
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
inventory.DeathSquadable = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "LockedWeaponGroup": {
|
case "LockedWeaponGroup": {
|
||||||
@ -576,7 +577,7 @@ export const addMissionInventoryUpdates = async (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "IncHarvester": {
|
case "IncHarvester": {
|
||||||
inventory.Harvestable = true;
|
// Unsure what to do with this
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "CurrentLoadOutIds": {
|
case "CurrentLoadOutIds": {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { PersonalRooms } from "@/src/models/personalRoomsModel";
|
import { PersonalRooms } from "@/src/models/personalRoomsModel";
|
||||||
import { addItem, getInventory } from "@/src/services/inventoryService";
|
import { addItem, getInventory } from "@/src/services/inventoryService";
|
||||||
import { TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes";
|
import { IGardeningDatabase, TPersonalRoomsDatabaseDocument } from "@/src/types/personalRoomsTypes";
|
||||||
import { IGardeningDatabase } from "../types/shipTypes";
|
import { getRandomElement } from "@/src/services/rngService";
|
||||||
import { getRandomElement } from "./rngService";
|
|
||||||
|
|
||||||
export const getPersonalRooms = async (
|
export const getPersonalRooms = async (
|
||||||
accountId: string,
|
accountId: string,
|
||||||
|
@ -20,8 +20,7 @@ import {
|
|||||||
IPurchaseParams
|
IPurchaseParams
|
||||||
} from "@/src/types/purchaseTypes";
|
} from "@/src/types/purchaseTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { getWorldState } from "./worldStateService";
|
import { getWorldState } from "@/src/services/worldStateService";
|
||||||
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
|
|
||||||
import {
|
import {
|
||||||
ExportBoosterPacks,
|
ExportBoosterPacks,
|
||||||
ExportBoosters,
|
ExportBoosters,
|
||||||
@ -33,11 +32,11 @@ import {
|
|||||||
ExportVendors,
|
ExportVendors,
|
||||||
TRarity
|
TRarity
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
import { config } from "./configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { fromStoreItem, toStoreItem } from "./itemDataService";
|
import { fromStoreItem, toStoreItem } from "@/src/services/itemDataService";
|
||||||
import { DailyDeal } from "../models/worldStateModel";
|
import { DailyDeal } from "@/src/models/worldStateModel";
|
||||||
import { fromMongoDate, toMongoDate } from "../helpers/inventoryHelpers";
|
import { fromMongoDate, toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
|
|
||||||
export const getStoreItemCategory = (storeItem: string): string => {
|
export const getStoreItemCategory = (storeItem: string): string => {
|
||||||
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
|
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
|
||||||
@ -305,14 +304,15 @@ export const handlePurchase = async (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PurchaseSource.PrimeVaultTrader: {
|
case PurchaseSource.PrimeVaultTrader: {
|
||||||
if (purchaseRequest.PurchaseParams.SourceId! != staticWorldState.PrimeVaultTraders[0]._id.$oid) {
|
const worldState = getWorldState();
|
||||||
|
if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) {
|
||||||
throw new Error("invalid request source");
|
throw new Error("invalid request source");
|
||||||
}
|
}
|
||||||
const offer =
|
const offer =
|
||||||
staticWorldState.PrimeVaultTraders[0].Manifest.find(
|
worldState.PrimeVaultTraders[0].Manifest.find(
|
||||||
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
||||||
) ??
|
) ??
|
||||||
staticWorldState.PrimeVaultTraders[0].EvergreenManifest.find(
|
worldState.PrimeVaultTraders[0].EvergreenManifest.find(
|
||||||
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
||||||
);
|
);
|
||||||
if (offer) {
|
if (offer) {
|
||||||
|
@ -4,13 +4,14 @@ import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/invento
|
|||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { addItem, addItems, addKeyChainItems, setupKahlSyndicate } from "@/src/services/inventoryService";
|
import { addItem, addItems, addKeyChainItems, setupKahlSyndicate } from "@/src/services/inventoryService";
|
||||||
import { fromStoreItem, getKeyChainMessage, getLevelKeyRewards } from "@/src/services/itemDataService";
|
import { fromStoreItem, getKeyChainMessage, getLevelKeyRewards } from "@/src/services/itemDataService";
|
||||||
import { IQuestKeyClient, IQuestKeyDatabase, IQuestStage, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IQuestKeyClient, IQuestKeyDatabase, IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { ExportKeys } from "warframe-public-export-plus";
|
import { ExportKeys } from "warframe-public-export-plus";
|
||||||
import { addFixedLevelRewards } from "./missionInventoryUpdateService";
|
import { addFixedLevelRewards } from "@/src/services/missionInventoryUpdateService";
|
||||||
import { IInventoryChanges } from "../types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import questCompletionItems from "@/static/fixed_responses/questCompletionRewards.json";
|
import questCompletionItems from "@/static/fixed_responses/questCompletionRewards.json";
|
||||||
|
import { ITypeCount } from "@/src/types/commonTypes";
|
||||||
|
|
||||||
export interface IUpdateQuestRequest {
|
export interface IUpdateQuestRequest {
|
||||||
QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[];
|
QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[];
|
||||||
@ -235,7 +236,7 @@ const handleQuestCompletion = async (
|
|||||||
setupKahlSyndicate(inventory);
|
setupKahlSyndicate(inventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whispers in the Walls is unlocked once The New + Heart of Deimos are completed.
|
// Whispers in the Walls is unlocked once The New War + Heart of Deimos are completed.
|
||||||
if (
|
if (
|
||||||
doesQuestCompletionFinishSet(inventory, questKey, [
|
doesQuestCompletionFinishSet(inventory, questKey, [
|
||||||
"/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain",
|
"/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain",
|
||||||
|
@ -18,6 +18,17 @@ export const getRandomInt = (min: number, max: number): number => {
|
|||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateRewardSeed = (): bigint => {
|
||||||
|
const hiDword = getRandomInt(0, 0x7fffffff);
|
||||||
|
const loDword = getRandomInt(0, 0xffffffff);
|
||||||
|
let seed = (BigInt(hiDword) << 32n) | BigInt(loDword);
|
||||||
|
if (Math.random() < 0.5) {
|
||||||
|
seed *= -1n;
|
||||||
|
seed -= 1n;
|
||||||
|
}
|
||||||
|
return seed;
|
||||||
|
};
|
||||||
|
|
||||||
export const getRewardAtPercentage = <T extends { probability: number }>(
|
export const getRewardAtPercentage = <T extends { probability: number }>(
|
||||||
pool: T[],
|
pool: T[],
|
||||||
percentage: number
|
percentage: number
|
||||||
@ -140,4 +151,57 @@ export class SRng {
|
|||||||
arr[lastIdx] = tmp;
|
arr[lastIdx] = tmp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shuffledArray<T>(inarr: readonly T[]): T[] {
|
||||||
|
const arr = [...inarr];
|
||||||
|
this.shuffleArray(arr);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sequentiallyUniqueRandomElement = <T>(
|
||||||
|
deck: readonly T[],
|
||||||
|
idx: number,
|
||||||
|
lookbehind: number,
|
||||||
|
seed: number = 0
|
||||||
|
): T | undefined => {
|
||||||
|
// This algorithm may modify a shuffle up to index `lookbehind + 1`. It assumes that the last `lookbehind` cards are not adjusted.
|
||||||
|
if (lookbehind + 1 >= deck.length - lookbehind) {
|
||||||
|
throw new Error(
|
||||||
|
`this algorithm cannot guarantee ${lookbehind} unique cards in a row with a deck of size ${deck.length}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const iteration = Math.trunc(idx / deck.length);
|
||||||
|
const card = idx % deck.length;
|
||||||
|
const currentShuffle = new SRng(mixSeeds(new SRng(iteration).randomInt(0, 100_000), seed)).shuffledArray(deck);
|
||||||
|
if (card < currentShuffle.length - lookbehind) {
|
||||||
|
// We are indexing before the end of the deck, so adjustments may be needed to achieve uniqueness.
|
||||||
|
const window: T[] = [];
|
||||||
|
{
|
||||||
|
const previousShuffle = new SRng(
|
||||||
|
mixSeeds(new SRng(iteration - 1).randomInt(0, 100_000), seed)
|
||||||
|
).shuffledArray(deck);
|
||||||
|
for (let i = previousShuffle.length - lookbehind; i != previousShuffle.length; ++i) {
|
||||||
|
window.push(previousShuffle[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// From this point on, `window.length == lookbehind` should hold.
|
||||||
|
for (let i = 0; i != lookbehind; ++i) {
|
||||||
|
if (window.indexOf(currentShuffle[i]) != -1) {
|
||||||
|
for (let j = i; ; ++j) {
|
||||||
|
// `j < currentShuffle.length - lookbehind` should hold.
|
||||||
|
if (window.indexOf(currentShuffle[j]) == -1) {
|
||||||
|
const tmp = currentShuffle[j];
|
||||||
|
currentShuffle[j] = currentShuffle[i];
|
||||||
|
currentShuffle[i] = tmp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.splice(0, 1);
|
||||||
|
window.push(currentShuffle[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentShuffle[card];
|
||||||
|
};
|
||||||
|
@ -13,8 +13,8 @@ import { Types } from "mongoose";
|
|||||||
import { isEmptyObject } from "@/src/helpers/general";
|
import { isEmptyObject } from "@/src/helpers/general";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IItemConfig } from "../types/inventoryTypes/commonInventoryTypes";
|
import { IItemConfig } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { importCrewMemberId } from "./importService";
|
import { importCrewMemberId } from "@/src/services/importService";
|
||||||
|
|
||||||
//TODO: setup default items on account creation or like originally in giveStartingItems.php
|
//TODO: setup default items on account creation or like originally in giveStartingItems.php
|
||||||
|
|
||||||
@ -167,8 +167,23 @@ export const handleInventoryItemConfigChange = async (
|
|||||||
inventory.LotusCustomization = equipmentChanges.LotusCustomization;
|
inventory.LotusCustomization = equipmentChanges.LotusCustomization;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "ValidNewLoadoutId": {
|
||||||
|
logger.debug(`ignoring ValidNewLoadoutId (${equipmentChanges.ValidNewLoadoutId})`);
|
||||||
|
// seems always equal to the id of loadout config NORMAL[0], likely has no purpose and we're free to ignore it
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "ActiveCrewShip": {
|
||||||
|
if (inventory.CrewShips.length != 1) {
|
||||||
|
logger.warn(`saving railjack changes with broken inventory?`);
|
||||||
|
} else if (!inventory.CrewShips[0]._id.equals(equipmentChanges.ActiveCrewShip.$oid)) {
|
||||||
|
logger.warn(
|
||||||
|
`client provided CrewShip id ${equipmentChanges.ActiveCrewShip.$oid} but id in inventory is ${inventory.CrewShips[0]._id.toString()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
if (equipmentKeys.includes(equipmentName as TEquipmentKey) && equipmentName != "ValidNewLoadoutId") {
|
if (equipmentKeys.includes(equipmentName as TEquipmentKey)) {
|
||||||
logger.debug(`general Item config saved of type ${equipmentName}`, {
|
logger.debug(`general Item config saved of type ${equipmentName}`, {
|
||||||
config: equipment
|
config: equipment
|
||||||
});
|
});
|
||||||
@ -216,7 +231,7 @@ export const handleInventoryItemConfigChange = async (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`loadout category not implemented, changes may be lost: ${equipmentName}`, {
|
logger.warn(`unknown saveLoadout field: ${equipmentName}`, {
|
||||||
config: equipment
|
config: equipment
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import { mixSeeds, SRng } from "@/src/services/rngService";
|
|||||||
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
|
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
|
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
|
||||||
import { config } from "./configService";
|
import { config } from "@/src/services/configService";
|
||||||
|
|
||||||
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
|
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
|
||||||
cycleOffset?: number;
|
cycleOffset?: number;
|
||||||
@ -299,9 +299,12 @@ const generateVendorManifest = (
|
|||||||
? numUncountedOffers + numCountedOffers
|
? numUncountedOffers + numCountedOffers
|
||||||
: manifest.numItems
|
: manifest.numItems
|
||||||
? numUncountedOffers +
|
? numUncountedOffers +
|
||||||
(useRng
|
Math.min(
|
||||||
? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
|
Object.values(remainingItemCapacity).reduce((a, b) => a + b, 0),
|
||||||
: manifest.numItems.minValue)
|
useRng
|
||||||
|
? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
|
||||||
|
: manifest.numItems.minValue
|
||||||
|
)
|
||||||
: manifest.items.length;
|
: manifest.items.length;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const rollableOffers = manifest.items.filter(x => x.probability !== undefined) as (Omit<
|
const rollableOffers = manifest.items.filter(x => x.probability !== undefined) as (Omit<
|
||||||
@ -495,4 +498,13 @@ if (args.dev) {
|
|||||||
) {
|
) {
|
||||||
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest`);
|
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// strange case where numItems is 5 even tho only 3 offers can possibly be generated
|
||||||
|
const loid = getVendorManifestByTypeName(
|
||||||
|
"/Lotus/Types/Game/VendorManifests/EntratiLabs/EntratiLabsCommisionsManifest",
|
||||||
|
false
|
||||||
|
)!.VendorInfo.ItemManifest;
|
||||||
|
if (loid.length != 3) {
|
||||||
|
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/EntratiLabs/EntratiLabsCommisionsManifest`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
import { getShip } from "@/src/services/shipService";
|
import { getShip } from "@/src/services/shipService";
|
||||||
import {
|
import {
|
||||||
|
ISetPlacedDecoInfoRequest,
|
||||||
ISetShipCustomizationsRequest,
|
ISetShipCustomizationsRequest,
|
||||||
IShipDecorationsRequest,
|
IShipDecorationsRequest,
|
||||||
IShipDecorationsResponse,
|
IShipDecorationsResponse,
|
||||||
ISetPlacedDecoInfoRequest,
|
RoomsType,
|
||||||
TBootLocation
|
TBootLocation,
|
||||||
} from "@/src/types/shipTypes";
|
TPersonalRoomsDatabaseDocument
|
||||||
|
} from "@/src/types/personalRoomsTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { addFusionTreasures, addShipDecorations, getInventory } from "./inventoryService";
|
import { addFusionTreasures, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
||||||
import { config } from "./configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { Guild } from "../models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { hasGuildPermission } from "./guildService";
|
import { hasGuildPermission } from "@/src/services/guildService";
|
||||||
import { GuildPermission } from "../types/guildTypes";
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
import { ExportResources } from "warframe-public-export-plus";
|
import { ExportResources } from "warframe-public-export-plus";
|
||||||
import { RoomsType, TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes";
|
|
||||||
|
|
||||||
export const setShipCustomizations = async (
|
export const setShipCustomizations = async (
|
||||||
accountId: string,
|
accountId: string,
|
||||||
@ -39,7 +40,7 @@ export const setShipCustomizations = async (
|
|||||||
personalRooms.TailorShop.LevelDecosVisible = shipCustomization.Customization.LevelDecosVisible;
|
personalRooms.TailorShop.LevelDecosVisible = shipCustomization.Customization.LevelDecosVisible;
|
||||||
personalRooms.TailorShop.CustomJson = shipCustomization.Customization.CustomJson;
|
personalRooms.TailorShop.CustomJson = shipCustomization.Customization.CustomJson;
|
||||||
} else {
|
} else {
|
||||||
personalRooms.ShipInteriorColors = shipCustomization.Customization.Colors;
|
personalRooms.Ship.ShipInterior = shipCustomization.Customization;
|
||||||
}
|
}
|
||||||
await personalRooms.save();
|
await personalRooms.save();
|
||||||
}
|
}
|
||||||
@ -64,8 +65,12 @@ export const handleSetShipDecorations = async (
|
|||||||
throw new Error(`unknown room: ${placedDecoration.Room}`);
|
throw new Error(`unknown room: ${placedDecoration.Room}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)!;
|
const entry = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type);
|
||||||
if (!itemType || meta.capacityCost === undefined) {
|
if (!entry) {
|
||||||
|
throw new Error(`unknown deco type: ${placedDecoration.Type}`);
|
||||||
|
}
|
||||||
|
const [itemType, meta] = entry;
|
||||||
|
if (meta.capacityCost === undefined) {
|
||||||
throw new Error(`unknown deco type: ${placedDecoration.Type}`);
|
throw new Error(`unknown deco type: ${placedDecoration.Type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from "@/src/types/statTypes";
|
} from "@/src/types/statTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { addEmailItem, getInventory } from "@/src/services/inventoryService";
|
import { addEmailItem, getInventory } from "@/src/services/inventoryService";
|
||||||
import { submitLeaderboardScore } from "./leaderboardService";
|
import { submitLeaderboardScore } from "@/src/services/leaderboardService";
|
||||||
|
|
||||||
export const createStats = async (accountId: string): Promise<TStatsDatabaseDocument> => {
|
export const createStats = async (accountId: string): Promise<TStatsDatabaseDocument> => {
|
||||||
const stats = new Stats({ accountOwnerId: accountId });
|
const stats = new Stats({ accountOwnerId: accountId });
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
import http from "http";
|
import http from "http";
|
||||||
import https from "https";
|
import https from "https";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { config } from "./configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { app } from "../app";
|
import { app } from "@/src/app";
|
||||||
import { AddressInfo } from "node:net";
|
import { AddressInfo } from "node:net";
|
||||||
import ws from "ws";
|
|
||||||
import { Account } from "../models/loginModel";
|
|
||||||
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService";
|
|
||||||
import { IDatabaseAccountJson } from "../types/loginTypes";
|
|
||||||
import { HydratedDocument } from "mongoose";
|
|
||||||
import { Agent, WebSocket as UnidiciWebSocket } from "undici";
|
import { Agent, WebSocket as UnidiciWebSocket } from "undici";
|
||||||
|
import { startWsServer, startWssServer, stopWsServers } from "@/src/services/wsService";
|
||||||
|
|
||||||
let httpServer: http.Server | undefined;
|
let httpServer: http.Server | undefined;
|
||||||
let httpsServer: https.Server | undefined;
|
let httpsServer: https.Server | undefined;
|
||||||
let wsServer: ws.Server | undefined;
|
|
||||||
let wssServer: ws.Server | undefined;
|
|
||||||
|
|
||||||
const tlsOptions = {
|
const tlsOptions = {
|
||||||
key: fs.readFileSync("static/certs/key.pem"),
|
key: fs.readFileSync("static/certs/key.pem"),
|
||||||
@ -29,16 +23,14 @@ export const startWebServer = (): void => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
httpServer = http.createServer(app);
|
httpServer = http.createServer(app);
|
||||||
httpServer.listen(httpPort, () => {
|
httpServer.listen(httpPort, () => {
|
||||||
wsServer = new ws.Server({ server: httpServer });
|
startWsServer(httpServer!);
|
||||||
wsServer.on("connection", wsOnConnect);
|
|
||||||
|
|
||||||
logger.info("HTTP server started on port " + httpPort);
|
logger.info("HTTP server started on port " + httpPort);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
httpsServer = https.createServer(tlsOptions, app);
|
httpsServer = https.createServer(tlsOptions, app);
|
||||||
httpsServer.listen(httpsPort, () => {
|
httpsServer.listen(httpsPort, () => {
|
||||||
wssServer = new ws.Server({ server: httpsServer });
|
startWssServer(httpsServer!);
|
||||||
wssServer.on("connection", wsOnConnect);
|
|
||||||
|
|
||||||
logger.info("HTTPS server started on port " + httpsPort);
|
logger.info("HTTPS server started on port " + httpsPort);
|
||||||
|
|
||||||
@ -115,182 +107,6 @@ export const stopWebServer = async (): Promise<void> => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (wsServer) {
|
stopWsServers(promises);
|
||||||
promises.push(
|
|
||||||
new Promise(resolve => {
|
|
||||||
wsServer!.close(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (wssServer) {
|
|
||||||
promises.push(
|
|
||||||
new Promise(resolve => {
|
|
||||||
wssServer!.close(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
let lastWsid: number = 0;
|
|
||||||
|
|
||||||
interface IWsCustomData extends ws {
|
|
||||||
id?: number;
|
|
||||||
accountId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IWsMsgFromClient {
|
|
||||||
auth?: {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
isRegister: boolean;
|
|
||||||
};
|
|
||||||
logout?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IWsMsgToClient {
|
|
||||||
//wsid?: number;
|
|
||||||
reload?: boolean;
|
|
||||||
ports?: {
|
|
||||||
http: number | undefined;
|
|
||||||
https: number | undefined;
|
|
||||||
};
|
|
||||||
config_reloaded?: boolean;
|
|
||||||
auth_succ?: {
|
|
||||||
id: string;
|
|
||||||
DisplayName: string;
|
|
||||||
Nonce: number;
|
|
||||||
};
|
|
||||||
auth_fail?: {
|
|
||||||
isRegister: boolean;
|
|
||||||
};
|
|
||||||
logged_out?: boolean;
|
|
||||||
update_inventory?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
|
|
||||||
if (req.url == "/custom/selftest") {
|
|
||||||
ws.send("SpaceNinjaServer");
|
|
||||||
ws.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(ws as IWsCustomData).id = ++lastWsid;
|
|
||||||
ws.send(JSON.stringify({ wsid: lastWsid }));
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
ws.on("message", async msg => {
|
|
||||||
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
|
|
||||||
if (data.auth) {
|
|
||||||
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
|
|
||||||
if (account) {
|
|
||||||
if (isCorrectPassword(data.auth.password, account.password)) {
|
|
||||||
if (!account.Nonce) {
|
|
||||||
account.ClientType = "webui";
|
|
||||||
account.Nonce = createNonce();
|
|
||||||
await (account as HydratedDocument<IDatabaseAccountJson>).save();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
account = null;
|
|
||||||
}
|
|
||||||
} else if (data.auth.isRegister) {
|
|
||||||
const name = await getUsernameFromEmail(data.auth.email);
|
|
||||||
account = await createAccount({
|
|
||||||
email: data.auth.email,
|
|
||||||
password: data.auth.password,
|
|
||||||
ClientType: "webui",
|
|
||||||
LastLogin: new Date(),
|
|
||||||
DisplayName: name,
|
|
||||||
Nonce: createNonce()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (account) {
|
|
||||||
(ws as IWsCustomData).accountId = account.id;
|
|
||||||
ws.send(
|
|
||||||
JSON.stringify({
|
|
||||||
auth_succ: {
|
|
||||||
id: account.id,
|
|
||||||
DisplayName: account.DisplayName,
|
|
||||||
Nonce: account.Nonce
|
|
||||||
}
|
|
||||||
} satisfies IWsMsgToClient)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ws.send(
|
|
||||||
JSON.stringify({
|
|
||||||
auth_fail: {
|
|
||||||
isRegister: data.auth.isRegister
|
|
||||||
}
|
|
||||||
} satisfies IWsMsgToClient)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.logout) {
|
|
||||||
const accountId = (ws as IWsCustomData).accountId;
|
|
||||||
(ws as IWsCustomData).accountId = undefined;
|
|
||||||
await Account.updateOne(
|
|
||||||
{
|
|
||||||
_id: accountId,
|
|
||||||
ClientType: "webui"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Nonce: 0
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
|
|
||||||
const msg = JSON.stringify(data);
|
|
||||||
if (wsServer) {
|
|
||||||
for (const client of wsServer.clients) {
|
|
||||||
client.send(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (wssServer) {
|
|
||||||
for (const client of wssServer.clients) {
|
|
||||||
client.send(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
|
|
||||||
const msg = JSON.stringify(data);
|
|
||||||
if (wsServer) {
|
|
||||||
for (const client of wsServer.clients) {
|
|
||||||
if ((client as IWsCustomData).accountId == accountId) {
|
|
||||||
client.send(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (wssServer) {
|
|
||||||
for (const client of wssServer.clients) {
|
|
||||||
if ((client as IWsCustomData).accountId == accountId) {
|
|
||||||
client.send(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
|
|
||||||
const msg = JSON.stringify(data);
|
|
||||||
if (wsServer) {
|
|
||||||
for (const client of wsServer.clients) {
|
|
||||||
if ((client as IWsCustomData).id != wsid) {
|
|
||||||
client.send(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (wssServer) {
|
|
||||||
for (const client of wssServer.clients) {
|
|
||||||
if ((client as IWsCustomData).id != wsid) {
|
|
||||||
client.send(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -1,20 +1,26 @@
|
|||||||
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
|
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
|
||||||
import baro from "@/static/fixed_responses/worldState/baro.json";
|
import baro from "@/static/fixed_responses/worldState/baro.json";
|
||||||
|
import varzia from "@/static/fixed_responses/worldState/varzia.json";
|
||||||
import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json";
|
import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json";
|
||||||
import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
|
import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
|
||||||
import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json";
|
import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json";
|
||||||
import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
|
import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
|
||||||
import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
|
import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
|
||||||
|
import invasionNodes from "@/static/fixed_responses/worldState/invasionNodes.json";
|
||||||
|
import invasionRewards from "@/static/fixed_responses/worldState/invasionRewards.json";
|
||||||
import { buildConfig } from "@/src/services/buildConfigService";
|
import { buildConfig } from "@/src/services/buildConfigService";
|
||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService";
|
import { getRandomElement, getRandomInt, sequentiallyUniqueRandomElement, SRng } from "@/src/services/rngService";
|
||||||
import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
|
import { eMissionType, ExportRegions, ExportSyndicates, IMissionReward, IRegion } from "warframe-public-export-plus";
|
||||||
import {
|
import {
|
||||||
ICalendarDay,
|
ICalendarDay,
|
||||||
ICalendarEvent,
|
ICalendarEvent,
|
||||||
ICalendarSeason,
|
ICalendarSeason,
|
||||||
|
IInvasion,
|
||||||
ILiteSortie,
|
ILiteSortie,
|
||||||
|
IPrimeVaultTrader,
|
||||||
|
IPrimeVaultTraderOffer,
|
||||||
ISeasonChallenge,
|
ISeasonChallenge,
|
||||||
ISortie,
|
ISortie,
|
||||||
ISortieMission,
|
ISortieMission,
|
||||||
@ -25,10 +31,10 @@ import {
|
|||||||
IVoidTraderOffer,
|
IVoidTraderOffer,
|
||||||
IWorldState,
|
IWorldState,
|
||||||
TCircuitGameMode
|
TCircuitGameMode
|
||||||
} from "../types/worldStateTypes";
|
} from "@/src/types/worldStateTypes";
|
||||||
import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers";
|
import { toMongoDate, toOid, version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { DailyDeal, Fissure } from "../models/worldStateModel";
|
import { DailyDeal, Fissure } from "@/src/models/worldStateModel";
|
||||||
|
|
||||||
const sortieBosses = [
|
const sortieBosses = [
|
||||||
"SORTIE_BOSS_HYENA",
|
"SORTIE_BOSS_HYENA",
|
||||||
@ -382,44 +388,35 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
|
|||||||
const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: number): ISeasonChallenge => {
|
const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: number): ISeasonChallenge => {
|
||||||
const dayStart = EPOCH + day * 86400000;
|
const dayStart = EPOCH + day * 86400000;
|
||||||
const dayEnd = EPOCH + (day + 3) * 86400000;
|
const dayEnd = EPOCH + (day + 3) * 86400000;
|
||||||
const rng = new SRng(new SRng(day).randomInt(0, 100_000));
|
|
||||||
return {
|
return {
|
||||||
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
|
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
|
||||||
Daily: true,
|
Daily: true,
|
||||||
Activation: { $date: { $numberLong: dayStart.toString() } },
|
Activation: { $date: { $numberLong: dayStart.toString() } },
|
||||||
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
||||||
Challenge: rng.randomElement(pools.daily)!
|
Challenge: sequentiallyUniqueRandomElement(pools.daily, day, 2, 605732938)!
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSeasonWeeklyChallenge = (pools: IRotatingSeasonChallengePools, week: number, id: number): ISeasonChallenge => {
|
const pushSeasonWeeklyChallenge = (
|
||||||
const weekStart = EPOCH + week * 604800000;
|
activeChallenges: ISeasonChallenge[],
|
||||||
const weekEnd = weekStart + 604800000;
|
pool: string[],
|
||||||
const challengeId = week * 7 + id;
|
|
||||||
const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000));
|
|
||||||
return {
|
|
||||||
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
|
||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
|
||||||
Challenge: rng.randomElement(pools.weekly)!
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSeasonWeeklyHardChallenge = (
|
|
||||||
pools: IRotatingSeasonChallengePools,
|
|
||||||
week: number,
|
week: number,
|
||||||
id: number
|
id: number
|
||||||
): ISeasonChallenge => {
|
): void => {
|
||||||
const weekStart = EPOCH + week * 604800000;
|
const weekStart = EPOCH + week * 604800000;
|
||||||
const weekEnd = weekStart + 604800000;
|
const weekEnd = weekStart + 604800000;
|
||||||
const challengeId = week * 7 + id;
|
const challengeId = week * 7 + id;
|
||||||
const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000));
|
const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000));
|
||||||
return {
|
let challenge: string;
|
||||||
|
do {
|
||||||
|
challenge = rng.randomElement(pool)!;
|
||||||
|
} while (activeChallenges.some(x => x.Challenge == challenge));
|
||||||
|
activeChallenges.push({
|
||||||
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||||
Challenge: rng.randomElement(pools.hardWeekly)!
|
Challenge: challenge
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushWeeklyActs = (
|
const pushWeeklyActs = (
|
||||||
@ -430,8 +427,8 @@ const pushWeeklyActs = (
|
|||||||
const weekStart = EPOCH + week * 604800000;
|
const weekStart = EPOCH + week * 604800000;
|
||||||
const weekEnd = weekStart + 604800000;
|
const weekEnd = weekStart + 604800000;
|
||||||
|
|
||||||
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 0));
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 0);
|
||||||
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 1));
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 1);
|
||||||
if (pools.hasWeeklyPermanent) {
|
if (pools.hasWeeklyPermanent) {
|
||||||
activeChallenges.push({
|
activeChallenges.push({
|
||||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
|
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
|
||||||
@ -451,14 +448,14 @@ const pushWeeklyActs = (
|
|||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||||
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies"
|
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies"
|
||||||
});
|
});
|
||||||
activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 2));
|
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 2);
|
||||||
activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 3));
|
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 3);
|
||||||
} else {
|
} else {
|
||||||
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 2));
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 2);
|
||||||
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 3));
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 3);
|
||||||
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 4));
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 4);
|
||||||
activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 5));
|
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 5);
|
||||||
activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 6));
|
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 6);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -977,25 +974,26 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
|
|||||||
|
|
||||||
// Not very faithful, but to avoid the same node coming up back-to-back (which is not valid), I've split these into 2 arrays which we're alternating between.
|
// Not very faithful, but to avoid the same node coming up back-to-back (which is not valid), I've split these into 2 arrays which we're alternating between.
|
||||||
|
|
||||||
const voidStormMissionsA = {
|
const voidStormMissions = {
|
||||||
VoidT1: ["CrewBattleNode519", "CrewBattleNode518", "CrewBattleNode515", "CrewBattleNode503"],
|
VoidT1: [
|
||||||
VoidT2: ["CrewBattleNode501", "CrewBattleNode534", "CrewBattleNode530"],
|
"CrewBattleNode519",
|
||||||
VoidT3: ["CrewBattleNode521", "CrewBattleNode516"],
|
"CrewBattleNode518",
|
||||||
|
"CrewBattleNode515",
|
||||||
|
"CrewBattleNode503",
|
||||||
|
"CrewBattleNode509",
|
||||||
|
"CrewBattleNode522",
|
||||||
|
"CrewBattleNode511",
|
||||||
|
"CrewBattleNode512"
|
||||||
|
],
|
||||||
|
VoidT2: ["CrewBattleNode501", "CrewBattleNode534", "CrewBattleNode530", "CrewBattleNode535", "CrewBattleNode533"],
|
||||||
|
VoidT3: ["CrewBattleNode521", "CrewBattleNode516", "CrewBattleNode524", "CrewBattleNode525"],
|
||||||
VoidT4: [
|
VoidT4: [
|
||||||
"CrewBattleNode555",
|
"CrewBattleNode555",
|
||||||
"CrewBattleNode553",
|
"CrewBattleNode553",
|
||||||
"CrewBattleNode554",
|
"CrewBattleNode554",
|
||||||
"CrewBattleNode539",
|
"CrewBattleNode539",
|
||||||
"CrewBattleNode531",
|
"CrewBattleNode531",
|
||||||
"CrewBattleNode527"
|
"CrewBattleNode527",
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const voidStormMissionsB = {
|
|
||||||
VoidT1: ["CrewBattleNode509", "CrewBattleNode522", "CrewBattleNode511", "CrewBattleNode512"],
|
|
||||||
VoidT2: ["CrewBattleNode535", "CrewBattleNode533"],
|
|
||||||
VoidT3: ["CrewBattleNode524", "CrewBattleNode525"],
|
|
||||||
VoidT4: [
|
|
||||||
"CrewBattleNode542",
|
"CrewBattleNode542",
|
||||||
"CrewBattleNode538",
|
"CrewBattleNode538",
|
||||||
"CrewBattleNode543",
|
"CrewBattleNode543",
|
||||||
@ -1003,18 +1001,21 @@ const voidStormMissionsB = {
|
|||||||
"CrewBattleNode550",
|
"CrewBattleNode550",
|
||||||
"CrewBattleNode529"
|
"CrewBattleNode529"
|
||||||
]
|
]
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
|
const voidStormLookbehind = {
|
||||||
|
VoidT1: 3,
|
||||||
|
VoidT2: 1,
|
||||||
|
VoidT3: 1,
|
||||||
|
VoidT4: 3
|
||||||
|
} as const;
|
||||||
|
|
||||||
const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
|
const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
|
||||||
const activation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute;
|
const activation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute;
|
||||||
const expiry = activation + 90 * unixTimesInMs.minute;
|
const expiry = activation + 90 * unixTimesInMs.minute;
|
||||||
let accum = 0;
|
let accum = 0;
|
||||||
const rng = new SRng(new SRng(hour).randomInt(0, 100_000));
|
const tierIdx = { VoidT1: hour * 2, VoidT2: hour, VoidT3: hour, VoidT4: hour * 2 };
|
||||||
const voidStormMissions = structuredClone(hour & 1 ? voidStormMissionsA : voidStormMissionsB);
|
|
||||||
for (const tier of ["VoidT1", "VoidT1", "VoidT2", "VoidT3", "VoidT4", "VoidT4"] as const) {
|
for (const tier of ["VoidT1", "VoidT1", "VoidT2", "VoidT3", "VoidT4", "VoidT4"] as const) {
|
||||||
const idx = rng.randomInt(0, voidStormMissions[tier].length - 1);
|
|
||||||
const node = voidStormMissions[tier][idx];
|
|
||||||
voidStormMissions[tier].splice(idx, 1);
|
|
||||||
arr.push({
|
arr.push({
|
||||||
_id: {
|
_id: {
|
||||||
$oid:
|
$oid:
|
||||||
@ -1022,7 +1023,12 @@ const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
|
|||||||
"0321e89b" +
|
"0321e89b" +
|
||||||
(accum++).toString().padStart(8, "0")
|
(accum++).toString().padStart(8, "0")
|
||||||
},
|
},
|
||||||
Node: node,
|
Node: sequentiallyUniqueRandomElement(
|
||||||
|
voidStormMissions[tier],
|
||||||
|
tierIdx[tier]++,
|
||||||
|
voidStormLookbehind[tier],
|
||||||
|
2051969264
|
||||||
|
)!,
|
||||||
Activation: { $date: { $numberLong: activation.toString() } },
|
Activation: { $date: { $numberLong: activation.toString() } },
|
||||||
Expiry: { $date: { $numberLong: expiry.toString() } },
|
Expiry: { $date: { $numberLong: expiry.toString() } },
|
||||||
ActiveMissionTier: tier
|
ActiveMissionTier: tier
|
||||||
@ -1030,53 +1036,280 @@ const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const doesTimeSatsifyConstraints = (timeSecs: number): boolean => {
|
interface ITimeConstraint {
|
||||||
if (config.worldState?.eidolonOverride) {
|
//name: string;
|
||||||
|
isValidTime: (timeSecs: number) => boolean;
|
||||||
|
getIdealTimeBefore: (timeSecs: number) => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eidolonDayConstraint: ITimeConstraint = {
|
||||||
|
//name: "eidolon day",
|
||||||
|
isValidTime: (timeSecs: number): boolean => {
|
||||||
const eidolonEpoch = 1391992660;
|
const eidolonEpoch = 1391992660;
|
||||||
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
|
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
|
||||||
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
|
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
|
||||||
const eidolonCycleEnd = eidolonCycleStart + 9000;
|
const eidolonCycleEnd = eidolonCycleStart + 9000;
|
||||||
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
|
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
|
||||||
if (config.worldState.eidolonOverride == "day") {
|
return !isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleNightStart * 1000);
|
||||||
if (
|
},
|
||||||
//timeSecs < eidolonCycleStart ||
|
getIdealTimeBefore: (timeSecs: number): number => {
|
||||||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleNightStart * 1000)
|
const eidolonEpoch = 1391992660;
|
||||||
) {
|
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
|
||||||
return false;
|
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
|
||||||
}
|
return eidolonCycleStart;
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
timeSecs < eidolonCycleNightStart ||
|
|
||||||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleEnd * 1000)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (config.worldState?.vallisOverride) {
|
const eidolonNightConstraint: ITimeConstraint = {
|
||||||
|
//name: "eidolon night",
|
||||||
|
isValidTime: (timeSecs: number): boolean => {
|
||||||
|
const eidolonEpoch = 1391992660;
|
||||||
|
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
|
||||||
|
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
|
||||||
|
const eidolonCycleEnd = eidolonCycleStart + 9000;
|
||||||
|
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
|
||||||
|
return (
|
||||||
|
timeSecs >= eidolonCycleNightStart &&
|
||||||
|
!isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleEnd * 1000)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getIdealTimeBefore: (timeSecs: number): number => {
|
||||||
|
const eidolonEpoch = 1391992660;
|
||||||
|
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
|
||||||
|
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
|
||||||
|
const eidolonCycleEnd = eidolonCycleStart + 9000;
|
||||||
|
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
|
||||||
|
if (eidolonCycleNightStart > timeSecs) {
|
||||||
|
// Night hasn't started yet, but we need to return a time in the past.
|
||||||
|
return eidolonCycleNightStart - 9000;
|
||||||
|
}
|
||||||
|
return eidolonCycleNightStart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const venusColdConstraint: ITimeConstraint = {
|
||||||
|
//name: "venus cold",
|
||||||
|
isValidTime: (timeSecs: number): boolean => {
|
||||||
const vallisEpoch = 1541837628;
|
const vallisEpoch = 1541837628;
|
||||||
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
|
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
|
||||||
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
|
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
|
||||||
const vallisCycleEnd = vallisCycleStart + 1600;
|
const vallisCycleEnd = vallisCycleStart + 1600;
|
||||||
const vallisCycleColdStart = vallisCycleStart + 400;
|
const vallisCycleColdStart = vallisCycleStart + 400;
|
||||||
if (config.worldState.vallisOverride == "cold") {
|
return (
|
||||||
if (
|
timeSecs >= vallisCycleColdStart &&
|
||||||
timeSecs < vallisCycleColdStart ||
|
!isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleEnd * 1000)
|
||||||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleEnd * 1000)
|
);
|
||||||
) {
|
},
|
||||||
return false;
|
getIdealTimeBefore: (timeSecs: number): number => {
|
||||||
}
|
const vallisEpoch = 1541837628;
|
||||||
} else {
|
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
|
||||||
if (
|
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
|
||||||
//timeSecs < vallisCycleStart ||
|
const vallisCycleColdStart = vallisCycleStart + 400;
|
||||||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleColdStart * 1000)
|
if (vallisCycleColdStart > timeSecs) {
|
||||||
) {
|
// Cold hasn't started yet, but we need to return a time in the past.
|
||||||
return false;
|
return vallisCycleColdStart - 1600;
|
||||||
|
}
|
||||||
|
return vallisCycleColdStart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const venusWarmConstraint: ITimeConstraint = {
|
||||||
|
//name: "venus warm",
|
||||||
|
isValidTime: (timeSecs: number): boolean => {
|
||||||
|
const vallisEpoch = 1541837628;
|
||||||
|
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
|
||||||
|
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
|
||||||
|
const vallisCycleColdStart = vallisCycleStart + 400;
|
||||||
|
return !isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleColdStart * 1000);
|
||||||
|
},
|
||||||
|
getIdealTimeBefore: (timeSecs: number): number => {
|
||||||
|
const vallisEpoch = 1541837628;
|
||||||
|
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
|
||||||
|
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
|
||||||
|
return vallisCycleStart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIdealTimeSatsifyingConstraints = (constraints: ITimeConstraint[]): number => {
|
||||||
|
let timeSecs = Math.trunc(Date.now() / 1000);
|
||||||
|
let allGood;
|
||||||
|
do {
|
||||||
|
allGood = true;
|
||||||
|
for (const constraint of constraints) {
|
||||||
|
if (!constraint.isValidTime(timeSecs)) {
|
||||||
|
//logger.debug(`${constraint.name} is not happy with ${timeSecs}`);
|
||||||
|
const prevTimeSecs = timeSecs;
|
||||||
|
const suggestion = constraint.getIdealTimeBefore(timeSecs);
|
||||||
|
timeSecs = suggestion;
|
||||||
|
do {
|
||||||
|
timeSecs += 60;
|
||||||
|
if (timeSecs >= prevTimeSecs || !constraint.isValidTime(timeSecs)) {
|
||||||
|
timeSecs = suggestion; // Can't find a compromise; just take the suggestion and try to compromise on another constraint.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (!constraints.every(constraint => constraint.isValidTime(timeSecs)));
|
||||||
|
allGood = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} while (!allGood);
|
||||||
|
return timeSecs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVarziaRotation = (week: number): string => {
|
||||||
|
const seed = new SRng(week).randomInt(0, 100_000);
|
||||||
|
const rng = new SRng(seed);
|
||||||
|
return rng.randomElement(varzia.primeDualPacks)!.ItemType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVarziaManifest = (dualPack: string): IPrimeVaultTraderOffer[] => {
|
||||||
|
const rotrationManifest = varzia.primeDualPacks.find(pack => pack.ItemType === dualPack);
|
||||||
|
if (!rotrationManifest) return [];
|
||||||
|
|
||||||
|
const mainPack = [{ ItemType: rotrationManifest.ItemType, PrimePrice: 10 }];
|
||||||
|
const singlePacks: IPrimeVaultTraderOffer[] = [];
|
||||||
|
const items: IPrimeVaultTraderOffer[] = [];
|
||||||
|
const bobbleHeads: IPrimeVaultTraderOffer[] = [];
|
||||||
|
|
||||||
|
for (const singlePackType of rotrationManifest.SinglePacks) {
|
||||||
|
singlePacks.push({ ItemType: singlePackType, PrimePrice: 6 });
|
||||||
|
|
||||||
|
const sp = varzia.primeSinglePacks.find(pack => pack.ItemType === singlePackType);
|
||||||
|
if (sp) {
|
||||||
|
items.push(...sp.Items);
|
||||||
|
sp.BobbleHeads.forEach(bobbleHead => {
|
||||||
|
bobbleHeads.push({ ItemType: bobbleHead, PrimePrice: 1 });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const relics = rotrationManifest.Relics.map(relic => ({ ItemType: relic, RegularPrice: 1 }));
|
||||||
|
|
||||||
|
return [singlePacks[0], ...mainPack, singlePacks[1], ...items, ...bobbleHeads, ...relics];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllVarziaManifests = (): IPrimeVaultTraderOffer[] => {
|
||||||
|
const dualPacks: IPrimeVaultTraderOffer[] = [];
|
||||||
|
const singlePacks: IPrimeVaultTraderOffer[] = [];
|
||||||
|
const items: IPrimeVaultTraderOffer[] = [];
|
||||||
|
const bobbleHeads: IPrimeVaultTraderOffer[] = [];
|
||||||
|
const relics: IPrimeVaultTraderOffer[] = [];
|
||||||
|
|
||||||
|
const singlePackSet = new Set<string>();
|
||||||
|
const itemsSet = new Set<string>();
|
||||||
|
const bobbleHeadsSet = new Set<string>();
|
||||||
|
|
||||||
|
varzia.primeDualPacks.forEach(dualPack => {
|
||||||
|
dualPacks.push({ ItemType: dualPack.ItemType, PrimePrice: 10 });
|
||||||
|
|
||||||
|
dualPack.SinglePacks.forEach(singlePackType => {
|
||||||
|
if (!singlePackSet.has(singlePackType)) {
|
||||||
|
singlePackSet.add(singlePackType);
|
||||||
|
singlePacks.push({ ItemType: singlePackType, PrimePrice: 6 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const sp = varzia.primeSinglePacks.find(pack => pack.ItemType === singlePackType)!;
|
||||||
|
sp.Items.forEach(item => {
|
||||||
|
if (!itemsSet.has(item.ItemType)) {
|
||||||
|
itemsSet.add(item.ItemType);
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sp.BobbleHeads.forEach(bobbleHead => {
|
||||||
|
if (!bobbleHeadsSet.has(bobbleHead)) {
|
||||||
|
bobbleHeadsSet.add(bobbleHead);
|
||||||
|
bobbleHeads.push({ ItemType: bobbleHead, PrimePrice: 1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
relics.push(...dualPack.Relics.map(relic => ({ ItemType: relic, RegularPrice: 1 })));
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...dualPacks, ...singlePacks, ...items, ...bobbleHeads, ...relics];
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInvasion = (day: number, idx: number): IInvasion => {
|
||||||
|
const id = day * 3 + idx;
|
||||||
|
const defender = (["FC_GRINEER", "FC_CORPUS", day % 2 ? "FC_GRINEER" : "FC_CORPUS"] as const)[idx];
|
||||||
|
const rng = new SRng(new SRng(id).randomInt(0, 1_000_000));
|
||||||
|
const isInfestationOutbreak = rng.randomInt(0, 1) == 0;
|
||||||
|
const attacker = isInfestationOutbreak ? "FC_INFESTATION" : defender == "FC_GRINEER" ? "FC_CORPUS" : "FC_GRINEER";
|
||||||
|
const startMs = EPOCH + day * 86400_000;
|
||||||
|
const oid =
|
||||||
|
((startMs / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
|
||||||
|
"fd148cb8" +
|
||||||
|
(idx & 0xffffffff).toString(16).padStart(8, "0");
|
||||||
|
const node = sequentiallyUniqueRandomElement(invasionNodes[defender], id, 5, 690175)!; // Can't repeat the other 2 on this day nor the last 3
|
||||||
|
const progress = (Date.now() - startMs) / 86400_000;
|
||||||
|
const countMultiplier = isInfestationOutbreak || rng.randomInt(0, 1) ? -1 : 1; // if defender is winning, count is negative
|
||||||
|
const fiftyPercent = rng.randomInt(1000, 29000); // introduce some 'yitter' for the percentages
|
||||||
|
const rewardFloat = rng.randomFloat();
|
||||||
|
const rewardTier = rewardFloat < 0.201 ? "RARE" : rewardFloat < 0.7788 ? "COMMON" : "UNCOMMON";
|
||||||
|
const attackerReward: IMissionReward = {};
|
||||||
|
const defenderReward: IMissionReward = {};
|
||||||
|
if (isInfestationOutbreak) {
|
||||||
|
defenderReward.countedItems = [
|
||||||
|
rng.randomElement(invasionRewards[rng.randomInt(0, 1) ? "FC_INFESTATION" : defender][rewardTier])!
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
attackerReward.countedItems = [rng.randomElement(invasionRewards[attacker][rewardTier])!];
|
||||||
|
defenderReward.countedItems = [rng.randomElement(invasionRewards[defender][rewardTier])!];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
_id: { $oid: oid },
|
||||||
|
Faction: attacker,
|
||||||
|
DefenderFaction: defender,
|
||||||
|
Node: node,
|
||||||
|
Count: Math.round(
|
||||||
|
(progress < 0.5 ? progress * 2 * fiftyPercent : fiftyPercent + (30_000 - fiftyPercent) * (progress - 0.5)) *
|
||||||
|
countMultiplier
|
||||||
|
),
|
||||||
|
Goal: 30000, // Value seems to range from 30000 to 98000 in intervals of 1000. Higher values are increasingly rare. I don't think this is relevant for the frontend besides dividing count by it.
|
||||||
|
LocTag: isInfestationOutbreak
|
||||||
|
? ExportRegions[node].missionIndex == 0
|
||||||
|
? "/Lotus/Language/Menu/InfestedInvasionBoss"
|
||||||
|
: "/Lotus/Language/Menu/InfestedInvasionGeneric"
|
||||||
|
: attacker == "FC_CORPUS"
|
||||||
|
? "/Lotus/Language/Menu/CorpusInvasionGeneric"
|
||||||
|
: "/Lotus/Language/Menu/GrineerInvasionGeneric",
|
||||||
|
Completed: startMs + 86400_000 < Date.now(), // Sorta unfaithful. Invasions on live are (at least in part) in fluenced by people completing them. And otherwise also probably not hardcoded to last 24 hours.
|
||||||
|
ChainID: { $oid: oid },
|
||||||
|
AttackerReward: attackerReward,
|
||||||
|
AttackerMissionInfo: {
|
||||||
|
seed: rng.randomInt(0, 1_000_000),
|
||||||
|
faction: defender
|
||||||
|
},
|
||||||
|
DefenderReward: defenderReward,
|
||||||
|
DefenderMissionInfo: {
|
||||||
|
seed: rng.randomInt(0, 1_000_000),
|
||||||
|
faction: attacker
|
||||||
|
},
|
||||||
|
Activation: {
|
||||||
|
$date: {
|
||||||
|
$numberLong: startMs.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInvasionByOid = (oid: string): IInvasion | undefined => {
|
||||||
|
const arr = oid.split("fd148cb8");
|
||||||
|
if (arr.length == 2 && arr[0].length == 8 && arr[1].length == 8) {
|
||||||
|
return createInvasion(idToDay(oid), parseInt(arr[1], 16));
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getWorldState = (buildLabel?: string): IWorldState => {
|
||||||
|
const constraints: ITimeConstraint[] = [];
|
||||||
|
if (config.worldState?.eidolonOverride) {
|
||||||
|
constraints.push(config.worldState.eidolonOverride == "day" ? eidolonDayConstraint : eidolonNightConstraint);
|
||||||
|
}
|
||||||
|
if (config.worldState?.vallisOverride) {
|
||||||
|
constraints.push(config.worldState.vallisOverride == "cold" ? venusColdConstraint : venusWarmConstraint);
|
||||||
|
}
|
||||||
if (config.worldState?.duviriOverride) {
|
if (config.worldState?.duviriOverride) {
|
||||||
const duviriMoods = ["sorrow", "fear", "joy", "anger", "envy"];
|
const duviriMoods = ["sorrow", "fear", "joy", "anger", "envy"];
|
||||||
const desiredMood = duviriMoods.indexOf(config.worldState.duviriOverride);
|
const desiredMood = duviriMoods.indexOf(config.worldState.duviriOverride);
|
||||||
@ -1086,26 +1319,22 @@ const doesTimeSatsifyConstraints = (timeSecs: number): boolean => {
|
|||||||
valid_values: duviriMoods
|
valid_values: duviriMoods
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const moodIndex = Math.trunc(timeSecs / 7200);
|
constraints.push({
|
||||||
const moodStart = moodIndex * 7200;
|
//name: `duviri ${config.worldState.duviriOverride}`,
|
||||||
const moodEnd = moodStart + 7200;
|
isValidTime: (timeSecs: number): boolean => {
|
||||||
if (
|
const moodIndex = Math.trunc(timeSecs / 7200);
|
||||||
moodIndex % 5 != desiredMood ||
|
return moodIndex % 5 == desiredMood;
|
||||||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, moodEnd * 1000)
|
},
|
||||||
) {
|
getIdealTimeBefore: (timeSecs: number): number => {
|
||||||
return false;
|
let moodIndex = Math.trunc(timeSecs / 7200);
|
||||||
}
|
moodIndex -= ((moodIndex % 5) - desiredMood + 5) % 5; // while (moodIndex % 5 != desiredMood) --moodIndex;
|
||||||
|
const moodStart = moodIndex * 7200;
|
||||||
|
return moodStart;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const timeSecs = getIdealTimeSatsifyingConstraints(constraints);
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getWorldState = (buildLabel?: string): IWorldState => {
|
|
||||||
let timeSecs = Math.round(Date.now() / 1000);
|
|
||||||
while (!doesTimeSatsifyConstraints(timeSecs)) {
|
|
||||||
timeSecs -= 60;
|
|
||||||
}
|
|
||||||
const timeMs = timeSecs * 1000;
|
const timeMs = timeSecs * 1000;
|
||||||
const day = Math.trunc((timeMs - EPOCH) / 86400000);
|
const day = Math.trunc((timeMs - EPOCH) / 86400000);
|
||||||
const week = Math.trunc(day / 7);
|
const week = Math.trunc(day / 7);
|
||||||
@ -1121,7 +1350,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
LiteSorties: [],
|
LiteSorties: [],
|
||||||
ActiveMissions: [],
|
ActiveMissions: [],
|
||||||
GlobalUpgrades: [],
|
GlobalUpgrades: [],
|
||||||
|
Invasions: [],
|
||||||
VoidTraders: [],
|
VoidTraders: [],
|
||||||
|
PrimeVaultTraders: [],
|
||||||
VoidStorms: [],
|
VoidStorms: [],
|
||||||
DailyDeals: [],
|
DailyDeals: [],
|
||||||
EndlessXpChoices: [],
|
EndlessXpChoices: [],
|
||||||
@ -1322,6 +1553,20 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rough outline of dynamic invasions.
|
||||||
|
// TODO: Invasions chains, e.g. an infestation mission would soon lead to other nodes on that planet also having an infestation invasion.
|
||||||
|
// TODO: Grineer/Corpus to fund their death stars with each invasion win.
|
||||||
|
{
|
||||||
|
worldState.Invasions.push(createInvasion(day, 0));
|
||||||
|
worldState.Invasions.push(createInvasion(day, 1));
|
||||||
|
worldState.Invasions.push(createInvasion(day, 2));
|
||||||
|
|
||||||
|
// Completed invasions stay for up to 24 hours as the winner 'occupies' that node
|
||||||
|
worldState.Invasions.push(createInvasion(day - 1, 0));
|
||||||
|
worldState.Invasions.push(createInvasion(day - 1, 1));
|
||||||
|
worldState.Invasions.push(createInvasion(day - 1, 2));
|
||||||
|
}
|
||||||
|
|
||||||
// Baro
|
// Baro
|
||||||
{
|
{
|
||||||
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
|
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
|
||||||
@ -1393,6 +1638,31 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Varzia
|
||||||
|
{
|
||||||
|
const pt: IPrimeVaultTrader = {
|
||||||
|
_id: { $oid: ((weekStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "c36af423770eaa97" },
|
||||||
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||||
|
InitialStartDate: { $date: { $numberLong: "1662738144266" } },
|
||||||
|
Node: "TradeHUB1",
|
||||||
|
Manifest: [],
|
||||||
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||||
|
EvergreenManifest: varzia.evergreen,
|
||||||
|
ScheduleInfo: []
|
||||||
|
};
|
||||||
|
worldState.PrimeVaultTraders.push(pt);
|
||||||
|
const rotation = config.worldState?.varziaOverride || getVarziaRotation(week);
|
||||||
|
pt.Manifest = config.worldState?.varziaFullyStocked ? getAllVarziaManifests() : getVarziaManifest(rotation);
|
||||||
|
if (config.worldState?.varziaOverride || config.worldState?.varziaFullyStocked) {
|
||||||
|
pt.Expiry = { $date: { $numberLong: "2000000000000" } };
|
||||||
|
} else {
|
||||||
|
pt.ScheduleInfo.push({
|
||||||
|
Expiry: { $date: { $numberLong: (weekEnd + unixTimesInMs.week).toString() } },
|
||||||
|
FeaturedItem: getVarziaRotation(week + 1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
|
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
|
||||||
{
|
{
|
||||||
const rollover = getSortieTime(day);
|
const rollover = getSortieTime(day);
|
||||||
|
200
src/services/wsService.ts
Normal file
200
src/services/wsService.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import http from "http";
|
||||||
|
import https from "https";
|
||||||
|
import ws from "ws";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
|
||||||
|
import { IDatabaseAccountJson } from "@/src/types/loginTypes";
|
||||||
|
import { HydratedDocument } from "mongoose";
|
||||||
|
|
||||||
|
let wsServer: ws.Server | undefined;
|
||||||
|
let wssServer: ws.Server | undefined;
|
||||||
|
|
||||||
|
export const startWsServer = (httpServer: http.Server): void => {
|
||||||
|
wsServer = new ws.Server({ server: httpServer });
|
||||||
|
wsServer.on("connection", wsOnConnect);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startWssServer = (httpsServer: https.Server): void => {
|
||||||
|
wssServer = new ws.Server({ server: httpsServer });
|
||||||
|
wssServer.on("connection", wsOnConnect);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopWsServers = (promises: Promise<void>[]): void => {
|
||||||
|
if (wsServer) {
|
||||||
|
promises.push(
|
||||||
|
new Promise(resolve => {
|
||||||
|
wsServer!.close(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (wssServer) {
|
||||||
|
promises.push(
|
||||||
|
new Promise(resolve => {
|
||||||
|
wssServer!.close(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastWsid: number = 0;
|
||||||
|
|
||||||
|
interface IWsCustomData extends ws {
|
||||||
|
id?: number;
|
||||||
|
accountId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IWsMsgFromClient {
|
||||||
|
auth?: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
isRegister: boolean;
|
||||||
|
};
|
||||||
|
logout?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IWsMsgToClient {
|
||||||
|
//wsid?: number;
|
||||||
|
reload?: boolean;
|
||||||
|
ports?: {
|
||||||
|
http: number | undefined;
|
||||||
|
https: number | undefined;
|
||||||
|
};
|
||||||
|
config_reloaded?: boolean;
|
||||||
|
auth_succ?: {
|
||||||
|
id: string;
|
||||||
|
DisplayName: string;
|
||||||
|
Nonce: number;
|
||||||
|
};
|
||||||
|
auth_fail?: {
|
||||||
|
isRegister: boolean;
|
||||||
|
};
|
||||||
|
logged_out?: boolean;
|
||||||
|
update_inventory?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
|
||||||
|
if (req.url == "/custom/selftest") {
|
||||||
|
ws.send("SpaceNinjaServer");
|
||||||
|
ws.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(ws as IWsCustomData).id = ++lastWsid;
|
||||||
|
ws.send(JSON.stringify({ wsid: lastWsid }));
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
ws.on("message", async msg => {
|
||||||
|
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
|
||||||
|
if (data.auth) {
|
||||||
|
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
|
||||||
|
if (account) {
|
||||||
|
if (isCorrectPassword(data.auth.password, account.password)) {
|
||||||
|
if (!account.Nonce) {
|
||||||
|
account.ClientType = "webui";
|
||||||
|
account.Nonce = createNonce();
|
||||||
|
await (account as HydratedDocument<IDatabaseAccountJson>).save();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
account = null;
|
||||||
|
}
|
||||||
|
} else if (data.auth.isRegister) {
|
||||||
|
const name = await getUsernameFromEmail(data.auth.email);
|
||||||
|
account = await createAccount({
|
||||||
|
email: data.auth.email,
|
||||||
|
password: data.auth.password,
|
||||||
|
ClientType: "webui",
|
||||||
|
LastLogin: new Date(),
|
||||||
|
DisplayName: name,
|
||||||
|
Nonce: createNonce()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (account) {
|
||||||
|
(ws as IWsCustomData).accountId = account.id;
|
||||||
|
ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
auth_succ: {
|
||||||
|
id: account.id,
|
||||||
|
DisplayName: account.DisplayName,
|
||||||
|
Nonce: account.Nonce
|
||||||
|
}
|
||||||
|
} satisfies IWsMsgToClient)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
auth_fail: {
|
||||||
|
isRegister: data.auth.isRegister
|
||||||
|
}
|
||||||
|
} satisfies IWsMsgToClient)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.logout) {
|
||||||
|
const accountId = (ws as IWsCustomData).accountId;
|
||||||
|
(ws as IWsCustomData).accountId = undefined;
|
||||||
|
await Account.updateOne(
|
||||||
|
{
|
||||||
|
_id: accountId,
|
||||||
|
ClientType: "webui"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Nonce: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
|
||||||
|
const msg = JSON.stringify(data);
|
||||||
|
if (wsServer) {
|
||||||
|
for (const client of wsServer.clients) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wssServer) {
|
||||||
|
for (const client of wssServer.clients) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
|
||||||
|
const msg = JSON.stringify(data);
|
||||||
|
if (wsServer) {
|
||||||
|
for (const client of wsServer.clients) {
|
||||||
|
if ((client as IWsCustomData).accountId == accountId) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wssServer) {
|
||||||
|
for (const client of wssServer.clients) {
|
||||||
|
if ((client as IWsCustomData).accountId == accountId) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
|
||||||
|
const msg = JSON.stringify(data);
|
||||||
|
if (wsServer) {
|
||||||
|
for (const client of wsServer.clients) {
|
||||||
|
if ((client as IWsCustomData).id != wsid) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wssServer) {
|
||||||
|
for (const client of wssServer.clients) {
|
||||||
|
if ((client as IWsCustomData).id != wsid) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user