Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
3809d6678c | |||
d117c93ef7 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -10,8 +10,6 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.2
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Setup Node.js environment
|
- name: Setup Node.js environment
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
|
||||||
node-version: ">=20.6.0"
|
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- run: npm run verify
|
- run: npm run verify
|
||||||
|
@ -14,8 +14,6 @@ ENV APP_INFINITE_PLATINUM=false
|
|||||||
ENV APP_INFINITE_ENDO=false
|
ENV APP_INFINITE_ENDO=false
|
||||||
ENV APP_INFINITE_REGAL_AYA=false
|
ENV APP_INFINITE_REGAL_AYA=false
|
||||||
ENV APP_INFINITE_HELMINTH_MATERIALS=false
|
ENV APP_INFINITE_HELMINTH_MATERIALS=false
|
||||||
ENV APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS=false
|
|
||||||
ENV APP_DONT_SUBTRACT_VOIDTRACES=false
|
|
||||||
ENV APP_DONT_SUBTRACT_CONSUMABLES=false
|
ENV APP_DONT_SUBTRACT_CONSUMABLES=false
|
||||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
|
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
|
||||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
|
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
|
||||||
@ -32,8 +30,6 @@ ENV APP_NO_MASTERY_RANK_UP_COOLDOWN=false
|
|||||||
ENV APP_NO_VENDOR_PURCHASE_LIMITS=true
|
ENV APP_NO_VENDOR_PURCHASE_LIMITS=true
|
||||||
ENV APP_NO_DEATH_MARKS=false
|
ENV APP_NO_DEATH_MARKS=false
|
||||||
ENV APP_NO_KIM_COOLDOWNS=false
|
ENV APP_NO_KIM_COOLDOWNS=false
|
||||||
ENV APP_SYNDICATE_MISSIONS_REPEATABLE=false
|
|
||||||
ENV APP_INSTANT_FINISH_RIVEN_CHALLENGE=false
|
|
||||||
ENV APP_INSTANT_RESOURCE_EXTRACTOR_DRONES=false
|
ENV APP_INSTANT_RESOURCE_EXTRACTOR_DRONES=false
|
||||||
ENV APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE=false
|
ENV APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE=false
|
||||||
ENV APP_SKIP_CLAN_KEY_CRAFTING=false
|
ENV APP_SKIP_CLAN_KEY_CRAFTING=false
|
||||||
|
20
README.md
20
README.md
@ -14,22 +14,4 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
|
|||||||
|
|
||||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
||||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
||||||
- `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
|
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.
|
||||||
- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
|
|
||||||
- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
|
|
||||||
- `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9
|
|
||||||
- `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8
|
|
||||||
- `RadioLegionIntermission11Syndicate` for Nora's Mix Vol. 7
|
|
||||||
- `RadioLegionIntermission10Syndicate` for Nora's Mix Vol. 6
|
|
||||||
- `RadioLegionIntermission9Syndicate` for Nora's Mix Vol. 5
|
|
||||||
- `RadioLegionIntermission8Syndicate` for Nora's Mix Vol. 4
|
|
||||||
- `RadioLegionIntermission7Syndicate` for Nora's Mix Vol. 3
|
|
||||||
- `RadioLegionIntermission6Syndicate` for Nora's Mix Vol. 2
|
|
||||||
- `RadioLegionIntermission5Syndicate` for Nora's Mix Vol. 1
|
|
||||||
- `RadioLegionIntermission4Syndicate` for Nora's Choice
|
|
||||||
- `RadioLegionIntermission3Syndicate` for Intermission III
|
|
||||||
- `RadioLegion3Syndicate` for Glassmaker
|
|
||||||
- `RadioLegionIntermission2Syndicate` for Intermission II
|
|
||||||
- `RadioLegion2Syndicate` for The Emissary
|
|
||||||
- `RadioLegionIntermissionSyndicate` for Intermission I
|
|
||||||
- `RadioLegionSyndicate` for The Wolf of Saturn Six
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
echo Updating SpaceNinjaServer...
|
echo Updating SpaceNinjaServer...
|
||||||
git fetch --prune
|
git fetch --prune
|
||||||
git stash
|
git stash
|
||||||
git checkout -f origin/main
|
git reset --hard origin/main
|
||||||
|
|
||||||
if exist static\data\0\ (
|
if exist static\data\0\ (
|
||||||
echo Updating stripped assets...
|
echo Updating stripped assets...
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "Updating SpaceNinjaServer..."
|
|
||||||
git fetch --prune
|
|
||||||
git stash
|
|
||||||
git checkout -f origin/main
|
|
||||||
|
|
||||||
if [ -d "static/data/0/" ]; then
|
|
||||||
echo "Updating stripped assets..."
|
|
||||||
cd static/data/0/
|
|
||||||
git pull
|
|
||||||
cd ../../../
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Updating dependencies..."
|
|
||||||
npm i --omit=dev
|
|
||||||
|
|
||||||
npm run build
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
npm run start
|
|
||||||
echo "SpaceNinjaServer seems to have crashed."
|
|
||||||
fi
|
|
||||||
|
|
@ -19,8 +19,6 @@
|
|||||||
"infiniteEndo": false,
|
"infiniteEndo": false,
|
||||||
"infiniteRegalAya": false,
|
"infiniteRegalAya": false,
|
||||||
"infiniteHelminthMaterials": false,
|
"infiniteHelminthMaterials": false,
|
||||||
"claimingBlueprintRefundsIngredients": false,
|
|
||||||
"dontSubtractVoidTraces": false,
|
|
||||||
"dontSubtractConsumables": false,
|
"dontSubtractConsumables": false,
|
||||||
"unlockAllShipFeatures": false,
|
"unlockAllShipFeatures": false,
|
||||||
"unlockAllShipDecorations": false,
|
"unlockAllShipDecorations": false,
|
||||||
@ -38,8 +36,6 @@
|
|||||||
"noVendorPurchaseLimits": true,
|
"noVendorPurchaseLimits": true,
|
||||||
"noDeathMarks": false,
|
"noDeathMarks": false,
|
||||||
"noKimCooldowns": false,
|
"noKimCooldowns": false,
|
||||||
"syndicateMissionsRepeatable": false,
|
|
||||||
"instantFinishRivenChallenge": false,
|
|
||||||
"instantResourceExtractorDrones": false,
|
"instantResourceExtractorDrones": false,
|
||||||
"noResourceExtractorDronesDamage": false,
|
"noResourceExtractorDronesDamage": false,
|
||||||
"skipClanKeyCrafting": false,
|
"skipClanKeyCrafting": false,
|
||||||
@ -50,17 +46,11 @@
|
|||||||
"noDojoResearchTime": false,
|
"noDojoResearchTime": false,
|
||||||
"fastClanAscension": false,
|
"fastClanAscension": false,
|
||||||
"spoofMasteryRank": -1,
|
"spoofMasteryRank": -1,
|
||||||
"nightwaveStandingMultiplier": 1,
|
|
||||||
"worldState": {
|
"worldState": {
|
||||||
"creditBoost": false,
|
"creditBoost": false,
|
||||||
"affinityBoost": false,
|
"affinityBoost": false,
|
||||||
"resourceBoost": false,
|
"resourceBoost": false,
|
||||||
"starDays": true,
|
"starDays": true,
|
||||||
"eidolonOverride": "",
|
"lockTime": 0
|
||||||
"vallisOverride": "",
|
|
||||||
"nightwaveOverride": ""
|
|
||||||
},
|
|
||||||
"dev": {
|
|
||||||
"keepVendorsExpired": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,6 @@ services:
|
|||||||
# APP_INFINITE_ENDO: false
|
# APP_INFINITE_ENDO: false
|
||||||
# APP_INFINITE_REGAL_AYA: false
|
# APP_INFINITE_REGAL_AYA: false
|
||||||
# APP_INFINITE_HELMINTH_MATERIALS: false
|
# APP_INFINITE_HELMINTH_MATERIALS: false
|
||||||
# APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS: false
|
|
||||||
# APP_DONT_SUBTRACT_VOIDTRACES: false
|
|
||||||
# APP_DONT_SUBTRACT_CONSUMABLES: false
|
# APP_DONT_SUBTRACT_CONSUMABLES: false
|
||||||
# APP_UNLOCK_ALL_SHIP_FEATURES: false
|
# APP_UNLOCK_ALL_SHIP_FEATURES: false
|
||||||
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false
|
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false
|
||||||
@ -39,8 +37,6 @@ services:
|
|||||||
# APP_NO_VENDOR_PURCHASE_LIMITS: true
|
# APP_NO_VENDOR_PURCHASE_LIMITS: true
|
||||||
# APP_NO_DEATH_MARKS: false
|
# APP_NO_DEATH_MARKS: false
|
||||||
# APP_NO_KIM_COOLDOWNS: false
|
# APP_NO_KIM_COOLDOWNS: false
|
||||||
# APP_SYNDICATE_MISSIONS_REPEATABLE: false
|
|
||||||
# APP_INSTANT_FINISH_RIVEN_CHALLENGE: false
|
|
||||||
# APP_INSTANT_RESOURCE_EXTRACTOR_DRONES: false
|
# APP_INSTANT_RESOURCE_EXTRACTOR_DRONES: false
|
||||||
# APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE: false
|
# APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE: false
|
||||||
# APP_SKIP_CLAN_KEY_CRAFTING: false
|
# APP_SKIP_CLAN_KEY_CRAFTING: false
|
||||||
|
667
package-lock.json
generated
667
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -20,24 +20,28 @@
|
|||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
"crc-32": "^1.2.2",
|
"crc-32": "^1.2.2",
|
||||||
"express": "^5",
|
"express": "^5",
|
||||||
"json-with-bigint": "^3.4.4",
|
"json-with-bigint": "^3.2.2",
|
||||||
"mongoose": "^8.11.0",
|
"mongoose": "^8.11.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"typescript": "^5.5",
|
"typescript": "^5.5",
|
||||||
"warframe-public-export-plus": "^0.5.67",
|
"warframe-public-export-plus": "^0.5.59",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rxliuli/tsgo": "^2025.3.31",
|
||||||
"@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",
|
||||||
"@typescript/native-preview": "^7.0.0-dev.20250523.1",
|
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-plugin-prettier": "^5.2.5",
|
"eslint-plugin-prettier": "^5.2.5",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"tsconfig-paths": "^4.2.0"
|
"tsconfig-paths": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.15.0",
|
||||||
|
"npm": ">=9.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,6 @@ app.use((req, _res, next) => {
|
|||||||
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
|
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
|
||||||
req.headers["content-encoding"] = undefined;
|
req.headers["content-encoding"] = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// U18 uses application/x-www-form-urlencoded even tho the data is JSON which Express doesn't like.
|
|
||||||
// U17 sets no Content-Type at all, which Express also doesn't like.
|
|
||||||
if (!req.headers["content-type"] || req.headers["content-type"] == "application/x-www-form-urlencoded") {
|
|
||||||
req.headers["content-type"] = "application/octet-stream";
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import {
|
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||||
createVeiledRivenFingerprint,
|
|
||||||
createUnveiledRivenFingerprint,
|
|
||||||
rivenRawToRealWeighted
|
|
||||||
} from "@/src/helpers/rivenHelper";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addMods, getInventory } from "@/src/services/inventoryService";
|
import { addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getRandomElement } from "@/src/services/rngService";
|
import { getRandomElement } from "@/src/services/rngService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
|
|
||||||
export const activateRandomModController: RequestHandler = async (req, res) => {
|
export const activateRandomModController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -23,9 +18,7 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
|
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
|
||||||
const fingerprint = config.instantFinishRivenChallenge
|
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||||
? createUnveiledRivenFingerprint(ExportUpgrades[rivenType])
|
|
||||||
: createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
|
||||||
const upgradeIndex =
|
const upgradeIndex =
|
||||||
inventory.Upgrades.push({
|
inventory.Upgrades.push({
|
||||||
ItemType: rivenType,
|
ItemType: rivenType,
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
|
||||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const addFriendController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const payload = getJSONfromString<IAddFriendRequest>(String(req.body));
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
const newFriends: IFriendInfo[] = [];
|
|
||||||
if (payload.friend == "all") {
|
|
||||||
const [internalFriendships, externalFriendships] = await Promise.all([
|
|
||||||
Friendship.find({ owner: accountId }, "friend"),
|
|
||||||
Friendship.find({ friend: accountId }, "owner")
|
|
||||||
]);
|
|
||||||
for (const externalFriendship of externalFriendships) {
|
|
||||||
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
|
||||||
promises.push(
|
|
||||||
Friendship.insertOne({
|
|
||||||
owner: accountId,
|
|
||||||
friend: externalFriendship.owner,
|
|
||||||
Note: externalFriendship.Note // TOVERIFY: Should the note be copied when accepting a friend request?
|
|
||||||
}) as unknown as Promise<void>
|
|
||||||
);
|
|
||||||
newFriends.push({
|
|
||||||
_id: toOid(externalFriendship.owner)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const externalFriendship = await Friendship.findOne({ owner: payload.friend, friend: accountId }, "Note");
|
|
||||||
if (externalFriendship) {
|
|
||||||
promises.push(
|
|
||||||
Friendship.insertOne({
|
|
||||||
owner: accountId,
|
|
||||||
friend: payload.friend,
|
|
||||||
Note: externalFriendship.Note
|
|
||||||
}) as unknown as Promise<void>
|
|
||||||
);
|
|
||||||
newFriends.push({
|
|
||||||
_id: { $oid: payload.friend }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const newFriend of newFriends) {
|
|
||||||
promises.push(addAccountDataToFriendInfo(newFriend));
|
|
||||||
promises.push(addInventoryDataToFriendInfo(newFriend));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
res.json({
|
|
||||||
Friends: newFriends
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAddFriendRequest {
|
|
||||||
friend: string; // oid or "all" in which case all=1 is also a query parameter
|
|
||||||
}
|
|
@ -2,7 +2,7 @@ import { toOid } from "@/src/helpers/inventoryHelpers";
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Account, Ignore } from "@/src/models/loginModel";
|
import { Account, Ignore } from "@/src/models/loginModel";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
import { IFriendInfo } from "@/src/types/guildTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const addIgnoredUserController: RequestHandler = async (req, res) => {
|
export const addIgnoredUserController: RequestHandler = async (req, res) => {
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
|
||||||
import { Account } from "@/src/models/loginModel";
|
|
||||||
import { addInventoryDataToFriendInfo, areFriendsOfFriends } from "@/src/services/friendService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const addPendingFriendController: RequestHandler = async (req, res) => {
|
|
||||||
const payload = getJSONfromString<IAddPendingFriendRequest>(String(req.body));
|
|
||||||
|
|
||||||
const account = await Account.findOne({ DisplayName: payload.friend });
|
|
||||||
if (!account) {
|
|
||||||
res.status(400).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(account._id.toString(), "Settings");
|
|
||||||
if (
|
|
||||||
inventory.Settings?.FriendInvRestriction == "GIFT_MODE_NONE" ||
|
|
||||||
(inventory.Settings?.FriendInvRestriction == "GIFT_MODE_FRIENDS" &&
|
|
||||||
!(await areFriendsOfFriends(account._id, accountId)))
|
|
||||||
) {
|
|
||||||
res.status(400).send("Friend Invite Restriction");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Friendship.insertOne({
|
|
||||||
owner: accountId,
|
|
||||||
friend: account._id,
|
|
||||||
Note: payload.message
|
|
||||||
});
|
|
||||||
|
|
||||||
const friendInfo: IFriendInfo = {
|
|
||||||
_id: toOid(account._id),
|
|
||||||
DisplayName: account.DisplayName,
|
|
||||||
LastLogin: toMongoDate(account.LastLogin),
|
|
||||||
Note: payload.message
|
|
||||||
};
|
|
||||||
await addInventoryDataToFriendInfo(friendInfo);
|
|
||||||
res.json({
|
|
||||||
Friend: friendInfo
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAddPendingFriendRequest {
|
|
||||||
friend: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
|
||||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { getEffectiveAvatarImageType, getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { GuildPermission } from "@/src/types/guildTypes";
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
@ -75,7 +75,7 @@ export const addToAllianceController: RequestHandler = async (req, res) => {
|
|||||||
const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
|
const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
|
||||||
const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
|
const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
|
||||||
const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
|
const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
|
||||||
const alliance = (await Alliance.findById(req.query.allianceId as string, "Name"))!;
|
const alliance = (await Alliance.findById(req.query.allianceId, "Name"))!;
|
||||||
await createMessage(invitedClanOwnerMember.accountId, [
|
await createMessage(invitedClanOwnerMember.accountId, [
|
||||||
{
|
{
|
||||||
sndr: getSuffixedName(account),
|
sndr: getSuffixedName(account),
|
||||||
@ -95,7 +95,7 @@ export const addToAllianceController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
|
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
|
||||||
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||||
contextInfo: alliance._id.toString(),
|
contextInfo: alliance._id.toString(),
|
||||||
highPriority: true,
|
highPriority: true,
|
||||||
acceptAction: "ALLIANCE_INVITE",
|
acceptAction: "ALLIANCE_INVITE",
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { addInventoryDataToFriendInfo, areFriends } from "@/src/services/friendService";
|
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
|
||||||
import { hasGuildPermission } from "@/src/services/guildService";
|
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { getEffectiveAvatarImageType, getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
||||||
@ -24,18 +22,15 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderAccount = await getAccountForRequest(req);
|
|
||||||
const inventory = await getInventory(account._id.toString(), "Settings");
|
const inventory = await getInventory(account._id.toString(), "Settings");
|
||||||
if (
|
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
||||||
inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE" ||
|
if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
|
||||||
(inventory.Settings?.GuildInvRestriction == "GIFT_MODE_FRIENDS" &&
|
|
||||||
!(await areFriends(account._id, senderAccount._id)))
|
|
||||||
) {
|
|
||||||
res.status(400).json("Invite restricted");
|
res.status(400).json("Invite restricted");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
|
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
|
||||||
|
const senderAccount = await getAccountForRequest(req);
|
||||||
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
|
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
|
||||||
res.status(400).json("Invalid permission");
|
res.status(400).json("Invalid permission");
|
||||||
}
|
}
|
||||||
@ -64,7 +59,7 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
||||||
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||||
contextInfo: payload.GuildId.$oid,
|
contextInfo: payload.GuildId.$oid,
|
||||||
highPriority: true,
|
highPriority: true,
|
||||||
acceptAction: "GUILD_INVITE",
|
acceptAction: "GUILD_INVITE",
|
||||||
@ -76,11 +71,10 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
|||||||
const member: IGuildMemberClient = {
|
const member: IGuildMemberClient = {
|
||||||
_id: { $oid: account._id.toString() },
|
_id: { $oid: account._id.toString() },
|
||||||
DisplayName: account.DisplayName,
|
DisplayName: account.DisplayName,
|
||||||
LastLogin: toMongoDate(account.LastLogin),
|
|
||||||
Rank: 7,
|
Rank: 7,
|
||||||
Status: 2
|
Status: 2
|
||||||
};
|
};
|
||||||
await addInventoryDataToFriendInfo(member);
|
await fillInInventoryDataForGuildMember(member);
|
||||||
res.json({ NewMember: member });
|
res.json({ NewMember: member });
|
||||||
} else if ("RequestMsg" in payload) {
|
} else if ("RequestMsg" in payload) {
|
||||||
// Player applying to join a clan
|
// Player applying to join a clan
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const adoptPetController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId, "KubrowPets");
|
|
||||||
const data = getJSONfromString<IAdoptPetRequest>(String(req.body));
|
|
||||||
const details = inventory.KubrowPets.id(data.petId)!.Details!;
|
|
||||||
details.Name = data.name;
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
petId: data.petId,
|
|
||||||
newName: data.name
|
|
||||||
} satisfies IAdoptPetResponse);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAdoptPetRequest {
|
|
||||||
petId: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IAdoptPetResponse {
|
|
||||||
petId: string;
|
|
||||||
newName: string;
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||||
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||||
import { IUpgradeFromClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
payload.Consumed.forEach(upgrade => {
|
payload.Consumed.forEach(upgrade => {
|
||||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||||
});
|
});
|
||||||
|
|
||||||
const rawRivenType = getRandomRawRivenType();
|
const rawRivenType = getRandomRawRivenType();
|
||||||
@ -57,8 +57,8 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
|||||||
payload.Consumed.forEach(upgrade => {
|
payload.Consumed.forEach(upgrade => {
|
||||||
const meta = ExportUpgrades[upgrade.ItemType];
|
const meta = ExportUpgrades[upgrade.ItemType];
|
||||||
counts[meta.rarity] += upgrade.ItemCount;
|
counts[meta.rarity] += upgrade.ItemCount;
|
||||||
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
|
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
||||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||||
} else {
|
} else {
|
||||||
addMods(inventory, [
|
addMods(inventory, [
|
||||||
{
|
{
|
||||||
@ -128,14 +128,24 @@ const getRandomRawRivenType = (): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IArtifactTransmutationRequest {
|
interface IArtifactTransmutationRequest {
|
||||||
Upgrade: IUpgradeFromClient;
|
Upgrade: IAgnosticUpgradeClient;
|
||||||
LevelDiff: number;
|
LevelDiff: number;
|
||||||
Consumed: IUpgradeFromClient[];
|
Consumed: IAgnosticUpgradeClient[];
|
||||||
Cost: number;
|
Cost: number;
|
||||||
FusionPointCost: number;
|
FusionPointCost: number;
|
||||||
RivenTransmute?: boolean;
|
RivenTransmute?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IAgnosticUpgradeClient {
|
||||||
|
ItemType: string;
|
||||||
|
ItemId: IOid;
|
||||||
|
FromSKU: boolean;
|
||||||
|
UpgradeFingerprint: string;
|
||||||
|
PendingRerollFingerprint: string;
|
||||||
|
ItemCount: number;
|
||||||
|
LastAdded: IOid;
|
||||||
|
}
|
||||||
|
|
||||||
const specialModSets: string[][] = [
|
const specialModSets: string[][] = [
|
||||||
[
|
[
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
import { IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import {
|
import {
|
||||||
getInventory,
|
getInventory,
|
||||||
updateCurrency,
|
updateCurrency,
|
||||||
@ -17,11 +17,8 @@ import {
|
|||||||
} 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 { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { InventorySlot, IPendingRecipeDatabase, Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { IRecipe } from "warframe-public-export-plus";
|
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
|
|
||||||
interface IClaimCompletedRecipeRequest {
|
interface IClaimCompletedRecipeRequest {
|
||||||
RecipeIds: IOid[];
|
RecipeIds: IOid[];
|
||||||
@ -29,8 +26,10 @@ interface IClaimCompletedRecipeRequest {
|
|||||||
|
|
||||||
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
||||||
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(account._id.toString());
|
if (!accountId) throw new Error("no account id");
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
||||||
if (!pendingRecipe) {
|
if (!pendingRecipe) {
|
||||||
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
||||||
@ -49,107 +48,9 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.query.cancel) {
|
if (req.query.cancel) {
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
const inventoryChanges: IInventoryChanges = {
|
||||||
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
|
...updateCurrency(inventory, recipe.buildPrice * -1, false)
|
||||||
await inventory.save();
|
|
||||||
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
|
||||||
} else {
|
|
||||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
|
||||||
|
|
||||||
let BrandedSuits: undefined | IOidWithLegacySupport[];
|
|
||||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
|
||||||
inventory.PendingSpectreLoadouts ??= [];
|
|
||||||
inventory.SpectreLoadouts ??= [];
|
|
||||||
|
|
||||||
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
|
|
||||||
x => x.ItemType == recipe.resultType
|
|
||||||
);
|
|
||||||
if (pendingLoadoutIndex != -1) {
|
|
||||||
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
|
|
||||||
if (loadoutIndex != -1) {
|
|
||||||
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
|
|
||||||
}
|
|
||||||
logger.debug(
|
|
||||||
"moving spectre loadout from pending to active",
|
|
||||||
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
|
|
||||||
);
|
|
||||||
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
|
||||||
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
|
||||||
}
|
|
||||||
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
|
|
||||||
inventory.BrandedSuits!.splice(
|
|
||||||
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
|
|
||||||
}
|
|
||||||
|
|
||||||
let InventoryChanges: IInventoryChanges = {};
|
|
||||||
if (recipe.consumeOnUse) {
|
|
||||||
addRecipes(inventory, [
|
|
||||||
{
|
|
||||||
ItemType: pendingRecipe.ItemType,
|
|
||||||
ItemCount: -1
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (req.query.rush) {
|
|
||||||
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
|
|
||||||
const start = end - recipe.buildTime;
|
|
||||||
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
|
|
||||||
const progress = secondsElapsed / recipe.buildTime;
|
|
||||||
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
|
|
||||||
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
|
|
||||||
InventoryChanges = {
|
|
||||||
...InventoryChanges,
|
|
||||||
...updateCurrency(inventory, cost, true)
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
|
|
||||||
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
|
|
||||||
if (pet.Details!.HatchDate!.getTime() > Date.now()) {
|
|
||||||
pet.Details!.HatchDate = new Date();
|
|
||||||
}
|
|
||||||
let canSetActive = true;
|
|
||||||
for (const pet of inventory.KubrowPets) {
|
|
||||||
if (pet.Details!.Status == Status.StatusAvailable) {
|
|
||||||
canSetActive = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
|
|
||||||
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
|
||||||
InventoryChanges = {
|
|
||||||
...InventoryChanges,
|
|
||||||
...(await addItem(
|
|
||||||
inventory,
|
|
||||||
recipe.resultType,
|
|
||||||
recipe.num,
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
pendingRecipe.TargetFingerprint
|
|
||||||
))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
config.claimingBlueprintRefundsIngredients &&
|
|
||||||
recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
|
|
||||||
) {
|
|
||||||
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
|
|
||||||
}
|
|
||||||
await inventory.save();
|
|
||||||
res.json({ InventoryChanges, BrandedSuits });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const refundRecipeIngredients = async (
|
|
||||||
inventory: TInventoryDatabaseDocument,
|
|
||||||
inventoryChanges: IInventoryChanges,
|
|
||||||
recipe: IRecipe,
|
|
||||||
pendingRecipe: IPendingRecipeDatabase
|
|
||||||
): Promise<void> => {
|
|
||||||
updateCurrency(inventory, recipe.buildPrice * -1, false, inventoryChanges);
|
|
||||||
|
|
||||||
const equipmentIngredients = new Set();
|
const equipmentIngredients = new Set();
|
||||||
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
||||||
@ -174,4 +75,75 @@ const refundRecipeIngredients = async (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||||
|
} else {
|
||||||
|
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||||
|
|
||||||
|
let BrandedSuits: undefined | IOid[];
|
||||||
|
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||||
|
inventory.PendingSpectreLoadouts ??= [];
|
||||||
|
inventory.SpectreLoadouts ??= [];
|
||||||
|
|
||||||
|
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
|
||||||
|
x => x.ItemType == recipe.resultType
|
||||||
|
);
|
||||||
|
if (pendingLoadoutIndex != -1) {
|
||||||
|
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
|
||||||
|
if (loadoutIndex != -1) {
|
||||||
|
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
|
||||||
|
}
|
||||||
|
logger.debug(
|
||||||
|
"moving spectre loadout from pending to active",
|
||||||
|
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
|
||||||
|
);
|
||||||
|
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
||||||
|
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
||||||
|
}
|
||||||
|
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
|
||||||
|
inventory.BrandedSuits!.splice(
|
||||||
|
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
|
||||||
|
}
|
||||||
|
|
||||||
|
let InventoryChanges: IInventoryChanges = {};
|
||||||
|
if (recipe.consumeOnUse) {
|
||||||
|
addRecipes(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: pendingRecipe.ItemType,
|
||||||
|
ItemCount: -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (req.query.rush) {
|
||||||
|
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
|
||||||
|
const start = end - recipe.buildTime;
|
||||||
|
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
|
||||||
|
const progress = secondsElapsed / recipe.buildTime;
|
||||||
|
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
|
||||||
|
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
|
||||||
|
InventoryChanges = {
|
||||||
|
...InventoryChanges,
|
||||||
|
...updateCurrency(inventory, cost, true)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
||||||
|
InventoryChanges = {
|
||||||
|
...InventoryChanges,
|
||||||
|
...(await addItem(
|
||||||
|
inventory,
|
||||||
|
recipe.resultType,
|
||||||
|
recipe.num,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
pendingRecipe.TargetFingerprint
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.json({ InventoryChanges, BrandedSuits });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -62,7 +62,7 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
|
|||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...(await getGuildClient(guild, account)),
|
...(await getGuildClient(guild, account._id.toString())),
|
||||||
InventoryChanges: inventoryChanges
|
InventoryChanges: inventoryChanges
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { createUniqueClanName, getGuildClient, giveClanKey } from "@/src/services/guildService";
|
import { createUniqueClanName, getGuildClient, giveClanKey } from "@/src/services/guildService";
|
||||||
@ -7,11 +7,11 @@ import { getInventory } from "@/src/services/inventoryService";
|
|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
export const createGuildController: RequestHandler = async (req, res) => {
|
export const createGuildController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
|
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
|
||||||
|
|
||||||
// Remove pending applications for this account
|
// Remove pending applications for this account
|
||||||
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
await GuildMember.deleteMany({ accountId, status: 1 });
|
||||||
|
|
||||||
// Create guild on database
|
// Create guild on database
|
||||||
const guild = new Guild({
|
const guild = new Guild({
|
||||||
@ -21,20 +21,20 @@ export const createGuildController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
// Create guild member on database
|
// Create guild member on database
|
||||||
await GuildMember.insertOne({
|
await GuildMember.insertOne({
|
||||||
accountId: account._id,
|
accountId: accountId,
|
||||||
guildId: guild._id,
|
guildId: guild._id,
|
||||||
status: 0,
|
status: 0,
|
||||||
rank: 0
|
rank: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
const inventory = await getInventory(accountId, "GuildId LevelKeys Recipes");
|
||||||
inventory.GuildId = guild._id;
|
inventory.GuildId = guild._id;
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
giveClanKey(inventory, inventoryChanges);
|
giveClanKey(inventory, inventoryChanges);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...(await getGuildClient(guild, account)),
|
...(await getGuildClient(guild, accountId)),
|
||||||
InventoryChanges: inventoryChanges
|
InventoryChanges: inventoryChanges
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
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 { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
@ -8,15 +7,8 @@ import { Types } from "mongoose";
|
|||||||
|
|
||||||
export const crewMembersController: RequestHandler = async (req, res) => {
|
export const crewMembersController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId, "CrewMembers NemesisHistory");
|
const inventory = await getInventory(accountId, "CrewMembers");
|
||||||
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
||||||
if (data.crewMember.SecondInCommand) {
|
|
||||||
clearOnCall(inventory);
|
|
||||||
}
|
|
||||||
if (data.crewMember.ItemId.$oid == "000000000000000000000000") {
|
|
||||||
const convertedNemesis = inventory.NemesisHistory!.find(x => x.fp == data.crewMember.NemesisFingerprint)!;
|
|
||||||
convertedNemesis.SecondInCommand = data.crewMember.SecondInCommand;
|
|
||||||
} else {
|
|
||||||
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
||||||
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
||||||
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
||||||
@ -24,7 +16,6 @@ export const crewMembersController: RequestHandler = async (req, res) => {
|
|||||||
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
||||||
dbCrewMember.Configs = data.crewMember.Configs;
|
dbCrewMember.Configs = data.crewMember.Configs;
|
||||||
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
||||||
}
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
crewMemberId: data.crewMember.ItemId.$oid,
|
crewMemberId: data.crewMember.ItemId.$oid,
|
||||||
@ -35,20 +26,3 @@ export const crewMembersController: RequestHandler = async (req, res) => {
|
|||||||
interface ICrewMembersRequest {
|
interface ICrewMembersRequest {
|
||||||
crewMember: ICrewMemberClient;
|
crewMember: ICrewMemberClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearOnCall = (inventory: TInventoryDatabaseDocument): void => {
|
|
||||||
for (const cm of inventory.CrewMembers) {
|
|
||||||
if (cm.SecondInCommand) {
|
|
||||||
cm.SecondInCommand = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inventory.NemesisHistory) {
|
|
||||||
for (const cm of inventory.NemesisHistory) {
|
|
||||||
if (cm.SecondInCommand) {
|
|
||||||
cm.SecondInCommand = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -1,529 +1,60 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { IEndlessXpReward, IInventoryClient, TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { ExportRewards, ICountedStoreItem } from "warframe-public-export-plus";
|
|
||||||
import { getRandomElement } from "@/src/services/rngService";
|
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
|
|
||||||
export const endlessXpController: RequestHandler = async (req, res) => {
|
export const endlessXpController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
||||||
if (payload.Mode == "r") {
|
|
||||||
const inventory = await getInventory(accountId, "EndlessXP");
|
|
||||||
inventory.EndlessXP ??= [];
|
inventory.EndlessXP ??= [];
|
||||||
let entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||||
if (!entry) {
|
if (entry) {
|
||||||
entry = {
|
entry.Choices = payload.Choices;
|
||||||
|
} else {
|
||||||
|
inventory.EndlessXP.push({
|
||||||
|
Category: payload.Category,
|
||||||
|
Choices: payload.Choices
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
NewProgress: {
|
||||||
Category: payload.Category,
|
Category: payload.Category,
|
||||||
Earn: 0,
|
Earn: 0,
|
||||||
Claim: 0,
|
Claim: 0,
|
||||||
|
BonusAvailable: {
|
||||||
|
$date: {
|
||||||
|
$numberLong: "9999999999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Expiry: {
|
||||||
|
$date: {
|
||||||
|
$numberLong: "9999999999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
Choices: payload.Choices,
|
Choices: payload.Choices,
|
||||||
PendingRewards: []
|
PendingRewards: [
|
||||||
};
|
|
||||||
inventory.EndlessXP.push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
const weekStart = 1734307200_000 + Math.trunc((Date.now() - 1734307200_000) / 604800000) * 604800000;
|
|
||||||
const weekEnd = weekStart + 604800000;
|
|
||||||
|
|
||||||
entry.Earn = 0;
|
|
||||||
entry.Claim = 0;
|
|
||||||
entry.BonusAvailable = new Date(weekStart);
|
|
||||||
entry.Expiry = new Date(weekEnd);
|
|
||||||
entry.Choices = payload.Choices;
|
|
||||||
entry.PendingRewards =
|
|
||||||
payload.Category == "EXC_HARD"
|
|
||||||
? generateHardModeRewards(payload.Choices)
|
|
||||||
: generateNormalModeRewards(payload.Choices);
|
|
||||||
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
NewProgress: inventory.toJSON<IInventoryClient>().EndlessXP!.find(x => x.Category == payload.Category)!
|
|
||||||
});
|
|
||||||
} else if (payload.Mode == "c") {
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const entry = inventory.EndlessXP!.find(x => x.Category == payload.Category)!;
|
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
|
||||||
for (const reward of entry.PendingRewards) {
|
|
||||||
if (entry.Claim < reward.RequiredTotalXp && reward.RequiredTotalXp <= entry.Earn) {
|
|
||||||
combineInventoryChanges(
|
|
||||||
inventoryChanges,
|
|
||||||
(
|
|
||||||
await handleStoreItemAcquisition(
|
|
||||||
reward.Rewards[0].StoreItem,
|
|
||||||
inventory,
|
|
||||||
reward.Rewards[0].ItemCount
|
|
||||||
)
|
|
||||||
).InventoryChanges
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry.Claim = entry.Earn;
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
InventoryChanges: inventoryChanges,
|
|
||||||
ClaimedXp: entry.Claim
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
|
||||||
throw new Error(`unexpected endlessXp mode: ${payload.Mode}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type IEndlessXpRequest =
|
|
||||||
| {
|
|
||||||
Mode: "r";
|
|
||||||
Category: TEndlessXpCategory;
|
|
||||||
Choices: string[];
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
Mode: "c" | "something else";
|
|
||||||
Category: TEndlessXpCategory;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateRandomRewards = (deckName: string): ICountedStoreItem[] => {
|
|
||||||
const reward = getRandomElement(ExportRewards[deckName][0])!;
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
StoreItem: reward.type,
|
|
||||||
ItemCount: reward.itemCount
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalModeChosenRewards: Record<string, string[]> = {
|
|
||||||
Excalibur: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Excalibur/RadialJavelinAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburBlueprint"
|
|
||||||
],
|
|
||||||
Trinity: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Trinity/EnergyVampireAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinitySystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityBlueprint"
|
|
||||||
],
|
|
||||||
Ember: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Ember/WorldOnFireAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberBlueprint"
|
|
||||||
],
|
|
||||||
Loki: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Loki/InvisibilityAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKISystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIBlueprint"
|
|
||||||
],
|
|
||||||
Mag: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Mag/CrushAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagBlueprint"
|
|
||||||
],
|
|
||||||
Rhino: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Rhino/RhinoChargeAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoBlueprint"
|
|
||||||
],
|
|
||||||
Ash: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Ninja/GlaiveAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshBlueprint"
|
|
||||||
],
|
|
||||||
Frost: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Frost/IceShieldAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostBlueprint"
|
|
||||||
],
|
|
||||||
Nyx: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Jade/SelfBulletAttractorAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxBlueprint"
|
|
||||||
],
|
|
||||||
Saryn: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Saryn/PoisonAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynBlueprint"
|
|
||||||
],
|
|
||||||
Vauban: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Trapper/LevTrapAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperBlueprint"
|
|
||||||
],
|
|
||||||
Nova: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/AntiMatter/MolecularPrimeAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaBlueprint"
|
|
||||||
],
|
|
||||||
Nekros: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Necro/CloneTheDeadAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroBlueprint"
|
|
||||||
],
|
|
||||||
Valkyr: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Berserker/IntimidateAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerBlueprint"
|
|
||||||
],
|
|
||||||
Oberon: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Paladin/RegenerationAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinBlueprint"
|
|
||||||
],
|
|
||||||
Hydroid: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Pirate/CannonBarrageAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidBlueprint"
|
|
||||||
],
|
|
||||||
Mirage: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Harlequin/LightAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinBlueprint"
|
|
||||||
],
|
|
||||||
Limbo: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Magician/TearInSpaceAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianBlueprint"
|
|
||||||
],
|
|
||||||
Mesa: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Cowgirl/GunFuPvPAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerBlueprint"
|
|
||||||
],
|
|
||||||
Chroma: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Dragon/DragonLuckAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaBlueprint"
|
|
||||||
],
|
|
||||||
Atlas: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Brawler/BrawlerPassiveAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerBlueprint"
|
|
||||||
],
|
|
||||||
Ivara: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Ranger/RangerStealAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerBlueprint"
|
|
||||||
],
|
|
||||||
Inaros: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Sandman/SandmanSwarmAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummySystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyBlueprint"
|
|
||||||
],
|
|
||||||
Titania: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Fairy/FairyFlightAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairySystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyBlueprint"
|
|
||||||
],
|
|
||||||
Nidus: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Infestation/InfestPodsAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusBlueprint"
|
|
||||||
],
|
|
||||||
Octavia: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Bard/BardCharmAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaBlueprint"
|
|
||||||
],
|
|
||||||
Harrow: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Priest/PriestPactAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestBlueprint"
|
|
||||||
],
|
|
||||||
Gara: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Glass/GlassFragmentAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassBlueprint"
|
|
||||||
],
|
|
||||||
Khora: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Khora/KhoraCrackAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraBlueprint"
|
|
||||||
],
|
|
||||||
Revenant: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Revenant/RevenantMarkAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantBlueprint"
|
|
||||||
],
|
|
||||||
Garuda: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Garuda/GarudaUnstoppableAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaBlueprint"
|
|
||||||
],
|
|
||||||
Baruuk: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Pacifist/PacifistFistAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistBlueprint"
|
|
||||||
],
|
|
||||||
Hildryn: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/IronFrame/IronFrameStripAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeBlueprint"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateNormalModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
|
||||||
const choiceRewards = normalModeChosenRewards[choices[0]];
|
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
RequiredTotalXp: 190,
|
RequiredTotalXp: 190,
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 400,
|
|
||||||
Rewards: [
|
Rewards: [
|
||||||
{
|
{
|
||||||
StoreItem: choiceRewards[0],
|
StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 630,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 890,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalMODRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1190,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: choiceRewards[1],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1540,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalGoldRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1950,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: choiceRewards[2],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 2430,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: choiceRewards[3],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 2990,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalArcaneRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 3640,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: choiceRewards[4],
|
|
||||||
ItemCount: 1
|
ItemCount: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
// ...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hardModeChosenRewards: Record<string, string> = {
|
interface IEndlessXpRequest {
|
||||||
Braton: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BratonIncarnonUnlocker",
|
Mode: string; // "r"
|
||||||
Lato: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LatoIncarnonUnlocker",
|
Category: TEndlessXpCategory;
|
||||||
Skana: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SkanaIncarnonUnlocker",
|
Choices: string[];
|
||||||
Paris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ParisIncarnonUnlocker",
|
}
|
||||||
Kunai: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/KunaiIncarnonUnlocker",
|
|
||||||
Boar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoarIncarnonUnlocker",
|
|
||||||
Gammacor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/GammacorIncarnonUnlocker",
|
|
||||||
Anku: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AnkuIncarnonUnlocker",
|
|
||||||
Gorgon: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/GorgonIncarnonUnlocker",
|
|
||||||
Angstrum: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AngstrumIncarnonUnlocker",
|
|
||||||
Bo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/BoIncarnonUnlocker",
|
|
||||||
Latron: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/LatronIncarnonUnlocker",
|
|
||||||
Furis: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/FurisIncarnonUnlocker",
|
|
||||||
Furax: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/FuraxIncarnonUnlocker",
|
|
||||||
Strun: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/StrunIncarnonUnlocker",
|
|
||||||
Lex: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LexIncarnonUnlocker",
|
|
||||||
Magistar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/MagistarIncarnonUnlocker",
|
|
||||||
Boltor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoltorIncarnonUnlocker",
|
|
||||||
Bronco: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/BroncoIncarnonUnlocker",
|
|
||||||
CeramicDagger: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/CeramicDaggerIncarnonUnlocker",
|
|
||||||
Torid: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ToridIncarnonUnlocker",
|
|
||||||
DualToxocyst: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DualToxocystIncarnonUnlocker",
|
|
||||||
DualIchor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/DualIchorIncarnonUnlocker",
|
|
||||||
Miter: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/MiterIncarnonUnlocker",
|
|
||||||
Atomos: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AtomosIncarnonUnlocker",
|
|
||||||
AckAndBrunt: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AckAndBruntIncarnonUnlocker",
|
|
||||||
Soma: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SomaIncarnonUnlocker",
|
|
||||||
Vasto: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/VastoIncarnonUnlocker",
|
|
||||||
NamiSolo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/NamiSoloIncarnonUnlocker",
|
|
||||||
Burston: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BurstonIncarnonUnlocker",
|
|
||||||
Zylok: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/ZylokIncarnonUnlocker",
|
|
||||||
Sibear: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SibearIncarnonUnlocker",
|
|
||||||
Dread: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DreadIncarnonUnlocker",
|
|
||||||
Despair: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DespairIncarnonUnlocker",
|
|
||||||
Hate: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/HateIncarnonUnlocker",
|
|
||||||
Dera: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DeraIncarnonUnlocker",
|
|
||||||
Cestra: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/CestraIncarnonUnlocker",
|
|
||||||
Okina: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/OkinaIncarnonUnlocker",
|
|
||||||
Sybaris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SybarisIncarnonUnlocker",
|
|
||||||
Sicarus: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/SicarusIncarnonUnlocker",
|
|
||||||
RivenPrimary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
|
|
||||||
RivenSecondary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawPistolRandomMod",
|
|
||||||
RivenMelee: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawMeleeRandomMod",
|
|
||||||
Kuva: "/Lotus/Types/Game/DuviriEndless/CircuitSteelPathBIGKuvaReward"
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateHardModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 285,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 600,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 945,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1335,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1785,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: hardModeChosenRewards[choices[0]],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 2310,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 2925,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 3645,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 4485,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSteelEssenceRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 5460,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: hardModeChosenRewards[choices[1]],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
@ -30,14 +30,15 @@ export const fishmongerController: RequestHandler = async (req, res) => {
|
|||||||
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
|
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
|
||||||
}
|
}
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
if (gainedStanding && syndicateTag) addStanding(inventory, syndicateTag, gainedStanding);
|
let affiliationMod;
|
||||||
|
if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: {
|
InventoryChanges: {
|
||||||
MiscItems: miscItemChanges
|
MiscItems: miscItemChanges
|
||||||
},
|
},
|
||||||
SyndicateTag: syndicateTag,
|
SyndicateTag: syndicateTag,
|
||||||
StandingChange: gainedStanding
|
StandingChange: affiliationMod?.Standing || 0
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,9 +64,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json({
|
res.end();
|
||||||
FocusUpgrade: { ItemType: focusType }
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FocusOperation.UnlockUpgrade: {
|
case FocusOperation.UnlockUpgrade: {
|
||||||
|
@ -1,54 +1,15 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { Request, Response } from "express";
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
|
||||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
|
||||||
import { Request, RequestHandler, Response } from "express";
|
|
||||||
|
|
||||||
// POST with {} instead of GET as of 38.5.0
|
// POST with {} instead of GET as of 38.5.0
|
||||||
export const getFriendsController: RequestHandler = async (req: Request, res: Response) => {
|
const getFriendsController = (_request: Request, response: Response): void => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
response.writeHead(200, {
|
||||||
const response: IGetFriendsResponse = {
|
//Connection: "keep-alive",
|
||||||
Current: [],
|
//"Content-Encoding": "gzip",
|
||||||
IncomingFriendRequests: [],
|
"Content-Type": "text/html",
|
||||||
OutgoingFriendRequests: []
|
// charset: "UTF - 8",
|
||||||
};
|
"Content-Length": "3"
|
||||||
const [internalFriendships, externalFriendships] = await Promise.all([
|
|
||||||
Friendship.find({ owner: accountId }),
|
|
||||||
Friendship.find({ friend: accountId }, "owner Note")
|
|
||||||
]);
|
|
||||||
for (const externalFriendship of externalFriendships) {
|
|
||||||
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
|
||||||
response.IncomingFriendRequests.push({
|
|
||||||
_id: toOid(externalFriendship.owner),
|
|
||||||
Note: externalFriendship.Note
|
|
||||||
});
|
});
|
||||||
}
|
response.end(Buffer.from([0x7b, 0x7d, 0x0a]));
|
||||||
}
|
|
||||||
for (const internalFriendship of internalFriendships) {
|
|
||||||
const friendInfo: IFriendInfo = {
|
|
||||||
_id: toOid(internalFriendship.friend)
|
|
||||||
};
|
|
||||||
if (externalFriendships.find(x => x.owner.equals(internalFriendship.friend))) {
|
|
||||||
response.Current.push(friendInfo);
|
|
||||||
} else {
|
|
||||||
response.OutgoingFriendRequests.push(friendInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
for (const arr of Object.values(response)) {
|
|
||||||
for (const friendInfo of arr) {
|
|
||||||
promises.push(addAccountDataToFriendInfo(friendInfo));
|
|
||||||
promises.push(addInventoryDataToFriendInfo(friendInfo));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
res.json(response);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// interface IGetFriendsResponse {
|
export { getFriendsController };
|
||||||
// Current: IFriendInfo[];
|
|
||||||
// IncomingFriendRequests: IFriendInfo[];
|
|
||||||
// OutgoingFriendRequests: IFriendInfo[];
|
|
||||||
// }
|
|
||||||
type IGetFriendsResponse = Record<"Current" | "IncomingFriendRequests" | "OutgoingFriendRequests", IFriendInfo[]>;
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
||||||
|
|
||||||
export const getGuildController: RequestHandler = async (req, res) => {
|
export const getGuildController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(account._id.toString(), "GuildId");
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
if (inventory.GuildId) {
|
if (inventory.GuildId) {
|
||||||
const guild = await Guild.findById(inventory.GuildId);
|
const guild = await Guild.findById(inventory.GuildId);
|
||||||
if (guild) {
|
if (guild) {
|
||||||
@ -24,7 +24,7 @@ export const getGuildController: RequestHandler = async (req, res) => {
|
|||||||
guild.CeremonyResetDate = undefined;
|
guild.CeremonyResetDate = undefined;
|
||||||
await guild.save();
|
await guild.save();
|
||||||
}
|
}
|
||||||
res.json(await getGuildClient(guild, account));
|
res.json(await getGuildClient(guild, accountId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { RequestHandler } from "express";
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { getDojoClient } from "@/src/services/guildService";
|
import { getDojoClient } from "@/src/services/guildService";
|
||||||
import { Account } from "@/src/models/loginModel";
|
|
||||||
|
|
||||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||||
const guildId = req.query.guildId as string;
|
const guildId = req.query.guildId as string;
|
||||||
@ -26,8 +25,7 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
|
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
|
||||||
const account = await Account.findById(req.query.accountId as string);
|
res.json(await getDojoClient(guild, 0, payload.ComponentId));
|
||||||
res.json(await getDojoClient(guild, 0, payload.ComponentId, account?.BuildLabel));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IGetGuildDojoRequest {
|
interface IGetGuildDojoRequest {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { Account, Ignore } from "@/src/models/loginModel";
|
import { Account, Ignore } from "@/src/models/loginModel";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
import { IFriendInfo } from "@/src/types/guildTypes";
|
||||||
import { parallelForeach } from "@/src/utils/async-utils";
|
import { parallelForeach } from "@/src/utils/async-utils";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ 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 { getShip } from "@/src/services/shipService";
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
import { IGetShipResponse } from "@/src/types/shipTypes";
|
||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||||
@ -20,6 +21,7 @@ export const getShipController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||||
const loadout = await getLoadout(accountId);
|
const loadout = await getLoadout(accountId);
|
||||||
|
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
|
||||||
|
|
||||||
const getShipResponse: IGetShipResponse = {
|
const getShipResponse: IGetShipResponse = {
|
||||||
ShipOwnerId: accountId,
|
ShipOwnerId: accountId,
|
||||||
@ -29,8 +31,8 @@ export const getShipController: RequestHandler = async (req, res) => {
|
|||||||
ShipId: toOid(personalRoomsDb.activeShipId),
|
ShipId: toOid(personalRoomsDb.activeShipId),
|
||||||
ShipInterior: {
|
ShipInterior: {
|
||||||
Colors: personalRooms.ShipInteriorColors,
|
Colors: personalRooms.ShipInteriorColors,
|
||||||
ShipAttachments: { HOOD_ORNAMENT: "" },
|
ShipAttachments: ship.ShipAttachments,
|
||||||
SkinFlavourItem: ""
|
SkinFlavourItem: ship.SkinFlavourItem
|
||||||
},
|
},
|
||||||
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
||||||
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
||||||
|
@ -1,29 +1,14 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
|
|
||||||
export const getVendorInfoController: RequestHandler = async (req, res) => {
|
export const getVendorInfoController: RequestHandler = (req, res) => {
|
||||||
let manifest = getVendorManifestByTypeName(req.query.vendor as string);
|
if (typeof req.query.vendor == "string") {
|
||||||
|
const manifest = getVendorManifestByTypeName(req.query.vendor);
|
||||||
if (!manifest) {
|
if (!manifest) {
|
||||||
throw new Error(`Unknown vendor: ${req.query.vendor as string}`);
|
throw new Error(`Unknown vendor: ${req.query.vendor}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For testing purposes, authenticating with this endpoint is optional here, but would be required on live.
|
|
||||||
if (req.query.accountId) {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
manifest = applyStandingToVendorManifest(inventory, manifest);
|
|
||||||
if (config.dev?.keepVendorsExpired) {
|
|
||||||
manifest = {
|
|
||||||
VendorInfo: {
|
|
||||||
...manifest.VendorInfo,
|
|
||||||
Expiry: { $date: { $numberLong: "0" } }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(manifest);
|
res.json(manifest);
|
||||||
|
} else {
|
||||||
|
res.status(400).end();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { areFriends } from "@/src/services/friendService";
|
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import {
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
combineInventoryChanges,
|
|
||||||
getEffectiveAvatarImageType,
|
|
||||||
getInventory,
|
|
||||||
updateCurrency
|
|
||||||
} from "@/src/services/inventoryService";
|
|
||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { IInventoryChanges, IPurchaseParams } from "@/src/types/purchaseTypes";
|
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
|
||||||
export const giftingController: RequestHandler = async (req, res) => {
|
export const giftingController: RequestHandler = async (req, res) => {
|
||||||
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
||||||
@ -37,11 +30,8 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cannot gift to players who have gifting disabled.
|
// Cannot gift to players who have gifting disabled.
|
||||||
const senderAccount = await getAccountForRequest(req);
|
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
||||||
if (
|
if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") {
|
||||||
inventory.Settings?.GiftMode == "GIFT_MODE_NONE" ||
|
|
||||||
(inventory.Settings?.GiftMode == "GIFT_MODE_FRIENDS" && !(await areFriends(account._id, senderAccount._id)))
|
|
||||||
) {
|
|
||||||
res.status(400).send("17").end();
|
res.status(400).send("17").end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -50,7 +40,11 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
|
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
|
||||||
// TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
|
// TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
|
||||||
|
|
||||||
const senderInventory = await getInventory(senderAccount._id.toString());
|
const senderAccount = await getAccountForRequest(req);
|
||||||
|
const senderInventory = await getInventory(
|
||||||
|
senderAccount._id.toString(),
|
||||||
|
"PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
|
||||||
|
);
|
||||||
|
|
||||||
if (senderInventory.GiftsRemaining == 0) {
|
if (senderInventory.GiftsRemaining == 0) {
|
||||||
res.status(400).send("10").end();
|
res.status(400).send("10").end();
|
||||||
@ -58,20 +52,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
senderInventory.GiftsRemaining -= 1;
|
senderInventory.GiftsRemaining -= 1;
|
||||||
|
|
||||||
const inventoryChanges: IInventoryChanges = updateCurrency(
|
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
|
||||||
senderInventory,
|
|
||||||
data.PurchaseParams.ExpectedPrice,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
if (data.PurchaseParams.StoreItem in ExportBundles) {
|
|
||||||
const bundle = ExportBundles[data.PurchaseParams.StoreItem];
|
|
||||||
if (bundle.giftingBonus) {
|
|
||||||
combineInventoryChanges(
|
|
||||||
inventoryChanges,
|
|
||||||
(await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await senderInventory.save();
|
await senderInventory.save();
|
||||||
|
|
||||||
const senderName = getSuffixedName(senderAccount);
|
const senderName = getSuffixedName(senderAccount);
|
||||||
@ -90,7 +71,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
||||||
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||||
gifts: [
|
gifts: [
|
||||||
{
|
{
|
||||||
GiftType: data.PurchaseParams.StoreItem
|
GiftType: data.PurchaseParams.StoreItem
|
||||||
@ -99,9 +80,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
res.json({
|
res.end();
|
||||||
InventoryChanges: inventoryChanges
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IGiftingRequest {
|
interface IGiftingRequest {
|
||||||
|
@ -104,7 +104,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
) {
|
) {
|
||||||
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
||||||
}
|
}
|
||||||
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId!)) {
|
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
|
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
|
||||||
);
|
);
|
||||||
|
@ -1,24 +1,17 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { createNewSession } from "@/src/managers/sessionManager";
|
import { createNewSession } from "@/src/managers/sessionManager";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ISession } from "@/src/types/session";
|
import { ISession } from "@/src/types/session";
|
||||||
import { JSONParse } from "json-with-bigint";
|
|
||||||
import { toOid2, version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
|
|
||||||
const hostSessionController: RequestHandler = async (req, res) => {
|
const hostSessionController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const hostSessionRequest = JSONParse(String(req.body)) as ISession;
|
const hostSessionRequest = JSON.parse(req.body as string) as ISession;
|
||||||
logger.debug("HostSession Request", { hostSessionRequest });
|
logger.debug("HostSession Request", { hostSessionRequest });
|
||||||
const session = createNewSession(hostSessionRequest, account._id);
|
const session = createNewSession(hostSessionRequest, accountId);
|
||||||
logger.debug(`New Session Created`, { session });
|
logger.debug(`New Session Created`, { session });
|
||||||
|
|
||||||
if (account.BuildLabel && version_compare(account.BuildLabel, "2015.03.21.08.17") < 0) {
|
res.json({ sessionId: { $oid: session.sessionId }, rewardSeed: 99999999 });
|
||||||
// U15 or below
|
|
||||||
res.send(session.sessionId.toString());
|
|
||||||
} else {
|
|
||||||
res.json({ sessionId: toOid2(session.sessionId, account.BuildLabel), rewardSeed: 99999999 });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { hostSessionController };
|
export { hostSessionController };
|
||||||
|
@ -9,12 +9,7 @@ import {
|
|||||||
getMessage
|
getMessage
|
||||||
} from "@/src/services/inboxService";
|
} from "@/src/services/inboxService";
|
||||||
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
||||||
import {
|
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||||
addItems,
|
|
||||||
combineInventoryChanges,
|
|
||||||
getEffectiveAvatarImageType,
|
|
||||||
getInventory
|
|
||||||
} from "@/src/services/inventoryService";
|
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ExportFlavour } from "warframe-public-export-plus";
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
@ -93,7 +88,7 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
||||||
icon: ExportFlavour[getEffectiveAvatarImageType(inventory)].icon,
|
icon: ExportFlavour[inventory.ActiveAvatarImageType].icon,
|
||||||
highPriority: true
|
highPriority: true
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
|
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { ExportMisc } from "warframe-public-export-plus";
|
import { ExportMisc } from "warframe-public-export-plus";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
import { toMongoDate, version_compare } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { colorToShard } from "@/src/helpers/shardHelper";
|
import { colorToShard } from "@/src/helpers/shardHelper";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
@ -23,12 +23,12 @@ import {
|
|||||||
} from "@/src/services/infestedFoundryService";
|
} from "@/src/services/infestedFoundryService";
|
||||||
|
|
||||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
switch (req.query.mode) {
|
switch (req.query.mode) {
|
||||||
case "s": {
|
case "s": {
|
||||||
// shard installation
|
// shard installation
|
||||||
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
||||||
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
||||||
@ -56,7 +56,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "x": {
|
case "x": {
|
||||||
// shard removal
|
// shard removal
|
||||||
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
|
|
||||||
const miscItemChanges: IMiscItem[] = [];
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
@ -70,30 +70,19 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
ItemCount: 1
|
ItemCount: 1
|
||||||
});
|
});
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
|
||||||
// consume resources
|
|
||||||
if (!config.infiniteHelminthMaterials) {
|
|
||||||
let type: string;
|
|
||||||
let count: number;
|
|
||||||
if (account.BuildLabel && version_compare(account.BuildLabel, "2025.05.20.10.18") < 0) {
|
|
||||||
// < 38.6.0
|
|
||||||
type = "/Lotus/Types/Items/InfestedFoundry/HelminthBile";
|
|
||||||
count = 300;
|
|
||||||
} else {
|
|
||||||
// >= 38.6.0
|
|
||||||
type =
|
|
||||||
archonCrystalRemovalResource[
|
|
||||||
suit.ArchonCrystalUpgrades![request.Slot].Color!.replace("_MYTHIC", "")
|
|
||||||
];
|
|
||||||
count = suit.ArchonCrystalUpgrades![request.Slot].Color!.indexOf("_MYTHIC") != -1 ? 300 : 150;
|
|
||||||
}
|
|
||||||
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == type)!.Count -= count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove from suit
|
// remove from suit
|
||||||
suit.ArchonCrystalUpgrades![request.Slot] = {};
|
suit.ArchonCrystalUpgrades![request.Slot] = {};
|
||||||
|
|
||||||
|
if (!config.infiniteHelminthMaterials) {
|
||||||
|
// remove bile
|
||||||
|
const bile = inventory.InfestedFoundry!.Resources!.find(
|
||||||
|
x => x.ItemType == "/Lotus/Types/Items/InfestedFoundry/HelminthBile"
|
||||||
|
)!;
|
||||||
|
bile.Count -= 300;
|
||||||
|
}
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
||||||
@ -110,7 +99,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "n": {
|
case "n": {
|
||||||
// name the beast
|
// name the beast
|
||||||
const request = getJSONfromString<IHelminthNameRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthNameRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.Name = request.newName;
|
inventory.InfestedFoundry.Name = request.newName;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
@ -133,7 +122,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.Resources ??= [];
|
inventory.InfestedFoundry.Resources ??= [];
|
||||||
|
|
||||||
@ -229,7 +218,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "o": {
|
case "o": {
|
||||||
// offerings update
|
// offerings update
|
||||||
const request = getJSONfromString<IHelminthOfferingsUpdate>(String(req.body));
|
const request = getJSONfromString<IHelminthOfferingsUpdate>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.InvigorationIndex = request.OfferingsIndex;
|
inventory.InfestedFoundry.InvigorationIndex = request.OfferingsIndex;
|
||||||
inventory.InfestedFoundry.InvigorationSuitOfferings = request.SuitTypes;
|
inventory.InfestedFoundry.InvigorationSuitOfferings = request.SuitTypes;
|
||||||
@ -250,7 +239,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "a": {
|
case "a": {
|
||||||
// subsume warframe
|
// subsume warframe
|
||||||
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const recipe = getRecipe(request.Recipe)!;
|
const recipe = getRecipe(request.Recipe)!;
|
||||||
if (!config.infiniteHelminthMaterials) {
|
if (!config.infiniteHelminthMaterials) {
|
||||||
for (const ingredient of recipe.secretIngredients!) {
|
for (const ingredient of recipe.secretIngredients!) {
|
||||||
@ -300,7 +289,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
case "r": {
|
case "r": {
|
||||||
// rush subsume
|
// rush subsume
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const currencyChanges = updateCurrency(inventory, 50, true);
|
const currencyChanges = updateCurrency(inventory, 50, true);
|
||||||
const recipeChanges = handleSubsumeCompletion(inventory);
|
const recipeChanges = handleSubsumeCompletion(inventory);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
@ -318,7 +307,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
case "u": {
|
case "u": {
|
||||||
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||||
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
||||||
@ -351,7 +340,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "custom_unlockall": {
|
case "custom_unlockall": {
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.XP ??= 0;
|
inventory.InfestedFoundry.XP ??= 0;
|
||||||
if (151875_00 > inventory.InfestedFoundry.XP) {
|
if (151875_00 > inventory.InfestedFoundry.XP) {
|
||||||
@ -450,12 +439,3 @@ const apetiteModel = (x: number): number => {
|
|||||||
}
|
}
|
||||||
return 3;
|
return 3;
|
||||||
};
|
};
|
||||||
|
|
||||||
const archonCrystalRemovalResource: Record<string, string> = {
|
|
||||||
ACC_RED: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides",
|
|
||||||
ACC_YELLOW: "/Lotus/Types/Items/InfestedFoundry/HelminthBile",
|
|
||||||
ACC_BLUE: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics",
|
|
||||||
ACC_GREEN: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics",
|
|
||||||
ACC_ORANGE: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones",
|
|
||||||
ACC_PURPLE: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx"
|
|
||||||
};
|
|
||||||
|
@ -24,11 +24,7 @@ import {
|
|||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
|
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
|
||||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
|
||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
|
||||||
import { Ship } from "@/src/models/shipModel";
|
|
||||||
import { toLegacyOid, version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
|
|
||||||
export const inventoryController: RequestHandler = async (request, response) => {
|
export const inventoryController: RequestHandler = async (request, response) => {
|
||||||
const account = await getAccountForRequest(request);
|
const account = await getAccountForRequest(request);
|
||||||
@ -134,12 +130,13 @@ export const getInventoryResponse = async (
|
|||||||
xpBasedLevelCapDisabled: boolean,
|
xpBasedLevelCapDisabled: boolean,
|
||||||
buildLabel: string | undefined
|
buildLabel: string | undefined
|
||||||
): Promise<IInventoryClient> => {
|
): Promise<IInventoryClient> => {
|
||||||
const [inventoryWithLoadOutPresets, ships] = await Promise.all([
|
const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>(
|
||||||
inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
|
"LoadOutPresets"
|
||||||
Ship.find({ ShipOwnerId: inventory.accountOwnerId })
|
);
|
||||||
]);
|
const inventoryWithLoadOutPresetsAndShips = await inventoryWithLoadOutPresets.populate<{ Ships: IShipInventory }>(
|
||||||
const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
|
"Ships"
|
||||||
inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
|
);
|
||||||
|
const inventoryResponse = inventoryWithLoadOutPresetsAndShips.toJSON<IInventoryClient>();
|
||||||
|
|
||||||
if (config.infiniteCredits) {
|
if (config.infiniteCredits) {
|
||||||
inventoryResponse.RegularCredits = 999999999;
|
inventoryResponse.RegularCredits = 999999999;
|
||||||
@ -306,44 +303,20 @@ export const getInventoryResponse = async (
|
|||||||
// Set 2FA enabled so trading post can be used
|
// Set 2FA enabled so trading post can be used
|
||||||
inventoryResponse.HWIDProtectEnabled = true;
|
inventoryResponse.HWIDProtectEnabled = true;
|
||||||
|
|
||||||
if (buildLabel) {
|
|
||||||
// Fix nemesis for older versions
|
// Fix nemesis for older versions
|
||||||
if (
|
if (
|
||||||
inventoryResponse.Nemesis &&
|
inventoryResponse.Nemesis &&
|
||||||
version_compare(buildLabel, getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild) < 0
|
buildLabel &&
|
||||||
|
!isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)
|
||||||
) {
|
) {
|
||||||
inventoryResponse.Nemesis = undefined;
|
inventoryResponse.Nemesis = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version_compare(buildLabel, "2018.02.22.14.34") < 0) {
|
|
||||||
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
|
|
||||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
|
||||||
inventoryResponse.Ship = personalRooms.Ship;
|
|
||||||
|
|
||||||
if (version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
|
|
||||||
// U19.5 and below use $id instead of $oid
|
|
||||||
for (const category of equipmentKeys) {
|
|
||||||
for (const item of inventoryResponse[category]) {
|
|
||||||
toLegacyOid(item.ItemId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const upgrade of inventoryResponse.Upgrades) {
|
|
||||||
toLegacyOid(upgrade.ItemId);
|
|
||||||
}
|
|
||||||
if (inventoryResponse.BrandedSuits) {
|
|
||||||
for (const id of inventoryResponse.BrandedSuits) {
|
|
||||||
toLegacyOid(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inventoryResponse;
|
return inventoryResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addString = (arr: string[], str: string): void => {
|
const addString = (arr: string[], str: string): void => {
|
||||||
if (arr.indexOf(str) == -1) {
|
if (!arr.find(x => x == str)) {
|
||||||
arr.push(str);
|
arr.push(str);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import { Account } from "@/src/models/loginModel";
|
|||||||
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
|
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
|
||||||
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/services/worldStateService";
|
||||||
|
|
||||||
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
|
||||||
@ -20,21 +20,7 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
? request.query.buildLabel.split(" ").join("+")
|
? request.query.buildLabel.split(" ").join("+")
|
||||||
: buildConfig.buildLabel;
|
: buildConfig.buildLabel;
|
||||||
|
|
||||||
let myAddress: string;
|
const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress;
|
||||||
let myUrlBase: string = request.protocol + "://";
|
|
||||||
if (request.host.indexOf("warframe.com") == -1) {
|
|
||||||
// Client request was redirected cleanly, so we know it can reach us how it's reaching us now.
|
|
||||||
myAddress = request.hostname;
|
|
||||||
myUrlBase += request.host;
|
|
||||||
} else {
|
|
||||||
// Don't know how the client reached us, hoping the config does.
|
|
||||||
myAddress = config.myAddress;
|
|
||||||
myUrlBase += myAddress;
|
|
||||||
const port: number = request.protocol == "http" ? config.httpPort || 80 : config.httpsPort || 443;
|
|
||||||
if (port != (request.protocol == "http" ? 80 : 443)) {
|
|
||||||
myUrlBase += ":" + port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!account &&
|
!account &&
|
||||||
@ -55,18 +41,17 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
email: loginRequest.email,
|
email: loginRequest.email,
|
||||||
password: loginRequest.password,
|
password: loginRequest.password,
|
||||||
DisplayName: name,
|
DisplayName: name,
|
||||||
CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
|
CountryCode: loginRequest.lang.toUpperCase(),
|
||||||
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
|
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
|
||||||
CrossPlatformAllowed: true,
|
CrossPlatformAllowed: true,
|
||||||
ForceLogoutVersion: 0,
|
ForceLogoutVersion: 0,
|
||||||
ConsentNeeded: false,
|
ConsentNeeded: false,
|
||||||
TrackedSettings: [],
|
TrackedSettings: [],
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
BuildLabel: buildLabel,
|
BuildLabel: buildLabel
|
||||||
LastLogin: new Date()
|
|
||||||
});
|
});
|
||||||
logger.debug("created new account");
|
logger.debug("created new account");
|
||||||
response.json(createLoginResponse(myAddress, myUrlBase, newAccount, buildLabel));
|
response.json(createLoginResponse(myAddress, newAccount, buildLabel));
|
||||||
return;
|
return;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@ -97,30 +82,21 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
||||||
// U17 seems to handle "nonce still set" like a login failure.
|
response.status(400).json({ error: "nonce still set" });
|
||||||
if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
|
|
||||||
response.status(400).send({ error: "nonce still set" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
account.ClientType = loginRequest.ClientType;
|
account.ClientType = loginRequest.ClientType;
|
||||||
account.Nonce = nonce;
|
account.Nonce = nonce;
|
||||||
account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
|
account.CountryCode = loginRequest.lang.toUpperCase();
|
||||||
account.BuildLabel = buildLabel;
|
account.BuildLabel = buildLabel;
|
||||||
account.LastLogin = new Date();
|
|
||||||
}
|
}
|
||||||
await account.save();
|
await account.save();
|
||||||
|
|
||||||
response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
|
response.json(createLoginResponse(myAddress, account.toJSON(), buildLabel));
|
||||||
};
|
};
|
||||||
|
|
||||||
const createLoginResponse = (
|
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
|
||||||
myAddress: string,
|
|
||||||
myUrlBase: string,
|
|
||||||
account: IDatabaseAccountJson,
|
|
||||||
buildLabel: string
|
|
||||||
): ILoginResponse => {
|
|
||||||
const resp: ILoginResponse = {
|
const resp: ILoginResponse = {
|
||||||
id: account.id,
|
id: account.id,
|
||||||
DisplayName: account.DisplayName,
|
DisplayName: account.DisplayName,
|
||||||
@ -128,15 +104,10 @@ const createLoginResponse = (
|
|||||||
AmazonAuthToken: account.AmazonAuthToken,
|
AmazonAuthToken: account.AmazonAuthToken,
|
||||||
AmazonRefreshToken: account.AmazonRefreshToken,
|
AmazonRefreshToken: account.AmazonRefreshToken,
|
||||||
Nonce: account.Nonce,
|
Nonce: account.Nonce,
|
||||||
|
IRC: config.myIrcAddresses ?? [myAddress],
|
||||||
|
NRS: config.NRS,
|
||||||
BuildLabel: buildLabel
|
BuildLabel: buildLabel
|
||||||
};
|
};
|
||||||
if (version_compare(buildLabel, "2015.02.13.10.41") >= 0) {
|
|
||||||
resp.NRS = config.NRS;
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2015.05.14.16.29") >= 0) {
|
|
||||||
// U17 and up
|
|
||||||
resp.IRC = config.myIrcAddresses ?? [myAddress];
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
|
if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
|
||||||
// U24 and up
|
// U24 and up
|
||||||
resp.ConsentNeeded = account.ConsentNeeded;
|
resp.ConsentNeeded = account.ConsentNeeded;
|
||||||
@ -158,11 +129,11 @@ const createLoginResponse = (
|
|||||||
}
|
}
|
||||||
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
|
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
|
||||||
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
|
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
|
||||||
resp.HUB = `${myUrlBase}/api/`;
|
resp.HUB = `https://${myAddress}/api/`;
|
||||||
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
|
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
|
||||||
}
|
}
|
||||||
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
|
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
|
||||||
resp.platformCDNs = [`${myUrlBase}/`];
|
resp.platformCDNs = [`https://${myAddress}/`];
|
||||||
}
|
}
|
||||||
return resp;
|
return resp;
|
||||||
};
|
};
|
||||||
|
@ -26,7 +26,7 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
|
|||||||
StoreItemType: body.ChosenReward
|
StoreItemType: body.ChosenReward
|
||||||
};
|
};
|
||||||
inventoryChanges = (await handleStoreItemAcquisition(body.ChosenReward, inventory)).InventoryChanges;
|
inventoryChanges = (await handleStoreItemAcquisition(body.ChosenReward, inventory)).InventoryChanges;
|
||||||
if (evergreenRewards.indexOf(body.ChosenReward) == -1) {
|
if (!evergreenRewards.find(x => x == body.ChosenReward)) {
|
||||||
inventory.LoginMilestoneRewards.push(body.ChosenReward);
|
inventory.LoginMilestoneRewards.push(body.ChosenReward);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -57,15 +57,11 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
|||||||
const firstCompletion = missionReport.SortieId
|
const firstCompletion = missionReport.SortieId
|
||||||
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
|
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
|
||||||
: false;
|
: false;
|
||||||
const inventoryUpdates = await addMissionInventoryUpdates(account, inventory, missionReport);
|
const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
missionReport.MissionStatus !== "GS_SUCCESS" &&
|
missionReport.MissionStatus !== "GS_SUCCESS" &&
|
||||||
!(
|
!(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
|
||||||
missionReport.RewardInfo?.jobId ||
|
|
||||||
missionReport.RewardInfo?.challengeMissionId ||
|
|
||||||
missionReport.RewardInfo?.T
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
if (missionReport.EndOfMatchUpload) {
|
if (missionReport.EndOfMatchUpload) {
|
||||||
inventory.RewardSeed = generateRewardSeed();
|
inventory.RewardSeed = generateRewardSeed();
|
||||||
|
@ -2,7 +2,7 @@ import { RequestHandler } from "express";
|
|||||||
import { ExportWeapons } from "warframe-public-export-plus";
|
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 { CRng } from "@/src/services/rngService";
|
||||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import {
|
import {
|
||||||
@ -140,7 +140,7 @@ const getModularWeaponSale = (
|
|||||||
partTypes: string[],
|
partTypes: string[],
|
||||||
getItemType: (parts: string[]) => string
|
getItemType: (parts: string[]) => string
|
||||||
): IModularWeaponSaleInfo => {
|
): IModularWeaponSaleInfo => {
|
||||||
const rng = new SRng(day);
|
const rng = new CRng(day);
|
||||||
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
||||||
let partsCost = 0;
|
let partsCost = 0;
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
|
@ -1,24 +1,18 @@
|
|||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import {
|
import {
|
||||||
consumeModCharge,
|
consumeModCharge,
|
||||||
decodeNemesisGuess,
|
|
||||||
encodeNemesisGuess,
|
encodeNemesisGuess,
|
||||||
getInfNodes,
|
getInfNodes,
|
||||||
getKnifeUpgrade,
|
getKnifeUpgrade,
|
||||||
getNemesisManifest,
|
|
||||||
getNemesisPasscode,
|
getNemesisPasscode,
|
||||||
getNemesisPasscodeModTypes,
|
getNemesisPasscodeModTypes,
|
||||||
GUESS_CORRECT,
|
getWeaponsForManifest,
|
||||||
GUESS_INCORRECT,
|
IKnifeResponse,
|
||||||
GUESS_NEUTRAL,
|
showdownNodes
|
||||||
GUESS_NONE,
|
|
||||||
GUESS_WILDCARD,
|
|
||||||
IKnifeResponse
|
|
||||||
} from "@/src/helpers/nemesisHelpers";
|
} from "@/src/helpers/nemesisHelpers";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } 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/inventoryTypes/commonInventoryTypes";
|
||||||
@ -30,17 +24,16 @@ import {
|
|||||||
IUpgradeClient,
|
IUpgradeClient,
|
||||||
IWeaponSkinClient,
|
IWeaponSkinClient,
|
||||||
LoadoutIndex,
|
LoadoutIndex,
|
||||||
TEquipmentKey,
|
TEquipmentKey
|
||||||
TNemesisFaction
|
|
||||||
} 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";
|
||||||
|
|
||||||
export const nemesisController: RequestHandler = async (req, res) => {
|
export const nemesisController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
if ((req.query.mode as string) == "f") {
|
if ((req.query.mode as string) == "f") {
|
||||||
const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
|
const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString(), body.Category + " WeaponBin");
|
const inventory = await getInventory(accountId, body.Category + " WeaponBin");
|
||||||
const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!;
|
const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!;
|
||||||
const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!;
|
const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!;
|
||||||
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
|
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
|
||||||
@ -75,7 +68,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if ((req.query.mode as string) == "p") {
|
} else if ((req.query.mode as string) == "p") {
|
||||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
const inventory = await getInventory(accountId, "Nemesis");
|
||||||
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||||
let guessResult = 0;
|
let guessResult = 0;
|
||||||
@ -88,7 +81,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i != 3; ++i) {
|
for (let i = 0; i != 3; ++i) {
|
||||||
if (body.guess[i] == passcode[i] || body.guess[i] == GUESS_WILDCARD) {
|
if (body.guess[i] == passcode[i]) {
|
||||||
++guessResult;
|
++guessResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,36 +89,25 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
res.json({ GuessResult: guessResult });
|
res.json({ GuessResult: guessResult });
|
||||||
} else if (req.query.mode == "r") {
|
} else if (req.query.mode == "r") {
|
||||||
const inventory = await getInventory(
|
const inventory = await getInventory(
|
||||||
account._id.toString(),
|
accountId,
|
||||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
||||||
);
|
);
|
||||||
const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
|
const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
|
||||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||||
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
|
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
|
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
|
||||||
const result1 = passcode == guess[0] ? GUESS_CORRECT : GUESS_INCORRECT;
|
|
||||||
const result2 = passcode == guess[1] ? GUESS_CORRECT : GUESS_INCORRECT;
|
// Add to GuessHistory
|
||||||
const result3 = passcode == guess[2] ? GUESS_CORRECT : GUESS_INCORRECT;
|
const result1 = passcode == guess[0] ? 0 : 1;
|
||||||
|
const result2 = passcode == guess[1] ? 0 : 1;
|
||||||
|
const result3 = passcode == guess[2] ? 0 : 1;
|
||||||
inventory.Nemesis!.GuessHistory.push(
|
inventory.Nemesis!.GuessHistory.push(
|
||||||
encodeNemesisGuess([
|
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
|
||||||
{
|
|
||||||
symbol: guess[0],
|
|
||||||
result: result1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: guess[1],
|
|
||||||
result: result2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: guess[2],
|
|
||||||
result: result3
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Increase antivirus if correct antivirus mod is installed
|
// Increase antivirus if correct antivirus mod is installed
|
||||||
const response: IKnifeResponse = {};
|
const response: IKnifeResponse = {};
|
||||||
if (result1 == GUESS_CORRECT || result2 == GUESS_CORRECT || result3 == GUESS_CORRECT) {
|
if (result1 == 0 || result2 == 0 || result3 == 0) {
|
||||||
let antivirusGain = 5;
|
let antivirusGain = 5;
|
||||||
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
|
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
|
||||||
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
|
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
|
||||||
@ -161,90 +143,45 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
if (inventory.Nemesis!.HenchmenKilled >= 100) {
|
if (inventory.Nemesis!.HenchmenKilled >= 100) {
|
||||||
inventory.Nemesis!.HenchmenKilled = 100;
|
inventory.Nemesis!.HenchmenKilled = 100;
|
||||||
}
|
}
|
||||||
inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0);
|
inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json(response);
|
res.json(response);
|
||||||
} else {
|
} else {
|
||||||
// For first guess, create a new entry.
|
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||||
if (body.position == 0) {
|
if (passcode[body.position] != body.guess) {
|
||||||
inventory.Nemesis!.GuessHistory.push(
|
res.end();
|
||||||
encodeNemesisGuess([
|
} else {
|
||||||
{
|
inventory.Nemesis!.Rank += 1;
|
||||||
symbol: GUESS_NONE,
|
inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank);
|
||||||
result: GUESS_NEUTRAL
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: GUESS_NONE,
|
|
||||||
result: GUESS_NEUTRAL
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: GUESS_NONE,
|
|
||||||
result: GUESS_NEUTRAL
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate guess
|
|
||||||
const correct =
|
|
||||||
body.guess == GUESS_WILDCARD || getNemesisPasscode(inventory.Nemesis!)[body.position] == body.guess;
|
|
||||||
|
|
||||||
// Update entry
|
|
||||||
const guess = decodeNemesisGuess(
|
|
||||||
inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1]
|
|
||||||
);
|
|
||||||
guess[body.position].symbol = body.guess;
|
|
||||||
guess[body.position].result = correct ? GUESS_CORRECT : GUESS_INCORRECT;
|
|
||||||
inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1] = encodeNemesisGuess(guess);
|
|
||||||
|
|
||||||
// Increase rank if incorrect
|
|
||||||
let RankIncrease: number | undefined;
|
|
||||||
if (!correct) {
|
|
||||||
RankIncrease = 1;
|
|
||||||
const manifest = getNemesisManifest(inventory.Nemesis!.manifest);
|
|
||||||
inventory.Nemesis!.Rank = Math.min(inventory.Nemesis!.Rank + 1, manifest.systemIndexes.length - 1);
|
|
||||||
inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank);
|
|
||||||
}
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({ RankIncrease });
|
res.json({ RankIncrease: 1 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if ((req.query.mode as string) == "rs") {
|
} else if ((req.query.mode as string) == "rs") {
|
||||||
// report spawn; POST but no application data in body
|
// report spawn; POST but no application data in body
|
||||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
const inventory = await getInventory(accountId, "Nemesis");
|
||||||
inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
|
inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
|
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
|
||||||
} else if ((req.query.mode as string) == "s") {
|
} else if ((req.query.mode as string) == "s") {
|
||||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
const inventory = await getInventory(accountId, "Nemesis");
|
||||||
if (inventory.Nemesis) {
|
|
||||||
logger.warn(`overwriting an existing nemesis as a new one is being requested`);
|
|
||||||
}
|
|
||||||
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
||||||
body.target.fp = BigInt(body.target.fp);
|
body.target.fp = BigInt(body.target.fp);
|
||||||
|
|
||||||
const manifest = getNemesisManifest(body.target.manifest);
|
|
||||||
if (account.BuildLabel && version_compare(account.BuildLabel, manifest.minBuild) < 0) {
|
|
||||||
logger.warn(
|
|
||||||
`client on version ${account.BuildLabel} provided nemesis manifest ${body.target.manifest} which was expected to require ${manifest.minBuild} or above. please file a bug report.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let weaponIdx = -1;
|
let weaponIdx = -1;
|
||||||
if (body.target.Faction != "FC_INFESTATION") {
|
if (body.target.Faction != "FC_INFESTATION") {
|
||||||
const weapons: readonly string[] = manifest.weapons;
|
const weapons = getWeaponsForManifest(body.target.manifest);
|
||||||
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
|
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
|
||||||
weaponIdx = initialWeaponIdx;
|
weaponIdx = initialWeaponIdx;
|
||||||
if (body.target.DisallowedWeapons) {
|
|
||||||
do {
|
do {
|
||||||
const weapon = weapons[weaponIdx];
|
const weapon = weapons[weaponIdx];
|
||||||
if (body.target.DisallowedWeapons.indexOf(weapon) == -1) {
|
if (!body.target.DisallowedWeapons.find(x => x == weapon)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
weaponIdx = (weaponIdx + 1) % weapons.length;
|
weaponIdx = (weaponIdx + 1) % weapons.length;
|
||||||
} while (weaponIdx != initialWeaponIdx);
|
} while (weaponIdx != initialWeaponIdx);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
inventory.Nemesis = {
|
inventory.Nemesis = {
|
||||||
fp: body.target.fp,
|
fp: body.target.fp,
|
||||||
@ -260,14 +197,14 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
k: false,
|
k: false,
|
||||||
Traded: false,
|
Traded: false,
|
||||||
d: new Date(),
|
d: new Date(),
|
||||||
InfNodes: getInfNodes(manifest, 0),
|
InfNodes: getInfNodes(body.target.Faction, 0),
|
||||||
GuessHistory: [],
|
GuessHistory: [],
|
||||||
Hints: [],
|
Hints: [],
|
||||||
HintProgress: 0,
|
HintProgress: 0,
|
||||||
Weakened: false,
|
Weakened: body.target.Weakened,
|
||||||
PrevOwners: 0,
|
PrevOwners: 0,
|
||||||
HenchmenKilled: 0,
|
HenchmenKilled: 0,
|
||||||
SecondInCommand: false,
|
SecondInCommand: body.target.SecondInCommand,
|
||||||
MissionCount: 0,
|
MissionCount: 0,
|
||||||
LastEnc: 0
|
LastEnc: 0
|
||||||
};
|
};
|
||||||
@ -278,14 +215,14 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
});
|
});
|
||||||
} else if ((req.query.mode as string) == "w") {
|
} else if ((req.query.mode as string) == "w") {
|
||||||
const inventory = await getInventory(
|
const inventory = await getInventory(
|
||||||
account._id.toString(),
|
accountId,
|
||||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
||||||
);
|
);
|
||||||
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
|
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
|
||||||
|
|
||||||
inventory.Nemesis!.InfNodes = [
|
inventory.Nemesis!.InfNodes = [
|
||||||
{
|
{
|
||||||
Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
|
Node: showdownNodes[inventory.Nemesis!.Faction],
|
||||||
Influence: 1
|
Influence: 1
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -328,11 +265,11 @@ interface INemesisStartRequest {
|
|||||||
KillingSuit: string;
|
KillingSuit: string;
|
||||||
killingDamageType: number;
|
killingDamageType: number;
|
||||||
ShoulderHelmet: string;
|
ShoulderHelmet: string;
|
||||||
DisallowedWeapons?: string[];
|
DisallowedWeapons: string[];
|
||||||
WeaponIdx: number;
|
WeaponIdx: number;
|
||||||
AgentIdx: number;
|
AgentIdx: number;
|
||||||
BirthNode: string;
|
BirthNode: string;
|
||||||
Faction: TNemesisFaction;
|
Faction: string;
|
||||||
Rank: number;
|
Rank: number;
|
||||||
k: boolean;
|
k: boolean;
|
||||||
Traded: boolean;
|
Traded: boolean;
|
||||||
|
@ -2,16 +2,13 @@ import { RequestHandler } from "express";
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { ExportRelics, IRelic } from "warframe-public-export-plus";
|
import { ExportRelics, IRelic } from "warframe-public-export-plus";
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
|
|
||||||
export const projectionManagerController: RequestHandler = async (req, res) => {
|
export const projectionManagerController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const request = JSON.parse(String(req.body)) as IProjectionUpgradeRequest;
|
const request = JSON.parse(String(req.body)) as IProjectionUpgradeRequest;
|
||||||
const [era, category, currentQuality] = parseProjection(request.projectionType);
|
const [era, category, currentQuality] = parseProjection(request.projectionType);
|
||||||
const upgradeCost = config.dontSubtractVoidTraces
|
const upgradeCost = (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
||||||
? 0
|
|
||||||
: (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
|
||||||
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
|
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
// Basic shim handling action=sync to login on U21
|
|
||||||
export const questControlController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const quests: IQuestState[] = [];
|
|
||||||
for (const quest of inventory.QuestKeys) {
|
|
||||||
quests.push({
|
|
||||||
quest: quest.ItemType,
|
|
||||||
state: 3 // COMPLETE
|
|
||||||
});
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
QuestState: quests
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IQuestState {
|
|
||||||
quest: string;
|
|
||||||
state: number;
|
|
||||||
task?: string;
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
|
||||||
import { Account } from "@/src/models/loginModel";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
|
||||||
import { parallelForeach } from "@/src/utils/async-utils";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
import { Types } from "mongoose";
|
|
||||||
|
|
||||||
export const removeFriendGetController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
if (req.query.all) {
|
|
||||||
const [internalFriendships, externalFriendships] = await Promise.all([
|
|
||||||
Friendship.find({ owner: accountId }, "friend"),
|
|
||||||
Friendship.find({ friend: accountId }, "owner")
|
|
||||||
]);
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
const friends: IOid[] = [];
|
|
||||||
for (const externalFriendship of externalFriendships) {
|
|
||||||
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
|
||||||
promises.push(Friendship.deleteOne({ _id: externalFriendship._id }) as unknown as Promise<void>);
|
|
||||||
friends.push(toOid(externalFriendship.owner));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
res.json({
|
|
||||||
Friends: friends
|
|
||||||
} satisfies IRemoveFriendsResponse);
|
|
||||||
} else {
|
|
||||||
const friendId = req.query.friendId as string;
|
|
||||||
await Promise.all([
|
|
||||||
Friendship.deleteOne({ owner: accountId, friend: friendId }),
|
|
||||||
Friendship.deleteOne({ owner: friendId, friend: accountId })
|
|
||||||
]);
|
|
||||||
res.json({
|
|
||||||
Friends: [{ $oid: friendId }]
|
|
||||||
} satisfies IRemoveFriendsResponse);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeFriendPostController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const data = getJSONfromString<IBatchRemoveFriendsRequest>(String(req.body));
|
|
||||||
const friends = new Set((await Friendship.find({ owner: accountId }, "friend")).map(x => x.friend));
|
|
||||||
// TOVERIFY: Should pending friendships also be kept?
|
|
||||||
|
|
||||||
// Keep friends that have been online within threshold
|
|
||||||
await parallelForeach([...friends], async friend => {
|
|
||||||
const account = (await Account.findById(friend, "LastLogin"))!;
|
|
||||||
const daysLoggedOut = (Date.now() - account.LastLogin.getTime()) / 86400_000;
|
|
||||||
if (daysLoggedOut < data.DaysLoggedOut) {
|
|
||||||
friends.delete(friend);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.SkipClanmates) {
|
|
||||||
const inventory = await getInventory(accountId, "GuildId");
|
|
||||||
if (inventory.GuildId) {
|
|
||||||
await parallelForeach([...friends], async friend => {
|
|
||||||
const friendInventory = await getInventory(friend.toString(), "GuildId");
|
|
||||||
if (friendInventory.GuildId?.equals(inventory.GuildId)) {
|
|
||||||
friends.delete(friend);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all remaining friends that aren't in SkipFriendIds & give response.
|
|
||||||
const promises = [];
|
|
||||||
const response: IOid[] = [];
|
|
||||||
for (const friend of friends) {
|
|
||||||
if (!data.SkipFriendIds.find(skipFriendId => checkFriendId(skipFriendId, friend))) {
|
|
||||||
promises.push(Friendship.deleteOne({ owner: accountId, friend: friend }));
|
|
||||||
promises.push(Friendship.deleteOne({ owner: friend, friend: accountId }));
|
|
||||||
response.push(toOid(friend));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
res.json({
|
|
||||||
Friends: response
|
|
||||||
} satisfies IRemoveFriendsResponse);
|
|
||||||
};
|
|
||||||
|
|
||||||
// The friend ids format is a bit weird, e.g. when 6633b81e9dba0b714f28ff02 (A) is friends with 67cdac105ef1f4b49741c267 (B), A's friend id for B is 808000105ef1f40560ca079e and B's friend id for A is 8000b81e9dba0b06408a8075.
|
|
||||||
const checkFriendId = (friendId: string, b: Types.ObjectId): boolean => {
|
|
||||||
return friendId.substring(6, 6 + 8) == b.toString().substring(6, 6 + 8);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IBatchRemoveFriendsRequest {
|
|
||||||
DaysLoggedOut: number;
|
|
||||||
SkipClanmates: boolean;
|
|
||||||
SkipFriendIds: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IRemoveFriendsResponse {
|
|
||||||
Friends: IOid[];
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const renamePetController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId, "KubrowPets PremiumCredits PremiumCreditsFree");
|
|
||||||
const data = getJSONfromString<IRenamePetRequest>(String(req.body));
|
|
||||||
const details = inventory.KubrowPets.id(data.petId)!.Details!;
|
|
||||||
details.Name = data.name;
|
|
||||||
const currencyChanges = updateCurrency(inventory, 15, true);
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
...data,
|
|
||||||
inventoryChanges: currencyChanges
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IRenamePetRequest {
|
|
||||||
petId: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
@ -1,7 +1,8 @@
|
|||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { addEmailItem, getDialogue, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
|
|||||||
inventory.DialogueHistory.Dialogues ??= [];
|
inventory.DialogueHistory.Dialogues ??= [];
|
||||||
const dialogue = getDialogue(inventory, request.DialogueName);
|
const dialogue = getDialogue(inventory, request.DialogueName);
|
||||||
dialogue.Rank = request.Rank;
|
dialogue.Rank = request.Rank;
|
||||||
dialogue.Chemistry += request.Chemistry;
|
dialogue.Chemistry = request.Chemistry;
|
||||||
dialogue.QueuedDialogues = request.QueuedDialogues;
|
dialogue.QueuedDialogues = request.QueuedDialogues;
|
||||||
for (const bool of request.Booleans) {
|
for (const bool of request.Booleans) {
|
||||||
dialogue.Booleans.push(bool);
|
dialogue.Booleans.push(bool);
|
||||||
@ -106,3 +107,26 @@ interface IOtherDialogueInfo {
|
|||||||
Tag: string;
|
Tag: string;
|
||||||
Value: number;
|
Value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
|
||||||
|
let dialogue = inventory.DialogueHistory!.Dialogues!.find(x => x.DialogueName == dialogueName);
|
||||||
|
if (!dialogue) {
|
||||||
|
dialogue =
|
||||||
|
inventory.DialogueHistory!.Dialogues![
|
||||||
|
inventory.DialogueHistory!.Dialogues!.push({
|
||||||
|
Rank: 0,
|
||||||
|
Chemistry: 0,
|
||||||
|
AvailableDate: new Date(0),
|
||||||
|
AvailableGiftDate: new Date(0),
|
||||||
|
RankUpExpiry: new Date(0),
|
||||||
|
BountyChemExpiry: new Date(0),
|
||||||
|
QueuedDialogues: [],
|
||||||
|
Gifts: [],
|
||||||
|
Booleans: [],
|
||||||
|
Completed: [],
|
||||||
|
DialogueName: dialogueName
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return dialogue;
|
||||||
|
};
|
||||||
|
@ -2,12 +2,11 @@ import { RequestHandler } from "express";
|
|||||||
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
|
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
|
||||||
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
|
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
|
|
||||||
export const saveLoadoutController: RequestHandler = async (req, res) => {
|
export const saveLoadoutController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
|
||||||
const body: ISaveLoadoutRequest = getJSONfromString<ISaveLoadoutRequest>(String(req.body));
|
const body: ISaveLoadoutRequest = JSON.parse(req.body as string) as ISaveLoadoutRequest;
|
||||||
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
|
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
@ -45,9 +45,6 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
|
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
|
||||||
requiredFields.add(InventorySlot.SPACEWEAPONS);
|
requiredFields.add(InventorySlot.SPACEWEAPONS);
|
||||||
}
|
}
|
||||||
if (payload.Items.MechSuits) {
|
|
||||||
requiredFields.add(InventorySlot.MECHSUITS);
|
|
||||||
}
|
|
||||||
if (payload.Items.Sentinels || payload.Items.SentinelWeapons || payload.Items.MoaPets) {
|
if (payload.Items.Sentinels || payload.Items.SentinelWeapons || payload.Items.MoaPets) {
|
||||||
requiredFields.add(InventorySlot.SENTINELS);
|
requiredFields.add(InventorySlot.SENTINELS);
|
||||||
}
|
}
|
||||||
@ -139,12 +136,6 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
|
freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (payload.Items.MechSuits) {
|
|
||||||
payload.Items.MechSuits.forEach(sellItem => {
|
|
||||||
inventory.MechSuits.pull({ _id: sellItem.String });
|
|
||||||
freeUpSlot(inventory, InventorySlot.MECHSUITS);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (payload.Items.Sentinels) {
|
if (payload.Items.Sentinels) {
|
||||||
payload.Items.Sentinels.forEach(sellItem => {
|
payload.Items.Sentinels.forEach(sellItem => {
|
||||||
inventory.Sentinels.pull({ _id: sellItem.String });
|
inventory.Sentinels.pull({ _id: sellItem.String });
|
||||||
@ -294,7 +285,6 @@ interface ISellRequest {
|
|||||||
SpaceSuits?: ISellItem[];
|
SpaceSuits?: ISellItem[];
|
||||||
SpaceGuns?: ISellItem[];
|
SpaceGuns?: ISellItem[];
|
||||||
SpaceMelee?: ISellItem[];
|
SpaceMelee?: ISellItem[];
|
||||||
MechSuits?: ISellItem[];
|
|
||||||
Sentinels?: ISellItem[];
|
Sentinels?: ISellItem[];
|
||||||
SentinelWeapons?: ISellItem[];
|
SentinelWeapons?: ISellItem[];
|
||||||
MoaPets?: ISellItem[];
|
MoaPets?: ISellItem[];
|
||||||
|
@ -13,7 +13,7 @@ export const setDojoComponentSettingsController: RequestHandler = async (req, re
|
|||||||
res.json({ DojoRequestStatus: -1 });
|
res.json({ DojoRequestStatus: -1 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const component = guild.DojoComponents.id(req.query.componentId as string)!;
|
const component = guild.DojoComponents.id(req.query.componentId)!;
|
||||||
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
|
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
|
||||||
component.Settings = data.Settings;
|
component.Settings = data.Settings;
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const setFriendNoteController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const payload = getJSONfromString<ISetFriendNoteRequest>(String(req.body));
|
|
||||||
const friendship = await Friendship.findOne({ owner: accountId, friend: payload.FriendId }, "Note Favorite");
|
|
||||||
if (friendship) {
|
|
||||||
if ("Note" in payload) {
|
|
||||||
friendship.Note = payload.Note;
|
|
||||||
} else {
|
|
||||||
friendship.Favorite = payload.Favorite;
|
|
||||||
}
|
|
||||||
await friendship.save();
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
Id: payload.FriendId,
|
|
||||||
SetNote: "Note" in payload,
|
|
||||||
Note: friendship?.Note,
|
|
||||||
Favorite: friendship?.Favorite
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ISetFriendNoteRequest {
|
|
||||||
FriendId: string;
|
|
||||||
Note?: string;
|
|
||||||
Favorite?: boolean;
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
|
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { hasGuildPermissionEx } from "@/src/services/guildService";
|
import { hasGuildPermissionEx } from "@/src/services/guildService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { version_compare } from "@/src/services/worldStateService";
|
||||||
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
|
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
interface IStartDojoRecipeRequest {
|
interface IStartDojoRecipeRequest {
|
||||||
@ -20,10 +20,10 @@ interface IStartDojoRecipeRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const startDojoRecipeController: RequestHandler = async (req, res) => {
|
export const startDojoRecipeController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys");
|
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, account._id, GuildPermission.Architect))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
|
||||||
res.json({ DojoRequestStatus: -1 });
|
res.json({ DojoRequestStatus: -1 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -64,5 +64,5 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
|
|||||||
setDojoRoomLogFunded(guild, component);
|
setDojoRoomLogFunded(guild, component);
|
||||||
}
|
}
|
||||||
await guild.save();
|
await guild.save();
|
||||||
res.json(await getDojoClient(guild, 0, undefined, account.BuildLabel));
|
res.json(await getDojoClient(guild, 0));
|
||||||
};
|
};
|
||||||
|
@ -3,14 +3,12 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
import { addItem, addKubrowPet, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addItem, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { ExportWeapons } from "warframe-public-export-plus";
|
import { ExportWeapons } from "warframe-public-export-plus";
|
||||||
import { getRandomElement } from "@/src/services/rngService";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
|
|
||||||
interface IStartRecipeRequest {
|
interface IStartRecipeRequest {
|
||||||
RecipeName: string;
|
RecipeName: string;
|
||||||
@ -43,13 +41,7 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (let i = 0; i != recipe.ingredients.length; ++i) {
|
for (let i = 0; i != recipe.ingredients.length; ++i) {
|
||||||
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
|
if (startRecipeRequest.Ids[i]) {
|
||||||
if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
|
|
||||||
const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
|
|
||||||
if (index != -1) {
|
|
||||||
inventory.KubrowPetEggs!.splice(index, 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
|
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
|
||||||
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
|
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
|
||||||
throw new Error(`unexpected equipment ingredient type: ${category}`);
|
throw new Error(`unexpected equipment ingredient type: ${category}`);
|
||||||
@ -62,17 +54,12 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
|||||||
pr[category].push(inventory[category][equipmentIndex]);
|
pr[category].push(inventory[category][equipmentIndex]);
|
||||||
inventory[category].splice(equipmentIndex, 1);
|
inventory[category].splice(equipmentIndex, 1);
|
||||||
freeUpSlot(inventory, InventorySlot.WEAPONS);
|
freeUpSlot(inventory, InventorySlot.WEAPONS);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1);
|
await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let inventoryChanges: IInventoryChanges | undefined;
|
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||||
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
|
|
||||||
inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType);
|
|
||||||
pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId));
|
|
||||||
} else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
|
||||||
const spectreLoadout: ISpectreLoadout = {
|
const spectreLoadout: ISpectreLoadout = {
|
||||||
ItemType: recipe.resultType,
|
ItemType: recipe.resultType,
|
||||||
Suits: "",
|
Suits: "",
|
||||||
@ -129,5 +116,5 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({ RecipeId: toOid(pr._id), InventoryChanges: inventoryChanges });
|
res.json({ RecipeId: toOid(pr._id) });
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import { addMiscItem, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { toStoreItem } from "@/src/services/itemDataService";
|
import { isStoreItem, toStoreItem } from "@/src/services/itemDataService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
|
|
||||||
|
const nightwaveCredsItemType = ExportNightwave.rewards[ExportNightwave.rewards.length - 1].uniqueName;
|
||||||
|
|
||||||
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
|
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
|
||||||
const accountId = await getAccountIdForRequest(request);
|
const accountId = await getAccountIdForRequest(request);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
@ -18,83 +20,74 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
|
|||||||
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1];
|
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldLevel = syndicate.Title ?? 0;
|
const level = data.SacrificeLevel - (syndicate.Title ?? 0);
|
||||||
const levelIncrease = data.SacrificeLevel - oldLevel;
|
|
||||||
if (levelIncrease < 0) {
|
|
||||||
throw new Error(`syndicate sacrifice can not decrease level`);
|
|
||||||
}
|
|
||||||
if (levelIncrease > 1 && !data.AllowMultiple) {
|
|
||||||
throw new Error(`desired syndicate level is an increase of ${levelIncrease}, max. allowed increase is 1`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const res: ISyndicateSacrificeResponse = {
|
const res: ISyndicateSacrificeResponse = {
|
||||||
AffiliationTag: data.AffiliationTag,
|
AffiliationTag: data.AffiliationTag,
|
||||||
InventoryChanges: {},
|
InventoryChanges: {},
|
||||||
Level: data.SacrificeLevel,
|
Level: data.SacrificeLevel,
|
||||||
LevelIncrease: data.SacrificeLevel < 0 ? 1 : levelIncrease,
|
LevelIncrease: level <= 0 ? 1 : level,
|
||||||
NewEpisodeReward: false
|
NewEpisodeReward: false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process sacrifices and rewards for every level we're reaching
|
|
||||||
const manifest = ExportSyndicates[data.AffiliationTag];
|
const manifest = ExportSyndicates[data.AffiliationTag];
|
||||||
for (let level = oldLevel + Math.min(levelIncrease, 1); level <= data.SacrificeLevel; ++level) {
|
|
||||||
let sacrifice: ISyndicateSacrifice | undefined;
|
let sacrifice: ISyndicateSacrifice | undefined;
|
||||||
if (level == 0) {
|
let reward: string | undefined;
|
||||||
|
if (data.SacrificeLevel == 0) {
|
||||||
sacrifice = manifest.initiationSacrifice;
|
sacrifice = manifest.initiationSacrifice;
|
||||||
if (manifest.initiationReward) {
|
reward = manifest.initiationReward;
|
||||||
combineInventoryChanges(
|
|
||||||
res.InventoryChanges,
|
|
||||||
(await handleStoreItemAcquisition(manifest.initiationReward, inventory)).InventoryChanges
|
|
||||||
);
|
|
||||||
}
|
|
||||||
syndicate.Initiated = true;
|
syndicate.Initiated = true;
|
||||||
} else {
|
} else {
|
||||||
sacrifice = manifest.titles?.find(x => x.level == level)?.sacrifice;
|
sacrifice = manifest.titles?.find(x => x.level == data.SacrificeLevel)?.sacrifice;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sacrifice) {
|
if (sacrifice) {
|
||||||
updateCurrency(inventory, sacrifice.credits, false, res.InventoryChanges);
|
res.InventoryChanges = { ...updateCurrency(inventory, sacrifice.credits, false) };
|
||||||
|
|
||||||
for (const item of sacrifice.items) {
|
const miscItemChanges = sacrifice.items.map(x => ({
|
||||||
addMiscItem(inventory, item.ItemType, item.ItemCount * -1, res.InventoryChanges);
|
ItemType: x.ItemType,
|
||||||
|
ItemCount: x.ItemCount * -1
|
||||||
|
}));
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
res.InventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
syndicate.Title ??= 0;
|
||||||
|
syndicate.Title += 1;
|
||||||
|
|
||||||
|
if (syndicate.Title > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == syndicate.Title)) {
|
||||||
|
syndicate.FreeFavorsEarned ??= [];
|
||||||
|
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
|
||||||
|
syndicate.FreeFavorsEarned.push(syndicate.Title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quacks like a nightwave syndicate?
|
if (reward) {
|
||||||
if (manifest.dailyChallenges) {
|
combineInventoryChanges(
|
||||||
const title = manifest.titles!.find(x => x.level == level);
|
res.InventoryChanges,
|
||||||
if (title) {
|
(await handleStoreItemAcquisition(reward, inventory)).InventoryChanges
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.AffiliationTag == ExportNightwave.affiliationTag) {
|
||||||
|
const index = syndicate.Title - 1;
|
||||||
|
if (index < ExportNightwave.rewards.length) {
|
||||||
res.NewEpisodeReward = true;
|
res.NewEpisodeReward = true;
|
||||||
let rewardType: string;
|
const reward = ExportNightwave.rewards[index];
|
||||||
let rewardCount: number;
|
let rewardType = reward.uniqueName;
|
||||||
if (title.storeItemReward) {
|
if (!isStoreItem(rewardType)) {
|
||||||
rewardType = title.storeItemReward;
|
rewardType = toStoreItem(rewardType);
|
||||||
rewardCount = 1;
|
|
||||||
} else {
|
|
||||||
rewardType = toStoreItem(title.reward!.ItemType);
|
|
||||||
rewardCount = title.reward!.ItemCount;
|
|
||||||
}
|
}
|
||||||
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount))
|
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount))
|
||||||
.InventoryChanges;
|
.InventoryChanges;
|
||||||
if (Object.keys(rewardInventoryChanges).length == 0) {
|
if (Object.keys(rewardInventoryChanges).length == 0) {
|
||||||
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
|
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
|
||||||
const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType;
|
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
|
||||||
addMiscItem(inventory, nightwaveCredsItemType, 50, rewardInventoryChanges);
|
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
|
||||||
}
|
}
|
||||||
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
|
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (level > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == level)) {
|
|
||||||
syndicate.FreeFavorsEarned ??= [];
|
|
||||||
if (!syndicate.FreeFavorsEarned.includes(level)) {
|
|
||||||
syndicate.FreeFavorsEarned.push(level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit
|
|
||||||
syndicate.Title = data.SacrificeLevel < 0 ? data.SacrificeLevel + 1 : data.SacrificeLevel;
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
response.json(res);
|
response.json(res);
|
||||||
|
@ -5,7 +5,7 @@ import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTy
|
|||||||
import { IOid } from "@/src/types/commonTypes";
|
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 { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
|
||||||
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
|
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
|
||||||
@ -54,14 +54,13 @@ export const syndicateStandingBonusController: RequestHandler = async (req, res)
|
|||||||
inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
|
inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const affiliationMods: IAffiliationMods[] = [];
|
const affiliationMod = addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, true);
|
||||||
addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, affiliationMods, true);
|
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: inventoryChanges,
|
InventoryChanges: inventoryChanges,
|
||||||
AffiliationMods: affiliationMods
|
AffiliationMods: [affiliationMod]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,17 +35,6 @@ const trainingResultController: RequestHandler = async (req, res): Promise<void>
|
|||||||
inventory.PlayerLevel += 1;
|
inventory.PlayerLevel += 1;
|
||||||
inventory.TradesRemaining += 1;
|
inventory.TradesRemaining += 1;
|
||||||
|
|
||||||
if (inventory.PlayerLevel == 2) {
|
|
||||||
await createMessage(accountId, [
|
|
||||||
{
|
|
||||||
sndr: "/Lotus/Language/Game/Maroo",
|
|
||||||
msg: "/Lotus/Language/Clan/MarooClanSearchDesc",
|
|
||||||
sub: "/Lotus/Language/Clan/MarooClanSearchTitle",
|
|
||||||
icon: "/Lotus/Interface/Icons/Npcs/Maroo.png"
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await createMessage(accountId, [
|
await createMessage(accountId, [
|
||||||
{
|
{
|
||||||
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
|
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
|
||||||
|
@ -1,26 +1,18 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { addChallenges, getInventory } from "@/src/services/inventoryService";
|
import { addChallenges, getInventory } from "@/src/services/inventoryService";
|
||||||
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IAffiliationMods } from "@/src/types/purchaseTypes";
|
import { IAffiliationMods } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
|
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
|
||||||
const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
|
const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
|
||||||
const inventory = await getInventory(
|
const inventory = await getInventory(accountId, "ChallengeProgress SeasonChallengeHistory Affiliations");
|
||||||
account._id.toString(),
|
|
||||||
"ChallengeProgress SeasonChallengeHistory Affiliations"
|
|
||||||
);
|
|
||||||
let affiliationMods: IAffiliationMods[] = [];
|
let affiliationMods: IAffiliationMods[] = [];
|
||||||
if (challenges.ChallengeProgress) {
|
if (challenges.ChallengeProgress) {
|
||||||
affiliationMods = addChallenges(
|
affiliationMods = addChallenges(inventory, challenges.ChallengeProgress, challenges.SeasonChallengeCompletions);
|
||||||
account,
|
|
||||||
inventory,
|
|
||||||
challenges.ChallengeProgress,
|
|
||||||
challenges.SeasonChallengeCompletions
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (challenges.SeasonChallengeHistory) {
|
if (challenges.SeasonChallengeHistory) {
|
||||||
challenges.SeasonChallengeHistory.forEach(({ challenge, id }) => {
|
challenges.SeasonChallengeHistory.forEach(({ challenge, id }) => {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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 { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
@ -12,7 +11,7 @@ export const addXpController: RequestHandler = async (req, res) => {
|
|||||||
const request = req.body as IAddXpRequest;
|
const request = req.body as IAddXpRequest;
|
||||||
for (const [category, gear] of Object.entries(request)) {
|
for (const [category, gear] of Object.entries(request)) {
|
||||||
for (const clientItem of gear) {
|
for (const clientItem of gear) {
|
||||||
const dbItem = inventory[category as TEquipmentKey].id((clientItem.ItemId as IOid).$oid);
|
const dbItem = inventory[category as TEquipmentKey].id(clientItem.ItemId.$oid);
|
||||||
if (dbItem) {
|
if (dbItem) {
|
||||||
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
|
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
|
||||||
if ((dbItem.Polarized ?? 0) < 5) {
|
if ((dbItem.Polarized ?? 0) < 5) {
|
||||||
|
@ -10,7 +10,6 @@ import { Stats } from "@/src/models/statsModel";
|
|||||||
import { GuildMember } from "@/src/models/guildModel";
|
import { GuildMember } from "@/src/models/guildModel";
|
||||||
import { Leaderboard } from "@/src/models/leaderboardModel";
|
import { Leaderboard } from "@/src/models/leaderboardModel";
|
||||||
import { deleteGuild } from "@/src/services/guildService";
|
import { deleteGuild } from "@/src/services/guildService";
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
|
||||||
|
|
||||||
export const deleteAccountController: RequestHandler = async (req, res) => {
|
export const deleteAccountController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -23,8 +22,6 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
Account.deleteOne({ _id: accountId }),
|
Account.deleteOne({ _id: accountId }),
|
||||||
Friendship.deleteMany({ owner: accountId }),
|
|
||||||
Friendship.deleteMany({ friend: accountId }),
|
|
||||||
GuildMember.deleteMany({ accountId: accountId }),
|
GuildMember.deleteMany({ accountId: accountId }),
|
||||||
Ignore.deleteMany({ ignorer: accountId }),
|
Ignore.deleteMany({ ignorer: accountId }),
|
||||||
Ignore.deleteMany({ ignoree: accountId }),
|
Ignore.deleteMany({ ignoree: accountId }),
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getAccountInfoController: RequestHandler = async (req, res) => {
|
export const getAccountInfoController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
const inventory = await getInventory(account._id.toString(), "QuestKeys");
|
|
||||||
const info: IAccountInfo = {
|
const info: IAccountInfo = {
|
||||||
DisplayName: account.DisplayName,
|
DisplayName: account.DisplayName
|
||||||
IsAdministrator: isAdministrator(account),
|
|
||||||
CompletedVorsPrize: !!inventory.QuestKeys.find(
|
|
||||||
x => x.ItemType == "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain"
|
|
||||||
)?.Completed
|
|
||||||
};
|
};
|
||||||
|
if (isAdministrator(account)) {
|
||||||
|
info.IsAdministrator = true;
|
||||||
|
}
|
||||||
const guildMember = await GuildMember.findOne({ accountId: account._id, status: 0 }, "guildId rank");
|
const guildMember = await GuildMember.findOne({ accountId: account._id, status: 0 }, "guildId rank");
|
||||||
if (guildMember) {
|
if (guildMember) {
|
||||||
const guild = (await Guild.findById(guildMember.guildId, "Ranks AllianceId"))!;
|
const guild = (await Guild.findById(guildMember.guildId, "Ranks AllianceId"))!;
|
||||||
@ -34,8 +31,7 @@ export const getAccountInfoController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
interface IAccountInfo {
|
interface IAccountInfo {
|
||||||
DisplayName: string;
|
DisplayName: string;
|
||||||
IsAdministrator: boolean;
|
IsAdministrator?: boolean;
|
||||||
CompletedVorsPrize: boolean;
|
|
||||||
GuildId?: string;
|
GuildId?: string;
|
||||||
GuildPermissions?: number;
|
GuildPermissions?: number;
|
||||||
GuildRank?: number;
|
GuildRank?: number;
|
||||||
|
@ -3,7 +3,6 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService"
|
|||||||
import {
|
import {
|
||||||
ExportArcanes,
|
ExportArcanes,
|
||||||
ExportAvionics,
|
ExportAvionics,
|
||||||
ExportBoosters,
|
|
||||||
ExportCustoms,
|
ExportCustoms,
|
||||||
ExportDrones,
|
ExportDrones,
|
||||||
ExportGear,
|
ExportGear,
|
||||||
@ -20,12 +19,12 @@ import {
|
|||||||
ExportWeapons,
|
ExportWeapons,
|
||||||
TRelicQuality
|
TRelicQuality
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
|
import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json";
|
||||||
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
|
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
|
||||||
|
|
||||||
interface ListedItem {
|
interface ListedItem {
|
||||||
uniqueName: string;
|
uniqueName: string;
|
||||||
name: string;
|
name: string;
|
||||||
subtype?: string;
|
|
||||||
fusionLimit?: number;
|
fusionLimit?: number;
|
||||||
exalted?: string[];
|
exalted?: string[];
|
||||||
badReason?: "starter" | "frivolous" | "notraw";
|
badReason?: "starter" | "frivolous" | "notraw";
|
||||||
@ -35,6 +34,7 @@ interface ListedItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ItemLists {
|
interface ItemLists {
|
||||||
|
archonCrystalUpgrades: Record<string, string>;
|
||||||
uniqueLevelCaps: Record<string, number>;
|
uniqueLevelCaps: Record<string, number>;
|
||||||
Suits: ListedItem[];
|
Suits: ListedItem[];
|
||||||
LongGuns: ListedItem[];
|
LongGuns: ListedItem[];
|
||||||
@ -54,7 +54,6 @@ interface ItemLists {
|
|||||||
KubrowPets: ListedItem[];
|
KubrowPets: ListedItem[];
|
||||||
EvolutionProgress: ListedItem[];
|
EvolutionProgress: ListedItem[];
|
||||||
mods: ListedItem[];
|
mods: ListedItem[];
|
||||||
Boosters: ListedItem[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
||||||
@ -67,6 +66,7 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
|||||||
const getItemListsController: RequestHandler = (req, response) => {
|
const getItemListsController: RequestHandler = (req, response) => {
|
||||||
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
|
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
|
||||||
const res: ItemLists = {
|
const res: ItemLists = {
|
||||||
|
archonCrystalUpgrades,
|
||||||
uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
|
uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
|
||||||
Suits: [],
|
Suits: [],
|
||||||
LongGuns: [],
|
LongGuns: [],
|
||||||
@ -85,8 +85,7 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
QuestKeys: [],
|
QuestKeys: [],
|
||||||
KubrowPets: [],
|
KubrowPets: [],
|
||||||
EvolutionProgress: [],
|
EvolutionProgress: [],
|
||||||
mods: [],
|
mods: []
|
||||||
Boosters: []
|
|
||||||
};
|
};
|
||||||
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
|
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
|
||||||
res[item.productCategory].push({
|
res[item.productCategory].push({
|
||||||
@ -176,8 +175,7 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
) {
|
) {
|
||||||
res.miscitems.push({
|
res.miscitems.push({
|
||||||
uniqueName: uniqueName,
|
uniqueName: uniqueName,
|
||||||
name: name,
|
name: name
|
||||||
subtype: "Resource"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,8 +193,7 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
for (const [uniqueName, item] of Object.entries(ExportGear)) {
|
for (const [uniqueName, item] of Object.entries(ExportGear)) {
|
||||||
res.miscitems.push({
|
res.miscitems.push({
|
||||||
uniqueName: uniqueName,
|
uniqueName: uniqueName,
|
||||||
name: getString(item.name, lang),
|
name: getString(item.name, lang)
|
||||||
subtype: "Gear"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const recipeNameTemplate = getString("/Lotus/Language/Items/BlueprintAndItem", lang);
|
const recipeNameTemplate = getString("/Lotus/Language/Items/BlueprintAndItem", lang);
|
||||||
@ -296,13 +293,6 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of Object.values(ExportBoosters)) {
|
|
||||||
res.Boosters.push({
|
|
||||||
uniqueName: item.typeName,
|
|
||||||
name: getString(item.name, lang)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
response.json(res);
|
response.json(res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
import { ExportBoosters } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
const I32_MAX = 0x7fffffff;
|
|
||||||
|
|
||||||
export const setBoosterController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const requests = req.body as { ItemType: string; ExpiryDate: number }[];
|
|
||||||
const inventory = await getInventory(accountId, "Boosters");
|
|
||||||
const boosters = inventory.Boosters;
|
|
||||||
if (
|
|
||||||
requests.some(request => {
|
|
||||||
if (typeof request.ItemType !== "string") return true;
|
|
||||||
if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined)
|
|
||||||
return true;
|
|
||||||
if (typeof request.ExpiryDate !== "number") return true;
|
|
||||||
if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true;
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
res.status(400).send("Invalid ItemType provided.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
for (const { ItemType, ExpiryDate } of requests) {
|
|
||||||
if (ExpiryDate < now) {
|
|
||||||
// remove expired boosters
|
|
||||||
const index = boosters.findIndex(item => item.ItemType === ItemType);
|
|
||||||
if (index !== -1) {
|
|
||||||
boosters.splice(index, 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const boosterItem = boosters.find(item => item.ItemType === ItemType);
|
|
||||||
if (boosterItem) {
|
|
||||||
boosterItem.ExpiryDate = ExpiryDate;
|
|
||||||
} else {
|
|
||||||
boosters.push({ ItemType, ExpiryDate });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await inventory.save();
|
|
||||||
res.end();
|
|
||||||
};
|
|
@ -84,10 +84,7 @@ export const getProfileViewingDataGetController: RequestHandler = async (req, re
|
|||||||
res.status(409).send("Could not find requested account");
|
res.status(409).send("Could not find requested account");
|
||||||
}
|
}
|
||||||
} else if (req.query.guildId) {
|
} else if (req.query.guildId) {
|
||||||
const guild = await Guild.findById(
|
const guild = await Guild.findById(req.query.guildId, "Name Tier XP Class Emblem TechProjects ClaimedXP");
|
||||||
req.query.guildId as string,
|
|
||||||
"Name Tier XP Class Emblem TechProjects ClaimedXP"
|
|
||||||
);
|
|
||||||
if (!guild) {
|
if (!guild) {
|
||||||
res.status(409).send("Could not find guild");
|
res.status(409).send("Could not find guild");
|
||||||
return;
|
return;
|
||||||
|
@ -48,8 +48,7 @@ const toDatabaseAccount = (createAccount: IAccountCreation): IDatabaseAccountReq
|
|||||||
CrossPlatformAllowed: true,
|
CrossPlatformAllowed: true,
|
||||||
ForceLogoutVersion: 0,
|
ForceLogoutVersion: 0,
|
||||||
TrackedSettings: [],
|
TrackedSettings: [],
|
||||||
Nonce: 0,
|
Nonce: 0
|
||||||
LastLogin: new Date()
|
|
||||||
} satisfies IDatabaseAccountRequiredFields;
|
} satisfies IDatabaseAccountRequiredFields;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,46 +1,9 @@
|
|||||||
import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { TRarity } from "warframe-public-export-plus";
|
import { TRarity } from "warframe-public-export-plus";
|
||||||
|
|
||||||
export const version_compare = (a: string, b: string): number => {
|
|
||||||
const a_digits = a
|
|
||||||
.split("/")[0]
|
|
||||||
.split(".")
|
|
||||||
.map(x => parseInt(x));
|
|
||||||
const b_digits = b
|
|
||||||
.split("/")[0]
|
|
||||||
.split(".")
|
|
||||||
.map(x => parseInt(x));
|
|
||||||
for (let i = 0; i != a_digits.length; ++i) {
|
|
||||||
if (a_digits[i] != b_digits[i]) {
|
|
||||||
return a_digits[i] > b_digits[i] ? 1 : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const toOid = (objectId: Types.ObjectId): IOid => {
|
export const toOid = (objectId: Types.ObjectId): IOid => {
|
||||||
return { $oid: objectId.toString() };
|
return { $oid: objectId.toString() } satisfies IOid;
|
||||||
};
|
|
||||||
|
|
||||||
export function toOid2(objectId: Types.ObjectId, buildLabel: undefined): IOid;
|
|
||||||
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport;
|
|
||||||
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport {
|
|
||||||
if (buildLabel && version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
|
|
||||||
return { $id: objectId.toString() };
|
|
||||||
}
|
|
||||||
return { $oid: objectId.toString() };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const toLegacyOid = (oid: IOidWithLegacySupport): void => {
|
|
||||||
if (!("$id" in oid)) {
|
|
||||||
oid.$id = oid.$oid;
|
|
||||||
delete oid.$oid;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fromOid = (oid: IOidWithLegacySupport): string => {
|
|
||||||
return (oid.$oid ?? oid.$id)!;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toMongoDate = (date: Date): IMongoDate => {
|
export const toMongoDate = (date: Date): IMongoDate => {
|
||||||
|
@ -1,203 +1,18 @@
|
|||||||
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, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
|
import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { IOid } from "../types/commonTypes";
|
import { IOid } from "../types/commonTypes";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { addMods, generateRewardSeed } from "../services/inventoryService";
|
import { addMods, generateRewardSeed } from "../services/inventoryService";
|
||||||
import { isArchwingMission } from "../services/worldStateService";
|
import { isArchwingMission, version_compare } from "../services/worldStateService";
|
||||||
|
import { fromStoreItem, toStoreItem } from "../services/itemDataService";
|
||||||
|
import { createMessage } from "../services/inboxService";
|
||||||
|
|
||||||
type TInnateDamageTag =
|
export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
|
||||||
| "InnateElectricityDamage"
|
|
||||||
| "InnateHeatDamage"
|
|
||||||
| "InnateFreezeDamage"
|
|
||||||
| "InnateToxinDamage"
|
|
||||||
| "InnateMagDamage"
|
|
||||||
| "InnateRadDamage"
|
|
||||||
| "InnateImpactDamage";
|
|
||||||
|
|
||||||
export interface INemesisManifest {
|
|
||||||
weapons: readonly string[];
|
|
||||||
systemIndexes: readonly number[];
|
|
||||||
showdownNode: string;
|
|
||||||
ephemeraChance: number;
|
|
||||||
ephemeraTypes?: Record<TInnateDamageTag, string>;
|
|
||||||
firstKillReward: string;
|
|
||||||
firstConvertReward: string;
|
|
||||||
messageTitle: string;
|
|
||||||
messageBody: string;
|
|
||||||
minBuild: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class KuvaLichManifest implements INemesisManifest {
|
|
||||||
weapons = [
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
|
||||||
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
|
|
||||||
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
|
|
||||||
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon"
|
|
||||||
];
|
|
||||||
systemIndexes = [2, 3, 9, 11, 18];
|
|
||||||
showdownNode = "CrewBattleNode557";
|
|
||||||
ephemeraChance = 0.05;
|
|
||||||
ephemeraTypes = {
|
|
||||||
InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera",
|
|
||||||
InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaFireEphemera",
|
|
||||||
InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaIceEphemera",
|
|
||||||
InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaToxinEphemera",
|
|
||||||
InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaMagneticEphemera",
|
|
||||||
InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaTricksterEphemera",
|
|
||||||
InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaImpactEphemera"
|
|
||||||
};
|
|
||||||
firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem";
|
|
||||||
firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil";
|
|
||||||
messageTitle = "/Lotus/Language/Inbox/VanquishKuvaMsgTitle";
|
|
||||||
messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
|
|
||||||
minBuild = "2019.10.31.22.42"; // 26.0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
class KuvaLichManifestVersionTwo extends KuvaLichManifest {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.ephemeraChance = 0.1;
|
|
||||||
this.minBuild = "2020.03.05.16.06"; // Unsure about this one, so using the same value as in version three.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class KuvaLichManifestVersionThree extends KuvaLichManifestVersionTwo {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.weapons.push("/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon");
|
|
||||||
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind");
|
|
||||||
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor");
|
|
||||||
this.ephemeraChance = 0.2;
|
|
||||||
this.minBuild = "2020.03.05.16.06"; // This is 27.2.0, tho 27.1.0 should also recognise this.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class KuvaLichManifestVersionFour extends KuvaLichManifestVersionThree {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.minBuild = "2021.07.05.17.03"; // Unsure about this one, so using the same value as in version five.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class KuvaLichManifestVersionFive extends KuvaLichManifestVersionFour {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon");
|
|
||||||
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr");
|
|
||||||
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler");
|
|
||||||
this.minBuild = "2021.07.05.17.03"; // 30.5.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class KuvaLichManifestVersionSix extends KuvaLichManifestVersionFive {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek");
|
|
||||||
this.minBuild = "2024.05.15.11.07"; // 35.6.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LawyerManifest implements INemesisManifest {
|
|
||||||
weapons = [
|
|
||||||
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
|
|
||||||
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
|
|
||||||
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol"
|
|
||||||
];
|
|
||||||
systemIndexes = [1, 15, 4, 7, 8];
|
|
||||||
showdownNode = "CrewBattleNode558";
|
|
||||||
ephemeraChance = 0.2;
|
|
||||||
ephemeraTypes = {
|
|
||||||
InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraA",
|
|
||||||
InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraB",
|
|
||||||
InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraC",
|
|
||||||
InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraD",
|
|
||||||
InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraE",
|
|
||||||
InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraF",
|
|
||||||
InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraG"
|
|
||||||
};
|
|
||||||
firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem";
|
|
||||||
firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil";
|
|
||||||
messageTitle = "/Lotus/Language/Inbox/VanquishLawyerMsgTitle";
|
|
||||||
messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
|
|
||||||
minBuild = "2021.07.05.17.03"; // 30.5.0
|
|
||||||
}
|
|
||||||
|
|
||||||
class LawyerManifestVersionTwo extends LawyerManifest {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.weapons.push("/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon");
|
|
||||||
this.minBuild = "2022.11.30.08.13"; // 32.2.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LawyerManifestVersionThree extends LawyerManifestVersionTwo {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.weapons.push("/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion");
|
|
||||||
this.minBuild = "2024.05.15.11.07"; // 35.6.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LawyerManifestVersionFour extends LawyerManifestVersionThree {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.minBuild = "2024.10.01.11.03"; // 37.0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InfestedLichManfest implements INemesisManifest {
|
|
||||||
weapons = [];
|
|
||||||
systemIndexes = [23];
|
|
||||||
showdownNode = "CrewBattleNode559";
|
|
||||||
ephemeraChance = 0;
|
|
||||||
firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichVanquishedSigil";
|
|
||||||
firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichConvertedSigil";
|
|
||||||
messageTitle = "/Lotus/Language/Inbox/VanquishBandMsgTitle";
|
|
||||||
messageBody = "/Lotus/Language/Inbox/VanquishBandMsgBody";
|
|
||||||
minBuild = "2025.03.18.09.51"; // 38.5.0
|
|
||||||
}
|
|
||||||
|
|
||||||
const nemesisManifests: Record<string, INemesisManifest> = {
|
|
||||||
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifest": new KuvaLichManifest(),
|
|
||||||
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionTwo": new KuvaLichManifestVersionTwo(),
|
|
||||||
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionThree": new KuvaLichManifestVersionThree(),
|
|
||||||
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionFour": new KuvaLichManifestVersionFour(),
|
|
||||||
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionFive": new KuvaLichManifestVersionFive(),
|
|
||||||
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix": new KuvaLichManifestVersionSix(),
|
|
||||||
"/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifest": new LawyerManifest(),
|
|
||||||
"/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionTwo": new LawyerManifestVersionTwo(),
|
|
||||||
"/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree": new LawyerManifestVersionThree(),
|
|
||||||
"/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour": new LawyerManifestVersionFour(),
|
|
||||||
"/Lotus/Types/Enemies/InfestedLich/InfestedLichManifest": new InfestedLichManfest()
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getNemesisManifest = (manifest: string): INemesisManifest => {
|
|
||||||
if (manifest in nemesisManifests) {
|
|
||||||
return nemesisManifests[manifest];
|
|
||||||
}
|
|
||||||
throw new Error(`unknown nemesis manifest: ${manifest}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getInfNodes = (manifest: INemesisManifest, rank: number): IInfNode[] => {
|
|
||||||
const infNodes = [];
|
const infNodes = [];
|
||||||
const systemIndex = manifest.systemIndexes[rank];
|
const systemIndex = systemIndexes[faction][rank];
|
||||||
for (const [key, value] of Object.entries(ExportRegions)) {
|
for (const [key, value] of Object.entries(ExportRegions)) {
|
||||||
if (
|
if (
|
||||||
value.systemIndex === systemIndex &&
|
value.systemIndex === systemIndex &&
|
||||||
@ -219,8 +34,20 @@ export const getInfNodes = (manifest: INemesisManifest, rank: number): IInfNode[
|
|||||||
return infNodes;
|
return infNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const systemIndexes: Record<string, number[]> = {
|
||||||
|
FC_GRINEER: [2, 3, 9, 11, 18],
|
||||||
|
FC_CORPUS: [1, 15, 4, 7, 8],
|
||||||
|
FC_INFESTATION: [23]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showdownNodes: Record<string, string> = {
|
||||||
|
FC_GRINEER: "CrewBattleNode557",
|
||||||
|
FC_CORPUS: "CrewBattleNode558",
|
||||||
|
FC_INFESTATION: "CrewBattleNode559"
|
||||||
|
};
|
||||||
|
|
||||||
// Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
|
// Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
|
||||||
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFaction }): number[] => {
|
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => {
|
||||||
const rng = new SRng(nemesis.fp);
|
const rng = new SRng(nemesis.fp);
|
||||||
const choices = [0, 1, 2, 3, 5, 6, 7];
|
const choices = [0, 1, 2, 3, 5, 6, 7];
|
||||||
let choiceIndex = rng.randomInt(0, choices.length - 1);
|
let choiceIndex = rng.randomInt(0, choices.length - 1);
|
||||||
@ -237,7 +64,7 @@ export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFacti
|
|||||||
return passcode;
|
return passcode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const requiemMods: readonly string[] = [
|
const reqiuemMods: readonly string[] = [
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
|
||||||
@ -259,55 +86,33 @@ const antivirusMods: readonly string[] = [
|
|||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): string[] => {
|
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: string }): string[] => {
|
||||||
const passcode = getNemesisPasscode(nemesis);
|
const passcode = getNemesisPasscode(nemesis);
|
||||||
return nemesis.Faction == "FC_INFESTATION"
|
return nemesis.Faction == "FC_INFESTATION"
|
||||||
? passcode.map(i => antivirusMods[i])
|
? passcode.map(i => antivirusMods[i])
|
||||||
: passcode.map(i => requiemMods[i]);
|
: passcode.map(i => reqiuemMods[i]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Symbols; 0-7 are the normal requiem mods.
|
export const encodeNemesisGuess = (
|
||||||
export const GUESS_NONE = 8;
|
symbol1: number,
|
||||||
export const GUESS_WILDCARD = 9;
|
result1: number,
|
||||||
|
symbol2: number,
|
||||||
// Results; there are 3, 4, 5 as well which are more muted versions but unused afaik.
|
result2: number,
|
||||||
export const GUESS_NEUTRAL = 0;
|
symbol3: number,
|
||||||
export const GUESS_INCORRECT = 1;
|
result3: number
|
||||||
export const GUESS_CORRECT = 2;
|
): number => {
|
||||||
|
|
||||||
interface NemesisPositionGuess {
|
|
||||||
symbol: number;
|
|
||||||
result: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NemesisGuess = [NemesisPositionGuess, NemesisPositionGuess, NemesisPositionGuess];
|
|
||||||
|
|
||||||
export const encodeNemesisGuess = (guess: NemesisGuess): number => {
|
|
||||||
return (
|
return (
|
||||||
(guess[0].symbol & 0xf) |
|
(symbol1 & 0xf) |
|
||||||
((guess[0].result & 3) << 12) |
|
((result1 & 3) << 12) |
|
||||||
((guess[1].symbol << 4) & 0xff) |
|
((symbol2 << 4) & 0xff) |
|
||||||
((guess[1].result << 14) & 0xffff) |
|
((result2 << 14) & 0xffff) |
|
||||||
((guess[2].symbol & 0xf) << 8) |
|
((symbol3 & 0xf) << 8) |
|
||||||
((guess[2].result & 3) << 16)
|
((result3 & 3) << 16)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decodeNemesisGuess = (val: number): NemesisGuess => {
|
export const decodeNemesisGuess = (val: number): number[] => {
|
||||||
return [
|
return [val & 0xf, (val >> 12) & 3, (val & 0xff) >> 4, (val & 0xffff) >> 14, (val >> 8) & 0xf, (val >> 16) & 3];
|
||||||
{
|
|
||||||
symbol: val & 0xf,
|
|
||||||
result: (val >> 12) & 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: (val & 0xff) >> 4,
|
|
||||||
result: (val & 0xffff) >> 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: (val >> 8) & 0xf,
|
|
||||||
result: (val >> 16) & 3
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IKnifeResponse {
|
export interface IKnifeResponse {
|
||||||
@ -394,31 +199,92 @@ export const consumeModCharge = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
|
const kuvaLichVersionSixWeapons = [
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
||||||
|
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
|
||||||
|
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
|
||||||
|
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
|
||||||
|
"/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
|
||||||
|
];
|
||||||
|
|
||||||
|
const corpusVersionThreeWeapons = [
|
||||||
|
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
|
||||||
|
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
|
||||||
|
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getWeaponsForManifest = (manifest: string): readonly string[] => {
|
||||||
|
switch (manifest) {
|
||||||
|
case "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix": // >= 35.6.0
|
||||||
|
return kuvaLichVersionSixWeapons;
|
||||||
|
case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree": // >= 35.6.0
|
||||||
|
case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour": // >= 37.0.0
|
||||||
|
return corpusVersionThreeWeapons;
|
||||||
|
}
|
||||||
|
throw new Error(`unknown nemesis manifest: ${manifest}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isNemesisCompatibleWithVersion = (
|
||||||
|
nemesis: { manifest: string; Faction: string },
|
||||||
|
buildLabel: string
|
||||||
|
): boolean => {
|
||||||
|
// Anything below 35.6.0 is not going to be okay given our set of supported manifests.
|
||||||
|
if (version_compare(buildLabel, "2024.05.15.11.07") < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nemesis.Faction == "FC_INFESTATION") {
|
||||||
|
// Anything below 38.5.0 isn't gonna like an infested lich.
|
||||||
|
if (version_compare(buildLabel, "2025.03.18.16.07") < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (nemesis.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour") {
|
||||||
|
// Anything below 37.0.0 isn't gonna know version 4, but version 3 is identical in terms of weapon choices, so we can spoof it to that.
|
||||||
|
if (version_compare(buildLabel, "2024.10.01.11.03") < 0) {
|
||||||
|
nemesis.manifest = "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInnateDamageTag = (
|
||||||
|
KillingSuit: string
|
||||||
|
):
|
||||||
|
| "InnateElectricityDamage"
|
||||||
|
| "InnateFreezeDamage"
|
||||||
|
| "InnateHeatDamage"
|
||||||
|
| "InnateImpactDamage"
|
||||||
|
| "InnateMagDamage"
|
||||||
|
| "InnateRadDamage"
|
||||||
|
| "InnateToxinDamage" => {
|
||||||
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
||||||
};
|
};
|
||||||
|
|
||||||
const petHeads = [
|
// TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
|
export const getInnateDamageValue = (fp: bigint): number => {
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export interface INemesisProfile {
|
|
||||||
innateDamageTag: TInnateDamageTag;
|
|
||||||
innateDamageValue: number;
|
|
||||||
ephemera?: string;
|
|
||||||
petHead?: (typeof petHeads)[number];
|
|
||||||
petBody?: string;
|
|
||||||
petLegs?: string;
|
|
||||||
petTail?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateNemesisProfile = (
|
|
||||||
fp: bigint = generateRewardSeed(),
|
|
||||||
manifest: INemesisManifest = new LawyerManifest(),
|
|
||||||
killingSuit: string = "/Lotus/Powersuits/Ember/Ember"
|
|
||||||
): INemesisProfile => {
|
|
||||||
const rng = new SRng(fp);
|
const rng = new SRng(fp);
|
||||||
rng.randomFloat(); // used for the weapon index
|
rng.randomFloat(); // used for the weapon index
|
||||||
const WeaponUpgradeValueAttenuationExponent = 2.25;
|
const WeaponUpgradeValueAttenuationExponent = 2.25;
|
||||||
@ -426,33 +292,7 @@ export const generateNemesisProfile = (
|
|||||||
if (value >= 0.941428) {
|
if (value >= 0.941428) {
|
||||||
value = 1;
|
value = 1;
|
||||||
}
|
}
|
||||||
const profile: INemesisProfile = {
|
return Math.trunc(value * 0x40000000);
|
||||||
innateDamageTag: getInnateDamageTag(killingSuit),
|
|
||||||
innateDamageValue: Math.trunc(value * 0x40000000) // TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
|
|
||||||
};
|
|
||||||
if (rng.randomFloat() <= manifest.ephemeraChance && manifest.ephemeraTypes) {
|
|
||||||
profile.ephemera = manifest.ephemeraTypes[profile.innateDamageTag];
|
|
||||||
}
|
|
||||||
rng.randomFloat(); // something related to sentinel agent maybe
|
|
||||||
if (manifest instanceof LawyerManifest) {
|
|
||||||
profile.petHead = rng.randomElement(petHeads)!;
|
|
||||||
profile.petBody = rng.randomElement([
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyC"
|
|
||||||
])!;
|
|
||||||
profile.petLegs = rng.randomElement([
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsA",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsB",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsC"
|
|
||||||
])!;
|
|
||||||
profile.petTail = rng.randomElement([
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailA",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailB",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailC"
|
|
||||||
])!;
|
|
||||||
}
|
|
||||||
return profile;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getKillTokenRewardCount = (fp: bigint): number => {
|
export const getKillTokenRewardCount = (fp: bigint): number => {
|
||||||
@ -511,3 +351,52 @@ export const getInfestedLichItemRewards = (fp: bigint): string[] => {
|
|||||||
const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type;
|
const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type;
|
||||||
return [rotAReward, rotBReward];
|
return [rotAReward, rotBReward];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sendCodaFinishedMessage = async (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
fp: bigint = generateRewardSeed(),
|
||||||
|
name: string = "ZEKE_BEATWOMAN_TM.1999",
|
||||||
|
killed: boolean = true
|
||||||
|
): Promise<void> => {
|
||||||
|
const att: string[] = [];
|
||||||
|
|
||||||
|
// First vanquish/convert gives a sigil
|
||||||
|
const sigil = killed
|
||||||
|
? "/Lotus/Upgrades/Skins/Sigils/InfLichVanquishedSigil"
|
||||||
|
: "/Lotus/Upgrades/Skins/Sigils/InfLichConvertedSigil";
|
||||||
|
if (!inventory.WeaponSkins.find(x => x.ItemType == sigil)) {
|
||||||
|
att.push(toStoreItem(sigil));
|
||||||
|
}
|
||||||
|
|
||||||
|
const [rotAReward, rotBReward] = getInfestedLichItemRewards(fp);
|
||||||
|
att.push(fromStoreItem(rotAReward));
|
||||||
|
att.push(fromStoreItem(rotBReward));
|
||||||
|
|
||||||
|
let countedAtt: ITypeCount[] | undefined;
|
||||||
|
if (killed) {
|
||||||
|
countedAtt = [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
|
||||||
|
ItemCount: getKillTokenRewardCount(fp)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
await createMessage(inventory.accountOwnerId, [
|
||||||
|
{
|
||||||
|
sndr: "/Lotus/Language/Bosses/Ordis",
|
||||||
|
msg: "/Lotus/Language/Inbox/VanquishBandMsgBody",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "LICH_NAME",
|
||||||
|
Tag: name
|
||||||
|
}
|
||||||
|
],
|
||||||
|
att: att,
|
||||||
|
countedAtt: countedAtt,
|
||||||
|
sub: "/Lotus/Language/Inbox/VanquishBandMsgTitle",
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
|
||||||
|
highPriority: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export const rootDir = path.join(__dirname, "../..");
|
export const rootDir = path.join(__dirname, "../..");
|
||||||
export const isDev = path.basename(rootDir) != "build";
|
export const repoDir = path.basename(rootDir) == "build" ? path.join(rootDir, "..") : rootDir;
|
||||||
export const repoDir = isDev ? rootDir : path.join(rootDir, "..");
|
|
||||||
|
@ -2,7 +2,7 @@ import { JSONParse } from "json-with-bigint";
|
|||||||
|
|
||||||
export const getJSONfromString = <T>(str: string): T => {
|
export const getJSONfromString = <T>(str: string): T => {
|
||||||
const jsonSubstring = str.substring(0, str.lastIndexOf("}") + 1);
|
const jsonSubstring = str.substring(0, str.lastIndexOf("}") + 1);
|
||||||
return JSONParse(jsonSubstring) as T;
|
return JSONParse<T>(jsonSubstring);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSubstringFromKeyword = (str: string, keyword: string): string => {
|
export const getSubstringFromKeyword = (str: string, keyword: string): string => {
|
||||||
|
@ -10,14 +10,3 @@ export const getMaxStanding = (syndicate: ISyndicate, title: number): number =>
|
|||||||
}
|
}
|
||||||
return syndicate.titles.find(x => x.level == title)!.maxStanding;
|
return syndicate.titles.find(x => x.level == title)!.maxStanding;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMinStanding = (syndicate: ISyndicate, title: number): number => {
|
|
||||||
if (!syndicate.titles) {
|
|
||||||
// LibrarySyndicate
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (title == 0) {
|
|
||||||
return syndicate.titles.find(x => x.level == -1)!.maxStanding;
|
|
||||||
}
|
|
||||||
return syndicate.titles.find(x => x.level == title)!.minStanding;
|
|
||||||
};
|
|
||||||
|
40
src/index.ts
40
src/index.ts
@ -1,14 +1,9 @@
|
|||||||
// First, init config.
|
// First, init config.
|
||||||
import { config, loadConfig } from "@/src/services/configService";
|
import { config, loadConfig } from "@/src/services/configService";
|
||||||
import fs from "fs";
|
|
||||||
try {
|
try {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (fs.existsSync("config.json")) {
|
console.log("ERROR: Failed to load config.json. You can copy config.json.example to create your config.json.");
|
||||||
console.log("Failed to load config.json: " + (e as Error).message);
|
|
||||||
} else {
|
|
||||||
console.log("Failed to load config.json. You can copy config.json.example to create your config.json.");
|
|
||||||
}
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,14 +12,18 @@ import { logger } from "@/src/utils/logger";
|
|||||||
logger.info("Starting up...");
|
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 http from "http";
|
||||||
|
import https from "https";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import { app } from "./app";
|
||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { JSONStringify } from "json-with-bigint";
|
import { Json, JSONStringify } from "json-with-bigint";
|
||||||
import { startWebServer } from "./services/webService";
|
|
||||||
|
|
||||||
import { validateConfig } from "@/src/services/configWatcherService";
|
import { validateConfig } from "@/src/services/configWatcherService";
|
||||||
|
|
||||||
// Patch JSON.stringify to work flawlessly with Bigints.
|
// Patch JSON.stringify to work flawlessly with Bigints.
|
||||||
JSON.stringify = JSONStringify;
|
JSON.stringify = (obj: Exclude<Json, undefined>, _replacer?: unknown, space?: string | number): string => {
|
||||||
|
return JSONStringify(obj, space);
|
||||||
|
};
|
||||||
|
|
||||||
validateConfig();
|
validateConfig();
|
||||||
|
|
||||||
@ -32,7 +31,26 @@ mongoose
|
|||||||
.connect(config.mongodbUrl)
|
.connect(config.mongodbUrl)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info("Connected to MongoDB");
|
logger.info("Connected to MongoDB");
|
||||||
startWebServer();
|
|
||||||
|
const httpPort = config.httpPort || 80;
|
||||||
|
const httpsPort = config.httpsPort || 443;
|
||||||
|
const options = {
|
||||||
|
key: fs.readFileSync("static/certs/key.pem"),
|
||||||
|
cert: fs.readFileSync("static/certs/cert.pem")
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
http.createServer(app).listen(httpPort, () => {
|
||||||
|
logger.info("HTTP server started on port " + httpPort);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
https.createServer(options, app).listen(httpsPort, () => {
|
||||||
|
logger.info("HTTPS server started on port " + httpsPort);
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { ISession, IFindSessionRequest } from "@/src/types/session";
|
import { ISession, IFindSessionRequest } from "@/src/types/session";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { JSONParse } from "json-with-bigint";
|
|
||||||
import { Types } from "mongoose";
|
|
||||||
|
|
||||||
const sessions: ISession[] = [];
|
const sessions: ISession[] = [];
|
||||||
|
|
||||||
function createNewSession(sessionData: ISession, Creator: Types.ObjectId): ISession {
|
function createNewSession(sessionData: ISession, Creator: string): ISession {
|
||||||
const sessionId = new Types.ObjectId();
|
const sessionId = getNewSessionID();
|
||||||
const newSession: ISession = {
|
const newSession: ISession = {
|
||||||
sessionId,
|
sessionId,
|
||||||
creatorId: Creator,
|
creatorId: Creator,
|
||||||
@ -27,7 +25,7 @@ function createNewSession(sessionData: ISession, Creator: Types.ObjectId): ISess
|
|||||||
customSettings: sessionData.customSettings || "",
|
customSettings: sessionData.customSettings || "",
|
||||||
rewardSeed: sessionData.rewardSeed || -1,
|
rewardSeed: sessionData.rewardSeed || -1,
|
||||||
guildId: sessionData.guildId || "",
|
guildId: sessionData.guildId || "",
|
||||||
buildId: sessionData.buildId || 4920386201513015989n,
|
buildId: sessionData.buildId || 4920386201513015989,
|
||||||
platform: sessionData.platform || 0,
|
platform: sessionData.platform || 0,
|
||||||
xplatform: sessionData.xplatform || true,
|
xplatform: sessionData.xplatform || true,
|
||||||
freePublic: sessionData.freePublic || 3,
|
freePublic: sessionData.freePublic || 3,
|
||||||
@ -42,15 +40,13 @@ function getAllSessions(): ISession[] {
|
|||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSessionByID(sessionId: string | Types.ObjectId): ISession | undefined {
|
function getSessionByID(sessionId: string): ISession | undefined {
|
||||||
return sessions.find(session => session.sessionId.equals(sessionId));
|
return sessions.find(session => session.sessionId === sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSession(
|
function getSession(sessionIdOrRequest: string | IFindSessionRequest): { createdBy: string; id: string }[] {
|
||||||
sessionIdOrRequest: string | Types.ObjectId | IFindSessionRequest
|
if (typeof sessionIdOrRequest === "string") {
|
||||||
): { createdBy: Types.ObjectId; id: Types.ObjectId }[] {
|
const session = sessions.find(session => session.sessionId === sessionIdOrRequest);
|
||||||
if (typeof sessionIdOrRequest === "string" || sessionIdOrRequest instanceof Types.ObjectId) {
|
|
||||||
const session = sessions.find(session => session.sessionId.equals(sessionIdOrRequest));
|
|
||||||
if (session) {
|
if (session) {
|
||||||
logger.debug("Found Sessions:", { session });
|
logger.debug("Found Sessions:", { session });
|
||||||
return [
|
return [
|
||||||
@ -83,15 +79,35 @@ function getSession(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSessionByCreatorID(creatorId: string | Types.ObjectId): ISession | undefined {
|
function getSessionByCreatorID(creatorId: string): ISession | undefined {
|
||||||
return sessions.find(session => session.creatorId.equals(creatorId));
|
return sessions.find(session => session.creatorId === creatorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSession(sessionId: string | Types.ObjectId, sessionData: string): boolean {
|
function getNewSessionID(): string {
|
||||||
const session = sessions.find(session => session.sessionId.equals(sessionId));
|
const characters = "0123456789abcdef";
|
||||||
|
const maxAttempts = 100;
|
||||||
|
let sessionId = "";
|
||||||
|
|
||||||
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||||
|
sessionId = "64";
|
||||||
|
for (let i = 0; i < 22; i++) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||||
|
sessionId += characters[randomIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sessions.some(session => session.sessionId === sessionId)) {
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Failed to generate a unique session ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSession(sessionId: string, sessionData: string): boolean {
|
||||||
|
const session = sessions.find(session => session.sessionId === sessionId);
|
||||||
if (!session) return false;
|
if (!session) return false;
|
||||||
try {
|
try {
|
||||||
Object.assign(session, JSONParse(sessionData));
|
Object.assign(session, JSON.parse(sessionData));
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Invalid JSON string for session update.");
|
console.error("Invalid JSON string for session update.");
|
||||||
@ -99,8 +115,8 @@ function updateSession(sessionId: string | Types.ObjectId, sessionData: string):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteSession(sessionId: string | Types.ObjectId): boolean {
|
function deleteSession(sessionId: string): boolean {
|
||||||
const index = sessions.findIndex(session => session.sessionId.equals(sessionId));
|
const index = sessions.findIndex(session => session.sessionId === sessionId);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
sessions.splice(index, 1);
|
sessions.splice(index, 1);
|
||||||
return true;
|
return true;
|
||||||
@ -113,6 +129,7 @@ export {
|
|||||||
getAllSessions,
|
getAllSessions,
|
||||||
getSessionByID,
|
getSessionByID,
|
||||||
getSessionByCreatorID,
|
getSessionByCreatorID,
|
||||||
|
getNewSessionID,
|
||||||
updateSession,
|
updateSession,
|
||||||
deleteSession,
|
deleteSession,
|
||||||
getSession
|
getSession
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import { IFriendship } from "@/src/types/friendTypes";
|
|
||||||
import { model, Schema } from "mongoose";
|
|
||||||
|
|
||||||
const friendshipSchema = new Schema<IFriendship>({
|
|
||||||
owner: { type: Schema.Types.ObjectId, required: true },
|
|
||||||
friend: { type: Schema.Types.ObjectId, required: true },
|
|
||||||
Note: String,
|
|
||||||
Favorite: Boolean
|
|
||||||
});
|
|
||||||
|
|
||||||
friendshipSchema.index({ owner: 1 });
|
|
||||||
friendshipSchema.index({ friend: 1 });
|
|
||||||
friendshipSchema.index({ owner: 1, friend: 1 }, { unique: true });
|
|
||||||
|
|
||||||
export const Friendship = model<IFriendship>("Friendship", friendshipSchema);
|
|
@ -13,8 +13,7 @@ import {
|
|||||||
IDojoLeaderboardEntry,
|
IDojoLeaderboardEntry,
|
||||||
IGuildAdDatabase,
|
IGuildAdDatabase,
|
||||||
IAllianceDatabase,
|
IAllianceDatabase,
|
||||||
IAllianceMemberDatabase,
|
IAllianceMemberDatabase
|
||||||
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 "./inventoryModels/inventoryModel";
|
||||||
@ -109,31 +108,31 @@ const defaultRanks: IGuildRank[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "/Lotus/Language/Game/Rank_General",
|
Name: "/Lotus/Language/Game/Rank_General",
|
||||||
Permissions: GuildPermission.Host | 4318
|
Permissions: 4318
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "/Lotus/Language/Game/Rank_Officer",
|
Name: "/Lotus/Language/Game/Rank_Officer",
|
||||||
Permissions: GuildPermission.Host | 4314
|
Permissions: 4314
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "/Lotus/Language/Game/Rank_Leader",
|
Name: "/Lotus/Language/Game/Rank_Leader",
|
||||||
Permissions: GuildPermission.Host | 4106
|
Permissions: 4106
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "/Lotus/Language/Game/Rank_Sage",
|
Name: "/Lotus/Language/Game/Rank_Sage",
|
||||||
Permissions: GuildPermission.Host | 4304
|
Permissions: 4304
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "/Lotus/Language/Game/Rank_Soldier",
|
Name: "/Lotus/Language/Game/Rank_Soldier",
|
||||||
Permissions: GuildPermission.Host | 4098
|
Permissions: 4098
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "/Lotus/Language/Game/Rank_Initiate",
|
Name: "/Lotus/Language/Game/Rank_Initiate",
|
||||||
Permissions: GuildPermission.Host | GuildPermission.Fabricator
|
Permissions: 4096
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "/Lotus/Language/Game/Rank_Utility",
|
Name: "/Lotus/Language/Game/Rank_Utility",
|
||||||
Permissions: GuildPermission.Host | GuildPermission.Fabricator
|
Permissions: 4096
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -38,8 +38,7 @@ import {
|
|||||||
IPeriodicMissionCompletionResponse,
|
IPeriodicMissionCompletionResponse,
|
||||||
ILoreFragmentScan,
|
ILoreFragmentScan,
|
||||||
IEvolutionProgress,
|
IEvolutionProgress,
|
||||||
IEndlessXpProgressDatabase,
|
IEndlessXpProgress,
|
||||||
IEndlessXpProgressClient,
|
|
||||||
ICrewShipCustomization,
|
ICrewShipCustomization,
|
||||||
ICrewShipWeapon,
|
ICrewShipWeapon,
|
||||||
ICrewShipWeaponEmplacements,
|
ICrewShipWeaponEmplacements,
|
||||||
@ -98,8 +97,7 @@ import {
|
|||||||
IInvasionProgressClient,
|
IInvasionProgressClient,
|
||||||
IAccolades,
|
IAccolades,
|
||||||
IHubNpcCustomization,
|
IHubNpcCustomization,
|
||||||
ILotusCustomization,
|
ILotusCustomization
|
||||||
IEndlessXpReward
|
|
||||||
} from "../../types/inventoryTypes/inventoryTypes";
|
} from "../../types/inventoryTypes/inventoryTypes";
|
||||||
import { IOid } from "../../types/commonTypes";
|
import { IOid } from "../../types/commonTypes";
|
||||||
import {
|
import {
|
||||||
@ -114,7 +112,6 @@ import {
|
|||||||
} 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 "./loadoutModel";
|
||||||
import { ICountedStoreItem } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
|
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
|
||||||
|
|
||||||
@ -813,48 +810,14 @@ const evolutionProgressSchema = new Schema<IEvolutionProgress>(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
const countedStoreItemSchema = new Schema<ICountedStoreItem>(
|
const endlessXpProgressSchema = new Schema<IEndlessXpProgress>(
|
||||||
{
|
{
|
||||||
StoreItem: String,
|
Category: String,
|
||||||
ItemCount: Number
|
Choices: [String]
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
const endlessXpRewardSchema = new Schema<IEndlessXpReward>(
|
|
||||||
{
|
|
||||||
RequiredTotalXp: Number,
|
|
||||||
Rewards: [countedStoreItemSchema]
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const endlessXpProgressSchema = new Schema<IEndlessXpProgressDatabase>(
|
|
||||||
{
|
|
||||||
Category: { type: String, required: true },
|
|
||||||
Earn: { type: Number, default: 0 },
|
|
||||||
Claim: { type: Number, default: 0 },
|
|
||||||
BonusAvailable: Date,
|
|
||||||
Expiry: Date,
|
|
||||||
Choices: { type: [String], required: true },
|
|
||||||
PendingRewards: { type: [endlessXpRewardSchema], default: [] }
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
endlessXpProgressSchema.set("toJSON", {
|
|
||||||
transform(_doc, ret) {
|
|
||||||
const db = ret as IEndlessXpProgressDatabase;
|
|
||||||
const client = ret as IEndlessXpProgressClient;
|
|
||||||
|
|
||||||
if (db.BonusAvailable) {
|
|
||||||
client.BonusAvailable = toMongoDate(db.BonusAvailable);
|
|
||||||
}
|
|
||||||
if (db.Expiry) {
|
|
||||||
client.Expiry = toMongoDate(db.Expiry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const crewShipWeaponEmplacementsSchema = new Schema<ICrewShipWeaponEmplacements>(
|
const crewShipWeaponEmplacementsSchema = new Schema<ICrewShipWeaponEmplacements>(
|
||||||
{
|
{
|
||||||
PRIMARY_A: EquipmentSelectionSchema,
|
PRIMARY_A: EquipmentSelectionSchema,
|
||||||
@ -1097,8 +1060,7 @@ const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
|
|||||||
LongGuns: { type: [EquipmentSchema], default: undefined },
|
LongGuns: { type: [EquipmentSchema], default: undefined },
|
||||||
Pistols: { type: [EquipmentSchema], default: undefined },
|
Pistols: { type: [EquipmentSchema], default: undefined },
|
||||||
Melee: { type: [EquipmentSchema], default: undefined },
|
Melee: { type: [EquipmentSchema], default: undefined },
|
||||||
SuitToUnbrand: { type: Schema.Types.ObjectId, default: undefined },
|
SuitToUnbrand: { type: Schema.Types.ObjectId, default: undefined }
|
||||||
KubrowPet: { type: Schema.Types.ObjectId, default: undefined }
|
|
||||||
},
|
},
|
||||||
{ id: false }
|
{ id: false }
|
||||||
);
|
);
|
||||||
@ -1116,7 +1078,6 @@ pendingRecipeSchema.set("toJSON", {
|
|||||||
delete returnedObject.Pistols;
|
delete returnedObject.Pistols;
|
||||||
delete returnedObject.Melees;
|
delete returnedObject.Melees;
|
||||||
delete returnedObject.SuitToUnbrand;
|
delete returnedObject.SuitToUnbrand;
|
||||||
delete returnedObject.KubrowPet;
|
|
||||||
(returnedObject as IPendingRecipeClient).CompletionDate = {
|
(returnedObject as IPendingRecipeClient).CompletionDate = {
|
||||||
$date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() }
|
$date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() }
|
||||||
};
|
};
|
||||||
@ -1320,7 +1281,7 @@ const nemesisSchema = new Schema<INemesisDatabase>(
|
|||||||
InfNodes: { type: [infNodeSchema], default: undefined },
|
InfNodes: { type: [infNodeSchema], default: undefined },
|
||||||
HenchmenKilled: Number,
|
HenchmenKilled: Number,
|
||||||
HintProgress: Number,
|
HintProgress: Number,
|
||||||
Hints: { type: [Number], default: [] },
|
Hints: { type: [Number], default: undefined },
|
||||||
GuessHistory: { type: [Number], default: undefined },
|
GuessHistory: { type: [Number], default: undefined },
|
||||||
MissionCount: Number,
|
MissionCount: Number,
|
||||||
LastEnc: Number
|
LastEnc: Number
|
||||||
@ -1623,7 +1584,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
Drones: [droneSchema],
|
Drones: [droneSchema],
|
||||||
|
|
||||||
//Active profile ico
|
//Active profile ico
|
||||||
ActiveAvatarImageType: String,
|
ActiveAvatarImageType: { type: String, default: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageDefault" },
|
||||||
|
|
||||||
// open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable
|
// open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable
|
||||||
DiscoveredMarkers: [discoveredMarkerSchema],
|
DiscoveredMarkers: [discoveredMarkerSchema],
|
||||||
|
@ -22,7 +22,6 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
|
|||||||
Nonce: { type: Number, default: 0 },
|
Nonce: { type: Number, default: 0 },
|
||||||
BuildLabel: String,
|
BuildLabel: String,
|
||||||
Dropped: Boolean,
|
Dropped: Boolean,
|
||||||
LastLogin: { type: Date, default: 0 },
|
|
||||||
LatestEventMessageDate: { type: Date, default: 0 },
|
LatestEventMessageDate: { type: Date, default: 0 },
|
||||||
LastLoginRewardDate: { type: Number, default: 0 },
|
LastLoginRewardDate: { type: Number, default: 0 },
|
||||||
LoginDays: { type: Number, default: 1 }
|
LoginDays: { type: Number, default: 1 }
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
IPlantDatabase,
|
IPlantDatabase,
|
||||||
IPlantClient
|
IPlantClient
|
||||||
} from "@/src/types/shipTypes";
|
} from "@/src/types/shipTypes";
|
||||||
import { Schema, Types, model } from "mongoose";
|
import { Schema, model } from "mongoose";
|
||||||
|
|
||||||
export const pictureFrameInfoSchema = new Schema<IPictureFrameInfo>(
|
export const pictureFrameInfoSchema = new Schema<IPictureFrameInfo>(
|
||||||
{
|
{
|
||||||
@ -153,18 +153,7 @@ const orbiterDefault: IOrbiter = {
|
|||||||
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 },
|
||||||
{
|
{ Name: "BridgeRoom", MaxCapacity: 1600 },
|
||||||
Name: "BridgeRoom",
|
|
||||||
MaxCapacity: 1600,
|
|
||||||
PlacedDecos: [
|
|
||||||
{
|
|
||||||
Type: "/Lotus/Objects/Tenno/Props/Ships/LandCraftPlayerProps/ConclaveConsolePlayerShipDeco",
|
|
||||||
Pos: [-30.082, -3.95954, -16.7913],
|
|
||||||
Rot: [-135, 0, 0],
|
|
||||||
_id: undefined as unknown as Types.ObjectId
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{ Name: "LisetRoom", MaxCapacity: 1000 },
|
{ Name: "LisetRoom", MaxCapacity: 1000 },
|
||||||
{ Name: "OperatorChamberRoom", MaxCapacity: 1600 },
|
{ Name: "OperatorChamberRoom", MaxCapacity: 1600 },
|
||||||
{ Name: "OutsideRoom", MaxCapacity: 1600 },
|
{ Name: "OutsideRoom", MaxCapacity: 1600 },
|
||||||
|
@ -28,16 +28,18 @@ shipSchema.set("toJSON", {
|
|||||||
delete returnedObject._id;
|
delete returnedObject._id;
|
||||||
delete returnedObject.__v;
|
delete returnedObject.__v;
|
||||||
delete returnedObject.ShipOwnerId;
|
delete returnedObject.ShipOwnerId;
|
||||||
|
if (shipDatabase.ShipExteriorColors) {
|
||||||
shipResponse.ShipExterior = {
|
shipResponse.ShipExterior = {
|
||||||
Colors: shipDatabase.ShipExteriorColors,
|
Colors: shipDatabase.ShipExteriorColors,
|
||||||
ShipAttachments: shipDatabase.ShipAttachments,
|
ShipAttachments: shipDatabase.ShipAttachments,
|
||||||
SkinFlavourItem: shipDatabase.SkinFlavourItem
|
SkinFlavourItem: shipDatabase.SkinFlavourItem
|
||||||
};
|
};
|
||||||
|
|
||||||
delete shipDatabase.ShipExteriorColors;
|
delete shipDatabase.ShipExteriorColors;
|
||||||
delete shipDatabase.ShipAttachments;
|
delete shipDatabase.ShipAttachments;
|
||||||
delete shipDatabase.SkinFlavourItem;
|
delete shipDatabase.SkinFlavourItem;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
shipSchema.set("toObject", {
|
shipSchema.set("toObject", {
|
||||||
|
@ -3,13 +3,10 @@ import { abandonLibraryDailyTaskController } from "@/src/controllers/api/abandon
|
|||||||
import { abortDojoComponentController } from "@/src/controllers/api/abortDojoComponentController";
|
import { abortDojoComponentController } from "@/src/controllers/api/abortDojoComponentController";
|
||||||
import { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController";
|
import { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController";
|
||||||
import { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
|
import { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
|
||||||
import { addFriendController } from "@/src/controllers/api/addFriendController";
|
|
||||||
import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
|
import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
|
||||||
import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserController";
|
import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserController";
|
||||||
import { addPendingFriendController } from "@/src/controllers/api/addPendingFriendController";
|
|
||||||
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
|
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
|
||||||
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
|
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
|
||||||
import { adoptPetController } from "@/src/controllers/api/adoptPetController";
|
|
||||||
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
|
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
|
||||||
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
|
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
|
||||||
import { artifactsController } from "@/src/controllers/api/artifactsController";
|
import { artifactsController } from "@/src/controllers/api/artifactsController";
|
||||||
@ -100,15 +97,12 @@ import { playerSkillsController } from "@/src/controllers/api/playerSkillsContro
|
|||||||
import { postGuildAdvertisementController } from "@/src/controllers/api/postGuildAdvertisementController";
|
import { postGuildAdvertisementController } from "@/src/controllers/api/postGuildAdvertisementController";
|
||||||
import { projectionManagerController } from "@/src/controllers/api/projectionManagerController";
|
import { projectionManagerController } from "@/src/controllers/api/projectionManagerController";
|
||||||
import { purchaseController } from "@/src/controllers/api/purchaseController";
|
import { purchaseController } from "@/src/controllers/api/purchaseController";
|
||||||
import { questControlController } from "@/src/controllers/api/questControlController";
|
|
||||||
import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
|
import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
|
||||||
import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCodeController";
|
import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCodeController";
|
||||||
import { releasePetController } from "@/src/controllers/api/releasePetController";
|
import { releasePetController } from "@/src/controllers/api/releasePetController";
|
||||||
import { removeFriendGetController, removeFriendPostController } from "@/src/controllers/api/removeFriendController";
|
|
||||||
import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
|
import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
|
||||||
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
|
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
|
||||||
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
|
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
|
||||||
import { renamePetController } from "@/src/controllers/api/renamePetController";
|
|
||||||
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
|
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
|
||||||
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";
|
||||||
@ -125,7 +119,6 @@ import { setDojoComponentColorsController } from "@/src/controllers/api/setDojoC
|
|||||||
import { setDojoComponentMessageController } from "@/src/controllers/api/setDojoComponentMessageController";
|
import { setDojoComponentMessageController } from "@/src/controllers/api/setDojoComponentMessageController";
|
||||||
import { setDojoComponentSettingsController } from "@/src/controllers/api/setDojoComponentSettingsController";
|
import { setDojoComponentSettingsController } from "@/src/controllers/api/setDojoComponentSettingsController";
|
||||||
import { setEquippedInstrumentController } from "@/src/controllers/api/setEquippedInstrumentController";
|
import { setEquippedInstrumentController } from "@/src/controllers/api/setEquippedInstrumentController";
|
||||||
import { setFriendNoteController } from "@/src/controllers/api/setFriendNoteController";
|
|
||||||
import { setGuildMotdController } from "@/src/controllers/api/setGuildMotdController";
|
import { setGuildMotdController } from "@/src/controllers/api/setGuildMotdController";
|
||||||
import { setHubNpcCustomizationsController } from "@/src/controllers/api/setHubNpcCustomizationsController";
|
import { setHubNpcCustomizationsController } from "@/src/controllers/api/setHubNpcCustomizationsController";
|
||||||
import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDecoInfoController";
|
import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDecoInfoController";
|
||||||
@ -186,10 +179,8 @@ apiRouter.get("/getGuildContributions.php", getGuildContributionsController);
|
|||||||
apiRouter.get("/getGuildDojo.php", getGuildDojoController);
|
apiRouter.get("/getGuildDojo.php", getGuildDojoController);
|
||||||
apiRouter.get("/getGuildLog.php", getGuildLogController);
|
apiRouter.get("/getGuildLog.php", getGuildLogController);
|
||||||
apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
|
apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
|
||||||
apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
|
|
||||||
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
|
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
|
||||||
apiRouter.get("/getShip.php", getShipController);
|
apiRouter.get("/getShip.php", getShipController);
|
||||||
apiRouter.get("/getShipDecos.php", (_req, res) => { res.end(); }); // needed to log in on U22.8
|
|
||||||
apiRouter.get("/getVendorInfo.php", getVendorInfoController);
|
apiRouter.get("/getVendorInfo.php", getVendorInfoController);
|
||||||
apiRouter.get("/hub", hubController);
|
apiRouter.get("/hub", hubController);
|
||||||
apiRouter.get("/hubInstances", hubInstancesController);
|
apiRouter.get("/hubInstances", hubInstancesController);
|
||||||
@ -201,9 +192,7 @@ apiRouter.get("/marketRecommendations.php", marketRecommendationsController);
|
|||||||
apiRouter.get("/marketSearchRecommendations.php", marketRecommendationsController);
|
apiRouter.get("/marketSearchRecommendations.php", marketRecommendationsController);
|
||||||
apiRouter.get("/modularWeaponSale.php", modularWeaponSaleController);
|
apiRouter.get("/modularWeaponSale.php", modularWeaponSaleController);
|
||||||
apiRouter.get("/playedParkourTutorial.php", playedParkourTutorialController);
|
apiRouter.get("/playedParkourTutorial.php", playedParkourTutorialController);
|
||||||
apiRouter.get("/questControl.php", questControlController);
|
|
||||||
apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController);
|
apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController);
|
||||||
apiRouter.get("/removeFriend.php", removeFriendGetController);
|
|
||||||
apiRouter.get("/removeFromAlliance.php", removeFromAllianceController);
|
apiRouter.get("/removeFromAlliance.php", removeFromAllianceController);
|
||||||
apiRouter.get("/setActiveQuest.php", setActiveQuestController);
|
apiRouter.get("/setActiveQuest.php", setActiveQuestController);
|
||||||
apiRouter.get("/setActiveShip.php", setActiveShipController);
|
apiRouter.get("/setActiveShip.php", setActiveShipController);
|
||||||
@ -221,13 +210,10 @@ apiRouter.get("/updateSession.php", updateSessionGetController);
|
|||||||
// post
|
// post
|
||||||
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
|
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
|
||||||
apiRouter.post("/activateRandomMod.php", activateRandomModController);
|
apiRouter.post("/activateRandomMod.php", activateRandomModController);
|
||||||
apiRouter.post("/addFriend.php", addFriendController);
|
|
||||||
apiRouter.post("/addFriendImage.php", addFriendImageController);
|
apiRouter.post("/addFriendImage.php", addFriendImageController);
|
||||||
apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
|
apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
|
||||||
apiRouter.post("/addPendingFriend.php", addPendingFriendController);
|
|
||||||
apiRouter.post("/addToAlliance.php", addToAllianceController);
|
apiRouter.post("/addToAlliance.php", addToAllianceController);
|
||||||
apiRouter.post("/addToGuild.php", addToGuildController);
|
apiRouter.post("/addToGuild.php", addToGuildController);
|
||||||
apiRouter.post("/adoptPet.php", adoptPetController);
|
|
||||||
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
|
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
|
||||||
apiRouter.post("/archonFusion.php", archonFusionController);
|
apiRouter.post("/archonFusion.php", archonFusionController);
|
||||||
apiRouter.post("/artifacts.php", artifactsController);
|
apiRouter.post("/artifacts.php", artifactsController);
|
||||||
@ -236,7 +222,6 @@ apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
|
|||||||
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
|
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
|
||||||
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
|
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
|
||||||
apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
|
apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
|
||||||
apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?)
|
|
||||||
apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
|
apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
|
||||||
apiRouter.post("/confirmGuildInvitation.php", confirmGuildInvitationPostController);
|
apiRouter.post("/confirmGuildInvitation.php", confirmGuildInvitationPostController);
|
||||||
apiRouter.post("/contributeGuildClass.php", contributeGuildClassController);
|
apiRouter.post("/contributeGuildClass.php", contributeGuildClassController);
|
||||||
@ -291,13 +276,10 @@ apiRouter.post("/playerSkills.php", playerSkillsController);
|
|||||||
apiRouter.post("/postGuildAdvertisement.php", postGuildAdvertisementController);
|
apiRouter.post("/postGuildAdvertisement.php", postGuildAdvertisementController);
|
||||||
apiRouter.post("/projectionManager.php", projectionManagerController);
|
apiRouter.post("/projectionManager.php", projectionManagerController);
|
||||||
apiRouter.post("/purchase.php", purchaseController);
|
apiRouter.post("/purchase.php", purchaseController);
|
||||||
apiRouter.post("/questControl.php", questControlController); // U17
|
|
||||||
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
|
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
|
||||||
apiRouter.post("/releasePet.php", releasePetController);
|
apiRouter.post("/releasePet.php", releasePetController);
|
||||||
apiRouter.post("/removeFriend.php", removeFriendPostController);
|
|
||||||
apiRouter.post("/removeFromGuild.php", removeFromGuildController);
|
apiRouter.post("/removeFromGuild.php", removeFromGuildController);
|
||||||
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
|
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
|
||||||
apiRouter.post("/renamePet.php", renamePetController);
|
|
||||||
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
|
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
|
||||||
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
|
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
|
||||||
apiRouter.post("/saveDialogue.php", saveDialogueController);
|
apiRouter.post("/saveDialogue.php", saveDialogueController);
|
||||||
@ -310,7 +292,6 @@ apiRouter.post("/setDojoComponentColors.php", setDojoComponentColorsController);
|
|||||||
apiRouter.post("/setDojoComponentMessage.php", setDojoComponentMessageController);
|
apiRouter.post("/setDojoComponentMessage.php", setDojoComponentMessageController);
|
||||||
apiRouter.post("/setDojoComponentSettings.php", setDojoComponentSettingsController);
|
apiRouter.post("/setDojoComponentSettings.php", setDojoComponentSettingsController);
|
||||||
apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController);
|
apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController);
|
||||||
apiRouter.post("/setFriendNote.php", setFriendNoteController);
|
|
||||||
apiRouter.post("/setGuildMotd.php", setGuildMotdController);
|
apiRouter.post("/setGuildMotd.php", setGuildMotdController);
|
||||||
apiRouter.post("/setHubNpcCustomizations.php", setHubNpcCustomizationsController);
|
apiRouter.post("/setHubNpcCustomizations.php", setHubNpcCustomizationsController);
|
||||||
apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController);
|
apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController);
|
||||||
|
@ -23,7 +23,6 @@ import { setEvolutionProgressController } from "@/src/controllers/custom/setEvol
|
|||||||
|
|
||||||
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
|
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
|
||||||
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
|
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
|
||||||
import { setBoosterController } from "../controllers/custom/setBoosterController";
|
|
||||||
|
|
||||||
const customRouter = express.Router();
|
const customRouter = express.Router();
|
||||||
|
|
||||||
@ -47,7 +46,6 @@ customRouter.post("/addXp", addXpController);
|
|||||||
customRouter.post("/import", importController);
|
customRouter.post("/import", importController);
|
||||||
customRouter.post("/manageQuests", manageQuestsController);
|
customRouter.post("/manageQuests", manageQuestsController);
|
||||||
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
|
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
|
||||||
customRouter.post("/setBooster", setBoosterController);
|
|
||||||
|
|
||||||
customRouter.get("/config", getConfigDataController);
|
customRouter.get("/config", getConfigDataController);
|
||||||
customRouter.post("/config", updateConfigDataController);
|
customRouter.post("/config", updateConfigDataController);
|
||||||
|
@ -24,8 +24,6 @@ interface IConfig {
|
|||||||
infiniteEndo?: boolean;
|
infiniteEndo?: boolean;
|
||||||
infiniteRegalAya?: boolean;
|
infiniteRegalAya?: boolean;
|
||||||
infiniteHelminthMaterials?: boolean;
|
infiniteHelminthMaterials?: boolean;
|
||||||
claimingBlueprintRefundsIngredients?: boolean;
|
|
||||||
dontSubtractVoidTraces?: boolean;
|
|
||||||
dontSubtractConsumables?: boolean;
|
dontSubtractConsumables?: boolean;
|
||||||
unlockAllShipFeatures?: boolean;
|
unlockAllShipFeatures?: boolean;
|
||||||
unlockAllShipDecorations?: boolean;
|
unlockAllShipDecorations?: boolean;
|
||||||
@ -44,8 +42,6 @@ interface IConfig {
|
|||||||
noVendorPurchaseLimits?: boolean;
|
noVendorPurchaseLimits?: boolean;
|
||||||
noDeathMarks?: boolean;
|
noDeathMarks?: boolean;
|
||||||
noKimCooldowns?: boolean;
|
noKimCooldowns?: boolean;
|
||||||
syndicateMissionsRepeatable?: boolean;
|
|
||||||
instantFinishRivenChallenge?: boolean;
|
|
||||||
instantResourceExtractorDrones?: boolean;
|
instantResourceExtractorDrones?: boolean;
|
||||||
noResourceExtractorDronesDamage?: boolean;
|
noResourceExtractorDronesDamage?: boolean;
|
||||||
skipClanKeyCrafting?: boolean;
|
skipClanKeyCrafting?: boolean;
|
||||||
@ -56,18 +52,12 @@ interface IConfig {
|
|||||||
noDojoResearchTime?: boolean;
|
noDojoResearchTime?: boolean;
|
||||||
fastClanAscension?: boolean;
|
fastClanAscension?: boolean;
|
||||||
spoofMasteryRank?: number;
|
spoofMasteryRank?: number;
|
||||||
nightwaveStandingMultiplier?: number;
|
|
||||||
worldState?: {
|
worldState?: {
|
||||||
creditBoost?: boolean;
|
creditBoost?: boolean;
|
||||||
affinityBoost?: boolean;
|
affinityBoost?: boolean;
|
||||||
resourceBoost?: boolean;
|
resourceBoost?: boolean;
|
||||||
starDays?: boolean;
|
starDays?: boolean;
|
||||||
eidolonOverride?: string;
|
lockTime?: number;
|
||||||
vallisOverride?: string;
|
|
||||||
nightwaveOverride?: string;
|
|
||||||
};
|
|
||||||
dev?: {
|
|
||||||
keepVendorsExpired?: boolean;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ import fs from "fs";
|
|||||||
import fsPromises from "fs/promises";
|
import fsPromises from "fs/promises";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { config, configPath, loadConfig } from "./configService";
|
import { config, configPath, loadConfig } from "./configService";
|
||||||
import { getWebPorts, startWebServer, stopWebServer } from "./webService";
|
|
||||||
|
|
||||||
let amnesia = false;
|
let amnesia = false;
|
||||||
fs.watchFile(configPath, () => {
|
fs.watchFile(configPath, () => {
|
||||||
@ -13,35 +12,17 @@ fs.watchFile(configPath, () => {
|
|||||||
try {
|
try {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("FATAL ERROR: Config failed to be reloaded: " + (e as Error).message);
|
logger.error("Failed to reload config.json. Did you delete it?! Execution cannot continue.");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
validateConfig();
|
validateConfig();
|
||||||
|
|
||||||
const webPorts = getWebPorts();
|
|
||||||
if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) {
|
|
||||||
logger.info(`Restarting web server to apply port changes.`);
|
|
||||||
void stopWebServer().then(startWebServer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const validateConfig = (): void => {
|
export const validateConfig = (): void => {
|
||||||
let modified = false;
|
if (typeof config.administratorNames == "string") {
|
||||||
if (config.administratorNames) {
|
logger.info(`Updating config.json to make administratorNames an array.`);
|
||||||
if (!Array.isArray(config.administratorNames)) {
|
|
||||||
config.administratorNames = [config.administratorNames];
|
config.administratorNames = [config.administratorNames];
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
for (let i = 0; i != config.administratorNames.length; ++i) {
|
|
||||||
if (typeof config.administratorNames[i] != "string") {
|
|
||||||
config.administratorNames[i] = String(config.administratorNames[i]);
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (modified) {
|
|
||||||
logger.info(`Updating config.json to fix some issues with it.`);
|
|
||||||
void saveConfig();
|
void saveConfig();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
import { IFriendInfo } from "../types/friendTypes";
|
|
||||||
import { getInventory } from "./inventoryService";
|
|
||||||
import { config } from "./configService";
|
|
||||||
import { Account } from "../models/loginModel";
|
|
||||||
import { Types } from "mongoose";
|
|
||||||
import { Friendship } from "../models/friendModel";
|
|
||||||
import { fromOid, toMongoDate } from "../helpers/inventoryHelpers";
|
|
||||||
|
|
||||||
export const addAccountDataToFriendInfo = async (info: IFriendInfo): Promise<void> => {
|
|
||||||
const account = (await Account.findById(fromOid(info._id), "DisplayName LastLogin"))!;
|
|
||||||
info.DisplayName = account.DisplayName;
|
|
||||||
info.LastLogin = toMongoDate(account.LastLogin);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addInventoryDataToFriendInfo = async (info: IFriendInfo): Promise<void> => {
|
|
||||||
const inventory = await getInventory(fromOid(info._id), "PlayerLevel ActiveAvatarImageType");
|
|
||||||
info.PlayerLevel = config.spoofMasteryRank == -1 ? inventory.PlayerLevel : config.spoofMasteryRank;
|
|
||||||
info.ActiveAvatarImageType = inventory.ActiveAvatarImageType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const areFriends = async (a: Types.ObjectId | string, b: Types.ObjectId | string): Promise<boolean> => {
|
|
||||||
const [aAddedB, bAddedA] = await Promise.all([
|
|
||||||
Friendship.exists({ owner: a, friend: b }),
|
|
||||||
Friendship.exists({ owner: b, friend: a })
|
|
||||||
]);
|
|
||||||
return Boolean(aAddedB && bAddedA);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const areFriendsOfFriends = async (a: Types.ObjectId | string, b: Types.ObjectId | string): Promise<boolean> => {
|
|
||||||
const [aInternalFriends, bInternalFriends] = await Promise.all([
|
|
||||||
Friendship.find({ owner: a }),
|
|
||||||
Friendship.find({ owner: b })
|
|
||||||
]);
|
|
||||||
for (const aInternalFriend of aInternalFriends) {
|
|
||||||
if (bInternalFriends.find(x => x.friend.equals(aInternalFriend.friend))) {
|
|
||||||
const c = aInternalFriend.friend;
|
|
||||||
const [cAcceptedA, cAcceptedB] = await Promise.all([
|
|
||||||
Friendship.exists({ owner: c, friend: a }),
|
|
||||||
Friendship.exists({ owner: c, friend: b })
|
|
||||||
]);
|
|
||||||
if (cAcceptedA && cAcceptedB) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
@ -1,5 +1,5 @@
|
|||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import { getAccountIdForRequest, TAccountDocument } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { addLevelKeys, addRecipes, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
import { addLevelKeys, addRecipes, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||||
import { Alliance, AllianceMember, Guild, GuildAd, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
import { Alliance, AllianceMember, Guild, GuildAd, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
@ -19,11 +19,12 @@ import {
|
|||||||
IGuildVault,
|
IGuildVault,
|
||||||
ITechProjectDatabase
|
ITechProjectDatabase
|
||||||
} from "@/src/types/guildTypes";
|
} from "@/src/types/guildTypes";
|
||||||
import { toMongoDate, toOid, toOid2 } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate, toOid } 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 "../utils/logger";
|
||||||
import { config } from "./configService";
|
import { config } from "./configService";
|
||||||
|
import { Account } from "../models/loginModel";
|
||||||
import { getRandomInt } from "./rngService";
|
import { getRandomInt } from "./rngService";
|
||||||
import { Inbox } from "../models/inboxModel";
|
import { Inbox } from "../models/inboxModel";
|
||||||
import { IFusionTreasure, ITypeCount } from "../types/inventoryTypes/inventoryTypes";
|
import { IFusionTreasure, ITypeCount } from "../types/inventoryTypes/inventoryTypes";
|
||||||
@ -31,7 +32,6 @@ import { IInventoryChanges } from "../types/purchaseTypes";
|
|||||||
import { parallelForeach } from "../utils/async-utils";
|
import { parallelForeach } from "../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 "./inboxService";
|
||||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "./friendService";
|
|
||||||
|
|
||||||
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);
|
||||||
@ -54,10 +54,7 @@ export const getGuildForRequestEx = async (
|
|||||||
return guild;
|
return guild;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getGuildClient = async (
|
export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: string): Promise<IGuildClient> => {
|
||||||
guild: TGuildDatabaseDocument,
|
|
||||||
account: TAccountDocument
|
|
||||||
): Promise<IGuildClient> => {
|
|
||||||
const guildMembers = await GuildMember.find({ guildId: guild._id });
|
const guildMembers = await GuildMember.find({ guildId: guild._id });
|
||||||
|
|
||||||
const members: IGuildMemberClient[] = [];
|
const members: IGuildMemberClient[] = [];
|
||||||
@ -65,30 +62,34 @@ export const getGuildClient = async (
|
|||||||
const dataFillInPromises: Promise<void>[] = [];
|
const dataFillInPromises: Promise<void>[] = [];
|
||||||
for (const guildMember of guildMembers) {
|
for (const guildMember of guildMembers) {
|
||||||
const member: IGuildMemberClient = {
|
const member: IGuildMemberClient = {
|
||||||
_id: toOid2(guildMember.accountId, account.BuildLabel),
|
_id: toOid(guildMember.accountId),
|
||||||
Rank: guildMember.rank,
|
Rank: guildMember.rank,
|
||||||
Status: guildMember.status,
|
Status: guildMember.status,
|
||||||
Note: guildMember.RequestMsg,
|
Note: guildMember.RequestMsg,
|
||||||
RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined
|
RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined
|
||||||
};
|
};
|
||||||
if (guildMember.accountId.equals(account._id)) {
|
if (guildMember.accountId.equals(accountId)) {
|
||||||
missingEntry = false;
|
missingEntry = false;
|
||||||
} else {
|
} else {
|
||||||
dataFillInPromises.push(addAccountDataToFriendInfo(member));
|
dataFillInPromises.push(
|
||||||
dataFillInPromises.push(addInventoryDataToFriendInfo(member));
|
(async (): Promise<void> => {
|
||||||
|
member.DisplayName = (await Account.findById(guildMember.accountId, "DisplayName"))!.DisplayName;
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
dataFillInPromises.push(fillInInventoryDataForGuildMember(member));
|
||||||
}
|
}
|
||||||
members.push(member);
|
members.push(member);
|
||||||
}
|
}
|
||||||
if (missingEntry) {
|
if (missingEntry) {
|
||||||
// Handle clans created prior to creation of the GuildMember model.
|
// Handle clans created prior to creation of the GuildMember model.
|
||||||
await GuildMember.insertOne({
|
await GuildMember.insertOne({
|
||||||
accountId: account._id,
|
accountId: accountId,
|
||||||
guildId: guild._id,
|
guildId: guild._id,
|
||||||
status: 0,
|
status: 0,
|
||||||
rank: 0
|
rank: 0
|
||||||
});
|
});
|
||||||
members.push({
|
members.push({
|
||||||
_id: toOid2(account._id, account.BuildLabel),
|
_id: { $oid: accountId },
|
||||||
Status: 0,
|
Status: 0,
|
||||||
Rank: 0
|
Rank: 0
|
||||||
});
|
});
|
||||||
@ -97,7 +98,7 @@ export const getGuildClient = async (
|
|||||||
await Promise.all(dataFillInPromises);
|
await Promise.all(dataFillInPromises);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: toOid2(guild._id, account.BuildLabel),
|
_id: toOid(guild._id),
|
||||||
Name: guild.Name,
|
Name: guild.Name,
|
||||||
MOTD: guild.MOTD,
|
MOTD: guild.MOTD,
|
||||||
LongMOTD: guild.LongMOTD,
|
LongMOTD: guild.LongMOTD,
|
||||||
@ -109,11 +110,11 @@ export const getGuildClient = async (
|
|||||||
ActiveDojoColorResearch: guild.ActiveDojoColorResearch,
|
ActiveDojoColorResearch: guild.ActiveDojoColorResearch,
|
||||||
Class: guild.Class,
|
Class: guild.Class,
|
||||||
XP: guild.XP,
|
XP: guild.XP,
|
||||||
IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(account._id)),
|
IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)),
|
||||||
NumContributors: guild.CeremonyContributors?.length ?? 0,
|
NumContributors: guild.CeremonyContributors?.length ?? 0,
|
||||||
CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined,
|
CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined,
|
||||||
AutoContributeFromVault: guild.AutoContributeFromVault,
|
AutoContributeFromVault: guild.AutoContributeFromVault,
|
||||||
AllianceId: guild.AllianceId ? toOid2(guild.AllianceId, account.BuildLabel) : undefined
|
AllianceId: guild.AllianceId ? toOid(guild.AllianceId) : undefined
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,11 +134,10 @@ export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => {
|
|||||||
export const getDojoClient = async (
|
export const getDojoClient = async (
|
||||||
guild: TGuildDatabaseDocument,
|
guild: TGuildDatabaseDocument,
|
||||||
status: number,
|
status: number,
|
||||||
componentId?: Types.ObjectId | string,
|
componentId?: Types.ObjectId | string
|
||||||
buildLabel?: string
|
|
||||||
): Promise<IDojoClient> => {
|
): Promise<IDojoClient> => {
|
||||||
const dojo: IDojoClient = {
|
const dojo: IDojoClient = {
|
||||||
_id: toOid2(guild._id, buildLabel),
|
_id: { $oid: guild._id.toString() },
|
||||||
Name: guild.Name,
|
Name: guild.Name,
|
||||||
Tier: guild.Tier,
|
Tier: guild.Tier,
|
||||||
GuildEmblem: guild.Emblem,
|
GuildEmblem: guild.Emblem,
|
||||||
@ -159,8 +159,8 @@ export const getDojoClient = async (
|
|||||||
for (const dojoComponent of guild.DojoComponents) {
|
for (const dojoComponent of guild.DojoComponents) {
|
||||||
if (!componentId || dojoComponent._id.equals(componentId)) {
|
if (!componentId || dojoComponent._id.equals(componentId)) {
|
||||||
const clientComponent: IDojoComponentClient = {
|
const clientComponent: IDojoComponentClient = {
|
||||||
id: toOid2(dojoComponent._id, buildLabel),
|
id: toOid(dojoComponent._id),
|
||||||
SortId: toOid2(dojoComponent.SortId ?? dojoComponent._id, buildLabel), // always providing a SortId so decos don't need repositioning to reparent
|
SortId: toOid(dojoComponent.SortId ?? dojoComponent._id), // always providing a SortId so decos don't need repositioning to reparent
|
||||||
pf: dojoComponent.pf,
|
pf: dojoComponent.pf,
|
||||||
ppf: dojoComponent.ppf,
|
ppf: dojoComponent.ppf,
|
||||||
Name: dojoComponent.Name,
|
Name: dojoComponent.Name,
|
||||||
@ -169,7 +169,7 @@ export const getDojoClient = async (
|
|||||||
Settings: dojoComponent.Settings
|
Settings: dojoComponent.Settings
|
||||||
};
|
};
|
||||||
if (dojoComponent.pi) {
|
if (dojoComponent.pi) {
|
||||||
clientComponent.pi = toOid2(dojoComponent.pi, buildLabel);
|
clientComponent.pi = toOid(dojoComponent.pi);
|
||||||
clientComponent.op = dojoComponent.op!;
|
clientComponent.op = dojoComponent.op!;
|
||||||
clientComponent.pp = dojoComponent.pp!;
|
clientComponent.pp = dojoComponent.pp!;
|
||||||
}
|
}
|
||||||
@ -225,7 +225,7 @@ export const getDojoClient = async (
|
|||||||
clientComponent.Decos = [];
|
clientComponent.Decos = [];
|
||||||
for (const deco of dojoComponent.Decos) {
|
for (const deco of dojoComponent.Decos) {
|
||||||
const clientDeco: IDojoDecoClient = {
|
const clientDeco: IDojoDecoClient = {
|
||||||
id: toOid2(deco._id, buildLabel),
|
id: toOid(deco._id),
|
||||||
Type: deco.Type,
|
Type: deco.Type,
|
||||||
Pos: deco.Pos,
|
Pos: deco.Pos,
|
||||||
Rot: deco.Rot,
|
Rot: deco.Rot,
|
||||||
@ -449,7 +449,7 @@ export const addGuildMemberShipDecoContribution = (guildMember: IGuildMemberData
|
|||||||
export const processDojoBuildMaterialsGathered = (guild: TGuildDatabaseDocument, build: IDojoBuild): void => {
|
export const processDojoBuildMaterialsGathered = (guild: TGuildDatabaseDocument, build: IDojoBuild): void => {
|
||||||
if (build.guildXpValue) {
|
if (build.guildXpValue) {
|
||||||
guild.ClaimedXP ??= [];
|
guild.ClaimedXP ??= [];
|
||||||
if (guild.ClaimedXP.indexOf(build.resultType) == -1) {
|
if (!guild.ClaimedXP.find(x => x == build.resultType)) {
|
||||||
guild.ClaimedXP.push(build.resultType);
|
guild.ClaimedXP.push(build.resultType);
|
||||||
guild.XP += build.guildXpValue;
|
guild.XP += build.guildXpValue;
|
||||||
}
|
}
|
||||||
@ -466,6 +466,12 @@ export const setDojoRoomLogFunded = (guild: TGuildDatabaseDocument, component: I
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fillInInventoryDataForGuildMember = async (member: IGuildMemberClient): Promise<void> => {
|
||||||
|
const inventory = await getInventory(member._id.$oid, "PlayerLevel ActiveAvatarImageType");
|
||||||
|
member.PlayerLevel = config.spoofMasteryRank == -1 ? inventory.PlayerLevel : config.spoofMasteryRank;
|
||||||
|
member.ActiveAvatarImageType = inventory.ActiveAvatarImageType;
|
||||||
|
};
|
||||||
|
|
||||||
export const createUniqueClanName = async (name: string): Promise<string> => {
|
export const createUniqueClanName = async (name: string): Promise<string> => {
|
||||||
const initialDiscriminator = getRandomInt(0, 999);
|
const initialDiscriminator = getRandomInt(0, 999);
|
||||||
let discriminator = initialDiscriminator;
|
let discriminator = initialDiscriminator;
|
||||||
|
@ -377,6 +377,9 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
|
|||||||
db[key] = client[key];
|
db[key] = client[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (client.EndlessXP !== undefined) {
|
||||||
|
db.EndlessXP = client.EndlessXP;
|
||||||
|
}
|
||||||
if (client.SongChallenges !== undefined) {
|
if (client.SongChallenges !== undefined) {
|
||||||
db.SongChallenges = client.SongChallenges;
|
db.SongChallenges = client.SongChallenges;
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,7 @@ import {
|
|||||||
ITraits,
|
ITraits,
|
||||||
ICalendarProgress,
|
ICalendarProgress,
|
||||||
INemesisWeaponTargetFingerprint,
|
INemesisWeaponTargetFingerprint,
|
||||||
INemesisPetTargetFingerprint,
|
INemesisPetTargetFingerprint
|
||||||
IDialogueDatabase
|
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
|
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
|
||||||
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
|
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
|
||||||
@ -44,7 +43,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
ExportArcanes,
|
ExportArcanes,
|
||||||
ExportBundles,
|
ExportBundles,
|
||||||
ExportChallenges,
|
|
||||||
ExportCustoms,
|
ExportCustoms,
|
||||||
ExportDrones,
|
ExportDrones,
|
||||||
ExportEmailItems,
|
ExportEmailItems,
|
||||||
@ -54,6 +52,7 @@ import {
|
|||||||
ExportGear,
|
ExportGear,
|
||||||
ExportKeys,
|
ExportKeys,
|
||||||
ExportMisc,
|
ExportMisc,
|
||||||
|
ExportNightwave,
|
||||||
ExportRailjackWeapons,
|
ExportRailjackWeapons,
|
||||||
ExportRecipes,
|
ExportRecipes,
|
||||||
ExportResources,
|
ExportResources,
|
||||||
@ -71,7 +70,6 @@ import { createShip } from "./shipService";
|
|||||||
import {
|
import {
|
||||||
catbrowDetails,
|
catbrowDetails,
|
||||||
fromMongoDate,
|
fromMongoDate,
|
||||||
fromOid,
|
|
||||||
kubrowDetails,
|
kubrowDetails,
|
||||||
kubrowFurPatternsWeights,
|
kubrowFurPatternsWeights,
|
||||||
kubrowWeights,
|
kubrowWeights,
|
||||||
@ -82,11 +80,9 @@ import { handleBundleAcqusition } from "./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 { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService";
|
||||||
import { createMessage } from "./inboxService";
|
import { createMessage } from "./inboxService";
|
||||||
import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper";
|
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
||||||
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
|
import { getWorldState } from "./worldStateService";
|
||||||
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
|
import { getInnateDamageTag, getInnateDamageValue } from "../helpers/nemesisHelpers";
|
||||||
import { TAccountDocument } from "./loginService";
|
|
||||||
import { unixTimesInMs } from "../constants/timeConstants";
|
|
||||||
|
|
||||||
export const createInventory = async (
|
export const createInventory = async (
|
||||||
accountOwnerId: Types.ObjectId,
|
accountOwnerId: Types.ObjectId,
|
||||||
@ -153,11 +149,6 @@ export const addStartingGear = async (
|
|||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
startingGear?: TPartialStartingGear
|
startingGear?: TPartialStartingGear
|
||||||
): Promise<IInventoryChanges> => {
|
): Promise<IInventoryChanges> => {
|
||||||
if (inventory.ReceivedStartingGear) {
|
|
||||||
throw new Error(`account has already received starting gear`);
|
|
||||||
}
|
|
||||||
inventory.ReceivedStartingGear = true;
|
|
||||||
|
|
||||||
const { LongGuns, Pistols, Suits, Melee } = startingGear || {
|
const { LongGuns, Pistols, Suits, Melee } = startingGear || {
|
||||||
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
|
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
|
||||||
Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
|
Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
|
||||||
@ -206,6 +197,11 @@ export const addStartingGear = async (
|
|||||||
combineInventoryChanges(inventoryChanges, inventoryDelta);
|
combineInventoryChanges(inventoryChanges, inventoryDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inventory.ReceivedStartingGear) {
|
||||||
|
logger.warn(`account already had starting gear but asked for it again?!`);
|
||||||
|
}
|
||||||
|
inventory.ReceivedStartingGear = true;
|
||||||
|
|
||||||
return inventoryChanges;
|
return inventoryChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -427,7 +423,7 @@ export const addItem = async (
|
|||||||
changes.push({
|
changes.push({
|
||||||
ItemType: egg.ItemType,
|
ItemType: egg.ItemType,
|
||||||
ExpirationDate: { $date: { $numberLong: "2000000000000" } },
|
ExpirationDate: { $date: { $numberLong: "2000000000000" } },
|
||||||
ItemId: toOid(egg._id) // TODO: Pass on buildLabel from purchaseService
|
ItemId: toOid(egg._id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -723,10 +719,6 @@ export const addItem = async (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Boons":
|
|
||||||
// Can purchase /Lotus/Upgrades/Boons/DuviriVendorBoonItem from Acrithis, doesn't need to be added to inventory.
|
|
||||||
return {};
|
|
||||||
|
|
||||||
case "Stickers":
|
case "Stickers":
|
||||||
{
|
{
|
||||||
const entry = inventory.RawUpgrades.find(x => x.ItemType == typeName);
|
const entry = inventory.RawUpgrades.find(x => x.ItemType == typeName);
|
||||||
@ -781,9 +773,7 @@ export const addItem = async (
|
|||||||
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
|
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
|
||||||
typeName.substr(1).split("/")[3] == "KubrowPet"
|
typeName.substr(1).split("/")[3] == "KubrowPet"
|
||||||
) {
|
) {
|
||||||
if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
|
|
||||||
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
|
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
|
||||||
}
|
|
||||||
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
|
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
|
||||||
if (!seed) {
|
if (!seed) {
|
||||||
throw new Error(`Expected crew member to have a seed`);
|
throw new Error(`Expected crew member to have a seed`);
|
||||||
@ -798,12 +788,6 @@ export const addItem = async (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Items": {
|
|
||||||
if (typeName.substr(1).split("/")[3] == "Emotes") {
|
|
||||||
return addCustomization(inventory, typeName);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "NeutralCreatures": {
|
case "NeutralCreatures": {
|
||||||
if (inventory.Horses.length != 0) {
|
if (inventory.Horses.length != 0) {
|
||||||
logger.warn("refusing to add Horse because account already has one");
|
logger.warn("refusing to add Horse because account already has one");
|
||||||
@ -885,14 +869,10 @@ const addSentinel = (
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, ExportSentinels[sentinelName]?.defaultUpgrades);
|
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, ExportSentinels[sentinelName]?.defaultUpgrades);
|
||||||
|
|
||||||
|
const features = premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined;
|
||||||
const sentinelIndex =
|
const sentinelIndex =
|
||||||
inventory.Sentinels.push({
|
inventory.Sentinels.push({ ItemType: sentinelName, Configs: configs, XP: 0, Features: features, IsNew: true }) -
|
||||||
ItemType: sentinelName,
|
1;
|
||||||
Configs: configs,
|
|
||||||
XP: 0,
|
|
||||||
Features: premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined,
|
|
||||||
IsNew: inventory.Sentinels.find(x => x.ItemType == sentinelName) ? undefined : true
|
|
||||||
}) - 1;
|
|
||||||
inventoryChanges.Sentinels ??= [];
|
inventoryChanges.Sentinels ??= [];
|
||||||
inventoryChanges.Sentinels.push(inventory.Sentinels[sentinelIndex].toJSON<IEquipmentClient>());
|
inventoryChanges.Sentinels.push(inventory.Sentinels[sentinelIndex].toJSON<IEquipmentClient>());
|
||||||
|
|
||||||
@ -941,9 +921,6 @@ export const addPowerSuit = async (
|
|||||||
},
|
},
|
||||||
defaultOverwrites
|
defaultOverwrites
|
||||||
);
|
);
|
||||||
if (suit.IsNew) {
|
|
||||||
suit.IsNew = !inventory.Suits.find(x => x.ItemType == powersuitName);
|
|
||||||
}
|
|
||||||
if (!suit.IsNew) {
|
if (!suit.IsNew) {
|
||||||
suit.IsNew = undefined;
|
suit.IsNew = undefined;
|
||||||
}
|
}
|
||||||
@ -978,7 +955,7 @@ export const addMechSuit = async (
|
|||||||
UpgradeVer: 101,
|
UpgradeVer: 101,
|
||||||
XP: 0,
|
XP: 0,
|
||||||
Features: features,
|
Features: features,
|
||||||
IsNew: inventory.MechSuits.find(x => x.ItemType == mechsuitName) ? undefined : true
|
IsNew: true
|
||||||
}) - 1;
|
}) - 1;
|
||||||
inventoryChanges.MechSuits ??= [];
|
inventoryChanges.MechSuits ??= [];
|
||||||
inventoryChanges.MechSuits.push(inventory.MechSuits[suitIndex].toJSON<IEquipmentClient>());
|
inventoryChanges.MechSuits.push(inventory.MechSuits[suitIndex].toJSON<IEquipmentClient>());
|
||||||
@ -1018,7 +995,7 @@ export const addSpaceSuit = (
|
|||||||
UpgradeVer: 101,
|
UpgradeVer: 101,
|
||||||
XP: 0,
|
XP: 0,
|
||||||
Features: features,
|
Features: features,
|
||||||
IsNew: inventory.SpaceSuits.find(x => x.ItemType == spacesuitName) ? undefined : true
|
IsNew: true
|
||||||
}) - 1;
|
}) - 1;
|
||||||
inventoryChanges.SpaceSuits ??= [];
|
inventoryChanges.SpaceSuits ??= [];
|
||||||
inventoryChanges.SpaceSuits.push(inventory.SpaceSuits[suitIndex].toJSON<IEquipmentClient>());
|
inventoryChanges.SpaceSuits.push(inventory.SpaceSuits[suitIndex].toJSON<IEquipmentClient>());
|
||||||
@ -1028,13 +1005,12 @@ export const addSpaceSuit = (
|
|||||||
export const addKubrowPet = (
|
export const addKubrowPet = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
kubrowPetName: string,
|
kubrowPetName: string,
|
||||||
details?: IKubrowPetDetailsDatabase,
|
details: IKubrowPetDetailsDatabase | undefined,
|
||||||
premiumPurchase: boolean = false,
|
premiumPurchase: boolean,
|
||||||
inventoryChanges: IInventoryChanges = {}
|
inventoryChanges: IInventoryChanges = {}
|
||||||
): IInventoryChanges => {
|
): IInventoryChanges => {
|
||||||
combineInventoryChanges(inventoryChanges, occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase));
|
combineInventoryChanges(inventoryChanges, occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase));
|
||||||
|
|
||||||
// TODO: When incubating, this should only be given when claiming the recipe.
|
|
||||||
const kubrowPet = ExportSentinels[kubrowPetName] as ISentinel | undefined;
|
const kubrowPet = ExportSentinels[kubrowPetName] as ISentinel | undefined;
|
||||||
const exalted = kubrowPet?.exalted ?? [];
|
const exalted = kubrowPet?.exalted ?? [];
|
||||||
for (const specialItem of exalted) {
|
for (const specialItem of exalted) {
|
||||||
@ -1083,11 +1059,11 @@ export const addKubrowPet = (
|
|||||||
|
|
||||||
details = {
|
details = {
|
||||||
Name: "",
|
Name: "",
|
||||||
IsPuppy: !premiumPurchase,
|
IsPuppy: false,
|
||||||
HasCollar: true,
|
HasCollar: true,
|
||||||
PrintsRemaining: 3,
|
PrintsRemaining: 2,
|
||||||
Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating,
|
Status: Status.StatusStasis,
|
||||||
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: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
|
||||||
IsMale: !!getRandomInt(0, 1),
|
IsMale: !!getRandomInt(0, 1),
|
||||||
Size: getRandomInt(70, 100) / 100,
|
Size: getRandomInt(70, 100) / 100,
|
||||||
DominantTraits: traits,
|
DominantTraits: traits,
|
||||||
@ -1101,7 +1077,7 @@ export const addKubrowPet = (
|
|||||||
Configs: configs,
|
Configs: configs,
|
||||||
XP: 0,
|
XP: 0,
|
||||||
Details: details,
|
Details: details,
|
||||||
IsNew: inventory.KubrowPets.find(x => x.ItemType == kubrowPetName) ? undefined : true
|
IsNew: true
|
||||||
}) - 1;
|
}) - 1;
|
||||||
inventoryChanges.KubrowPets ??= [];
|
inventoryChanges.KubrowPets ??= [];
|
||||||
inventoryChanges.KubrowPets.push(inventory.KubrowPets[kubrowPetIndex].toJSON<IEquipmentClient>());
|
inventoryChanges.KubrowPets.push(inventory.KubrowPets[kubrowPetIndex].toJSON<IEquipmentClient>());
|
||||||
@ -1129,29 +1105,24 @@ const isCurrencyTracked = (usePremium: boolean): boolean => {
|
|||||||
export const updateCurrency = (
|
export const updateCurrency = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
price: number,
|
price: number,
|
||||||
usePremium: boolean,
|
usePremium: boolean
|
||||||
inventoryChanges: IInventoryChanges = {}
|
|
||||||
): IInventoryChanges => {
|
): IInventoryChanges => {
|
||||||
|
const currencyChanges: IInventoryChanges = {};
|
||||||
if (price != 0 && isCurrencyTracked(usePremium)) {
|
if (price != 0 && isCurrencyTracked(usePremium)) {
|
||||||
if (usePremium) {
|
if (usePremium) {
|
||||||
if (inventory.PremiumCreditsFree > 0) {
|
if (inventory.PremiumCreditsFree > 0) {
|
||||||
const premiumCreditsFreeDelta = Math.min(price, inventory.PremiumCreditsFree) * -1;
|
currencyChanges.PremiumCreditsFree = Math.min(price, inventory.PremiumCreditsFree) * -1;
|
||||||
inventoryChanges.PremiumCreditsFree ??= 0;
|
inventory.PremiumCreditsFree += currencyChanges.PremiumCreditsFree;
|
||||||
inventoryChanges.PremiumCreditsFree += premiumCreditsFreeDelta;
|
|
||||||
inventory.PremiumCreditsFree += premiumCreditsFreeDelta;
|
|
||||||
}
|
}
|
||||||
inventoryChanges.PremiumCredits ??= 0;
|
currencyChanges.PremiumCredits = -price;
|
||||||
inventoryChanges.PremiumCredits -= price;
|
inventory.PremiumCredits += currencyChanges.PremiumCredits;
|
||||||
inventory.PremiumCredits -= price;
|
|
||||||
logger.debug(`currency changes `, { PremiumCredits: -price });
|
|
||||||
} else {
|
} else {
|
||||||
inventoryChanges.RegularCredits ??= 0;
|
currencyChanges.RegularCredits = -price;
|
||||||
inventoryChanges.RegularCredits -= price;
|
inventory.RegularCredits += currencyChanges.RegularCredits;
|
||||||
inventory.RegularCredits -= price;
|
|
||||||
logger.debug(`currency changes `, { RegularCredits: -price });
|
|
||||||
}
|
}
|
||||||
|
logger.debug(`currency changes `, currencyChanges);
|
||||||
}
|
}
|
||||||
return inventoryChanges;
|
return currencyChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addFusionPoints = (inventory: TInventoryDatabaseDocument, add: number): number => {
|
export const addFusionPoints = (inventory: TInventoryDatabaseDocument, add: number): number => {
|
||||||
@ -1202,10 +1173,8 @@ export const addStanding = (
|
|||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
syndicateTag: string,
|
syndicateTag: string,
|
||||||
gainedStanding: number,
|
gainedStanding: number,
|
||||||
affiliationMods: IAffiliationMods[] = [],
|
isMedallion: boolean = false
|
||||||
isMedallion: boolean = false,
|
): IAffiliationMods => {
|
||||||
propagateAlignments: boolean = true
|
|
||||||
): void => {
|
|
||||||
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
|
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
|
||||||
const syndicateMeta = ExportSyndicates[syndicateTag];
|
const syndicateMeta = ExportSyndicates[syndicateTag];
|
||||||
|
|
||||||
@ -1217,10 +1186,6 @@ 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) {
|
|
||||||
gainedStanding = -71000 + syndicate.Standing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
|
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
|
||||||
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
||||||
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
|
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
|
||||||
@ -1229,27 +1194,10 @@ export const addStanding = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
syndicate.Standing += gainedStanding;
|
syndicate.Standing += gainedStanding;
|
||||||
const affiliationMod: IAffiliationMods = {
|
return {
|
||||||
Tag: syndicateTag,
|
Tag: syndicateTag,
|
||||||
Standing: gainedStanding
|
Standing: gainedStanding
|
||||||
};
|
};
|
||||||
affiliationMods.push(affiliationMod);
|
|
||||||
|
|
||||||
if (syndicateMeta.alignments) {
|
|
||||||
if (propagateAlignments) {
|
|
||||||
for (const [tag, factor] of Object.entries(syndicateMeta.alignments)) {
|
|
||||||
addStanding(inventory, tag, gainedStanding * factor, affiliationMods, isMedallion, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (syndicate.Standing < getMinStanding(syndicateMeta, syndicate.Title ?? 0)) {
|
|
||||||
syndicate.Title ??= 0;
|
|
||||||
syndicate.Title -= 1;
|
|
||||||
affiliationMod.Title ??= 0;
|
|
||||||
affiliationMod.Title -= 1;
|
|
||||||
logger.debug(`${syndicateTag} is decreasing to title ${syndicate.Title} after applying alignment`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: AffiliationMods support (Nightwave).
|
// TODO: AffiliationMods support (Nightwave).
|
||||||
@ -1309,9 +1257,6 @@ export const addEquipment = (
|
|||||||
},
|
},
|
||||||
defaultOverwrites
|
defaultOverwrites
|
||||||
);
|
);
|
||||||
if (equipment.IsNew) {
|
|
||||||
equipment.IsNew = !inventory[category].find(x => x.ItemType == type);
|
|
||||||
}
|
|
||||||
if (!equipment.IsNew) {
|
if (!equipment.IsNew) {
|
||||||
equipment.IsNew = undefined;
|
equipment.IsNew = undefined;
|
||||||
}
|
}
|
||||||
@ -1546,10 +1491,9 @@ export const applyClientEquipmentUpdates = (
|
|||||||
const category = inventory[categoryName];
|
const category = inventory[categoryName];
|
||||||
|
|
||||||
gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
|
gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
|
||||||
const item = category.id(fromOid(ItemId));
|
const item = category.id(ItemId.$oid);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
logger.warn(`Skipping unknown ${categoryName} item: id ${fromOid(ItemId)} not found`);
|
throw new Error(`No item with id ${ItemId.$oid} in ${categoryName}`);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (XP) {
|
if (XP) {
|
||||||
@ -1625,17 +1569,12 @@ export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray:
|
|||||||
if (MiscItems[itemIndex].ItemCount == 0) {
|
if (MiscItems[itemIndex].ItemCount == 0) {
|
||||||
MiscItems.splice(itemIndex, 1);
|
MiscItems.splice(itemIndex, 1);
|
||||||
} else if (MiscItems[itemIndex].ItemCount <= 0) {
|
} else if (MiscItems[itemIndex].ItemCount <= 0) {
|
||||||
logger.warn(`inventory.MiscItems has a negative count for ${ItemType}`);
|
logger.warn(`account now owns a negative amount of ${ItemType}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyArrayChanges = (
|
const applyArrayChanges = (arr: ITypeCount[], changes: ITypeCount[]): void => {
|
||||||
inventory: TInventoryDatabaseDocument,
|
|
||||||
key: "ShipDecorations" | "Consumables" | "CrewShipRawSalvage" | "CrewShipAmmo" | "Recipes" | "LevelKeys",
|
|
||||||
changes: ITypeCount[]
|
|
||||||
): void => {
|
|
||||||
const arr: ITypeCount[] = inventory[key];
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
if (change.ItemCount != 0) {
|
if (change.ItemCount != 0) {
|
||||||
let itemIndex = arr.findIndex(x => x.ItemType === change.ItemType);
|
let itemIndex = arr.findIndex(x => x.ItemType === change.ItemType);
|
||||||
@ -1647,34 +1586,34 @@ const applyArrayChanges = (
|
|||||||
if (arr[itemIndex].ItemCount == 0) {
|
if (arr[itemIndex].ItemCount == 0) {
|
||||||
arr.splice(itemIndex, 1);
|
arr.splice(itemIndex, 1);
|
||||||
} else if (arr[itemIndex].ItemCount <= 0) {
|
} else if (arr[itemIndex].ItemCount <= 0) {
|
||||||
logger.warn(`inventory.${key} has a negative count for ${change.ItemType}`);
|
logger.warn(`account now owns a negative amount of ${change.ItemType}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addShipDecorations = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addShipDecorations = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "ShipDecorations", itemsArray);
|
applyArrayChanges(inventory.ShipDecorations, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addConsumables = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addConsumables = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "Consumables", itemsArray);
|
applyArrayChanges(inventory.Consumables, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addCrewShipRawSalvage = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addCrewShipRawSalvage = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "CrewShipRawSalvage", itemsArray);
|
applyArrayChanges(inventory.CrewShipRawSalvage, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addCrewShipAmmo = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addCrewShipAmmo = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "CrewShipAmmo", itemsArray);
|
applyArrayChanges(inventory.CrewShipAmmo, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addRecipes = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addRecipes = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "Recipes", itemsArray);
|
applyArrayChanges(inventory.Recipes, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addLevelKeys = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addLevelKeys = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "LevelKeys", itemsArray);
|
applyArrayChanges(inventory.LevelKeys, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawUpgrade[]): void => {
|
export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawUpgrade[]): void => {
|
||||||
@ -1694,7 +1633,7 @@ export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawU
|
|||||||
if (RawUpgrades[itemIndex].ItemCount == 0) {
|
if (RawUpgrades[itemIndex].ItemCount == 0) {
|
||||||
RawUpgrades.splice(itemIndex, 1);
|
RawUpgrades.splice(itemIndex, 1);
|
||||||
} else if (RawUpgrades[itemIndex].ItemCount <= 0) {
|
} else if (RawUpgrades[itemIndex].ItemCount <= 0) {
|
||||||
logger.warn(`inventory.RawUpgrades has a negative count for ${ItemType}`);
|
logger.warn(`account now owns a negative amount of ${ItemType}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -1709,7 +1648,7 @@ export const addFusionTreasures = (inventory: TInventoryDatabaseDocument, itemsA
|
|||||||
if (FusionTreasures[itemIndex].ItemCount == 0) {
|
if (FusionTreasures[itemIndex].ItemCount == 0) {
|
||||||
FusionTreasures.splice(itemIndex, 1);
|
FusionTreasures.splice(itemIndex, 1);
|
||||||
} else if (FusionTreasures[itemIndex].ItemCount <= 0) {
|
} else if (FusionTreasures[itemIndex].ItemCount <= 0) {
|
||||||
logger.warn(`inventory.FusionTreasures has a negative count for ${ItemType}`);
|
logger.warn(`account now owns a negative amount of ${ItemType}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FusionTreasures.push({ ItemCount, ItemType, Sockets });
|
FusionTreasures.push({ ItemCount, ItemType, Sockets });
|
||||||
@ -1755,7 +1694,6 @@ export const addLoreFragmentScans = (inventory: TInventoryDatabaseDocument, arr:
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const addChallenges = (
|
export const addChallenges = (
|
||||||
account: TAccountDocument,
|
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
ChallengeProgress: IChallengeProgress[],
|
ChallengeProgress: IChallengeProgress[],
|
||||||
SeasonChallengeCompletions: ISeasonChallenge[] | undefined
|
SeasonChallengeCompletions: ISeasonChallenge[] | undefined
|
||||||
@ -1778,33 +1716,26 @@ export const addChallenges = (
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = ExportChallenges[challenge.challenge];
|
const meta = ExportNightwave.challenges[challenge.challenge];
|
||||||
const nightwaveSyndicateTag = getNightwaveSyndicateTag(account.BuildLabel);
|
logger.debug("Completed challenge", meta);
|
||||||
logger.debug("Completed season challenge", {
|
|
||||||
uniqueName: challenge.challenge,
|
let affiliation = inventory.Affiliations.find(x => x.Tag == ExportNightwave.affiliationTag);
|
||||||
syndicateTag: nightwaveSyndicateTag,
|
|
||||||
...meta
|
|
||||||
});
|
|
||||||
if (nightwaveSyndicateTag) {
|
|
||||||
let affiliation = inventory.Affiliations.find(x => x.Tag == nightwaveSyndicateTag);
|
|
||||||
if (!affiliation) {
|
if (!affiliation) {
|
||||||
affiliation =
|
affiliation =
|
||||||
inventory.Affiliations[
|
inventory.Affiliations[
|
||||||
inventory.Affiliations.push({
|
inventory.Affiliations.push({
|
||||||
Tag: nightwaveSyndicateTag,
|
Tag: ExportNightwave.affiliationTag,
|
||||||
Standing: 0
|
Standing: 0
|
||||||
}) - 1
|
}) - 1
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
affiliation.Standing += meta.standing;
|
||||||
|
|
||||||
const standingToAdd = meta.standing! * (config.nightwaveStandingMultiplier ?? 1);
|
|
||||||
affiliation.Standing += standingToAdd;
|
|
||||||
if (affiliationMods.length == 0) {
|
if (affiliationMods.length == 0) {
|
||||||
affiliationMods.push({ Tag: nightwaveSyndicateTag });
|
affiliationMods.push({ Tag: ExportNightwave.affiliationTag });
|
||||||
}
|
}
|
||||||
affiliationMods[0].Standing ??= 0;
|
affiliationMods[0].Standing ??= 0;
|
||||||
affiliationMods[0].Standing += standingToAdd;
|
affiliationMods[0].Standing += meta.standing;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return affiliationMods;
|
return affiliationMods;
|
||||||
@ -1954,29 +1885,6 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
|
|
||||||
let dialogue = inventory.DialogueHistory!.Dialogues!.find(x => x.DialogueName == dialogueName);
|
|
||||||
if (!dialogue) {
|
|
||||||
dialogue =
|
|
||||||
inventory.DialogueHistory!.Dialogues![
|
|
||||||
inventory.DialogueHistory!.Dialogues!.push({
|
|
||||||
Rank: 0,
|
|
||||||
Chemistry: 0,
|
|
||||||
AvailableDate: new Date(0),
|
|
||||||
AvailableGiftDate: new Date(0),
|
|
||||||
RankUpExpiry: new Date(0),
|
|
||||||
BountyChemExpiry: new Date(0),
|
|
||||||
QueuedDialogues: [],
|
|
||||||
Gifts: [],
|
|
||||||
Booleans: [],
|
|
||||||
Completed: [],
|
|
||||||
DialogueName: dialogueName
|
|
||||||
}) - 1
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return dialogue;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICalendarProgress => {
|
export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICalendarProgress => {
|
||||||
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
||||||
|
|
||||||
@ -2016,7 +1924,8 @@ export const giveNemesisWeaponRecipe = (
|
|||||||
weaponType: string,
|
weaponType: string,
|
||||||
nemesisName: string = "AGOR ROK",
|
nemesisName: string = "AGOR ROK",
|
||||||
weaponLoc?: string,
|
weaponLoc?: string,
|
||||||
profile: INemesisProfile = generateNemesisProfile()
|
KillingSuit: string = "/Lotus/Powersuits/Ember/Ember",
|
||||||
|
fp: bigint = generateRewardSeed()
|
||||||
): void => {
|
): void => {
|
||||||
if (!weaponLoc) {
|
if (!weaponLoc) {
|
||||||
weaponLoc = ExportWeapons[weaponType].name;
|
weaponLoc = ExportWeapons[weaponType].name;
|
||||||
@ -2037,8 +1946,8 @@ export const giveNemesisWeaponRecipe = (
|
|||||||
compat: weaponType,
|
compat: weaponType,
|
||||||
buffs: [
|
buffs: [
|
||||||
{
|
{
|
||||||
Tag: profile.innateDamageTag,
|
Tag: getInnateDamageTag(KillingSuit),
|
||||||
Value: profile.innateDamageValue
|
Value: getInnateDamageValue(fp)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2047,15 +1956,27 @@ export const giveNemesisWeaponRecipe = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const giveNemesisPetRecipe = (
|
export const giveNemesisPetRecipe = (inventory: TInventoryDatabaseDocument, nemesisName: string = "AGOR ROK"): void => {
|
||||||
inventory: TInventoryDatabaseDocument,
|
const head = getRandomElement([
|
||||||
nemesisName: string = "AGOR ROK",
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
|
||||||
profile: INemesisProfile = generateNemesisProfile()
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
|
||||||
): void => {
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
|
||||||
const head = profile.petHead!;
|
])!;
|
||||||
const body = profile.petBody!;
|
const body = getRandomElement([
|
||||||
const legs = profile.petLegs!;
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
|
||||||
const tail = profile.petTail!;
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyC"
|
||||||
|
])!;
|
||||||
|
const legs = getRandomElement([
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsA",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsC"
|
||||||
|
])!;
|
||||||
|
const tail = getRandomElement([
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailA",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailC"
|
||||||
|
])!;
|
||||||
const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == head)![0];
|
const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == head)![0];
|
||||||
addRecipes(inventory, [
|
addRecipes(inventory, [
|
||||||
{
|
{
|
||||||
@ -2072,7 +1993,3 @@ export const giveNemesisPetRecipe = (
|
|||||||
} satisfies INemesisPetTargetFingerprint)
|
} satisfies INemesisPetTargetFingerprint)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEffectiveAvatarImageType = (inventory: TInventoryDatabaseDocument): string => {
|
|
||||||
return inventory.ActiveAvatarImageType ?? "/Lotus/Types/StoreItems/AvatarImages/AvatarImageDefault";
|
|
||||||
};
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
||||||
|
import { getIndexAfter } from "@/src/helpers/stringHelpers";
|
||||||
import {
|
import {
|
||||||
dict_de,
|
dict_de,
|
||||||
dict_en,
|
dict_en,
|
||||||
@ -52,32 +53,20 @@ export const getRecipeByResult = (resultType: string): IRecipe | undefined => {
|
|||||||
return Object.values(ExportRecipes).find(x => x.resultType == resultType);
|
return Object.values(ExportRecipes).find(x => x.resultType == resultType);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getItemCategoryByUniqueName = (uniqueName: string): string | undefined => {
|
export const getItemCategoryByUniqueName = (uniqueName: string): string => {
|
||||||
if (uniqueName in ExportCustoms) {
|
//Lotus/Types/Items/MiscItems/PolymerBundle
|
||||||
return ExportCustoms[uniqueName].productCategory;
|
|
||||||
|
let splitWord = "Items/";
|
||||||
|
if (!uniqueName.includes("/Items/")) {
|
||||||
|
splitWord = "/Types/";
|
||||||
}
|
}
|
||||||
if (uniqueName in ExportDrones) {
|
|
||||||
return "Drones";
|
const index = getIndexAfter(uniqueName, splitWord);
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error(`error parsing item category ${uniqueName}`);
|
||||||
}
|
}
|
||||||
if (uniqueName in ExportKeys) {
|
const category = uniqueName.substring(index).split("/")[0];
|
||||||
return "LevelKeys";
|
return category;
|
||||||
}
|
|
||||||
if (uniqueName in ExportGear) {
|
|
||||||
return "Consumables";
|
|
||||||
}
|
|
||||||
if (uniqueName in ExportResources) {
|
|
||||||
return ExportResources[uniqueName].productCategory;
|
|
||||||
}
|
|
||||||
if (uniqueName in ExportSentinels) {
|
|
||||||
return ExportSentinels[uniqueName].productCategory;
|
|
||||||
}
|
|
||||||
if (uniqueName in ExportWarframes) {
|
|
||||||
return ExportWarframes[uniqueName].productCategory;
|
|
||||||
}
|
|
||||||
if (uniqueName in ExportWeapons) {
|
|
||||||
return ExportWeapons[uniqueName].productCategory;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getItemName = (uniqueName: string): string | undefined => {
|
export const getItemName = (uniqueName: string): string | undefined => {
|
||||||
@ -233,7 +222,7 @@ export const isStoreItem = (type: string): boolean => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const toStoreItem = (type: string): string => {
|
export const toStoreItem = (type: string): string => {
|
||||||
if (type.startsWith("/Lotus/Types/Boosters/")) {
|
if (type.startsWith("/Lotus/Types/StoreItems/Boosters/")) {
|
||||||
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type);
|
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type);
|
||||||
if (boosterEntry) {
|
if (boosterEntry) {
|
||||||
return boosterEntry[0];
|
return boosterEntry[0];
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
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 "../types/purchaseTypes";
|
||||||
import { TAccountDocument } from "./loginService";
|
import { TAccountDocument } from "./loginService";
|
||||||
import { mixSeeds, SRng } from "./rngService";
|
import { CRng, mixSeeds } from "./rngService";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
||||||
import { addBooster, updateCurrency } from "./inventoryService";
|
import { addBooster, updateCurrency } from "./inventoryService";
|
||||||
import { handleStoreItemAcquisition } from "./purchaseService";
|
import { handleStoreItemAcquisition } from "./purchaseService";
|
||||||
import {
|
import { ExportBoosters, ExportRecipes, ExportWarframes, ExportWeapons } from "warframe-public-export-plus";
|
||||||
ExportBoosterPacks,
|
|
||||||
ExportBoosters,
|
|
||||||
ExportRecipes,
|
|
||||||
ExportWarframes,
|
|
||||||
ExportWeapons
|
|
||||||
} from "warframe-public-export-plus";
|
|
||||||
import { toStoreItem } from "./itemDataService";
|
import { toStoreItem } from "./itemDataService";
|
||||||
|
|
||||||
export interface ILoginRewardsReponse {
|
export interface ILoginRewardsReponse {
|
||||||
@ -55,8 +49,8 @@ const scaleAmount = (day: number, amount: number, scalingMultiplier: number): nu
|
|||||||
// Always produces the same result for the same account _id & LoginDays pair.
|
// Always produces the same result for the same account _id & LoginDays pair.
|
||||||
export const isLoginRewardAChoice = (account: TAccountDocument): boolean => {
|
export const isLoginRewardAChoice = (account: TAccountDocument): boolean => {
|
||||||
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
||||||
const rng = new SRng(mixSeeds(accountSeed, account.LoginDays));
|
const rng = new CRng(mixSeeds(accountSeed, account.LoginDays));
|
||||||
return rng.randomFloat() < 0.25;
|
return rng.random() < 0.25; // Using 25% as an approximate chance for pick-a-doors. More conclusive data analysis is needed.
|
||||||
};
|
};
|
||||||
|
|
||||||
// Always produces the same result for the same account _id & LoginDays pair.
|
// Always produces the same result for the same account _id & LoginDays pair.
|
||||||
@ -65,8 +59,8 @@ export const getRandomLoginRewards = (
|
|||||||
inventory: TInventoryDatabaseDocument
|
inventory: TInventoryDatabaseDocument
|
||||||
): ILoginReward[] => {
|
): ILoginReward[] => {
|
||||||
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
||||||
const rng = new SRng(mixSeeds(accountSeed, account.LoginDays));
|
const rng = new CRng(mixSeeds(accountSeed, account.LoginDays));
|
||||||
const pick_a_door = rng.randomFloat() < 0.25;
|
const pick_a_door = rng.random() < 0.25; // Using 25% as an approximate chance for pick-a-doors. More conclusive data analysis is needed.
|
||||||
const rewards = [getRandomLoginReward(rng, account.LoginDays, inventory)];
|
const rewards = [getRandomLoginReward(rng, account.LoginDays, inventory)];
|
||||||
if (pick_a_door) {
|
if (pick_a_door) {
|
||||||
do {
|
do {
|
||||||
@ -79,10 +73,9 @@ export const getRandomLoginRewards = (
|
|||||||
return rewards;
|
return rewards;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRandomLoginReward = (rng: SRng, day: number, inventory: TInventoryDatabaseDocument): ILoginReward => {
|
const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatabaseDocument): ILoginReward => {
|
||||||
const reward = rng.randomReward(randomRewards)!;
|
const reward = rng.randomReward(randomRewards)!;
|
||||||
//const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!;
|
//const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!;
|
||||||
let storeItemType: string = reward.StoreItemType;
|
|
||||||
if (reward.RewardType == "RT_RANDOM_RECIPE") {
|
if (reward.RewardType == "RT_RANDOM_RECIPE") {
|
||||||
const masteredItems = new Set();
|
const masteredItems = new Set();
|
||||||
for (const entry of inventory.XPInfo) {
|
for (const entry of inventory.XPInfo) {
|
||||||
@ -109,12 +102,7 @@ const getRandomLoginReward = (rng: SRng, day: number, inventory: TInventoryDatab
|
|||||||
// This account has all applicable warframes and weapons already mastered (filthy cheater), need a different reward.
|
// This account has all applicable warframes and weapons already mastered (filthy cheater), need a different reward.
|
||||||
return getRandomLoginReward(rng, day, inventory);
|
return getRandomLoginReward(rng, day, inventory);
|
||||||
}
|
}
|
||||||
storeItemType = toStoreItem(rng.randomElement(eligibleRecipes)!);
|
reward.StoreItemType = toStoreItem(rng.randomElement(eligibleRecipes)!);
|
||||||
} else if (reward.StoreItemType == "/Lotus/StoreItems/Types/BoosterPacks/LoginRewardRandomProjection") {
|
|
||||||
storeItemType = toStoreItem(
|
|
||||||
rng.randomElement(ExportBoosterPacks["/Lotus/Types/BoosterPacks/LoginRewardRandomProjection"].components)!
|
|
||||||
.Item
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
//_id: toOid(new Types.ObjectId()),
|
//_id: toOid(new Types.ObjectId()),
|
||||||
@ -122,7 +110,7 @@ const getRandomLoginReward = (rng: SRng, day: number, inventory: TInventoryDatab
|
|||||||
//CouponType: "CPT_PLATINUM",
|
//CouponType: "CPT_PLATINUM",
|
||||||
Icon: reward.Icon ?? "",
|
Icon: reward.Icon ?? "",
|
||||||
//ItemType: "",
|
//ItemType: "",
|
||||||
StoreItemType: storeItemType,
|
StoreItemType: reward.StoreItemType,
|
||||||
//ProductCategory: "Pistols",
|
//ProductCategory: "Pistols",
|
||||||
Amount: reward.Duration ? 1 : Math.round(scaleAmount(day, reward.Amount, reward.ScalingMultiplier)),
|
Amount: reward.Duration ? 1 : Math.round(scaleAmount(day, reward.Amount, reward.ScalingMultiplier)),
|
||||||
ScalingMultiplier: reward.ScalingMultiplier,
|
ScalingMultiplier: reward.ScalingMultiplier,
|
||||||
|
@ -74,7 +74,7 @@ export const getAccountForRequest = async (req: Request): Promise<TAccountDocume
|
|||||||
throw new Error("Request is missing nonce parameter");
|
throw new Error("Request is missing nonce parameter");
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = await Account.findById(req.query.accountId as string);
|
const account = await Account.findById(req.query.accountId);
|
||||||
if (!account || account.Nonce != nonce) {
|
if (!account || account.Nonce != nonce) {
|
||||||
throw new Error("Invalid accountId-nonce pair");
|
throw new Error("Invalid accountId-nonce pair");
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ export const getAccountIdForRequest = async (req: Request): Promise<string> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isAdministrator = (account: TAccountDocument): boolean => {
|
export const isAdministrator = (account: TAccountDocument): boolean => {
|
||||||
return config.administratorNames?.indexOf(account.DisplayName) != -1;
|
return !!config.administratorNames?.find(x => x == account.DisplayName);
|
||||||
};
|
};
|
||||||
|
|
||||||
const platform_magics = [753, 639, 247, 37, 60];
|
const platform_magics = [753, 639, 247, 37, 60];
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ import {
|
|||||||
updateSlots
|
updateSlots
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { getRandomWeightedRewardUc } from "@/src/services/rngService";
|
import { getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||||
import { applyStandingToVendorManifest, getVendorManifestByOid } from "@/src/services/serversideVendorsService";
|
import { getVendorManifestByOid } from "@/src/services/serversideVendorsService";
|
||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IPurchaseRequest, IPurchaseResponse, SlotPurchase, IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IPurchaseRequest, IPurchaseResponse, SlotPurchase, IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
@ -53,9 +53,8 @@ export const handlePurchase = async (
|
|||||||
const prePurchaseInventoryChanges: IInventoryChanges = {};
|
const prePurchaseInventoryChanges: IInventoryChanges = {};
|
||||||
let seed: bigint | undefined;
|
let seed: bigint | undefined;
|
||||||
if (purchaseRequest.PurchaseParams.Source == 7) {
|
if (purchaseRequest.PurchaseParams.Source == 7) {
|
||||||
let manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!);
|
const manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!);
|
||||||
if (manifest) {
|
if (manifest) {
|
||||||
manifest = applyStandingToVendorManifest(inventory, manifest);
|
|
||||||
let ItemId: string | undefined;
|
let ItemId: string | undefined;
|
||||||
if (purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) {
|
if (purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) {
|
||||||
ItemId = (JSON.parse(purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) as { ItemId: string })
|
ItemId = (JSON.parse(purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) as { ItemId: string })
|
||||||
@ -93,7 +92,7 @@ export const handlePurchase = async (
|
|||||||
if (!config.noVendorPurchaseLimits && ItemId) {
|
if (!config.noVendorPurchaseLimits && ItemId) {
|
||||||
inventory.RecentVendorPurchases ??= [];
|
inventory.RecentVendorPurchases ??= [];
|
||||||
let vendorPurchases = inventory.RecentVendorPurchases.find(
|
let vendorPurchases = inventory.RecentVendorPurchases.find(
|
||||||
x => x.VendorType == manifest!.VendorInfo.TypeName
|
x => x.VendorType == manifest.VendorInfo.TypeName
|
||||||
);
|
);
|
||||||
if (!vendorPurchases) {
|
if (!vendorPurchases) {
|
||||||
vendorPurchases =
|
vendorPurchases =
|
||||||
|
@ -86,12 +86,54 @@ export const mixSeeds = (seed1: number, seed2: number): number => {
|
|||||||
return seed >>> 0;
|
return seed >>> 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Seeded RNG with identical results to the game client. Based on work by Donald Knuth.
|
// Seeded RNG for internal usage. Based on recommendations in the ISO C standards.
|
||||||
|
export class CRng {
|
||||||
|
state: number;
|
||||||
|
|
||||||
|
constructor(seed: number = 1) {
|
||||||
|
this.state = seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
random(): number {
|
||||||
|
this.state = (this.state * 1103515245 + 12345) & 0x7fffffff;
|
||||||
|
return (this.state & 0x3fffffff) / 0x3fffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
randomInt(min: number, max: number): number {
|
||||||
|
const diff = max - min;
|
||||||
|
if (diff != 0) {
|
||||||
|
if (diff < 0) {
|
||||||
|
throw new Error(`max must be greater than min`);
|
||||||
|
}
|
||||||
|
if (diff > 0x3fffffff) {
|
||||||
|
throw new Error(`insufficient entropy`);
|
||||||
|
}
|
||||||
|
min += Math.floor(this.random() * (diff + 1));
|
||||||
|
}
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
randomElement<T>(arr: readonly T[]): T | undefined {
|
||||||
|
return arr[Math.floor(this.random() * arr.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
||||||
|
return getRewardAtPercentage(pool, this.random());
|
||||||
|
}
|
||||||
|
|
||||||
|
churnSeed(its: number): void {
|
||||||
|
while (its--) {
|
||||||
|
this.state = (this.state * 1103515245 + 12345) & 0x7fffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seeded RNG for cases where we need identical results to the game client. Based on work by Donald Knuth.
|
||||||
export class SRng {
|
export class SRng {
|
||||||
state: bigint;
|
state: bigint;
|
||||||
|
|
||||||
constructor(seed: bigint | number) {
|
constructor(seed: bigint) {
|
||||||
this.state = BigInt(seed);
|
this.state = seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
randomInt(min: number, max: number): number {
|
randomInt(min: number, max: number): number {
|
||||||
@ -115,19 +157,4 @@ export class SRng {
|
|||||||
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
||||||
return getRewardAtPercentage(pool, this.randomFloat());
|
return getRewardAtPercentage(pool, this.randomFloat());
|
||||||
}
|
}
|
||||||
|
|
||||||
churnSeed(its: number): void {
|
|
||||||
while (its--) {
|
|
||||||
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shuffleArray<T>(arr: T[]): void {
|
|
||||||
for (let lastIdx = arr.length - 1; lastIdx >= 1; --lastIdx) {
|
|
||||||
const swapIdx = this.randomInt(0, lastIdx);
|
|
||||||
const tmp = arr[swapIdx];
|
|
||||||
arr[swapIdx] = arr[lastIdx];
|
|
||||||
arr[lastIdx] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { isDev } from "@/src/helpers/pathHelper";
|
|
||||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { CRng, mixSeeds } from "@/src/services/rngService";
|
||||||
import { mixSeeds, SRng } from "@/src/services/rngService";
|
|
||||||
import { IMongoDate } from "@/src/types/commonTypes";
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
|
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { ExportVendors, IRange } from "warframe-public-export-plus";
|
||||||
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
|
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
|
||||||
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
|
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
|
||||||
@ -17,14 +14,19 @@ import DeimosHivemindCommisionsManifestTokenVendor from "@/static/fixed_response
|
|||||||
import DeimosHivemindCommisionsManifestWeaponsmith from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json";
|
import DeimosHivemindCommisionsManifestWeaponsmith from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json";
|
||||||
import DeimosHivemindTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json";
|
import DeimosHivemindTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json";
|
||||||
import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json";
|
import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json";
|
||||||
|
import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json";
|
||||||
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
|
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
|
||||||
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
|
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
|
||||||
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
|
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
|
||||||
|
import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json";
|
||||||
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
|
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
|
||||||
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
|
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
|
||||||
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
|
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
|
||||||
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
|
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
|
||||||
|
import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json";
|
||||||
|
import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json";
|
||||||
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
|
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
|
||||||
|
import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json";
|
||||||
import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json";
|
import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json";
|
||||||
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
|
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
|
||||||
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
|
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
|
||||||
@ -39,14 +41,19 @@ const rawVendorManifests: IVendorManifest[] = [
|
|||||||
DeimosHivemindCommisionsManifestWeaponsmith,
|
DeimosHivemindCommisionsManifestWeaponsmith,
|
||||||
DeimosHivemindTokenVendorManifest,
|
DeimosHivemindTokenVendorManifest,
|
||||||
DeimosPetVendorManifest,
|
DeimosPetVendorManifest,
|
||||||
|
DeimosProspectorVendorManifest,
|
||||||
DuviriAcrithisVendorManifest,
|
DuviriAcrithisVendorManifest,
|
||||||
EntratiLabsEntratiLabsCommisionsManifest,
|
EntratiLabsEntratiLabsCommisionsManifest,
|
||||||
EntratiLabsEntratiLabVendorManifest,
|
EntratiLabsEntratiLabVendorManifest,
|
||||||
|
HubsIronwakeDondaVendorManifest, // uses preprocessing
|
||||||
HubsRailjackCrewMemberVendorManifest,
|
HubsRailjackCrewMemberVendorManifest,
|
||||||
MaskSalesmanManifest,
|
MaskSalesmanManifest,
|
||||||
Nova1999ConquestShopManifest,
|
Nova1999ConquestShopManifest,
|
||||||
OstronPetVendorManifest,
|
OstronPetVendorManifest,
|
||||||
|
OstronProspectorVendorManifest,
|
||||||
|
RadioLegionIntermission12VendorManifest,
|
||||||
SolarisDebtTokenVendorRepossessionsManifest,
|
SolarisDebtTokenVendorRepossessionsManifest,
|
||||||
|
SolarisProspectorVendorManifest,
|
||||||
Temple1999VendorManifest,
|
Temple1999VendorManifest,
|
||||||
TeshinHardModeVendorManifest, // uses preprocessing
|
TeshinHardModeVendorManifest, // uses preprocessing
|
||||||
ZarimanCommisionsManifestArchimedean
|
ZarimanCommisionsManifestArchimedean
|
||||||
@ -74,32 +81,23 @@ const generatableVendors: IGeneratableVendorInfo[] = [
|
|||||||
WeaponUpgradeValueAttenuationExponent: 2.25,
|
WeaponUpgradeValueAttenuationExponent: 2.25,
|
||||||
cycleOffset: 1744934400_000,
|
cycleOffset: 1744934400_000,
|
||||||
cycleDuration: 4 * unixTimesInMs.day
|
cycleDuration: 4 * unixTimesInMs.day
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: { $oid: "61ba123467e5d37975aeeb03" },
|
||||||
|
TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
|
||||||
|
RandomSeedType: "VRST_FLAVOUR_TEXT",
|
||||||
|
cycleDuration: unixTimesInMs.week // TODO: Auto-detect this based on the items, so we don't need to specify it explicitly.
|
||||||
}
|
}
|
||||||
|
// {
|
||||||
|
// _id: { $oid: "5dbb4c41e966f7886c3ce939" },
|
||||||
|
// TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest"
|
||||||
|
// }
|
||||||
];
|
];
|
||||||
|
|
||||||
const getVendorOid = (typeName: string): string => {
|
const getVendorOid = (typeName: string): string => {
|
||||||
return "5be4a159b144f3cd" + catBreadHash(typeName).toString(16).padStart(8, "0");
|
return "5be4a159b144f3cd" + catBreadHash(typeName).toString(16).padStart(8, "0");
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://stackoverflow.com/a/17445304
|
|
||||||
const gcd = (a: number, b: number): number => {
|
|
||||||
return b ? gcd(b, a % b) : a;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCycleDuration = (manifest: IVendor): number => {
|
|
||||||
let dur = 0;
|
|
||||||
for (const item of manifest.items) {
|
|
||||||
if (typeof item.durationHours != "number") {
|
|
||||||
dur = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (dur != item.durationHours) {
|
|
||||||
dur = gcd(dur, item.durationHours);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dur * unixTimesInMs.hour;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
|
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
|
||||||
for (const vendorManifest of rawVendorManifests) {
|
for (const vendorManifest of rawVendorManifests) {
|
||||||
if (vendorManifest.VendorInfo.TypeName == typeName) {
|
if (vendorManifest.VendorInfo.TypeName == typeName) {
|
||||||
@ -112,12 +110,11 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest |
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeName in ExportVendors) {
|
if (typeName in ExportVendors) {
|
||||||
const manifest = ExportVendors[typeName];
|
|
||||||
return generateVendorManifest({
|
return generateVendorManifest({
|
||||||
_id: { $oid: getVendorOid(typeName) },
|
_id: { $oid: getVendorOid(typeName) },
|
||||||
TypeName: typeName,
|
TypeName: typeName,
|
||||||
RandomSeedType: manifest.randomSeedType,
|
RandomSeedType: ExportVendors[typeName].randomSeedType,
|
||||||
cycleDuration: getCycleDuration(manifest)
|
cycleDuration: unixTimesInMs.hour
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -141,50 +138,13 @@ export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined
|
|||||||
_id: { $oid: typeNameOid },
|
_id: { $oid: typeNameOid },
|
||||||
TypeName: typeName,
|
TypeName: typeName,
|
||||||
RandomSeedType: manifest.randomSeedType,
|
RandomSeedType: manifest.randomSeedType,
|
||||||
cycleDuration: getCycleDuration(manifest)
|
cycleDuration: unixTimesInMs.hour
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const applyStandingToVendorManifest = (
|
|
||||||
inventory: TInventoryDatabaseDocument,
|
|
||||||
vendorManifest: IVendorManifest
|
|
||||||
): IVendorManifest => {
|
|
||||||
return {
|
|
||||||
VendorInfo: {
|
|
||||||
...vendorManifest.VendorInfo,
|
|
||||||
ItemManifest: [...vendorManifest.VendorInfo.ItemManifest].map(offer => {
|
|
||||||
if (offer.Affiliation && offer.ReductionPerPositiveRank && offer.IncreasePerNegativeRank) {
|
|
||||||
const title: number = inventory.Affiliations.find(x => x.Tag == offer.Affiliation)?.Title ?? 0;
|
|
||||||
const factor =
|
|
||||||
1 + (title < 0 ? offer.IncreasePerNegativeRank : offer.ReductionPerPositiveRank) * title * -1;
|
|
||||||
//console.log(offer.Affiliation, title, factor);
|
|
||||||
if (factor) {
|
|
||||||
offer = { ...offer };
|
|
||||||
if (offer.RegularPrice) {
|
|
||||||
offer.RegularPriceBeforeDiscount = offer.RegularPrice;
|
|
||||||
offer.RegularPrice = [
|
|
||||||
Math.trunc(offer.RegularPriceBeforeDiscount[0] * factor),
|
|
||||||
Math.trunc(offer.RegularPriceBeforeDiscount[1] * factor)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (offer.ItemPrices) {
|
|
||||||
offer.ItemPricesBeforeDiscount = offer.ItemPrices;
|
|
||||||
offer.ItemPrices = [];
|
|
||||||
for (const item of offer.ItemPricesBeforeDiscount) {
|
|
||||||
offer.ItemPrices.push({ ...item, ItemCount: Math.trunc(item.ItemCount * factor) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return offer;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const preprocessVendorManifest = (originalManifest: IVendorManifest): IVendorManifest => {
|
const preprocessVendorManifest = (originalManifest: IVendorManifest): IVendorManifest => {
|
||||||
if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) {
|
if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) {
|
||||||
const manifest = structuredClone(originalManifest);
|
const manifest = structuredClone(originalManifest);
|
||||||
@ -216,49 +176,24 @@ const toRange = (value: IRange | number): IRange => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCycleDurationRange = (manifest: IVendor): IRange | undefined => {
|
const vendorInfoCache: Record<string, IVendorInfo> = {};
|
||||||
const res: IRange = { minValue: Number.MAX_SAFE_INTEGER, maxValue: 0 };
|
|
||||||
for (const offer of manifest.items) {
|
|
||||||
if (offer.durationHours) {
|
|
||||||
const range = toRange(offer.durationHours);
|
|
||||||
if (res.minValue > range.minValue) {
|
|
||||||
res.minValue = range.minValue;
|
|
||||||
}
|
|
||||||
if (res.maxValue < range.maxValue) {
|
|
||||||
res.maxValue = range.maxValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.maxValue != 0 ? res : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const vendorManifestCache: Record<string, IVendorManifest> = {};
|
|
||||||
|
|
||||||
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
|
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
|
||||||
if (!(vendorInfo.TypeName in vendorManifestCache)) {
|
if (!(vendorInfo.TypeName in vendorInfoCache)) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
|
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
|
||||||
vendorManifestCache[vendorInfo.TypeName] = {
|
vendorInfoCache[vendorInfo.TypeName] = {
|
||||||
VendorInfo: {
|
|
||||||
...clientVendorInfo,
|
...clientVendorInfo,
|
||||||
ItemManifest: [],
|
ItemManifest: [],
|
||||||
Expiry: { $date: { $numberLong: "0" } }
|
Expiry: { $date: { $numberLong: "0" } }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const cacheEntry = vendorManifestCache[vendorInfo.TypeName];
|
const processed = vendorInfoCache[vendorInfo.TypeName];
|
||||||
const info = cacheEntry.VendorInfo;
|
if (Date.now() >= parseInt(processed.Expiry.$date.$numberLong)) {
|
||||||
const manifest = ExportVendors[vendorInfo.TypeName];
|
|
||||||
const cycleDurationRange = getCycleDurationRange(manifest);
|
|
||||||
let now = Date.now();
|
|
||||||
if (cycleDurationRange && cycleDurationRange.minValue != cycleDurationRange.maxValue) {
|
|
||||||
now -= (cycleDurationRange.maxValue - 1) * unixTimesInMs.hour;
|
|
||||||
}
|
|
||||||
while (Date.now() >= parseInt(info.Expiry.$date.$numberLong)) {
|
|
||||||
// Remove expired offers
|
// Remove expired offers
|
||||||
for (let i = 0; i != info.ItemManifest.length; ) {
|
for (let i = 0; i != processed.ItemManifest.length; ) {
|
||||||
if (now >= parseInt(info.ItemManifest[i].Expiry.$date.$numberLong)) {
|
if (Date.now() >= parseInt(processed.ItemManifest[i].Expiry.$date.$numberLong)) {
|
||||||
info.ItemManifest.splice(i, 1);
|
processed.ItemManifest.splice(i, 1);
|
||||||
} else {
|
} else {
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
@ -268,63 +203,36 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
const vendorSeed = parseInt(vendorInfo._id.$oid.substring(16), 16);
|
const vendorSeed = parseInt(vendorInfo._id.$oid.substring(16), 16);
|
||||||
const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000;
|
const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000;
|
||||||
const cycleDuration = vendorInfo.cycleDuration;
|
const cycleDuration = vendorInfo.cycleDuration;
|
||||||
const cycleIndex = Math.trunc((now - cycleOffset) / cycleDuration);
|
const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration);
|
||||||
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
|
const rng = new CRng(mixSeeds(vendorSeed, cycleIndex));
|
||||||
const offersToAdd: IVendorOffer[] = [];
|
const manifest = ExportVendors[vendorInfo.TypeName];
|
||||||
if (!manifest.isOneBinPerCycle) {
|
const offersToAdd = [];
|
||||||
const remainingItemCapacity: Record<string, number> = {};
|
if (manifest.numItems && !manifest.isOneBinPerCycle) {
|
||||||
for (const item of manifest.items) {
|
|
||||||
remainingItemCapacity[item.storeItem] = 1 + item.duplicates;
|
|
||||||
}
|
|
||||||
for (const offer of info.ItemManifest) {
|
|
||||||
remainingItemCapacity[offer.StoreItem] -= 1;
|
|
||||||
}
|
|
||||||
if (manifest.numItems && manifest.items.length != manifest.numItems.minValue) {
|
|
||||||
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
|
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
|
||||||
while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
|
while (processed.ItemManifest.length + offersToAdd.length < numItemsTarget) {
|
||||||
// TODO: Consider per-bin item limits
|
// TODO: Consider per-bin item limits
|
||||||
// TODO: Consider item probability weightings
|
// TODO: Consider item probability weightings
|
||||||
const item = rng.randomElement(manifest.items)!;
|
offersToAdd.push(rng.randomElement(manifest.items)!);
|
||||||
if (remainingItemCapacity[item.storeItem] != 0) {
|
|
||||||
remainingItemCapacity[item.storeItem] -= 1;
|
|
||||||
offersToAdd.push(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const item of manifest.items) {
|
let binThisCycle;
|
||||||
if (!item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) {
|
if (manifest.isOneBinPerCycle) {
|
||||||
remainingItemCapacity[item.storeItem] -= 1;
|
binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
|
||||||
offersToAdd.push(item);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (const e of Object.entries(remainingItemCapacity)) {
|
|
||||||
const item = manifest.items.find(x => x.storeItem == e[0])!;
|
|
||||||
if (!item.alwaysOffered) {
|
|
||||||
while (e[1] != 0) {
|
|
||||||
e[1] -= 1;
|
|
||||||
offersToAdd.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const item of manifest.items) {
|
|
||||||
if (item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) {
|
|
||||||
remainingItemCapacity[item.storeItem] -= 1;
|
|
||||||
offersToAdd.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
offersToAdd.reverse();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
|
|
||||||
for (const rawItem of manifest.items) {
|
for (const rawItem of manifest.items) {
|
||||||
if (rawItem.bin == binThisCycle) {
|
if (!manifest.isOneBinPerCycle || rawItem.bin == binThisCycle) {
|
||||||
offersToAdd.push(rawItem);
|
offersToAdd.push(rawItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For most vendors, the offers seem to roughly be in reverse order from the manifest. Coda weapons are an odd exception.
|
||||||
|
if (!manifest.isOneBinPerCycle) {
|
||||||
|
offersToAdd.reverse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const cycleStart = cycleOffset + cycleIndex * cycleDuration;
|
const cycleStart = cycleOffset + cycleIndex * cycleDuration;
|
||||||
for (const rawItem of offersToAdd) {
|
for (const rawItem of offersToAdd) {
|
||||||
const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration);
|
const durationHoursRange = toRange(rawItem.durationHours);
|
||||||
const expiry =
|
const expiry =
|
||||||
cycleStart +
|
cycleStart +
|
||||||
rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour;
|
rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour;
|
||||||
@ -332,14 +240,15 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
StoreItem: rawItem.storeItem,
|
StoreItem: rawItem.storeItem,
|
||||||
ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
|
ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
|
||||||
Bin: "BIN_" + rawItem.bin,
|
Bin: "BIN_" + rawItem.bin,
|
||||||
QuantityMultiplier: rawItem.quantity,
|
QuantityMultiplier: 1,
|
||||||
Expiry: { $date: { $numberLong: expiry.toString() } },
|
Expiry: { $date: { $numberLong: expiry.toString() } },
|
||||||
AllowMultipurchase: false,
|
AllowMultipurchase: false,
|
||||||
Id: {
|
Id: {
|
||||||
$oid:
|
$oid:
|
||||||
((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
|
((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
|
||||||
vendorInfo._id.$oid.substring(8, 16) +
|
vendorInfo._id.$oid.substring(8, 16) +
|
||||||
rng.randomInt(0, 0xffff_ffff).toString(16).padStart(8, "0")
|
rng.randomInt(0, 0xffff).toString(16).padStart(4, "0") +
|
||||||
|
rng.randomInt(0, 0xffff).toString(16).padStart(4, "0")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (rawItem.numRandomItemPrices) {
|
if (rawItem.numRandomItemPrices) {
|
||||||
@ -374,54 +283,26 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
item.PremiumPrice = [value, value];
|
item.PremiumPrice = [value, value];
|
||||||
}
|
}
|
||||||
if (vendorInfo.RandomSeedType) {
|
if (vendorInfo.RandomSeedType) {
|
||||||
item.LocTagRandSeed = rng.randomInt(0, 0xffff_ffff);
|
item.LocTagRandSeed = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
|
||||||
if (vendorInfo.RandomSeedType == "VRST_WEAPON") {
|
if (vendorInfo.RandomSeedType == "VRST_WEAPON") {
|
||||||
const highDword = rng.randomInt(0, 0xffff_ffff);
|
const highDword = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
|
||||||
item.LocTagRandSeed = (BigInt(highDword) << 32n) | (BigInt(item.LocTagRandSeed) & 0xffffffffn);
|
item.LocTagRandSeed = (BigInt(highDword) << 32n) | (BigInt(item.LocTagRandSeed) & 0xffffffffn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info.ItemManifest.push(item);
|
processed.ItemManifest.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update vendor expiry
|
// Update vendor expiry
|
||||||
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
|
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
|
||||||
for (const offer of info.ItemManifest) {
|
for (const offer of processed.ItemManifest) {
|
||||||
const offerExpiry = parseInt(offer.Expiry.$date.$numberLong);
|
const offerExpiry = parseInt(offer.Expiry.$date.$numberLong);
|
||||||
if (soonestOfferExpiry > offerExpiry) {
|
if (soonestOfferExpiry > offerExpiry) {
|
||||||
soonestOfferExpiry = offerExpiry;
|
soonestOfferExpiry = offerExpiry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
|
processed.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
|
||||||
|
|
||||||
now += unixTimesInMs.hour;
|
|
||||||
}
|
}
|
||||||
return cacheEntry;
|
return {
|
||||||
|
VendorInfo: processed
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")!
|
|
||||||
.VendorInfo.ItemManifest;
|
|
||||||
if (
|
|
||||||
ads.length != 5 ||
|
|
||||||
ads[0].Bin != "BIN_4" ||
|
|
||||||
ads[1].Bin != "BIN_3" ||
|
|
||||||
ads[2].Bin != "BIN_2" ||
|
|
||||||
ads[3].Bin != "BIN_1" ||
|
|
||||||
ads[4].Bin != "BIN_0"
|
|
||||||
) {
|
|
||||||
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pall = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest")!
|
|
||||||
.VendorInfo.ItemManifest;
|
|
||||||
if (
|
|
||||||
pall.length != 5 ||
|
|
||||||
pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
|
|
||||||
pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" ||
|
|
||||||
pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" ||
|
|
||||||
pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" ||
|
|
||||||
pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack"
|
|
||||||
) {
|
|
||||||
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
import http from "http";
|
|
||||||
import https from "https";
|
|
||||||
import fs from "node:fs";
|
|
||||||
import { config } from "./configService";
|
|
||||||
import { logger } from "../utils/logger";
|
|
||||||
import { app } from "../app";
|
|
||||||
import { AddressInfo } from "node:net";
|
|
||||||
|
|
||||||
let httpServer: http.Server | undefined;
|
|
||||||
let httpsServer: https.Server | undefined;
|
|
||||||
|
|
||||||
const tlsOptions = {
|
|
||||||
key: fs.readFileSync("static/certs/key.pem"),
|
|
||||||
cert: fs.readFileSync("static/certs/cert.pem")
|
|
||||||
};
|
|
||||||
|
|
||||||
export const startWebServer = (): void => {
|
|
||||||
const httpPort = config.httpPort || 80;
|
|
||||||
const httpsPort = config.httpsPort || 443;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
httpServer = http.createServer(app);
|
|
||||||
httpServer.listen(httpPort, () => {
|
|
||||||
logger.info("HTTP server started on port " + httpPort);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
httpsServer = https.createServer(tlsOptions, app);
|
|
||||||
httpsServer.listen(httpsPort, () => {
|
|
||||||
logger.info("HTTPS server started on port " + httpsPort);
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getWebPorts = (): Record<"http" | "https", number | undefined> => {
|
|
||||||
return {
|
|
||||||
http: (httpServer?.address() as AddressInfo | undefined)?.port,
|
|
||||||
https: (httpsServer?.address() as AddressInfo | undefined)?.port
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const stopWebServer = async (): Promise<void> => {
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
if (httpServer) {
|
|
||||||
promises.push(
|
|
||||||
new Promise(resolve => {
|
|
||||||
httpServer!.close(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (httpsServer) {
|
|
||||||
promises.push(
|
|
||||||
new Promise(resolve => {
|
|
||||||
httpsServer!.close(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
};
|
|
File diff suppressed because it is too large
Load Diff
@ -4,11 +4,6 @@ export interface IOid {
|
|||||||
$oid: string;
|
$oid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOidWithLegacySupport {
|
|
||||||
$oid?: string;
|
|
||||||
$id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMongoDate {
|
export interface IMongoDate {
|
||||||
$date: {
|
$date: {
|
||||||
$numberLong: string;
|
$numberLong: string;
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { Types } from "mongoose";
|
|
||||||
import { IMongoDate, IOidWithLegacySupport } from "./commonTypes";
|
|
||||||
|
|
||||||
export interface IFriendInfo {
|
|
||||||
_id: IOidWithLegacySupport;
|
|
||||||
DisplayName?: string;
|
|
||||||
PlatformNames?: string[];
|
|
||||||
PlatformAccountId?: string;
|
|
||||||
Status?: number;
|
|
||||||
ActiveAvatarImageType?: string;
|
|
||||||
LastLogin?: IMongoDate;
|
|
||||||
PlayerLevel?: number;
|
|
||||||
Suffix?: number;
|
|
||||||
Note?: string;
|
|
||||||
Favorite?: boolean;
|
|
||||||
NewRequest?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFriendship {
|
|
||||||
owner: Types.ObjectId;
|
|
||||||
friend: Types.ObjectId;
|
|
||||||
Note?: string;
|
|
||||||
Favorite?: boolean;
|
|
||||||
}
|
|
@ -1,11 +1,10 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { IOid, IMongoDate, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
import { IOid, IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IPictureFrameInfo } from "./shipTypes";
|
import { IPictureFrameInfo } from "./shipTypes";
|
||||||
import { IFriendInfo } from "./friendTypes";
|
|
||||||
|
|
||||||
export interface IGuildClient {
|
export interface IGuildClient {
|
||||||
_id: IOidWithLegacySupport;
|
_id: IOid;
|
||||||
Name: string;
|
Name: string;
|
||||||
MOTD: string;
|
MOTD: string;
|
||||||
LongMOTD?: ILongMOTD;
|
LongMOTD?: ILongMOTD;
|
||||||
@ -22,7 +21,7 @@ export interface IGuildClient {
|
|||||||
CeremonyResetDate?: IMongoDate;
|
CeremonyResetDate?: IMongoDate;
|
||||||
CrossPlatformEnabled?: boolean;
|
CrossPlatformEnabled?: boolean;
|
||||||
AutoContributeFromVault?: boolean;
|
AutoContributeFromVault?: boolean;
|
||||||
AllianceId?: IOidWithLegacySupport;
|
AllianceId?: IOid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGuildDatabase {
|
export interface IGuildDatabase {
|
||||||
@ -71,6 +70,7 @@ export interface ILongMOTD {
|
|||||||
authorGuildName?: string;
|
authorGuildName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 32 seems to be reserved
|
||||||
export enum GuildPermission {
|
export enum GuildPermission {
|
||||||
Ruler = 1, // Clan: Change hierarchy. Alliance (Creator only): Kick clans.
|
Ruler = 1, // Clan: Change hierarchy. Alliance (Creator only): Kick clans.
|
||||||
Advertiser = 8192,
|
Advertiser = 8192,
|
||||||
@ -78,7 +78,6 @@ export enum GuildPermission {
|
|||||||
Regulator = 4, // Kick members
|
Regulator = 4, // Kick members
|
||||||
Promoter = 8, // Clan: Promote and demote members. Alliance (Creator only): Change clan permissions.
|
Promoter = 8, // Clan: Promote and demote members. Alliance (Creator only): Change clan permissions.
|
||||||
Architect = 16, // Create and destroy rooms
|
Architect = 16, // Create and destroy rooms
|
||||||
Host = 32, // No longer used in modern versions
|
|
||||||
Decorator = 1024, // Create and destroy decos
|
Decorator = 1024, // Create and destroy decos
|
||||||
Treasurer = 64, // Clan: Contribute from vault and edit tax rate. Alliance: Divvy vault.
|
Treasurer = 64, // Clan: Contribute from vault and edit tax rate. Alliance: Divvy vault.
|
||||||
Tech = 128, // Queue research
|
Tech = 128, // Queue research
|
||||||
@ -105,6 +104,21 @@ export interface IGuildMemberDatabase {
|
|||||||
ShipDecorationsContributed?: ITypeCount[];
|
ShipDecorationsContributed?: ITypeCount[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFriendInfo {
|
||||||
|
_id: IOid;
|
||||||
|
DisplayName?: string;
|
||||||
|
PlatformNames?: string[];
|
||||||
|
PlatformAccountId?: string;
|
||||||
|
Status?: number;
|
||||||
|
ActiveAvatarImageType?: string;
|
||||||
|
LastLogin?: IMongoDate;
|
||||||
|
PlayerLevel?: number;
|
||||||
|
Suffix?: number;
|
||||||
|
Note?: string;
|
||||||
|
Favorite?: boolean;
|
||||||
|
NewRequest?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// GuildMemberInfo
|
// GuildMemberInfo
|
||||||
export interface IGuildMemberClient extends IFriendInfo {
|
export interface IGuildMemberClient extends IFriendInfo {
|
||||||
Rank: number;
|
Rank: number;
|
||||||
@ -127,13 +141,13 @@ export interface IGuildVault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IDojoClient {
|
export interface IDojoClient {
|
||||||
_id: IOidWithLegacySupport; // ID of the guild
|
_id: IOid; // ID of the guild
|
||||||
Name: string;
|
Name: string;
|
||||||
Tier: number;
|
Tier: number;
|
||||||
TradeTax?: number;
|
TradeTax?: number;
|
||||||
FixedContributions: boolean;
|
FixedContributions: boolean;
|
||||||
DojoRevision: number;
|
DojoRevision: number;
|
||||||
AllianceId?: IOidWithLegacySupport;
|
AllianceId?: IOid;
|
||||||
Vault?: IGuildVault;
|
Vault?: IGuildVault;
|
||||||
Class?: number; // Level
|
Class?: number; // Level
|
||||||
RevisionTime: number;
|
RevisionTime: number;
|
||||||
@ -148,11 +162,11 @@ export interface IDojoClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IDojoComponentClient {
|
export interface IDojoComponentClient {
|
||||||
id: IOidWithLegacySupport;
|
id: IOid;
|
||||||
SortId?: IOidWithLegacySupport;
|
SortId?: IOid;
|
||||||
pf: string; // Prefab (.level)
|
pf: string; // Prefab (.level)
|
||||||
ppf: string;
|
ppf: string;
|
||||||
pi?: IOidWithLegacySupport; // Parent ID. N/A to root.
|
pi?: IOid; // Parent ID. N/A to root.
|
||||||
op?: string; // Name of the door within this room that leads to its parent. N/A to root.
|
op?: string; // Name of the door within this room that leads to its parent. N/A to root.
|
||||||
pp?: string; // Name of the door within the parent that leads to this room. N/A to root.
|
pp?: string; // Name of the door within the parent that leads to this room. N/A to root.
|
||||||
Name?: string;
|
Name?: string;
|
||||||
@ -166,7 +180,7 @@ export interface IDojoComponentClient {
|
|||||||
DestructionTimeRemaining?: number; // old versions
|
DestructionTimeRemaining?: number; // old versions
|
||||||
Decos?: IDojoDecoClient[];
|
Decos?: IDojoDecoClient[];
|
||||||
DecoCapacity?: number;
|
DecoCapacity?: number;
|
||||||
PaintBot?: IOidWithLegacySupport;
|
PaintBot?: IOid;
|
||||||
PendingColors?: number[];
|
PendingColors?: number[];
|
||||||
Colors?: number[];
|
Colors?: number[];
|
||||||
PendingLights?: number[];
|
PendingLights?: number[];
|
||||||
@ -191,7 +205,7 @@ export interface IDojoComponentDatabase
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IDojoDecoClient {
|
export interface IDojoDecoClient {
|
||||||
id: IOidWithLegacySupport;
|
id: IOid;
|
||||||
Type: string;
|
Type: string;
|
||||||
Pos: number[];
|
Pos: number[];
|
||||||
Rot: number[];
|
Rot: number[];
|
||||||
@ -285,7 +299,7 @@ export interface IGuildAdDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IAllianceClient {
|
export interface IAllianceClient {
|
||||||
_id: IOidWithLegacySupport;
|
_id: IOid;
|
||||||
Name: string;
|
Name: string;
|
||||||
MOTD?: ILongMOTD;
|
MOTD?: ILongMOTD;
|
||||||
LongMOTD?: ILongMOTD;
|
LongMOTD?: ILongMOTD;
|
||||||
@ -306,7 +320,7 @@ export interface IAllianceDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IAllianceMemberClient {
|
export interface IAllianceMemberClient {
|
||||||
_id: IOidWithLegacySupport;
|
_id: IOid;
|
||||||
Name: string;
|
Name: string;
|
||||||
Tier: number;
|
Tier: number;
|
||||||
Pending: boolean;
|
Pending: boolean;
|
||||||
@ -314,7 +328,7 @@ export interface IAllianceMemberClient {
|
|||||||
Permissions: number;
|
Permissions: number;
|
||||||
MemberCount: number;
|
MemberCount: number;
|
||||||
ClanLeader?: string;
|
ClanLeader?: string;
|
||||||
ClanLeaderId?: IOidWithLegacySupport;
|
ClanLeaderId?: IOid;
|
||||||
OriginalPlatform?: number;
|
OriginalPlatform?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
ICrewShipCustomization,
|
ICrewShipCustomization,
|
||||||
@ -92,7 +92,7 @@ export interface IEquipmentClient
|
|||||||
IEquipmentDatabase,
|
IEquipmentDatabase,
|
||||||
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "CrewMembers" | "Details"
|
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "CrewMembers" | "Details"
|
||||||
> {
|
> {
|
||||||
ItemId: IOidWithLegacySupport;
|
ItemId: IOid;
|
||||||
InfestationDate?: IMongoDate;
|
InfestationDate?: IMongoDate;
|
||||||
Expiry?: IMongoDate;
|
Expiry?: IMongoDate;
|
||||||
UpgradesExpiry?: IMongoDate;
|
UpgradesExpiry?: IMongoDate;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { IOid, IMongoDate, IOidWithLegacySupport } from "../commonTypes";
|
import { IOid, IMongoDate } from "../commonTypes";
|
||||||
import {
|
import {
|
||||||
IColor,
|
IColor,
|
||||||
IItemConfig,
|
IItemConfig,
|
||||||
@ -11,8 +11,6 @@ import {
|
|||||||
IOperatorConfigDatabase
|
IOperatorConfigDatabase
|
||||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { IFingerprintStat, RivenFingerprint } from "@/src/helpers/rivenHelper";
|
import { IFingerprintStat, RivenFingerprint } from "@/src/helpers/rivenHelper";
|
||||||
import { IOrbiter } from "../personalRoomsTypes";
|
|
||||||
import { ICountedStoreItem } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
export type InventoryDatabaseEquipment = {
|
export type InventoryDatabaseEquipment = {
|
||||||
[_ in TEquipmentKey]: IEquipmentDatabase[];
|
[_ in TEquipmentKey]: IEquipmentDatabase[];
|
||||||
@ -55,7 +53,6 @@ export interface IInventoryDatabase
|
|||||||
| "CrewMembers"
|
| "CrewMembers"
|
||||||
| "QualifyingInvasions"
|
| "QualifyingInvasions"
|
||||||
| "LastInventorySync"
|
| "LastInventorySync"
|
||||||
| "EndlessXP"
|
|
||||||
| TEquipmentKey
|
| TEquipmentKey
|
||||||
>,
|
>,
|
||||||
InventoryDatabaseEquipment {
|
InventoryDatabaseEquipment {
|
||||||
@ -94,7 +91,6 @@ export interface IInventoryDatabase
|
|||||||
CrewMembers: ICrewMemberDatabase[];
|
CrewMembers: ICrewMemberDatabase[];
|
||||||
QualifyingInvasions: IInvasionProgressDatabase[];
|
QualifyingInvasions: IInvasionProgressDatabase[];
|
||||||
LastInventorySync?: Types.ObjectId;
|
LastInventorySync?: Types.ObjectId;
|
||||||
EndlessXP?: IEndlessXpProgressDatabase[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuestKeyDatabase {
|
export interface IQuestKeyDatabase {
|
||||||
@ -296,7 +292,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
SortieRewardAttenuation?: ISortieRewardAttenuation[];
|
SortieRewardAttenuation?: ISortieRewardAttenuation[];
|
||||||
Drones: IDroneClient[];
|
Drones: IDroneClient[];
|
||||||
StepSequencers: IStepSequencer[];
|
StepSequencers: IStepSequencer[];
|
||||||
ActiveAvatarImageType?: string;
|
ActiveAvatarImageType: string;
|
||||||
ShipDecorations: ITypeCount[];
|
ShipDecorations: ITypeCount[];
|
||||||
DiscoveredMarkers: IDiscoveredMarker[];
|
DiscoveredMarkers: IDiscoveredMarker[];
|
||||||
//CompletedJobs: ICompletedJob[];
|
//CompletedJobs: ICompletedJob[];
|
||||||
@ -359,7 +355,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
PendingCoupon?: IPendingCouponClient;
|
PendingCoupon?: IPendingCouponClient;
|
||||||
Harvestable: boolean;
|
Harvestable: boolean;
|
||||||
DeathSquadable: boolean;
|
DeathSquadable: boolean;
|
||||||
EndlessXP?: IEndlessXpProgressClient[];
|
EndlessXP?: IEndlessXpProgress[];
|
||||||
DialogueHistory?: IDialogueHistoryClient;
|
DialogueHistory?: IDialogueHistoryClient;
|
||||||
CalendarProgress?: ICalendarProgress;
|
CalendarProgress?: ICalendarProgress;
|
||||||
SongChallenges?: ISongChallenge[];
|
SongChallenges?: ISongChallenge[];
|
||||||
@ -374,10 +370,9 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
EchoesHexConquestCacheScoreMission?: number;
|
EchoesHexConquestCacheScoreMission?: number;
|
||||||
EchoesHexConquestActiveFrameVariants?: string[];
|
EchoesHexConquestActiveFrameVariants?: string[];
|
||||||
EchoesHexConquestActiveStickers?: string[];
|
EchoesHexConquestActiveStickers?: string[];
|
||||||
BrandedSuits?: IOidWithLegacySupport[];
|
BrandedSuits?: IOid[];
|
||||||
LockedWeaponGroup?: ILockedWeaponGroupClient;
|
LockedWeaponGroup?: ILockedWeaponGroupClient;
|
||||||
HubNpcCustomizations?: IHubNpcCustomization[];
|
HubNpcCustomizations?: IHubNpcCustomization[];
|
||||||
Ship?: IOrbiter; // U22 and below, response only
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAffiliation {
|
export interface IAffiliation {
|
||||||
@ -535,16 +530,6 @@ export interface IUpgradeDatabase extends Omit<IUpgradeClient, "ItemId"> {
|
|||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUpgradeFromClient {
|
|
||||||
ItemType: string;
|
|
||||||
ItemId: IOidWithLegacySupport;
|
|
||||||
FromSKU?: boolean;
|
|
||||||
UpgradeFingerprint: string;
|
|
||||||
PendingRerollFingerprint: string;
|
|
||||||
ItemCount: number;
|
|
||||||
LastAdded: IOidWithLegacySupport;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICrewShipMembersClient {
|
export interface ICrewShipMembersClient {
|
||||||
SLOT_A?: ICrewShipMemberClient;
|
SLOT_A?: ICrewShipMemberClient;
|
||||||
SLOT_B?: ICrewShipMemberClient;
|
SLOT_B?: ICrewShipMemberClient;
|
||||||
@ -573,7 +558,7 @@ export interface ICrewShipCustomization {
|
|||||||
|
|
||||||
export interface IShipExterior {
|
export interface IShipExterior {
|
||||||
SkinFlavourItem?: string;
|
SkinFlavourItem?: string;
|
||||||
Colors?: IColor;
|
Colors: IColor;
|
||||||
ShipAttachments?: IShipAttachments;
|
ShipAttachments?: IShipAttachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -765,8 +750,7 @@ export interface IKubrowPetDetailsClient extends Omit<IKubrowPetDetailsDatabase,
|
|||||||
|
|
||||||
export enum Status {
|
export enum Status {
|
||||||
StatusAvailable = "STATUS_AVAILABLE",
|
StatusAvailable = "STATUS_AVAILABLE",
|
||||||
StatusStasis = "STATUS_STASIS",
|
StatusStasis = "STATUS_STASIS"
|
||||||
StatusIncubating = "STATUS_INCUBATING"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILastSortieRewardClient {
|
export interface ILastSortieRewardClient {
|
||||||
@ -864,8 +848,6 @@ export interface IMission extends IMissionDatabase {
|
|||||||
RewardsCooldownTime?: IMongoDate;
|
RewardsCooldownTime?: IMongoDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TNemesisFaction = "FC_GRINEER" | "FC_CORPUS" | "FC_INFESTATION";
|
|
||||||
|
|
||||||
export interface INemesisBaseClient {
|
export interface INemesisBaseClient {
|
||||||
fp: bigint | number;
|
fp: bigint | number;
|
||||||
manifest: string;
|
manifest: string;
|
||||||
@ -875,7 +857,7 @@ export interface INemesisBaseClient {
|
|||||||
WeaponIdx: number;
|
WeaponIdx: number;
|
||||||
AgentIdx: number;
|
AgentIdx: number;
|
||||||
BirthNode: string;
|
BirthNode: string;
|
||||||
Faction: TNemesisFaction;
|
Faction: string;
|
||||||
Rank: number;
|
Rank: number;
|
||||||
k: boolean;
|
k: boolean;
|
||||||
Traded: boolean;
|
Traded: boolean;
|
||||||
@ -930,14 +912,10 @@ export interface IPendingRecipeDatabase {
|
|||||||
Pistols?: IEquipmentDatabase[];
|
Pistols?: IEquipmentDatabase[];
|
||||||
Melee?: IEquipmentDatabase[];
|
Melee?: IEquipmentDatabase[];
|
||||||
SuitToUnbrand?: Types.ObjectId;
|
SuitToUnbrand?: Types.ObjectId;
|
||||||
KubrowPet?: Types.ObjectId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPendingRecipeClient
|
export interface IPendingRecipeClient
|
||||||
extends Omit<
|
extends Omit<IPendingRecipeDatabase, "CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand"> {
|
||||||
IPendingRecipeDatabase,
|
|
||||||
"CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand" | "KubrowPet"
|
|
||||||
> {
|
|
||||||
CompletionDate: IMongoDate;
|
CompletionDate: IMongoDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1070,7 +1048,7 @@ export interface IQuestStage {
|
|||||||
export interface IRawUpgrade {
|
export interface IRawUpgrade {
|
||||||
ItemType: string;
|
ItemType: string;
|
||||||
ItemCount: number;
|
ItemCount: number;
|
||||||
LastAdded?: IOidWithLegacySupport;
|
LastAdded?: IOid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISeasonChallenge {
|
export interface ISeasonChallenge {
|
||||||
@ -1163,24 +1141,9 @@ export interface IEvolutionProgress {
|
|||||||
|
|
||||||
export type TEndlessXpCategory = "EXC_NORMAL" | "EXC_HARD";
|
export type TEndlessXpCategory = "EXC_NORMAL" | "EXC_HARD";
|
||||||
|
|
||||||
export interface IEndlessXpProgressDatabase {
|
export interface IEndlessXpProgress {
|
||||||
Category: TEndlessXpCategory;
|
Category: TEndlessXpCategory;
|
||||||
Earn: number;
|
|
||||||
Claim: number;
|
|
||||||
BonusAvailable?: Date;
|
|
||||||
Expiry?: Date;
|
|
||||||
Choices: string[];
|
Choices: string[];
|
||||||
PendingRewards: IEndlessXpReward[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IEndlessXpProgressClient extends Omit<IEndlessXpProgressDatabase, "BonusAvailable" | "Expiry"> {
|
|
||||||
BonusAvailable?: IMongoDate;
|
|
||||||
Expiry?: IMongoDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IEndlessXpReward {
|
|
||||||
RequiredTotalXp: number;
|
|
||||||
Rewards: ICountedStoreItem[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDialogueHistoryClient {
|
export interface IDialogueHistoryClient {
|
||||||
|
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