XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat: initial invasions (#2458)

A rough generation of 3 invasions that change at daily reset, so missing the planet-based invasion 'chains'. Battle pay is fully working tho, just a few points of uncertainty there due to missing research and logs. Death marks are also roughly working. Re #1097 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2458 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

7eb95c99
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

7 个文件 +488 -44
Modified src/controllers/api/inventoryController.ts +68 -2
@@ -6,10 +6,18 @@ import allDialogue from "@/static/fixed_responses/allDialogue.json";
6 6 import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
7 7 import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
8 8 import { IPolarity, ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
9 import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
9 import {
10 eFaction,
11 ExportCustoms,
12 ExportFlavour,
13 ExportResources,
14 ExportVirtuals,
15 ICountedItem
16 } from "warframe-public-export-plus";
10 17 import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
11 18 import {
12 19 addEmailItem,
20 addItem,
13 21 addMiscItems,
14 22 allDailyAffiliationKeys,
15 23 checkCalendarAutoAdvance,
@@ -30,7 +38,8 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
30 38 import { DailyDeal } from "@/src/models/worldStateModel";
31 39 import { EquipmentFeatures } from "@/src/types/equipmentTypes";
32 40 import { generateRewardSeed } from "@/src/services/rngService";
33 import { getWorldState } from "@/src/services/worldStateService";
41 import { getInvasionByOid, getWorldState } from "@/src/services/worldStateService";
42 import { createMessage } from "@/src/services/inboxService";
34 43
35 44 export const inventoryController: RequestHandler = async (request, response) => {
36 45 const account = await getAccountForRequest(request);
@@ -186,6 +195,63 @@ export const inventoryController: RequestHandler = async (request, response) =>
186 195 //await inventory.save();
187 196 }
188 197
198 for (let i = 0; i != inventory.QualifyingInvasions.length; ) {
199 const qi = inventory.QualifyingInvasions[i];
200 const invasion = getInvasionByOid(qi.invasionId.toString());
201 if (!invasion) {
202 logger.debug(`removing QualifyingInvasions entry for unknown invasion: ${qi.invasionId.toString()}`);
203 inventory.QualifyingInvasions.splice(i, 1);
204 continue;
205 }
206 if (invasion.Completed) {
207 let factionSidedWith: string | undefined;
208 let battlePay: ICountedItem[] | undefined;
209 if (qi.AttackerScore >= 3) {
210 factionSidedWith = invasion.Faction;
211 battlePay = invasion.AttackerReward.countedItems;
212 logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
213 } else if (qi.DefenderScore >= 3) {
214 factionSidedWith = invasion.DefenderFaction;
215 battlePay = invasion.DefenderReward.countedItems;
216 logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
217 }
218 if (factionSidedWith) {
219 if (battlePay) {
220 // Decoupling rewards from the inbox message because it may delete itself without being read
221 for (const item of battlePay) {
222 await addItem(inventory, item.ItemType, item.ItemCount);
223 }
224 await createMessage(account._id, [
225 {
226 sndr: eFaction.find(x => x.tag == factionSidedWith)?.name ?? factionSidedWith, // TOVERIFY
227 msg: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageBody`,
228 sub: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageSubject`,
229 countedAtt: battlePay,
230 attVisualOnly: true,
231 icon:
232 factionSidedWith == "FC_GRINEER"
233 ? "/Lotus/Interface/Icons/Npcs/EliteRifleLancerAvatar.png" // Source: https://www.reddit.com/r/Warframe/comments/1aj4usx/battle_pay_worth_10_plat/, https://www.youtube.com/watch?v=XhNZ6ai6BOY
234 : "/Lotus/Interface/Icons/Npcs/CrewmanNormal.png", // My best source for this is https://www.youtube.com/watch?v=rxrCCFm73XE around 1:37
235 // TOVERIFY: highPriority?
236 endDate: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's not clear if this is correct.
237 }
238 ]);
239 }
240 if (invasion.Faction != "FC_INFESTATION") {
241 // Sided with grineer -> opposed corpus -> send zanuka (harvester)
242 // Sided with corpus -> opposed grineer -> send g3 (death squad)
243 inventory[factionSidedWith != "FC_GRINEER" ? "DeathSquadable" : "Harvestable"] = true;
244 // TOVERIFY: Should this happen earlier?
245 // TOVERIFY: Should this send an (ephemeral) email?
246 }
247 }
248 logger.debug(`removing QualifyingInvasions entry for completed invasion: ${qi.invasionId.toString()}`);
249 inventory.QualifyingInvasions.splice(i, 1);
250 continue;
251 }
252 ++i;
253 }
254
189 255 if (inventory.LastInventorySync) {
190 256 const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
191 257 const currentDuviriMood = Math.trunc(Date.now() / 7200000);
Modified src/services/missionInventoryUpdateService.ts +2 -1
@@ -558,6 +558,7 @@ export const addMissionInventoryUpdates = async (
558 558 }
559 559 ]);
560 560 }
561 inventory.DeathSquadable = false;
561 562 break;
562 563 }
563 564 case "LockedWeaponGroup": {
@@ -576,7 +577,7 @@ export const addMissionInventoryUpdates = async (
576 577 break;
577 578 }
578 579 case "IncHarvester": {
579 inventory.Harvestable = true;
580 // Unsure what to do with this
580 581 break;
581 582 }
582 583 case "CurrentLoadOutIds": {
Modified src/services/worldStateService.ts +91 -1
@@ -6,15 +6,18 @@ import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.j
6 6 import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json";
7 7 import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
8 8 import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
9 import invasionNodes from "@/static/fixed_responses/worldState/invasionNodes.json";
10 import invasionRewards from "@/static/fixed_responses/worldState/invasionRewards.json";
9 11 import { buildConfig } from "@/src/services/buildConfigService";
10 12 import { unixTimesInMs } from "@/src/constants/timeConstants";
11 13 import { config } from "@/src/services/configService";
12 14 import { getRandomElement, getRandomInt, sequentiallyUniqueRandomElement, SRng } from "@/src/services/rngService";
13 import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
15 import { eMissionType, ExportRegions, ExportSyndicates, IMissionReward, IRegion } from "warframe-public-export-plus";
14 16 import {
15 17 ICalendarDay,
16 18 ICalendarEvent,
17 19 ICalendarSeason,
20 IInvasion,
18 21 ILiteSortie,
19 22 IPrimeVaultTrader,
20 23 IPrimeVaultTraderOffer,
@@ -1227,6 +1230,78 @@ const getAllVarziaManifests = (): IPrimeVaultTraderOffer[] => {
1227 1230 return [...dualPacks, ...singlePacks, ...items, ...bobbleHeads, ...relics];
1228 1231 };
1229 1232
1233 const createInvasion = (day: number, idx: number): IInvasion => {
1234 const id = day * 3 + idx;
1235 const defender = (["FC_GRINEER", "FC_CORPUS", day % 2 ? "FC_GRINEER" : "FC_CORPUS"] as const)[idx];
1236 const rng = new SRng(new SRng(id).randomInt(0, 1_000_000));
1237 const isInfestationOutbreak = rng.randomInt(0, 1) == 0;
1238 const attacker = isInfestationOutbreak ? "FC_INFESTATION" : defender == "FC_GRINEER" ? "FC_CORPUS" : "FC_GRINEER";
1239 const startMs = EPOCH + day * 86400_000;
1240 const oid =
1241 ((startMs / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
1242 "fd148cb8" +
1243 (idx & 0xffffffff).toString(16).padStart(8, "0");
1244 const node = sequentiallyUniqueRandomElement(invasionNodes[defender], id, 5, 690175)!; // Can't repeat the other 2 on this day nor the last 3
1245 const progress = (Date.now() - startMs) / 86400_000;
1246 const countMultiplier = isInfestationOutbreak || rng.randomInt(0, 1) ? -1 : 1; // if defender is winning, count is negative
1247 const fiftyPercent = rng.randomInt(1000, 29000); // introduce some 'yitter' for the percentages
1248 const rewardFloat = rng.randomFloat();
1249 const rewardTier = rewardFloat < 0.201 ? "RARE" : rewardFloat < 0.7788 ? "COMMON" : "UNCOMMON";
1250 const attackerReward: IMissionReward = {};
1251 const defenderReward: IMissionReward = {};
1252 if (isInfestationOutbreak) {
1253 defenderReward.countedItems = [
1254 rng.randomElement(invasionRewards[rng.randomInt(0, 1) ? "FC_INFESTATION" : defender][rewardTier])!
1255 ];
1256 } else {
1257 attackerReward.countedItems = [rng.randomElement(invasionRewards[attacker][rewardTier])!];
1258 defenderReward.countedItems = [rng.randomElement(invasionRewards[defender][rewardTier])!];
1259 }
1260 return {
1261 _id: { $oid: oid },
1262 Faction: attacker,
1263 DefenderFaction: defender,
1264 Node: node,
1265 Count: Math.round(
1266 (progress < 0.5 ? progress * 2 * fiftyPercent : fiftyPercent + (30_000 - fiftyPercent) * (progress - 0.5)) *
1267 countMultiplier
1268 ),
1269 Goal: 30000, // Value seems to range from 30000 to 98000 in intervals of 1000. Higher values are increasingly rare. I don't think this is relevant for the frontend besides dividing count by it.
1270 LocTag: isInfestationOutbreak
1271 ? ExportRegions[node].missionIndex == 0
1272 ? "/Lotus/Language/Menu/InfestedInvasionBoss"
1273 : "/Lotus/Language/Menu/InfestedInvasionGeneric"
1274 : attacker == "FC_CORPUS"
1275 ? "/Lotus/Language/Menu/CorpusInvasionGeneric"
1276 : "/Lotus/Language/Menu/GrineerInvasionGeneric",
1277 Completed: startMs + 86400_000 < Date.now(), // Sorta unfaithful. Invasions on live are (at least in part) in fluenced by people completing them. And otherwise also probably not hardcoded to last 24 hours.
1278 ChainID: { $oid: oid },
1279 AttackerReward: attackerReward,
1280 AttackerMissionInfo: {
1281 seed: rng.randomInt(0, 1_000_000),
1282 faction: defender
1283 },
1284 DefenderReward: defenderReward,
1285 DefenderMissionInfo: {
1286 seed: rng.randomInt(0, 1_000_000),
1287 faction: attacker
1288 },
1289 Activation: {
1290 $date: {
1291 $numberLong: startMs.toString()
1292 }
1293 }
1294 };
1295 };
1296
1297 export const getInvasionByOid = (oid: string): IInvasion | undefined => {
1298 const arr = oid.split("fd148cb8");
1299 if (arr.length == 2 && arr[0].length == 8 && arr[1].length == 8) {
1300 return createInvasion(idToDay(oid), parseInt(arr[1], 16));
1301 }
1302 return undefined;
1303 };
1304
1230 1305 export const getWorldState = (buildLabel?: string): IWorldState => {
1231 1306 const constraints: ITimeConstraint[] = [];
1232 1307 if (config.worldState?.eidolonOverride) {
@@ -1275,6 +1350,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
1275 1350 LiteSorties: [],
1276 1351 ActiveMissions: [],
1277 1352 GlobalUpgrades: [],
1353 Invasions: [],
1278 1354 VoidTraders: [],
1279 1355 PrimeVaultTraders: [],
1280 1356 VoidStorms: [],
@@ -1477,6 +1553,20 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
1477 1553 });
1478 1554 }
1479 1555
1556 // Rough outline of dynamic invasions.
1557 // TODO: Invasions chains, e.g. an infestation mission would soon lead to other nodes on that planet also having an infestation invasion.
1558 // TODO: Grineer/Corpus to fund their death stars with each invasion win.
1559 {
1560 worldState.Invasions.push(createInvasion(day, 0));
1561 worldState.Invasions.push(createInvasion(day, 1));
1562 worldState.Invasions.push(createInvasion(day, 2));
1563
1564 // Completed invasions stay for up to 24 hours as the winner 'occupies' that node
1565 worldState.Invasions.push(createInvasion(day - 1, 0));
1566 worldState.Invasions.push(createInvasion(day - 1, 1));
1567 worldState.Invasions.push(createInvasion(day - 1, 2));
1568 }
1569
1480 1570 // Baro
1481 1571 {
1482 1572 const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
Modified src/types/worldStateTypes.ts +23 -0
@@ -12,6 +12,7 @@ export interface IWorldState {
12 12 SyndicateMissions: ISyndicateMissionInfo[];
13 13 ActiveMissions: IFissure[];
14 14 GlobalUpgrades: IGlobalUpgrade[];
15 Invasions: IInvasion[];
15 16 NodeOverrides: INodeOverride[];
16 17 VoidTraders: IVoidTrader[];
17 18 PrimeVaultTraders: IPrimeVaultTrader[];
@@ -82,6 +83,28 @@ export interface IGlobalUpgrade {
82 83 LocalizeDescTag: string;
83 84 }
84 85
86 export interface IInvasion {
87 _id: IOid;
88 Faction: string;
89 DefenderFaction: string;
90 Node: string;
91 Count: number;
92 Goal: number;
93 LocTag: string;
94 Completed: boolean;
95 ChainID: IOid;
96 AttackerReward: IMissionReward;
97 AttackerMissionInfo: IInvasionMissionInfo;
98 DefenderReward: IMissionReward;
99 DefenderMissionInfo: IInvasionMissionInfo;
100 Activation: IMongoDate;
101 }
102
103 export interface IInvasionMissionInfo {
104 seed: number;
105 faction: string;
106 }
107
85 108 export interface IFissure {
86 109 _id: IOid;
87 110 Region: number;
Added static/fixed_responses/worldState/invasionNodes.json +114 -0
@@ -0,0 +1,114 @@
1 {
2 "FC_CORPUS": [
3 "SettlementNode1",
4 "SettlementNode2",
5 "SettlementNode3",
6 "SettlementNode11",
7 "SettlementNode12",
8 "SettlementNode14",
9 "SettlementNode15",
10 "SettlementNode20",
11 "SolNode1",
12 "SolNode2",
13 "SolNode4",
14 "SolNode6",
15 "SolNode10",
16 "SolNode17",
17 "SolNode21",
18 "SolNode22",
19 "SolNode23",
20 "SolNode25",
21 "SolNode38",
22 "SolNode43",
23 "SolNode48",
24 "SolNode49",
25 "SolNode51",
26 "SolNode53",
27 "SolNode56",
28 "SolNode57",
29 "SolNode61",
30 "SolNode62",
31 "SolNode65",
32 "SolNode66",
33 "SolNode72",
34 "SolNode73",
35 "SolNode74",
36 "SolNode76",
37 "SolNode78",
38 "SolNode81",
39 "SolNode84",
40 "SolNode88",
41 "SolNode97",
42 "SolNode100",
43 "SolNode101",
44 "SolNode102",
45 "SolNode104",
46 "SolNode107",
47 "SolNode109",
48 "SolNode118",
49 "SolNode121",
50 "SolNode123",
51 "SolNode125",
52 "SolNode126",
53 "SolNode127",
54 "SolNode128",
55 "SolNode203",
56 "SolNode205",
57 "SolNode209",
58 "SolNode210",
59 "SolNode211",
60 "SolNode212",
61 "SolNode214",
62 "SolNode216",
63 "SolNode217",
64 "SolNode220"
65 ],
66 "FC_GRINEER": [
67 "SolNode11",
68 "SolNode16",
69 "SolNode18",
70 "SolNode19",
71 "SolNode20",
72 "SolNode30",
73 "SolNode31",
74 "SolNode32",
75 "SolNode36",
76 "SolNode41",
77 "SolNode42",
78 "SolNode45",
79 "SolNode46",
80 "SolNode50",
81 "SolNode58",
82 "SolNode67",
83 "SolNode68",
84 "SolNode70",
85 "SolNode82",
86 "SolNode93",
87 "SolNode96",
88 "SolNode99",
89 "SolNode106",
90 "SolNode113",
91 "SolNode131",
92 "SolNode132",
93 "SolNode135",
94 "SolNode137",
95 "SolNode138",
96 "SolNode139",
97 "SolNode140",
98 "SolNode141",
99 "SolNode144",
100 "SolNode146",
101 "SolNode147",
102 "SolNode149",
103 "SolNode177",
104 "SolNode181",
105 "SolNode184",
106 "SolNode185",
107 "SolNode187",
108 "SolNode188",
109 "SolNode189",
110 "SolNode191",
111 "SolNode195",
112 "SolNode196"
113 ]
114 }
Added static/fixed_responses/worldState/invasionRewards.json +190 -0
@@ -0,0 +1,190 @@
1 {
2 "FC_GRINEER": {
3 "COMMON": [
4 {
5 "ItemType": "/Lotus/Types/Items/Research/ChemComponent",
6 "ItemCount": 3
7 }
8 ],
9 "UNCOMMON": [
10 {
11 "ItemType": "/Lotus/Types/Recipes/Weapons/KarakWraithBlueprint",
12 "ItemCount": 1
13 },
14 {
15 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/KarakWraithBarrel",
16 "ItemCount": 1
17 },
18 {
19 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/KarakWraithReceiver",
20 "ItemCount": 1
21 },
22 {
23 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/KarakWraithStock",
24 "ItemCount": 1
25 },
26 {
27 "ItemType": "/Lotus/Types/Recipes/Weapons/StrunWraithBlueprint",
28 "ItemCount": 1
29 },
30 {
31 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/StrunWraithBarrel",
32 "ItemCount": 1
33 },
34 {
35 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/StrunWraithReceiver",
36 "ItemCount": 1
37 },
38 {
39 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/StrunWraithStock",
40 "ItemCount": 1
41 },
42 {
43 "ItemType": "/Lotus/Types/Recipes/Weapons/LatronWraithBlueprint",
44 "ItemCount": 1
45 },
46 {
47 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/LatronWraithBarrel",
48 "ItemCount": 1
49 },
50 {
51 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/LatronWraithReceiver",
52 "ItemCount": 1
53 },
54 {
55 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/LatronWraithStock",
56 "ItemCount": 1
57 },
58 {
59 "ItemType": "/Lotus/Types/Recipes/Weapons/TwinVipersWraithBlueprint",
60 "ItemCount": 1
61 },
62 {
63 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/TwinVipersWraithBarrel",
64 "ItemCount": 1
65 },
66 {
67 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/TwinVipersWraithLink",
68 "ItemCount": 1
69 },
70 {
71 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/TwinVipersWraithReceiver",
72 "ItemCount": 1
73 },
74 {
75 "ItemType": "/Lotus/Types/Recipes/Weapons/GrineerCombatKnifeSortieBlueprint",
76 "ItemCount": 1
77 },
78 {
79 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/GrineerCombatKnifeHilt",
80 "ItemCount": 1
81 },
82 {
83 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/GrineerCombatKnifeBlade",
84 "ItemCount": 1
85 },
86 {
87 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/GrineerCombatKnifeHeatsink",
88 "ItemCount": 1
89 }
90 ],
91 "RARE": [
92 {
93 "ItemType": "/Lotus/Types/Recipes/Components/OrokinCatalystBlueprint",
94 "ItemCount": 1
95 },
96 {
97 "ItemType": "/Lotus/Types/Recipes/Components/OrokinReactorBlueprint",
98 "ItemCount": 1
99 },
100 {
101 "ItemType": "/Lotus/Types/Recipes/Components/FormaBlueprint",
102 "ItemCount": 1
103 },
104 {
105 "ItemType": "/Lotus/Types/Recipes/Components/UtilityUnlockerBlueprint",
106 "ItemCount": 1
107 }
108 ]
109 },
110 "FC_CORPUS": {
111 "COMMON": [
112 {
113 "ItemType": "/Lotus/Types/Items/Research/EnergyComponent",
114 "ItemCount": 3
115 }
116 ],
117 "UNCOMMON": [
118 {
119 "ItemType": "/Lotus/Types/Recipes/Weapons/DeraVandalBlueprint",
120 "ItemCount": 1
121 },
122 {
123 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/DeraVandalBarrel",
124 "ItemCount": 1
125 },
126 {
127 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/DeraVandalReceiver",
128 "ItemCount": 1
129 },
130 {
131 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/DeraVandalStock",
132 "ItemCount": 1
133 },
134 {
135 "ItemType": "/Lotus/Types/Recipes/Weapons/SnipetronVandalBlueprint",
136 "ItemCount": 1
137 },
138 {
139 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/SnipetronVandalStock",
140 "ItemCount": 1
141 },
142 {
143 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/SnipetronVandalReceiver",
144 "ItemCount": 1
145 },
146 {
147 "ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/SnipetronVandalBarrel",
148 "ItemCount": 1
149 }
150 ],
151 "RARE": [
152 {
153 "ItemType": "/Lotus/Types/Recipes/Components/OrokinCatalystBlueprint",
154 "ItemCount": 1
155 },
156 {
157 "ItemType": "/Lotus/Types/Recipes/Components/OrokinReactorBlueprint",
158 "ItemCount": 1
159 },
160 {
161 "ItemType": "/Lotus/Types/Recipes/Components/FormaBlueprint",
162 "ItemCount": 1
163 },
164 {
165 "ItemType": "/Lotus/Types/Recipes/Components/UtilityUnlockerBlueprint",
166 "ItemCount": 1
167 }
168 ]
169 },
170 "FC_INFESTATION": {
171 "COMMON": [
172 {
173 "ItemType": "/Lotus/Types/Items/Research/BioComponent",
174 "ItemCount": 1
175 }
176 ],
177 "UNCOMMON": [
178 {
179 "ItemType": "/Lotus/Types/Items/Research/BioComponent",
180 "ItemCount": 2
181 }
182 ],
183 "RARE": [
184 {
185 "ItemType": "/Lotus/Types/Items/MiscItems/InfestedAladCoordinate",
186 "ItemCount": 1
187 }
188 ]
189 }
190 }
Modified static/fixed_responses/worldState/worldState.json +0 -40
@@ -117,46 +117,6 @@
117 117 ]
118 118 }
119 119 },
120 "Invasions": [
121 {
122 "_id": {
123 "$oid": "67c8ec8b3d0d86b236c1c18f"
124 },
125 "Faction": "FC_INFESTATION",
126 "DefenderFaction": "FC_CORPUS",
127 "Node": "SolNode53",
128 "Count": -28558,
129 "Goal": 30000,
130 "LocTag": "/Lotus/Language/Menu/InfestedInvasionBoss",
131 "Completed": false,
132 "ChainID": {
133 "$oid": "67c8b6a2bde0dfd0f7c1c18d"
134 },
135 "AttackerReward": [],
136 "AttackerMissionInfo": {
137 "seed": 488863,
138 "faction": "FC_CORPUS"
139 },
140 "DefenderReward": {
141 "countedItems": [
142 {
143 "ItemType": "/Lotus/Types/Items/Research/EnergyComponent",
144 "ItemCount": 3
145 }
146 ]
147 },
148 "DefenderMissionInfo": {
149 "seed": 127653,
150 "faction": "FC_INFESTATION",
151 "missionReward": []
152 },
153 "Activation": {
154 "$date": {
155 "$numberLong": "1741221003031"
156 }
157 }
158 }
159 ],
160 120 "SyndicateMissions": [
161 121 {
162 122 "_id": { "$oid": "663a4fc5ba6f84724fa4804c" },