Compare commits

..

10 Commits

Author SHA1 Message Date
ed82225ec1 feat: dog days
Re #1103
2025-08-11 20:17:20 +02:00
2e1326cde8 chore: update PE+ (#2606)
Reviewed-on: OpenWF/SpaceNinjaServer#2606
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-11 08:09:30 -07:00
70be467cbf feat: disruption rewards (#2605)
Closes #2599

Reviewed-on: OpenWF/SpaceNinjaServer#2605
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-11 08:09:15 -07:00
fac3ec01c6 chore: improve structuring of mission response types (#2604)
Reviewed-on: OpenWF/SpaceNinjaServer#2604
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-11 08:09:08 -07:00
ebdca760e6 chore: simplify syncing of challenge 'Completed' field (#2603)
Challenges are mostly client-authoritative, so narrow the special-casing to "challengeRewards".

Reviewed-on: OpenWF/SpaceNinjaServer#2603
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-11 08:08:47 -07:00
51c0ddda38 feat(goals): cetus events (#2598)
Includes `Plague Star` and `Ghoul Purge`.
Translation for webUI taken from game files.
Re #1103

Reviewed-on: OpenWF/SpaceNinjaServer#2598
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-08-11 08:08:40 -07:00
9129bdb5fc chore(webui): update zh (#2601)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2601
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: qingchun <qingchun@noreply.localhost>
Co-committed-by: qingchun <qingchun@noreply.localhost>
2025-08-10 16:53:03 -07:00
a4922d4c35 chore: improve handling of RJ interstitial missionInventoryUpdate (#2600)
InventoryJson should only be returned when going back to dojo, in which case RJ is also not present in the request anymore.

Reviewed-on: OpenWF/SpaceNinjaServer#2600
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-09 03:38:10 -07:00
679752633a feat: recover nightwave challenges (#2593)
Closes #1534

Reviewed-on: OpenWF/SpaceNinjaServer#2593
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-08-08 04:21:18 -07:00
67b5890f39 feat(webui): ukrainian translation by LoseFace (#2596)
Reviewed-on: OpenWF/SpaceNinjaServer#2596
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-07 12:31:38 -07:00
25 changed files with 1385 additions and 142 deletions

View File

@ -72,7 +72,11 @@
"resourceBoost": false,
"tennoLiveRelay": false,
"galleonOfGhouls": 0,
"ghoulEmergenceOverride": null,
"plagueStarOverride": null,
"starDaysOverride": null,
"waterFightOverride": null,
"waterFightRewardsOverride": null,
"eidolonOverride": "",
"vallisOverride": "",
"duviriOverride": "",

8
package-lock.json generated
View File

@ -23,7 +23,7 @@
"ncp": "^2.0.0",
"typescript": "^5.5",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.79",
"warframe-public-export-plus": "^0.5.80",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
@ -5507,9 +5507,9 @@
}
},
"node_modules/warframe-public-export-plus": {
"version": "0.5.79",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.79.tgz",
"integrity": "sha512-mPaFGX7bmSo1FP0xzo//vNCkbzMRz517NHiQB1ZU8gyoRm41IaGQ7/pFWCP8iY8KWbEMvPty4pj1hZtxsujCIA=="
"version": "0.5.80",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.80.tgz",
"integrity": "sha512-K5f1Ws3szVdnO0tBcxlNdhXoGHIw09cjHel7spKPGL7aF/vmEkbBGRmYQFvs8n5cGo+v+3qIDMre54Ghb3t0Iw=="
},
"node_modules/warframe-riven-info": {
"version": "0.1.2",

View File

@ -40,7 +40,7 @@
"ncp": "^2.0.0",
"typescript": "^5.5",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.79",
"warframe-public-export-plus": "^0.5.80",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",

View File

@ -0,0 +1,62 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { EPOCH, getSeasonChallengePools, getWorldState, pushWeeklyActs } from "@/src/services/worldStateService";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { ISeasonChallenge } from "@/src/types/worldStateTypes";
import { ExportChallenges } from "warframe-public-export-plus";
export const getPastWeeklyChallengesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "SeasonChallengeHistory ChallengeProgress");
const worldState = getWorldState(undefined);
if (worldState.SeasonInfo) {
const pools = getSeasonChallengePools(worldState.SeasonInfo.AffiliationTag);
const nightwaveStartTimestamp = Number(worldState.SeasonInfo.Activation.$date.$numberLong);
const nightwaveSeason = worldState.SeasonInfo.Season;
const timeMs = worldState.Time * 1000;
const completedChallengesIds = new Set<string>();
inventory.SeasonChallengeHistory.forEach(challengeHistory => {
const entryNightwaveSeason = parseInt(challengeHistory.id.slice(0, 4), 10) - 1;
if (nightwaveSeason == entryNightwaveSeason) {
const meta = Object.entries(ExportChallenges).find(
([key]) => key.split("/").pop() === challengeHistory.challenge
);
if (meta) {
const [, challengeMeta] = meta;
const challengeProgress = inventory.ChallengeProgress.find(
c => c.Name === challengeHistory.challenge
);
if (challengeProgress && challengeProgress.Progress >= (challengeMeta.requiredCount ?? 1)) {
completedChallengesIds.add(challengeHistory.id);
}
}
}
});
const PastWeeklyChallenges: ISeasonChallenge[] = [];
let week = Math.trunc((timeMs - EPOCH) / unixTimesInMs.week) - 1;
while (EPOCH + week * unixTimesInMs.week >= nightwaveStartTimestamp && PastWeeklyChallenges.length < 3) {
const tempActs: ISeasonChallenge[] = [];
pushWeeklyActs(tempActs, pools, week, nightwaveStartTimestamp, nightwaveSeason);
for (const act of tempActs) {
if (!completedChallengesIds.has(act._id.$oid) && PastWeeklyChallenges.length < 3) {
if (act.Challenge.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")) {
act.Permanent = true;
}
PastWeeklyChallenges.push(act);
}
}
week--;
}
res.json({ PastWeeklyChallenges: PastWeeklyChallenges });
}
};

View File

@ -13,7 +13,8 @@ import {
addItems,
combineInventoryChanges,
getEffectiveAvatarImageType,
getInventory
getInventory,
updateCurrency
} from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger";
import { ExportFlavour } from "warframe-public-export-plus";
@ -100,6 +101,9 @@ export const inboxController: RequestHandler = async (req, res) => {
}
}
}
if (message.RegularCredits) {
updateCurrency(inventory, -message.RegularCredits, false, inventoryChanges);
}
await inventory.save();
res.json({ InventoryChanges: inventoryChanges });
} else if (latestClientMessageId) {

View File

@ -6,7 +6,11 @@ import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/mi
import { getInventory } from "@/src/services/inventoryService";
import { getInventoryResponse } from "@/src/controllers/api/inventoryController";
import { logger } from "@/src/utils/logger";
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
import {
IMissionInventoryUpdateResponse,
IMissionInventoryUpdateResponseBackToDryDock,
IMissionInventoryUpdateResponseRailjackInterstitial
} from "@/src/types/missionTypes";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { generateRewardSeed } from "@/src/services/rngService";
@ -95,11 +99,9 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
inventory.RewardSeed = generateRewardSeed();
}
await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
//TODO: figure out when to send inventory. it is needed for many cases.
res.json({
InventoryJson: JSON.stringify(inventoryResponse),
const deltas: IMissionInventoryUpdateResponseRailjackInterstitial = {
InventoryChanges: inventoryChanges,
MissionRewards,
...credits,
@ -108,7 +110,25 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
SyndicateXPItemReward,
AffiliationMods,
ConquestCompletedMissionsCount
} satisfies IMissionInventoryUpdateResponse);
};
if (missionReport.RJ) {
logger.debug(`railjack interstitial request, sending only deltas`, deltas);
res.json(deltas);
} else if (missionReport.RewardInfo) {
logger.debug(`classic mission completion, sending everything`);
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
res.json({
InventoryJson: JSON.stringify(inventoryResponse),
...deltas
} satisfies IMissionInventoryUpdateResponse);
} else {
logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`);
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
res.json({
InventoryJson: JSON.stringify(inventoryResponse)
} satisfies IMissionInventoryUpdateResponseBackToDryDock);
}
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
};

View File

@ -47,6 +47,7 @@ export interface IMessage {
acceptAction?: string;
declineAction?: string;
hasAccountAction?: boolean;
RegularCredits?: number;
}
export interface Arg {
@ -139,7 +140,8 @@ const messageSchema = new Schema<IMessageDatabase>(
contextInfo: String,
acceptAction: String,
declineAction: String,
hasAccountAction: Boolean
hasAccountAction: Boolean,
RegularCredits: Number
},
{ id: false }
);

View File

@ -66,6 +66,7 @@ import { getGuildLogController } from "@/src/controllers/api/getGuildLogControll
import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController";
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
import { getProfileViewingDataPostController } from "@/src/controllers/dynamic/getProfileViewingDataController";
import { getPastWeeklyChallengesController } from "@/src/controllers/api/getPastWeeklyChallengesController";
import { getShipController } from "@/src/controllers/api/getShipController";
import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController";
import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController";
@ -195,6 +196,7 @@ apiRouter.get("/getGuildLog.php", getGuildLogController);
apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
apiRouter.get("/getPastWeeklyChallenges.php", getPastWeeklyChallengesController)
apiRouter.get("/getShip.php", getShipController);
apiRouter.get("/getShipDecos.php", (_req, res) => { res.end(); }); // needed to log in on U22.8
apiRouter.get("/getVendorInfo.php", getVendorInfoController);

View File

@ -84,7 +84,11 @@ export interface IConfig {
tennoLiveRelay?: boolean;
baroTennoConRelay?: boolean;
galleonOfGhouls?: number;
ghoulEmergenceOverride?: boolean;
plagueStarOverride?: boolean;
starDaysOverride?: boolean;
waterFightOverride?: boolean;
waterFightRewardsOverride?: number;
eidolonOverride?: string;
vallisOverride?: string;
duviriOverride?: string;

View File

@ -1980,17 +1980,20 @@ export const addChallenges = async (
dbChallenge.Completed ??= [];
for (const completion of Completed!) {
if (dbChallenge.Completed.indexOf(completion) == -1) {
dbChallenge.Completed.push(completion);
if (completion == "challengeRewards") {
if (Name in challengeRewardsInboxMessages) {
await createMessage(account._id, [challengeRewardsInboxMessages[Name]]);
dbChallenge.Completed.push(completion);
// Would love to somehow let the client know about inbox or inventory changes, but there doesn't seem to anything for updateChallengeProgress.
continue;
}
logger.warn(`ignoring unknown challenge completion`, { challenge: Name, completion });
dbChallenge.Completed = [];
}
logger.warn(`ignoring unknown challenge completion`, { challenge: Name, completion });
}
}
} else {
dbChallenge.Completed = Completed;
}
}

View File

@ -50,7 +50,7 @@ import { getEntriesUnsafe } from "@/src/utils/ts-utils";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { IMissionCredits, IMissionReward } from "@/src/types/missionTypes";
import { crackRelic } from "@/src/helpers/relicHelper";
import { createMessage } from "@/src/services/inboxService";
import { createMessage, IMessageCreationTemplate } from "@/src/services/inboxService";
import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json";
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
@ -76,13 +76,18 @@ import {
} from "@/src/services/worldStateService";
import { config } from "@/src/services/configService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { ISyndicateMissionInfo } from "@/src/types/worldStateTypes";
import { IGoal, ISyndicateMissionInfo } from "@/src/types/worldStateTypes";
import { fromOid } from "@/src/helpers/inventoryHelpers";
import { TAccountDocument } from "@/src/services/loginService";
import { ITypeCount } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/equipmentTypes";
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
// Disruption missions just tell us (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2599)
if (rewardInfo.rewardTierOverrides) {
return rewardInfo.rewardTierOverrides;
}
// For Spy missions, e.g. 3 vaults cracked = A, B, C
if (rewardInfo.VaultsCracked) {
const rotations: number[] = [];
@ -619,37 +624,93 @@ export const addMissionInventoryUpdates = async (
if (goal && goal.Personal) {
inventory.PersonalGoalProgress ??= [];
const goalProgress = inventory.PersonalGoalProgress.find(x => x.goalId.equals(goal._id.$oid));
if (goalProgress) {
goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
goalProgress.Count += uploadProgress.Count;
} else {
if (!goalProgress) {
inventory.PersonalGoalProgress.push({
Best: uploadProgress.Best,
Count: uploadProgress.Count,
Tag: goal.Tag,
goalId: new Types.ObjectId(goal._id.$oid)
});
}
const currentNode = inventoryUpdates.RewardInfo!.node;
let currentMissionKey;
if (currentNode == goal.Node) {
currentMissionKey = goal.MissionKeyName;
} else if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) {
for (let i = 0; i < goal.ConcurrentNodes.length; i++) {
if (currentNode == goal.ConcurrentNodes[i]) {
currentMissionKey = goal.ConcurrentMissionKeyNames[i];
break;
}
}
}
if (currentMissionKey && currentMissionKey in goalMessagesByKey) {
const totalCount = (goalProgress?.Count ?? 0) + uploadProgress.Count;
let reward;
if (goal.InterimGoals && goal.InterimRewards) {
for (let i = 0; i < goal.InterimGoals.length; i++) {
if (
goal.InterimGoals[i] &&
goal.InterimGoals[i] <= totalCount &&
(!goalProgress || goalProgress.Count < goal.InterimGoals[i]) &&
goal.InterimRewards[i]
) {
reward = goal.InterimRewards[i];
break;
}
}
}
if (
goal.Reward &&
goal.Reward.items &&
goal.MissionKeyName &&
goal.MissionKeyName in goalMessagesByKey
!reward &&
goal.Goal &&
goal.Goal <= totalCount &&
(!goalProgress || goalProgress.Count < goal.Goal) &&
goal.Reward
) {
// Send reward via inbox
const info = goalMessagesByKey[goal.MissionKeyName];
await createMessage(inventory.accountOwnerId, [
{
reward = goal.Reward;
}
if (
!reward &&
goal.BonusGoal &&
goal.BonusGoal <= totalCount &&
(!goalProgress || goalProgress.Count < goal.BonusGoal) &&
goal.BonusReward
) {
reward = goal.BonusReward;
}
if (reward) {
if (currentMissionKey in goalMessagesByKey) {
// Send reward via inbox
const info = goalMessagesByKey[currentMissionKey];
const message: IMessageCreationTemplate = {
sndr: info.sndr,
msg: info.msg,
att: goal.Reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x)),
sub: info.sub,
icon: info.icon,
highPriority: true
};
if (reward.items) {
message.att = reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x));
}
]);
if (reward.countedItems) {
message.countedAtt = reward.countedItems;
}
if (reward.credits) {
message.RegularCredits = reward.credits;
}
await createMessage(inventory.accountOwnerId, [message]);
}
}
}
if (goalProgress) {
goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
goalProgress.Count += uploadProgress.Count;
}
}
}
break;
@ -1006,8 +1067,16 @@ export const addMissionRewards = async (
if (rewardInfo.goalId) {
const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId);
if (goal?.MissionKeyName) {
levelKeyName = goal.MissionKeyName;
if (goal) {
if (rewardInfo.node == goal.Node && goal.MissionKeyName) levelKeyName = goal.MissionKeyName;
if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) {
for (let i = 0; i < goal.ConcurrentNodes.length && i < goal.ConcurrentMissionKeyNames.length; i++) {
if (rewardInfo.node == goal.ConcurrentNodes[i]) {
levelKeyName = goal.ConcurrentMissionKeyNames[i];
break;
}
}
}
}
}
@ -1259,6 +1328,8 @@ export const addMissionRewards = async (
}
}
AffiliationMods ??= [];
if (rewardInfo.JobStage != undefined && rewardInfo.jobId) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [jobType, unkIndex, hubNode, syndicateMissionId] = rewardInfo.jobId.split("_");
@ -1266,9 +1337,29 @@ export const addMissionRewards = async (
if (syndicateMissionId) {
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId));
}
const syndicateEntry = syndicateMissions.find(m => m._id.$oid === syndicateMissionId);
let syndicateEntry: ISyndicateMissionInfo | IGoal | undefined = syndicateMissions.find(
m => m._id.$oid === syndicateMissionId
);
if (
[
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBounty",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBounty"
].some(prefix => jobType.startsWith(prefix))
) {
const { Goals } = getWorldState(undefined);
syndicateEntry = Goals.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!;
}
if (syndicateEntry && syndicateEntry.Jobs) {
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
if (
[
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBounty",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBounty"
].some(prefix => jobType.startsWith(prefix))
) {
currentJob = syndicateEntry.Jobs.find(j => j.jobType === jobType)!;
}
if (syndicateEntry.Tag === "EntratiSyndicate") {
if (
[
@ -1311,31 +1402,35 @@ export const addMissionRewards = async (
`Giving ${medallionAmount} medallions for the ${rewardInfo.JobStage} stage of the ${rewardInfo.JobTier} tier bounty`
);
} else {
if (rewardInfo.JobTier! >= 0) {
const specialCase = [
{ endings: ["Heists/HeistProfitTakerBountyOne"], stage: 2, amount: 1000 },
{ endings: ["Hunts/AllTeralystsHunt"], stage: 2, amount: 5000 },
{
endings: [
"Hunts/TeralystHunt",
"Heists/HeistProfitTakerBountyTwo",
"Heists/HeistProfitTakerBountyThree",
"Heists/HeistProfitTakerBountyFour",
"Heists/HeistExploiterBountyOne"
],
amount: 1000
}
];
const specialCaseReward = specialCase.find(
rule =>
rule.endings.some(e => jobType.endsWith(e)) &&
(rule.stage === undefined || rewardInfo.JobStage === rule.stage)
);
if (specialCaseReward) {
addStanding(inventory, syndicateEntry.Tag, specialCaseReward.amount, AffiliationMods);
} else {
addStanding(
inventory,
syndicateEntry.Tag,
Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)),
AffiliationMods
);
} else {
if (jobType.endsWith("Heists/HeistProfitTakerBountyOne") && rewardInfo.JobStage === 2) {
addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods);
}
if (jobType.endsWith("Hunts/AllTeralystsHunt") && rewardInfo.JobStage === 2) {
addStanding(inventory, syndicateEntry.Tag, 5000, AffiliationMods);
}
if (
[
"Hunts/TeralystHunt",
"Heists/HeistProfitTakerBountyTwo",
"Heists/HeistProfitTakerBountyThree",
"Heists/HeistProfitTakerBountyFour",
"Heists/HeistExploiterBountyOne"
].some(ending => jobType.endsWith(ending))
) {
addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods);
}
}
}
}
@ -1672,7 +1767,19 @@ function getRandomMissionDrops(
if (syndicateMissionId) {
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId));
}
const syndicateEntry = syndicateMissions.find(m => m._id.$oid === syndicateMissionId);
let syndicateEntry: ISyndicateMissionInfo | IGoal | undefined = syndicateMissions.find(
m => m._id.$oid === syndicateMissionId
);
if (
[
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBounty",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBounty"
].some(prefix => jobType.startsWith(prefix))
) {
const { Goals } = getWorldState(undefined);
syndicateEntry = Goals.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!;
}
if (syndicateEntry && syndicateEntry.Jobs) {
let job = syndicateEntry.Jobs[RewardInfo.JobTier!];
@ -1757,6 +1864,14 @@ function getRandomMissionDrops(
}
}
}
if (
[
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBounty",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBounty"
].some(prefix => jobType.startsWith(prefix))
) {
job = syndicateEntry.Jobs.find(j => j.jobType === jobType)!;
}
rewardManifests = [job.rewards];
if (job.xpAmounts.length > 1) {
const curentStage = RewardInfo.JobStage! + 1;
@ -2098,5 +2213,29 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgC",
sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
},
"/Lotus/Types/Keys/TacAlertKeyWaterFightA": {
sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
msg: "/Lotus/Language/Inbox/WaterFightRewardMsgA",
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectA",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
},
"/Lotus/Types/Keys/TacAlertKeyWaterFightB": {
sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
msg: "/Lotus/Language/Inbox/WaterFightRewardMsgB",
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectB",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
},
"/Lotus/Types/Keys/TacAlertKeyWaterFightC": {
sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
msg: "/Lotus/Language/Inbox/WaterFightRewardMsgC",
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectC",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
},
"/Lotus/Types/Keys/TacAlertKeyWaterFightD": {
sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
msg: "/Lotus/Language/Inbox/WaterFightRewardMsgD",
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectD",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
}
};

View File

@ -131,6 +131,13 @@ const eidolonNarmerJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib"
];
const eidolonGhoulJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBountyAss",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBountyExt",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBountyHunt",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBountyRes"
];
const venusJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation",
@ -180,7 +187,7 @@ const microplanetEndlessJobs: readonly string[] = [
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
];
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
export const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
const isBeforeNextExpectedWorldStateRefresh = (nowMs: number, thenMs: number): boolean => {
return nowMs + 300_000 > thenMs;
@ -365,10 +372,10 @@ interface IRotatingSeasonChallengePools {
daily: string[];
weekly: string[];
hardWeekly: string[];
hasWeeklyPermanent: boolean;
weeklyPermanent: string[];
}
const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallengePools => {
export const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallengePools => {
const syndicate = ExportSyndicates[syndicateTag];
return {
daily: syndicate.dailyChallenges!,
@ -380,7 +387,7 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
hardWeekly: syndicate.weeklyChallenges!.filter(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
),
hasWeeklyPermanent: syndicate.weeklyChallenges!.some(x =>
weeklyPermanent: syndicate.weeklyChallenges!.filter(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
)
};
@ -401,6 +408,7 @@ const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: numb
const pushSeasonWeeklyChallenge = (
activeChallenges: ISeasonChallenge[],
pool: string[],
nightwaveSeason: number,
week: number,
id: number
): void => {
@ -413,51 +421,57 @@ const pushSeasonWeeklyChallenge = (
challenge = rng.randomElement(pool)!;
} while (activeChallenges.some(x => x.Challenge == challenge));
activeChallenges.push({
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
_id: {
$oid:
(nightwaveSeason + 1).toString().padStart(4, "0") +
"bb2d9d00cb47" +
challengeId.toString().padStart(8, "0")
},
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: challenge
});
};
const pushWeeklyActs = (
export const pushWeeklyActs = (
activeChallenges: ISeasonChallenge[],
pools: IRotatingSeasonChallengePools,
week: number
week: number,
nightwaveStartTimestamp: number,
nightwaveSeason: number
): void => {
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 0);
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 1);
if (pools.hasWeeklyPermanent) {
activeChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions"
});
activeChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus"
});
activeChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies"
});
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 2);
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 3);
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 0);
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 1);
if (pools.weeklyPermanent.length > 0) {
const weekStart = EPOCH + week * unixTimesInMs.week;
const weekEnd = weekStart + unixTimesInMs.week;
const nightwaveWeekStart = ((): number => {
let ts = nightwaveStartTimestamp - EPOCH;
ts -= ts % unixTimesInMs.week;
return EPOCH + ts;
})();
const nightwaveWeek = Math.trunc((weekStart - nightwaveWeekStart) / unixTimesInMs.week);
const weeklyPermanentIndex = (nightwaveWeek * 3) % pools.weeklyPermanent.length;
for (let i = 0; i < 3; i++) {
activeChallenges.push({
_id: {
$oid:
(nightwaveSeason + 1).toString().padStart(4, "0") +
"b96e9d00cb47" +
(week * 7 + 2 + i).toString().padStart(8, "0")
},
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: pools.weeklyPermanent[weeklyPermanentIndex + i]
});
}
} else {
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 2);
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 3);
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 4);
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 5);
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 6);
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 2);
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 3);
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 4);
}
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, nightwaveSeason, week, 5);
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, nightwaveSeason, week, 6);
};
const generateXpAmounts = (rng: SRng, stageCount: number, minXp: number, maxXp: number): number[] => {
@ -1378,6 +1392,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Sorties: [],
LiteSorties: [],
ActiveMissions: [],
FlashSales: [],
GlobalUpgrades: [],
Invasions: [],
VoidTraders: [],
@ -1387,7 +1402,8 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
EndlessXpChoices: [],
KnownCalendarSeasons: [],
...staticWorldState,
SyndicateMissions: [...staticWorldState.SyndicateMissions]
SyndicateMissions: [...staticWorldState.SyndicateMissions],
InGameMarket: staticWorldState.InGameMarket
};
// Old versions seem to really get hung up on not being able to load these.
@ -1549,14 +1565,375 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
});
}
const firstNovemberWeekday = new Date(Date.UTC(date.getUTCFullYear(), 10, 1)).getUTCDay();
const firstNovemberMondayOffset = (8 - firstNovemberWeekday) % 7;
const plagueStarStart = Date.UTC(date.getUTCFullYear(), 10, firstNovemberMondayOffset + 1, 16);
const plagueStarEnd = Date.UTC(date.getUTCFullYear(), 10, firstNovemberMondayOffset + 15, 16);
const isPlagueStarActive = timeMs >= plagueStarStart && timeMs < plagueStarEnd;
if (config.worldState?.plagueStarOverride ?? isPlagueStarActive) {
worldState.Goals.push({
_id: { $oid: "654a5058c757487cdb11824f" },
Activation: {
$date: {
$numberLong: config.worldState?.plagueStarOverride ? "1699372800000" : plagueStarStart.toString()
}
},
Expiry: {
$date: {
$numberLong: config.worldState?.plagueStarOverride ? "2000000000000" : plagueStarEnd.toString()
}
},
Tag: "InfestedPlains",
RegionIdx: 2,
Faction: "FC_INFESTATION",
Desc: "/Lotus/Language/InfestedPlainsEvent/InfestedPlainsBountyName",
ToolTip: "/Lotus/Language/InfestedPlainsEvent/InfestedPlainsBountyDesc",
Icon: "/Lotus/Materials/Emblems/PlagueStarEventBadge_e.png",
JobAffiliationTag: "EventSyndicate",
Jobs: [
{
jobType: "/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBounty",
rewards: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/PlagueStarTableRewards",
minEnemyLevel: 15,
maxEnemyLevel: 25,
xpAmounts: [50, 300, 100, 575]
},
{
jobType: "/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBountyAdvanced",
rewards: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/PlagueStarTableRewards",
minEnemyLevel: 55,
maxEnemyLevel: 65,
xpAmounts: [200, 1000, 300, 1700],
requiredItems: [
"/Lotus/StoreItems/Types/Items/Eidolon/InfestedEventIngredient",
"/Lotus/StoreItems/Types/Items/Eidolon/InfestedEventClanIngredient"
],
useRequiredItemsAsMiscItemFee: true
},
{
jobType: "/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBountySteelPath",
rewards: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/PlagueStarTableSteelPathRewards",
minEnemyLevel: 100,
maxEnemyLevel: 110,
xpAmounts: [200, 1100, 400, 2100],
masteryReq: 10,
requiredItems: [
"/Lotus/StoreItems/Types/Items/Eidolon/InfestedEventIngredient",
"/Lotus/StoreItems/Types/Items/Eidolon/InfestedEventClanIngredient"
],
useRequiredItemsAsMiscItemFee: true
}
],
Transmission: "/Lotus/Sounds/Dialog/PlainsMeteorLeadUp/LeadUp/DLeadUp0021Lotus",
InstructionalItem: "/Lotus/Types/StoreItems/Packages/PlagueStarEventStoreItem"
});
}
const firstAugustWeekday = new Date(Date.UTC(date.getUTCFullYear(), 7, 1)).getUTCDay();
const firstAugustWednesdayOffset = (3 - firstAugustWeekday + 7) % 7;
const waterFightStart = Date.UTC(date.getUTCFullYear(), 7, 1 + firstAugustWednesdayOffset, 15);
const firstSeptemberWeekday = new Date(Date.UTC(date.getUTCFullYear(), 8, 1)).getUTCDay();
const firstSeptemberWednesdayOffset = (3 - firstSeptemberWeekday + 7) % 7;
const waterFightEnd = Date.UTC(date.getUTCFullYear(), 8, 1 + firstSeptemberWednesdayOffset, 15);
const isWaterFightActive = timeMs >= waterFightStart && timeMs < waterFightEnd;
logger.debug(isWaterFightActive);
if (config.worldState?.waterFightOverride ?? isWaterFightActive) {
const activationTimeStamp = config.worldState?.waterFightOverride
? "1699372800000"
: waterFightStart.toString();
const expiryTimeStamp = config.worldState?.waterFightOverride ? "2000000000000" : waterFightEnd.toString();
const rewards = [
[
{
credits: 50000,
items: ["/Lotus/StoreItems/Upgrades/Skins/Weapons/Redeemer/RedeemerRelayWaterSkin"]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileHydroidRelay"]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/RelayHydroidBobbleHead"]
},
{
items: [
"/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
}
],
[
{
credits: 50000,
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/DogDays2023ASigil"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 25
}
]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyBeachKavat"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 50
}
]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyRucksackKubrow"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 75
}
]
},
{
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCleaningDroneBeachcomber"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 100
}
]
}
],
[
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageDogDays2024Glyph"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 25
}
]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/DogDays2024Poster"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 50
}
]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/DogDaysKubrowBadgeItem"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 75
}
]
},
{
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/DogDays2024LisetPropCleaningDroneBeachcomber"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 100
}
]
}
],
[
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageDogDaysHydroidGlyph"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 25
}
]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageDogDaysLokiGlyph"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 50
}
]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageDogDaysNovaGlyph"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 75
}
]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageDogDaysValkyrGlyph"],
countedItems: [
{
ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks",
ItemCount: 100
}
]
}
]
];
const year = config.worldState?.waterFightRewardsOverride ?? 3;
worldState.Goals.push({
_id: {
$oid: ((waterFightStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "c57487c3768936df"
},
Activation: { $date: { $numberLong: activationTimeStamp } },
Expiry: { $date: { $numberLong: expiryTimeStamp } },
Count: 0,
Goal: 100,
InterimGoals: [25, 50],
BonusGoal: 200,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode25",
ConcurrentMissionKeyNames: [
"/Lotus/Types/Keys/TacAlertKeyWaterFightB",
"/Lotus/Types/Keys/TacAlertKeyWaterFightC",
"/Lotus/Types/Keys/TacAlertKeyWaterFightD"
],
ConcurrentNodeReqs: [25, 50, 100],
ConcurrentNodes: ["EventNode24", "EventNode34", "EventNode35"],
MissionKeyName: "/Lotus/Types/Keys/TacAlertKeyWaterFightA",
Faction: "FC_CORPUS",
Desc: "/Lotus/Language/Alerts/TacAlertWaterFight",
Icon: "/Lotus/Interface/Icons/StoreIcons/Emblems/SplashEventIcon.png",
Tag: "WaterFight",
InterimRewards: rewards[year].slice(0, 2),
Reward: rewards[year][2],
BonusReward: rewards[year][3],
ScoreVar: "Team1Score",
NightLevel: "/Lotus/Levels/GrineerBeach/GrineerBeachEventNight.level"
});
const baseStoreItem = {
ShowInMarket: true,
HideFromMarket: false,
SupporterPack: false,
Discount: 0,
BogoBuy: 0,
BogoGet: 0,
StartDate: { $date: { $numberLong: activationTimeStamp } },
EndDate: { $date: { $numberLong: expiryTimeStamp } },
ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } }
};
const storeItems = [
{
TypeName: "/Lotus/Types/StoreItems/Packages/WaterFightNoggleBundle",
PremiumOverride: 240,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFBeastMasterBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFChargerBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFEngineerBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFGruntBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImagePopsicleGrineerPurple",
PremiumOverride: 0,
RegularOverride: 1
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHealerBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHeavyBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHellionBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFSniperBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFTankBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerRollers",
PremiumOverride: 75,
RegularOverride: 0
}
];
worldState.FlashSales.push(...storeItems.map(item => ({ ...baseStoreItem, ...item })));
const seasonalItems = storeItems.map(item => item.TypeName);
const seasonalCategory = worldState.InGameMarket.LandingPage.Categories.find(c => c.CategoryName == "SEASONAL");
if (seasonalCategory) {
seasonalCategory.Items ??= [];
seasonalCategory.Items.push(...seasonalItems);
} else {
worldState.InGameMarket.LandingPage.Categories.push({
CategoryName: "SEASONAL",
Name: "/Lotus/Language/Store/SeasonalCategoryTitle",
Icon: "seasonal",
AddToMenu: true,
Items: seasonalItems
});
}
}
// Nightwave Challenges
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
if (nightwaveSyndicateTag) {
const nightwaveStartTimestamp = 1747851300000;
const nightwaveSeason = nightwaveTagToSeason[nightwaveSyndicateTag];
worldState.SeasonInfo = {
Activation: { $date: { $numberLong: "1715796000000" } },
Activation: { $date: { $numberLong: nightwaveStartTimestamp.toString() } },
Expiry: { $date: { $numberLong: "2000000000000" } },
AffiliationTag: nightwaveSyndicateTag,
Season: nightwaveTagToSeason[nightwaveSyndicateTag],
Season: nightwaveSeason,
Phase: 0,
Params: "",
ActiveChallenges: []
@ -1568,9 +1945,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
if (isBeforeNextExpectedWorldStateRefresh(timeMs, EPOCH + (day + 1) * 86400000)) {
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day + 1));
}
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week);
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week, nightwaveStartTimestamp, nightwaveSeason);
if (isBeforeNextExpectedWorldStateRefresh(timeMs, weekEnd)) {
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week + 1);
pushWeeklyActs(
worldState.SeasonInfo.ActiveChallenges,
pools,
week + 1,
nightwaveStartTimestamp,
nightwaveSeason
);
}
}
@ -1611,6 +1994,103 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
pushClassicBounties(worldState.SyndicateMissions, bountyCycle);
} while (isBeforeNextExpectedWorldStateRefresh(timeMs, bountyCycleEnd) && ++bountyCycle);
const ghoulsCycleDay = day % 21;
const isGhoulEmergenceActive = ghoulsCycleDay >= 17 && ghoulsCycleDay <= 20; // 4 days for event and 17 days for break
if (config.worldState?.ghoulEmergenceOverride ?? isGhoulEmergenceActive) {
const ghoulPool = [...eidolonGhoulJobs];
const pastGhoulPool = [...eidolonGhoulJobs];
const seed = new SRng(bountyCycle).randomInt(0, 100_000);
const pastSeed = new SRng(bountyCycle - 1).randomInt(0, 100_000);
const rng = new SRng(seed);
const pastRng = new SRng(pastSeed);
const activeStartDay = day - ghoulsCycleDay + 17;
const activeEndDay = activeStartDay + 5;
const dayWithFraction = (timeMs - EPOCH) / 86400000;
const progress = (dayWithFraction - activeStartDay) / (activeEndDay - activeStartDay);
const healthPct = 1 - Math.min(Math.max(progress, 0), 1);
worldState.Goals.push({
_id: { $oid: "687ebbe6d1d17841c9c59f38" },
Activation: {
$date: {
$numberLong: config.worldState?.ghoulEmergenceOverride
? "1753204900185"
: Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate() + (day - ghoulsCycleDay + 17)
).toString()
}
},
Expiry: {
$date: {
$numberLong: config.worldState?.ghoulEmergenceOverride
? "2000000000000"
: Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate() + (day - ghoulsCycleDay + 21)
).toString()
}
},
HealthPct: config.worldState?.ghoulEmergenceOverride ? 1 : healthPct,
VictimNode: "SolNode228",
Regions: [2],
Success: 0,
Desc: "/Lotus/Language/GameModes/RecurringGhoulAlert",
ToolTip: "/Lotus/Language/GameModes/RecurringGhoulAlertDesc",
Icon: "/Lotus/Interface/Icons/Categories/IconGhouls256.png",
Tag: "GhoulEmergence",
JobAffiliationTag: "CetusSyndicate",
JobCurrentVersion: {
$oid: ((bountyCycle * 9000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
},
Jobs: [
{
jobType: rng.randomElementPop(ghoulPool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/GhoulBountyTableARewards`,
masteryReq: 1,
minEnemyLevel: 15,
maxEnemyLevel: 25,
xpAmounts: [270, 270, 270, 400] // not faithful
},
{
jobType: rng.randomElementPop(ghoulPool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/GhoulBountyTableBRewards`,
masteryReq: 3,
minEnemyLevel: 40,
maxEnemyLevel: 50,
xpAmounts: [480, 480, 480, 710] // not faithful
}
],
JobPreviousVersion: {
$oid: (((bountyCycle - 1) * 9000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
},
PreviousJobs: [
{
jobType: pastRng.randomElementPop(pastGhoulPool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/GhoulBountyTableARewards`,
masteryReq: 1,
minEnemyLevel: 15,
maxEnemyLevel: 25,
xpAmounts: [270, 270, 270, 400] // not faithful
},
{
jobType: pastRng.randomElementPop(pastGhoulPool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/GhoulBountyTableBRewards`,
masteryReq: 3,
minEnemyLevel: 40,
maxEnemyLevel: 50,
xpAmounts: [480, 480, 480, 710] // not faithful
}
]
});
}
if (config.worldState?.creditBoost) {
worldState.GlobalUpgrades.push({
_id: { $oid: "5b23106f283a555109666672" },

View File

@ -23,12 +23,19 @@ export interface IMissionCredits {
DailyMissionBonus?: boolean;
}
export interface IMissionInventoryUpdateResponse extends Partial<IMissionCredits> {
export interface IMissionInventoryUpdateResponseRailjackInterstitial extends Partial<IMissionCredits> {
ConquestCompletedMissionsCount?: number;
InventoryJson?: string;
MissionRewards?: IMissionReward[];
InventoryChanges?: IInventoryChanges;
FusionPoints?: number;
SyndicateXPItemReward?: number;
AffiliationMods?: IAffiliationMods[];
}
export interface IMissionInventoryUpdateResponse extends IMissionInventoryUpdateResponseRailjackInterstitial {
InventoryJson?: string;
}
export interface IMissionInventoryUpdateResponseBackToDryDock {
InventoryJson: string;
}

View File

@ -148,6 +148,7 @@ export type IMissionInventoryUpdateRequest = {
MultiProgress: unknown[];
}[];
InvasionProgress?: IInvasionProgressClient[];
RJ?: boolean;
ConquestMissionsCompleted?: number;
duviriSuitSelection?: string;
duviriPistolSelection?: string;
@ -184,6 +185,7 @@ export interface IRewardInfo {
NemesisHintProgress?: number;
EOM_AFK?: number;
rewardQualifications?: string; // did a Survival for 5 minutes and this was "1"
rewardTierOverrides?: number[]; // Disruption
PurgatoryRewardQualifications?: string;
rewardSeed?: number | bigint;
periodicMissionTag?: string;

View File

@ -5,12 +5,14 @@ export interface IWorldState {
Version: number; // for goals
BuildLabel: string;
Time: number;
InGameMarket: IInGameMarket;
Goals: IGoal[];
Alerts: [];
Sorties: ISortie[];
LiteSorties: ILiteSortie[];
SyndicateMissions: ISyndicateMissionInfo[];
ActiveMissions: IFissure[];
FlashSales: IFlashSale[];
GlobalUpgrades: IGlobalUpgrade[];
Invasions: IInvasion[];
NodeOverrides: INodeOverride[];
@ -37,19 +39,56 @@ export interface IGoal {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
Count: number;
Goal: number;
Success: number;
Personal: boolean;
Count?: number;
Goal?: number;
InterimGoals?: number[];
BonusGoal?: number;
HealthPct?: number;
Success?: number;
Personal?: boolean;
Bounty?: boolean;
Faction?: string;
ClampNodeScores?: boolean;
Desc: string;
ToolTip?: string;
Transmission?: string;
InstructionalItem?: string;
Icon: string;
Tag: string;
Node: string;
Node?: string;
VictimNode?: string;
ConcurrentMissionKeyNames?: string[];
ConcurrentNodeReqs?: number[];
ConcurrentNodes?: string[];
RegionIdx?: number;
Regions?: number[];
MissionKeyName?: string;
Reward?: IMissionReward;
InterimRewards?: IMissionReward[];
BonusReward?: IMissionReward;
JobAffiliationTag?: string;
Jobs?: ISyndicateJob[];
PreviousJobs?: ISyndicateJob[];
JobCurrentVersion?: IOid;
JobPreviousVersion?: IOid;
ScoreVar?: string;
NightLevel?: string;
}
export interface ISyndicateJob {
jobType?: string;
rewards: string;
masteryReq?: number;
minEnemyLevel: number;
maxEnemyLevel: number;
xpAmounts: number[];
endless?: boolean;
locationTag?: string;
isVault?: boolean;
requiredItems?: string[];
useRequiredItemsAsMiscItemFee?: boolean;
}
export interface ISyndicateMissionInfo {
@ -59,17 +98,7 @@ export interface ISyndicateMissionInfo {
Tag: string;
Seed: number;
Nodes: string[];
Jobs?: {
jobType?: string;
rewards: string;
masteryReq: number;
minEnemyLevel: number;
maxEnemyLevel: number;
xpAmounts: number[];
endless?: boolean;
locationTag?: string;
isVault?: boolean;
}[];
Jobs?: ISyndicateJob[];
}
export interface IGlobalUpgrade {
@ -265,6 +294,7 @@ export interface IEndlessXpChoice {
export interface ISeasonChallenge {
_id: IOid;
Daily?: boolean;
Permanent?: boolean; // only for getPastWeeklyChallenges response
Activation: IMongoDate;
Expiry: IMongoDate;
Challenge: string;
@ -303,6 +333,37 @@ export type TCircuitGameMode =
| "Assassination"
| "Alchemy";
export interface IFlashSale {
TypeName: string;
ShowInMarket: boolean;
HideFromMarket: boolean;
SupporterPack: boolean;
Discount: number;
BogoBuy: number;
BogoGet: number;
PremiumOverride: number;
RegularOverride: number;
ProductExpiryOverride?: IMongoDate;
StartDate: IMongoDate;
EndDate: IMongoDate;
}
export interface IInGameMarket {
LandingPage: ILandingPage;
}
export interface ILandingPage {
Categories: IGameMarketCategory[];
}
export interface IGameMarketCategory {
CategoryName: string;
Name: string;
Icon: string;
AddToMenu?: boolean;
Items?: string[];
}
export interface ITmp {
cavabegin: string;
PurchasePlatformLockEnabled: boolean; // Seems unused

View File

@ -946,6 +946,22 @@
<option value="3" data-loc="worldState_we3"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.ghoulEmergenceOverride" data-loc="worldState_ghoulEmergence"></label>
<select class="form-control" id="worldState.ghoulEmergenceOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.plagueStarOverride" data-loc="worldState_plagueStar"></label>
<select class="form-control" id="worldState.plagueStarOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.starDaysOverride" data-loc="worldState_starDays"></label>
<select class="form-control" id="worldState.starDaysOverride" data-default="null">
@ -954,6 +970,26 @@
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill">
<label class="form-label" for="worldState.waterFightOverride" data-loc="worldState_waterFight"></label>
<select class="form-control" id="worldState.waterFightOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="flex-fill">
<label class="form-label" for="worldState.waterFightRewardsOverride" data-loc="worldState_waterFightRewards"></label>
<select class="form-control" id="worldState.waterFightRewardsOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="3" data-loc="worldState_from_2025"></option>
<option value="2" data-loc="worldState_from_2024"></option>
<option value="1" data-loc="worldState_from_2023"></option>
<option value="0" data-loc="worldState_pre_2023"></option>
</select>
</div>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.eidolonOverride" data-loc="worldState_eidolonOverride"></label>
<select class="form-control" id="worldState.eidolonOverride" data-default="">

View File

@ -212,7 +212,7 @@ function setActiveLanguage(lang) {
document.querySelector("[data-lang=" + lang + "]").classList.add("active");
window.dictPromise = new Promise(resolve => {
const webui_lang = ["en", "ru", "fr", "de", "zh", "es"].indexOf(lang) == -1 ? "en" : lang;
const webui_lang = ["en", "ru", "fr", "de", "zh", "es", "uk"].indexOf(lang) == -1 ? "en" : lang;
let script = document.getElementById("translations");
if (script) document.documentElement.removeChild(script);

View File

@ -11,7 +11,8 @@
margin-left: 7rem;
}
body.logged-in:has([data-lang="de"].active) #main-content {
body.logged-in:has([data-lang="de"].active) #main-content,
body.logged-in:has([data-lang="uk"].active) #main-content {
margin-left: 8rem;
}

View File

@ -246,6 +246,14 @@ dict = {
worldState_baroTennoConRelay: `Baros TennoCon Relais`,
worldState_starDays: `Sternen-Tage`,
worldState_galleonOfGhouls: `Galeone der Ghule`,
worldState_ghoulEmergence: `Ghul Ausrottung`,
worldState_plagueStar: `Plagenstern`,
worldState_waterFight: `Hitzefrei`,
worldState_waterFightRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_from_2025: `[UNTRANSLATED] from 2025`,
worldState_from_2024: `[UNTRANSLATED] from 2024`,
worldState_from_2023: `[UNTRANSLATED] from 2023`,
worldState_pre_2023: `[UNTRANSLATED] pre 2023`,
enabled: `Aktiviert`,
disabled: `Deaktiviert`,
worldState_we1: `Wochenende 1`,

View File

@ -245,6 +245,14 @@ dict = {
worldState_baroTennoConRelay: `Baro's TennoCon Relay`,
worldState_starDays: `Star Days`,
worldState_galleonOfGhouls: `Galleon of Ghouls`,
worldState_ghoulEmergence: `Ghoul Purge`,
worldState_plagueStar: `Plague Star`,
worldState_waterFight: `Dog Days`,
worldState_waterFightRewards: `Dog Days Rewards`,
worldState_from_2025: `from 2025`,
worldState_from_2024: `from 2024`,
worldState_from_2023: `from 2023`,
worldState_pre_2023: `pre 2023`,
enabled: `Enabled`,
disabled: `Disabled`,
worldState_we1: `Weekend 1`,

View File

@ -246,6 +246,14 @@ dict = {
worldState_baroTennoConRelay: `Repetidor de Baro de la TennoCon`,
worldState_starDays: `Días estelares`,
worldState_galleonOfGhouls: `Galeón de Gules`,
worldState_ghoulEmergence: `Purga de Gules`,
worldState_plagueStar: `Estrella Infestada`,
worldState_waterFight: `Canícula`,
worldState_waterFightRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_from_2025: `[UNTRANSLATED] from 2025`,
worldState_from_2024: `[UNTRANSLATED] from 2024`,
worldState_from_2023: `[UNTRANSLATED] from 2023`,
worldState_pre_2023: `[UNTRANSLATED] pre 2023`,
enabled: `Activado`,
disabled: `Desactivado`,
worldState_we1: `Semana 1`,

View File

@ -246,6 +246,14 @@ dict = {
worldState_baroTennoConRelay: `[UNTRANSLATED] Baro's TennoCon Relay`,
worldState_starDays: `Jours Stellaires`,
worldState_galleonOfGhouls: `Galion des Goules`,
worldState_ghoulEmergence: `Purge des Goules`,
worldState_plagueStar: `Fléau Céleste`,
worldState_waterFight: `Bataille d'Eau`,
worldState_waterFightRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_from_2025: `[UNTRANSLATED] from 2025`,
worldState_from_2024: `[UNTRANSLATED] from 2024`,
worldState_from_2023: `[UNTRANSLATED] from 2023`,
worldState_pre_2023: `[UNTRANSLATED] pre 2023`,
enabled: `[UNTRANSLATED] Enabled`,
disabled: `Désactivé`,
worldState_we1: `Weekend 1`,

View File

@ -246,6 +246,14 @@ dict = {
worldState_baroTennoConRelay: `Реле Баро TennoCon`,
worldState_starDays: `Звёздные дни`,
worldState_galleonOfGhouls: `Галеон Гулей`,
worldState_ghoulEmergence: `Избавление от гулей`,
worldState_plagueStar: `Чумная звезда`,
worldState_waterFight: `Знойные дни`,
worldState_waterFightRewards: `Награды Знойных дней`,
worldState_from_2025: `из 2025`,
worldState_from_2024: `из 2024`,
worldState_from_2023: `из 2023`,
worldState_pre_2023: `до 2023`,
enabled: `Включено`,
disabled: `Отключено`,
worldState_we1: `Выходные 1`,

View File

@ -0,0 +1,368 @@
// Ukrainian translation by LoseFace
dict = {
general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати свій інвентар, наприклад, використовуючи команду /sync завантажувача, відвідавши Доджьо/Реле або перезавантаживши гру.`,
general_addButton: `Добавити`,
general_setButton: `Встановити`,
general_none: `Відсутній`,
general_bulkActions: `Масові дії`,
general_loading: `Завантаження...`,
code_loginFail: `Не вдалося увійти. Перевірте адресу електронної пошти та пароль.`,
code_regFail: `Не вдалося зареєструватися. Обліковий запис вже існує?`,
code_changeNameConfirm: `Яке ім'я ви хочете встановити для свого облікового запису?`,
code_deleteAccountConfirm: `Ви впевнені, що хочете видалити обліковий запис |DISPLAYNAME| (|EMAIL|)? Цю дію не можна скасувати.`,
code_archgun: `Арч-Пушка`,
code_melee: `Ближній бій`,
code_pistol: `Пістолет`,
code_rifle: `Гвинтівка`,
code_shotgun: `Рушниця`,
code_kitgun: `Складостріл`,
code_zaw: `Зо`,
code_moteAmp: `Порошинка`,
code_amp: `Підсилювач`,
code_kDrive: `К-Драйв`,
code_legendaryCore: `Легендарне ядро`,
code_traumaticPeculiar: `Травмуюча Странність`,
code_starter: `|MOD| (Пошкоджений)`,
code_badItem: `(Самозванець)`,
code_maxRank: `Максимальний рівень`,
code_rename: `Переіменувати`,
code_renamePrompt: `Введіть нове ім'я:`,
code_remove: `Видалити`,
code_addItemsConfirm: `Ви впевнені, що хочете додати |COUNT| предметів на ваш обліковий запис?`,
code_succRankUp: `Рівень успішно підвищено`,
code_noEquipmentToRankUp: `Немає спорядження для підвищення рівня.`,
code_succAdded: `Успішно додано.`,
code_succRemoved: `Успішно видалено.`,
code_buffsNumber: `Кількість позитивних ефектів`,
code_cursesNumber: `Кількість негативних ефектів`,
code_rerollsNumber: `Кількість рероллів`,
code_viewStats: `Перегляд характеристики`,
code_rank: `Рівень`,
code_rankUp: `Підвищити рівень`,
code_rankDown: `Понизити рівень`,
code_count: `Кількість`,
code_focusAllUnlocked: `Всі школи фокуса вже розблоковані.`,
code_focusUnlocked: `Розблоковано |COUNT| нових шкіл фокуса! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`,
code_addModsConfirm: `Ви впевнені, що хочете додати |COUNT| модифікаторів на ваш обліковий запис?`,
code_succImport: `Успішно імпортовано.`,
code_succRelog: `Готово. Зверніть увагу, що вам потрібно буде перезайти, щоб побачити зміни в грі.`,
code_nothingToDo: `Готово. Немає що робити.`,
code_gild: `Покращити`,
code_moa: `МОА`,
code_zanuka: `Гончарка`,
code_stage: `Етап`,
code_complete: `Завершити`,
code_nextStage: `Наступний етап`,
code_prevStage: `Попередній етап`,
code_reset: `Скинути`,
code_setInactive: `Зробити пригоду неактивною`,
code_completed: `Завершено`,
code_active: `Активний`,
code_pigment: `Пігмент`,
code_mature: `Підготувати до бою`,
code_unmature: `Обернути старіння`,
code_succChange: `Успішно змінено.`,
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне покращення.`,
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього сервера).`,
login_emailLabel: `Адреса електронної пошти`,
login_passwordLabel: `Пароль`,
login_loginButton: `Увійти`,
login_registerButton: `Зареєструватися`,
navbar_logout: `Вийти`,
navbar_renameAccount: `Переіменувати обліковий запис`,
navbar_deleteAccount: `Видалити обліковий запис`,
navbar_inventory: `Спорядження`,
navbar_mods: `Моди`,
navbar_quests: `Пригоди`,
navbar_cheats: `Чити`,
navbar_import: `Імпорт`,
inventory_addItems: `Додати предмети`,
inventory_suits: `Ворфрейми`,
inventory_longGuns: `Основне озброєння`,
inventory_pistols: `Допоміжне озброєння`,
inventory_melee: `Холодне озброєння`,
inventory_spaceSuits: `Арквінґи`,
inventory_spaceGuns: `Озброєння арквінґів`,
inventory_spaceMelee: `Холодне озброєння арквінґів`,
inventory_mechSuits: `Некрамехи`,
inventory_sentinels: `Вартові`,
inventory_sentinelWeapons: `Озброєння вартових`,
inventory_operatorAmps: `Підсилювачі`,
inventory_hoverboards: `К-Драйви`,
inventory_moaPets: `МОА`,
inventory_kubrowPets: `Тварини`,
inventory_evolutionProgress: `Прогрес Еволюції Інкарнонов`,
inventory_Boosters: `Бустери`,
inventory_bulkAddSuits: `Додати відсутні ворфрейми`,
inventory_bulkAddWeapons: `Додати відсутнє озброєння`,
inventory_bulkAddSpaceSuits: `Додати відсутні арквінґи`,
inventory_bulkAddSpaceWeapons: `Додати відсутнє озброєння арквінґів`,
inventory_bulkAddSentinels: `Додати відсутніх вартових`,
inventory_bulkAddSentinelWeapons: `Додати відсутнє озброєння вартових`,
inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес Еволюції Інкарнонов`,
inventory_bulkRankUpSuits: `Максимальний рівень всіх ворфреймів`,
inventory_bulkRankUpWeapons: `Максимальний рівень всього озброєння`,
inventory_bulkRankUpSpaceSuits: `Максимальний рівень всіх арквінґів`,
inventory_bulkRankUpSpaceWeapons: `Максимальний рівень всього озброєння арквінґів`,
inventory_bulkRankUpSentinels: `Максимальний рівень всіх вартових`,
inventory_bulkRankUpSentinelWeapons: `Максимальний рівень всього озброєння вартових`,
inventory_bulkRankUpEvolutionProgress: `Максимальний рівень всіх еволюцій Інкарнонов`,
inventory_maxPlexus: `Максимальний рівень Плексу`,
quests_list: `Пригоди`,
quests_completeAll: `Закінчити всі пригоди`,
quests_resetAll: `Скинути прогрес всіх пригод`,
quests_giveAll: `Видати всі пригоди`,
currency_RegularCredits: `Кредити`,
currency_PremiumCredits: `Платина`,
currency_FusionPoints: `Ендо`,
currency_PrimeTokens: `Королівські Ая`,
currency_owned: `У тебе |COUNT|.`,
detailedView_archonShardsLabel: `Клітинки осколків архонта`,
detailedView_archonShardsDescription: `Ви можете використовувати ці необмежені клітинки для встановлення безлічі вдосконалень.`,
detailedView_archonShardsDescription2: `Зверніть увагу: кожен уламок архонта застосовується з затримкою при завантаженні.`,
detailedView_valenceBonusLabel: `Ознака Валентності`,
detailedView_valenceBonusDescription: `Ви можете встановити або прибрати ознака валентності з вашої зброї.`,
detailedView_modularPartsLabel: `Змінити Модульні Частини`,
detailedView_suitInvigorationLabel: `Зміцнення Ворфрейма`,
detailedView_loadoutLabel: `Конфігурації`,
invigorations_offensive_AbilityStrength: `+200% Потужності Здібностей`,
invigorations_offensive_AbilityRange: `+100% Досяжність Здібностей`,
invigorations_offensive_AbilityDuration: `+100% Тривалість Здібностей`,
invigorations_offensive_MeleeDamage: `+250% Шкода Ближнього Бою`,
invigorations_offensive_PrimaryDamage: `+250% Шкода Основного Озброєння`,
invigorations_offensive_SecondaryDamage: `+250% Шкода Допоміжного Озброєння`,
invigorations_offensive_PrimaryCritChance: `+200% Імовірність Критичної Шкоди Основного Озброєння`,
invigorations_offensive_SecondaryCritChance: `+200% Імовірність Критичної Шкоди Допоміжного Озброєння`,
invigorations_offensive_MeleeCritChance: `+200% Імовірність Критичної Шкоди Ближнього Бою`,
invigorations_utility_AbilityEfficiency: `+75% Ощадливість Здібностей`,
invigorations_utility_SprintSpeed: `+75% Швидкість Бігу`,
invigorations_utility_ParkourVelocity: `+75% Швидкість Паркура`,
invigorations_utility_HealthMax: `+1000 Здоров'я`,
invigorations_utility_EnergyMax: `+200% Максимум Енергії`,
invigorations_utility_StatusImmune: `Імунітет до Ефектів Статусу`,
invigorations_utility_ReloadSpeed: `+75% Швидкість Перезаряджання`,
invigorations_utility_HealthRegen: `+25 Здоров'я в секунду`,
invigorations_utility_ArmorMax: `+1000 Захисту`,
invigorations_utility_Jumps: `+5 Оновлень Стрибків`,
invigorations_utility_EnergyRegen: `+2 Енергії в секунду`,
invigorations_offensiveLabel: `Атакуюче Вдосконалення`,
invigorations_defensiveLabel: `Вспомогательное Вдосконалення`,
invigorations_expiryLabel: `Термін дії Зміцнення (необов'язково)`,
abilityOverride_label: `Перевизначення здібностей`,
abilityOverride_onSlot: `у клітинці`,
mods_addRiven: `Добавити Модифікатор Розколу`,
mods_fingerprint: `Відбиток`,
mods_fingerprintHelp: `Потрібна допомога з відбитком?`,
mods_rivens: `Модифікатори Розколу`,
mods_mods: `Модифікатори`,
mods_addMax: `Добавити максимально вдосконалений`,
mods_addMissingUnrankedMods: `Добавити недостаючі модифікатори без рівня`,
mods_removeUnranked: `Видалити модифікатори без рівня`,
mods_addMissingMaxRankMods: `Добавити недостаючі модифікатори максимального рівня`,
cheats_administratorRequirement: `Ви повинні бути адміністратором для використання цієї функції. Щоб стати адміністратором, додайте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
cheats_server: `Сервер`,
cheats_skipTutorial: `Пропустити навчання`,
cheats_skipAllDialogue: `Пропустити всі діалоги`,
cheats_unlockAllScans: `Розблокувати всі сканування`,
cheats_unlockAllMissions: `Розблокувати всі місії`,
cheats_unlockAllMissions_ok: `Успіх. Будь ласка, зверніть увагу, що вам потрібно буде увійти в Доджьо/Реле або перезайти, щоб клієнт оновив зоряну мапу.`,
cheats_infiniteCredits: `Бескінечні кредити`,
cheats_infinitePlatinum: `Бескінечна платина`,
cheats_infiniteEndo: `Бескінечне ендо`,
cheats_infiniteRegalAya: `Бескінечна Королівська Ая`,
cheats_infiniteHelminthMaterials: `Бескінечні Секреції Гельмінта`,
cheats_claimingBlueprintRefundsIngredients: `Повернення інгредієнтів креслеників`,
cheats_dontSubtractPurchaseCreditCost: `Не вираховувати вартість кредитів при купівлі`,
cheats_dontSubtractPurchasePlatinumCost: `Не вираховувати вартість платини при купівлі`,
cheats_dontSubtractPurchaseItemCost: `Не вираховувати вартість предметів при купівлі`,
cheats_dontSubtractPurchaseStandingCost: `Не вираховувати вартість репутації при купівлі`,
cheats_dontSubtractVoidTraces: `Не вираховувати кількість Відголосків Безодні`,
cheats_dontSubtractConsumables: `Не вираховувати кількість витратних матеріалів`,
cheats_unlockAllShipFeatures: `Розблокувати всі функції судна`,
cheats_unlockAllShipDecorations: `Розблокувати всі прикраси судна`,
cheats_unlockAllFlavourItems: `Розблокувати всі <abbr title="Набори анімацій, гліфи, палітри і т. д.">унікальні предмети</abbr>`,
cheats_unlockAllSkins: `Розблокувати всі скіни`,
cheats_unlockAllCapturaScenes: `Розблокувати всі сцени Світлописця`,
cheats_unlockAllDecoRecipes: `Розблокувати всі рецепти декорацій Доджьо`,
cheats_universalPolarityEverywhere: `Будь-яка полярність скрізь`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Орокінські Реактори/Каталізатори скрізь`,
cheats_unlockExilusEverywhere: `Ексилотримач скрізь`,
cheats_unlockArcanesEverywhere: `Тримач містифікаторів скрізь`,
cheats_noDailyStandingLimits: `Без щоденних лімітів репутації`,
cheats_noDailyFocusLimit: `Без щоденних лімітів фокуса`,
cheats_noArgonCrystalDecay: `Без розпаду аргонових кристалів`,
cheats_noMasteryRankUpCooldown: `Підвищення ранга майстерності без очікування`,
cheats_noVendorPurchaseLimits: `Відсутність лімітів на купівлю у продавців`,
cheats_noDeathMarks: `Без позначок смерті`,
cheats_noKimCooldowns: `Чати KIM без очікування`,
cheats_fullyStockedVendors: `Повністю укомплектовані продавці`,
cheats_baroAlwaysAvailable: `Баро завжди доступний`,
cheats_baroFullyStocked: `Баро повністю укомплектований`,
cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
cheats_instantFinishRivenChallenge: `Миттєве завершення випробування Модифікатора Розколу`,
cheats_instantResourceExtractorDrones: `Миттєво добуваючі дрони-видобувачі`,
cheats_noResourceExtractorDronesDamage: `Без шкоди по дронам-видобувачам`,
cheats_skipClanKeyCrafting: `Пропустити створення кланового ключа`,
cheats_noDojoRoomBuildStage: `Миттєве будівництво Кімнат Доджьо`,
cheats_noDojoDecoBuildStage: `Миттєве будівництво Декорацій Доджьо`,
cheats_fastDojoRoomDestruction: `Миттєве знищення Кімнат Доджьо`,
cheats_noDojoResearchCosts: `Безкоштовні Дослідження Доджьо`,
cheats_noDojoResearchTime: `Миттєві Дослідження Доджьо`,
cheats_fastClanAscension: `Миттєве Піднесення Клану`,
cheats_missionsCanGiveAllRelics: `Місії можуть давати всі реліквії`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Вийняткові реліквії завжди дають бронзову нагороду`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `Бездоганні реліквії завжди дають срібну нагороду`,
cheats_radiantRelicsAlwaysGiveGoldReward: `Сяйнисті реліквії завжди дають золоту нагороду`,
cheats_unlockAllSimarisResearchEntries: `Розблокувати всі записи досліджень Симаріса`,
cheats_disableDailyTribute: `Вимкнути щоденні нагороди`,
cheats_spoofMasteryRank: `Підроблений ранг майстерності (-1 для вимкнення)`,
cheats_relicRewardItemCountMultiplier: `Множник кількості предметів нагороди реліквії`,
cheats_nightwaveStandingMultiplier: `Множник репутації Нічної хвилі`,
cheats_save: `Зберегти`,
cheats_account: `Обліковий запис`,
cheats_unlockAllFocusSchools: `Розблокувати всі школи фокуса`,
cheats_helminthUnlockAll: `Повністю покращити Гельмінта`,
cheats_addMissingSubsumedAbilities: `Додати відсутні поглинуті здібності`,
cheats_intrinsicsUnlockAll: `Повністю покращити Кваліфікації`,
cheats_changeSupportedSyndicate: `Підтримуваний синдикат`,
cheats_changeButton: `Змінити`,
cheats_markAllAsRead: `Помітити всі вхідні як прочитані`,
worldState: `Стан світу`,
worldState_creditBoost: `Глобальний бустер кредитів`,
worldState_affinityBoost: `Глобальний бустер синтезу`,
worldState_resourceBoost: `Глобальний бустер ресурсів`,
worldState_tennoLiveRelay: `Реле TennoLive`,
worldState_baroTennoConRelay: `Реле Баро TennoCon`,
worldState_starDays: `Зоряні дні`,
worldState_galleonOfGhouls: `Гульський Галеон`,
worldState_ghoulEmergence: `Зачищення від гулів`,
worldState_plagueStar: `Морова зірка`,
worldState_waterFight: `Спекотні дні`,
worldState_waterFightRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_from_2025: `[UNTRANSLATED] from 2025`,
worldState_from_2024: `[UNTRANSLATED] from 2024`,
worldState_from_2023: `[UNTRANSLATED] from 2023`,
worldState_pre_2023: `[UNTRANSLATED] pre 2023`,
enabled: `Увімкнено`,
disabled: `Вимкнено`,
worldState_we1: `Вихідні 1`,
worldState_we2: `Вихідні 2`,
worldState_we3: `Вихідні 3`,
worldState_eidolonOverride: `Цикл Рівнин Ейдолонів/Деймоса`,
worldState_day: `День/Фасс`,
worldState_night: `Ніч/Воум`,
worldState_vallisOverride: `Цикл Долини куль`,
worldState_warm: `Тепло`,
worldState_cold: `Холод`,
worldState_duviriOverride: `Цикл Дувірі`,
worldState_joy: `Радість`,
worldState_anger: `Гнів`,
worldState_envy: `Заздрість`,
worldState_sorrow: `Скорбота`,
worldState_fear: `Страх`,
worldState_nightwaveOverride: `Сезон Нічної хвилі`,
worldState_RadioLegionIntermission13Syndicate: `Вибірка Нори 9`,
worldState_RadioLegionIntermission12Syndicate: `Вибірка Нори 8`,
worldState_RadioLegionIntermission11Syndicate: `Вибірка Нори 7`,
worldState_RadioLegionIntermission10Syndicate: `Вибірка Нори 6`,
worldState_RadioLegionIntermission9Syndicate: `Вибірка Нори 5`,
worldState_RadioLegionIntermission8Syndicate: `Вибірка Нори 4`,
worldState_RadioLegionIntermission7Syndicate: `Вибірка Нори 3`,
worldState_RadioLegionIntermission6Syndicate: `Вибірка Нори 2`,
worldState_RadioLegionIntermission5Syndicate: `Вибірка Нори 1`,
worldState_RadioLegionIntermission4Syndicate: `Вибір Нори`,
worldState_RadioLegionIntermission3Syndicate: `Антракт III`,
worldState_RadioLegion3Syndicate: `Скляр`,
worldState_RadioLegionIntermission2Syndicate: `Антракт II`,
worldState_RadioLegion2Syndicate: `Емісар`,
worldState_RadioLegionIntermissionSyndicate: `Антракт I`,
worldState_RadioLegionSyndicate: `Вовк із Сатурна-6`,
worldState_fissures: `Прориви порожнечі`,
normal: `Стандартні`,
worldState_allAtOnceNormal: `Всі одразу, в звичайному режимі`,
worldState_allAtOnceSteelPath: `Всі одразу, в режимі Шляху Сталі`,
worldState_theCircuitOverride: `Типи місій у підземеллі Дувірі`,
worldState_darvoStockMultiplier: `Множник Запасів Дарво`,
worldState_varziaFullyStocked: `Повний Асортимент Варзії`,
worldState_varziaOverride: `Зміна Ротації Варзії`,
import_importNote: `Ви можете завантажити повну або часткову відповідь спорядження (клієнтське представлення) тут. Всі підтримувані поля <b>будуть перезаписані</b> у вашому акаунті.`,
import_submit: `Відправити`,
import_samples: `Приклад:`,
import_samples_maxFocus: `Всі школи Фокуса максимального рівня`,
upgrade_Equilibrium: `+|VAL|% Енергія від підбирання здоров'я, +|VAL|% Здоров'я від підбирання енергії`,
upgrade_MeleeCritDamage: `+|VAL|% Критична шкода ближнього бою`,
upgrade_PrimaryStatusChance: `+|VAL|% Імовірність накладення ефекту стану основною зброєю`,
upgrade_SecondaryCritChance: `+|VAL|% Імовірність критичної шкоди допоміжною зброєю`,
upgrade_WarframeAbilityDuration: `+|VAL|% Тривалість здібностей`,
upgrade_WarframeAbilityStrength: `+|VAL|% Потужність здібностей`,
upgrade_WarframeArmorMax: `+|VAL| Захист`,
upgrade_WarframeBlastProc: `+|VAL| Щит при вбивстві з Вибуховим Уронoм`,
upgrade_WarframeCastingSpeed: `+|VAL|% Швидкість Застосування Здібностей`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Урон Здібностей по ворогам, ураженим Корозією`,
upgrade_WarframeCorrosiveStack: `Збільшити макс. стаки Корозії на +|VAL|`,
upgrade_WarframeCritDamageBoost: `+|VAL|% Критична шкода Ближнього Бою (Подвоюється при 500 Енергії)`,
upgrade_WarframeElectricDamage: `+|VAL1|% Урон Електрикою Основним Озброєнням (+|VAL2|% за кожен додатковий Уламок)`,
upgrade_WarframeElectricDamageBoost: `+|VAL|% Шкода Здібностей по ворогам, ураженим Електрикою`,
upgrade_WarframeEnergyMax: `+|VAL| Макс. Енергія`,
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% Ефективність згустків Енергії`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% Ефективність згустків Здоров'я`,
upgrade_WarframeHealthMax: `+|VAL| Макс. Здоров'я`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| Здоров'я при вбивстві з Вибуховою шкодою (Макс. |VAL2| Здоров'я)`,
upgrade_WarframeParkourVelocity: `+|VAL|% Швидкість Паркура`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% Шкода Здібностей по ворогам, ураженим Радіацією`,
upgrade_WarframeHealthRegen: `+|VAL| Здоров'я в секунду`,
upgrade_WarframeShieldMax: `+|VAL| Щиту`,
upgrade_WarframeStartingEnergy: `+|VAL|% Енергії при Спавні`,
upgrade_WarframeToxinDamage: `+|VAL|% Шкода Токсином`,
upgrade_WarframeToxinHeal: `+|VAL| Здоров'я при нанесенні шкоди ворогам з Токсином`,
upgrade_WeaponCritBoostFromHeat: `+|VAL1|% Імовірність Критичної Шкоди Допоміжною Зброєю за кожного вбитого ворога, ураженого Термічною шкодою (Макс. |VAL2|%)`,
upgrade_AvatarAbilityRange: `+7.5% Досяжність Здібностей`,
upgrade_AvatarAbilityEfficiency: `+5% Ощадливість Здібностей`,
upgrade_AvatarEnergyRegen: `+0.5 Відновлення Енергії в секунду`,
upgrade_AvatarEnemyRadar: `+5m Виявлення ворогів`,
upgrade_AvatarLootRadar: `+7m Виявлення здобичі`,
upgrade_WeaponAmmoMax: `+15% Макс. Набоїв`,
upgrade_EnemyArmorReductionAura: `-3% Захист Ворогів`,
upgrade_OnExecutionAmmo: `+100% Заповнення Магазина Основного і Допоміжного Озброєння при вбивстві Милосердям`,
upgrade_OnExecutionHealthDrop: `+100% Імовірність Падіння згустка Здоров'я при вбивстві Милосердям`,
upgrade_OnExecutionEnergyDrop: `+50% Імовірність Падіння згустка Енергії при вбивстві Милосердям`,
upgrade_OnFailHackReset: `+50% Імовірність Повтора Зламу`,
upgrade_DamageReductionOnHack: `+75% Зменшення Шкоди під час Зламу`,
upgrade_OnExecutionReviveCompanion: `Вбивства Милосердям зменшують час відновлення Компаньйона на 15 секунд`,
upgrade_OnExecutionParkourSpeed: `+60% Швидкість Паркура після вбивства Милосердям на 15 секунд`,
upgrade_AvatarTimeLimitIncrease: `+8 секунд до Зламу`,
upgrade_ElectrifyOnHack: `Шокувати ворогів в межах 20 метрів під час Зламу`,
upgrade_OnExecutionTerrify: `+50% Імовірність, що вороги в межах 15 метрів будуть тремтіти від страху протягом 8 секунд після вбивства Милосердям`,
upgrade_OnHackLockers: `Відкрити 5 шафок в межах 20 метрів після Зламу`,
upgrade_OnExecutionBlind: `Засліпити ворогів в межах 18 метрів після вбивства Милосердям`,
upgrade_OnExecutionDrainPower: `Наступне застосування здібності отримує +50% Потужності Здібності після вбивства Милосердям`,
upgrade_OnHackSprintSpeed: `+75% Швидкість Бігу протягом 15 секунд після Зламу`,
upgrade_SwiftExecute: `+50% Швидкість Вбивства Милосердям`,
upgrade_OnHackInvis: `Невидимість протягом 15 секунд після Зламу`,
damageType_Electricity: `Електричний`,
damageType_Fire: `Трммічний`,
damageType_Freeze: `Крижаний`,
damageType_Impact: `Ударний`,
damageType_Magnetic: `Магнетичний`,
damageType_Poison: `Токсичний`,
damageType_Radiation: `Радіаційний`,
theme_dark: `Темна тема`,
theme_light: `Світла тема`,
prettier_sucks_ass: ``
};

View File

@ -1,16 +1,16 @@
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang & Corvus
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun
dict = {
general_inventoryUpdateNote: `注意:要在游戏中查看更改,您需要重新同步库存,例如使用引导程序的 /sync 命令、访问道场/中继站或重新登录客户端.`,
general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录。`,
general_addButton: `添加`,
general_setButton: `设置`,
general_none: ``,
general_bulkActions: `批量操作`,
general_loading: `加载中...`,
code_loginFail: `登录失败.请检查邮箱和密码.`,
code_regFail: `注册失败.账号已存在.`,
code_loginFail: `登录失败。请检查邮箱和密码。`,
code_regFail: `注册失败。账号是否已存在?`,
code_changeNameConfirm: `您想将账户名称更改为?`,
code_deleteAccountConfirm: `确定要删除账户 |DISPLAYNAME|(|EMAIL|)吗?此操作不可撤销。`,
code_deleteAccountConfirm: `确定要删除您的账户 |DISPLAYNAME|(|EMAIL|) 吗?此操作不可撤销。`,
code_archgun: `空战`,
code_melee: `近战`,
code_pistol: `手枪`,
@ -23,17 +23,17 @@ dict = {
code_kDrive: `K式悬浮板`,
code_legendaryCore: `传奇核心`,
code_traumaticPeculiar: `创伤怪奇`,
code_starter: `|MOD|(有瑕疵的)`,
code_starter: `|MOD| (有瑕疵的)`,
code_badItem: `(Imposter)`,
code_maxRank: `满级`,
code_rename: `重命名`,
code_renamePrompt: `输入新的自定义名称:`,
code_renamePrompt: `输入新的自定义名称`,
code_remove: `移除`,
code_addItemsConfirm: `确定要向账户添加|COUNT|件物品吗?`,
code_addItemsConfirm: `确定要向您的账户添加 |COUNT| 件物品吗?`,
code_succRankUp: `等级已提升`,
code_noEquipmentToRankUp: `没有可升级的装备`,
code_succAdded: `已成功添加。`,
code_succRemoved: `已成功移除。`,
code_noEquipmentToRankUp: `没有可升级的装备`,
code_succAdded: `添加成功`,
code_succRemoved: `移除成功`,
code_buffsNumber: `增益数量`,
code_cursesNumber: `负面数量`,
code_rerollsNumber: `洗卡次数`,
@ -42,12 +42,12 @@ dict = {
code_rankUp: `等级提升`,
code_rankDown: `等级下降`,
code_count: `数量`,
code_focusAllUnlocked: `所有专精学派均已解锁`,
code_focusUnlocked: `已解锁|COUNT|个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新.`,
code_addModsConfirm: `确定要向账户添加|COUNT|张MOD吗?`,
code_succImport: `导入成功`,
code_succRelog: `完成. 需要重新登录游戏才能看到变化.`,
code_nothingToDo: `完成. 没有可执行的操作.`,
code_focusAllUnlocked: `所有专精学派均已解锁`,
code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新`,
code_addModsConfirm: `确定要向您的账户添加 |COUNT| 张MOD吗?`,
code_succImport: `导入成功`,
code_succRelog: `完成。需要重新登录游戏才能看到变化。`,
code_nothingToDo: `完成。没有可执行的操作。`,
code_gild: `镀金`,
code_moa: `恐鸟`,
code_zanuka: `猎犬`,
@ -62,9 +62,9 @@ dict = {
code_pigment: `颜料`,
code_mature: `成长并战备`,
code_unmature: `逆转衰老基因`,
code_succChange: `更改成功.`,
code_requiredInvigorationUpgrade: `您必须同时选择一个进攻型和一个功能型活化属性.`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同).`,
code_succChange: `更改成功`,
code_requiredInvigorationUpgrade: `您必须同时选择一个进攻型和一个功能型活化属性`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`,
login_loginButton: `登录`,
@ -125,7 +125,7 @@ dict = {
detailedView_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`,
detailedView_archonShardsDescription2: `请注意,在加载时,每个执政官源力石都需要一定的时间来生效。`,
detailedView_valenceBonusLabel: `效价加成`,
detailedView_valenceBonusDescription: `您可以设置或移除武器上的效价加成.`,
detailedView_valenceBonusDescription: `您可以设置或移除武器上的效价加成`,
detailedView_modularPartsLabel: `更换部件`,
detailedView_suitInvigorationLabel: `编辑战甲活化属性`,
detailedView_loadoutLabel: `配置`,
@ -168,19 +168,19 @@ dict = {
mods_addMissingUnrankedMods: `添加所有缺失的Mods`,
mods_removeUnranked: `删除所有未升级的Mods`,
mods_addMissingMaxRankMods: `添加所有缺失的满级Mods`,
cheats_administratorRequirement: `您必须是管理员才能使用此功能.要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中.`,
cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请在 config.json 中将 <code>|DISPLAYNAME|</code> 添加到 <code>administratorNames</code>`,
cheats_server: `服务器`,
cheats_skipTutorial: `跳过教程`,
cheats_skipAllDialogue: `跳过所有对话`,
cheats_unlockAllScans: `解锁所有扫描`,
cheats_unlockAllMissions: `解锁所有任务`,
cheats_unlockAllMissions_ok: `操作成功.请注意,您需要进入道场/中继站或重新登录客户端以刷新星图数据。`,
cheats_unlockAllMissions: `解锁所有星图`,
cheats_unlockAllMissions_ok: `操作成功请注意,您需要进入道场/中继站或重新登录以刷新星图数据。`,
cheats_infiniteCredits: `无限现金`,
cheats_infinitePlatinum: `无限白金`,
cheats_infiniteEndo: `无限内融核心`,
cheats_infiniteRegalAya: `无限御品阿耶`,
cheats_infiniteHelminthMaterials: `无限Helminth材料`,
cheats_claimingBlueprintRefundsIngredients: `取消蓝图制造时返还材料`,
cheats_claimingBlueprintRefundsIngredients: `蓝图制造完成领取后返还材料`,
cheats_dontSubtractPurchaseCreditCost: `购物时不减少现金花费`,
cheats_dontSubtractPurchasePlatinumCost: `购物时不减少白金花费`,
cheats_dontSubtractPurchaseItemCost: `购物时不减少物品花费`,
@ -246,6 +246,14 @@ dict = {
worldState_baroTennoConRelay: `Baro的TennoCon中继站`,
worldState_starDays: `活动:星日`,
worldState_galleonOfGhouls: `战术警报:尸鬼的帆船战舰`,
worldState_ghoulEmergence: `尸鬼净化`,
worldState_plagueStar: `瘟疫之星`,
worldState_waterFight: `三伏天`,
worldState_waterFightRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_from_2025: `[UNTRANSLATED] from 2025`,
worldState_from_2024: `[UNTRANSLATED] from 2024`,
worldState_from_2023: `[UNTRANSLATED] from 2023`,
worldState_pre_2023: `[UNTRANSLATED] pre 2023`,
enabled: `启用`,
disabled: `关闭/取消配置`,
worldState_we1: `活动阶段:第一周`,
@ -289,7 +297,7 @@ dict = {
worldState_varziaFullyStocked: `瓦奇娅开启全部库存商品`,
worldState_varziaOverride: `瓦奇娅(Prime重生)轮换状态`,
import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示).支持的所有字段<b>将被覆盖</b>到您的账户中.`,
import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段<b>将被覆盖</b>到您的账户中。`,
import_submit: `提交`,
import_samples: `示例:`,
import_samples_maxFocus: `所有专精学派完全精通`,