merge upstream
This commit is contained in:
commit
dd0d60c22f
@ -11,9 +11,10 @@ import {
|
|||||||
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ExportFlavour, ExportGear } from "warframe-public-export-plus";
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
|
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
|
||||||
export const inboxController: RequestHandler = async (req, res) => {
|
export const inboxController: RequestHandler = async (req, res) => {
|
||||||
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
|
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
|
||||||
@ -28,10 +29,10 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteMessageRead(deleteId as string);
|
await deleteMessageRead(parseOid(deleteId as string));
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
} else if (messageId) {
|
} else if (messageId) {
|
||||||
const message = await getMessage(messageId as string);
|
const message = await getMessage(parseOid(messageId as string));
|
||||||
message.r = true;
|
message.r = true;
|
||||||
await message.save();
|
await message.save();
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
inventory,
|
inventory,
|
||||||
attachmentItems.map(attItem => ({
|
attachmentItems.map(attItem => ({
|
||||||
ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
|
ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
|
||||||
ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1
|
ItemCount: 1
|
||||||
})),
|
})),
|
||||||
inventoryChanges
|
inventoryChanges
|
||||||
);
|
);
|
||||||
@ -100,7 +101,7 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
await createNewEventMessages(req);
|
await createNewEventMessages(req);
|
||||||
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
|
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
|
||||||
|
|
||||||
const latestClientMessage = messages.find(m => m._id.toString() === latestClientMessageId);
|
const latestClientMessage = messages.find(m => m._id.toString() === parseOid(latestClientMessageId as string));
|
||||||
|
|
||||||
if (!latestClientMessage) {
|
if (!latestClientMessage) {
|
||||||
logger.debug(`this should only happen after DeleteAllRead `);
|
logger.debug(`this should only happen after DeleteAllRead `);
|
||||||
@ -123,3 +124,11 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
res.json({ Inbox: inbox });
|
res.json({ Inbox: inbox });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
|
||||||
|
const parseOid = (oid: string): string => {
|
||||||
|
if (oid[0] == "{") {
|
||||||
|
return (JSON.parse(oid) as IOid).$oid;
|
||||||
|
}
|
||||||
|
return oid;
|
||||||
|
};
|
||||||
|
@ -106,9 +106,16 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
|
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
|
||||||
if (lastSyncDuviriMood != currentDuviriMood) {
|
if (lastSyncDuviriMood != currentDuviriMood) {
|
||||||
logger.debug(`refreshing duviri seed`);
|
logger.debug(`refreshing duviri seed`);
|
||||||
|
if (!inventory.DuviriInfo) {
|
||||||
|
inventory.DuviriInfo = {
|
||||||
|
Seed: generateRewardSeed(),
|
||||||
|
NumCompletions: 0
|
||||||
|
};
|
||||||
|
} else {
|
||||||
inventory.DuviriInfo.Seed = generateRewardSeed();
|
inventory.DuviriInfo.Seed = generateRewardSeed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
inventory.LastInventorySync = new Types.ObjectId();
|
inventory.LastInventorySync = new Types.ObjectId();
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
|
@ -7,6 +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/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
|
||||||
@ -94,12 +95,11 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
|
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
|
||||||
return {
|
const resp: ILoginResponse = {
|
||||||
id: account.id,
|
id: account.id,
|
||||||
DisplayName: account.DisplayName,
|
DisplayName: account.DisplayName,
|
||||||
CountryCode: account.CountryCode,
|
CountryCode: account.CountryCode,
|
||||||
ClientType: account.ClientType,
|
ClientType: account.ClientType,
|
||||||
CrossPlatformAllowed: account.CrossPlatformAllowed,
|
|
||||||
ForceLogoutVersion: account.ForceLogoutVersion,
|
ForceLogoutVersion: account.ForceLogoutVersion,
|
||||||
AmazonAuthToken: account.AmazonAuthToken,
|
AmazonAuthToken: account.AmazonAuthToken,
|
||||||
AmazonRefreshToken: account.AmazonRefreshToken,
|
AmazonRefreshToken: account.AmazonRefreshToken,
|
||||||
@ -108,11 +108,17 @@ const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, b
|
|||||||
Nonce: account.Nonce,
|
Nonce: account.Nonce,
|
||||||
Groups: [],
|
Groups: [],
|
||||||
IRC: config.myIrcAddresses ?? [myAddress],
|
IRC: config.myIrcAddresses ?? [myAddress],
|
||||||
platformCDNs: [`https://${myAddress}/`],
|
|
||||||
HUB: `https://${myAddress}/api/`,
|
|
||||||
NRS: config.NRS,
|
NRS: config.NRS,
|
||||||
DTLS: 99,
|
DTLS: 99,
|
||||||
BuildLabel: buildLabel,
|
BuildLabel: buildLabel
|
||||||
MatchmakingBuildId: buildConfig.matchmakingBuildId
|
|
||||||
};
|
};
|
||||||
|
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
|
||||||
|
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
|
||||||
|
resp.HUB = `https://${myAddress}/api/`;
|
||||||
|
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
|
||||||
|
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
|
||||||
|
resp.platformCDNs = [`https://${myAddress}/`];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { addGearExpByCategory, 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 { 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";
|
||||||
@ -20,7 +20,7 @@ export const addXpController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addGearExpByCategory(inventory, gear, category as TEquipmentKey);
|
applyClientEquipmentUpdates(inventory, gear, category as TEquipmentKey);
|
||||||
}
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.end();
|
res.end();
|
||||||
|
@ -10,6 +10,10 @@ export const toMongoDate = (date: Date): IMongoDate => {
|
|||||||
return { $date: { $numberLong: date.getTime().toString() } };
|
return { $date: { $numberLong: date.getTime().toString() } };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fromMongoDate = (date: IMongoDate): Date => {
|
||||||
|
return new Date(parseInt(date.$date.$numberLong));
|
||||||
|
};
|
||||||
|
|
||||||
export const kubrowWeights: Record<TRarity, number> = {
|
export const kubrowWeights: Record<TRarity, number> = {
|
||||||
COMMON: 6,
|
COMMON: 6,
|
||||||
UNCOMMON: 4,
|
UNCOMMON: 4,
|
||||||
|
@ -391,8 +391,8 @@ MailboxSchema.set("toJSON", {
|
|||||||
|
|
||||||
const DuviriInfoSchema = new Schema<IDuviriInfo>(
|
const DuviriInfoSchema = new Schema<IDuviriInfo>(
|
||||||
{
|
{
|
||||||
Seed: BigInt,
|
Seed: { type: BigInt, required: true },
|
||||||
NumCompletions: { type: Number, default: 0 }
|
NumCompletions: { type: Number, required: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: false,
|
_id: false,
|
||||||
@ -1688,9 +1688,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
//Like BossAladV,BossCaptainVor come for you on missions % chance
|
//Like BossAladV,BossCaptainVor come for you on missions % chance
|
||||||
DeathMarks: { type: [String], default: [] },
|
DeathMarks: { type: [String], default: [] },
|
||||||
//Zanuka
|
//Zanuka
|
||||||
Harvestable: Boolean,
|
Harvestable: { type: Boolean, default: true },
|
||||||
//Grustag three
|
//Grustag three
|
||||||
DeathSquadable: Boolean,
|
DeathSquadable: { type: Boolean, default: true },
|
||||||
|
|
||||||
EndlessXP: { type: [endlessXpProgressSchema], default: undefined },
|
EndlessXP: { type: [endlessXpProgressSchema], default: undefined },
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ import {
|
|||||||
import { createShip } from "./shipService";
|
import { createShip } from "./shipService";
|
||||||
import {
|
import {
|
||||||
catbrowDetails,
|
catbrowDetails,
|
||||||
|
fromMongoDate,
|
||||||
kubrowDetails,
|
kubrowDetails,
|
||||||
kubrowFurPatternsWeights,
|
kubrowFurPatternsWeights,
|
||||||
kubrowWeights,
|
kubrowWeights,
|
||||||
@ -486,6 +487,10 @@ export const addItem = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (typeName in ExportGear) {
|
if (typeName in ExportGear) {
|
||||||
|
// Multipling by purchase quantity for gear because:
|
||||||
|
// - The Saya's Vigil scanner message has it as a non-counted attachment.
|
||||||
|
// - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity.
|
||||||
|
quantity *= ExportGear[typeName].purchaseQuantity ?? 1;
|
||||||
const consumablesChanges = [
|
const consumablesChanges = [
|
||||||
{
|
{
|
||||||
ItemType: typeName,
|
ItemType: typeName,
|
||||||
@ -1471,21 +1476,20 @@ export const addEmailItem = async (
|
|||||||
return inventoryChanges;
|
return inventoryChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: wrong id is not erroring
|
export const applyClientEquipmentUpdates = (
|
||||||
export const addGearExpByCategory = (
|
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
gearArray: IEquipmentClient[],
|
gearArray: IEquipmentClient[],
|
||||||
categoryName: TEquipmentKey
|
categoryName: TEquipmentKey
|
||||||
): void => {
|
): void => {
|
||||||
const category = inventory[categoryName];
|
const category = inventory[categoryName];
|
||||||
|
|
||||||
gearArray.forEach(({ ItemId, XP }) => {
|
gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
|
||||||
if (!XP) {
|
const item = category.id(ItemId.$oid);
|
||||||
return;
|
if (!item) {
|
||||||
|
throw new Error(`No item with id ${ItemId.$oid} in ${categoryName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = category.id(ItemId.$oid);
|
if (XP) {
|
||||||
if (item) {
|
|
||||||
item.XP ??= 0;
|
item.XP ??= 0;
|
||||||
item.XP += XP;
|
item.XP += XP;
|
||||||
|
|
||||||
@ -1500,6 +1504,10 @@ export const addGearExpByCategory = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (InfestationDate) {
|
||||||
|
item.InfestationDate = fromMongoDate(InfestationDate);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ import {
|
|||||||
addFocusXpIncreases,
|
addFocusXpIncreases,
|
||||||
addFusionPoints,
|
addFusionPoints,
|
||||||
addFusionTreasures,
|
addFusionTreasures,
|
||||||
addGearExpByCategory,
|
|
||||||
addItem,
|
addItem,
|
||||||
addLevelKeys,
|
addLevelKeys,
|
||||||
addLoreFragmentScans,
|
addLoreFragmentScans,
|
||||||
@ -32,6 +31,7 @@ import {
|
|||||||
addShipDecorations,
|
addShipDecorations,
|
||||||
addSkin,
|
addSkin,
|
||||||
addStanding,
|
addStanding,
|
||||||
|
applyClientEquipmentUpdates,
|
||||||
combineInventoryChanges,
|
combineInventoryChanges,
|
||||||
generateRewardSeed,
|
generateRewardSeed,
|
||||||
getCalendarProgress,
|
getCalendarProgress,
|
||||||
@ -143,38 +143,6 @@ export const addMissionInventoryUpdates = async (
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Somewhat heuristically detect G3 capture:
|
|
||||||
// - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1365
|
|
||||||
// - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1694
|
|
||||||
// - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1724
|
|
||||||
if (
|
|
||||||
inventoryUpdates.MissionFailed &&
|
|
||||||
inventoryUpdates.MissionStatus == "GS_FAILURE" &&
|
|
||||||
inventoryUpdates.ObjectiveReached &&
|
|
||||||
!inventoryUpdates.LockedWeaponGroup &&
|
|
||||||
!inventory.LockedWeaponGroup &&
|
|
||||||
!inventoryUpdates.LevelKeyName
|
|
||||||
) {
|
|
||||||
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
|
|
||||||
const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!;
|
|
||||||
const SuitId = new Types.ObjectId(config.s!.ItemId.$oid);
|
|
||||||
|
|
||||||
inventory.BrandedSuits ??= [];
|
|
||||||
if (!inventory.BrandedSuits.find(x => x.equals(SuitId))) {
|
|
||||||
inventory.BrandedSuits.push(SuitId);
|
|
||||||
|
|
||||||
await createMessage(inventory.accountOwnerId, [
|
|
||||||
{
|
|
||||||
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
|
|
||||||
msg: "/Lotus/Language/G1Quests/BrandedMessage",
|
|
||||||
sub: "/Lotus/Language/G1Quests/BrandedTitle",
|
|
||||||
att: ["/Lotus/Types/Recipes/Components/BrandRemovalBlueprint"],
|
|
||||||
highPriority: true // TOVERIFY: I cannot find any content of this within the last 10 years so I can only assume that highPriority is set (it certainly would make sense), but I just don't know for sure that it is so on live.
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (inventoryUpdates.RewardInfo) {
|
if (inventoryUpdates.RewardInfo) {
|
||||||
if (inventoryUpdates.RewardInfo.periodicMissionTag) {
|
if (inventoryUpdates.RewardInfo.periodicMissionTag) {
|
||||||
@ -537,6 +505,23 @@ export const addMissionInventoryUpdates = async (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "BrandedSuits": {
|
||||||
|
inventory.BrandedSuits ??= [];
|
||||||
|
if (!inventory.BrandedSuits.find(x => x.equals(value.$oid))) {
|
||||||
|
inventory.BrandedSuits.push(new Types.ObjectId(value.$oid));
|
||||||
|
|
||||||
|
await createMessage(inventory.accountOwnerId, [
|
||||||
|
{
|
||||||
|
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
|
||||||
|
msg: "/Lotus/Language/G1Quests/BrandedMessage",
|
||||||
|
sub: "/Lotus/Language/G1Quests/BrandedTitle",
|
||||||
|
att: ["/Lotus/Types/Recipes/Components/BrandRemovalBlueprint"],
|
||||||
|
highPriority: true // TOVERIFY: I cannot find any content of this within the last 10 years so I can only assume that highPriority is set (it certainly would make sense), but I just don't know for sure that it is so on live.
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "LockedWeaponGroup": {
|
case "LockedWeaponGroup": {
|
||||||
inventory.LockedWeaponGroup = {
|
inventory.LockedWeaponGroup = {
|
||||||
s: new Types.ObjectId(value.s.$oid),
|
s: new Types.ObjectId(value.s.$oid),
|
||||||
@ -545,12 +530,17 @@ export const addMissionInventoryUpdates = async (
|
|||||||
m: value.m ? new Types.ObjectId(value.m.$oid) : undefined,
|
m: value.m ? new Types.ObjectId(value.m.$oid) : undefined,
|
||||||
sn: value.sn ? new Types.ObjectId(value.sn.$oid) : undefined
|
sn: value.sn ? new Types.ObjectId(value.sn.$oid) : undefined
|
||||||
};
|
};
|
||||||
|
inventory.Harvestable = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "UnlockWeapons": {
|
case "UnlockWeapons": {
|
||||||
inventory.LockedWeaponGroup = undefined;
|
inventory.LockedWeaponGroup = undefined;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "IncHarvester": {
|
||||||
|
inventory.Harvestable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "CurrentLoadOutIds": {
|
case "CurrentLoadOutIds": {
|
||||||
if (value.LoadOuts) {
|
if (value.LoadOuts) {
|
||||||
const loadout = await Loadout.findOne({ loadoutOwnerId: inventory.accountOwnerId });
|
const loadout = await Loadout.findOne({ loadoutOwnerId: inventory.accountOwnerId });
|
||||||
@ -611,7 +601,7 @@ export const addMissionInventoryUpdates = async (
|
|||||||
case "duviriCaveOffers": {
|
case "duviriCaveOffers": {
|
||||||
// Duviri cave offers (generated with the duviri seed) change after completing one of its game modes (not when aborting).
|
// Duviri cave offers (generated with the duviri seed) change after completing one of its game modes (not when aborting).
|
||||||
if (inventoryUpdates.MissionStatus != "GS_QUIT") {
|
if (inventoryUpdates.MissionStatus != "GS_QUIT") {
|
||||||
inventory.DuviriInfo.Seed = generateRewardSeed();
|
inventory.DuviriInfo!.Seed = generateRewardSeed();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -670,9 +660,8 @@ export const addMissionInventoryUpdates = async (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Equipment XP updates
|
|
||||||
if (equipmentKeys.includes(key as TEquipmentKey)) {
|
if (equipmentKeys.includes(key as TEquipmentKey)) {
|
||||||
addGearExpByCategory(inventory, value as IEquipmentClient[], key as TEquipmentKey);
|
applyClientEquipmentUpdates(inventory, value as IEquipmentClient[], key as TEquipmentKey);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// if (
|
// if (
|
||||||
|
@ -6,7 +6,7 @@ export interface IRngResult {
|
|||||||
probability: number;
|
probability: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getRandomElement = <T>(arr: T[]): T | undefined => {
|
export const getRandomElement = <T>(arr: readonly T[]): T | undefined => {
|
||||||
return arr[Math.floor(Math.random() * arr.length)];
|
return arr[Math.floor(Math.random() * arr.length)];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ export class CRng {
|
|||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
|
|
||||||
randomElement<T>(arr: T[]): T | undefined {
|
randomElement<T>(arr: readonly T[]): T | undefined {
|
||||||
return arr[Math.floor(this.random() * arr.length)];
|
return arr[Math.floor(this.random() * arr.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ export class SRng {
|
|||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
|
|
||||||
randomElement<T>(arr: T[]): T | undefined {
|
randomElement<T>(arr: readonly T[]): T | undefined {
|
||||||
return arr[this.randomInt(0, arr.length - 1)];
|
return arr[this.randomInt(0, arr.length - 1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,9 +33,11 @@ const sortieBosses = [
|
|||||||
"SORTIE_BOSS_LEPHANTIS",
|
"SORTIE_BOSS_LEPHANTIS",
|
||||||
"SORTIE_BOSS_INFALAD",
|
"SORTIE_BOSS_INFALAD",
|
||||||
"SORTIE_BOSS_CORRUPTED_VOR"
|
"SORTIE_BOSS_CORRUPTED_VOR"
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
const sortieBossToFaction: Record<string, string> = {
|
type TSortieBoss = (typeof sortieBosses)[number];
|
||||||
|
|
||||||
|
const sortieBossToFaction: Record<TSortieBoss, string> = {
|
||||||
SORTIE_BOSS_HYENA: "FC_CORPUS",
|
SORTIE_BOSS_HYENA: "FC_CORPUS",
|
||||||
SORTIE_BOSS_KELA: "FC_GRINEER",
|
SORTIE_BOSS_KELA: "FC_GRINEER",
|
||||||
SORTIE_BOSS_VOR: "FC_GRINEER",
|
SORTIE_BOSS_VOR: "FC_GRINEER",
|
||||||
@ -74,21 +76,22 @@ const sortieFactionToSpecialMissionTileset: Record<string, string> = {
|
|||||||
FC_INFESTATION: "CorpusShipTileset"
|
FC_INFESTATION: "CorpusShipTileset"
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortieBossNode: Record<string, string> = {
|
const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">, string> = {
|
||||||
SORTIE_BOSS_HYENA: "SolNode127",
|
|
||||||
SORTIE_BOSS_KELA: "SolNode193",
|
|
||||||
SORTIE_BOSS_VOR: "SolNode108",
|
|
||||||
SORTIE_BOSS_RUK: "SolNode32",
|
|
||||||
SORTIE_BOSS_HEK: "SolNode24",
|
|
||||||
SORTIE_BOSS_KRIL: "SolNode99",
|
|
||||||
SORTIE_BOSS_TYL: "SolNode105",
|
|
||||||
SORTIE_BOSS_JACKAL: "SolNode104",
|
|
||||||
SORTIE_BOSS_ALAD: "SolNode53",
|
SORTIE_BOSS_ALAD: "SolNode53",
|
||||||
SORTIE_BOSS_AMBULAS: "SolNode51",
|
SORTIE_BOSS_AMBULAS: "SolNode51",
|
||||||
SORTIE_BOSS_NEF: "SettlementNode20",
|
SORTIE_BOSS_HEK: "SolNode24",
|
||||||
SORTIE_BOSS_RAPTOR: "SolNode210",
|
SORTIE_BOSS_HYENA: "SolNode127",
|
||||||
|
SORTIE_BOSS_INFALAD: "SolNode166",
|
||||||
|
SORTIE_BOSS_JACKAL: "SolNode104",
|
||||||
|
SORTIE_BOSS_KELA: "SolNode193",
|
||||||
|
SORTIE_BOSS_KRIL: "SolNode99",
|
||||||
SORTIE_BOSS_LEPHANTIS: "SolNode712",
|
SORTIE_BOSS_LEPHANTIS: "SolNode712",
|
||||||
SORTIE_BOSS_INFALAD: "SolNode705"
|
SORTIE_BOSS_NEF: "SettlementNode20",
|
||||||
|
SORTIE_BOSS_PHORID: "SolNode171",
|
||||||
|
SORTIE_BOSS_RAPTOR: "SolNode210",
|
||||||
|
SORTIE_BOSS_RUK: "SolNode32",
|
||||||
|
SORTIE_BOSS_TYL: "SolNode105",
|
||||||
|
SORTIE_BOSS_VOR: "SolNode108"
|
||||||
};
|
};
|
||||||
|
|
||||||
const eidolonJobs = [
|
const eidolonJobs = [
|
||||||
@ -270,6 +273,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
|
|||||||
key in sortieTilesets
|
key in sortieTilesets
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
|
value.missionIndex != 0 && // Assassination will be decided independently
|
||||||
value.missionIndex != 5 && // Sorties do not have capture missions
|
value.missionIndex != 5 && // Sorties do not have capture missions
|
||||||
!availableMissionIndexes.includes(value.missionIndex)
|
!availableMissionIndexes.includes(value.missionIndex)
|
||||||
) {
|
) {
|
||||||
@ -310,20 +314,10 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
|
|||||||
sortieFactionToSpecialMissionTileset[sortieBossToFaction[boss]]
|
sortieFactionToSpecialMissionTileset[sortieBossToFaction[boss]]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (i == 2 && rng.randomInt(0, 2) == 2) {
|
if (i == 2 && boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2) {
|
||||||
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
|
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
|
||||||
const modifierType = rng.randomElement(filteredModifiers)!;
|
const modifierType = rng.randomElement(filteredModifiers)!;
|
||||||
|
|
||||||
if (boss == "SORTIE_BOSS_PHORID") {
|
|
||||||
selectedNodes.push({
|
|
||||||
missionType: "MT_ASSASSINATION",
|
|
||||||
modifierType,
|
|
||||||
node,
|
|
||||||
tileset: sortieTilesets[node as keyof typeof sortieTilesets]
|
|
||||||
});
|
|
||||||
nodes.splice(randomIndex, 1);
|
|
||||||
continue;
|
|
||||||
} else if (sortieBossNode[boss]) {
|
|
||||||
selectedNodes.push({
|
selectedNodes.push({
|
||||||
missionType: "MT_ASSASSINATION",
|
missionType: "MT_ASSASSINATION",
|
||||||
modifierType,
|
modifierType,
|
||||||
@ -332,7 +326,6 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
|
|||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const missionType = eMissionType[missionIndex].tag;
|
const missionType = eMissionType[missionIndex].tag;
|
||||||
|
|
||||||
@ -724,6 +717,11 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
SyndicateMissions: [...staticWorldState.SyndicateMissions]
|
SyndicateMissions: [...staticWorldState.SyndicateMissions]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Omit void fissures for versions prior to Whispers in the Walls to avoid errors with the unknown deimos nodes having void fissures.
|
||||||
|
if (buildLabel && version_compare(buildLabel, "2023.11.06.13.39") <= 0) {
|
||||||
|
worldState.ActiveMissions = [];
|
||||||
|
}
|
||||||
|
|
||||||
if (config.worldState?.starDays) {
|
if (config.worldState?.starDays) {
|
||||||
worldState.Goals.push({
|
worldState.Goals.push({
|
||||||
_id: { $oid: "67a4dcce2a198564d62e1647" },
|
_id: { $oid: "67a4dcce2a198564d62e1647" },
|
||||||
@ -1227,3 +1225,20 @@ export const isArchwingMission = (node: IRegion): boolean => {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
@ -202,7 +202,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
OperatorLoadOuts: IOperatorConfigClient[];
|
OperatorLoadOuts: IOperatorConfigClient[];
|
||||||
KahlLoadOuts: IOperatorConfigClient[];
|
KahlLoadOuts: IOperatorConfigClient[];
|
||||||
|
|
||||||
DuviriInfo: IDuviriInfo;
|
DuviriInfo?: IDuviriInfo;
|
||||||
Mailbox?: IMailboxClient;
|
Mailbox?: IMailboxClient;
|
||||||
SubscribedToEmails: number;
|
SubscribedToEmails: number;
|
||||||
Created: IMongoDate;
|
Created: IMongoDate;
|
||||||
|
@ -4,7 +4,7 @@ export interface IAccountAndLoginResponseCommons {
|
|||||||
DisplayName: string;
|
DisplayName: string;
|
||||||
CountryCode: string;
|
CountryCode: string;
|
||||||
ClientType: string;
|
ClientType: string;
|
||||||
CrossPlatformAllowed: boolean;
|
CrossPlatformAllowed?: boolean;
|
||||||
ForceLogoutVersion: number;
|
ForceLogoutVersion: number;
|
||||||
AmazonAuthToken?: string;
|
AmazonAuthToken?: string;
|
||||||
AmazonRefreshToken?: string;
|
AmazonRefreshToken?: string;
|
||||||
@ -46,7 +46,7 @@ export interface ILoginResponse extends IAccountAndLoginResponseCommons {
|
|||||||
id: string;
|
id: string;
|
||||||
Groups: IGroup[];
|
Groups: IGroup[];
|
||||||
BuildLabel: string;
|
BuildLabel: string;
|
||||||
MatchmakingBuildId: string;
|
MatchmakingBuildId?: string;
|
||||||
platformCDNs?: string[];
|
platformCDNs?: string[];
|
||||||
NRS?: string[];
|
NRS?: string[];
|
||||||
DTLS: number;
|
DTLS: number;
|
||||||
|
@ -130,6 +130,7 @@ export type IMissionInventoryUpdateRequest = {
|
|||||||
}[];
|
}[];
|
||||||
KubrowPetEggs?: IKubrowPetEggClient[];
|
KubrowPetEggs?: IKubrowPetEggClient[];
|
||||||
DiscoveredMarkers?: IDiscoveredMarker[];
|
DiscoveredMarkers?: IDiscoveredMarker[];
|
||||||
|
BrandedSuits?: IOid; // sent when captured by g3
|
||||||
LockedWeaponGroup?: ILockedWeaponGroupClient; // sent when captured by zanuka
|
LockedWeaponGroup?: ILockedWeaponGroupClient; // sent when captured by zanuka
|
||||||
UnlockWeapons?: boolean; // sent when recovered weapons from zanuka capture
|
UnlockWeapons?: boolean; // sent when recovered weapons from zanuka capture
|
||||||
IncHarvester?: boolean; // sent when recovered weapons from zanuka capture
|
IncHarvester?: boolean; // sent when recovered weapons from zanuka capture
|
||||||
|
@ -10,6 +10,7 @@ export interface IWorldState {
|
|||||||
LiteSorties: ILiteSortie[];
|
LiteSorties: ILiteSortie[];
|
||||||
SyndicateMissions: ISyndicateMissionInfo[];
|
SyndicateMissions: ISyndicateMissionInfo[];
|
||||||
GlobalUpgrades: IGlobalUpgrade[];
|
GlobalUpgrades: IGlobalUpgrade[];
|
||||||
|
ActiveMissions: IFissure[];
|
||||||
NodeOverrides: INodeOverride[];
|
NodeOverrides: INodeOverride[];
|
||||||
EndlessXpChoices: IEndlessXpChoice[];
|
EndlessXpChoices: IEndlessXpChoice[];
|
||||||
SeasonInfo: {
|
SeasonInfo: {
|
||||||
@ -71,6 +72,18 @@ export interface IGlobalUpgrade {
|
|||||||
LocalizeDescTag: string;
|
LocalizeDescTag: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFissure {
|
||||||
|
_id: IOid;
|
||||||
|
Region: number;
|
||||||
|
Seed: number;
|
||||||
|
Activation: IMongoDate;
|
||||||
|
Expiry: IMongoDate;
|
||||||
|
Node: string;
|
||||||
|
MissionType: string;
|
||||||
|
Modifier: string;
|
||||||
|
Hard?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface INodeOverride {
|
export interface INodeOverride {
|
||||||
_id: IOid;
|
_id: IOid;
|
||||||
Activation?: IMongoDate;
|
Activation?: IMongoDate;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user