chore(webui): update Chinese translation #2453

Merged
Sainan merged 9 commits from Corvus/SpaceNinjaServer.CN:translations.CN into main 2025-07-08 22:12:26 -07:00
20 changed files with 456 additions and 132 deletions
Showing only changes of commit c22c376828 - Show all commits

View File

@ -14,7 +14,9 @@ import {
addRecipes,
occupySlot,
combineInventoryChanges,
addKubrowPetPrint
addKubrowPetPrint,
addPowerSuit,
addEquipment
} from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
@ -22,7 +24,7 @@ import { toOid2 } from "@/src/helpers/inventoryHelpers";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { IRecipe } from "warframe-public-export-plus";
import { config } from "@/src/services/configService";
import { IEquipmentClient, Status } from "@/src/types/equipmentTypes";
import { EquipmentFeatures, IEquipmentClient, Status } from "@/src/types/equipmentTypes";
interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[];
@ -124,17 +126,122 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
addKubrowPetPrint(inventory, pet, InventoryChanges);
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = {
...InventoryChanges,
...(await addItem(
if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
// Quite the special case here...
// We don't just get Umbra, but also Skiajati and Umbra Mods. Both items are max rank, potatoed, and with the mods are pre-installed.
// Source: https://wiki.warframe.com/w/The_Sacrifice, https://wiki.warframe.com/w/Excalibur/Umbra, https://wiki.warframe.com/w/Skiajati
const umbraModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModC = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
InventoryChanges.Upgrades ??= [];
InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
await addPowerSuit(
inventory,
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
))
};
"/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
{
Configs: [
{
Upgrades: [
"",
"",
"",
"",
"",
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
]
}
],
XP: 900_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
XP: 900_000
});
addEquipment(
inventory,
"Melee",
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
{
Configs: [
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
],
XP: 450_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
XP: 450_000
});
} else {
InventoryChanges = {
...InventoryChanges,
...(await addItem(
inventory,
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
))
};
}
}
if (
config.claimingBlueprintRefundsIngredients &&

View File

@ -2,7 +2,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, updateCurrency, updateSlots } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
import { exhaustive } from "@/src/utils/ts-utils";
/*
loadout slots are additionally purchased slots only
@ -22,13 +22,44 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) {
logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
let price;
let amount;
switch (body.Bin) {
case InventorySlot.SUITS:
case InventorySlot.MECHSUITS:
case InventorySlot.PVE_LOADOUTS:
case InventorySlot.CREWMEMBERS:
price = 20;
amount = 1;
break;
case InventorySlot.SPACESUITS:
price = 12;
amount = 1;
break;
case InventorySlot.WEAPONS:
case InventorySlot.SPACEWEAPONS:
case InventorySlot.SENTINELS:
case InventorySlot.RJ_COMPONENT_AND_ARMAMENTS:
case InventorySlot.AMPS:
price = 12;
amount = 2;
break;
case InventorySlot.RIVENS:
price = 60;
amount = 3;
break;
default:
exhaustive(body.Bin);
throw new Error(`unexpected slot purchase of type ${body.Bin as string}`);
}
const inventory = await getInventory(accountId);
const currencyChanges = updateCurrency(inventory, 20, true);
updateSlots(inventory, body.Bin, 1, 1);
const currencyChanges = updateCurrency(inventory, price, true);
updateSlots(inventory, body.Bin, amount, amount);
await inventory.save();
res.json({ InventoryChanges: currencyChanges });

View File

@ -1,25 +1,39 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { addConsumables, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
export const playerSkillsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "PlayerSkills");
const inventory = await getInventory(accountId, "PlayerSkills Consumables");
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000;
inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost;
inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
await inventory.save();
const inventoryChanges: IInventoryChanges = {};
if (request.Skill == "LPS_COMMAND" && inventory.PlayerSkills.LPS_COMMAND == 9) {
const consumablesChanges = [
{
ItemType: "/Lotus/Types/Restoratives/Consumable/CrewmateBall",
ItemCount: 1
}
];
addConsumables(inventory, consumablesChanges);
inventoryChanges.Consumables = consumablesChanges;
}
await inventory.save();
res.json({
Pool: request.Pool,
PoolInc: -cost,
Skill: request.Skill,
Rank: oldRank + 1
Rank: oldRank + 1,
InventoryChanges: inventoryChanges
});
};

View File

@ -23,10 +23,16 @@ export const crackRelic = async (
weights = { COMMON: 0, UNCOMMON: 0, RARE: 1, LEGENDARY: 0 };
}
logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
const reward = getRandomWeightedReward(
let reward = getRandomWeightedReward(
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
weights
)!;
if (config.relicRewardItemCountMultiplier !== undefined && (config.relicRewardItemCountMultiplier ?? 1) != 1) {
reward = {
...reward,
itemCount: reward.itemCount * config.relicRewardItemCountMultiplier
};
}
logger.debug(`relic rolled`, reward);
participant.Reward = reward.type;
@ -43,13 +49,7 @@ export const crackRelic = async (
// Give reward
combineInventoryChanges(
inventoryChanges,
(
await handleStoreItemAcquisition(
reward.type,
inventory,
reward.itemCount * (config.relicRewardItemCountMultiplier ?? 1)
)
).InventoryChanges
(await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount)).InventoryChanges
);
return reward;

View File

@ -482,11 +482,14 @@ export const addItem = async (
if (quantity != 1) {
logger.warn(`adding 1 of ${typeName} ${targetFingerprint} even tho quantity ${quantity} was requested`);
}
inventory.Upgrades.push({
ItemType: typeName,
UpgradeFingerprint: targetFingerprint
});
return {}; // there's not exactly a common "InventoryChanges" format for these
const upgrade =
inventory.Upgrades[
inventory.Upgrades.push({
ItemType: typeName,
UpgradeFingerprint: targetFingerprint
}) - 1
];
return { Upgrades: [upgrade.toJSON<IUpgradeClient>()] };
}
const changes = [
{
@ -812,7 +815,7 @@ export const addItem = async (
if (!seed) {
throw new Error(`Expected crew member to have a seed`);
}
seed |= 0x33b81en << 32n;
seed |= BigInt(Math.trunc(inventory.Created.getTime() / 1000) & 0xffffff) << 32n;
return {
...addCrewMember(inventory, typeName, seed),
...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase)
@ -1106,6 +1109,10 @@ export const addKubrowPet = (
};
} else {
dominantTraits = createRandomTraits(kubrowPetName, traitsPool);
if (kubrowPetName == "/Lotus/Types/Game/KubrowPet/ChargerKubrowPetPowerSuit") {
dominantTraits.BodyType = "/Lotus/Types/Game/KubrowPet/BodyTypes/ChargerKubrowPetBodyType";
dominantTraits.FurPattern = "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternInfested";
}
}
const recessiveTraits: ITraits = createRandomTraits(

View File

@ -236,7 +236,7 @@ const handleQuestCompletion = async (
setupKahlSyndicate(inventory);
}
// Whispers in the Walls is unlocked once The New + Heart of Deimos are completed.
// Whispers in the Walls is unlocked once The New War + Heart of Deimos are completed.
if (
doesQuestCompletionFinishSet(inventory, questKey, [
"/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain",

View File

@ -971,25 +971,26 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
// Not very faithful, but to avoid the same node coming up back-to-back (which is not valid), I've split these into 2 arrays which we're alternating between.
const voidStormMissionsA = {
VoidT1: ["CrewBattleNode519", "CrewBattleNode518", "CrewBattleNode515", "CrewBattleNode503"],
VoidT2: ["CrewBattleNode501", "CrewBattleNode534", "CrewBattleNode530"],
VoidT3: ["CrewBattleNode521", "CrewBattleNode516"],
const voidStormMissions = {
VoidT1: [
"CrewBattleNode519",
"CrewBattleNode518",
"CrewBattleNode515",
"CrewBattleNode503",
"CrewBattleNode509",
"CrewBattleNode522",
"CrewBattleNode511",
"CrewBattleNode512"
],
VoidT2: ["CrewBattleNode501", "CrewBattleNode534", "CrewBattleNode530", "CrewBattleNode535", "CrewBattleNode533"],
VoidT3: ["CrewBattleNode521", "CrewBattleNode516", "CrewBattleNode524", "CrewBattleNode525"],
VoidT4: [
"CrewBattleNode555",
"CrewBattleNode553",
"CrewBattleNode554",
"CrewBattleNode539",
"CrewBattleNode531",
"CrewBattleNode527"
]
};
const voidStormMissionsB = {
VoidT1: ["CrewBattleNode509", "CrewBattleNode522", "CrewBattleNode511", "CrewBattleNode512"],
VoidT2: ["CrewBattleNode535", "CrewBattleNode533"],
VoidT3: ["CrewBattleNode524", "CrewBattleNode525"],
VoidT4: [
"CrewBattleNode527",
"CrewBattleNode542",
"CrewBattleNode538",
"CrewBattleNode543",
@ -997,18 +998,21 @@ const voidStormMissionsB = {
"CrewBattleNode550",
"CrewBattleNode529"
]
};
} as const;
const voidStormLookbehind = {
VoidT1: 3,
VoidT2: 1,
VoidT3: 1,
VoidT4: 3
} as const;
const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
const activation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute;
const expiry = activation + 90 * unixTimesInMs.minute;
let accum = 0;
const rng = new SRng(new SRng(hour).randomInt(0, 100_000));
const voidStormMissions = structuredClone(hour & 1 ? voidStormMissionsA : voidStormMissionsB);
const tierIdx = { VoidT1: hour * 2, VoidT2: hour, VoidT3: hour, VoidT4: hour * 2 };
for (const tier of ["VoidT1", "VoidT1", "VoidT2", "VoidT3", "VoidT4", "VoidT4"] as const) {
const idx = rng.randomInt(0, voidStormMissions[tier].length - 1);
const node = voidStormMissions[tier][idx];
voidStormMissions[tier].splice(idx, 1);
arr.push({
_id: {
$oid:
@ -1016,7 +1020,12 @@ const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
"0321e89b" +
(accum++).toString().padStart(8, "0")
},
Node: node,
Node: sequentiallyUniqueRandomElement(
voidStormMissions[tier],
tierIdx[tier]++,
voidStormLookbehind[tier],
2051969264
)!,
Activation: { $date: { $numberLong: activation.toString() } },
Expiry: { $date: { $numberLong: expiry.toString() } },
ActiveMissionTier: tier
@ -1024,75 +1033,124 @@ const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
}
};
const doesTimeSatsifyConstraints = (timeSecs: number): boolean => {
if (config.worldState?.eidolonOverride) {
interface ITimeConstraint {
//name: string;
isValidTime: (timeSecs: number) => boolean;
getIdealTimeBefore: (timeSecs: number) => number;
}
const eidolonDayConstraint: ITimeConstraint = {
//name: "eidolon day",
isValidTime: (timeSecs: number): boolean => {
const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
const eidolonCycleEnd = eidolonCycleStart + 9000;
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
if (config.worldState.eidolonOverride == "day") {
if (
//timeSecs < eidolonCycleStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleNightStart * 1000)
) {
return false;
}
} else {
if (
timeSecs < eidolonCycleNightStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleEnd * 1000)
) {
return false;
}
}
return !isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleNightStart * 1000);
},
getIdealTimeBefore: (timeSecs: number): number => {
const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
return eidolonCycleStart;
}
};
if (config.worldState?.vallisOverride) {
const eidolonNightConstraint: ITimeConstraint = {
//name: "eidolon night",
isValidTime: (timeSecs: number): boolean => {
const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
const eidolonCycleEnd = eidolonCycleStart + 9000;
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
return (
timeSecs >= eidolonCycleNightStart &&
!isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleEnd * 1000)
);
},
getIdealTimeBefore: (timeSecs: number): number => {
const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
const eidolonCycleEnd = eidolonCycleStart + 9000;
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
if (eidolonCycleNightStart > timeSecs) {
// Night hasn't started yet, but we need to return a time in the past.
return eidolonCycleNightStart - 9000;
}
return eidolonCycleNightStart;
}
};
const venusColdConstraint: ITimeConstraint = {
//name: "venus cold",
isValidTime: (timeSecs: number): boolean => {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
const vallisCycleEnd = vallisCycleStart + 1600;
const vallisCycleColdStart = vallisCycleStart + 400;
if (config.worldState.vallisOverride == "cold") {
if (
timeSecs < vallisCycleColdStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleEnd * 1000)
) {
return false;
}
} else {
if (
//timeSecs < vallisCycleStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleColdStart * 1000)
) {
return false;
return (
timeSecs >= vallisCycleColdStart &&
!isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleEnd * 1000)
);
},
getIdealTimeBefore: (timeSecs: number): number => {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
const vallisCycleColdStart = vallisCycleStart + 400;
if (vallisCycleColdStart > timeSecs) {
// Cold hasn't started yet, but we need to return a time in the past.
return vallisCycleColdStart - 1600;
}
return vallisCycleColdStart;
}
};
const venusWarmConstraint: ITimeConstraint = {
//name: "venus warm",
isValidTime: (timeSecs: number): boolean => {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
const vallisCycleColdStart = vallisCycleStart + 400;
return !isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleColdStart * 1000);
},
getIdealTimeBefore: (timeSecs: number): number => {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
return vallisCycleStart;
}
};
const getIdealTimeSatsifyingConstraints = (constraints: ITimeConstraint[]): number => {
let timeSecs = Math.trunc(Date.now() / 1000);
let allGood;
do {
allGood = true;
for (const constraint of constraints) {
if (!constraint.isValidTime(timeSecs)) {
//logger.debug(`${constraint.name} is not happy with ${timeSecs}`);
const prevTimeSecs = timeSecs;
const suggestion = constraint.getIdealTimeBefore(timeSecs);
timeSecs = suggestion;
do {
timeSecs += 60;
if (timeSecs >= prevTimeSecs || !constraint.isValidTime(timeSecs)) {
timeSecs = suggestion; // Can't find a compromise; just take the suggestion and try to compromise on another constraint.
break;
}
} while (!constraints.every(constraint => constraint.isValidTime(timeSecs)));
allGood = false;
break;
}
}
}
if (config.worldState?.duviriOverride) {
const duviriMoods = ["sorrow", "fear", "joy", "anger", "envy"];
const desiredMood = duviriMoods.indexOf(config.worldState.duviriOverride);
if (desiredMood == -1) {
logger.warn(`ignoring invalid config value for worldState.duviriOverride`, {
value: config.worldState.duviriOverride,
valid_values: duviriMoods
});
} else {
const moodIndex = Math.trunc(timeSecs / 7200);
const moodStart = moodIndex * 7200;
const moodEnd = moodStart + 7200;
if (
moodIndex % 5 != desiredMood ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, moodEnd * 1000)
) {
return false;
}
}
}
return true;
} while (!allGood);
return timeSecs;
};
const getVarziaRotation = (week: number): string => {
@ -1170,10 +1228,38 @@ const getAllVarziaManifests = (): IPrimeVaultTraderOffer[] => {
};
export const getWorldState = (buildLabel?: string): IWorldState => {
let timeSecs = Math.round(Date.now() / 1000);
while (!doesTimeSatsifyConstraints(timeSecs)) {
timeSecs -= 60;
const constraints: ITimeConstraint[] = [];
if (config.worldState?.eidolonOverride) {
constraints.push(config.worldState.eidolonOverride == "day" ? eidolonDayConstraint : eidolonNightConstraint);
}
if (config.worldState?.vallisOverride) {
constraints.push(config.worldState.vallisOverride == "cold" ? venusColdConstraint : venusWarmConstraint);
}
if (config.worldState?.duviriOverride) {
const duviriMoods = ["sorrow", "fear", "joy", "anger", "envy"];
const desiredMood = duviriMoods.indexOf(config.worldState.duviriOverride);
if (desiredMood == -1) {
logger.warn(`ignoring invalid config value for worldState.duviriOverride`, {
value: config.worldState.duviriOverride,
valid_values: duviriMoods
});
} else {
constraints.push({
//name: `duviri ${config.worldState.duviriOverride}`,
isValidTime: (timeSecs: number): boolean => {
const moodIndex = Math.trunc(timeSecs / 7200);
return moodIndex % 5 == desiredMood;
},
getIdealTimeBefore: (timeSecs: number): number => {
let moodIndex = Math.trunc(timeSecs / 7200);
moodIndex -= ((moodIndex % 5) - desiredMood + 5) % 5; // while (moodIndex % 5 != desiredMood) --moodIndex;
const moodStart = moodIndex * 7200;
return moodStart;
}
});
}
}
const timeSecs = getIdealTimeSatsifyingConstraints(constraints);
const timeMs = timeSecs * 1000;
const day = Math.trunc((timeMs - EPOCH) / 86400000);
const week = Math.trunc(day / 7);

View File

@ -520,7 +520,8 @@ export enum InventorySlot {
SENTINELS = "SentinelBin",
AMPS = "OperatorAmpBin",
RJ_COMPONENT_AND_ARMAMENTS = "CrewShipSalvageBin",
CREWMEMBERS = "CrewMemberBin"
CREWMEMBERS = "CrewMemberBin",
RIVENS = "RandomModBin"
}
export interface ISlots {

View File

@ -8,7 +8,8 @@ import {
IRecentVendorPurchaseClient,
TEquipmentKey,
ICrewMemberClient,
IKubrowPetPrintClient
IKubrowPetPrintClient,
IUpgradeClient
} from "@/src/types/inventoryTypes/inventoryTypes";
export enum PurchaseSource {
@ -80,6 +81,7 @@ export type IInventoryChanges = {
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
CrewMembers?: ICrewMemberClient[];
KubrowPetPrints?: IKubrowPetPrintClient[];
Upgrades?: IUpgradeClient[]; // TOVERIFY
} & Record<
Exclude<
string,

View File

@ -3,3 +3,5 @@ type Entries<T, K extends keyof T = keyof T> = (K extends unknown ? [K, T[K]] :
export function getEntriesUnsafe<T extends object>(object: T): Entries<T> {
return Object.entries(object) as Entries<T>;
}
export const exhaustive = (_: never): void => {};

View File

@ -135,5 +135,6 @@
"/Lotus/Language/EntratiLab/EntratiGeneral/HumanLoidLoved",
"ConquestSetupIntro",
"EntratiLabConquestHardModeUnlocked",
"/Lotus/Language/Npcs/KonzuPostNewWar"
"/Lotus/Language/Npcs/KonzuPostNewWar",
"/Lotus/Language/SolarisVenus/EudicoPostNewWar"
]

View File

@ -16,6 +16,10 @@
{
"ItemType": "/Lotus/Types/Keys/1999PrologueQuest/1999PrologueQuestKeyChain",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Items/EmailItems/TennokaiEmailItem",
"ItemCount": 1
}
]
}

View File

@ -457,7 +457,8 @@
</div>
</div>
<div id="detailedView-route" data-route="/webui/detailedView" data-title="Inventory | OpenWF WebUI">
<h3 class="mb-0"></h3>
<h3 id="detailedView-loading" class="mb-0" data-loc="general_loading"></h3>
<h3 id="detailedView-title" class="mb-0"></h3>
<p class="text-body-secondary"></p>
<div id="archonShards-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_archonShardsLabel"></h5>
@ -527,8 +528,8 @@
<form class="input-group mb-3" onsubmit="doAcquireMod();return false;">
<input class="form-control" id="mod-count" type="number" value="1"/>
<input class="form-control w-50" id="mod-to-acquire" list="datalist-mods" />
<button class="btn btn-success" onclick="window.maxed=true" type="submit" data-loc="mods_addMax"></button>
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
<button class="btn btn-success" onclick="window.maxed=true" data-loc="mods_addMax"></button>
</form>
<table class="table table-hover w-100">
<tbody id="mods-list"></tbody>
@ -803,21 +804,21 @@
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<div class="input-group">
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" data-default="-1" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('relicRewardItemCountMultiplier'); return false;">
<label class="form-label" for="relicRewardItemCountMultiplier" data-loc="cheats_relicRewardItemCountMultiplier"></label>
<div class="input-group">
<input class="form-control" id="relicRewardItemCountMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('nightwaveStandingMultiplier'); return false;">
<label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
</div>
@ -830,6 +831,7 @@
<div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="debounce(doUnlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
<button class="btn btn-primary" onclick="debounce(markAllAsRead);" data-loc="cheats_markAllAsRead"></button>
<button class="btn btn-primary" onclick="doUnlockAllFocusSchools();" data-loc="cheats_unlockAllFocusSchools"></button>
<button class="btn btn-primary" onclick="doHelminthUnlockAll();" data-loc="cheats_helminthUnlockAll"></button>
<button class="btn btn-primary" onclick="debounce(addMissingHelminthRecipes);" data-loc="cheats_addMissingSubsumedAbilities"></button>
@ -942,14 +944,14 @@
<label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_theCircuitOverride"></label>
<div class="input-group">
<input id="worldState.circuitGameModes" type="text" class="form-control tags-input" list="datalist-circuitGameModes" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigFloat('worldState.darvoStockMultiplier'); return false;">
<label class="form-label" for="worldState.darvoStockMultiplier" data-loc="worldState_darvoStockMultiplier"></label>
<div class="input-group">
<input id="worldState.darvoStockMultiplier" class="form-control" type="number" step="0.01" data-default="1" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
</div>

View File

@ -273,6 +273,8 @@ function fetchItemList() {
window.itemListPromise = new Promise(resolve => {
const req = $.get("/custom/getItemLists?lang=" + window.lang);
req.done(async data => {
window.allQuestKeys = data.QuestKeys;
await dictPromise;
document.querySelectorAll('[id^="datalist-"]').forEach(datalist => {
@ -879,6 +881,14 @@ function updateInventory() {
// Populate quests route
document.getElementById("QuestKeys-list").innerHTML = "";
window.allQuestKeys.forEach(questKey => {
if (!data.QuestKeys.some(x => x.ItemType == questKey.uniqueName)) {
const datalist = document.getElementById("datalist-QuestKeys");
if (!datalist.querySelector(`option[data-key="${questKey.uniqueName}"]`)) {
readdQuestKey(itemMap, questKey.uniqueName);
}
}
});
data.QuestKeys.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
@ -972,10 +982,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
const option = document.createElement("option");
option.setAttribute("data-key", item.ItemType);
option.value = itemMap[item.ItemType]?.name ?? item.ItemType;
document.getElementById("datalist-QuestKeys").appendChild(option);
readdQuestKey(itemMap, item.ItemType);
doQuestUpdate("deleteKey", item.ItemType);
};
a.title = loc("code_remove");
@ -1166,14 +1173,15 @@ function updateInventory() {
const item = data[category].find(x => x.ItemId.$oid == oid);
if (item) {
document.getElementById("detailedView-loading").classList.add("d-none");
if (item.ItemName) {
$("#detailedView-route h3").text(item.ItemName);
$("#detailedView-title").text(item.ItemName);
$("#detailedView-route .text-body-secondary").text(
itemMap[item.ItemType]?.name ?? item.ItemType
);
} else {
$("#detailedView-route h3").text(itemMap[item.ItemType]?.name ?? item.ItemType);
$("#detailedView-route .text-body-secondary").text("");
$("#detailedView-title").text(itemMap[item.ItemType]?.name ?? item.ItemType);
}
if (category == "Suits") {
@ -1954,6 +1962,19 @@ for (const id of uiConfigs) {
}
}
document.querySelectorAll(".config-form .input-group").forEach(grp => {
const input = grp.querySelector("input");
const btn = grp.querySelector("button");
input.oninput = input.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
btn.onclick = function () {
btn.classList.remove("btn-primary");
btn.classList.add("btn-secondary");
};
});
function doSaveConfigInt(id) {
$.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
@ -2171,7 +2192,9 @@ function doAddMissingMaxRankMods() {
// DetailedView Route
single.getRoute("#detailedView-route").on("beforeload", function () {
this.element.querySelector("h3").textContent = "Loading...";
document.getElementById("detailedView-loading").classList.remove("d-none");
document.getElementById("detailedView-title").textContent = "";
document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
document.getElementById("archonShards-card").classList.add("d-none");
document.getElementById("valenceBonus-card").classList.add("d-none");
if (window.didInitialInventoryUpdate) {
@ -2254,6 +2277,13 @@ function doAddCurrency(currency) {
});
}
function readdQuestKey(itemMap, itemType) {
const option = document.createElement("option");
option.setAttribute("data-key", itemType);
option.value = itemMap[itemType]?.name ?? itemType;
document.getElementById("datalist-QuestKeys").appendChild(option);
}
function doQuestUpdate(operation, itemType) {
revalidateAuthz().then(() => {
$.post({
@ -2764,3 +2794,16 @@ document.querySelectorAll("#sidebar .nav-link").forEach(function (elm) {
window.scrollTo(0, 0);
});
});
async function markAllAsRead() {
await revalidateAuthz();
const { Inbox } = await fetch("/api/inbox.php?" + window.authz).then(x => x.json());
let any = false;
for (const msg of Inbox) {
if (!msg.r) {
await fetch("/api/inbox.php?" + window.authz + "&messageId=" + msg.messageId.$oid);
any = true;
}
}
toast(loc(any ? "code_succRelog" : "code_nothingToDo"));
}

View File

@ -4,6 +4,7 @@ dict = {
general_addButton: `Hinzufügen`,
general_setButton: `[UNTRANSLATED] Set`,
general_bulkActions: `Massenaktionen`,
general_loading: `[UNTRANSLATED] Loading...`,
code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`,
@ -44,6 +45,8 @@ dict = {
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_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`,
code_succImport: `Erfolgreich importiert.`,
code_succRelog: `[UNTRANSLATED] Done. Please note that you'll need to relog to see a difference in-game.`,
code_nothingToDo: `[UNTRANSLATED] Done. There was nothing to do.`,
code_gild: `Veredeln`,
code_moa: `Moa`,
code_zanuka: `Jagdhund`,
@ -199,6 +202,7 @@ dict = {
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`,
cheats_none: `Keines`,
cheats_markAllAsRead: `[UNTRANSLATED] Mark Inbox As Read`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,

View File

@ -3,6 +3,7 @@ dict = {
general_addButton: `Add`,
general_setButton: `Set`,
general_bulkActions: `Bulk Actions`,
general_loading: `Loading...`,
code_loginFail: `Login failed. Double-check the email and password.`,
code_regFail: `Registration failed. Account already exists?`,
@ -43,6 +44,8 @@ dict = {
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_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`,
code_succImport: `Successfully imported.`,
code_succRelog: `Done. Please note that you'll need to relog to see a difference in-game.`,
code_nothingToDo: `Done. There was nothing to do.`,
code_gild: `Gild`,
code_moa: `Moa`,
code_zanuka: `Hound`,
@ -198,6 +201,7 @@ dict = {
cheats_changeSupportedSyndicate: `Supported syndicate`,
cheats_changeButton: `Change`,
cheats_none: `None`,
cheats_markAllAsRead: `Mark Inbox As Read`,
worldState: `World State`,
worldState_creditBoost: `Credit Boost`,

View File

@ -4,6 +4,7 @@ dict = {
general_addButton: `Agregar`,
general_setButton: `Establecer`,
general_bulkActions: `Acciones masivas`,
general_loading: `[UNTRANSLATED] Loading...`,
code_loginFail: `Error al iniciar sesión. Verifica el correo electrónico y la contraseña.`,
code_regFail: `Error al registrar la cuenta. ¿Ya existe una cuenta con este correo?`,
@ -44,6 +45,8 @@ dict = {
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_addModsConfirm: `¿Estás seguro de que deseas agregar |COUNT| modificadores a tu cuenta?`,
code_succImport: `Importación exitosa.`,
code_succRelog: `[UNTRANSLATED] Done. Please note that you'll need to relog to see a difference in-game.`,
code_nothingToDo: `[UNTRANSLATED] Done. There was nothing to do.`,
code_gild: `Refinar`,
code_moa: `Moa`,
code_zanuka: `Sabueso`,
@ -199,6 +202,7 @@ dict = {
cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
cheats_changeButton: `Cambiar`,
cheats_none: `Ninguno`,
cheats_markAllAsRead: `[UNTRANSLATED] Mark Inbox As Read`,
worldState: `Estado del mundo`,
worldState_creditBoost: `Potenciador de Créditos`,

View File

@ -4,6 +4,7 @@ dict = {
general_addButton: `Ajouter`,
general_setButton: `[UNTRANSLATED] Set`,
general_bulkActions: `Action groupée`,
general_loading: `[UNTRANSLATED] Loading...`,
code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
code_regFail: `Enregistrement impossible. Compte existant?`,
@ -44,6 +45,8 @@ dict = {
code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
code_addModsConfirm: `Ajouter |COUNT| mods à l'inventaire ?`,
code_succImport: `Importé.`,
code_succRelog: `[UNTRANSLATED] Done. Please note that you'll need to relog to see a difference in-game.`,
code_nothingToDo: `[UNTRANSLATED] Done. There was nothing to do.`,
code_gild: `Polir`,
code_moa: `Moa`,
code_zanuka: `Molosse`,
@ -199,6 +202,7 @@ dict = {
cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`,
cheats_none: `Aucun`,
cheats_markAllAsRead: `[UNTRANSLATED] Mark Inbox As Read`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,

View File

@ -4,6 +4,7 @@ dict = {
general_addButton: `Добавить`,
general_setButton: `Установить`,
general_bulkActions: `Массовые действия`,
general_loading: `[UNTRANSLATED] Loading...`,
code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`,
@ -44,6 +45,8 @@ dict = {
code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`,
code_succImport: `Успешно импортировано.`,
code_succRelog: `[UNTRANSLATED] Done. Please note that you'll need to relog to see a difference in-game.`,
code_nothingToDo: `[UNTRANSLATED] Done. There was nothing to do.`,
code_gild: `Улучшить`,
code_moa: `МОА`,
code_zanuka: `Гончая`,
@ -199,6 +202,7 @@ dict = {
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
cheats_changeButton: `Изменить`,
cheats_none: `Отсутствует`,
cheats_markAllAsRead: `[UNTRANSLATED] Mark Inbox As Read`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,

View File

@ -4,6 +4,7 @@ dict = {
general_addButton: `添加`,
general_setButton: `设置`,
general_bulkActions: `批量操作`,
general_loading: `[UNTRANSLATED] Loading...`,
code_loginFail: `登录失败.请检查邮箱和密码.`,
code_regFail: `注册失败.账号已存在.`,
@ -44,6 +45,8 @@ dict = {
code_focusUnlocked: `已解锁|COUNT|个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新.`,
code_addModsConfirm: `确定要向账户添加|COUNT|张MOD吗?`,
code_succImport: `导入成功。`,
code_succRelog: `[UNTRANSLATED] Done. Please note that you'll need to relog to see a difference in-game.`,
code_nothingToDo: `[UNTRANSLATED] Done. There was nothing to do.`,
code_gild: `镀金`,
code_moa: `恐鸟`,
code_zanuka: `猎犬`,
@ -199,6 +202,7 @@ dict = {
cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`,
cheats_none: ``,
cheats_markAllAsRead: `[UNTRANSLATED] Mark Inbox As Read`,
worldState: `世界状态配置`,
worldState_creditBoost: `现金加成`,