forked from OpenWF/SpaceNinjaServer
merge upstream
This commit is contained in:
commit
6532fd1fe8
8
package-lock.json
generated
8
package-lock.json
generated
@ -18,7 +18,7 @@
|
|||||||
"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.60",
|
"warframe-public-export-plus": "^0.5.62",
|
||||||
"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"
|
||||||
@ -3703,9 +3703,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/warframe-public-export-plus": {
|
"node_modules/warframe-public-export-plus": {
|
||||||
"version": "0.5.60",
|
"version": "0.5.62",
|
||||||
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.60.tgz",
|
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.62.tgz",
|
||||||
"integrity": "sha512-vMfytUc4xRi+b7RTSq+TJEl91vwEegpQKxLtXwRPfs9ZHhntxc4rmDYSNWJTvgf/aWXsFUxQlqL/GV5OLPGM7g=="
|
"integrity": "sha512-D8ZzjkU9rrK/59VqCfpMoV31HVmwHZV1dNZxPO85AOlcjg/G81Fu3kgITQTaw9sdNagLPLQnFaiXY58pxxRwgA=="
|
||||||
},
|
},
|
||||||
"node_modules/warframe-riven-info": {
|
"node_modules/warframe-riven-info": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"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.60",
|
"warframe-public-export-plus": "^0.5.62",
|
||||||
"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"
|
||||||
|
@ -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 { getInventory } from "@/src/services/inventoryService";
|
import { getEffectiveAvatarImageType, 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";
|
||||||
@ -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[senderInventory.ActiveAvatarImageType].icon,
|
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
||||||
contextInfo: alliance._id.toString(),
|
contextInfo: alliance._id.toString(),
|
||||||
highPriority: true,
|
highPriority: true,
|
||||||
acceptAction: "ALLIANCE_INVITE",
|
acceptAction: "ALLIANCE_INVITE",
|
||||||
|
@ -4,7 +4,7 @@ import { Account } from "@/src/models/loginModel";
|
|||||||
import { addInventoryDataToFriendInfo, areFriends } from "@/src/services/friendService";
|
import { addInventoryDataToFriendInfo, areFriends } from "@/src/services/friendService";
|
||||||
import { hasGuildPermission } from "@/src/services/guildService";
|
import { hasGuildPermission } from "@/src/services/guildService";
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getEffectiveAvatarImageType, 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";
|
||||||
@ -64,7 +64,7 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
||||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
||||||
contextInfo: payload.GuildId.$oid,
|
contextInfo: payload.GuildId.$oid,
|
||||||
highPriority: true,
|
highPriority: true,
|
||||||
acceptAction: "GUILD_INVITE",
|
acceptAction: "GUILD_INVITE",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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";
|
||||||
@ -7,15 +8,23 @@ 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");
|
const inventory = await getInventory(accountId, "CrewMembers NemesisHistory");
|
||||||
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
||||||
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
if (data.crewMember.SecondInCommand) {
|
||||||
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
clearOnCall(inventory);
|
||||||
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
}
|
||||||
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
|
if (data.crewMember.ItemId.$oid == "000000000000000000000000") {
|
||||||
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
const convertedNemesis = inventory.NemesisHistory!.find(x => x.fp == data.crewMember.NemesisFingerprint)!;
|
||||||
dbCrewMember.Configs = data.crewMember.Configs;
|
convertedNemesis.SecondInCommand = data.crewMember.SecondInCommand;
|
||||||
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
} else {
|
||||||
|
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
||||||
|
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
||||||
|
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
||||||
|
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
|
||||||
|
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
||||||
|
dbCrewMember.Configs = data.crewMember.Configs;
|
||||||
|
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,
|
||||||
@ -26,3 +35,20 @@ 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,14 +1,20 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
|
||||||
export const getVendorInfoController: RequestHandler = (req, res) => {
|
export const getVendorInfoController: RequestHandler = async (req, res) => {
|
||||||
if (typeof req.query.vendor == "string") {
|
let manifest = getVendorManifestByTypeName(req.query.vendor as 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}`);
|
|
||||||
}
|
|
||||||
res.json(manifest);
|
|
||||||
} else {
|
|
||||||
res.status(400).end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(manifest);
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,12 @@ 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 { areFriends } from "@/src/services/friendService";
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import {
|
||||||
|
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 { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
@ -85,7 +90,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
||||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
||||||
gifts: [
|
gifts: [
|
||||||
{
|
{
|
||||||
GiftType: data.PurchaseParams.StoreItem
|
GiftType: data.PurchaseParams.StoreItem
|
||||||
|
@ -9,7 +9,12 @@ 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 { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
import {
|
||||||
|
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";
|
||||||
@ -88,7 +93,7 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
||||||
icon: ExportFlavour[inventory.ActiveAvatarImageType].icon,
|
icon: ExportFlavour[getEffectiveAvatarImageType(inventory)].icon,
|
||||||
highPriority: true
|
highPriority: true
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -340,7 +340,7 @@ export const getInventoryResponse = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addString = (arr: string[], str: string): void => {
|
const addString = (arr: string[], str: string): void => {
|
||||||
if (!arr.find(x => x == str)) {
|
if (arr.indexOf(str) == -1) {
|
||||||
arr.push(str);
|
arr.push(str);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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.find(x => x == body.ChosenReward)) {
|
if (evergreenRewards.indexOf(body.ChosenReward) == -1) {
|
||||||
inventory.LoginMilestoneRewards.push(body.ChosenReward);
|
inventory.LoginMilestoneRewards.push(body.ChosenReward);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -61,7 +61,11 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
|||||||
|
|
||||||
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();
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
getNemesisPasscodeModTypes,
|
getNemesisPasscodeModTypes,
|
||||||
getWeaponsForManifest,
|
getWeaponsForManifest,
|
||||||
IKnifeResponse,
|
IKnifeResponse,
|
||||||
showdownNodes
|
nemesisFactionInfos
|
||||||
} 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";
|
||||||
@ -24,7 +24,8 @@ 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";
|
||||||
@ -176,7 +177,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
weaponIdx = initialWeaponIdx;
|
weaponIdx = initialWeaponIdx;
|
||||||
do {
|
do {
|
||||||
const weapon = weapons[weaponIdx];
|
const weapon = weapons[weaponIdx];
|
||||||
if (!body.target.DisallowedWeapons.find(x => x == weapon)) {
|
if (body.target.DisallowedWeapons.indexOf(weapon) == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
weaponIdx = (weaponIdx + 1) % weapons.length;
|
weaponIdx = (weaponIdx + 1) % weapons.length;
|
||||||
@ -222,7 +223,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
inventory.Nemesis!.InfNodes = [
|
inventory.Nemesis!.InfNodes = [
|
||||||
{
|
{
|
||||||
Node: showdownNodes[inventory.Nemesis!.Faction],
|
Node: nemesisFactionInfos[inventory.Nemesis!.Faction].showdownNode,
|
||||||
Influence: 1
|
Influence: 1
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -269,7 +270,7 @@ interface INemesisStartRequest {
|
|||||||
WeaponIdx: number;
|
WeaponIdx: number;
|
||||||
AgentIdx: number;
|
AgentIdx: number;
|
||||||
BirthNode: string;
|
BirthNode: string;
|
||||||
Faction: string;
|
Faction: TNemesisFaction;
|
||||||
Rank: number;
|
Rank: number;
|
||||||
k: boolean;
|
k: boolean;
|
||||||
Traded: boolean;
|
Traded: boolean;
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addEmailItem, getDialogue, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { ICompletedDialogue } 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";
|
||||||
|
|
||||||
@ -107,26 +106,3 @@ 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,11 +2,12 @@ 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 = JSON.parse(req.body as string) as ISaveLoadoutRequest;
|
const body: ISaveLoadoutRequest = getJSONfromString<ISaveLoadoutRequest>(String(req.body));
|
||||||
// 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
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
|
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
|
||||||
import { IInfNode, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInfNode, TNemesisFaction } 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";
|
||||||
@ -7,13 +7,51 @@ 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 } from "../services/worldStateService";
|
||||||
import { fromStoreItem, toStoreItem } from "../services/itemDataService";
|
|
||||||
import { createMessage } from "../services/inboxService";
|
|
||||||
import { version_compare } from "./inventoryHelpers";
|
import { version_compare } from "./inventoryHelpers";
|
||||||
|
|
||||||
export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
|
export interface INemesisFactionInfo {
|
||||||
|
systemIndexes: number[];
|
||||||
|
showdownNode: string;
|
||||||
|
ephemeraChance: number;
|
||||||
|
firstKillReward: string;
|
||||||
|
firstConvertReward: string;
|
||||||
|
messageTitle: string;
|
||||||
|
messageBody: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nemesisFactionInfos: Record<TNemesisFaction, INemesisFactionInfo> = {
|
||||||
|
FC_GRINEER: {
|
||||||
|
systemIndexes: [2, 3, 9, 11, 18],
|
||||||
|
showdownNode: "CrewBattleNode557",
|
||||||
|
ephemeraChance: 0.05,
|
||||||
|
firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem",
|
||||||
|
firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil",
|
||||||
|
messageTitle: "/Lotus/Language/Inbox/VanquishKuvaMsgTitle",
|
||||||
|
messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody"
|
||||||
|
},
|
||||||
|
FC_CORPUS: {
|
||||||
|
systemIndexes: [1, 15, 4, 7, 8],
|
||||||
|
showdownNode: "CrewBattleNode558",
|
||||||
|
ephemeraChance: 0.2,
|
||||||
|
firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem",
|
||||||
|
firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil",
|
||||||
|
messageTitle: "/Lotus/Language/Inbox/VanquishLawyerMsgTitle",
|
||||||
|
messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody"
|
||||||
|
},
|
||||||
|
FC_INFESTATION: {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[] => {
|
||||||
const infNodes = [];
|
const infNodes = [];
|
||||||
const systemIndex = systemIndexes[faction][rank];
|
const systemIndex = nemesisFactionInfos[faction].systemIndexes[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 &&
|
||||||
@ -35,20 +73,38 @@ export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
|
|||||||
return infNodes;
|
return infNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
const systemIndexes: Record<string, number[]> = {
|
type TInnateDamageTag =
|
||||||
FC_GRINEER: [2, 3, 9, 11, 18],
|
| "InnateElectricityDamage"
|
||||||
FC_CORPUS: [1, 15, 4, 7, 8],
|
| "InnateHeatDamage"
|
||||||
FC_INFESTATION: [23]
|
| "InnateFreezeDamage"
|
||||||
};
|
| "InnateToxinDamage"
|
||||||
|
| "InnateMagDamage"
|
||||||
|
| "InnateRadDamage"
|
||||||
|
| "InnateImpactDamage";
|
||||||
|
|
||||||
export const showdownNodes: Record<string, string> = {
|
const ephmeraTypes: Record<"FC_GRINEER" | "FC_CORPUS", Record<TInnateDamageTag, string>> = {
|
||||||
FC_GRINEER: "CrewBattleNode557",
|
FC_GRINEER: {
|
||||||
FC_CORPUS: "CrewBattleNode558",
|
InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera",
|
||||||
FC_INFESTATION: "CrewBattleNode559"
|
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"
|
||||||
|
},
|
||||||
|
FC_CORPUS: {
|
||||||
|
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"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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: string }): number[] => {
|
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFaction }): 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);
|
||||||
@ -87,7 +143,7 @@ const antivirusMods: readonly string[] = [
|
|||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: string }): string[] => {
|
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): 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])
|
||||||
@ -248,7 +304,7 @@ export const getWeaponsForManifest = (manifest: string): readonly string[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isNemesisCompatibleWithVersion = (
|
export const isNemesisCompatibleWithVersion = (
|
||||||
nemesis: { manifest: string; Faction: string },
|
nemesis: { manifest: string; Faction: TNemesisFaction },
|
||||||
buildLabel: string
|
buildLabel: string
|
||||||
): boolean => {
|
): boolean => {
|
||||||
// Anything below 35.6.0 is not going to be okay given our set of supported manifests.
|
// Anything below 35.6.0 is not going to be okay given our set of supported manifests.
|
||||||
@ -271,21 +327,31 @@ export const isNemesisCompatibleWithVersion = (
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInnateDamageTag = (
|
export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
|
||||||
KillingSuit: string
|
|
||||||
):
|
|
||||||
| "InnateElectricityDamage"
|
|
||||||
| "InnateFreezeDamage"
|
|
||||||
| "InnateHeatDamage"
|
|
||||||
| "InnateImpactDamage"
|
|
||||||
| "InnateMagDamage"
|
|
||||||
| "InnateRadDamage"
|
|
||||||
| "InnateToxinDamage" => {
|
|
||||||
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
|
const petHeads = [
|
||||||
export const getInnateDamageValue = (fp: bigint): number => {
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
|
||||||
|
"/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(),
|
||||||
|
Faction: TNemesisFaction = "FC_CORPUS",
|
||||||
|
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;
|
||||||
@ -293,7 +359,33 @@ export const getInnateDamageValue = (fp: bigint): number => {
|
|||||||
if (value >= 0.941428) {
|
if (value >= 0.941428) {
|
||||||
value = 1;
|
value = 1;
|
||||||
}
|
}
|
||||||
return Math.trunc(value * 0x40000000);
|
const profile: INemesisProfile = {
|
||||||
|
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() <= nemesisFactionInfos[Faction].ephemeraChance && Faction != "FC_INFESTATION") {
|
||||||
|
profile.ephemera = ephmeraTypes[Faction][profile.innateDamageTag];
|
||||||
|
}
|
||||||
|
rng.randomFloat(); // something related to sentinel agent maybe
|
||||||
|
if (Faction == "FC_CORPUS") {
|
||||||
|
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 => {
|
||||||
@ -352,52 +444,3 @@ 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
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
@ -1318,7 +1318,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: undefined },
|
Hints: { type: [Number], default: [] },
|
||||||
GuessHistory: { type: [Number], default: undefined },
|
GuessHistory: { type: [Number], default: undefined },
|
||||||
MissionCount: Number,
|
MissionCount: Number,
|
||||||
LastEnc: Number
|
LastEnc: Number
|
||||||
@ -1621,7 +1621,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
Drones: [droneSchema],
|
Drones: [droneSchema],
|
||||||
|
|
||||||
//Active profile ico
|
//Active profile ico
|
||||||
ActiveAvatarImageType: { type: String, default: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageDefault" },
|
ActiveAvatarImageType: String,
|
||||||
|
|
||||||
// open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable
|
// open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable
|
||||||
DiscoveredMarkers: [discoveredMarkerSchema],
|
DiscoveredMarkers: [discoveredMarkerSchema],
|
||||||
|
@ -445,7 +445,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.find(x => x == build.resultType)) {
|
if (guild.ClaimedXP.indexOf(build.resultType) == -1) {
|
||||||
guild.ClaimedXP.push(build.resultType);
|
guild.ClaimedXP.push(build.resultType);
|
||||||
guild.XP += build.guildXpValue;
|
guild.XP += build.guildXpValue;
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,8 @@ 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";
|
||||||
@ -83,7 +84,7 @@ import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from ".
|
|||||||
import { createMessage } from "./inboxService";
|
import { createMessage } from "./inboxService";
|
||||||
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
||||||
import { getWorldState } from "./worldStateService";
|
import { getWorldState } from "./worldStateService";
|
||||||
import { getInnateDamageTag, getInnateDamageValue } from "../helpers/nemesisHelpers";
|
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
|
||||||
|
|
||||||
export const createInventory = async (
|
export const createInventory = async (
|
||||||
accountOwnerId: Types.ObjectId,
|
accountOwnerId: Types.ObjectId,
|
||||||
@ -1906,6 +1907,29 @@ 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];
|
||||||
|
|
||||||
@ -1945,8 +1969,7 @@ export const giveNemesisWeaponRecipe = (
|
|||||||
weaponType: string,
|
weaponType: string,
|
||||||
nemesisName: string = "AGOR ROK",
|
nemesisName: string = "AGOR ROK",
|
||||||
weaponLoc?: string,
|
weaponLoc?: string,
|
||||||
KillingSuit: string = "/Lotus/Powersuits/Ember/Ember",
|
profile: INemesisProfile = generateNemesisProfile()
|
||||||
fp: bigint = generateRewardSeed()
|
|
||||||
): void => {
|
): void => {
|
||||||
if (!weaponLoc) {
|
if (!weaponLoc) {
|
||||||
weaponLoc = ExportWeapons[weaponType].name;
|
weaponLoc = ExportWeapons[weaponType].name;
|
||||||
@ -1967,8 +1990,8 @@ export const giveNemesisWeaponRecipe = (
|
|||||||
compat: weaponType,
|
compat: weaponType,
|
||||||
buffs: [
|
buffs: [
|
||||||
{
|
{
|
||||||
Tag: getInnateDamageTag(KillingSuit),
|
Tag: profile.innateDamageTag,
|
||||||
Value: getInnateDamageValue(fp)
|
Value: profile.innateDamageValue
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1977,27 +2000,15 @@ export const giveNemesisWeaponRecipe = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const giveNemesisPetRecipe = (inventory: TInventoryDatabaseDocument, nemesisName: string = "AGOR ROK"): void => {
|
export const giveNemesisPetRecipe = (
|
||||||
const head = getRandomElement([
|
inventory: TInventoryDatabaseDocument,
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
|
nemesisName: string = "AGOR ROK",
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
|
profile: INemesisProfile = generateNemesisProfile()
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
|
): void => {
|
||||||
])!;
|
const head = profile.petHead!;
|
||||||
const body = getRandomElement([
|
const body = profile.petBody!;
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
|
const legs = profile.petLegs!;
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
|
const tail = profile.petTail!;
|
||||||
"/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, [
|
||||||
{
|
{
|
||||||
@ -2014,3 +2025,7 @@ export const giveNemesisPetRecipe = (inventory: TInventoryDatabaseDocument, neme
|
|||||||
} satisfies INemesisPetTargetFingerprint)
|
} satisfies INemesisPetTargetFingerprint)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getEffectiveAvatarImageType = (inventory: TInventoryDatabaseDocument): string => {
|
||||||
|
return inventory.ActiveAvatarImageType ?? "/Lotus/Types/StoreItems/AvatarImages/AvatarImageDefault";
|
||||||
|
};
|
||||||
|
@ -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?.find(x => x == account.DisplayName);
|
return config.administratorNames?.indexOf(account.DisplayName) != -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const platform_magics = [753, 639, 247, 37, 60];
|
const platform_magics = [753, 639, 247, 37, 60];
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
|
import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
|
||||||
import { equipmentKeys, IMission, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import {
|
import {
|
||||||
addBooster,
|
addBooster,
|
||||||
addChallenges,
|
addChallenges,
|
||||||
@ -35,6 +35,7 @@ import {
|
|||||||
combineInventoryChanges,
|
combineInventoryChanges,
|
||||||
generateRewardSeed,
|
generateRewardSeed,
|
||||||
getCalendarProgress,
|
getCalendarProgress,
|
||||||
|
getDialogue,
|
||||||
giveNemesisPetRecipe,
|
giveNemesisPetRecipe,
|
||||||
giveNemesisWeaponRecipe,
|
giveNemesisWeaponRecipe,
|
||||||
updateCurrency,
|
updateCurrency,
|
||||||
@ -43,7 +44,7 @@ import {
|
|||||||
import { updateQuestKey } from "@/src/services/questService";
|
import { updateQuestKey } from "@/src/services/questService";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService";
|
import { fromStoreItem, getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
|
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
@ -55,10 +56,26 @@ import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.
|
|||||||
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
|
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
|
||||||
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
|
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
|
||||||
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
|
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
|
||||||
import { getInfNodes, getWeaponsForManifest, sendCodaFinishedMessage } from "@/src/helpers/nemesisHelpers";
|
import {
|
||||||
|
generateNemesisProfile,
|
||||||
|
getInfestedLichItemRewards,
|
||||||
|
getInfNodes,
|
||||||
|
getKillTokenRewardCount,
|
||||||
|
getNemesisPasscode,
|
||||||
|
getWeaponsForManifest,
|
||||||
|
nemesisFactionInfos
|
||||||
|
} from "@/src/helpers/nemesisHelpers";
|
||||||
import { Loadout } from "../models/inventoryModels/loadoutModel";
|
import { Loadout } from "../models/inventoryModels/loadoutModel";
|
||||||
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
|
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
|
||||||
import { getLiteSortie, getSortie, idToBountyCycle, idToDay, idToWeek, pushClassicBounties } from "./worldStateService";
|
import {
|
||||||
|
getLiteSortie,
|
||||||
|
getSortie,
|
||||||
|
getWorldState,
|
||||||
|
idToBountyCycle,
|
||||||
|
idToDay,
|
||||||
|
idToWeek,
|
||||||
|
pushClassicBounties
|
||||||
|
} from "./worldStateService";
|
||||||
import { config } from "./configService";
|
import { config } from "./configService";
|
||||||
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
||||||
import { ISyndicateMissionInfo } from "../types/worldStateTypes";
|
import { ISyndicateMissionInfo } from "../types/worldStateTypes";
|
||||||
@ -168,6 +185,14 @@ export const addMissionInventoryUpdates = async (
|
|||||||
}
|
}
|
||||||
if (inventoryUpdates.RewardInfo.NemesisHintProgress && inventory.Nemesis) {
|
if (inventoryUpdates.RewardInfo.NemesisHintProgress && inventory.Nemesis) {
|
||||||
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress;
|
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress;
|
||||||
|
if (inventory.Nemesis.Faction != "FC_INFESTATION" && inventory.Nemesis.Hints.length != 3) {
|
||||||
|
const progressNeeded = [35, 60, 100][inventory.Nemesis.Hints.length];
|
||||||
|
if (inventory.Nemesis.HintProgress >= progressNeeded) {
|
||||||
|
inventory.Nemesis.HintProgress -= progressNeeded;
|
||||||
|
const passcode = getNemesisPasscode(inventory.Nemesis);
|
||||||
|
inventory.Nemesis.Hints.push(passcode[inventory.Nemesis.Hints.length]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) {
|
if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) {
|
||||||
// e.g. for Profit-Taker Phase 1:
|
// e.g. for Profit-Taker Phase 1:
|
||||||
@ -358,7 +383,7 @@ export const addMissionInventoryUpdates = async (
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
inventory.LibraryActiveDailyTaskInfo &&
|
inventory.LibraryActiveDailyTaskInfo &&
|
||||||
inventory.LibraryActiveDailyTaskInfo.EnemyTypes.find(x => x == scan.EnemyType)
|
inventory.LibraryActiveDailyTaskInfo.EnemyTypes.indexOf(scan.EnemyType) != -1
|
||||||
) {
|
) {
|
||||||
inventory.LibraryActiveDailyTaskInfo.Scans ??= 0;
|
inventory.LibraryActiveDailyTaskInfo.Scans ??= 0;
|
||||||
inventory.LibraryActiveDailyTaskInfo.Scans += scan.Count;
|
inventory.LibraryActiveDailyTaskInfo.Scans += scan.Count;
|
||||||
@ -631,12 +656,21 @@ export const addMissionInventoryUpdates = async (
|
|||||||
Rank: inventory.Nemesis.Rank,
|
Rank: inventory.Nemesis.Rank,
|
||||||
Traded: inventory.Nemesis.Traded,
|
Traded: inventory.Nemesis.Traded,
|
||||||
PrevOwners: inventory.Nemesis.PrevOwners,
|
PrevOwners: inventory.Nemesis.PrevOwners,
|
||||||
SecondInCommand: inventory.Nemesis.SecondInCommand,
|
SecondInCommand: false,
|
||||||
Weakened: inventory.Nemesis.Weakened,
|
Weakened: inventory.Nemesis.Weakened,
|
||||||
// And set killed flag
|
// And set killed flag
|
||||||
k: value.killed
|
k: value.killed
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const profile = generateNemesisProfile(
|
||||||
|
inventory.Nemesis.fp,
|
||||||
|
inventory.Nemesis.Faction,
|
||||||
|
inventory.Nemesis.KillingSuit
|
||||||
|
);
|
||||||
|
const nemesisFactionInfo = nemesisFactionInfos[inventory.Nemesis.Faction];
|
||||||
|
const att: string[] = [];
|
||||||
|
let countedAtt: ITypeCount[] | undefined;
|
||||||
|
|
||||||
if (value.killed) {
|
if (value.killed) {
|
||||||
if (
|
if (
|
||||||
value.weaponLoc &&
|
value.weaponLoc &&
|
||||||
@ -645,23 +679,79 @@ export const addMissionInventoryUpdates = async (
|
|||||||
const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
|
const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
|
||||||
inventory.Nemesis.WeaponIdx
|
inventory.Nemesis.WeaponIdx
|
||||||
];
|
];
|
||||||
giveNemesisWeaponRecipe(
|
giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile);
|
||||||
inventory,
|
att.push(weaponType);
|
||||||
weaponType,
|
|
||||||
value.nemesisName,
|
|
||||||
value.weaponLoc,
|
|
||||||
inventory.Nemesis.KillingSuit,
|
|
||||||
inventory.Nemesis.fp
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (value.petLoc) {
|
//if (value.petLoc) {
|
||||||
giveNemesisPetRecipe(inventory);
|
if (profile.petHead) {
|
||||||
|
giveNemesisPetRecipe(inventory, value.nemesisName, profile);
|
||||||
|
att.push(
|
||||||
|
{
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
|
||||||
|
"/Lotus/Types/Recipes/ZanukaPet/ZanukaPetCompleteHeadABlueprint",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
|
||||||
|
"/Lotus/Types/Recipes/ZanukaPet/ZanukaPetCompleteHeadBBlueprint",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
|
||||||
|
"/Lotus/Types/Recipes/ZanukaPet/ZanukaPetCompleteHeadCBlueprint"
|
||||||
|
}[profile.petHead]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given?
|
// "Players will receive a Lich's Ephemera regardless of whether they Vanquish or Convert them."
|
||||||
|
if (profile.ephemera) {
|
||||||
|
addSkin(inventory, profile.ephemera);
|
||||||
|
att.push(profile.ephemera);
|
||||||
|
}
|
||||||
|
|
||||||
|
const skinRewardStoreItem = value.killed
|
||||||
|
? nemesisFactionInfo.firstKillReward
|
||||||
|
: nemesisFactionInfo.firstConvertReward;
|
||||||
|
if (Object.keys(addSkin(inventory, fromStoreItem(skinRewardStoreItem))).length != 0) {
|
||||||
|
att.push(skinRewardStoreItem);
|
||||||
|
}
|
||||||
|
|
||||||
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
|
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
|
||||||
await sendCodaFinishedMessage(inventory, inventory.Nemesis.fp, value.nemesisName, value.killed);
|
const [rotARewardStoreItem, rotBRewardStoreItem] = getInfestedLichItemRewards(
|
||||||
|
inventory.Nemesis.fp
|
||||||
|
);
|
||||||
|
const rotAReward = fromStoreItem(rotARewardStoreItem);
|
||||||
|
const rotBReward = fromStoreItem(rotBRewardStoreItem);
|
||||||
|
await addItem(inventory, rotAReward);
|
||||||
|
await addItem(inventory, rotBReward);
|
||||||
|
att.push(rotAReward);
|
||||||
|
att.push(rotBReward);
|
||||||
|
|
||||||
|
if (value.killed) {
|
||||||
|
countedAtt = [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
|
||||||
|
ItemCount: getKillTokenRewardCount(inventory.Nemesis.fp)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addMiscItems(inventory, countedAtt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.killed) {
|
||||||
|
await createMessage(inventory.accountOwnerId, [
|
||||||
|
{
|
||||||
|
sndr: "/Lotus/Language/Bosses/Ordis",
|
||||||
|
msg: nemesisFactionInfo.messageBody,
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "LICH_NAME",
|
||||||
|
Tag: value.nemesisName
|
||||||
|
}
|
||||||
|
],
|
||||||
|
att: att,
|
||||||
|
countedAtt: countedAtt,
|
||||||
|
attVisualOnly: true,
|
||||||
|
sub: nemesisFactionInfo.messageTitle,
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
|
||||||
|
highPriority: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
inventory.Nemesis = undefined;
|
inventory.Nemesis = undefined;
|
||||||
@ -1175,8 +1265,9 @@ export const addMissionRewards = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rewardInfo.challengeMissionId) {
|
if (rewardInfo.challengeMissionId) {
|
||||||
const [syndicateTag, tierStr] = rewardInfo.challengeMissionId.split("_"); // TODO: third part in HexSyndicate jobs - Chemistry points
|
const [syndicateTag, tierStr, chemistryStr] = rewardInfo.challengeMissionId.split("_");
|
||||||
const tier = Number(tierStr);
|
const tier = Number(tierStr);
|
||||||
|
const chemistry = Number(chemistryStr);
|
||||||
const isSteelPath = missions?.Tier;
|
const isSteelPath = missions?.Tier;
|
||||||
if (syndicateTag === "ZarimanSyndicate") {
|
if (syndicateTag === "ZarimanSyndicate") {
|
||||||
let medallionAmount = tier + 1;
|
let medallionAmount = tier + 1;
|
||||||
@ -1193,6 +1284,23 @@ export const addMissionRewards = async (
|
|||||||
if (isSteelPath) standingAmount *= 1.5;
|
if (isSteelPath) standingAmount *= 1.5;
|
||||||
AffiliationMods.push(addStanding(inventory, syndicateTag, standingAmount));
|
AffiliationMods.push(addStanding(inventory, syndicateTag, standingAmount));
|
||||||
}
|
}
|
||||||
|
if (syndicateTag == "HexSyndicate" && chemistry && tier < 6) {
|
||||||
|
const seed = getWorldState().SyndicateMissions.find(x => x.Tag == "HexSyndicate")!.Seed;
|
||||||
|
const { nodes, buddies } = getHexBounties(seed);
|
||||||
|
const buddy = buddies[tier];
|
||||||
|
logger.debug(`Hex seed is ${seed}, giving chemistry for ${buddy}`);
|
||||||
|
if (missions?.Tag != nodes[tier]) {
|
||||||
|
logger.warn(
|
||||||
|
`Uh-oh, tier ${tier} bounty should've been on ${nodes[tier]} but you were just on ${missions?.Tag}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const tomorrowAt0Utc = config.noKimCooldowns
|
||||||
|
? Date.now()
|
||||||
|
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
|
||||||
|
const dialogue = getDialogue(inventory, buddy);
|
||||||
|
dialogue.Chemistry += chemistry;
|
||||||
|
dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc);
|
||||||
|
}
|
||||||
if (isSteelPath) {
|
if (isSteelPath) {
|
||||||
await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1);
|
await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1);
|
||||||
MissionRewards.push({
|
MissionRewards.push({
|
||||||
@ -1403,6 +1511,37 @@ function getRandomMissionDrops(
|
|||||||
} else {
|
} else {
|
||||||
rewardManifests = [];
|
rewardManifests = [];
|
||||||
}
|
}
|
||||||
|
} else if (RewardInfo.T == 13) {
|
||||||
|
// Undercroft extra/side portal (normal mode), gives 1 Pathos Clamp + Duviri Arcane.
|
||||||
|
drops.push({
|
||||||
|
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
rewardManifests = [
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriStaticUndercroftResourceRewards"
|
||||||
|
];
|
||||||
|
} else if (RewardInfo.T == 14) {
|
||||||
|
// Undercroft extra/side portal (steel path), gives 3 Pathos Clamps + Eidolon Arcane.
|
||||||
|
drops.push({
|
||||||
|
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
|
||||||
|
ItemCount: 3
|
||||||
|
});
|
||||||
|
rewardManifests = [
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriSteelPathStaticUndercroftResourceRewards"
|
||||||
|
];
|
||||||
|
} else if (RewardInfo.T == 15) {
|
||||||
|
rewardManifests = [
|
||||||
|
mission?.Tier == 1
|
||||||
|
? "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoSteelPathRNGRewards"
|
||||||
|
: "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoNormalRNGRewards"
|
||||||
|
];
|
||||||
|
} else if (RewardInfo.T == 70) {
|
||||||
|
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
|
||||||
|
drops.push({
|
||||||
|
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
|
||||||
|
ItemCount: mission?.Tier == 1 ? 15 : 10
|
||||||
|
});
|
||||||
|
rewardManifests = [];
|
||||||
} else {
|
} else {
|
||||||
rewardManifests = region.rewardManifests;
|
rewardManifests = region.rewardManifests;
|
||||||
}
|
}
|
||||||
@ -1721,3 +1860,55 @@ const libraryPersonalTargetToAvatar: Record<string, string> = {
|
|||||||
"/Lotus/Types/Game/Library/Targets/Research10Target":
|
"/Lotus/Types/Game/Library/Targets/Research10Target":
|
||||||
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar"
|
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const node_excluded_buddies: Record<string, string> = {
|
||||||
|
SolNode856: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
|
||||||
|
SolNode852: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
|
||||||
|
SolNode851: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
|
||||||
|
SolNode850: "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
|
||||||
|
SolNode853: "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
|
||||||
|
SolNode854: "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue"
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } => {
|
||||||
|
// We're gonna shuffle these arrays, so they're not truly 'const'.
|
||||||
|
const nodes: string[] = [
|
||||||
|
"SolNode850",
|
||||||
|
"SolNode851",
|
||||||
|
"SolNode852",
|
||||||
|
"SolNode853",
|
||||||
|
"SolNode854",
|
||||||
|
"SolNode856",
|
||||||
|
"SolNode858"
|
||||||
|
];
|
||||||
|
const excludable_nodes: string[] = ["SolNode851", "SolNode852", "SolNode853", "SolNode854"];
|
||||||
|
const buddies: string[] = [
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue"
|
||||||
|
];
|
||||||
|
|
||||||
|
const rng = new SRng(seed);
|
||||||
|
rng.shuffleArray(nodes);
|
||||||
|
rng.shuffleArray(excludable_nodes);
|
||||||
|
while (nodes.length > buddies.length) {
|
||||||
|
nodes.splice(
|
||||||
|
nodes.findIndex(x => x == excludable_nodes[0]),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
excludable_nodes.splice(0, 1);
|
||||||
|
}
|
||||||
|
rng.shuffleArray(buddies);
|
||||||
|
for (let i = 0; i != 6; ++i) {
|
||||||
|
if (buddies[i] == node_excluded_buddies[nodes[i]]) {
|
||||||
|
const swapIdx = (i + 1) % buddies.length;
|
||||||
|
const tmp = buddies[swapIdx];
|
||||||
|
buddies[swapIdx] = buddies[i];
|
||||||
|
buddies[i] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { nodes, buddies };
|
||||||
|
};
|
||||||
|
@ -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 { getVendorManifestByOid } from "@/src/services/serversideVendorsService";
|
import { applyStandingToVendorManifest, 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,8 +53,9 @@ 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) {
|
||||||
const manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!);
|
let 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 })
|
||||||
@ -92,7 +93,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 =
|
||||||
|
@ -115,4 +115,19 @@ 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,9 +1,10 @@
|
|||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { mixSeeds, SRng } 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 { ExportVendors, IRange } from "warframe-public-export-plus";
|
import { ExportVendors, IRange, IVendor } 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";
|
||||||
@ -81,12 +82,6 @@ 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" },
|
// _id: { $oid: "5dbb4c41e966f7886c3ce939" },
|
||||||
@ -98,6 +93,25 @@ 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) {
|
||||||
@ -110,11 +124,12 @@ 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: ExportVendors[typeName].randomSeedType,
|
RandomSeedType: manifest.randomSeedType,
|
||||||
cycleDuration: unixTimesInMs.hour
|
cycleDuration: getCycleDuration(manifest)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -138,13 +153,50 @@ export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined
|
|||||||
_id: { $oid: typeNameOid },
|
_id: { $oid: typeNameOid },
|
||||||
TypeName: typeName,
|
TypeName: typeName,
|
||||||
RandomSeedType: manifest.randomSeedType,
|
RandomSeedType: manifest.randomSeedType,
|
||||||
cycleDuration: unixTimesInMs.hour
|
cycleDuration: getCycleDuration(manifest)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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);
|
||||||
@ -176,24 +228,27 @@ const toRange = (value: IRange | number): IRange => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const vendorInfoCache: Record<string, IVendorInfo> = {};
|
const vendorManifestCache: Record<string, IVendorManifest> = {};
|
||||||
|
|
||||||
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
|
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
|
||||||
if (!(vendorInfo.TypeName in vendorInfoCache)) {
|
if (!(vendorInfo.TypeName in vendorManifestCache)) {
|
||||||
// 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;
|
||||||
vendorInfoCache[vendorInfo.TypeName] = {
|
vendorManifestCache[vendorInfo.TypeName] = {
|
||||||
...clientVendorInfo,
|
VendorInfo: {
|
||||||
ItemManifest: [],
|
...clientVendorInfo,
|
||||||
Expiry: { $date: { $numberLong: "0" } }
|
ItemManifest: [],
|
||||||
|
Expiry: { $date: { $numberLong: "0" } }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const processed = vendorInfoCache[vendorInfo.TypeName];
|
const cacheEntry = vendorManifestCache[vendorInfo.TypeName];
|
||||||
if (Date.now() >= parseInt(processed.Expiry.$date.$numberLong)) {
|
const info = cacheEntry.VendorInfo;
|
||||||
|
if (Date.now() >= parseInt(info.Expiry.$date.$numberLong)) {
|
||||||
// Remove expired offers
|
// Remove expired offers
|
||||||
for (let i = 0; i != processed.ItemManifest.length; ) {
|
for (let i = 0; i != info.ItemManifest.length; ) {
|
||||||
if (Date.now() >= parseInt(processed.ItemManifest[i].Expiry.$date.$numberLong)) {
|
if (Date.now() >= parseInt(info.ItemManifest[i].Expiry.$date.$numberLong)) {
|
||||||
processed.ItemManifest.splice(i, 1);
|
info.ItemManifest.splice(i, 1);
|
||||||
} else {
|
} else {
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
@ -207,9 +262,14 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
|
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
|
||||||
const manifest = ExportVendors[vendorInfo.TypeName];
|
const manifest = ExportVendors[vendorInfo.TypeName];
|
||||||
const offersToAdd = [];
|
const offersToAdd = [];
|
||||||
if (manifest.numItems && !manifest.isOneBinPerCycle) {
|
if (
|
||||||
|
manifest.numItems &&
|
||||||
|
(manifest.numItems.minValue != manifest.numItems.maxValue ||
|
||||||
|
manifest.items.length != manifest.numItems.minValue) &&
|
||||||
|
!manifest.isOneBinPerCycle
|
||||||
|
) {
|
||||||
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
|
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
|
||||||
while (processed.ItemManifest.length + offersToAdd.length < numItemsTarget) {
|
while (info.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
|
||||||
offersToAdd.push(rng.randomElement(manifest.items)!);
|
offersToAdd.push(rng.randomElement(manifest.items)!);
|
||||||
@ -288,20 +348,18 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
item.LocTagRandSeed = (BigInt(highDword) << 32n) | (BigInt(item.LocTagRandSeed) & 0xffffffffn);
|
item.LocTagRandSeed = (BigInt(highDword) << 32n) | (BigInt(item.LocTagRandSeed) & 0xffffffffn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processed.ItemManifest.push(item);
|
info.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 processed.ItemManifest) {
|
for (const offer of info.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processed.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
|
info.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
|
||||||
}
|
}
|
||||||
return {
|
return cacheEntry;
|
||||||
VendorInfo: processed
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@ -296,7 +296,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[];
|
||||||
@ -863,6 +863,8 @@ 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;
|
||||||
@ -872,7 +874,7 @@ export interface INemesisBaseClient {
|
|||||||
WeaponIdx: number;
|
WeaponIdx: number;
|
||||||
AgentIdx: number;
|
AgentIdx: number;
|
||||||
BirthNode: string;
|
BirthNode: string;
|
||||||
Faction: string;
|
Faction: TNemesisFaction;
|
||||||
Rank: number;
|
Rank: number;
|
||||||
k: boolean;
|
k: boolean;
|
||||||
Traded: boolean;
|
Traded: boolean;
|
||||||
|
@ -177,6 +177,7 @@ export interface IRewardInfo {
|
|||||||
PurgatoryRewardQualifications?: string;
|
PurgatoryRewardQualifications?: string;
|
||||||
rewardSeed?: number | bigint;
|
rewardSeed?: number | bigint;
|
||||||
periodicMissionTag?: string;
|
periodicMissionTag?: string;
|
||||||
|
T?: number; // Duviri
|
||||||
ConquestType?: string;
|
ConquestType?: string;
|
||||||
ConquestCompleted?: number;
|
ConquestCompleted?: number;
|
||||||
ConquestEquipmentSuggestionsFulfilled?: number;
|
ConquestEquipmentSuggestionsFulfilled?: number;
|
||||||
|
@ -15,10 +15,16 @@ export interface IItemManifest {
|
|||||||
QuantityMultiplier: number;
|
QuantityMultiplier: number;
|
||||||
Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
|
Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
|
||||||
PurchaseQuantityLimit?: number;
|
PurchaseQuantityLimit?: number;
|
||||||
|
Affiliation?: string;
|
||||||
|
MinAffiliationRank?: number;
|
||||||
|
ReductionPerPositiveRank?: number;
|
||||||
|
IncreasePerNegativeRank?: number;
|
||||||
RotatedWeekly?: boolean;
|
RotatedWeekly?: boolean;
|
||||||
AllowMultipurchase: boolean;
|
AllowMultipurchase: boolean;
|
||||||
LocTagRandSeed?: number | bigint;
|
LocTagRandSeed?: number | bigint;
|
||||||
Id: IOid;
|
Id: IOid;
|
||||||
|
RegularPriceBeforeDiscount?: number[];
|
||||||
|
ItemPricesBeforeDiscount?: IItemPrice[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVendorInfo {
|
export interface IVendorInfo {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user