From 4606980d7fcc62be1cab428738ecf654bf577d70 Mon Sep 17 00:00:00 2001
From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Date: Sat, 9 Aug 2025 01:57:54 +0200
Subject: [PATCH 1/5] feat(goals): cetus events
Includes `Plague Star` and `Ghoul Purge`.
Translation for webUI taken from game files.
Re #1103
---
config-vanilla.json | 4 +-
src/services/configService.ts | 2 +
src/services/missionInventoryUpdateService.ts | 90 +++++++++---
src/services/worldStateService.ts | 134 ++++++++++++++++++
src/types/worldStateTypes.ts | 49 ++++---
static/webui/index.html | 8 ++
static/webui/translations/de.js | 2 +
static/webui/translations/en.js | 2 +
static/webui/translations/es.js | 2 +
static/webui/translations/fr.js | 2 +
static/webui/translations/ru.js | 2 +
static/webui/translations/uk.js | 2 +
static/webui/translations/zh.js | 2 +
13 files changed, 262 insertions(+), 39 deletions(-)
diff --git a/config-vanilla.json b/config-vanilla.json
index 12600852..05e7905f 100644
--- a/config-vanilla.json
+++ b/config-vanilla.json
@@ -81,7 +81,9 @@
"circuitGameModes": null,
"darvoStockMultiplier": 1,
"varziaOverride": "",
- "varziaFullyStocked": false
+ "varziaFullyStocked": false,
+ "plagueStar": false,
+ "ghoulEmergence": false
},
"dev": {
"keepVendorsExpired": false
diff --git a/src/services/configService.ts b/src/services/configService.ts
index f46c747b..289e1e2e 100644
--- a/src/services/configService.ts
+++ b/src/services/configService.ts
@@ -94,6 +94,8 @@ export interface IConfig {
darvoStockMultiplier?: number;
varziaOverride?: string;
varziaFullyStocked?: boolean;
+ plagueStar?: boolean;
+ ghoulEmergence?: boolean;
};
dev?: {
keepVendorsExpired?: boolean;
diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts
index 1125be42..a498d2e6 100644
--- a/src/services/missionInventoryUpdateService.ts
+++ b/src/services/missionInventoryUpdateService.ts
@@ -76,7 +76,7 @@ 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";
@@ -1259,6 +1259,8 @@ export const addMissionRewards = async (
}
}
+ if (!AffiliationMods) 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 +1268,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 +1333,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 specialСase = [
+ { 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 = specialСase.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 +1698,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 +1795,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;
diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts
index a7b09372..2de08f46 100644
--- a/src/services/worldStateService.ts
+++ b/src/services/worldStateService.ts
@@ -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",
@@ -1556,6 +1563,65 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
});
}
+ if (config.worldState?.plagueStar) {
+ worldState.Goals.push({
+ _id: { $oid: "654a5058c757487cdb11824f" },
+ Activation: {
+ $date: {
+ $numberLong: "1699372800000"
+ }
+ },
+ Expiry: {
+ $date: {
+ $numberLong: "2000000000000"
+ }
+ },
+ 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"
+ });
+ }
+
// Nightwave Challenges
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
if (nightwaveSyndicateTag) {
@@ -1626,6 +1692,74 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
pushClassicBounties(worldState.SyndicateMissions, bountyCycle);
} while (isBeforeNextExpectedWorldStateRefresh(timeMs, bountyCycleEnd) && ++bountyCycle);
+ if (config.worldState?.ghoulEmergence) {
+ 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);
+
+ worldState.Goals.push({
+ _id: { $oid: "687ebbe6d1d17841c9c59f38" },
+ Activation: { $date: { $numberLong: "1753204900185" } },
+ Expiry: { $date: { $numberLong: "2000000000000" } },
+ HealthPct: 1,
+ 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 - 9000) / 1000) & 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" },
diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts
index 4ab502c7..a1b7feca 100644
--- a/src/types/worldStateTypes.ts
+++ b/src/types/worldStateTypes.ts
@@ -37,19 +37,46 @@ export interface IGoal {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
- Count: number;
- Goal: number;
- Success: number;
- Personal: boolean;
+ Count?: number;
+ Goal?: 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;
+ RegionIdx?: number;
+ Regions?: number[];
MissionKeyName?: string;
Reward?: IMissionReward;
+
+ JobAffiliationTag?: string;
+ Jobs?: ISyndicateJob[];
+ PreviousJobs?: ISyndicateJob[];
+ JobCurrentVersion?: IOid;
+ JobPreviousVersion?: IOid;
+}
+
+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 +86,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 {
diff --git a/static/webui/index.html b/static/webui/index.html
index b827fc45..10434d23 100644
--- a/static/webui/index.html
+++ b/static/webui/index.html
@@ -937,6 +937,14 @@
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
--
2.47.2
From 27e1621c26c830b11035ab575123d4596fba234d Mon Sep 17 00:00:00 2001
From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Date: Sun, 10 Aug 2025 17:04:57 +0200
Subject: [PATCH 5/5] Update config-vanilla.json
---
config-vanilla.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config-vanilla.json b/config-vanilla.json
index 7a4ba0af..a8834907 100644
--- a/config-vanilla.json
+++ b/config-vanilla.json
@@ -72,8 +72,8 @@
"resourceBoost": false,
"tennoLiveRelay": false,
"galleonOfGhouls": 0,
- "ghoulEmergence": null,
- "plagueStar": null,
+ "ghoulEmergenceOverride": null,
+ "plagueStarOverride": null,
"starDaysOverride": null,
"eidolonOverride": "",
"vallisOverride": "",
--
2.47.2