merge upstream

This commit is contained in:
hxedcl 2025-04-20 20:50:29 -07:00
commit 8d729386a0
38 changed files with 985 additions and 454 deletions

View File

@ -4,7 +4,7 @@
"description": "WF Emulator", "description": "WF Emulator",
"main": "index.ts", "main": "index.ts",
"scripts": { "scripts": {
"start": "node --import ./build/src/pathman.js build/src/index.js", "start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
"dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ", "dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
"build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui", "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
"verify": "tsgo --noEmit", "verify": "tsgo --noEmit",

View File

@ -0,0 +1,30 @@
import { toOid } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Account, Ignore } from "@/src/models/loginModel";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IFriendInfo } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const addIgnoredUserController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IAddIgnoredUserRequest>(String(req.body));
const ignoreeAccount = await Account.findOne(
{ DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
"_id"
);
if (ignoreeAccount) {
await Ignore.create({ ignorer: accountId, ignoree: ignoreeAccount._id });
res.json({
Ignored: {
_id: toOid(ignoreeAccount._id),
DisplayName: data.playerName
} satisfies IFriendInfo
});
} else {
res.status(400).end();
}
};
interface IAddIgnoredUserRequest {
playerName: string;
}

View File

@ -18,6 +18,7 @@ import {
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { toOid } from "@/src/helpers/inventoryHelpers";
interface IClaimCompletedRecipeRequest { interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[]; RecipeIds: IOid[];
@ -80,6 +81,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
} else { } else {
logger.debug("Claiming Recipe", { recipe, pendingRecipe }); logger.debug("Claiming Recipe", { recipe, pendingRecipe });
let BrandedSuits: undefined | IOid[];
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") { if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
inventory.PendingSpectreLoadouts ??= []; inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= []; inventory.SpectreLoadouts ??= [];
@ -104,9 +106,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)), inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
1 1
); );
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
} }
let InventoryChanges = {}; let InventoryChanges: IInventoryChanges = {};
if (recipe.consumeOnUse) { if (recipe.consumeOnUse) {
addRecipes(inventory, [ addRecipes(inventory, [
{ {
@ -134,6 +137,6 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
}; };
} }
await inventory.save(); await inventory.save();
res.json({ InventoryChanges }); res.json({ InventoryChanges, BrandedSuits });
} }
}; };

View File

@ -0,0 +1,28 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
import { Types } from "mongoose";
export const crewMembersController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "CrewMembers");
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
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();
res.json({
crewMemberId: data.crewMember.ItemId.$oid,
NemesisFingerprint: data.crewMember.NemesisFingerprint
});
};
interface ICrewMembersRequest {
crewMember: ICrewMemberClient;
}

View File

@ -12,6 +12,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { getRandomInt } from "@/src/services/rngService"; import { getRandomInt } from "@/src/services/rngService";
import { IFingerprintStat } from "@/src/helpers/rivenHelper"; import { IFingerprintStat } from "@/src/helpers/rivenHelper";
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => { export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -42,22 +43,33 @@ export const crewShipIdentifySalvageController: RequestHandler = async (req, res
); );
} else { } else {
const meta = ExportRailjackWeapons[payload.ItemType]; const meta = ExportRailjackWeapons[payload.ItemType];
const upgradeType = meta.defaultUpgrades![0].ItemType; let defaultOverwrites: Partial<IEquipmentDatabase> | undefined;
const upgradeMeta = ExportUpgrades[upgradeType]; if (meta.defaultUpgrades?.[0]) {
const buffs: IFingerprintStat[] = []; const upgradeType = meta.defaultUpgrades[0].ItemType;
for (const buff of upgradeMeta.upgradeEntries!) { const upgradeMeta = ExportUpgrades[upgradeType];
buffs.push({ const buffs: IFingerprintStat[] = [];
Tag: buff.tag, for (const buff of upgradeMeta.upgradeEntries!) {
Value: Math.trunc(Math.random() * 0x40000000) buffs.push({
}); Tag: buff.tag,
Value: Math.trunc(Math.random() * 0x40000000)
});
}
defaultOverwrites = {
UpgradeType: upgradeType,
UpgradeFingerprint: JSON.stringify({
compat: payload.ItemType,
buffs
} satisfies IInnateDamageFingerprint)
};
} }
addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, undefined, inventoryChanges, { addEquipment(
UpgradeType: upgradeType, inventory,
UpgradeFingerprint: JSON.stringify({ "CrewShipSalvagedWeapons",
compat: payload.ItemType, payload.ItemType,
buffs undefined,
} satisfies IInnateDamageFingerprint) inventoryChanges,
}); defaultOverwrites
);
} }
inventoryChanges.CrewShipRawSalvage = [ inventoryChanges.CrewShipRawSalvage = [

View File

@ -1,16 +1,20 @@
import { toOid } from "@/src/helpers/inventoryHelpers";
import { Account, Ignore } from "@/src/models/loginModel";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IFriendInfo } from "@/src/types/guildTypes";
import { parallelForeach } from "@/src/utils/async-utils";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
const getIgnoredUsersController: RequestHandler = (_req, res) => { export const getIgnoredUsersController: RequestHandler = async (req, res) => {
res.writeHead(200, { const accountId = await getAccountIdForRequest(req);
"Content-Type": "text/html", const ignores = await Ignore.find({ ignorer: accountId });
"Content-Length": "3" const ignoredUsers: IFriendInfo[] = [];
await parallelForeach(ignores, async ignore => {
const ignoreeAccount = (await Account.findById(ignore.ignoree, "DisplayName"))!;
ignoredUsers.push({
_id: toOid(ignore.ignoree),
DisplayName: ignoreeAccount.DisplayName + ""
});
}); });
res.end( res.json({ IgnoredUsers: ignoredUsers });
Buffer.from([
0x7b, 0x22, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x38, 0x33, 0x30, 0x34, 0x30, 0x37, 0x37, 0x32, 0x32,
0x34, 0x30, 0x32, 0x32, 0x32, 0x36, 0x31, 0x35, 0x30, 0x31, 0x7d
])
);
}; };
export { getIgnoredUsersController };

View File

@ -97,6 +97,19 @@ export const guildTechController: RequestHandler = async (req, res) => {
res.end(); res.end();
} else { } else {
const recipe = ExportDojoRecipes.research[data.RecipeType]; const recipe = ExportDojoRecipes.research[data.RecipeType];
if (data.TechProductCategory) {
if (
data.TechProductCategory != "CrewShipWeapons" &&
data.TechProductCategory != "CrewShipWeaponSkins"
) {
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
}
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId)) {
throw new Error(
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
);
}
}
const techProject = const techProject =
inventory.PersonalTechProjects[ inventory.PersonalTechProjects[
inventory.PersonalTechProjects.push({ inventory.PersonalTechProjects.push({
@ -347,6 +360,22 @@ export const guildTechController: RequestHandler = async (req, res) => {
res.json({ res.json({
inventoryChanges: inventoryChanges inventoryChanges: inventoryChanges
}); });
} else if (data.Action == "InstantFinish") {
if (data.TechProductCategory != "CrewShipWeapons" && data.TechProductCategory != "CrewShipWeaponSkins") {
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
}
const inventoryChanges = finishComponentRepair(inventory, data.TechProductCategory, data.CategoryItemId!);
inventoryChanges.MiscItems = [
{
ItemType: "/Lotus/Types/Items/MiscItems/InstantSalvageRepairItem",
ItemCount: -1
}
];
addMiscItems(inventory, inventoryChanges.MiscItems);
await inventory.save();
res.json({
inventoryChanges: inventoryChanges
});
} else { } else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`); logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unhandled guildTech request`); throw new Error(`unhandled guildTech request`);
@ -359,7 +388,7 @@ type TGuildTechRequest =
| IGuildTechContributeRequest; | IGuildTechContributeRequest;
interface IGuildTechBasicRequest { interface IGuildTechBasicRequest {
Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush"; Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush" | "InstantFinish";
Mode: "Guild" | "Personal"; Mode: "Guild" | "Personal";
RecipeType: string; RecipeType: string;
TechProductCategory?: string; TechProductCategory?: string;
@ -380,6 +409,12 @@ interface IGuildTechContributeRequest {
VaultMiscItems: IMiscItem[]; VaultMiscItems: IMiscItem[];
} }
const getSalvageCategory = (
category: "CrewShipWeapons" | "CrewShipWeaponSkins"
): "CrewShipSalvagedWeapons" | "CrewShipSalvagedWeaponSkins" => {
return category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
};
const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => { const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
// delete personal tech project // delete personal tech project
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId)); const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId));
@ -387,11 +422,19 @@ const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: s
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1); inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
const category = personalTechProject.ProductCategory! as "CrewShipWeapons" | "CrewShipWeaponSkins"; const category = personalTechProject.ProductCategory! as "CrewShipWeapons" | "CrewShipWeaponSkins";
const salvageCategory = category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins"; return finishComponentRepair(inventory, category, itemId);
};
const finishComponentRepair = (
inventory: TInventoryDatabaseDocument,
category: "CrewShipWeapons" | "CrewShipWeaponSkins",
itemId: string
): IInventoryChanges => {
const salvageCategory = getSalvageCategory(category);
// find salved part & delete it // find salved part & delete it
const salvageIndex = inventory[salvageCategory].findIndex(x => x._id.equals(itemId)); const salvageIndex = inventory[salvageCategory].findIndex(x => x._id.equals(itemId));
const salvageItem = inventory[category][salvageIndex]; const salvageItem = inventory[salvageCategory][salvageIndex];
inventory[salvageCategory].splice(salvageIndex, 1); inventory[salvageCategory].splice(salvageIndex, 1);
// add final item // add final item

View File

@ -53,6 +53,9 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
logger.debug("mission report:", missionReport); logger.debug("mission report:", missionReport);
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const firstCompletion = missionReport.SortieId
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
: false;
const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport); const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
if ( if (
@ -69,7 +72,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
} }
const { MissionRewards, inventoryChanges, credits, AffiliationMods, SyndicateXPItemReward } = const { MissionRewards, inventoryChanges, credits, AffiliationMods, SyndicateXPItemReward } =
await addMissionRewards(inventory, missionReport); await addMissionRewards(inventory, missionReport, firstCompletion);
await inventory.save(); await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true); const inventoryResponse = await getInventoryResponse(inventory, true);

View File

@ -17,7 +17,7 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper"; import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { getRandomInt } from "@/src/services/rngService"; import { getRandomInt } from "@/src/services/rngService";
import { ExportSentinels } from "warframe-public-export-plus"; import { ExportSentinels, IDefaultUpgrade } from "warframe-public-export-plus";
import { Status } from "@/src/types/inventoryTypes/inventoryTypes"; import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
interface IModularCraftRequest { interface IModularCraftRequest {
@ -34,10 +34,8 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
const category = modularWeaponTypes[data.WeaponType]; const category = modularWeaponTypes[data.WeaponType];
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const defaultUpgrades = getDefaultUpgrades(data.Parts); let defaultUpgrades: IDefaultUpgrade[] | undefined;
const defaultOverwrites: Partial<IEquipmentDatabase> = { const defaultOverwrites: Partial<IEquipmentDatabase> = {};
Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
};
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
if (category == "KubrowPets") { if (category == "KubrowPets") {
const traits = { const traits = {
@ -129,10 +127,17 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
// Only save mutagen & antigen in the ModularParts. // Only save mutagen & antigen in the ModularParts.
defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]]; defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
for (const specialItem of ExportSentinels[data.WeaponType].exalted!) { const meta = ExportSentinels[data.WeaponType];
for (const specialItem of meta.exalted!) {
addSpecialItem(inventory, specialItem, inventoryChanges); addSpecialItem(inventory, specialItem, inventoryChanges);
} }
defaultUpgrades = meta.defaultUpgrades;
} else {
defaultUpgrades = getDefaultUpgrades(data.Parts);
} }
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites); addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false)); combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
if (defaultUpgrades) { if (defaultUpgrades) {

View File

@ -0,0 +1,21 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Account, Ignore } from "@/src/models/loginModel";
import { getAccountForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const removeIgnoredUserController: RequestHandler = async (req, res) => {
const accountId = await getAccountForRequest(req);
const data = getJSONfromString<IRemoveIgnoredUserRequest>(String(req.body));
const ignoreeAccount = await Account.findOne(
{ DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
"_id"
);
if (ignoreeAccount) {
await Ignore.deleteOne({ ignorer: accountId, ignoree: ignoreeAccount._id });
}
res.end();
};
interface IRemoveIgnoredUserRequest {
playerName: string;
}

View File

@ -1,6 +1,6 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { Account } from "@/src/models/loginModel"; import { Account, Ignore } from "@/src/models/loginModel";
import { Inbox } from "@/src/models/inboxModel"; import { Inbox } from "@/src/models/inboxModel";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
@ -23,6 +23,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
await Promise.all([ await Promise.all([
Account.deleteOne({ _id: accountId }), Account.deleteOne({ _id: accountId }),
GuildMember.deleteMany({ accountId: accountId }), GuildMember.deleteMany({ accountId: accountId }),
Ignore.deleteMany({ ignorer: accountId }),
Ignore.deleteMany({ ignoree: accountId }),
Inbox.deleteMany({ ownerId: accountId }), Inbox.deleteMany({ ownerId: accountId }),
Inventory.deleteOne({ accountOwnerId: accountId }), Inventory.deleteOne({ accountOwnerId: accountId }),
Leaderboard.deleteMany({ ownerId: accountId }), Leaderboard.deleteMany({ ownerId: accountId }),

View File

@ -39,10 +39,9 @@ import {
ILoreFragmentScan, ILoreFragmentScan,
IEvolutionProgress, IEvolutionProgress,
IEndlessXpProgress, IEndlessXpProgress,
ICrewShipPortGuns,
ICrewShipCustomization, ICrewShipCustomization,
ICrewShipWeapon, ICrewShipWeapon,
ICrewShipPilotWeapon, ICrewShipWeaponEmplacements,
IShipExterior, IShipExterior,
IHelminthFoodRecord, IHelminthFoodRecord,
ICrewShipMembersDatabase, ICrewShipMembersDatabase,
@ -88,7 +87,15 @@ import {
IPersonalTechProjectDatabase, IPersonalTechProjectDatabase,
IPersonalTechProjectClient, IPersonalTechProjectClient,
ILastSortieRewardDatabase, ILastSortieRewardDatabase,
ILastSortieRewardClient ILastSortieRewardClient,
ICrewMemberSkill,
ICrewMemberSkillEfficiency,
ICrewMemberDatabase,
ICrewMemberClient,
ISortieRewardAttenuation,
IInvasionProgressDatabase,
IInvasionProgressClient,
IAccolades
} from "../../types/inventoryTypes/inventoryTypes"; } from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes"; import { IOid } from "../../types/commonTypes";
import { import {
@ -102,7 +109,7 @@ import {
IEquipmentClient IEquipmentClient
} from "@/src/types/inventoryTypes/commonInventoryTypes"; } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { EquipmentSelectionSchema } from "./loadoutModel"; import { EquipmentSelectionSchema, oidSchema } from "./loadoutModel";
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false }); export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
@ -294,6 +301,55 @@ upgradeSchema.set("toJSON", {
} }
}); });
const crewMemberSkillSchema = new Schema<ICrewMemberSkill>(
{
Assigned: Number
},
{ _id: false }
);
const crewMemberSkillEfficiencySchema = new Schema<ICrewMemberSkillEfficiency>(
{
PILOTING: crewMemberSkillSchema,
GUNNERY: crewMemberSkillSchema,
ENGINEERING: crewMemberSkillSchema,
COMBAT: crewMemberSkillSchema,
SURVIVABILITY: crewMemberSkillSchema
},
{ _id: false }
);
const crewMemberSchema = new Schema<ICrewMemberDatabase>(
{
ItemType: { type: String, required: true },
NemesisFingerprint: { type: BigInt, default: 0n },
Seed: { type: BigInt, default: 0n },
AssignedRole: Number,
SkillEfficiency: crewMemberSkillEfficiencySchema,
WeaponConfigIdx: Number,
WeaponId: { type: Schema.Types.ObjectId, default: "000000000000000000000000" },
XP: { type: Number, default: 0 },
PowersuitType: { type: String, required: true },
Configs: [ItemConfigSchema],
SecondInCommand: { type: Boolean, default: false }
},
{ id: false }
);
crewMemberSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
const db = obj as ICrewMemberDatabase;
const client = obj as ICrewMemberClient;
client.WeaponId = toOid(db.WeaponId);
client.ItemId = toOid(db._id);
delete obj._id;
delete obj.__v;
}
});
const slotsBinSchema = new Schema<ISlots>( const slotsBinSchema = new Schema<ISlots>(
{ {
Slots: Number, Slots: Number,
@ -631,6 +687,27 @@ questKeysSchema.set("toJSON", {
export const fusionTreasuresSchema = new Schema<IFusionTreasure>().add(typeCountSchema).add({ Sockets: Number }); export const fusionTreasuresSchema = new Schema<IFusionTreasure>().add(typeCountSchema).add({ Sockets: Number });
const invasionProgressSchema = new Schema<IInvasionProgressDatabase>(
{
invasionId: Schema.Types.ObjectId,
Delta: Number,
AttackerScore: Number,
DefenderScore: Number
},
{ _id: false }
);
invasionProgressSchema.set("toJSON", {
transform(_doc, obj) {
const db = obj as IInvasionProgressDatabase;
const client = obj as IInvasionProgressClient;
client._id = toOid(db.invasionId);
delete obj.invasionId;
delete obj.__v;
}
});
const spectreLoadoutsSchema = new Schema<ISpectreLoadout>( const spectreLoadoutsSchema = new Schema<ISpectreLoadout>(
{ {
ItemType: String, ItemType: String,
@ -719,25 +796,23 @@ const endlessXpProgressSchema = new Schema<IEndlessXpProgress>(
{ _id: false } { _id: false }
); );
const crewShipPilotWeaponSchema = new Schema<ICrewShipPilotWeapon>( const crewShipWeaponEmplacementsSchema = new Schema<ICrewShipWeaponEmplacements>(
{ {
PRIMARY_A: EquipmentSelectionSchema, PRIMARY_A: EquipmentSelectionSchema,
SECONDARY_A: EquipmentSelectionSchema PRIMARY_B: EquipmentSelectionSchema,
}, SECONDARY_A: EquipmentSelectionSchema,
{ _id: false } SECONDARY_B: EquipmentSelectionSchema
);
const crewShipPortGunsSchema = new Schema<ICrewShipPortGuns>(
{
PRIMARY_A: EquipmentSelectionSchema
}, },
{ _id: false } { _id: false }
); );
const crewShipWeaponSchema = new Schema<ICrewShipWeapon>( const crewShipWeaponSchema = new Schema<ICrewShipWeapon>(
{ {
PILOT: crewShipPilotWeaponSchema, PILOT: crewShipWeaponEmplacementsSchema,
PORT_GUNS: crewShipPortGunsSchema PORT_GUNS: crewShipWeaponEmplacementsSchema,
STARBOARD_GUNS: crewShipWeaponEmplacementsSchema,
ARTILLERY: crewShipWeaponEmplacementsSchema,
SCANNER: crewShipWeaponEmplacementsSchema
}, },
{ _id: false } { _id: false }
); );
@ -761,7 +836,7 @@ const crewShipCustomizationSchema = new Schema<ICrewShipCustomization>(
const crewShipMemberSchema = new Schema<ICrewShipMemberDatabase>( const crewShipMemberSchema = new Schema<ICrewShipMemberDatabase>(
{ {
ItemId: { type: Schema.Types.ObjectId, required: false }, ItemId: { type: Schema.Types.ObjectId, required: false },
NemesisFingerprint: { type: Number, required: false } NemesisFingerprint: { type: BigInt, required: false }
}, },
{ _id: false } { _id: false }
); );
@ -985,6 +1060,13 @@ pendingRecipeSchema.set("toJSON", {
} }
}); });
const accoladesSchema = new Schema<IAccolades>(
{
Heirloom: Boolean
},
{ _id: false }
);
const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>( const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>(
{ {
Name: String, Name: String,
@ -1226,6 +1308,14 @@ lastSortieRewardSchema.set("toJSON", {
} }
}); });
const sortieRewardAttenutationSchema = new Schema<ISortieRewardAttenuation>(
{
Tag: String,
Atten: Number
},
{ _id: false }
);
const lockedWeaponGroupSchema = new Schema<ILockedWeaponGroupDatabase>( const lockedWeaponGroupSchema = new Schema<ILockedWeaponGroupDatabase>(
{ {
s: Schema.Types.ObjectId, s: Schema.Types.ObjectId,
@ -1363,7 +1453,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
CrewShipSalvagedWeaponSkins: [upgradeSchema], CrewShipSalvagedWeaponSkins: [upgradeSchema],
//RailJack Crew //RailJack Crew
CrewMembers: [Schema.Types.Mixed], CrewMembers: [crewMemberSchema],
//Complete Mission\Quests //Complete Mission\Quests
Missions: [missionSchema], Missions: [missionSchema],
@ -1384,6 +1474,16 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//Mastery Rank next availability //Mastery Rank next availability
TrainingDate: { type: Date, default: new Date(0) }, TrainingDate: { type: Date, default: new Date(0) },
//Accolades
Staff: Boolean,
Founder: Number,
Guide: Number,
Moderator: Boolean,
Partner: Boolean,
Accolades: accoladesSchema,
//Not an accolade but unlocks an extra chat
Counselor: Boolean,
//you saw last played Region when you opened the star map //you saw last played Region when you opened the star map
LastRegionPlayed: String, LastRegionPlayed: String,
@ -1423,7 +1523,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
SentientSpawnChanceBoosters: Schema.Types.Mixed, SentientSpawnChanceBoosters: Schema.Types.Mixed,
QualifyingInvasions: [Schema.Types.Mixed], QualifyingInvasions: [invasionProgressSchema],
FactionScores: [Number], FactionScores: [Number],
// https://warframe.fandom.com/wiki/Specter_(Tenno) // https://warframe.fandom.com/wiki/Specter_(Tenno)
@ -1445,6 +1545,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
CompletedSorties: [String], CompletedSorties: [String],
LastSortieReward: { type: [lastSortieRewardSchema], default: undefined }, LastSortieReward: { type: [lastSortieRewardSchema], default: undefined },
LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined }, LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined },
SortieRewardAttenuation: { type: [sortieRewardAttenutationSchema], default: undefined },
// Resource Extractor Drones // Resource Extractor Drones
Drones: [droneSchema], Drones: [droneSchema],
@ -1538,7 +1639,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
HasContributedToDojo: Boolean, HasContributedToDojo: Boolean,
HWIDProtectEnabled: Boolean, HWIDProtectEnabled: Boolean,
LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" }, LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" },
CurrentLoadOutIds: [Schema.Types.Mixed], CurrentLoadOutIds: [oidSchema],
RandomUpgradesIdentified: Number, RandomUpgradesIdentified: Number,
BountyScore: Number, BountyScore: Number,
ChallengeInstanceStates: [Schema.Types.Mixed], ChallengeInstanceStates: [Schema.Types.Mixed],
@ -1645,6 +1746,7 @@ export type InventoryDocumentProps = {
CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>; CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>; CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>; PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> }; } & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
// eslint-disable-next-line @typescript-eslint/no-empty-object-type // eslint-disable-next-line @typescript-eslint/no-empty-object-type

View File

@ -3,7 +3,7 @@ import { IEquipmentSelection } from "@/src/types/inventoryTypes/commonInventoryT
import { ILoadoutConfigDatabase, ILoadoutDatabase } from "@/src/types/saveLoadoutTypes"; import { ILoadoutConfigDatabase, ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
import { Document, Model, Schema, Types, model } from "mongoose"; import { Document, Model, Schema, Types, model } from "mongoose";
const oidSchema = new Schema<IOid>( export const oidSchema = new Schema<IOid>(
{ {
$oid: String $oid: String
}, },

View File

@ -1,4 +1,4 @@
import { IDatabaseAccountJson } from "@/src/types/loginTypes"; import { IDatabaseAccountJson, IIgnore } from "@/src/types/loginTypes";
import { model, Schema, SchemaOptions } from "mongoose"; import { model, Schema, SchemaOptions } from "mongoose";
const opts = { const opts = {
@ -37,3 +37,13 @@ databaseAccountSchema.set("toJSON", {
}); });
export const Account = model<IDatabaseAccountJson>("Account", databaseAccountSchema); export const Account = model<IDatabaseAccountJson>("Account", databaseAccountSchema);
const ignoreSchema = new Schema<IIgnore>({
ignorer: Schema.Types.ObjectId,
ignoree: Schema.Types.ObjectId
});
ignoreSchema.index({ ignorer: 1 });
ignoreSchema.index({ ignorer: 1, ignoree: 1 }, { unique: true });
export const Ignore = model<IIgnore>("Ignore", ignoreSchema);

View File

@ -4,6 +4,7 @@ import { abortDojoComponentController } from "@/src/controllers/api/abortDojoCom
import { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController"; import { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController";
import { activateRandomModController } from "@/src/controllers/api/activateRandomModController"; import { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
import { addFriendImageController } from "@/src/controllers/api/addFriendImageController"; import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserController";
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController"; import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
import { addToGuildController } from "@/src/controllers/api/addToGuildController"; import { addToGuildController } from "@/src/controllers/api/addToGuildController";
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController"; import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
@ -27,6 +28,7 @@ import { contributeToVaultController } from "@/src/controllers/api/contributeToV
import { createAllianceController } from "@/src/controllers/api/createAllianceController"; import { createAllianceController } from "@/src/controllers/api/createAllianceController";
import { createGuildController } from "@/src/controllers/api/createGuildController"; import { createGuildController } from "@/src/controllers/api/createGuildController";
import { creditsController } from "@/src/controllers/api/creditsController"; import { creditsController } from "@/src/controllers/api/creditsController";
import { crewMembersController } from "@/src/controllers/api/crewMembersController";
import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController"; import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController"; import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController"; import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
@ -97,6 +99,7 @@ import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCode
import { releasePetController } from "@/src/controllers/api/releasePetController"; import { releasePetController } from "@/src/controllers/api/releasePetController";
import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController"; import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController"; import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController"; import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController"; import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController"; import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
@ -202,6 +205,7 @@ apiRouter.get("/updateSession.php", updateSessionGetController);
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController); apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
apiRouter.post("/activateRandomMod.php", activateRandomModController); apiRouter.post("/activateRandomMod.php", activateRandomModController);
apiRouter.post("/addFriendImage.php", addFriendImageController); apiRouter.post("/addFriendImage.php", addFriendImageController);
apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
apiRouter.post("/addToAlliance.php", addToAllianceController); apiRouter.post("/addToAlliance.php", addToAllianceController);
apiRouter.post("/addToGuild.php", addToGuildController); apiRouter.post("/addToGuild.php", addToGuildController);
apiRouter.post("/arcaneCommon.php", arcaneCommonController); apiRouter.post("/arcaneCommon.php", arcaneCommonController);
@ -219,6 +223,7 @@ apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentContro
apiRouter.post("/contributeToVault.php", contributeToVaultController); apiRouter.post("/contributeToVault.php", contributeToVaultController);
apiRouter.post("/createAlliance.php", createAllianceController); apiRouter.post("/createAlliance.php", createAllianceController);
apiRouter.post("/createGuild.php", createGuildController); apiRouter.post("/createGuild.php", createGuildController);
apiRouter.post("/crewMembers.php", crewMembersController);
apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController); apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController); apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController); apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
@ -266,6 +271,7 @@ apiRouter.post("/purchase.php", purchaseController);
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController); apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
apiRouter.post("/releasePet.php", releasePetController); apiRouter.post("/releasePet.php", releasePetController);
apiRouter.post("/removeFromGuild.php", removeFromGuildController); apiRouter.post("/removeFromGuild.php", removeFromGuildController);
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
apiRouter.post("/rerollRandomMod.php", rerollRandomModController); apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController); apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
apiRouter.post("/saveDialogue.php", saveDialogueController); apiRouter.post("/saveDialogue.php", saveDialogueController);

View File

@ -59,6 +59,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
const members: IGuildMemberClient[] = []; const members: IGuildMemberClient[] = [];
let missingEntry = true; let missingEntry = true;
const dataFillInPromises: Promise<void>[] = [];
for (const guildMember of guildMembers) { for (const guildMember of guildMembers) {
const member: IGuildMemberClient = { const member: IGuildMemberClient = {
_id: toOid(guildMember.accountId), _id: toOid(guildMember.accountId),
@ -70,8 +71,12 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
if (guildMember.accountId.equals(accountId)) { if (guildMember.accountId.equals(accountId)) {
missingEntry = false; missingEntry = false;
} else { } else {
member.DisplayName = (await Account.findById(guildMember.accountId, "DisplayName"))!.DisplayName; dataFillInPromises.push(
await fillInInventoryDataForGuildMember(member); (async (): Promise<void> => {
member.DisplayName = (await Account.findById(guildMember.accountId, "DisplayName"))!.DisplayName;
})()
);
dataFillInPromises.push(fillInInventoryDataForGuildMember(member));
} }
members.push(member); members.push(member);
} }
@ -90,6 +95,8 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
}); });
} }
await Promise.all(dataFillInPromises);
return { return {
_id: toOid(guild._id), _id: toOid(guild._id),
Name: guild.Name, Name: guild.Name,

View File

@ -104,18 +104,18 @@ const replaceSlots = (db: ISlots, client: ISlots): void => {
db.Slots = client.Slots; db.Slots = client.Slots;
}; };
const convertCrewShipMember = (client: ICrewShipMemberClient): ICrewShipMemberDatabase => { export const importCrewMemberId = (crewMemberId: ICrewShipMemberClient): ICrewShipMemberDatabase => {
return { if (crewMemberId.ItemId) {
...client, return { ItemId: new Types.ObjectId(crewMemberId.ItemId.$oid) };
ItemId: client.ItemId ? new Types.ObjectId(client.ItemId.$oid) : undefined }
}; return { NemesisFingerprint: BigInt(crewMemberId.NemesisFingerprint ?? 0) };
}; };
const convertCrewShipMembers = (client: ICrewShipMembersClient): ICrewShipMembersDatabase => { const convertCrewShipMembers = (client: ICrewShipMembersClient): ICrewShipMembersDatabase => {
return { return {
SLOT_A: client.SLOT_A ? convertCrewShipMember(client.SLOT_A) : undefined, SLOT_A: client.SLOT_A ? importCrewMemberId(client.SLOT_A) : undefined,
SLOT_B: client.SLOT_B ? convertCrewShipMember(client.SLOT_B) : undefined, SLOT_B: client.SLOT_B ? importCrewMemberId(client.SLOT_B) : undefined,
SLOT_C: client.SLOT_C ? convertCrewShipMember(client.SLOT_C) : undefined SLOT_C: client.SLOT_C ? importCrewMemberId(client.SLOT_C) : undefined
}; };
}; };
@ -230,17 +230,23 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
replaceSlots(db[key], client[key]); replaceSlots(db[key], client[key]);
} }
} }
// boolean
for (const key of [ for (const key of [
"UseAdultOperatorLoadout", "UseAdultOperatorLoadout",
"HasOwnedVoidProjectionsPreviously", "HasOwnedVoidProjectionsPreviously",
"ReceivedStartingGear", "ReceivedStartingGear",
"ArchwingEnabled", "ArchwingEnabled",
"PlayedParkourTutorial" "PlayedParkourTutorial",
"Staff",
"Moderator",
"Partner",
"Counselor"
] as const) { ] as const) {
if (client[key] !== undefined) { if (client[key] !== undefined) {
db[key] = client[key]; db[key] = client[key];
} }
} }
// number
for (const key of [ for (const key of [
"PlayerLevel", "PlayerLevel",
"RegularCredits", "RegularCredits",
@ -250,12 +256,15 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"PrimeTokens", "PrimeTokens",
"TradesRemaining", "TradesRemaining",
"GiftsRemaining", "GiftsRemaining",
"ChallengesFixVersion" "ChallengesFixVersion",
"Founder",
"Guide"
] as const) { ] as const) {
if (client[key] !== undefined) { if (client[key] !== undefined) {
db[key] = client[key]; db[key] = client[key];
} }
} }
// string
for (const key of [ for (const key of [
"ThemeStyle", "ThemeStyle",
"ThemeBackground", "ThemeBackground",
@ -270,6 +279,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
db[key] = client[key]; db[key] = client[key];
} }
} }
// string[]
for (const key of [ for (const key of [
"EquippedGear", "EquippedGear",
"EquippedEmotes", "EquippedEmotes",
@ -380,6 +390,9 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
}); });
}); });
} }
if (client.Accolades !== undefined) {
db.Accolades = client.Accolades;
}
}; };
const convertLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => { const convertLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => {

View File

@ -22,7 +22,8 @@ import {
IDroneClient, IDroneClient,
IUpgradeClient, IUpgradeClient,
TPartialStartingGear, TPartialStartingGear,
ILoreFragmentScan ILoreFragmentScan,
ICrewMemberClient
} 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";
@ -713,6 +714,15 @@ export const addItem = async (
return { return {
MiscItems: miscItemChanges MiscItems: miscItemChanges
}; };
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
if (!seed) {
throw new Error(`Expected crew member to have a seed`);
}
seed |= 0x33b81en << 32n;
return {
...addCrewMember(inventory, typeName, seed),
...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase)
};
} else if (typeName == "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness") { } else if (typeName == "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness") {
return addCrewShipHarness(inventory, typeName); return addCrewShipHarness(inventory, typeName);
} }
@ -1212,6 +1222,78 @@ const addDrone = (
return inventoryChanges; return inventoryChanges;
}; };
/*const getCrewMemberSkills = (seed: bigint, skillPointsToAssign: number): Record<string, number> => {
const rng = new SRng(seed);
const skills = ["PILOTING", "GUNNERY", "ENGINEERING", "COMBAT", "SURVIVABILITY"];
for (let i = 1; i != 5; ++i) {
const swapIndex = rng.randomInt(0, i);
if (swapIndex != i) {
const tmp = skills[i];
skills[i] = skills[swapIndex];
skills[swapIndex] = tmp;
}
}
rng.randomFloat(); // unused afaict
const skillAssignments = [0, 0, 0, 0, 0];
for (let skill = 0; skillPointsToAssign; skill = (skill + 1) % 5) {
const maxIncrease = Math.min(5 - skillAssignments[skill], skillPointsToAssign);
const increase = rng.randomInt(0, maxIncrease);
skillAssignments[skill] += increase;
skillPointsToAssign -= increase;
}
skillAssignments.sort((a, b) => b - a);
const combined: Record<string, number> = {};
for (let i = 0; i != 5; ++i) {
combined[skills[i]] = skillAssignments[i];
}
return combined;
};*/
const addCrewMember = (
inventory: TInventoryDatabaseDocument,
itemType: string,
seed: bigint,
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
// SkillEfficiency is additional to the base stats, so we don't need to compute this
//const skillPointsToAssign = itemType.endsWith("Strong") ? 12 : itemType.indexOf("Medium") != -1 ? 10 : 8;
//const skills = getCrewMemberSkills(seed, skillPointsToAssign);
// Arbiters = male
// CephalonSuda = female
// NewLoka = female
// Perrin = male
// RedVeil = male
// SteelMeridian = female
const powersuitType =
itemType.indexOf("Arbiters") != -1 || itemType.indexOf("Perrin") != -1 || itemType.indexOf("RedVeil") != -1
? "/Lotus/Powersuits/NpcPowersuits/CrewMemberMaleSuit"
: "/Lotus/Powersuits/NpcPowersuits/CrewMemberFemaleSuit";
const index =
inventory.CrewMembers.push({
ItemType: itemType,
NemesisFingerprint: 0n,
Seed: seed,
SkillEfficiency: {
PILOTING: { Assigned: 0 },
GUNNERY: { Assigned: 0 },
ENGINEERING: { Assigned: 0 },
COMBAT: { Assigned: 0 },
SURVIVABILITY: { Assigned: 0 }
},
PowersuitType: powersuitType
}) - 1;
inventoryChanges.CrewMembers ??= [];
inventoryChanges.CrewMembers.push(inventory.CrewMembers[index].toJSON<ICrewMemberClient>());
return inventoryChanges;
};
export const addEmailItem = async ( export const addEmailItem = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
typeName: string, typeName: string,

View File

@ -53,7 +53,7 @@ import conservationAnimals from "@/static/fixed_responses/conservationAnimals.js
import { getInfNodes } from "@/src/helpers/nemesisHelpers"; import { getInfNodes } 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 { getWorldState } from "./worldStateService"; import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService";
import { config } from "./configService"; import { config } from "./configService";
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => { const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
@ -71,7 +71,12 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
return [rewardInfo.rewardTier]; return [rewardInfo.rewardTier];
} }
const rotationCount = rewardInfo.rewardQualifications?.length || 0; // Aborting a railjack mission should not give any rewards (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741)
if (rewardInfo.rewardQualifications === undefined) {
return [];
}
const rotationCount = rewardInfo.rewardQualifications.length || 0;
if (rotationCount === 0) return [0]; if (rotationCount === 0) return [0];
const rotationPattern = const rotationPattern =
@ -128,11 +133,18 @@ 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 ( if (
inventoryUpdates.MissionFailed && inventoryUpdates.MissionFailed &&
inventoryUpdates.MissionStatus == "GS_FAILURE" && inventoryUpdates.MissionStatus == "GS_FAILURE" &&
inventoryUpdates.ObjectiveReached && inventoryUpdates.ObjectiveReached &&
!inventoryUpdates.LockedWeaponGroup !inventoryUpdates.LockedWeaponGroup &&
!inventory.LockedWeaponGroup &&
!inventoryUpdates.LevelKeyName
) { ) {
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!; const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!; const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!;
@ -407,7 +419,9 @@ export const addMissionInventoryUpdates = async (
break; break;
} }
case "SortieId": { case "SortieId": {
inventory.CompletedSorties.push(value); if (inventory.CompletedSorties.indexOf(value) == -1) {
inventory.CompletedSorties.push(value);
}
break; break;
} }
case "SeasonChallengeCompletions": { case "SeasonChallengeCompletions": {
@ -525,6 +539,26 @@ export const addMissionInventoryUpdates = async (
inventoryChanges.RegularCredits -= value; inventoryChanges.RegularCredits -= value;
break; break;
} }
case "InvasionProgress": {
for (const clientProgress of value) {
const dbProgress = inventory.QualifyingInvasions.find(x =>
x.invasionId.equals(clientProgress._id.$oid)
);
if (dbProgress) {
dbProgress.Delta += clientProgress.Delta;
dbProgress.AttackerScore += clientProgress.AttackerScore;
dbProgress.DefenderScore += clientProgress.DefenderScore;
} else {
inventory.QualifyingInvasions.push({
invasionId: new Types.ObjectId(clientProgress._id.$oid),
Delta: clientProgress.Delta,
AttackerScore: clientProgress.AttackerScore,
DefenderScore: clientProgress.DefenderScore
});
}
}
break;
}
default: default:
// Equipment XP updates // Equipment XP updates
if (equipmentKeys.includes(key as TEquipmentKey)) { if (equipmentKeys.includes(key as TEquipmentKey)) {
@ -564,7 +598,8 @@ export const addMissionRewards = async (
RegularCredits: creditDrops, RegularCredits: creditDrops,
VoidTearParticipantsCurrWave: voidTearWave, VoidTearParticipantsCurrWave: voidTearWave,
StrippedItems: strippedItems StrippedItems: strippedItems
}: IMissionInventoryUpdateRequest }: IMissionInventoryUpdateRequest,
firstCompletion: boolean
): Promise<AddMissionRewardsReturnType> => { ): Promise<AddMissionRewardsReturnType> => {
if (!rewardInfo) { if (!rewardInfo) {
//TODO: if there is a case where you can have credits collected during a mission but no rewardInfo, add credits needs to be handled earlier //TODO: if there is a case where you can have credits collected during a mission but no rewardInfo, add credits needs to be handled earlier
@ -578,22 +613,12 @@ export const addMissionRewards = async (
} }
//TODO: check double reward merging //TODO: check double reward merging
const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo, wagerTier); const MissionRewards: IMissionReward[] = getRandomMissionDrops(inventory, rewardInfo, wagerTier, firstCompletion);
logger.debug("random mission drops:", MissionRewards); logger.debug("random mission drops:", MissionRewards);
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
const AffiliationMods: IAffiliationMods[] = []; const AffiliationMods: IAffiliationMods[] = [];
let SyndicateXPItemReward; let SyndicateXPItemReward;
if (rewardInfo.sortieTag == "Final") {
inventory.LastSortieReward = [
{
SortieId: new Types.ObjectId(rewardInfo.sortieId!.split("_")[1]),
StoreItem: MissionRewards[0].StoreItem,
Manifest: "/Lotus/Types/Game/MissionDecks/SortieRewards"
}
];
}
let missionCompletionCredits = 0; let missionCompletionCredits = 0;
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
if (levelKeyName) { if (levelKeyName) {
@ -695,6 +720,12 @@ export const addMissionRewards = async (
if (strippedItems) { if (strippedItems) {
for (const si of strippedItems) { for (const si of strippedItems) {
if (si.DropTable == "/Lotus/Types/DropTables/ManInTheWall/MITWGruzzlingArcanesDropTable") {
logger.debug(
`rewriting ${si.DropTable} to /Lotus/Types/DropTables/EntratiLabDropTables/DoppelgangerDropTable`
);
si.DropTable = "/Lotus/Types/DropTables/EntratiLabDropTables/DoppelgangerDropTable";
}
const droptables = ExportEnemies.droptables[si.DropTable] ?? []; const droptables = ExportEnemies.droptables[si.DropTable] ?? [];
if (si.DROP_MOD) { if (si.DROP_MOD) {
const modDroptable = droptables.find(x => x.type == "mod"); const modDroptable = droptables.find(x => x.type == "mod");
@ -951,11 +982,74 @@ function getLevelCreditRewards(node: IRegion): number {
//TODO: get dark sektor fixed credit rewards and railjack bonus //TODO: get dark sektor fixed credit rewards and railjack bonus
} }
function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | undefined): IMissionReward[] { function getRandomMissionDrops(
inventory: TInventoryDatabaseDocument,
RewardInfo: IRewardInfo,
tierOverride: number | undefined,
firstCompletion: boolean
): IMissionReward[] {
const drops: IMissionReward[] = []; const drops: IMissionReward[] = [];
if (RewardInfo.sortieTag == "Final") { if (RewardInfo.sortieTag == "Final" && firstCompletion) {
const drop = getRandomRewardByChance(ExportRewards["/Lotus/Types/Game/MissionDecks/SortieRewards"][0])!; const arr = RewardInfo.sortieId!.split("_");
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount }); let sortieId = arr[1];
if (sortieId == "Lite") {
sortieId = arr[2];
const boss = getLiteSortie(idToWeek(sortieId)).Boss;
let crystalType = {
SORTIE_BOSS_AMAR: "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalAmar",
SORTIE_BOSS_NIRA: "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalNira",
SORTIE_BOSS_BOREAL: "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalBoreal"
}[boss];
const attenTag = {
SORTIE_BOSS_AMAR: "NarmerSortieAmarCrystalRewards",
SORTIE_BOSS_NIRA: "NarmerSortieNiraCrystalRewards",
SORTIE_BOSS_BOREAL: "NarmerSortieBorealCrystalRewards"
}[boss];
const attenIndex = inventory.SortieRewardAttenuation?.findIndex(x => x.Tag == attenTag) ?? -1;
const mythicProbability =
0.2 + (inventory.SortieRewardAttenuation?.find(x => x.Tag == attenTag)?.Atten ?? 0);
if (Math.random() < mythicProbability) {
crystalType += "Mythic";
if (attenIndex != -1) {
inventory.SortieRewardAttenuation!.splice(attenIndex, 1);
}
} else {
if (attenIndex == -1) {
inventory.SortieRewardAttenuation ??= [];
inventory.SortieRewardAttenuation.push({
Tag: attenTag,
Atten: 0.2
});
} else {
inventory.SortieRewardAttenuation![attenIndex].Atten += 0.2;
}
}
drops.push({ StoreItem: crystalType, ItemCount: 1 });
const drop = getRandomRewardByChance(
ExportRewards["/Lotus/Types/Game/MissionDecks/ArchonSortieRewards"][0]
)!;
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
inventory.LastLiteSortieReward = [
{
SortieId: new Types.ObjectId(sortieId),
StoreItem: drop.type,
Manifest: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards"
}
];
} else {
const drop = getRandomRewardByChance(ExportRewards["/Lotus/Types/Game/MissionDecks/SortieRewards"][0])!;
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
inventory.LastSortieReward = [
{
SortieId: new Types.ObjectId(sortieId),
StoreItem: drop.type,
Manifest: "/Lotus/Types/Game/MissionDecks/SortieRewards"
}
];
}
} }
if (RewardInfo.periodicMissionTag?.startsWith("HardDaily")) { if (RewardInfo.periodicMissionTag?.startsWith("HardDaily")) {
drops.push({ drops.push({
@ -965,10 +1059,16 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
} }
if (RewardInfo.node in ExportRegions) { if (RewardInfo.node in ExportRegions) {
const region = ExportRegions[RewardInfo.node]; const region = ExportRegions[RewardInfo.node];
let rewardManifests: string[] = let rewardManifests: string[];
RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB" if (RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB") {
? ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"] rewardManifests = ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"];
: region.rewardManifests; } else if (RewardInfo.invasionId && region.missionIndex == 0) {
// Invasion assassination has Phorid has the boss who should drop Nyx parts
// TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic
rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"];
} else {
rewardManifests = region.rewardManifests;
}
let rotations: number[] = []; let rotations: number[] = [];
if (RewardInfo.jobId) { if (RewardInfo.jobId) {

View File

@ -141,7 +141,8 @@ export const handlePurchase = async (
inventory, inventory,
purchaseRequest.PurchaseParams.Quantity, purchaseRequest.PurchaseParams.Quantity,
undefined, undefined,
undefined, false,
purchaseRequest.PurchaseParams.UsePremium,
seed seed
); );
combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges); combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges);
@ -331,6 +332,7 @@ export const handleStoreItemAcquisition = async (
quantity: number = 1, quantity: number = 1,
durability: TRarity = "COMMON", durability: TRarity = "COMMON",
ignorePurchaseQuantity: boolean = false, ignorePurchaseQuantity: boolean = false,
premiumPurchase: boolean = true,
seed?: bigint seed?: bigint
): Promise<IPurchaseResponse> => { ): Promise<IPurchaseResponse> => {
let purchaseResponse = { let purchaseResponse = {
@ -352,11 +354,20 @@ export const handleStoreItemAcquisition = async (
} }
switch (storeCategory) { switch (storeCategory) {
default: { default: {
purchaseResponse = { InventoryChanges: await addItem(inventory, internalName, quantity, true, seed) }; purchaseResponse = {
InventoryChanges: await addItem(inventory, internalName, quantity, premiumPurchase, seed)
};
break; break;
} }
case "Types": case "Types":
purchaseResponse = await handleTypesPurchase(internalName, inventory, quantity, ignorePurchaseQuantity); purchaseResponse = await handleTypesPurchase(
internalName,
inventory,
quantity,
ignorePurchaseQuantity,
premiumPurchase,
seed
);
break; break;
case "Boosters": case "Boosters":
purchaseResponse = handleBoostersPurchase(storeItemName, inventory, durability); purchaseResponse = handleBoostersPurchase(storeItemName, inventory, durability);
@ -478,13 +489,15 @@ const handleTypesPurchase = async (
typesName: string, typesName: string,
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
quantity: number, quantity: number,
ignorePurchaseQuantity: boolean ignorePurchaseQuantity: boolean,
premiumPurchase: boolean = true,
seed?: bigint
): Promise<IPurchaseResponse> => { ): Promise<IPurchaseResponse> => {
const typeCategory = getStoreItemTypesCategory(typesName); const typeCategory = getStoreItemTypesCategory(typesName);
logger.debug(`type category ${typeCategory}`); logger.debug(`type category ${typeCategory}`);
switch (typeCategory) { switch (typeCategory) {
default: default:
return { InventoryChanges: await addItem(inventory, typesName, quantity) }; return { InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed) };
case "BoosterPacks": case "BoosterPacks":
return handleBoosterPackPurchase(typesName, inventory, quantity); return handleBoosterPackPurchase(typesName, inventory, quantity);
case "SlotItems": case "SlotItems":

View File

@ -216,6 +216,27 @@ const handleQuestCompletion = async (
setupKahlSyndicate(inventory); setupKahlSyndicate(inventory);
} }
// Whispers in the Walls is unlocked once The New + Heart of Deimos are completed.
if (
(questKey == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain" &&
inventory.QuestKeys.find(
x => x.ItemType == "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain"
)?.Completed) ||
(questKey == "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain" &&
inventory.QuestKeys.find(x => x.ItemType == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain")?.Completed)
) {
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Bosses/Loid",
msg: "/Lotus/Language/EntratiLab/EntratiQuest/WiTWQuestRecievedInboxBody",
att: ["/Lotus/Types/Keys/EntratiLab/EntratiQuestKeyChain"],
sub: "/Lotus/Language/EntratiLab/EntratiQuest/WiTWQuestRecievedInboxTitle",
icon: "/Lotus/Interface/Icons/Npcs/Entrati/Loid.png",
highPriority: true
}
]);
}
const questCompletionItems = getQuestCompletionItems(questKey); const questCompletionItems = getQuestCompletionItems(questKey);
logger.debug(`quest completion items`, questCompletionItems); logger.debug(`quest completion items`, questCompletionItems);
if (questCompletionItems) { if (questCompletionItems) {

View File

@ -13,6 +13,8 @@ import { Types } from "mongoose";
import { isEmptyObject } from "@/src/helpers/general"; import { isEmptyObject } from "@/src/helpers/general";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { IItemConfig } from "../types/inventoryTypes/commonInventoryTypes";
import { importCrewMemberId } from "./importService";
//TODO: setup default items on account creation or like originally in giveStartingItems.php //TODO: setup default items on account creation or like originally in giveStartingItems.php
@ -174,8 +176,8 @@ export const handleInventoryItemConfigChange = async (
} }
for (const [configId, config] of Object.entries(itemConfigEntries)) { for (const [configId, config] of Object.entries(itemConfigEntries)) {
if (typeof config !== "boolean") { if (/^[0-9]+$/.test(configId)) {
inventoryItem.Configs[parseInt(configId)] = config; inventoryItem.Configs[parseInt(configId)] = config as IItemConfig;
} }
} }
if ("Favorite" in itemConfigEntries) { if ("Favorite" in itemConfigEntries) {
@ -184,6 +186,26 @@ export const handleInventoryItemConfigChange = async (
if ("IsNew" in itemConfigEntries) { if ("IsNew" in itemConfigEntries) {
inventoryItem.IsNew = itemConfigEntries.IsNew; inventoryItem.IsNew = itemConfigEntries.IsNew;
} }
if ("ItemName" in itemConfigEntries) {
inventoryItem.ItemName = itemConfigEntries.ItemName;
}
if ("RailjackImage" in itemConfigEntries) {
inventoryItem.RailjackImage = itemConfigEntries.RailjackImage;
}
if ("Customization" in itemConfigEntries) {
inventoryItem.Customization = itemConfigEntries.Customization;
}
if ("Weapon" in itemConfigEntries) {
inventoryItem.Weapon = itemConfigEntries.Weapon;
}
if (itemConfigEntries.CrewMembers) {
inventoryItem.CrewMembers = {
SLOT_A: importCrewMemberId(itemConfigEntries.CrewMembers.SLOT_A ?? {}),
SLOT_B: importCrewMemberId(itemConfigEntries.CrewMembers.SLOT_B ?? {}),
SLOT_C: importCrewMemberId(itemConfigEntries.CrewMembers.SLOT_C ?? {})
};
}
} }
break; break;
} else { } else {

View File

@ -1,6 +1,4 @@
import fs from "fs"; import { unixTimesInMs } from "@/src/constants/timeConstants";
import path from "path";
import { repoDir } from "@/src/helpers/pathHelper";
import { CRng, mixSeeds } from "@/src/services/rngService"; import { CRng, mixSeeds } from "@/src/services/rngService";
import { IMongoDate } from "@/src/types/commonTypes"; import { IMongoDate } from "@/src/types/commonTypes";
import { import {
@ -9,49 +7,73 @@ import {
IVendorInfo, IVendorInfo,
IVendorManifestPreprocessed IVendorManifestPreprocessed
} from "@/src/types/vendorTypes"; } from "@/src/types/vendorTypes";
import { JSONParse } from "json-with-bigint";
import { ExportVendors } from "warframe-public-export-plus"; import { ExportVendors } from "warframe-public-export-plus";
import { unixTimesInMs } from "../constants/timeConstants";
const getVendorManifestJson = (name: string): IRawVendorManifest => { import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
return JSONParse(fs.readFileSync(path.join(repoDir, `static/fixed_responses/getVendorInfo/${name}.json`), "utf-8")); import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
}; import DeimosFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosFishmongerVendorManifest.json";
import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json";
import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json";
import DeimosHivemindCommisionsManifestProspector from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json";
import DeimosHivemindCommisionsManifestTokenVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json";
import DeimosHivemindCommisionsManifestWeaponsmith from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json";
import DeimosHivemindTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json";
import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json";
import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json";
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
import GuildAdvertisementVendorManifest from "@/static/fixed_responses/getVendorInfo/GuildAdvertisementVendorManifest.json";
import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json";
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
import OstronFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json";
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json";
import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json";
import SolarisDebtTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorManifest.json";
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json";
import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json";
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
const rawVendorManifests: IRawVendorManifest[] = [ const rawVendorManifests: IRawVendorManifest[] = [
getVendorManifestJson("ArchimedeanVendorManifest"), ArchimedeanVendorManifest,
getVendorManifestJson("DeimosEntratiFragmentVendorProductsManifest"), DeimosEntratiFragmentVendorProductsManifest,
getVendorManifestJson("DeimosFishmongerVendorManifest"), DeimosFishmongerVendorManifest,
getVendorManifestJson("DeimosHivemindCommisionsManifestFishmonger"), DeimosHivemindCommisionsManifestFishmonger,
getVendorManifestJson("DeimosHivemindCommisionsManifestPetVendor"), DeimosHivemindCommisionsManifestPetVendor,
getVendorManifestJson("DeimosHivemindCommisionsManifestProspector"), DeimosHivemindCommisionsManifestProspector,
getVendorManifestJson("DeimosHivemindCommisionsManifestTokenVendor"), DeimosHivemindCommisionsManifestTokenVendor,
getVendorManifestJson("DeimosHivemindCommisionsManifestWeaponsmith"), DeimosHivemindCommisionsManifestWeaponsmith,
getVendorManifestJson("DeimosHivemindTokenVendorManifest"), DeimosHivemindTokenVendorManifest,
getVendorManifestJson("DeimosPetVendorManifest"), DeimosPetVendorManifest,
getVendorManifestJson("DeimosProspectorVendorManifest"), DeimosProspectorVendorManifest,
getVendorManifestJson("DuviriAcrithisVendorManifest"), DuviriAcrithisVendorManifest,
getVendorManifestJson("EntratiLabsEntratiLabsCommisionsManifest"), EntratiLabsEntratiLabsCommisionsManifest,
getVendorManifestJson("EntratiLabsEntratiLabVendorManifest"), EntratiLabsEntratiLabVendorManifest,
getVendorManifestJson("GuildAdvertisementVendorManifest"), // uses preprocessing GuildAdvertisementVendorManifest, // uses preprocessing
getVendorManifestJson("HubsIronwakeDondaVendorManifest"), // uses preprocessing HubsIronwakeDondaVendorManifest, // uses preprocessing
getVendorManifestJson("HubsPerrinSequenceWeaponVendorManifest"), HubsRailjackCrewMemberVendorManifest,
getVendorManifestJson("HubsRailjackCrewMemberVendorManifest"), MaskSalesmanManifest,
getVendorManifestJson("MaskSalesmanManifest"), Nova1999ConquestShopManifest,
getVendorManifestJson("Nova1999ConquestShopManifest"), OstronFishmongerVendorManifest,
getVendorManifestJson("OstronFishmongerVendorManifest"), OstronPetVendorManifest,
getVendorManifestJson("OstronPetVendorManifest"), OstronProspectorVendorManifest,
getVendorManifestJson("OstronProspectorVendorManifest"), RadioLegionIntermission12VendorManifest,
getVendorManifestJson("RadioLegionIntermission12VendorManifest"), SolarisDebtTokenVendorManifest,
getVendorManifestJson("SolarisDebtTokenVendorManifest"), SolarisDebtTokenVendorRepossessionsManifest,
getVendorManifestJson("SolarisDebtTokenVendorRepossessionsManifest"), SolarisFishmongerVendorManifest,
getVendorManifestJson("SolarisFishmongerVendorManifest"), SolarisProspectorVendorManifest,
getVendorManifestJson("SolarisProspectorVendorManifest"), TeshinHardModeVendorManifest, // uses preprocessing
getVendorManifestJson("TeshinHardModeVendorManifest"), // uses preprocessing ZarimanCommisionsManifestArchimedean
getVendorManifestJson("ZarimanCommisionsManifestArchimedean")
]; ];
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> { interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
cycleDuration?: number; cycleStart: number;
cycleDuration: number;
} }
const generatableVendors: IGeneratableVendorInfo[] = [ const generatableVendors: IGeneratableVendorInfo[] = [
@ -62,6 +84,16 @@ const generatableVendors: IGeneratableVendorInfo[] = [
RandomSeedType: "VRST_WEAPON", RandomSeedType: "VRST_WEAPON",
RequiredGoalTag: "", RequiredGoalTag: "",
WeaponUpgradeValueAttenuationExponent: 2.25, WeaponUpgradeValueAttenuationExponent: 2.25,
cycleStart: 1740960000_000,
cycleDuration: 4 * unixTimesInMs.day
},
{
_id: { $oid: "60ad3b6ec96976e97d227e19" },
TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/PerrinSequenceWeaponVendorManifest",
PropertyTextHash: "34F8CF1DFF745F0D67433A5EF0A03E70",
RandomSeedType: "VRST_WEAPON",
WeaponUpgradeValueAttenuationExponent: 2.25,
cycleStart: 1744934400_000,
cycleDuration: 4 * unixTimesInMs.day cycleDuration: 4 * unixTimesInMs.day
} }
// { // {
@ -124,7 +156,7 @@ const preprocessVendorManifest = (originalManifest: IRawVendorManifest): IVendor
const refreshExpiry = (expiry: IMongoDate): number => { const refreshExpiry = (expiry: IMongoDate): number => {
const period = parseInt(expiry.$date.$numberLong); const period = parseInt(expiry.$date.$numberLong);
if (Date.now() >= period) { if (Date.now() >= period) {
const epoch = 1734307200 * 1000; // Monday (for weekly schedules) const epoch = 1734307200_000; // Monday (for weekly schedules)
const iteration = Math.trunc((Date.now() - epoch) / period); const iteration = Math.trunc((Date.now() - epoch) / period);
const start = epoch + iteration * period; const start = epoch + iteration * period;
const end = start + period; const end = start + period;
@ -135,11 +167,11 @@ const refreshExpiry = (expiry: IMongoDate): number => {
}; };
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifestPreprocessed => { const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifestPreprocessed => {
const EPOCH = 1740960000 * 1000; // Monday; aligns with coda weapons 8 day cycle. const EPOCH = vendorInfo.cycleStart;
const manifest = ExportVendors[vendorInfo.TypeName]; const manifest = ExportVendors[vendorInfo.TypeName];
let binThisCycle; let binThisCycle;
if (manifest.isOneBinPerCycle) { if (manifest.isOneBinPerCycle) {
const cycleDuration = vendorInfo.cycleDuration!; // manifest.items[0].durationHours! * 3600_000; const cycleDuration = vendorInfo.cycleDuration; // manifest.items[0].durationHours! * 3600_000;
const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration); const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now. binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
} }
@ -150,7 +182,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
if (manifest.isOneBinPerCycle && rawItem.bin != binThisCycle) { if (manifest.isOneBinPerCycle && rawItem.bin != binThisCycle) {
continue; continue;
} }
const cycleDuration = vendorInfo.cycleDuration!; // rawItem.durationHours! * 3600_000; const cycleDuration = vendorInfo.cycleDuration; // rawItem.durationHours! * 3600_000;
const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration); const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
const cycleStart = EPOCH + cycleIndex * cycleDuration; const cycleStart = EPOCH + cycleIndex * cycleDuration;
const cycleEnd = cycleStart + cycleDuration; const cycleEnd = cycleStart + cycleDuration;
@ -181,10 +213,11 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
items.push(item); items.push(item);
} }
} }
delete vendorInfo.cycleDuration; // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { cycleStart, cycleDuration, ...clientVendorInfo } = vendorInfo;
return { return {
VendorInfo: { VendorInfo: {
...vendorInfo, ...clientVendorInfo,
ItemManifest: items, ItemManifest: items,
Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } } Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } }
} }

View File

@ -1,6 +1,5 @@
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel"; import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
import { import {
IEnemy,
IStatsAdd, IStatsAdd,
IStatsMax, IStatsMax,
IStatsSet, IStatsSet,
@ -137,34 +136,34 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate)
case "HEADSHOT": case "HEADSHOT":
case "KILL_ASSIST": { case "KILL_ASSIST": {
playerStats.Enemies ??= []; playerStats.Enemies ??= [];
const enemyStatKey = { const enemyStatKey = (
KILL_ENEMY: "kills", {
EXECUTE_ENEMY: "executions", KILL_ENEMY: "kills",
HEADSHOT: "headshots", EXECUTE_ENEMY: "executions",
KILL_ASSIST: "assists" HEADSHOT: "headshots",
}[category] as "kills" | "executions" | "headshots" | "assists"; KILL_ASSIST: "assists"
} as const
)[category];
for (const [type, count] of Object.entries(data as IUploadEntry)) { for (const [type, count] of Object.entries(data as IUploadEntry)) {
const enemy = playerStats.Enemies.find(element => element.type === type); let enemy = playerStats.Enemies.find(element => element.type === type);
if (enemy) { if (!enemy) {
if (category === "KILL_ENEMY") { enemy = { type: type };
enemy.kills ??= 0; playerStats.Enemies.push(enemy);
const captureCount = (actionData as IStatsAdd)["CAPTURE_ENEMY"]?.[type]; }
if (captureCount) { if (category === "KILL_ENEMY") {
enemy.kills += Math.max(count - captureCount, 0); enemy.kills ??= 0;
enemy.captures ??= 0; const captureCount = (actionData as IStatsAdd)["CAPTURE_ENEMY"]?.[type];
enemy.captures += captureCount; if (captureCount) {
} else { enemy.kills += Math.max(count - captureCount, 0);
enemy.kills += count; enemy.captures ??= 0;
} enemy.captures += captureCount;
} else { } else {
enemy[enemyStatKey] ??= 0; enemy.kills += count;
enemy[enemyStatKey] += count;
} }
} else { } else {
const newEnemy: IEnemy = { type: type }; enemy[enemyStatKey] ??= 0;
newEnemy[enemyStatKey] = count; enemy[enemyStatKey] += count;
playerStats.Enemies.push(newEnemy);
} }
} }
break; break;

View File

@ -4,7 +4,14 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { CRng } from "@/src/services/rngService"; import { CRng } from "@/src/services/rngService";
import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus"; import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus";
import { ICalendarDay, ICalendarSeason, ISeasonChallenge, ISortie, IWorldState } from "../types/worldStateTypes"; import {
ICalendarDay,
ICalendarSeason,
ILiteSortie,
ISeasonChallenge,
ISortie,
IWorldState
} from "../types/worldStateTypes";
const sortieBosses = [ const sortieBosses = [
"SORTIE_BOSS_HYENA", "SORTIE_BOSS_HYENA",
@ -348,6 +355,34 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
}; };
}; };
const pushWeeklyActs = (worldState: IWorldState, week: number): void => {
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 0));
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 1));
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 2));
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 3));
worldState.SeasonInfo.ActiveChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" + (week - 12)
});
worldState.SeasonInfo.ActiveChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" + (week - 12)
});
worldState.SeasonInfo.ActiveChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" + (week - 12)
});
};
const birthdays: number[] = [ const birthdays: number[] = [
1, // Kaya 1, // Kaya
45, // Lettie 45, // Lettie
@ -604,29 +639,10 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
if (isBeforeNextExpectedWorldStateRefresh(EPOCH + (day + 1) * 86400000)) { if (isBeforeNextExpectedWorldStateRefresh(EPOCH + (day + 1) * 86400000)) {
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day + 1)); worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day + 1));
} }
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 0)); pushWeeklyActs(worldState, week);
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 1)); if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 2)); pushWeeklyActs(worldState, week + 1);
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 3)); }
worldState.SeasonInfo.ActiveChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" + (week - 12)
});
worldState.SeasonInfo.ActiveChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" + (week - 12)
});
worldState.SeasonInfo.ActiveChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" + (week - 12)
});
// TODO: Provide upcoming weekly acts if rollover is imminent
// Elite Sanctuary Onslaught cycling every week // Elite Sanctuary Onslaught cycling every week
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful
@ -941,65 +957,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
pushSortieIfRelevant(worldState.Sorties, day); pushSortieIfRelevant(worldState.Sorties, day);
// Archon Hunt cycling every week // Archon Hunt cycling every week
// TODO: Handle imminent rollover worldState.LiteSorties.push(getLiteSortie(week));
{ if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3]; worldState.LiteSorties.push(getLiteSortie(week + 1));
const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3];
const systemIndex = [3, 4, 2][week % 3]; // Mars, Jupiter, Earth
const nodes: string[] = [];
for (const [key, value] of Object.entries(ExportRegions)) {
if (
value.systemIndex === systemIndex &&
value.factionIndex !== undefined &&
value.factionIndex < 2 &&
value.name.indexOf("Archwing") == -1 &&
value.missionIndex != 0 // Exclude MT_ASSASSINATION
) {
nodes.push(key);
}
}
const rng = new CRng(week);
const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
const firstNode = nodes[firstNodeIndex];
nodes.splice(firstNodeIndex, 1);
worldState.LiteSorties.push({
_id: {
$oid: Math.trunc(weekStart / 1000).toString(16) + "5e23a244740a190c"
},
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
Seed: week,
Boss: boss,
Missions: [
{
missionType: rng.randomElement([
"MT_INTEL",
"MT_MOBILE_DEFENSE",
"MT_EXTERMINATION",
"MT_SABOTAGE",
"MT_RESCUE"
]),
node: firstNode
},
{
missionType: rng.randomElement([
"MT_DEFENSE",
"MT_TERRITORY",
"MT_ARTIFACT",
"MT_EXCAVATE",
"MT_SURVIVAL"
]),
node: rng.randomElement(nodes)
},
{
missionType: "MT_ASSASSINATION",
node: showdownNode
}
]
});
} }
// Circuit choices cycling every week // Circuit choices cycling every week
@ -1071,3 +1031,70 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
return worldState; return worldState;
}; };
export const idToWeek = (id: string): number => {
return (parseInt(id.substring(0, 8), 16) * 1000 - EPOCH) / 604800000;
};
export const getLiteSortie = (week: number): ILiteSortie => {
const boss = (["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"] as const)[week % 3];
const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3];
const systemIndex = [3, 4, 2][week % 3]; // Mars, Jupiter, Earth
const nodes: string[] = [];
for (const [key, value] of Object.entries(ExportRegions)) {
if (
value.systemIndex === systemIndex &&
value.factionIndex !== undefined &&
value.factionIndex < 2 &&
value.name.indexOf("Archwing") == -1 &&
value.missionIndex != 0 // Exclude MT_ASSASSINATION
) {
nodes.push(key);
}
}
const rng = new CRng(week);
const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
const firstNode = nodes[firstNodeIndex];
nodes.splice(firstNodeIndex, 1);
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
return {
_id: {
$oid: Math.trunc(weekStart / 1000).toString(16) + "5e23a244740a190c"
},
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
Seed: week,
Boss: boss,
Missions: [
{
missionType: rng.randomElement([
"MT_INTEL",
"MT_MOBILE_DEFENSE",
"MT_EXTERMINATION",
"MT_SABOTAGE",
"MT_RESCUE"
]),
node: firstNode
},
{
missionType: rng.randomElement([
"MT_DEFENSE",
"MT_TERRITORY",
"MT_ARTIFACT",
"MT_EXCAVATE",
"MT_SURVIVAL"
]),
node: rng.randomElement(nodes)
},
{
missionType: "MT_ASSASSINATION",
node: showdownNode
}
]
};
};

View File

@ -103,12 +103,12 @@ export interface IGuildMemberDatabase {
ShipDecorationsContributed?: ITypeCount[]; ShipDecorationsContributed?: ITypeCount[];
} }
interface IFriendInfo { export interface IFriendInfo {
_id: IOid; _id: IOid;
DisplayName?: string; DisplayName?: string;
PlatformNames?: string[]; PlatformNames?: string[];
PlatformAccountId?: string; PlatformAccountId?: string;
Status: number; Status?: number;
ActiveAvatarImageType?: string; ActiveAvatarImageType?: string;
LastLogin?: IMongoDate; LastLogin?: IMongoDate;
PlayerLevel?: number; PlayerLevel?: number;

View File

@ -49,6 +49,8 @@ export interface IInventoryDatabase
| "PersonalTechProjects" | "PersonalTechProjects"
| "LastSortieReward" | "LastSortieReward"
| "LastLiteSortieReward" | "LastLiteSortieReward"
| "CrewMembers"
| "QualifyingInvasions"
| TEquipmentKey | TEquipmentKey
>, >,
InventoryDatabaseEquipment { InventoryDatabaseEquipment {
@ -83,6 +85,8 @@ export interface IInventoryDatabase
PersonalTechProjects: IPersonalTechProjectDatabase[]; PersonalTechProjects: IPersonalTechProjectDatabase[];
LastSortieReward?: ILastSortieRewardDatabase[]; LastSortieReward?: ILastSortieRewardDatabase[];
LastLiteSortieReward?: ILastSortieRewardDatabase[]; LastLiteSortieReward?: ILastSortieRewardDatabase[];
CrewMembers: ICrewMemberDatabase[];
QualifyingInvasions: IInvasionProgressDatabase[];
} }
export interface IQuestKeyDatabase { export interface IQuestKeyDatabase {
@ -246,9 +250,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Guide?: number; Guide?: number;
Moderator?: boolean; Moderator?: boolean;
Partner?: boolean; Partner?: boolean;
Accolades?: { Accolades?: IAccolades;
Heirloom?: boolean;
};
Counselor?: boolean; Counselor?: boolean;
Upgrades: IUpgradeClient[]; Upgrades: IUpgradeClient[];
EquippedGear: string[]; EquippedGear: string[];
@ -270,7 +272,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters; SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters;
SupportedSyndicate?: string; SupportedSyndicate?: string;
Affiliations: IAffiliation[]; Affiliations: IAffiliation[];
QualifyingInvasions: any[]; QualifyingInvasions: IInvasionProgressClient[];
FactionScores: number[]; FactionScores: number[];
ArchwingEnabled?: boolean; ArchwingEnabled?: boolean;
PendingSpectreLoadouts?: ISpectreLoadout[]; PendingSpectreLoadouts?: ISpectreLoadout[];
@ -283,6 +285,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
CompletedSorties: string[]; CompletedSorties: string[];
LastSortieReward?: ILastSortieRewardClient[]; LastSortieReward?: ILastSortieRewardClient[];
LastLiteSortieReward?: ILastSortieRewardClient[]; LastLiteSortieReward?: ILastSortieRewardClient[];
SortieRewardAttenuation?: ISortieRewardAttenuation[];
Drones: IDroneClient[]; Drones: IDroneClient[];
StepSequencers: IStepSequencer[]; StepSequencers: IStepSequencer[];
ActiveAvatarImageType: string; ActiveAvatarImageType: string;
@ -324,7 +327,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
InfestedFoundry?: IInfestedFoundryClient; InfestedFoundry?: IInfestedFoundryClient;
BlessingCooldown?: IMongoDate; BlessingCooldown?: IMongoDate;
CrewShipRawSalvage: ITypeCount[]; CrewShipRawSalvage: ITypeCount[];
CrewMembers: ICrewMember[]; CrewMembers: ICrewMemberClient[];
LotusCustomization: ILotusCustomization; LotusCustomization: ILotusCustomization;
UseAdultOperatorLoadout?: boolean; UseAdultOperatorLoadout?: boolean;
NemesisAbandonedRewards: string[]; NemesisAbandonedRewards: string[];
@ -461,32 +464,36 @@ export interface ICompletedJob {
StageCompletions: number[]; StageCompletions: number[];
} }
export interface ICrewMember { export interface ICrewMemberSkill {
Assigned: number;
}
export interface ICrewMemberSkillEfficiency {
PILOTING: ICrewMemberSkill;
GUNNERY: ICrewMemberSkill;
ENGINEERING: ICrewMemberSkill;
COMBAT: ICrewMemberSkill;
SURVIVABILITY: ICrewMemberSkill;
}
export interface ICrewMemberClient {
ItemType: string; ItemType: string;
NemesisFingerprint: number; NemesisFingerprint: bigint;
Seed: number; Seed: bigint;
HireDate: IMongoDate; AssignedRole?: number;
AssignedRole: number; SkillEfficiency: ICrewMemberSkillEfficiency;
SkillEfficiency: ISkillEfficiency;
WeaponConfigIdx: number; WeaponConfigIdx: number;
WeaponId: IOid; WeaponId: IOid;
XP: number; XP: number;
PowersuitType: string; PowersuitType: string;
Configs: IItemConfig[]; Configs: IItemConfig[];
SecondInCommand: boolean; SecondInCommand: boolean; // on call
ItemId: IOid; ItemId: IOid;
} }
export interface ISkillEfficiency { export interface ICrewMemberDatabase extends Omit<ICrewMemberClient, "WeaponId" | "ItemId"> {
PILOTING: ICombat; WeaponId: Types.ObjectId;
GUNNERY: ICombat; _id: Types.ObjectId;
ENGINEERING: ICombat;
COMBAT: ICombat;
SURVIVABILITY: ICombat;
}
export interface ICombat {
Assigned: number;
} }
export enum InventorySlot { export enum InventorySlot {
@ -532,12 +539,12 @@ export interface ICrewShipMembersDatabase {
export interface ICrewShipMemberClient { export interface ICrewShipMemberClient {
ItemId?: IOid; ItemId?: IOid;
NemesisFingerprint?: number; NemesisFingerprint?: number | bigint;
} }
export interface ICrewShipMemberDatabase { export interface ICrewShipMemberDatabase {
ItemId?: Types.ObjectId; ItemId?: Types.ObjectId;
NemesisFingerprint?: number; NemesisFingerprint?: bigint;
} }
export interface ICrewShipCustomization { export interface ICrewShipCustomization {
@ -562,17 +569,18 @@ export type IMiscItem = ITypeCount;
// inventory.CrewShips[0].Weapon // inventory.CrewShips[0].Weapon
export interface ICrewShipWeapon { export interface ICrewShipWeapon {
PILOT: ICrewShipPilotWeapon; PILOT?: ICrewShipWeaponEmplacements;
PORT_GUNS: ICrewShipPortGuns; PORT_GUNS?: ICrewShipWeaponEmplacements;
STARBOARD_GUNS?: ICrewShipWeaponEmplacements;
ARTILLERY?: ICrewShipWeaponEmplacements;
SCANNER?: ICrewShipWeaponEmplacements;
} }
export interface ICrewShipPilotWeapon { export interface ICrewShipWeaponEmplacements {
PRIMARY_A: IEquipmentSelection; PRIMARY_A?: IEquipmentSelection;
SECONDARY_A: IEquipmentSelection; PRIMARY_B?: IEquipmentSelection;
} SECONDARY_A?: IEquipmentSelection;
SECONDARY_B?: IEquipmentSelection;
export interface ICrewShipPortGuns {
PRIMARY_A: IEquipmentSelection;
} }
export interface IDiscoveredMarker { export interface IDiscoveredMarker {
@ -668,6 +676,17 @@ export interface IInvasionChainProgress {
count: number; count: number;
} }
export interface IInvasionProgressClient {
_id: IOid;
Delta: number;
AttackerScore: number;
DefenderScore: number;
}
export interface IInvasionProgressDatabase extends Omit<IInvasionProgressClient, "_id"> {
invasionId: Types.ObjectId;
}
export interface IKubrowPetEggClient { export interface IKubrowPetEggClient {
ItemType: string; ItemType: string;
ExpirationDate: IMongoDate; // seems to be set to 7 days ahead @ 0 UTC ExpirationDate: IMongoDate; // seems to be set to 7 days ahead @ 0 UTC
@ -739,6 +758,11 @@ export interface ILastSortieRewardDatabase extends Omit<ILastSortieRewardClient,
SortieId: Types.ObjectId; SortieId: Types.ObjectId;
} }
export interface ISortieRewardAttenuation {
Tag: string;
Atten: number;
}
export interface ILibraryDailyTaskInfo { export interface ILibraryDailyTaskInfo {
EnemyTypes: string[]; EnemyTypes: string[];
EnemyLocTag: string; EnemyLocTag: string;
@ -888,6 +912,10 @@ export interface IPendingRecipeClient
CompletionDate: IMongoDate; CompletionDate: IMongoDate;
} }
export interface IAccolades {
Heirloom?: boolean;
}
export interface IPendingTrade { export interface IPendingTrade {
State: number; State: number;
SelfReady: boolean; SelfReady: boolean;

View File

@ -1,3 +1,5 @@
import { Types } from "mongoose";
export interface IAccountAndLoginResponseCommons { export interface IAccountAndLoginResponseCommons {
DisplayName: string; DisplayName: string;
CountryCode: string; CountryCode: string;
@ -56,3 +58,8 @@ export interface IGroup {
experiment: string; experiment: string;
experimentGroup: string; experimentGroup: string;
} }
export interface IIgnore {
ignorer: Types.ObjectId;
ignoree: Types.ObjectId;
}

View File

@ -6,7 +6,8 @@ import {
INemesisClient, INemesisClient,
ITypeCount, ITypeCount,
IRecentVendorPurchaseClient, IRecentVendorPurchaseClient,
TEquipmentKey TEquipmentKey,
ICrewMemberClient
} from "./inventoryTypes/inventoryTypes"; } from "./inventoryTypes/inventoryTypes";
export interface IPurchaseRequest { export interface IPurchaseRequest {
@ -47,6 +48,7 @@ export type IInventoryChanges = {
Nemesis?: Partial<INemesisClient>; Nemesis?: Partial<INemesisClient>;
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0 NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0 RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
CrewMembers?: ICrewMemberClient[];
} & Record< } & Record<
Exclude< Exclude<
string, string,

View File

@ -19,7 +19,8 @@ import {
ICollectibleEntry, ICollectibleEntry,
IDiscoveredMarker, IDiscoveredMarker,
ILockedWeaponGroupClient, ILockedWeaponGroupClient,
ILoadOutPresets ILoadOutPresets,
IInvasionProgressClient
} from "./inventoryTypes/inventoryTypes"; } from "./inventoryTypes/inventoryTypes";
import { IGroup } from "./loginTypes"; import { IGroup } from "./loginTypes";
@ -123,12 +124,15 @@ export type IMissionInventoryUpdateRequest = {
}; };
wagerTier?: number; // the index wagerTier?: number; // the index
creditsFee?: number; // the index creditsFee?: number; // the index
InvasionProgress?: IInvasionProgressClient[];
} & { } & {
[K in TEquipmentKey]?: IEquipmentClient[]; [K in TEquipmentKey]?: IEquipmentClient[];
}; };
export interface IRewardInfo { export interface IRewardInfo {
node: string; node: string;
invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
sortieId?: string; sortieId?: string;
sortieTag?: string; sortieTag?: string;
sortiePrereqs?: string[]; sortiePrereqs?: string[];

View File

@ -1,7 +1,13 @@
import { IOid } from "@/src/types/commonTypes"; import { IOid } from "@/src/types/commonTypes";
import { IItemConfig, IOperatorConfigClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IItemConfig, IOperatorConfigClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { ILoadoutConfigClient } from "./inventoryTypes/inventoryTypes"; import {
ICrewShipCustomization,
ICrewShipMembersClient,
ICrewShipWeapon,
IFlavourItem,
ILoadoutConfigClient
} from "./inventoryTypes/inventoryTypes";
export interface ISaveLoadoutRequest { export interface ISaveLoadoutRequest {
LoadOuts: ILoadoutClient; LoadOuts: ILoadoutClient;
@ -51,7 +57,16 @@ export interface IItemEntry {
export type IConfigEntry = { export type IConfigEntry = {
[configId in "0" | "1" | "2" | "3" | "4" | "5"]: IItemConfig; [configId in "0" | "1" | "2" | "3" | "4" | "5"]: IItemConfig;
} & { Favorite?: boolean; IsNew?: boolean }; } & {
Favorite?: boolean;
IsNew?: boolean;
// Railjack
ItemName?: string;
RailjackImage?: IFlavourItem;
Customization?: ICrewShipCustomization;
Weapon?: ICrewShipWeapon;
CrewMembers?: ICrewShipMembersClient;
};
export type ILoadoutClient = Omit<ILoadoutDatabase, "_id" | "loadoutOwnerId">; export type ILoadoutClient = Omit<ILoadoutDatabase, "_id" | "loadoutOwnerId">;

View File

@ -32,7 +32,7 @@ export interface IVendorInfo {
TypeName: string; TypeName: string;
ItemManifest: IItemManifest[]; ItemManifest: IItemManifest[];
PropertyTextHash?: string; PropertyTextHash?: string;
RandomSeedType?: "VRST_WEAPON"; RandomSeedType?: string;
RequiredGoalTag?: string; RequiredGoalTag?: string;
WeaponUpgradeValueAttenuationExponent?: number; WeaponUpgradeValueAttenuationExponent?: 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.

View File

@ -103,7 +103,7 @@ export interface ILiteSortie {
Expiry: IMongoDate; Expiry: IMongoDate;
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards"; Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards";
Seed: number; Seed: number;
Boss: string; // "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL" Boss: "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL";
Missions: { Missions: {
missionType: string; missionType: string;
node: string; node: string;

View File

@ -1,3 +1,4 @@
// Misnomer: We have concurrency, not parallelism - oh well!
export const parallelForeach = async <T>(data: T[], op: (datum: T) => Promise<void>): Promise<void> => { export const parallelForeach = async <T>(data: T[], op: (datum: T) => Promise<void>): Promise<void> => {
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
for (const datum of data) { for (const datum of data) {

View File

@ -1092,5 +1092,11 @@
"/Lotus/Types/Game/CrewShip/GrineerDestroyer/GrineerDestroyerAvatar", "/Lotus/Types/Game/CrewShip/GrineerDestroyer/GrineerDestroyerAvatar",
"/Lotus/Types/LevelObjects/Zariman/ZarLootCrateUltraRare", "/Lotus/Types/LevelObjects/Zariman/ZarLootCrateUltraRare",
"/Lotus/Objects/DomestikDrone/GrineerOceanDomestikDroneMover", "/Lotus/Objects/DomestikDrone/GrineerOceanDomestikDroneMover",
"/Lotus/Types/Gameplay/1999Wf/Extermination/SupplyCrate" "/Lotus/Types/Gameplay/1999Wf/Extermination/SupplyCrate",
"/Lotus/Objects/Orokin/Props/CollectibleSeriesOne",
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLamp",
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampLarge",
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampSmall",
"/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem",
"/Lotus/Types/Enemies/Orokin/OrokinMoaBipedAvatar"
] ]

View File

@ -1,133 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "60ad3b6ec96976e97d227e19"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/PerrinSequenceWeaponVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Weapons/Corpus/BoardExec/Primary/CrpBEFerrox/CrpBEFerrox",
"ItemPrices": [
{
"ItemCount": 40,
"ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 4383829823946960400,
"Id": {
"$oid": "66fd60b20ba592c4c95e9488"
}
},
{
"StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/CrpBriefcaseScythe/CrpBriefcaseScythe",
"ItemPrices": [
{
"ItemCount": 40,
"ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 7952272124248276000,
"Id": {
"$oid": "66fd60b20ba592c4c95e9489"
}
},
{
"StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/CrpBriefcase2HKatana/CrpBriefcase2HKatana",
"ItemPrices": [
{
"ItemCount": 40,
"ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 465952672558014140,
"Id": {
"$oid": "66fd60b20ba592c4c95e948a"
}
},
{
"StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/CrpBigSlash/CrpBigSlash",
"ItemPrices": [
{
"ItemCount": 40,
"ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 8342430883077507000,
"Id": {
"$oid": "66fd60b20ba592c4c95e948b"
}
},
{
"StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/ShieldAndSword/CrpHammerShield/CrpHammerShield",
"ItemPrices": [
{
"ItemCount": 40,
"ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 7441523153174502000,
"Id": {
"$oid": "66fd60b20ba592c4c95e948c"
}
}
],
"PropertyTextHash": "34F8CF1DFF745F0D67433A5EF0A03E70",
"RandomSeedType": "VRST_WEAPON",
"WeaponUpgradeValueAttenuationExponent": 2.25,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -487,6 +487,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
document.getElementById(category + "-list").removeChild(tr);
disposeOfGear(category, item.ItemId.$oid); disposeOfGear(category, item.ItemId.$oid);
}; };
a.title = loc("code_remove"); a.title = loc("code_remove");
@ -683,6 +684,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
document.getElementById("riven-list").removeChild(tr);
disposeOfGear("Upgrades", item.ItemId.$oid); disposeOfGear("Upgrades", item.ItemId.$oid);
}; };
a.title = loc("code_remove"); a.title = loc("code_remove");
@ -723,6 +725,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
document.getElementById("mods-list").removeChild(tr);
disposeOfGear("Upgrades", item.ItemId.$oid); disposeOfGear("Upgrades", item.ItemId.$oid);
}; };
a.title = loc("code_remove"); a.title = loc("code_remove");
@ -765,6 +768,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
document.getElementById("mods-list").removeChild(tr);
disposeOfItems("Upgrades", item.ItemType, item.ItemCount); disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
}; };
a.title = loc("code_remove"); a.title = loc("code_remove");
@ -1097,8 +1101,6 @@ function disposeOfGear(category, oid) {
url: "/api/sell.php?" + window.authz, url: "/api/sell.php?" + window.authz,
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify(data) data: JSON.stringify(data)
}).done(function () {
updateInventory();
}); });
}); });
} }
@ -1120,8 +1122,6 @@ function disposeOfItems(category, type, count) {
url: "/api/sell.php?" + window.authz, url: "/api/sell.php?" + window.authz,
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify(data) data: JSON.stringify(data)
}).done(function () {
updateInventory();
}); });
}); });
} }
@ -1260,21 +1260,28 @@ function doAcquireMod() {
$("#mod-to-acquire").addClass("is-invalid").focus(); $("#mod-to-acquire").addClass("is-invalid").focus();
return; return;
} }
revalidateAuthz(() => { const count = parseInt($("#mod-count").val());
$.post({ if (count != 0) {
url: "/custom/addItems?" + window.authz, revalidateAuthz(() => {
contentType: "application/json", $.post({
data: JSON.stringify([ url: "/custom/addItems?" + window.authz,
{ contentType: "application/json",
ItemType: uniqueName, data: JSON.stringify([
ItemCount: parseInt($("#mod-count").val()) {
ItemType: uniqueName,
ItemCount: count
}
])
}).done(function () {
if (count > 0) {
toast(loc("code_succAdded"));
} else {
toast(loc("code_succRemoved"));
} }
]) updateInventory();
}).done(function () { });
document.getElementById("mod-to-acquire").value = "";
updateInventory();
}); });
}); }
} }
const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id); const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id);
@ -1315,7 +1322,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
interval = setInterval(() => { interval = setInterval(() => {
if (window.authz) { if (window.authz) {
clearInterval(interval); clearInterval(interval);
fetch("/custom/config?" + window.authz).then(res => { fetch("/custom/config?" + window.authz).then(async res => {
if (res.status == 200) { if (res.status == 200) {
$("#server-settings-no-perms").addClass("d-none"); $("#server-settings-no-perms").addClass("d-none");
$("#server-settings").removeClass("d-none"); $("#server-settings").removeClass("d-none");
@ -1335,8 +1342,16 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
}) })
); );
} else { } else {
$("#server-settings-no-perms").removeClass("d-none"); if ((await res.text()) == "Log-in expired") {
$("#server-settings").addClass("d-none"); revalidateAuthz(() => {
if (single.getCurrentPath() == "/webui/cheats") {
single.loadRoute("/webui/cheats");
}
});
} else {
$("#server-settings-no-perms").removeClass("d-none");
$("#server-settings").addClass("d-none");
}
} }
}); });
} }

View File

@ -138,7 +138,7 @@ dict = {
cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`, cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`,
cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`, cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`,
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`, cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
cheats_noDeathMarks: `[UNTRANSLATED] No Death Marks`, cheats_noDeathMarks: `Keine Todesmarkierungen`,
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`, cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`, cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,