Compare commits

..

13 Commits

Author SHA1 Message Date
dcfe2a01d8 feat: archon hunt rewards
All checks were successful
Build / build (pull_request) Successful in 1m30s
Build / build (push) Successful in 1m28s
also added a check for first completion to avoid giving another reward for repeating the final mission
2025-04-18 20:22:27 +02:00
da6067ec43 fix: use correct drop table for phorid assassination (#1718)
All checks were successful
Build Docker image / docker (push) Successful in 34s
Build / build (push) Successful in 1m27s
Reviewed-on: #1718
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:18:26 -07:00
a98e18d511 feat: tenet weapon vendor rotation (#1717)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
Reviewed-on: #1717
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:18:11 -07:00
6394adb0f0 fix(webui): handle config get request failing due to expired authz (#1716)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
Reviewed-on: #1716
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:17:55 -07:00
bc5dc02fc9 chore: fill in guild member data asynchronously (#1715)
Some checks failed
Build Docker image / docker (push) Has been cancelled
Build / build (push) Has been cancelled
Reviewed-on: #1715
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:17:36 -07:00
de5fd5fce0 chore: provide a proper schema for CurrentLoadOutIds (#1714)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
Reviewed-on: #1714
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:17:19 -07:00
a6d4fab595 chore: rewrite gruzzling droptable to scathing/mocking whispers (#1712)
Some checks failed
Build Docker image / docker (push) Has been cancelled
Build / build (push) Has been cancelled
Closes #1708

Reviewed-on: #1712
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:16:58 -07:00
f549b042d6 feat: ignore list (#1711)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
Closes #1707

Reviewed-on: #1711
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:16:43 -07:00
0c34c87d75 fix: give defaultUpgrades for infested pets (#1710)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
Fixes #1709

Reviewed-on: #1710
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:16:11 -07:00
3baf6ad015 feat: handle railjack armaments, crew, & customizations in saveLoadout (#1706)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
Closes #467

Reviewed-on: #1706
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:15:50 -07:00
196182f9a8 feat: acquisition of CrewMembers (#1705)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
Reviewed-on: #1705
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-18 11:15:27 -07:00
379f57be2c chore: add pumpkin containers to allScans (#1703)
All checks were successful
Build Docker image / docker (push) Successful in 59s
Build / build (push) Successful in 1m17s
Closes #1693

Reviewed-on: #1703
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-17 18:40:51 -07:00
0d8f5ee66c fix: provide proper response when unbranding a suit (#1697)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
Fixes #1695

Reviewed-on: #1697
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-17 18:40:41 -07:00
27 changed files with 431 additions and 240 deletions

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

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";
const getIgnoredUsersController: RequestHandler = (_req, res) => {
res.writeHead(200, {
"Content-Type": "text/html",
"Content-Length": "3"
export const getIgnoredUsersController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const ignores = await Ignore.find({ ignorer: accountId });
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(
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
])
);
res.json({ IgnoredUsers: ignoredUsers });
};
export { getIgnoredUsersController };

View File

@ -17,7 +17,7 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
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";
interface IModularCraftRequest {
@ -34,10 +34,8 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
const category = modularWeaponTypes[data.WeaponType];
const inventory = await getInventory(accountId);
const defaultUpgrades = getDefaultUpgrades(data.Parts);
const defaultOverwrites: Partial<IEquipmentDatabase> = {
Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
};
let defaultUpgrades: IDefaultUpgrade[] | undefined;
const defaultOverwrites: Partial<IEquipmentDatabase> = {};
const inventoryChanges: IInventoryChanges = {};
if (category == "KubrowPets") {
const traits = {
@ -129,10 +127,17 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
// Only save mutagen & antigen in the ModularParts.
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);
}
defaultUpgrades = meta.defaultUpgrades;
} else {
defaultUpgrades = getDefaultUpgrades(data.Parts);
}
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
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 { 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 { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
@ -23,6 +23,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
await Promise.all([
Account.deleteOne({ _id: accountId }),
GuildMember.deleteMany({ accountId: accountId }),
Ignore.deleteMany({ ignorer: accountId }),
Ignore.deleteMany({ ignoree: accountId }),
Inbox.deleteMany({ ownerId: accountId }),
Inventory.deleteOne({ accountOwnerId: accountId }),
Leaderboard.deleteMany({ ownerId: accountId }),

View File

@ -39,10 +39,9 @@ import {
ILoreFragmentScan,
IEvolutionProgress,
IEndlessXpProgress,
ICrewShipPortGuns,
ICrewShipCustomization,
ICrewShipWeapon,
ICrewShipPilotWeapon,
ICrewShipWeaponEmplacements,
IShipExterior,
IHelminthFoodRecord,
ICrewShipMembersDatabase,
@ -89,6 +88,10 @@ import {
IPersonalTechProjectClient,
ILastSortieRewardDatabase,
ILastSortieRewardClient,
ICrewMemberSkill,
ICrewMemberSkillEfficiency,
ICrewMemberDatabase,
ICrewMemberClient,
ISortieRewardAttenuation
} from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes";
@ -103,7 +106,7 @@ import {
IEquipmentClient
} from "@/src/types/inventoryTypes/commonInventoryTypes";
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 });
@ -295,6 +298,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>(
{
Slots: Number,
@ -720,25 +772,23 @@ const endlessXpProgressSchema = new Schema<IEndlessXpProgress>(
{ _id: false }
);
const crewShipPilotWeaponSchema = new Schema<ICrewShipPilotWeapon>(
const crewShipWeaponEmplacementsSchema = new Schema<ICrewShipWeaponEmplacements>(
{
PRIMARY_A: EquipmentSelectionSchema,
SECONDARY_A: EquipmentSelectionSchema
},
{ _id: false }
);
const crewShipPortGunsSchema = new Schema<ICrewShipPortGuns>(
{
PRIMARY_A: EquipmentSelectionSchema
PRIMARY_B: EquipmentSelectionSchema,
SECONDARY_A: EquipmentSelectionSchema,
SECONDARY_B: EquipmentSelectionSchema
},
{ _id: false }
);
const crewShipWeaponSchema = new Schema<ICrewShipWeapon>(
{
PILOT: crewShipPilotWeaponSchema,
PORT_GUNS: crewShipPortGunsSchema
PILOT: crewShipWeaponEmplacementsSchema,
PORT_GUNS: crewShipWeaponEmplacementsSchema,
STARBOARD_GUNS: crewShipWeaponEmplacementsSchema,
ARTILLERY: crewShipWeaponEmplacementsSchema,
SCANNER: crewShipWeaponEmplacementsSchema
},
{ _id: false }
);
@ -762,7 +812,7 @@ const crewShipCustomizationSchema = new Schema<ICrewShipCustomization>(
const crewShipMemberSchema = new Schema<ICrewShipMemberDatabase>(
{
ItemId: { type: Schema.Types.ObjectId, required: false },
NemesisFingerprint: { type: Number, required: false }
NemesisFingerprint: { type: BigInt, required: false }
},
{ _id: false }
);
@ -1372,7 +1422,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
CrewShipSalvagedWeaponSkins: [upgradeSchema],
//RailJack Crew
CrewMembers: [Schema.Types.Mixed],
CrewMembers: [crewMemberSchema],
//Complete Mission\Quests
Missions: [missionSchema],
@ -1548,7 +1598,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
HasContributedToDojo: Boolean,
HWIDProtectEnabled: Boolean,
LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" },
CurrentLoadOutIds: [Schema.Types.Mixed],
CurrentLoadOutIds: [oidSchema],
RandomUpgradesIdentified: Number,
BountyScore: Number,
ChallengeInstanceStates: [Schema.Types.Mixed],
@ -1655,6 +1705,7 @@ export type InventoryDocumentProps = {
CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
// 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 { Document, Model, Schema, Types, model } from "mongoose";
const oidSchema = new Schema<IOid>(
export const oidSchema = new Schema<IOid>(
{
$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";
const opts = {
@ -37,3 +37,13 @@ databaseAccountSchema.set("toJSON", {
});
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 { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserController";
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
@ -97,6 +98,7 @@ import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCode
import { releasePetController } from "@/src/controllers/api/releasePetController";
import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
@ -202,6 +204,7 @@ apiRouter.get("/updateSession.php", updateSessionGetController);
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
apiRouter.post("/activateRandomMod.php", activateRandomModController);
apiRouter.post("/addFriendImage.php", addFriendImageController);
apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
apiRouter.post("/addToAlliance.php", addToAllianceController);
apiRouter.post("/addToGuild.php", addToGuildController);
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
@ -266,6 +269,7 @@ apiRouter.post("/purchase.php", purchaseController);
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
apiRouter.post("/releasePet.php", releasePetController);
apiRouter.post("/removeFromGuild.php", removeFromGuildController);
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
apiRouter.post("/saveDialogue.php", saveDialogueController);

View File

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

View File

@ -104,18 +104,18 @@ const replaceSlots = (db: ISlots, client: ISlots): void => {
db.Slots = client.Slots;
};
const convertCrewShipMember = (client: ICrewShipMemberClient): ICrewShipMemberDatabase => {
return {
...client,
ItemId: client.ItemId ? new Types.ObjectId(client.ItemId.$oid) : undefined
};
export const importCrewMemberId = (crewMemberId: ICrewShipMemberClient): ICrewShipMemberDatabase => {
if (crewMemberId.ItemId) {
return { ItemId: new Types.ObjectId(crewMemberId.ItemId.$oid) };
}
return { NemesisFingerprint: BigInt(crewMemberId.NemesisFingerprint ?? 0) };
};
const convertCrewShipMembers = (client: ICrewShipMembersClient): ICrewShipMembersDatabase => {
return {
SLOT_A: client.SLOT_A ? convertCrewShipMember(client.SLOT_A) : undefined,
SLOT_B: client.SLOT_B ? convertCrewShipMember(client.SLOT_B) : undefined,
SLOT_C: client.SLOT_C ? convertCrewShipMember(client.SLOT_C) : undefined
SLOT_A: client.SLOT_A ? importCrewMemberId(client.SLOT_A) : undefined,
SLOT_B: client.SLOT_B ? importCrewMemberId(client.SLOT_B) : undefined,
SLOT_C: client.SLOT_C ? importCrewMemberId(client.SLOT_C) : undefined
};
};

View File

@ -22,7 +22,8 @@ import {
IDroneClient,
IUpgradeClient,
TPartialStartingGear,
ILoreFragmentScan
ILoreFragmentScan,
ICrewMemberClient
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
@ -713,6 +714,15 @@ export const addItem = async (
return {
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") {
return addCrewShipHarness(inventory, typeName);
}
@ -1212,6 +1222,78 @@ const addDrone = (
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 (
inventory: TInventoryDatabaseDocument,
typeName: string,

View File

@ -693,6 +693,12 @@ export const addMissionRewards = async (
if (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] ?? [];
if (si.DROP_MOD) {
const modDroptable = droptables.find(x => x.type == "mod");
@ -1030,10 +1036,16 @@ function getRandomMissionDrops(
}
if (RewardInfo.node in ExportRegions) {
const region = ExportRegions[RewardInfo.node];
let rewardManifests: string[] =
RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB"
? ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"]
: region.rewardManifests;
let rewardManifests: string[];
if (RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB") {
rewardManifests = ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"];
} 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[] = [];
if (RewardInfo.jobId) {

View File

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

View File

@ -13,6 +13,8 @@ import { Types } from "mongoose";
import { isEmptyObject } from "@/src/helpers/general";
import { logger } from "@/src/utils/logger";
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
@ -174,8 +176,8 @@ export const handleInventoryItemConfigChange = async (
}
for (const [configId, config] of Object.entries(itemConfigEntries)) {
if (typeof config !== "boolean") {
inventoryItem.Configs[parseInt(configId)] = config;
if (/^[0-9]+$/.test(configId)) {
inventoryItem.Configs[parseInt(configId)] = config as IItemConfig;
}
}
if ("Favorite" in itemConfigEntries) {
@ -184,6 +186,26 @@ export const handleInventoryItemConfigChange = async (
if ("IsNew" in itemConfigEntries) {
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;
} else {

View File

@ -34,7 +34,6 @@ const rawVendorManifests: IRawVendorManifest[] = [
getVendorManifestJson("EntratiLabsEntratiLabVendorManifest"),
getVendorManifestJson("GuildAdvertisementVendorManifest"), // uses preprocessing
getVendorManifestJson("HubsIronwakeDondaVendorManifest"), // uses preprocessing
getVendorManifestJson("HubsPerrinSequenceWeaponVendorManifest"),
getVendorManifestJson("HubsRailjackCrewMemberVendorManifest"),
getVendorManifestJson("MaskSalesmanManifest"),
getVendorManifestJson("Nova1999ConquestShopManifest"),
@ -51,7 +50,8 @@ const rawVendorManifests: IRawVendorManifest[] = [
];
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
cycleDuration?: number;
cycleStart: number;
cycleDuration: number;
}
const generatableVendors: IGeneratableVendorInfo[] = [
@ -62,6 +62,16 @@ const generatableVendors: IGeneratableVendorInfo[] = [
RandomSeedType: "VRST_WEAPON",
RequiredGoalTag: "",
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
}
// {
@ -124,7 +134,7 @@ const preprocessVendorManifest = (originalManifest: IRawVendorManifest): IVendor
const refreshExpiry = (expiry: IMongoDate): number => {
const period = parseInt(expiry.$date.$numberLong);
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 start = epoch + iteration * period;
const end = start + period;
@ -135,11 +145,11 @@ const refreshExpiry = (expiry: IMongoDate): number => {
};
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];
let binThisCycle;
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);
binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
}
@ -150,7 +160,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
if (manifest.isOneBinPerCycle && rawItem.bin != binThisCycle) {
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 cycleStart = EPOCH + cycleIndex * cycleDuration;
const cycleEnd = cycleStart + cycleDuration;
@ -181,10 +191,11 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
items.push(item);
}
}
delete vendorInfo.cycleDuration;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { cycleStart, cycleDuration, ...clientVendorInfo } = vendorInfo;
return {
VendorInfo: {
...vendorInfo,
...clientVendorInfo,
ItemManifest: items,
Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } }
}

View File

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

View File

@ -49,6 +49,7 @@ export interface IInventoryDatabase
| "PersonalTechProjects"
| "LastSortieReward"
| "LastLiteSortieReward"
| "CrewMembers"
| TEquipmentKey
>,
InventoryDatabaseEquipment {
@ -83,6 +84,7 @@ export interface IInventoryDatabase
PersonalTechProjects: IPersonalTechProjectDatabase[];
LastSortieReward?: ILastSortieRewardDatabase[];
LastLiteSortieReward?: ILastSortieRewardDatabase[];
CrewMembers: ICrewMemberDatabase[];
}
export interface IQuestKeyDatabase {
@ -325,7 +327,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
InfestedFoundry?: IInfestedFoundryClient;
BlessingCooldown?: IMongoDate;
CrewShipRawSalvage: ITypeCount[];
CrewMembers: ICrewMember[];
CrewMembers: ICrewMemberClient[];
LotusCustomization: ILotusCustomization;
UseAdultOperatorLoadout?: boolean;
NemesisAbandonedRewards: string[];
@ -462,13 +464,24 @@ export interface ICompletedJob {
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;
NemesisFingerprint: number;
Seed: number;
HireDate: IMongoDate;
AssignedRole: number;
SkillEfficiency: ISkillEfficiency;
NemesisFingerprint: bigint;
Seed: bigint;
AssignedRole?: number;
SkillEfficiency: ICrewMemberSkillEfficiency;
WeaponConfigIdx: number;
WeaponId: IOid;
XP: number;
@ -478,16 +491,9 @@ export interface ICrewMember {
ItemId: IOid;
}
export interface ISkillEfficiency {
PILOTING: ICombat;
GUNNERY: ICombat;
ENGINEERING: ICombat;
COMBAT: ICombat;
SURVIVABILITY: ICombat;
}
export interface ICombat {
Assigned: number;
export interface ICrewMemberDatabase extends Omit<ICrewMemberClient, "WeaponId" | "ItemId"> {
WeaponId: Types.ObjectId;
_id: Types.ObjectId;
}
export enum InventorySlot {
@ -533,12 +539,12 @@ export interface ICrewShipMembersDatabase {
export interface ICrewShipMemberClient {
ItemId?: IOid;
NemesisFingerprint?: number;
NemesisFingerprint?: number | bigint;
}
export interface ICrewShipMemberDatabase {
ItemId?: Types.ObjectId;
NemesisFingerprint?: number;
NemesisFingerprint?: bigint;
}
export interface ICrewShipCustomization {
@ -563,17 +569,18 @@ export type IMiscItem = ITypeCount;
// inventory.CrewShips[0].Weapon
export interface ICrewShipWeapon {
PILOT: ICrewShipPilotWeapon;
PORT_GUNS: ICrewShipPortGuns;
PILOT?: ICrewShipWeaponEmplacements;
PORT_GUNS?: ICrewShipWeaponEmplacements;
STARBOARD_GUNS?: ICrewShipWeaponEmplacements;
ARTILLERY?: ICrewShipWeaponEmplacements;
SCANNER?: ICrewShipWeaponEmplacements;
}
export interface ICrewShipPilotWeapon {
PRIMARY_A: IEquipmentSelection;
SECONDARY_A: IEquipmentSelection;
}
export interface ICrewShipPortGuns {
PRIMARY_A: IEquipmentSelection;
export interface ICrewShipWeaponEmplacements {
PRIMARY_A?: IEquipmentSelection;
PRIMARY_B?: IEquipmentSelection;
SECONDARY_A?: IEquipmentSelection;
SECONDARY_B?: IEquipmentSelection;
}
export interface IDiscoveredMarker {

View File

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

View File

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

View File

@ -129,6 +129,8 @@ export type IMissionInventoryUpdateRequest = {
export interface IRewardInfo {
node: string;
invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
sortieId?: string;
sortieTag?: string;
sortiePrereqs?: string[];

View File

@ -1,7 +1,13 @@
import { IOid } from "@/src/types/commonTypes";
import { IItemConfig, IOperatorConfigClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { Types } from "mongoose";
import { ILoadoutConfigClient } from "./inventoryTypes/inventoryTypes";
import {
ICrewShipCustomization,
ICrewShipMembersClient,
ICrewShipWeapon,
IFlavourItem,
ILoadoutConfigClient
} from "./inventoryTypes/inventoryTypes";
export interface ISaveLoadoutRequest {
LoadOuts: ILoadoutClient;
@ -51,7 +57,16 @@ export interface IItemEntry {
export type IConfigEntry = {
[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">;

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> => {
const promises: Promise<void>[] = [];
for (const datum of data) {

View File

@ -1092,5 +1092,10 @@
"/Lotus/Types/Game/CrewShip/GrineerDestroyer/GrineerDestroyerAvatar",
"/Lotus/Types/LevelObjects/Zariman/ZarLootCrateUltraRare",
"/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"
]

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

@ -1315,7 +1315,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
interval = setInterval(() => {
if (window.authz) {
clearInterval(interval);
fetch("/custom/config?" + window.authz).then(res => {
fetch("/custom/config?" + window.authz).then(async res => {
if (res.status == 200) {
$("#server-settings-no-perms").addClass("d-none");
$("#server-settings").removeClass("d-none");
@ -1335,8 +1335,16 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
})
);
} else {
$("#server-settings-no-perms").removeClass("d-none");
$("#server-settings").addClass("d-none");
if ((await res.text()) == "Log-in expired") {
revalidateAuthz(() => {
if (single.getCurrentPath() == "/webui/cheats") {
single.loadRoute("/webui/cheats");
}
});
} else {
$("#server-settings-no-perms").removeClass("d-none");
$("#server-settings").addClass("d-none");
}
}
});
}