Compare commits

...

25 Commits

Author SHA1 Message Date
f3e56480e5 fix(webui): error for inventory without EvolutionProgress (#1861)
Reviewed-on: OpenWF/SpaceNinjaServer#1861
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 21:26:58 -07:00
6f46ace40c fix(webui): revalidate authz for rename & delete account actions (#1860)
Reviewed-on: OpenWF/SpaceNinjaServer#1860
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 21:26:52 -07:00
883426e429 fix: align guild advertisment vendor rotation to monday 0 UTC (#1858)
Reviewed-on: OpenWF/SpaceNinjaServer#1858
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 21:26:16 -07:00
13432bf034 fix: future-proof oid string generation (#1847)
This ensures they are still 24 bytes long even past the year 2106. :^)

Reviewed-on: OpenWF/SpaceNinjaServer#1847
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 18:55:09 -07:00
a1267e5f64 chore: add temple vendor manifest (#1851)
Closes #1850

Reviewed-on: OpenWF/SpaceNinjaServer#1851
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 18:54:52 -07:00
2058207b6a fix: nemesis fp from client could be of type number 2025-04-26 03:15:58 +02:00
c7c416c100 chore: simplify arguments defaulted to undefined 2025-04-26 00:40:57 +02:00
90e97d7888 chore: auto-generate guild advertisment vendor (#1845)
With this, preprocessing is simplified to just refreshing expiry dates. No real change to auto-generation logic.

Reviewed-on: OpenWF/SpaceNinjaServer#1845
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 15:12:45 -07:00
3f6734ac1c feat(webui): EvolutionProgress support (#1818)
Closes #1815

Reviewed-on: OpenWF/SpaceNinjaServer#1818
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-25 12:00:38 -07:00
6f64690b91 fix: refresh duviri seed after non-quit completion of a duviri game mode (#1834)
Reviewed-on: OpenWF/SpaceNinjaServer#1834
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:56:40 -07:00
fb5a7320bb chore: update to allScans cheat (#1844)
Reviewed-on: OpenWF/SpaceNinjaServer#1844
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-25 11:56:27 -07:00
143b358a03 chore: always update rewardSeed in missionInventoryUpdate (#1833)
This should be slightly more faithful. Also logging a warning in case we have a mismatch as that shouldn't happen.

Reviewed-on: OpenWF/SpaceNinjaServer#1833
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:54:11 -07:00
0b75757277 chore: improve distribution of rewardSeed (#1831)
This was previously not ideal due to float imprecision, but now it's 64 bits and there's enough entropy for all of them.

Reviewed-on: OpenWF/SpaceNinjaServer#1831
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:53:54 -07:00
fd7f4c9e92 feat: calendar progress (#1830)
Closes #1775

Reviewed-on: OpenWF/SpaceNinjaServer#1830
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:53:34 -07:00
fa6fac494b fix: some problems with 1999 calendar rotation (#1829)
- First day was incorrect for summer & autumn
- Only 1 reward was shown, now is a choice of 2
- Only 1 upgrade was shown, now is a choice of 3
- First 2 challenges in the season are now guaranteed to be "easy"

Reviewed-on: OpenWF/SpaceNinjaServer#1829
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:53:04 -07:00
6b3f524574 feat: sortie mission credit rewards (#1828)
Closes #1820

Reviewed-on: OpenWF/SpaceNinjaServer#1828
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:52:42 -07:00
506365f97e feat: auto-generate debt token vendor manifest (#1827)
Yet another pretty big change to how these things are generated, but getting closer to where we wanna be now.

Reviewed-on: OpenWF/SpaceNinjaServer#1827
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:52:31 -07:00
70646160c3 fix: give no rewards if there are no qualifications (#1826)
Fixes #1823

Reviewed-on: OpenWF/SpaceNinjaServer#1826
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:52:16 -07:00
3ffa4a7fd3 fix: exclude some more nodes from syndicate missions (#1825)
Closes #1819

Reviewed-on: OpenWF/SpaceNinjaServer#1825
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:51:54 -07:00
826a09a473 fix: provide a response to setShipFavouriteLoadout (#1824)
Seems to be the same format as the request, so just mirror it back. This is so the client knows we acknowledged the change as it won't resync the ship until the next login.

Closes #1822

Reviewed-on: OpenWF/SpaceNinjaServer#1824
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:51:43 -07:00
100aefcee4 fix: give corresponding weapon when crafting Hound (#1816)
Reviewed-on: OpenWF/SpaceNinjaServer#1816
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-24 11:24:53 -07:00
409c089d11 feat: handle account already owning a nightwave skin item (#1814)
Closes #1811

Reviewed-on: OpenWF/SpaceNinjaServer#1814
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-24 11:24:38 -07:00
8c32dc2670 fix: add MoaPets into sellController (#1813)
Reviewed-on: OpenWF/SpaceNinjaServer#1813
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-24 11:24:25 -07:00
a67f99b665 chore: don't use sequential values as RNG seeds directly (#1812)
This should help get a slightly better distribution

Reviewed-on: OpenWF/SpaceNinjaServer#1812
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-24 11:24:11 -07:00
756a01d270 fix: pass Emblem field on in getGuildClient 2025-04-24 05:36:16 +02:00
37 changed files with 1265 additions and 612 deletions

View File

@ -2,15 +2,18 @@ const millisecondsPerSecond = 1000;
const secondsPerMinute = 60; const secondsPerMinute = 60;
const minutesPerHour = 60; const minutesPerHour = 60;
const hoursPerDay = 24; const hoursPerDay = 24;
const daysPerWeek = 7;
const unixSecond = millisecondsPerSecond; const unixSecond = millisecondsPerSecond;
const unixMinute = secondsPerMinute * millisecondsPerSecond; const unixMinute = secondsPerMinute * millisecondsPerSecond;
const unixHour = unixMinute * minutesPerHour; const unixHour = unixMinute * minutesPerHour;
const unixDay = hoursPerDay * unixHour; const unixDay = hoursPerDay * unixHour;
const unixWeek = daysPerWeek * unixDay;
export const unixTimesInMs = { export const unixTimesInMs = {
second: unixSecond, second: unixSecond,
minute: unixMinute, minute: unixMinute,
hour: unixHour, hour: unixHour,
day: unixDay day: unixDay,
week: unixWeek
}; };

View File

@ -0,0 +1,41 @@
import { getCalendarProgress, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { getWorldState } from "@/src/services/worldStateService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
// GET request; query parameters: CompletedEventIdx=0&Iteration=4&Version=19&Season=CST_SUMMER
export const completeCalendarEventController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const calendarProgress = getCalendarProgress(inventory);
const currentSeason = getWorldState().KnownCalendarSeasons[0];
let inventoryChanges: IInventoryChanges = {};
let dayIndex = 0;
for (const day of currentSeason.Days) {
if (day.events.length == 0 || day.events[0].type != "CET_CHALLENGE") {
if (dayIndex == calendarProgress.SeasonProgress.LastCompletedDayIdx) {
if (day.events.length != 0) {
const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
if (selection.type == "CET_REWARD") {
inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory))
.InventoryChanges;
} else if (selection.type == "CET_UPGRADE") {
calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
} else if (selection.type != "CET_PLOT") {
throw new Error(`unexpected selection type: ${selection.type}`);
}
}
break;
}
++dayIndex;
}
}
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
CalendarProgress: inventory.CalendarProgress
});
};

View File

@ -3,7 +3,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes"; import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService"; import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
import { getInventory } from "@/src/services/inventoryService"; import { generateRewardSeed, getInventory } from "@/src/services/inventoryService";
import { getInventoryResponse } from "./inventoryController"; import { getInventoryResponse } from "./inventoryController";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes"; import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
@ -63,6 +63,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
missionReport.MissionStatus !== "GS_SUCCESS" && missionReport.MissionStatus !== "GS_SUCCESS" &&
!(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId) !(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
) { ) {
inventory.RewardSeed = generateRewardSeed();
await inventory.save(); await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true); const inventoryResponse = await getInventoryResponse(inventory, true);
res.json({ res.json({
@ -81,6 +82,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
ConquestCompletedMissionsCount ConquestCompletedMissionsCount
} = await addMissionRewards(inventory, missionReport, firstCompletion); } = await addMissionRewards(inventory, missionReport, firstCompletion);
inventory.RewardSeed = generateRewardSeed();
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, IDefaultUpgrade } from "warframe-public-export-plus"; import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
import { Status } from "@/src/types/inventoryTypes/inventoryTypes"; import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
interface IModularCraftRequest { interface IModularCraftRequest {
@ -138,6 +138,18 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
} else { } else {
defaultUpgrades = getDefaultUpgrades(data.Parts); defaultUpgrades = getDefaultUpgrades(data.Parts);
} }
if (category == "MoaPets") {
const weapon = ExportSentinels[data.WeaponType].defaultWeapon;
if (weapon) {
const category = ExportWeapons[weapon].productCategory;
addEquipment(inventory, category, weapon, undefined, inventoryChanges);
combineInventoryChanges(
inventoryChanges,
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
);
}
}
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades); defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites); addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
combineInventoryChanges( combineInventoryChanges(

View File

@ -45,7 +45,7 @@ export const sellController: RequestHandler = async (req, res) => {
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) { if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
requiredFields.add(InventorySlot.SPACEWEAPONS); requiredFields.add(InventorySlot.SPACEWEAPONS);
} }
if (payload.Items.Sentinels || payload.Items.SentinelWeapons) { if (payload.Items.Sentinels || payload.Items.SentinelWeapons || payload.Items.MoaPets) {
requiredFields.add(InventorySlot.SENTINELS); requiredFields.add(InventorySlot.SENTINELS);
} }
if (payload.Items.OperatorAmps) { if (payload.Items.OperatorAmps) {
@ -148,6 +148,12 @@ export const sellController: RequestHandler = async (req, res) => {
freeUpSlot(inventory, InventorySlot.SENTINELS); freeUpSlot(inventory, InventorySlot.SENTINELS);
}); });
} }
if (payload.Items.MoaPets) {
payload.Items.MoaPets.forEach(sellItem => {
inventory.MoaPets.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SENTINELS);
});
}
if (payload.Items.OperatorAmps) { if (payload.Items.OperatorAmps) {
payload.Items.OperatorAmps.forEach(sellItem => { payload.Items.OperatorAmps.forEach(sellItem => {
inventory.OperatorAmps.pull({ _id: sellItem.String }); inventory.OperatorAmps.pull({ _id: sellItem.String });
@ -281,6 +287,7 @@ interface ISellRequest {
SpaceMelee?: ISellItem[]; SpaceMelee?: ISellItem[];
Sentinels?: ISellItem[]; Sentinels?: ISellItem[];
SentinelWeapons?: ISellItem[]; SentinelWeapons?: ISellItem[];
MoaPets?: ISellItem[];
OperatorAmps?: ISellItem[]; OperatorAmps?: ISellItem[];
Hoverboards?: ISellItem[]; Hoverboards?: ISellItem[];
Drones?: ISellItem[]; Drones?: ISellItem[];

View File

@ -20,7 +20,7 @@ export const setShipFavouriteLoadoutController: RequestHandler = async (req, res
throw new Error(`unexpected BootLocation: ${body.BootLocation}`); throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
} }
await personalRooms.save(); await personalRooms.save();
res.json({}); res.json(body);
}; };
interface ISetShipFavouriteLoadoutRequest { interface ISetShipFavouriteLoadoutRequest {

View File

@ -6,6 +6,9 @@ import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { isStoreItem, toStoreItem } from "@/src/services/itemDataService"; import { isStoreItem, toStoreItem } from "@/src/services/itemDataService";
import { logger } from "@/src/utils/logger";
const nightwaveCredsItemType = ExportNightwave.rewards[ExportNightwave.rewards.length - 1].uniqueName;
export const syndicateSacrificeController: RequestHandler = async (request, response) => { export const syndicateSacrificeController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request); const accountId = await getAccountIdForRequest(request);
@ -74,10 +77,14 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
if (!isStoreItem(rewardType)) { if (!isStoreItem(rewardType)) {
rewardType = toStoreItem(rewardType); rewardType = toStoreItem(rewardType);
} }
combineInventoryChanges( const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount))
res.InventoryChanges, .InventoryChanges;
(await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount)).InventoryChanges if (Object.keys(rewardInventoryChanges).length == 0) {
); logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
}
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
} }
} }

View File

@ -20,6 +20,7 @@ import {
TRelicQuality TRelicQuality
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json"; import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json";
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
interface ListedItem { interface ListedItem {
uniqueName: string; uniqueName: string;
@ -51,6 +52,7 @@ interface ItemLists {
OperatorAmps: ListedItem[]; OperatorAmps: ListedItem[];
QuestKeys: ListedItem[]; QuestKeys: ListedItem[];
KubrowPets: ListedItem[]; KubrowPets: ListedItem[];
EvolutionProgress: ListedItem[];
mods: ListedItem[]; mods: ListedItem[];
} }
@ -82,6 +84,7 @@ const getItemListsController: RequestHandler = (req, response) => {
OperatorAmps: [], OperatorAmps: [],
QuestKeys: [], QuestKeys: [],
KubrowPets: [], KubrowPets: [],
EvolutionProgress: [],
mods: [] mods: []
}; };
for (const [uniqueName, item] of Object.entries(ExportWarframes)) { for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
@ -283,6 +286,12 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
} }
for (const uniqueName of allIncarnons) {
res.EvolutionProgress.push({
uniqueName,
name: getString(getItemName(uniqueName) || "", lang)
});
}
response.json(res); response.json(res);
}; };

View File

@ -0,0 +1,33 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const setEvolutionProgressController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = req.body as ISetEvolutionProgressRequest;
inventory.EvolutionProgress ??= [];
payload.forEach(element => {
const entry = inventory.EvolutionProgress!.find(entry => entry.ItemType === element.ItemType);
if (entry) {
entry.Progress = 0;
entry.Rank = element.Rank;
} else {
inventory.EvolutionProgress!.push({
Progress: 0,
Rank: element.Rank,
ItemType: element.ItemType
});
}
});
await inventory.save();
res.end();
};
type ISetEvolutionProgressRequest = {
ItemType: string;
Rank: number;
}[];

View File

@ -390,7 +390,7 @@ MailboxSchema.set("toJSON", {
const DuviriInfoSchema = new Schema<IDuviriInfo>( const DuviriInfoSchema = new Schema<IDuviriInfo>(
{ {
Seed: Number, Seed: BigInt,
NumCompletions: { type: Number, default: 0 } NumCompletions: { type: Number, default: 0 }
}, },
{ {
@ -1125,15 +1125,15 @@ const CustomMarkersSchema = new Schema<ICustomMarkers>(
const calenderProgressSchema = new Schema<ICalendarProgress>( const calenderProgressSchema = new Schema<ICalendarProgress>(
{ {
Version: { type: Number, default: 19 }, Version: { type: Number, default: 19 },
Iteration: { type: Number, default: 2 }, Iteration: { type: Number, required: true },
YearProgress: { YearProgress: {
Upgrades: { type: [] } Upgrades: { type: [String], default: [] }
}, },
SeasonProgress: { SeasonProgress: {
SeasonType: String, SeasonType: { type: String, required: true },
LastCompletedDayIdx: { type: Number, default: -1 }, LastCompletedDayIdx: { type: Number, default: 0 },
LastCompletedChallengeDayIdx: { type: Number, default: -1 }, LastCompletedChallengeDayIdx: { type: Number, default: 0 },
ActivatedChallenges: [] ActivatedChallenges: { type: [String], default: [] }
} }
}, },
{ _id: false } { _id: false }

View File

@ -19,6 +19,7 @@ import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompl
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController"; import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController"; import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController";
import { completeCalendarEventController } from "@/src/controllers/api/completeCalendarEventController";
import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController"; import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController";
import { confirmAllianceInvitationController } from "@/src/controllers/api/confirmAllianceInvitationController"; import { confirmAllianceInvitationController } from "@/src/controllers/api/confirmAllianceInvitationController";
import { confirmGuildInvitationGetController, confirmGuildInvitationPostController } from "@/src/controllers/api/confirmGuildInvitationController"; import { confirmGuildInvitationGetController, confirmGuildInvitationPostController } from "@/src/controllers/api/confirmGuildInvitationController";
@ -158,6 +159,7 @@ apiRouter.get("/changeDojoRoot.php", changeDojoRootController);
apiRouter.get("/changeGuildRank.php", changeGuildRankController); apiRouter.get("/changeGuildRank.php", changeGuildRankController);
apiRouter.get("/checkDailyMissionBonus.php", checkDailyMissionBonusController); apiRouter.get("/checkDailyMissionBonus.php", checkDailyMissionBonusController);
apiRouter.get("/claimLibraryDailyTaskReward.php", claimLibraryDailyTaskRewardController); apiRouter.get("/claimLibraryDailyTaskReward.php", claimLibraryDailyTaskRewardController);
apiRouter.get("/completeCalendarEvent.php", completeCalendarEventController);
apiRouter.get("/confirmAllianceInvitation.php", confirmAllianceInvitationController); apiRouter.get("/confirmAllianceInvitation.php", confirmAllianceInvitationController);
apiRouter.get("/confirmGuildInvitation.php", confirmGuildInvitationGetController); apiRouter.get("/confirmGuildInvitation.php", confirmGuildInvitationGetController);
apiRouter.get("/credits.php", creditsController); apiRouter.get("/credits.php", creditsController);

View File

@ -17,10 +17,11 @@ import { addCurrencyController } from "@/src/controllers/custom/addCurrencyContr
import { addItemsController } from "@/src/controllers/custom/addItemsController"; import { addItemsController } from "@/src/controllers/custom/addItemsController";
import { addXpController } from "@/src/controllers/custom/addXpController"; import { addXpController } from "@/src/controllers/custom/addXpController";
import { importController } from "@/src/controllers/custom/importController"; import { importController } from "@/src/controllers/custom/importController";
import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController";
import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController"; import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController";
const customRouter = express.Router(); const customRouter = express.Router();
@ -42,6 +43,7 @@ customRouter.post("/addItems", addItemsController);
customRouter.post("/addXp", addXpController); customRouter.post("/addXp", addXpController);
customRouter.post("/import", importController); customRouter.post("/import", importController);
customRouter.post("/manageQuests", manageQuestsController); customRouter.post("/manageQuests", manageQuestsController);
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
customRouter.get("/config", getConfigDataController); customRouter.get("/config", getConfigDataController);
customRouter.post("/config", updateConfigDataController); customRouter.post("/config", updateConfigDataController);

View File

@ -105,6 +105,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
Members: members, Members: members,
Ranks: guild.Ranks, Ranks: guild.Ranks,
Tier: guild.Tier, Tier: guild.Tier,
Emblem: guild.Emblem,
Vault: getGuildVault(guild), Vault: getGuildVault(guild),
ActiveDojoColorResearch: guild.ActiveDojoColorResearch, ActiveDojoColorResearch: guild.ActiveDojoColorResearch,
Class: guild.Class, Class: guild.Class,
@ -133,7 +134,7 @@ export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => {
export const getDojoClient = async ( export const getDojoClient = async (
guild: TGuildDatabaseDocument, guild: TGuildDatabaseDocument,
status: number, status: number,
componentId: Types.ObjectId | string | undefined = undefined componentId?: Types.ObjectId | string
): Promise<IDojoClient> => { ): Promise<IDojoClient> => {
const dojo: IDojoClient = { const dojo: IDojoClient = {
_id: { $oid: guild._id.toString() }, _id: { $oid: guild._id.toString() },
@ -553,7 +554,7 @@ export const setGuildTechLogState = (
guild: TGuildDatabaseDocument, guild: TGuildDatabaseDocument,
type: string, type: string,
state: number, state: number,
dateTime: Date | undefined = undefined dateTime?: Date
): boolean => { ): boolean => {
guild.TechChanges ??= []; guild.TechChanges ??= [];
const entry = guild.TechChanges.find(x => x.details == type); const entry = guild.TechChanges.find(x => x.details == type);

View File

@ -169,6 +169,7 @@ const convertPendingRecipe = (client: IPendingRecipeClient): IPendingRecipeDatab
const convertNemesis = (client: INemesisClient): INemesisDatabase => { const convertNemesis = (client: INemesisClient): INemesisDatabase => {
return { return {
...client, ...client,
fp: BigInt(client.fp),
d: convertDate(client.d) d: convertDate(client.d)
}; };
}; };

View File

@ -18,7 +18,6 @@ import {
IKubrowPetEggDatabase, IKubrowPetEggDatabase,
IKubrowPetEggClient, IKubrowPetEggClient,
ILibraryDailyTaskInfo, ILibraryDailyTaskInfo,
ICalendarProgress,
IDroneClient, IDroneClient,
IUpgradeClient, IUpgradeClient,
TPartialStartingGear, TPartialStartingGear,
@ -26,7 +25,8 @@ import {
ICrewMemberClient, ICrewMemberClient,
Status, Status,
IKubrowPetDetailsDatabase, IKubrowPetDetailsDatabase,
ITraits ITraits,
ICalendarProgress
} 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";
@ -78,6 +78,7 @@ import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService"; import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService";
import { createMessage } from "./inboxService"; import { createMessage } from "./inboxService";
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper"; import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
import { getWorldState } from "./worldStateService";
export const createInventory = async ( export const createInventory = async (
accountOwnerId: Types.ObjectId, accountOwnerId: Types.ObjectId,
@ -91,7 +92,6 @@ export const createInventory = async (
}); });
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask(); inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
inventory.CalendarProgress = createCalendar();
inventory.RewardSeed = generateRewardSeed(); inventory.RewardSeed = generateRewardSeed();
inventory.DuviriInfo = { inventory.DuviriInfo = {
Seed: generateRewardSeed(), Seed: generateRewardSeed(),
@ -120,10 +120,15 @@ export const createInventory = async (
} }
}; };
export const generateRewardSeed = (): number => { export const generateRewardSeed = (): bigint => {
const min = -Number.MAX_SAFE_INTEGER; const hiDword = getRandomInt(0, 0x7fffffff);
const max = Number.MAX_SAFE_INTEGER; const loDword = getRandomInt(0, 0xffffffff);
return Math.floor(Math.random() * (max - min + 1)) + min; let seed = (BigInt(hiDword) << 32n) | BigInt(loDword);
if (Math.random() < 0.5) {
seed *= -1n;
seed -= 1n;
}
return seed;
}; };
//TODO: RawUpgrades might need to return a LastAdded //TODO: RawUpgrades might need to return a LastAdded
@ -138,7 +143,7 @@ const awakeningRewards = [
export const addStartingGear = async ( export const addStartingGear = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
startingGear: TPartialStartingGear | undefined = undefined startingGear?: TPartialStartingGear
): Promise<IInventoryChanges> => { ): Promise<IInventoryChanges> => {
const { LongGuns, Pistols, Suits, Melee } = startingGear || { const { LongGuns, Pistols, Suits, Melee } = startingGear || {
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }], LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
@ -240,7 +245,7 @@ export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, del
export const getInventory = async ( export const getInventory = async (
accountOwnerId: string, accountOwnerId: string,
projection: string | undefined = undefined projection?: string
): Promise<TInventoryDatabaseDocument> => { ): Promise<TInventoryDatabaseDocument> => {
const inventory = await Inventory.findOne({ accountOwnerId: accountOwnerId }, projection); const inventory = await Inventory.findOne({ accountOwnerId: accountOwnerId }, projection);
@ -851,7 +856,7 @@ export const addPowerSuit = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
powersuitName: string, powersuitName: string,
inventoryChanges: IInventoryChanges = {}, inventoryChanges: IInventoryChanges = {},
features: number | undefined = undefined features?: number
): Promise<IInventoryChanges> => { ): Promise<IInventoryChanges> => {
const powersuit = ExportWarframes[powersuitName] as IPowersuit | undefined; const powersuit = ExportWarframes[powersuitName] as IPowersuit | undefined;
const exalted = powersuit?.exalted ?? []; const exalted = powersuit?.exalted ?? [];
@ -883,7 +888,7 @@ export const addMechSuit = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
mechsuitName: string, mechsuitName: string,
inventoryChanges: IInventoryChanges = {}, inventoryChanges: IInventoryChanges = {},
features: number | undefined = undefined features?: number
): Promise<IInventoryChanges> => { ): Promise<IInventoryChanges> => {
const powersuit = ExportWarframes[mechsuitName] as IPowersuit | undefined; const powersuit = ExportWarframes[mechsuitName] as IPowersuit | undefined;
const exalted = powersuit?.exalted ?? []; const exalted = powersuit?.exalted ?? [];
@ -935,7 +940,7 @@ export const addSpaceSuit = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
spacesuitName: string, spacesuitName: string,
inventoryChanges: IInventoryChanges = {}, inventoryChanges: IInventoryChanges = {},
features: number | undefined = undefined features?: number
): IInventoryChanges => { ): IInventoryChanges => {
const suitIndex = const suitIndex =
inventory.SpaceSuits.push({ inventory.SpaceSuits.push({
@ -1194,9 +1199,9 @@ export const addEquipment = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
category: TEquipmentKey, category: TEquipmentKey,
type: string, type: string,
modularParts: string[] | undefined = undefined, modularParts?: string[],
inventoryChanges: IInventoryChanges = {}, inventoryChanges: IInventoryChanges = {},
defaultOverwrites: Partial<IEquipmentDatabase> | undefined = undefined defaultOverwrites?: Partial<IEquipmentDatabase>
): IInventoryChanges => { ): IInventoryChanges => {
const equipment = Object.assign( const equipment = Object.assign(
{ {
@ -1236,12 +1241,16 @@ export const addSkin = (
typeName: string, typeName: string,
inventoryChanges: IInventoryChanges = {} inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => { ): IInventoryChanges => {
const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1; if (inventory.WeaponSkins.find(x => x.ItemType == typeName)) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
inventoryChanges.WeaponSkins ??= []; } else {
(inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push( const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1;
inventory.WeaponSkins[index].toJSON<IWeaponSkinClient>() // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
); inventoryChanges.WeaponSkins ??= [];
(inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push(
inventory.WeaponSkins[index].toJSON<IWeaponSkinClient>()
);
}
return inventoryChanges; return inventoryChanges;
}; };
@ -1752,20 +1761,6 @@ export const createLibraryDailyTask = (): ILibraryDailyTaskInfo => {
}; };
}; };
const createCalendar = (): ICalendarProgress => {
return {
Version: 19,
Iteration: 2,
YearProgress: { Upgrades: [] },
SeasonProgress: {
SeasonType: "CST_SPRING",
LastCompletedDayIdx: -1,
LastCompletedChallengeDayIdx: -1,
ActivatedChallenges: []
}
};
};
export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void => { export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void => {
inventory.Affiliations.push({ inventory.Affiliations.push({
Title: 1, Title: 1,
@ -1802,3 +1797,37 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
LibrarySyndicate.FreeFavorsEarned = undefined; LibrarySyndicate.FreeFavorsEarned = undefined;
} }
}; };
export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICalendarProgress => {
const currentSeason = getWorldState().KnownCalendarSeasons[0];
if (!inventory.CalendarProgress) {
inventory.CalendarProgress = {
Version: 19,
Iteration: currentSeason.YearIteration,
YearProgress: {
Upgrades: []
},
SeasonProgress: {
SeasonType: currentSeason.Season,
LastCompletedDayIdx: 0,
LastCompletedChallengeDayIdx: 0,
ActivatedChallenges: []
}
};
}
const yearRolledOver = inventory.CalendarProgress.Iteration != currentSeason.YearIteration;
if (yearRolledOver) {
inventory.CalendarProgress.Iteration = currentSeason.YearIteration;
inventory.CalendarProgress.YearProgress.Upgrades = [];
}
if (yearRolledOver || inventory.CalendarProgress.SeasonProgress.SeasonType != currentSeason.Season) {
inventory.CalendarProgress.SeasonProgress.SeasonType = currentSeason.Season;
inventory.CalendarProgress.SeasonProgress.LastCompletedDayIdx = -1;
inventory.CalendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = -1;
inventory.CalendarProgress.SeasonProgress.ActivatedChallenges = [];
}
return inventory.CalendarProgress;
};

View File

@ -33,6 +33,7 @@ import {
addStanding, addStanding,
combineInventoryChanges, combineInventoryChanges,
generateRewardSeed, generateRewardSeed,
getCalendarProgress,
updateCurrency, updateCurrency,
updateSyndicate updateSyndicate
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
@ -73,13 +74,11 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
return [rewardInfo.rewardTier]; return [rewardInfo.rewardTier];
} }
// Aborting a railjack mission should not give any rewards (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741) const rotationCount = rewardInfo.rewardQualifications?.length || 0;
if (rewardInfo.rewardQualifications === undefined) {
return [];
}
const rotationCount = rewardInfo.rewardQualifications.length || 0; // Empty or absent rewardQualifications should not give rewards:
if (rotationCount === 0) return [0]; // - Aborting a railjack mission (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741)
// - Completing only 1 zone of (E)SO (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1823)
const rotationPattern = const rotationPattern =
tierOverride === undefined tierOverride === undefined
@ -562,6 +561,22 @@ export const addMissionInventoryUpdates = async (
} }
break; break;
} }
case "CalendarProgress": {
const calendarProgress = getCalendarProgress(inventory);
for (const progress of value) {
const challengeName = progress.challenge.substring(progress.challenge.lastIndexOf("/") + 1);
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx++;
calendarProgress.SeasonProgress.ActivatedChallenges.push(challengeName);
}
break;
}
case "duviriCaveOffers": {
// Duviri cave offers (generated with the duviri seed) change after completing one of its game modes (not when aborting).
if (inventoryUpdates.MissionStatus != "GS_QUIT") {
inventory.DuviriInfo.Seed = generateRewardSeed();
}
break;
}
default: default:
// Equipment XP updates // Equipment XP updates
if (equipmentKeys.includes(key as TEquipmentKey)) { if (equipmentKeys.includes(key as TEquipmentKey)) {
@ -742,11 +757,6 @@ export const addMissionRewards = async (
return { MissionRewards: [] }; return { MissionRewards: [] };
} }
if (rewardInfo.rewardSeed) {
// We're using a reward seed, so give the client a new one in the response. On live, missionInventoryUpdate seems to always provide a fresh one in the response.
inventory.RewardSeed = generateRewardSeed();
}
//TODO: check double reward merging //TODO: check double reward merging
const MissionRewards: IMissionReward[] = getRandomMissionDrops(inventory, rewardInfo, wagerTier, firstCompletion); const MissionRewards: IMissionReward[] = getRandomMissionDrops(inventory, rewardInfo, wagerTier, firstCompletion);
logger.debug("random mission drops:", MissionRewards); logger.debug("random mission drops:", MissionRewards);
@ -792,7 +802,8 @@ export const addMissionRewards = async (
missions.Tag != "SolNode761" && // the index missions.Tag != "SolNode761" && // the index
missions.Tag != "SolNode762" && // the index missions.Tag != "SolNode762" && // the index
missions.Tag != "SolNode763" && // the index missions.Tag != "SolNode763" && // the index
missions.Tag != "CrewBattleNode556" // free flight missions.Tag != "CrewBattleNode556" && // free flight
getRotations(rewardInfo).length > 0 // (E)SO should not give credits for only completing zone 1, in which case it has no rewardQualifications (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1823)
) { ) {
const levelCreditReward = getLevelCreditRewards(node); const levelCreditReward = getLevelCreditRewards(node);
missionCompletionCredits += levelCreditReward; missionCompletionCredits += levelCreditReward;
@ -804,6 +815,14 @@ export const addMissionRewards = async (
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo); missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo);
} }
if (rewardInfo.sortieTag == "Mission1") {
missionCompletionCredits += 20_000;
} else if (rewardInfo.sortieTag == "Mission2") {
missionCompletionCredits += 30_000;
} else if (rewardInfo.sortieTag == "Final") {
missionCompletionCredits += 50_000;
}
if (missions.Tag == "PlutoToErisJunction") { if (missions.Tag == "PlutoToErisJunction") {
await createMessage(inventory.accountOwnerId, [ await createMessage(inventory.accountOwnerId, [
{ {
@ -1406,6 +1425,11 @@ function getRandomMissionDrops(
if (rewardManifests.length != 0) { if (rewardManifests.length != 0) {
logger.debug(`generating random mission rewards`, { rewardManifests, rotations }); logger.debug(`generating random mission rewards`, { rewardManifests, rotations });
} }
if (RewardInfo.rewardSeed) {
if (RewardInfo.rewardSeed != inventory.RewardSeed) {
logger.warn(`RewardSeed mismatch:`, { client: RewardInfo.rewardSeed, database: inventory.RewardSeed });
}
}
const rng = new SRng(BigInt(RewardInfo.rewardSeed ?? generateRewardSeed()) ^ 0xffffffffffffffffn); const rng = new SRng(BigInt(RewardInfo.rewardSeed ?? generateRewardSeed()) ^ 0xffffffffffffffffn);
rewardManifests.forEach(name => { rewardManifests.forEach(name => {
const table = ExportRewards[name]; const table = ExportRewards[name];

View File

@ -97,9 +97,17 @@ export class CRng {
} }
randomInt(min: number, max: number): number { randomInt(min: number, max: number): number {
min = Math.ceil(min); const diff = max - min;
max = Math.floor(max); if (diff != 0) {
return Math.floor(this.random() * (max - min + 1)) + min; if (diff < 0) {
throw new Error(`max must be greater than min`);
}
if (diff > 0x3fffffff) {
throw new Error(`insufficient entropy`);
}
min += Math.floor(this.random() * (diff + 1));
}
return min;
} }
randomElement<T>(arr: T[]): T { randomElement<T>(arr: T[]): T {

View File

@ -1,13 +1,8 @@
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "@/src/constants/timeConstants";
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 { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
IItemManifestPreprocessed, import { ExportVendors, IRange } from "warframe-public-export-plus";
IRawVendorManifest,
IVendorInfo,
IVendorManifestPreprocessed
} from "@/src/types/vendorTypes";
import { ExportVendors } from "warframe-public-export-plus";
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json"; import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json"; import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
@ -23,7 +18,6 @@ import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorIn
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json"; import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json"; import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json"; import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
import GuildAdvertisementVendorManifest from "@/static/fixed_responses/getVendorInfo/GuildAdvertisementVendorManifest.json";
import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json"; import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json";
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json"; import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json"; import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
@ -32,14 +26,14 @@ import OstronFishmongerVendorManifest from "@/static/fixed_responses/getVendorIn
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json"; import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json";
import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.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 SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json"; import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json";
import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json"; import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json";
import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json";
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json"; import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json"; import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
const rawVendorManifests: IRawVendorManifest[] = [ const rawVendorManifests: IVendorManifest[] = [
ArchimedeanVendorManifest, ArchimedeanVendorManifest,
DeimosEntratiFragmentVendorProductsManifest, DeimosEntratiFragmentVendorProductsManifest,
DeimosFishmongerVendorManifest, DeimosFishmongerVendorManifest,
@ -54,7 +48,6 @@ const rawVendorManifests: IRawVendorManifest[] = [
DuviriAcrithisVendorManifest, DuviriAcrithisVendorManifest,
EntratiLabsEntratiLabsCommisionsManifest, EntratiLabsEntratiLabsCommisionsManifest,
EntratiLabsEntratiLabVendorManifest, EntratiLabsEntratiLabVendorManifest,
GuildAdvertisementVendorManifest, // uses preprocessing
HubsIronwakeDondaVendorManifest, // uses preprocessing HubsIronwakeDondaVendorManifest, // uses preprocessing
HubsRailjackCrewMemberVendorManifest, HubsRailjackCrewMemberVendorManifest,
MaskSalesmanManifest, MaskSalesmanManifest,
@ -63,16 +56,16 @@ const rawVendorManifests: IRawVendorManifest[] = [
OstronPetVendorManifest, OstronPetVendorManifest,
OstronProspectorVendorManifest, OstronProspectorVendorManifest,
RadioLegionIntermission12VendorManifest, RadioLegionIntermission12VendorManifest,
SolarisDebtTokenVendorManifest,
SolarisDebtTokenVendorRepossessionsManifest, SolarisDebtTokenVendorRepossessionsManifest,
SolarisFishmongerVendorManifest, SolarisFishmongerVendorManifest,
SolarisProspectorVendorManifest, SolarisProspectorVendorManifest,
Temple1999VendorManifest,
TeshinHardModeVendorManifest, // uses preprocessing TeshinHardModeVendorManifest, // uses preprocessing
ZarimanCommisionsManifestArchimedean ZarimanCommisionsManifestArchimedean
]; ];
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> { interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
cycleStart: number; cycleOffset?: number;
cycleDuration: number; cycleDuration: number;
} }
@ -84,7 +77,7 @@ const generatableVendors: IGeneratableVendorInfo[] = [
RandomSeedType: "VRST_WEAPON", RandomSeedType: "VRST_WEAPON",
RequiredGoalTag: "", RequiredGoalTag: "",
WeaponUpgradeValueAttenuationExponent: 2.25, WeaponUpgradeValueAttenuationExponent: 2.25,
cycleStart: 1740960000_000, cycleOffset: 1740960000_000,
cycleDuration: 4 * unixTimesInMs.day cycleDuration: 4 * unixTimesInMs.day
}, },
{ {
@ -93,8 +86,22 @@ const generatableVendors: IGeneratableVendorInfo[] = [
PropertyTextHash: "34F8CF1DFF745F0D67433A5EF0A03E70", PropertyTextHash: "34F8CF1DFF745F0D67433A5EF0A03E70",
RandomSeedType: "VRST_WEAPON", RandomSeedType: "VRST_WEAPON",
WeaponUpgradeValueAttenuationExponent: 2.25, WeaponUpgradeValueAttenuationExponent: 2.25,
cycleStart: 1744934400_000, cycleOffset: 1744934400_000,
cycleDuration: 4 * unixTimesInMs.day cycleDuration: 4 * unixTimesInMs.day
},
{
_id: { $oid: "5be4a159b144f3cdf1c22efa" },
TypeName: "/Lotus/Types/Game/VendorManifests/Solaris/DebtTokenVendorManifest",
PropertyTextHash: "A39621049CA3CA13761028CD21C239EF",
RandomSeedType: "VRST_FLAVOUR_TEXT",
cycleDuration: unixTimesInMs.hour
},
{
_id: { $oid: "61ba123467e5d37975aeeb03" },
TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
PropertyTextHash: "255AFE2169BAE4130B4B20D7C55D14FA",
RandomSeedType: "VRST_FLAVOUR_TEXT",
cycleDuration: unixTimesInMs.week
} }
// { // {
// _id: { $oid: "5dbb4c41e966f7886c3ce939" }, // _id: { $oid: "5dbb4c41e966f7886c3ce939" },
@ -103,7 +110,7 @@ const generatableVendors: IGeneratableVendorInfo[] = [
// } // }
]; ];
export const getVendorManifestByTypeName = (typeName: string): IVendorManifestPreprocessed | undefined => { export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) { for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo.TypeName == typeName) { if (vendorManifest.VendorInfo.TypeName == typeName) {
return preprocessVendorManifest(vendorManifest); return preprocessVendorManifest(vendorManifest);
@ -117,7 +124,7 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifestPr
return undefined; return undefined;
}; };
export const getVendorManifestByOid = (oid: string): IVendorManifestPreprocessed | undefined => { export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) { for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo._id.$oid == oid) { if (vendorManifest.VendorInfo._id.$oid == oid) {
return preprocessVendorManifest(vendorManifest); return preprocessVendorManifest(vendorManifest);
@ -131,29 +138,20 @@ export const getVendorManifestByOid = (oid: string): IVendorManifestPreprocessed
return undefined; return undefined;
}; };
const preprocessVendorManifest = (originalManifest: IRawVendorManifest): IVendorManifestPreprocessed => { const preprocessVendorManifest = (originalManifest: IVendorManifest): IVendorManifest => {
if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) { if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) {
const manifest = structuredClone(originalManifest); const manifest = structuredClone(originalManifest);
const info = manifest.VendorInfo; const info = manifest.VendorInfo;
refreshExpiry(info.Expiry); refreshExpiry(info.Expiry);
for (const offer of info.ItemManifest) { for (const offer of info.ItemManifest) {
const iteration = refreshExpiry(offer.Expiry); refreshExpiry(offer.Expiry);
if (offer.ItemPrices) {
for (const price of offer.ItemPrices) {
if (typeof price.ItemType != "string") {
const itemSeed = parseInt(offer.Id.$oid.substring(16), 16);
const rng = new CRng(mixSeeds(itemSeed, iteration));
price.ItemType = rng.randomElement(price.ItemType);
}
}
}
} }
return manifest as IVendorManifestPreprocessed; return manifest;
} }
return originalManifest as IVendorManifestPreprocessed; return originalManifest;
}; };
const refreshExpiry = (expiry: IMongoDate): number => { const refreshExpiry = (expiry: IMongoDate): void => {
const period = parseInt(expiry.$date.$numberLong); const period = parseInt(expiry.$date.$numberLong);
if (Date.now() >= period) { if (Date.now() >= period) {
const epoch = 1734307200_000; // Monday (for weekly schedules) const epoch = 1734307200_000; // Monday (for weekly schedules)
@ -161,65 +159,136 @@ const refreshExpiry = (expiry: IMongoDate): number => {
const start = epoch + iteration * period; const start = epoch + iteration * period;
const end = start + period; const end = start + period;
expiry.$date.$numberLong = end.toString(); expiry.$date.$numberLong = end.toString();
return iteration;
} }
return 0;
}; };
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifestPreprocessed => { const toRange = (value: IRange | number): IRange => {
const EPOCH = vendorInfo.cycleStart; if (typeof value == "number") {
const manifest = ExportVendors[vendorInfo.TypeName]; return { minValue: value, maxValue: value };
let binThisCycle;
if (manifest.isOneBinPerCycle) {
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.
} }
const items: IItemManifestPreprocessed[] = []; return value;
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER; };
for (let i = 0; i != manifest.items.length; ++i) {
const rawItem = manifest.items[i]; const vendorInfoCache: Record<string, IVendorInfo> = {};
if (manifest.isOneBinPerCycle && rawItem.bin != binThisCycle) {
continue; const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
if (!(vendorInfo.TypeName in vendorInfoCache)) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
vendorInfoCache[vendorInfo.TypeName] = {
...clientVendorInfo,
ItemManifest: [],
Expiry: { $date: { $numberLong: "0" } }
};
}
const processed = vendorInfoCache[vendorInfo.TypeName];
if (Date.now() >= parseInt(processed.Expiry.$date.$numberLong)) {
// Remove expired offers
for (let i = 0; i != processed.ItemManifest.length; ) {
if (Date.now() >= parseInt(processed.ItemManifest[i].Expiry.$date.$numberLong)) {
processed.ItemManifest.splice(i, 1);
} else {
++i;
}
} }
const cycleDuration = vendorInfo.cycleDuration; // rawItem.durationHours! * 3600_000;
const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration); // Add new offers
const cycleStart = EPOCH + cycleIndex * cycleDuration; const vendorSeed = parseInt(vendorInfo._id.$oid.substring(16), 16);
const cycleEnd = cycleStart + cycleDuration; const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000;
if (soonestOfferExpiry > cycleEnd) { const cycleDuration = vendorInfo.cycleDuration;
soonestOfferExpiry = cycleEnd; const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration);
const rng = new CRng(mixSeeds(vendorSeed, cycleIndex));
const manifest = ExportVendors[vendorInfo.TypeName];
const offersToAdd = [];
if (manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue) {
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
while (processed.ItemManifest.length + offersToAdd.length < numItemsTarget) {
// TODO: Consider per-bin item limits
// TODO: Consider item probability weightings
offersToAdd.push(rng.randomElement(manifest.items));
}
} else {
let binThisCycle;
if (manifest.isOneBinPerCycle) {
binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
}
for (const rawItem of manifest.items) {
if (!manifest.isOneBinPerCycle || rawItem.bin == binThisCycle) {
offersToAdd.push(rawItem);
}
}
// For most vendors, the offers seem to roughly be in reverse order from the manifest. Coda weapons are an odd exception.
if (!manifest.isOneBinPerCycle) {
offersToAdd.reverse();
}
} }
const rng = new CRng(cycleIndex); const cycleStart = cycleOffset + cycleIndex * cycleDuration;
rng.churnSeed(i); for (const rawItem of offersToAdd) {
/*for (let j = -1; j != rawItem.duplicates; ++j)*/ { const durationHoursRange = toRange(rawItem.durationHours);
const item: IItemManifestPreprocessed = { const expiry =
cycleStart +
rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour;
const item: IItemManifest = {
StoreItem: rawItem.storeItem, StoreItem: rawItem.storeItem,
ItemPrices: rawItem.itemPrices!.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })), ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
Bin: "BIN_" + rawItem.bin, Bin: "BIN_" + rawItem.bin,
QuantityMultiplier: 1, QuantityMultiplier: 1,
Expiry: { $date: { $numberLong: cycleEnd.toString() } }, Expiry: { $date: { $numberLong: expiry.toString() } },
AllowMultipurchase: false, AllowMultipurchase: false,
Id: { Id: {
$oid: $oid:
i.toString(16).padStart(8, "0") + ((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
vendorInfo._id.$oid.substring(8, 16) + vendorInfo._id.$oid.substring(8, 16) +
rng.randomInt(0, 0xffffffff).toString(16).padStart(8, "0") rng.randomInt(0, 0xffff).toString(16).padStart(4, "0") +
rng.randomInt(0, 0xffff).toString(16).padStart(4, "0")
} }
}; };
if (vendorInfo.RandomSeedType) { if (rawItem.numRandomItemPrices) {
item.LocTagRandSeed = item.ItemPrices = [];
(BigInt(rng.randomInt(0, 0xffffffff)) << 32n) | BigInt(rng.randomInt(0, 0xffffffff)); for (let i = 0; i != rawItem.numRandomItemPrices; ++i) {
let itemPrice: { type: string; count: IRange };
do {
itemPrice = rng.randomElement(manifest.randomItemPricesPerBin![rawItem.bin]);
} while (item.ItemPrices.find(x => x.ItemType == itemPrice.type));
item.ItemPrices.push({
ItemType: itemPrice.type,
ItemCount: rng.randomInt(itemPrice.count.minValue, itemPrice.count.maxValue),
ProductCategory: "MiscItems"
});
}
} }
items.push(item); if (rawItem.credits) {
const value =
typeof rawItem.credits == "number"
? rawItem.credits
: rng.randomInt(
rawItem.credits.minValue / rawItem.credits.step,
rawItem.credits.maxValue / rawItem.credits.step
) * rawItem.credits.step;
item.RegularPrice = [value, value];
}
if (vendorInfo.RandomSeedType) {
item.LocTagRandSeed = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
if (vendorInfo.RandomSeedType == "VRST_WEAPON") {
const highDword = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
item.LocTagRandSeed = (BigInt(highDword) << 32n) | BigInt(item.LocTagRandSeed);
}
}
processed.ItemManifest.push(item);
} }
// Update vendor expiry
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
for (const offer of processed.ItemManifest) {
const offerExpiry = parseInt(offer.Expiry.$date.$numberLong);
if (soonestOfferExpiry > offerExpiry) {
soonestOfferExpiry = offerExpiry;
}
}
processed.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { cycleStart, cycleDuration, ...clientVendorInfo } = vendorInfo;
return { return {
VendorInfo: { VendorInfo: processed
...clientVendorInfo,
ItemManifest: items,
Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } }
}
}; };
}; };

View File

@ -7,6 +7,7 @@ import { CRng } from "@/src/services/rngService";
import { eMissionType, ExportNightwave, ExportRegions, IRegion } from "warframe-public-export-plus"; import { eMissionType, ExportNightwave, ExportRegions, IRegion } from "warframe-public-export-plus";
import { import {
ICalendarDay, ICalendarDay,
ICalendarEvent,
ICalendarSeason, ICalendarSeason,
ILiteSortie, ILiteSortie,
ISeasonChallenge, ISeasonChallenge,
@ -186,10 +187,12 @@ const pushSyndicateMissions = (
for (const [key, value] of Object.entries(ExportRegions)) { for (const [key, value] of Object.entries(ExportRegions)) {
if ( if (
!isArchwingMission(value) && !isArchwingMission(value) &&
value.systemIndex != 23 && // no 1999 stuff !value.questReq && // Exclude zariman, murmor, and 1999 stuff
!value.hidden && // Exclude the index
!value.darkSectorData && // Exclude dark sectors
value.missionIndex != 10 && // Exclude MT_PVP (for relays) value.missionIndex != 10 && // Exclude MT_PVP (for relays)
value.missionIndex != 23 && // no junctions value.missionIndex != 23 && // no junctions
value.missionIndex <= 28 // no railjack or some such value.missionIndex < 28 // no open worlds, railjack, etc
) { ) {
nodeOptions.push(key); nodeOptions.push(key);
} }
@ -206,7 +209,7 @@ const pushSyndicateMissions = (
const dayStart = getSortieTime(day); const dayStart = getSortieTime(day);
const dayEnd = getSortieTime(day + 1); const dayEnd = getSortieTime(day + 1);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + idSuffix }, _id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + idSuffix },
Activation: { $date: { $numberLong: dayStart.toString() } }, Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } }, Expiry: { $date: { $numberLong: dayEnd.toString() } },
Tag: syndicateTag, Tag: syndicateTag,
@ -225,7 +228,8 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
return; return;
} }
const rng = new CRng(day); const seed = new CRng(day).randomInt(0, 0xffff);
const rng = new CRng(seed);
const boss = rng.randomElement(sortieBosses); const boss = rng.randomElement(sortieBosses);
@ -350,11 +354,11 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
} }
worldState.Sorties.push({ worldState.Sorties.push({
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" }, _id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "d4d932c97c0a3acd" },
Activation: { $date: { $numberLong: dayStart.toString() } }, Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } }, Expiry: { $date: { $numberLong: dayEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards", Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
Seed: day, Seed: seed,
Boss: boss, Boss: boss,
Variants: selectedNodes Variants: selectedNodes
}); });
@ -374,7 +378,7 @@ const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
const getSeasonDailyChallenge = (day: number): ISeasonChallenge => { const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
const dayStart = EPOCH + day * 86400000; const dayStart = EPOCH + day * 86400000;
const dayEnd = EPOCH + (day + 3) * 86400000; const dayEnd = EPOCH + (day + 3) * 86400000;
const rng = new CRng(day); const rng = new CRng(new CRng(day).randomInt(0, 0xffff));
return { return {
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") }, _id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
Daily: true, Daily: true,
@ -394,7 +398,7 @@ const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge =>
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
const challengeId = week * 7 + id; const challengeId = week * 7 + id;
const rng = new CRng(challengeId); const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
return { return {
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") }, _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
@ -411,7 +415,7 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
const challengeId = week * 7 + id; const challengeId = week * 7 + id;
const rng = new CRng(challengeId); const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
return { return {
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") }, _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
@ -463,8 +467,8 @@ const birthdays: number[] = [
const getCalendarSeason = (week: number): ICalendarSeason => { const getCalendarSeason = (week: number): ICalendarSeason => {
const seasonIndex = week % 4; const seasonIndex = week % 4;
const seasonDay1 = seasonIndex * 90 + 1; const seasonDay1 = [1, 91, 182, 274][seasonIndex];
const seasonDay91 = seasonIndex * 90 + 91; const seasonDay91 = seasonDay1 + 90;
const eventDays: ICalendarDay[] = []; const eventDays: ICalendarDay[] = [];
for (const day of birthdays) { for (const day of birthdays) {
if (day < seasonDay1) { if (day < seasonDay1) {
@ -476,7 +480,7 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
//logger.debug(`birthday on day ${day}`); //logger.debug(`birthday on day ${day}`);
eventDays.push({ day, events: [] }); // This is how CET_PLOT looks in worldState as of around 38.5.0 eventDays.push({ day, events: [] }); // This is how CET_PLOT looks in worldState as of around 38.5.0
} }
const rng = new CRng(week); const rng = new CRng(new CRng(week).randomInt(0, 0xffff));
const challenges = [ const challenges = [
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy", "/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy",
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium", "/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium",
@ -523,8 +527,12 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
challengeDay = rng.randomInt(chunkDay1, chunkDay13); challengeDay = rng.randomInt(chunkDay1, chunkDay13);
} while (birthdays.indexOf(challengeDay) != -1); } while (birthdays.indexOf(challengeDay) != -1);
const challengeIndex = rng.randomInt(0, challenges.length - 1); let challengeIndex;
const challenge = challenges[challengeIndex]; let challenge;
do {
challengeIndex = rng.randomInt(0, challenges.length - 1);
challenge = challenges[challengeIndex];
} while (i < 2 && !challenge.endsWith("Easy")); // First 2 challenges should be easy
challenges.splice(challengeIndex, 1); challenges.splice(challengeIndex, 1);
//logger.debug(`challenge on day ${challengeDay}`); //logger.debug(`challenge on day ${challengeDay}`);
@ -571,69 +579,100 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
"/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalViolet" "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalViolet"
]; ];
for (let i = 0; i != rewardRanges.length - 1; ++i) { for (let i = 0; i != rewardRanges.length - 1; ++i) {
const rewardIndex = rng.randomInt(0, rewards.length - 1); const events: ICalendarEvent[] = [];
const reward = rewards[rewardIndex]; for (let j = 0; j != 2; ++j) {
rewards.splice(rewardIndex, 1); const rewardIndex = rng.randomInt(0, rewards.length - 1);
events.push({ type: "CET_REWARD", reward: rewards[rewardIndex] });
rewards.splice(rewardIndex, 1);
}
//logger.debug(`trying to fit a reward between day ${rewardRanges[i]} and ${rewardRanges[i + 1]}`); //logger.debug(`trying to fit rewards between day ${rewardRanges[i]} and ${rewardRanges[i + 1]}`);
let day: number; let day: number;
do { do {
day = rng.randomInt(rewardRanges[i] + 1, rewardRanges[i + 1] - 1); day = rng.randomInt(rewardRanges[i] + 1, rewardRanges[i + 1] - 1);
} while (eventDays.find(x => x.day == day)); } while (eventDays.find(x => x.day == day));
eventDays.push({ day, events: [{ type: "CET_REWARD", reward }] }); eventDays.push({ day, events });
} }
const upgrades = [ const upgradesByHexMember = [
"/Lotus/Upgrades/Calendar/MeleeCritChance", [
"/Lotus/Upgrades/Calendar/MeleeAttackSpeed", "/Lotus/Upgrades/Calendar/AttackAndMovementSpeedOnCritMelee",
"/Lotus/Upgrades/Calendar/EnergyOrbToAbilityRange", "/Lotus/Upgrades/Calendar/ElectricalDamageOnBulletJump",
"/Lotus/Upgrades/Calendar/AbilityStrength", "/Lotus/Upgrades/Calendar/ElectricDamagePerDistance",
"/Lotus/Upgrades/Calendar/Armor", "/Lotus/Upgrades/Calendar/ElectricStatusDamageAndChance",
"/Lotus/Upgrades/Calendar/RadiationProcOnTakeDamage", "/Lotus/Upgrades/Calendar/OvershieldCap",
"/Lotus/Upgrades/Calendar/CompanionDamage", "/Lotus/Upgrades/Calendar/SpeedBuffsWhenAirborne"
"/Lotus/Upgrades/Calendar/GasChanceToPrimaryAndSecondary", ],
"/Lotus/Upgrades/Calendar/MagazineCapacity", [
"/Lotus/Upgrades/Calendar/PunchToPrimary", "/Lotus/Upgrades/Calendar/AbilityStrength",
"/Lotus/Upgrades/Calendar/HealingEffects", "/Lotus/Upgrades/Calendar/EnergyOrbToAbilityRange",
"/Lotus/Upgrades/Calendar/EnergyRestoration", "/Lotus/Upgrades/Calendar/MagnetStatusPull",
"/Lotus/Upgrades/Calendar/OvershieldCap", "/Lotus/Upgrades/Calendar/MagnitizeWithinRangeEveryXCasts",
"/Lotus/Upgrades/Calendar/ElectricStatusDamageAndChance", "/Lotus/Upgrades/Calendar/PowerStrengthAndEfficiencyPerEnergySpent",
"/Lotus/Upgrades/Calendar/FinisherChancePerComboMultiplier", "/Lotus/Upgrades/Calendar/SharedFreeAbilityEveryXCasts"
"/Lotus/Upgrades/Calendar/MagnetStatusPull", ],
"/Lotus/Upgrades/Calendar/CompanionsBuffNearbyPlayer", [
"/Lotus/Upgrades/Calendar/StatusChancePerAmmoSpent", "/Lotus/Upgrades/Calendar/EnergyWavesOnCombo",
"/Lotus/Upgrades/Calendar/OrbsDuplicateOnPickup", "/Lotus/Upgrades/Calendar/FinisherChancePerComboMultiplier",
"/Lotus/Upgrades/Calendar/AttackAndMovementSpeedOnCritMelee", "/Lotus/Upgrades/Calendar/MeleeAttackSpeed",
"/Lotus/Upgrades/Calendar/RadialJavelinOnHeavy", "/Lotus/Upgrades/Calendar/MeleeCritChance",
"/Lotus/Upgrades/Calendar/MagnitizeWithinRangeEveryXCasts", "/Lotus/Upgrades/Calendar/MeleeSlideFowardMomentumOnEnemyHit",
"/Lotus/Upgrades/Calendar/CompanionsRadiationChance", "/Lotus/Upgrades/Calendar/RadialJavelinOnHeavy"
"/Lotus/Upgrades/Calendar/BlastEveryXShots", ],
"/Lotus/Upgrades/Calendar/GenerateOmniOrbsOnWeakKill", [
"/Lotus/Upgrades/Calendar/ElectricDamagePerDistance", "/Lotus/Upgrades/Calendar/Armor",
"/Lotus/Upgrades/Calendar/MeleeSlideFowardMomentumOnEnemyHit", "/Lotus/Upgrades/Calendar/CloneActiveCompanionForEnergySpent",
"/Lotus/Upgrades/Calendar/SharedFreeAbilityEveryXCasts", "/Lotus/Upgrades/Calendar/CompanionDamage",
"/Lotus/Upgrades/Calendar/ReviveEnemyAsSpectreOnKill", "/Lotus/Upgrades/Calendar/CompanionsBuffNearbyPlayer",
"/Lotus/Upgrades/Calendar/RefundBulletOnStatusProc", "/Lotus/Upgrades/Calendar/CompanionsRadiationChance",
"/Lotus/Upgrades/Calendar/ExplodingHealthOrbs", "/Lotus/Upgrades/Calendar/RadiationProcOnTakeDamage",
"/Lotus/Upgrades/Calendar/SpeedBuffsWhenAirborne", "/Lotus/Upgrades/Calendar/ReviveEnemyAsSpectreOnKill"
"/Lotus/Upgrades/Calendar/EnergyWavesOnCombo", ],
"/Lotus/Upgrades/Calendar/PowerStrengthAndEfficiencyPerEnergySpent", [
"/Lotus/Upgrades/Calendar/CloneActiveCompanionForEnergySpent", "/Lotus/Upgrades/Calendar/EnergyOrbsGrantShield",
"/Lotus/Upgrades/Calendar/GuidingMissilesChance", "/Lotus/Upgrades/Calendar/EnergyRestoration",
"/Lotus/Upgrades/Calendar/EnergyOrbsGrantShield", "/Lotus/Upgrades/Calendar/ExplodingHealthOrbs",
"/Lotus/Upgrades/Calendar/ElectricalDamageOnBulletJump" "/Lotus/Upgrades/Calendar/GenerateOmniOrbsOnWeakKill",
"/Lotus/Upgrades/Calendar/HealingEffects",
"/Lotus/Upgrades/Calendar/OrbsDuplicateOnPickup"
],
[
"/Lotus/Upgrades/Calendar/BlastEveryXShots",
"/Lotus/Upgrades/Calendar/GasChanceToPrimaryAndSecondary",
"/Lotus/Upgrades/Calendar/GuidingMissilesChance",
"/Lotus/Upgrades/Calendar/MagazineCapacity",
"/Lotus/Upgrades/Calendar/PunchToPrimary",
"/Lotus/Upgrades/Calendar/RefundBulletOnStatusProc",
"/Lotus/Upgrades/Calendar/StatusChancePerAmmoSpent"
]
]; ];
for (let i = 0; i != upgradeRanges.length - 1; ++i) { for (let i = 0; i != upgradeRanges.length - 1; ++i) {
const upgradeIndex = rng.randomInt(0, upgrades.length - 1); // Pick 3 unique hex members
const upgrade = upgrades[upgradeIndex]; const hexMembersPickedForThisDay: number[] = [];
upgrades.splice(upgradeIndex, 1); for (let j = 0; j != 3; ++j) {
let hexMemberIndex: number;
do {
hexMemberIndex = rng.randomInt(0, upgradesByHexMember.length - 1);
} while (hexMembersPickedForThisDay.indexOf(hexMemberIndex) != -1);
hexMembersPickedForThisDay.push(hexMemberIndex);
}
hexMembersPickedForThisDay.sort(); // Always present them in the same order
//logger.debug(`trying to fit an upgrade between day ${upgradeRanges[i]} and ${upgradeRanges[i + 1]}`); // For each hex member, pick an upgrade that was not yet picked this season.
const events: ICalendarEvent[] = [];
for (const hexMemberIndex of hexMembersPickedForThisDay) {
const upgrades = upgradesByHexMember[hexMemberIndex];
const upgradeIndex = rng.randomInt(0, upgrades.length - 1);
events.push({ type: "CET_UPGRADE", upgrade: upgrades[upgradeIndex] });
upgrades.splice(upgradeIndex, 1);
}
//logger.debug(`trying to fit upgrades between day ${upgradeRanges[i]} and ${upgradeRanges[i + 1]}`);
let day: number; let day: number;
do { do {
day = rng.randomInt(upgradeRanges[i] + 1, upgradeRanges[i + 1] - 1); day = rng.randomInt(upgradeRanges[i] + 1, upgradeRanges[i + 1] - 1);
} while (eventDays.find(x => x.day == day)); } while (eventDays.find(x => x.day == day));
eventDays.push({ day, events: [{ type: "CET_UPGRADE", upgrade }] }); eventDays.push({ day, events });
} }
eventDays.sort((a, b) => a.day - b.day); eventDays.sort((a, b) => a.day - b.day);
@ -644,7 +683,7 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Days: eventDays, Days: eventDays,
Season: ["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"][seasonIndex], Season: (["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"] as const)[seasonIndex],
YearIteration: Math.trunc(week / 4), YearIteration: Math.trunc(week / 4),
Version: 19, Version: 19,
UpgradeAvaliabilityRequirements: ["/Lotus/Upgrades/Calendar/1999UpgradeApplicationRequirement"] UpgradeAvaliabilityRequirements: ["/Lotus/Upgrades/Calendar/1999UpgradeApplicationRequirement"]
@ -710,7 +749,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
// 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 = new CRng(week).randomInt(0, 0xffff);
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation // Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
let bountyCycle = Math.trunc(Date.now() / 9000000); let bountyCycle = Math.trunc(Date.now() / 9000000);
@ -719,7 +758,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
const bountyCycleStart = bountyCycle * 9000000; const bountyCycleStart = bountyCycle * 9000000;
bountyCycleEnd = bountyCycleStart + 9000000; bountyCycleEnd = bountyCycleStart + 9000000;
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000029" }, _id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000029" },
Activation: { $date: { $numberLong: bountyCycleStart.toString() } }, Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
Tag: "ZarimanSyndicate", Tag: "ZarimanSyndicate",
@ -727,7 +766,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Nodes: [] Nodes: []
}); });
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000004" }, _id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000004" },
Activation: { $date: { $numberLong: bountyCycleStart.toString() } }, Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
Tag: "EntratiLabSyndicate", Tag: "EntratiLabSyndicate",
@ -735,7 +774,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Nodes: [] Nodes: []
}); });
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000006" }, _id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000006" },
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "HexSyndicate", Tag: "HexSyndicate",
@ -749,14 +788,18 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// TODO: xpAmounts need to be calculated based on the jobType somehow? // TODO: xpAmounts need to be calculated based on the jobType somehow?
const seed = new CRng(bountyCycle).randomInt(0, 0xffff);
{ {
const rng = new CRng(bountyCycle); const rng = new CRng(seed);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000008" }, _id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
},
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "CetusSyndicate", Tag: "CetusSyndicate",
Seed: bountyCycle, Seed: seed,
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
@ -820,13 +863,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
{ {
const rng = new CRng(bountyCycle); const rng = new CRng(seed);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000025" }, _id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
},
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "SolarisSyndicate", Tag: "SolarisSyndicate",
Seed: bountyCycle, Seed: seed,
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
@ -890,13 +935,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
{ {
const rng = new CRng(bountyCycle); const rng = new CRng(seed);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000002" }, _id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
},
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "EntratiSyndicate", Tag: "EntratiSyndicate",
Seed: bountyCycle, Seed: seed,
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
@ -1119,7 +1166,8 @@ export const getLiteSortie = (week: number): ILiteSortie => {
} }
} }
const rng = new CRng(week); const seed = new CRng(week).randomInt(0, 0xffff);
const rng = new CRng(seed);
const firstNodeIndex = rng.randomInt(0, nodes.length - 1); const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
const firstNode = nodes[firstNodeIndex]; const firstNode = nodes[firstNodeIndex];
nodes.splice(firstNodeIndex, 1); nodes.splice(firstNodeIndex, 1);
@ -1128,12 +1176,12 @@ export const getLiteSortie = (week: number): ILiteSortie => {
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
return { return {
_id: { _id: {
$oid: Math.trunc(weekStart / 1000).toString(16) + "5e23a244740a190c" $oid: ((weekStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "5e23a244740a190c"
}, },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards", Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
Seed: week, Seed: seed,
Boss: boss, Boss: boss,
Missions: [ Missions: [
{ {

View File

@ -11,6 +11,7 @@ export interface IGuildClient {
Members: IGuildMemberClient[]; Members: IGuildMemberClient[];
Ranks: IGuildRank[]; Ranks: IGuildRank[];
Tier: number; Tier: number;
Emblem?: boolean;
Vault: IGuildVault; Vault: IGuildVault;
ActiveDojoColorResearch: string; ActiveDojoColorResearch: string;
Class: number; Class: number;

View File

@ -134,7 +134,7 @@ export const equipmentKeys = [
export type TEquipmentKey = (typeof equipmentKeys)[number]; export type TEquipmentKey = (typeof equipmentKeys)[number];
export interface IDuviriInfo { export interface IDuviriInfo {
Seed: number; Seed: bigint;
NumCompletions: number; NumCompletions: number;
} }
@ -202,7 +202,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Mailbox?: IMailboxClient; Mailbox?: IMailboxClient;
SubscribedToEmails: number; SubscribedToEmails: number;
Created: IMongoDate; Created: IMongoDate;
RewardSeed: number | bigint; RewardSeed: bigint;
RegularCredits: number; RegularCredits: number;
PremiumCredits: number; PremiumCredits: number;
PremiumCreditsFree: number; PremiumCreditsFree: number;
@ -353,7 +353,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
DeathSquadable: boolean; DeathSquadable: boolean;
EndlessXP?: IEndlessXpProgress[]; EndlessXP?: IEndlessXpProgress[];
DialogueHistory?: IDialogueHistoryClient; DialogueHistory?: IDialogueHistoryClient;
CalendarProgress: ICalendarProgress; CalendarProgress?: ICalendarProgress;
SongChallenges?: ISongChallenge[]; SongChallenges?: ISongChallenge[];
EntratiVaultCountLastPeriod?: number; EntratiVaultCountLastPeriod?: number;
EntratiVaultCountResetDate?: IMongoDate; EntratiVaultCountResetDate?: IMongoDate;
@ -845,7 +845,7 @@ export interface IMission extends IMissionDatabase {
} }
export interface INemesisBaseClient { export interface INemesisBaseClient {
fp: bigint; fp: bigint | number;
manifest: string; manifest: string;
KillingSuit: string; KillingSuit: string;
killingDamageType: number; killingDamageType: number;
@ -863,7 +863,8 @@ export interface INemesisBaseClient {
Weakened: boolean; Weakened: boolean;
} }
export interface INemesisBaseDatabase extends Omit<INemesisBaseClient, "d"> { export interface INemesisBaseDatabase extends Omit<INemesisBaseClient, "fp" | "d"> {
fp: bigint;
d: Date; d: Date;
} }
@ -877,7 +878,8 @@ export interface INemesisClient extends INemesisBaseClient {
LastEnc: number; LastEnc: number;
} }
export interface INemesisDatabase extends Omit<INemesisClient, "d"> { export interface INemesisDatabase extends Omit<INemesisClient, "fp" | "d"> {
fp: bigint;
d: Date; d: Date;
} }
@ -1193,17 +1195,18 @@ export interface IMarker {
z: number; z: number;
showInHud: boolean; showInHud: boolean;
} }
export interface ISeasonProgress { export interface ISeasonProgress {
SeasonType: "CST_UNDEFINED" | "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL"; SeasonType: "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL";
LastCompletedDayIdx: number; LastCompletedDayIdx: number;
LastCompletedChallengeDayIdx: number; LastCompletedChallengeDayIdx: number;
ActivatedChallenges: unknown[]; ActivatedChallenges: string[];
} }
export interface ICalendarProgress { export interface ICalendarProgress {
Version: number; Version: number;
Iteration: number; Iteration: number;
YearProgress: { Upgrades: unknown[] }; YearProgress: { Upgrades: string[] };
SeasonProgress: ISeasonProgress; SeasonProgress: ISeasonProgress;
} }

View File

@ -44,6 +44,7 @@ export type IMissionInventoryUpdateRequest = {
SyndicateId?: string; SyndicateId?: string;
SortieId?: string; SortieId?: string;
CalendarProgress?: { challenge: string }[];
SeasonChallengeCompletions?: ISeasonChallenge[]; SeasonChallengeCompletions?: ISeasonChallenge[];
AffiliationChanges?: IAffiliationChange[]; AffiliationChanges?: IAffiliationChange[];
crossPlaySetting?: string; crossPlaySetting?: string;
@ -126,6 +127,15 @@ export type IMissionInventoryUpdateRequest = {
creditsFee?: number; // the index creditsFee?: number; // the index
InvasionProgress?: IInvasionProgressClient[]; InvasionProgress?: IInvasionProgressClient[];
ConquestMissionsCompleted?: number; ConquestMissionsCompleted?: number;
duviriSuitSelection?: string;
duviriPistolSelection?: string;
duviriLongGunSelection?: string;
duviriMeleeSelection?: string;
duviriCaveOffers?: {
Seed: number | bigint;
Warframes: string[];
Weapons: string[];
};
} & { } & {
[K in TEquipmentKey]?: IEquipmentClient[]; [K in TEquipmentKey]?: IEquipmentClient[];
}; };
@ -135,7 +145,7 @@ export interface IRewardInfo {
invasionId?: string; invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS"; invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
sortieId?: string; sortieId?: string;
sortieTag?: string; sortieTag?: "Mission1" | "Mission2" | "Final";
sortiePrereqs?: string[]; sortiePrereqs?: string[];
VaultsCracked?: number; // for Spy missions VaultsCracked?: number; // for Spy missions
rewardTier?: number; rewardTier?: number;

View File

@ -1,18 +1,15 @@
import { IMongoDate, IOid } from "./commonTypes"; import { IMongoDate, IOid } from "./commonTypes";
export interface IItemPrice { export interface IItemPrice {
ItemType: string | string[]; // If string[], preprocessing will use RNG to pick one for the current period. ItemType: string;
ItemCount: number; ItemCount: number;
ProductCategory: string; ProductCategory: string;
} }
export interface IItemPricePreprocessed extends Omit<IItemPrice, "ItemType"> {
ItemType: string;
}
export interface IItemManifest { export interface IItemManifest {
StoreItem: string; StoreItem: string;
ItemPrices?: IItemPrice[]; ItemPrices?: IItemPrice[];
RegularPrice?: number[];
Bin: string; Bin: string;
QuantityMultiplier: number; QuantityMultiplier: number;
Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing. Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
@ -23,10 +20,6 @@ export interface IItemManifest {
Id: IOid; Id: IOid;
} }
export interface IItemManifestPreprocessed extends Omit<IItemManifest, "ItemPrices"> {
ItemPrices?: IItemPricePreprocessed[];
}
export interface IVendorInfo { export interface IVendorInfo {
_id: IOid; _id: IOid;
TypeName: string; TypeName: string;
@ -38,14 +31,6 @@ export interface IVendorInfo {
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.
} }
export interface IVendorInfoPreprocessed extends Omit<IVendorInfo, "ItemManifest"> { export interface IVendorManifest {
ItemManifest: IItemManifestPreprocessed[];
}
export interface IRawVendorManifest {
VendorInfo: IVendorInfo; VendorInfo: IVendorInfo;
} }
export interface IVendorManifestPreprocessed {
VendorInfo: IVendorInfoPreprocessed;
}

View File

@ -133,7 +133,7 @@ export interface ISeasonChallenge {
export interface ICalendarSeason { export interface ICalendarSeason {
Activation: IMongoDate; Activation: IMongoDate;
Expiry: IMongoDate; Expiry: IMongoDate;
Season: string; // "CST_UNDEFINED" | "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL" Season: "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL";
Days: ICalendarDay[]; Days: ICalendarDay[];
YearIteration: number; YearIteration: number;
Version: number; Version: number;

View File

@ -0,0 +1,49 @@
[
"/Lotus/Weapons/ClanTech/Bio/BioWeapon",
"/Lotus/Weapons/ClanTech/Energy/EnergyRifle",
"/Lotus/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun",
"/Lotus/Weapons/Corpus/Pistols/CrpHandRL/CorpusHandRocketLauncher",
"/Lotus/Weapons/Grineer/LongGuns/GrineerSawbladeGun/SawBladeGun",
"/Lotus/Weapons/Grineer/Melee/GrineerTylAxeAndBoar/RegorAxeShield",
"/Lotus/Weapons/Grineer/Pistols/HeatGun/GrnHeatGun",
"/Lotus/Weapons/Infested/Pistols/InfVomitGun/InfVomitGunWep",
"/Lotus/Weapons/Syndicates/CephalonSuda/Pistols/CSDroidArray",
"/Lotus/Weapons/Tenno/Bows/HuntingBow",
"/Lotus/Weapons/Tenno/Bows/StalkerBow",
"/Lotus/Weapons/Tenno/LongGuns/TnoLeverAction/TnoLeverActionRifle",
"/Lotus/Weapons/Tenno/Melee/Axe/DualInfestedAxesWeapon",
"/Lotus/Weapons/Tenno/Melee/Dagger/CeramicDagger",
"/Lotus/Weapons/Tenno/Melee/Fist/Fist",
"/Lotus/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer",
"/Lotus/Weapons/Tenno/Melee/LongSword/LongSword",
"/Lotus/Weapons/Tenno/Melee/Maces/PaladinMace/PaladinMaceWeapon",
"/Lotus/Weapons/Tenno/Melee/Scythe/StalkerScytheWeapon",
"/Lotus/Weapons/Tenno/Melee/Scythe/ParisScythe/ParisScythe",
"/Lotus/Weapons/Tenno/Melee/Staff/Staff",
"/Lotus/Weapons/Tenno/Melee/Swords/CutlassAndPoignard/TennoCutlass",
"/Lotus/Weapons/Tenno/Melee/Swords/TennoSai/TennoSais",
"/Lotus/Weapons/Tenno/Pistol/AutoPistol",
"/Lotus/Weapons/Tenno/Pistol/BurstPistol",
"/Lotus/Weapons/Tenno/Pistol/HandShotGun",
"/Lotus/Weapons/Tenno/Pistol/HeavyPistol",
"/Lotus/Weapons/Tenno/Pistol/Pistol",
"/Lotus/Weapons/Tenno/Pistol/RevolverPistol",
"/Lotus/Weapons/Tenno/Pistols/ConclaveLeverPistol/ConclaveLeverPistol",
"/Lotus/Weapons/Tenno/Rifle/BoltoRifle",
"/Lotus/Weapons/Tenno/Rifle/BurstRifle",
"/Lotus/Weapons/Tenno/Rifle/HeavyRifle",
"/Lotus/Weapons/Tenno/Rifle/Rifle",
"/Lotus/Weapons/Tenno/Rifle/SemiAutoRifle",
"/Lotus/Weapons/Tenno/Rifle/TennoAR",
"/Lotus/Weapons/Tenno/Shotgun/FullAutoShotgun",
"/Lotus/Weapons/Tenno/Shotgun/Shotgun",
"/Lotus/Weapons/Tenno/ThrowingWeapons/Kunai",
"/Lotus/Weapons/Tenno/ThrowingWeapons/StalkerKunai",
"/Lotus/Weapons/Tenno/Zariman/LongGuns/PumpShotgun/ZarimanPumpShotgun",
"/Lotus/Weapons/Tenno/Zariman/LongGuns/SemiAutoRifle/ZarimanSemiAutoRifle",
"/Lotus/Weapons/Tenno/Zariman/Melee/Dagger/ZarimanDaggerWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
]

View File

@ -1098,5 +1098,6 @@
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampLarge", "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampLarge",
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampSmall", "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampSmall",
"/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem", "/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem",
"/Lotus/Types/Enemies/Orokin/OrokinMoaBipedAvatar" "/Lotus/Types/Enemies/Orokin/OrokinMoaBipedAvatar",
"/Lotus/Types/Enemies/Grineer/AIWeek/Avatars/BeastMasterAvatar"
] ]

View File

@ -1,101 +0,0 @@
{
"VendorInfo": {
"_id": { "$oid": "61ba123467e5d37975aeeb03" },
"TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementMoon",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 12,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_4",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 79554843,
"Id": { "$oid": "67bbb592e1534511d6c1c1e2" }
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementMountain",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 7,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_3",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2413820225,
"Id": { "$oid": "67bbb592e1534511d6c1c1e3" }
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementStorm",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 3,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3262300883,
"Id": { "$oid": "67bbb592e1534511d6c1c1e4" }
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementShadow",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 20,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2797325750,
"Id": { "$oid": "67bbb592e1534511d6c1c1e5" }
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementGhost",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 554932310,
"Id": { "$oid": "67bbb592e1534511d6c1c1e6" }
}
],
"PropertyTextHash": "255AFE2169BAE4130B4B20D7C55D14FA",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": { "$date": { "$numberLong": "604800000" } }
}
}

View File

@ -1,248 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5be4a159b144f3cdf1c22efa"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Solaris/DebtTokenVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Venus/Resources/VenusCoconutItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/MiscItems/Circuits",
"ItemCount": 3664,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [87300, 87300],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 1881404827,
"Id": {
"$oid": "670daf92d21f34757a5e73b4"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleRareC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/MiscItems/NeuralSensor",
"ItemCount": 1,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [53300, 53300],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 1943984533,
"Id": {
"$oid": "6710b5029e1a3080a65e73a7"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleCommonG",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/MiscItems/Salvage",
"ItemCount": 11540,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [27300, 27300],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 744199559,
"Id": {
"$oid": "67112582cc115756985e73a4"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Solaris/FishParts/CorpusFishThermalLaserItem",
"ItemCount": 9,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [75800, 75800],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 3744711432,
"Id": {
"$oid": "670de7d28a6ec82cd25e73a2"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/MiscItems/Rubedo",
"ItemCount": 3343,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [52200, 52200],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 1579000687,
"Id": {
"$oid": "670e58526171148e125e73ad"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleCommonA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Venus/Resources/CoolantItem",
"ItemCount": 9,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Solaris/FishParts/CorpusFishAnoscopicSensorItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [12400, 12400],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 3589081466,
"Id": {
"$oid": "67112582cc115756985e73a5"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Solaris/SolarisCommonOreBAlloyItem",
"ItemCount": 13,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [77500, 77500],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 1510234814,
"Id": {
"$oid": "670f0f21250ad046c35e73ee"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Solaris/FishParts/CorpusFishParralelBiodeItem",
"ItemCount": 7,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Solaris/SolarisCommonGemBCutItem",
"ItemCount": 12,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [94600, 94600],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 4222095721,
"Id": {
"$oid": "670f63827be40254f95e739d"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleCommonJ",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/MiscItems/Nanospores",
"ItemCount": 14830,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [25600, 25600],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 2694388669,
"Id": {
"$oid": "67112582cc115756985e73a6"
}
}
],
"PropertyTextHash": "A39621049CA3CA13761028CD21C239EF",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -0,0 +1,459 @@
{
"VendorInfo": {
"_id": {
"$oid": "67dadc30e4b6e0e5979c8d56"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleBlueprint",
"ItemPrices": [
{
"ItemCount": 195,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c18c"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleSystemsBlueprint",
"ItemPrices": [
{
"ItemCount": 65,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c18d"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleChassisBlueprint",
"ItemPrices": [
{
"ItemCount": 65,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c18e"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleHelmetBlueprint",
"ItemPrices": [
{
"ItemCount": 65,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c18f"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/1999EntHybridPistolBlueprint",
"ItemPrices": [
{
"ItemCount": 120,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c190"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolBarrelBlueprint",
"ItemPrices": [
{
"ItemCount": 60,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c191"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolReceiverBlueprint",
"ItemPrices": [
{
"ItemCount": 60,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c192"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolStockBlueprint",
"ItemPrices": [
{
"ItemCount": 60,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c193"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumCoreKitA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c194"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumCymbalA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c195"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumFloorTomA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c196"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumSnareA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c197"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c198"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseB",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c199"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseC",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19a"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseD",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19b"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseE",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19c"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseF",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19d"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumSynthKeyboardA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19e"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/PhotoBooth/Vania/PhotoboothTileVaniaObjTempleDefense",
"ItemPrices": [
{
"ItemCount": 100,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": false,
"Id": {
"$oid": "67dadc30641da66dc5c1c19f"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva",
"ItemPrices": [
{
"ItemCount": 110,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 6000,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"PurchaseQuantityLimit": 7,
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c1a5"
}
}
],
"PropertyTextHash": "20B13D9EB78FEC80EA32D0687F5BA1AE",
"RequiredGoalTag": "",
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
}
}
}

View File

@ -401,6 +401,22 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_evolutionProgress"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;">
<input class="form-control" id="acquire-type-EvolutionProgress" list="datalist-EvolutionProgress" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<table class="table table-hover w-100">
<tbody id="EvolutionProgress-list"></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body"> <div class="card-body">
@ -411,6 +427,7 @@
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button> <button class="btn btn-primary" onclick="addMissingEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button> <button class="btn btn-primary" onclick="addMissingEquipment(['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button> <button class="btn btn-primary" onclick="addMissingEquipment(['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEvolutionProgress();" data-loc="inventory_bulkAddEvolutionProgress"></button>
</div> </div>
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-success" onclick="maxRankAllEquipment(['Suits']);" data-loc="inventory_bulkRankUpSuits"></button> <button class="btn btn-success" onclick="maxRankAllEquipment(['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
@ -419,6 +436,7 @@
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></button> <button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Sentinels']);" data-loc="inventory_bulkRankUpSentinels"></button> <button class="btn btn-success" onclick="maxRankAllEquipment(['Sentinels']);" data-loc="inventory_bulkRankUpSentinels"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button> <button class="btn btn-success" onclick="maxRankAllEquipment(['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEvolutions();" data-loc="inventory_bulkRankUpEvolutionProgress"></button>
</div> </div>
</div> </div>
</div> </div>
@ -733,6 +751,7 @@
</datalist> </datalist>
<datalist id="datalist-archonCrystalUpgrades"></datalist> <datalist id="datalist-archonCrystalUpgrades"></datalist>
<datalist id="datalist-OperatorAmps"></datalist> <datalist id="datalist-OperatorAmps"></datalist>
<datalist id="datalist-EvolutionProgress"></datalist>
<datalist id="datalist-ModularParts"></datalist> <datalist id="datalist-ModularParts"></datalist>
<datalist id="datalist-ModularParts-HB_DECK"></datalist> <datalist id="datalist-ModularParts-HB_DECK"></datalist>
<datalist id="datalist-ModularParts-HB_ENGINE"></datalist> <datalist id="datalist-ModularParts-HB_ENGINE"></datalist>

View File

@ -78,18 +78,22 @@ function logout() {
function renameAccount() { function renameAccount() {
const newname = window.prompt(loc("code_changeNameConfirm")); const newname = window.prompt(loc("code_changeNameConfirm"));
if (newname) { if (newname) {
fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => { revalidateAuthz(() => {
$(".displayname").text(newname); fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => {
updateLocElements(); $(".displayname").text(newname);
updateLocElements();
});
}); });
} }
} }
function deleteAccount() { function deleteAccount() {
if (window.confirm(loc("code_deleteAccountConfirm"))) { if (window.confirm(loc("code_deleteAccountConfirm"))) {
fetch("/custom/deleteAccount?" + window.authz).then(() => { revalidateAuthz(() => {
logout(); fetch("/custom/deleteAccount?" + window.authz).then(() => {
single.loadRoute("/webui/"); // Show login screen logout();
single.loadRoute("/webui/"); // Show login screen
});
}); });
} }
} }
@ -183,6 +187,16 @@ const webUiModularWeapons = [
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit" "/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
]; ];
const permanentEvolutionWeapons = new Set([
"/Lotus/Weapons/Tenno/Zariman/LongGuns/PumpShotgun/ZarimanPumpShotgun",
"/Lotus/Weapons/Tenno/Zariman/LongGuns/SemiAutoRifle/ZarimanSemiAutoRifle",
"/Lotus/Weapons/Tenno/Zariman/Melee/Dagger/ZarimanDaggerWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
]);
let uniqueLevelCaps = {}; let uniqueLevelCaps = {};
function fetchItemList() { function fetchItemList() {
window.itemListPromise = new Promise(resolve => { window.itemListPromise = new Promise(resolve => {
@ -563,6 +577,82 @@ function updateInventory() {
}); });
}); });
document.getElementById("EvolutionProgress-list").innerHTML = "";
data.EvolutionProgress?.forEach(item => {
const datalist = document.getElementById("datalist-EvolutionProgress");
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
if (optionToRemove) {
datalist.removeChild(optionToRemove);
}
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.Rank != null) {
td.textContent += " | " + loc("code_rank") + ": [" + item.Rank + "/5]";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (item.Rank < 5) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: 5 }]);
};
a.title = loc("code_maxRank");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
td.appendChild(a);
}
if ((permanentEvolutionWeapons.has(item.ItemType) && item.Rank > 0) || item.Rank > 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank - 1 }]);
};
a.title = loc("code_rankDown");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>`;
td.appendChild(a);
}
if (item.Rank < 5) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank + 1 }]);
};
a.title = loc("code_rankUp");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z"/></svg>`;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("EvolutionProgress-list").appendChild(tr);
});
const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
const giveAllQEvolutionProgress = document.querySelector(
'button[onclick*="addMissingEvolutionProgress()"]'
);
if (datalistEvolutionProgress.length === 0) {
formEvolutionProgress.classList.add("disabled");
formEvolutionProgress.querySelector("input").disabled = true;
formEvolutionProgress.querySelector("button").disabled = true;
giveAllQEvolutionProgress.disabled = true;
}
// Populate quests route // Populate quests route
document.getElementById("QuestKeys-list").innerHTML = ""; document.getElementById("QuestKeys-list").innerHTML = "";
data.QuestKeys.forEach(item => { data.QuestKeys.forEach(item => {
@ -674,18 +764,18 @@ function updateInventory() {
}); });
const datalistQuestKeys = document.querySelectorAll("#datalist-QuestKeys option"); const datalistQuestKeys = document.querySelectorAll("#datalist-QuestKeys option");
const form = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]"); const formQuestKeys = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]");
const giveAllQuestButton = document.querySelector("button[onclick*=\"doBulkQuestUpdate('giveAll')\"]"); const giveAllQuestButton = document.querySelector("button[onclick*=\"doBulkQuestUpdate('giveAll')\"]");
if (datalistQuestKeys.length === 0) { if (datalistQuestKeys.length === 0) {
form.classList.add("disabled"); formQuestKeys.classList.add("disabled");
form.querySelector("input").disabled = true; formQuestKeys.querySelector("input").disabled = true;
form.querySelector("button").disabled = true; formQuestKeys.querySelector("button").disabled = true;
giveAllQuestButton.disabled = true; giveAllQuestButton.disabled = true;
} else { } else {
form.classList.remove("disabled"); formQuestKeys.classList.remove("disabled");
form.querySelector("input").disabled = false; formQuestKeys.querySelector("input").disabled = false;
form.querySelector("button").disabled = false; formQuestKeys.querySelector("button").disabled = false;
giveAllQuestButton.disabled = false; giveAllQuestButton.disabled = false;
} }
@ -1080,6 +1170,16 @@ function doAcquireModularEquipment(category, WeaponType) {
} }
} }
function doAcquireEvolution() {
const uniqueName = getKey(document.getElementById("acquire-type-EvolutionProgress"));
if (!uniqueName) {
$("#acquire-type-EvolutionProgress").addClass("is-invalid").focus();
return;
}
setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
}
$("input[list]").on("input", function () { $("input[list]").on("input", function () {
$(this).removeClass("is-invalid"); $(this).removeClass("is-invalid");
}); });
@ -1117,6 +1217,40 @@ function addMissingEquipment(categories) {
} }
} }
function addMissingEvolutionProgress() {
const requests = [];
document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {
const uniqueName = elm.getAttribute("data-key");
requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
});
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
setEvolutionProgress(requests);
}
}
function maxRankAllEvolutions() {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => {
const requests = [];
data.EvolutionProgress.forEach(item => {
if (item.Rank < 5) {
requests.push({
ItemType: item.ItemType,
Rank: 5
});
}
});
if (Object.keys(requests).length > 0) {
return setEvolutionProgress(requests);
}
toast(loc("code_noEquipmentToRankUp"));
});
}
function maxRankAllEquipment(categories) { function maxRankAllEquipment(categories) {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
@ -1304,6 +1438,19 @@ function maturePet(oid, revert) {
}); });
} }
function setEvolutionProgress(requests) {
revalidateAuthz(() => {
const req = $.post({
url: "/custom/setEvolutionProgress?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
});
});
}
function doAcquireMiscItems() { function doAcquireMiscItems() {
const uniqueName = getKey(document.getElementById("miscitem-type")); const uniqueName = getKey(document.getElementById("miscitem-type"));
if (!uniqueName) { if (!uniqueName) {

View File

@ -34,6 +34,8 @@ dict = {
code_rerollsNumber: `Anzahl der Umrollversuche`, code_rerollsNumber: `Anzahl der Umrollversuche`,
code_viewStats: `Statistiken anzeigen`, code_viewStats: `Statistiken anzeigen`,
code_rank: `Rang`, code_rank: `Rang`,
code_rankUp: `[UNTRANSLATED] Rank up`,
code_rankDown: `[UNTRANSLATED] Rank down`,
code_count: `Anzahl`, code_count: `Anzahl`,
code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`, code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`,
code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
@ -84,18 +86,21 @@ dict = {
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moa`,
inventory_kubrowPets: `Bestien`, inventory_kubrowPets: `Bestien`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`, inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`, inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`, inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`, inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`,
inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`, inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`,
inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`, inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`, inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`, inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`, inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
inventory_bulkRankUpSpaceWeapons: `Alle Archwing-Waffen auf Max. Rang`, inventory_bulkRankUpSpaceWeapons: `Alle Archwing-Waffen auf Max. Rang`,
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`, inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`, quests_completeAll: `Alle Quests abschließen`,

View File

@ -33,6 +33,8 @@ dict = {
code_rerollsNumber: `Number of rerolls`, code_rerollsNumber: `Number of rerolls`,
code_viewStats: `View Stats`, code_viewStats: `View Stats`,
code_rank: `Rank`, code_rank: `Rank`,
code_rankUp: `Rank up`,
code_rankDown: `Rank down`,
code_count: `Count`, code_count: `Count`,
code_focusAllUnlocked: `All focus schools are already unlocked.`, code_focusAllUnlocked: `All focus schools are already unlocked.`,
code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`, code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`,
@ -83,18 +85,21 @@ dict = {
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moa`,
inventory_kubrowPets: `Beasts`, inventory_kubrowPets: `Beasts`,
inventory_evolutionProgress: `Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Add Missing Warframes`, inventory_bulkAddSuits: `Add Missing Warframes`,
inventory_bulkAddWeapons: `Add Missing Weapons`, inventory_bulkAddWeapons: `Add Missing Weapons`,
inventory_bulkAddSpaceSuits: `Add Missing Archwings`, inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`, inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`,
inventory_bulkAddSentinels: `Add Missing Sentinels`, inventory_bulkAddSentinels: `Add Missing Sentinels`,
inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`, inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`,
inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
inventory_bulkRankUpSuits: `Max Rank All Warframes`, inventory_bulkRankUpSuits: `Max Rank All Warframes`,
inventory_bulkRankUpWeapons: `Max Rank All Weapons`, inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`, inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`,
inventory_bulkRankUpSpaceWeapons: `Max Rank All Archwing Weapons`, inventory_bulkRankUpSpaceWeapons: `Max Rank All Archwing Weapons`,
inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, inventory_bulkRankUpSentinels: `Max Rank All Sentinels`,
inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Complete All Quests`, quests_completeAll: `Complete All Quests`,

View File

@ -34,6 +34,8 @@ dict = {
code_rerollsNumber: `Cantidad de reintentos`, code_rerollsNumber: `Cantidad de reintentos`,
code_viewStats: `Ver estadísticas`, code_viewStats: `Ver estadísticas`,
code_rank: `Rango`, code_rank: `Rango`,
code_rankUp: `[UNTRANSLATED] Rank up`,
code_rankDown: `[UNTRANSLATED] Rank down`,
code_count: `Cantidad`, code_count: `Cantidad`,
code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`, code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`,
code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`, code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`,
@ -84,18 +86,21 @@ dict = {
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moa`,
inventory_kubrowPets: `Bestias`, inventory_kubrowPets: `Bestias`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Agregar Warframes faltantes`, inventory_bulkAddSuits: `Agregar Warframes faltantes`,
inventory_bulkAddWeapons: `Agregar armas faltantes`, inventory_bulkAddWeapons: `Agregar armas faltantes`,
inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`, inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`, inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`,
inventory_bulkAddSentinels: `Agregar centinelas faltantes`, inventory_bulkAddSentinels: `Agregar centinelas faltantes`,
inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`, inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`, inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`, inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`, inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
inventory_bulkRankUpSpaceWeapons: `Maximizar rango de todas las armas Archwing`, inventory_bulkRankUpSpaceWeapons: `Maximizar rango de todas las armas Archwing`,
inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`, inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`,
inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
quests_list: `Misiones`, quests_list: `Misiones`,
quests_completeAll: `Completar todas las misiones`, quests_completeAll: `Completar todas las misiones`,

View File

@ -34,6 +34,8 @@ dict = {
code_rerollsNumber: `Nombre de rerolls`, code_rerollsNumber: `Nombre de rerolls`,
code_viewStats: `Voir les stats`, code_viewStats: `Voir les stats`,
code_rank: `Rang`, code_rank: `Rang`,
code_rankUp: `[UNTRANSLATED] Rank up`,
code_rankDown: `[UNTRANSLATED] Rank down`,
code_count: `Quantité`, code_count: `Quantité`,
code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`, code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`,
code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`, code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
@ -84,18 +86,21 @@ dict = {
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moa`,
inventory_kubrowPets: `Bêtes`, inventory_kubrowPets: `Bêtes`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
inventory_bulkAddWeapons: `Ajouter les armes manquantes`, inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`, inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`, inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`,
inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`, inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`,
inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`, inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
inventory_bulkRankUpSuits: `Toutes les Warframes rang max`, inventory_bulkRankUpSuits: `Toutes les Warframes rang max`,
inventory_bulkRankUpWeapons: `Toutes les armes rang max`, inventory_bulkRankUpWeapons: `Toutes les armes rang max`,
inventory_bulkRankUpSpaceSuits: `Tous les Archwings rang max`, inventory_bulkRankUpSpaceSuits: `Tous les Archwings rang max`,
inventory_bulkRankUpSpaceWeapons: `Toutes les armes d'Archwing rang max`, inventory_bulkRankUpSpaceWeapons: `Toutes les armes d'Archwing rang max`,
inventory_bulkRankUpSentinels: `Toutes les Sentinelles rang max`, inventory_bulkRankUpSentinels: `Toutes les Sentinelles rang max`,
inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles rang max`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
quests_list: `Quêtes`, quests_list: `Quêtes`,
quests_completeAll: `Compléter toutes les quêtes`, quests_completeAll: `Compléter toutes les quêtes`,

View File

@ -34,6 +34,8 @@ dict = {
code_rerollsNumber: `Количество циклов`, code_rerollsNumber: `Количество циклов`,
code_viewStats: `Просмотр характеристики`, code_viewStats: `Просмотр характеристики`,
code_rank: `Ранг`, code_rank: `Ранг`,
code_rankUp: `Повысить Ранг`,
code_rankDown: `Понизить Ранг`,
code_count: `Количество`, code_count: `Количество`,
code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`, code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`,
code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
@ -84,18 +86,21 @@ dict = {
inventory_hoverboards: `К-Драйвы`, inventory_hoverboards: `К-Драйвы`,
inventory_moaPets: `МОА`, inventory_moaPets: `МОА`,
inventory_kubrowPets: `Звери`, inventory_kubrowPets: `Звери`,
inventory_evolutionProgress: `Прогресс эволюции Инкарнонов`,
inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`, inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`,
inventory_bulkAddWeapons: `Добавить отсутствующее оружие`, inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`, inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`,
inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`, inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`,
inventory_bulkAddSentinels: `Добавить отсутствующих стражей`, inventory_bulkAddSentinels: `Добавить отсутствующих стражей`,
inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`, inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`,
inventory_bulkAddEvolutionProgress: `Добавить отсуствующий прогресс эволюции Инкарнонов`,
inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`, inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`,
inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`, inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`,
inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`, inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`,
inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`, inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`,
inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`,
quests_list: `Квесты`, quests_list: `Квесты`,
quests_completeAll: `Завершить все квесты`, quests_completeAll: `Завершить все квесты`,

View File

@ -34,6 +34,8 @@ dict = {
code_rerollsNumber: `洗卡次数`, code_rerollsNumber: `洗卡次数`,
code_viewStats: `查看属性`, code_viewStats: `查看属性`,
code_rank: `等级`, code_rank: `等级`,
code_rankUp: `[UNTRANSLATED] Rank up`,
code_rankDown: `[UNTRANSLATED] Rank down`,
code_count: `数量`, code_count: `数量`,
code_focusAllUnlocked: `所有专精学派均已解锁。`, code_focusAllUnlocked: `所有专精学派均已解锁。`,
code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`, code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`,
@ -84,18 +86,21 @@ dict = {
inventory_hoverboards: `K式悬浮板`, inventory_hoverboards: `K式悬浮板`,
inventory_moaPets: `恐鸟`, inventory_moaPets: `恐鸟`,
inventory_kubrowPets: `[UNTRANSLATED] Beasts`, inventory_kubrowPets: `[UNTRANSLATED] Beasts`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失Archwing`, inventory_bulkAddSpaceSuits: `添加缺失Archwing`,
inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`, inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`,
inventory_bulkAddSentinels: `添加缺失守护`, inventory_bulkAddSentinels: `添加缺失守护`,
inventory_bulkAddSentinelWeapons: `添加缺失守护武器`, inventory_bulkAddSentinelWeapons: `添加缺失守护武器`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`, inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`,
inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`, inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`,
inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinels: `所有守护升满级`,
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
quests_list: `任务`, quests_list: `任务`,
quests_completeAll: `完成所有任务`, quests_completeAll: `完成所有任务`,