Compare commits

..

12 Commits
main ... main

Author SHA1 Message Date
005690daa4 fix: handle "skip prologue" to visit orbiter in U14 (#1999)
Reviewed-on: OpenWF/SpaceNinjaServer#1999
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-06 07:37:30 -07:00
5fefd189af fix: login failure on U15 (#1997)
Reviewed-on: OpenWF/SpaceNinjaServer#1997
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-06 06:05:12 -07:00
f6cbc02c47 fix: ignore client providing not-an-id in recipe ids array (#1990)
Closes #1989

Reviewed-on: OpenWF/SpaceNinjaServer#1990
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-06 02:29:23 -07:00
da6d75c748 fix: don't give sortie assassination rewards if mission type differs (#1992)
Closes #1987

Reviewed-on: OpenWF/SpaceNinjaServer#1992
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-06 02:29:11 -07:00
460deed3ed fix: login failure on U16 (#1991)
Reviewed-on: OpenWF/SpaceNinjaServer#1991
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-06 02:29:03 -07:00
4e57bcd1ae fix: login failure on U17 (#1986)
Reviewed-on: OpenWF/SpaceNinjaServer#1986
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-05 18:09:03 -07:00
e2fe406017 fix: always use rotation A for Profit-Taker bounty rewards (#1981)
Closes #1964

Reviewed-on: OpenWF/SpaceNinjaServer#1981
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-05 18:03:53 -07:00
c53dc2fd02 fix: remove non-existent sortie modifiers (#1980)
Closes #1976

Reviewed-on: OpenWF/SpaceNinjaServer#1980
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-05 18:03:44 -07:00
cfa3586f64 fix: don't provide syndicate missions in advance (#1979)
Closes #1975

Reviewed-on: OpenWF/SpaceNinjaServer#1979
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-05 18:03:36 -07:00
238af294fe fix: login failure on U18 (#1983)
Reviewed-on: OpenWF/SpaceNinjaServer#1983
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-05 04:57:44 -07:00
1914fd8f10 fix: venus bounties having wrong reward tables (#1978)
Due to missing / on six of the reward tables.

Fixes #1977.

Reviewed-on: OpenWF/SpaceNinjaServer#1978
Co-authored-by: VampireKitten <dynamightkobold@gmail.com>
Co-committed-by: VampireKitten <dynamightkobold@gmail.com>
2025-05-04 17:31:36 -07:00
ec4af075b5 fix: login failure on U21 (#1974)
Reviewed-on: OpenWF/SpaceNinjaServer#1974
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-04 17:30:48 -07:00
11 changed files with 143 additions and 46 deletions

View File

@ -21,6 +21,13 @@ app.use((req, _res, next) => {
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") { if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
req.headers["content-encoding"] = undefined; req.headers["content-encoding"] = undefined;
} }
// U18 uses application/x-www-form-urlencoded even tho the data is JSON which Express doesn't like.
// U17 sets no Content-Type at all, which Express also doesn't like.
if (!req.headers["content-type"] || req.headers["content-type"] == "application/x-www-form-urlencoded") {
req.headers["content-type"] = "application/octet-stream";
}
next(); next();
}); });

View File

@ -25,6 +25,9 @@ import { logger } from "@/src/utils/logger";
import { catBreadHash } from "@/src/helpers/stringHelpers"; import { catBreadHash } from "@/src/helpers/stringHelpers";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers"; import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
import { version_compare } from "@/src/services/worldStateService";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
export const inventoryController: RequestHandler = async (request, response) => { export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request); const account = await getAccountForRequest(request);
@ -312,6 +315,12 @@ export const getInventoryResponse = async (
inventoryResponse.Nemesis = undefined; inventoryResponse.Nemesis = undefined;
} }
if (buildLabel && version_compare(buildLabel, "2018.02.22.14.34") < 0) {
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
inventoryResponse.Ship = personalRooms.Ship;
}
return inventoryResponse; return inventoryResponse;
}; };

View File

@ -41,7 +41,7 @@ export const loginController: RequestHandler = async (request, response) => {
email: loginRequest.email, email: loginRequest.email,
password: loginRequest.password, password: loginRequest.password,
DisplayName: name, DisplayName: name,
CountryCode: loginRequest.lang.toUpperCase(), CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType, ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
CrossPlatformAllowed: true, CrossPlatformAllowed: true,
ForceLogoutVersion: 0, ForceLogoutVersion: 0,
@ -82,13 +82,16 @@ export const loginController: RequestHandler = async (request, response) => {
} }
} else { } else {
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) { if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
response.status(400).json({ error: "nonce still set" }); // U17 seems to handle "nonce still set" like a login failure.
if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
response.status(400).send({ error: "nonce still set" });
return; return;
} }
}
account.ClientType = loginRequest.ClientType; account.ClientType = loginRequest.ClientType;
account.Nonce = nonce; account.Nonce = nonce;
account.CountryCode = loginRequest.lang.toUpperCase(); account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
account.BuildLabel = buildLabel; account.BuildLabel = buildLabel;
} }
await account.save(); await account.save();
@ -104,10 +107,15 @@ const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, b
AmazonAuthToken: account.AmazonAuthToken, AmazonAuthToken: account.AmazonAuthToken,
AmazonRefreshToken: account.AmazonRefreshToken, AmazonRefreshToken: account.AmazonRefreshToken,
Nonce: account.Nonce, Nonce: account.Nonce,
IRC: config.myIrcAddresses ?? [myAddress],
NRS: config.NRS,
BuildLabel: buildLabel BuildLabel: buildLabel
}; };
if (version_compare(buildLabel, "2015.02.13.10.41") >= 0) {
resp.NRS = config.NRS;
}
if (version_compare(buildLabel, "2015.05.14.16.29") >= 0) {
// U17 and up
resp.IRC = config.myIrcAddresses ?? [myAddress];
}
if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) { if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
// U24 and up // U24 and up
resp.ConsentNeeded = account.ConsentNeeded; resp.ConsentNeeded = account.ConsentNeeded;

View File

@ -0,0 +1,25 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
// Basic shim handling action=sync to login on U21
export const questControlController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const quests: IQuestState[] = [];
for (const quest of inventory.QuestKeys) {
quests.push({
quest: quest.ItemType,
state: 3 // COMPLETE
});
}
res.json({
QuestState: quests
});
};
interface IQuestState {
quest: string;
state: number;
task?: string;
}

View File

@ -41,7 +41,7 @@ export const startRecipeController: RequestHandler = async (req, res) => {
]; ];
for (let i = 0; i != recipe.ingredients.length; ++i) { for (let i = 0; i != recipe.ingredients.length; ++i) {
if (startRecipeRequest.Ids[i]) { if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory; const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
if (category != "LongGuns" && category != "Pistols" && category != "Melee") { if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
throw new Error(`unexpected equipment ingredient type: ${category}`); throw new Error(`unexpected equipment ingredient type: ${category}`);

View File

@ -97,6 +97,7 @@ import { playerSkillsController } from "@/src/controllers/api/playerSkillsContro
import { postGuildAdvertisementController } from "@/src/controllers/api/postGuildAdvertisementController"; import { postGuildAdvertisementController } from "@/src/controllers/api/postGuildAdvertisementController";
import { projectionManagerController } from "@/src/controllers/api/projectionManagerController"; import { projectionManagerController } from "@/src/controllers/api/projectionManagerController";
import { purchaseController } from "@/src/controllers/api/purchaseController"; import { purchaseController } from "@/src/controllers/api/purchaseController";
import { questControlController } from "@/src/controllers/api/questControlController";
import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController"; import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCodeController"; import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCodeController";
import { releasePetController } from "@/src/controllers/api/releasePetController"; import { releasePetController } from "@/src/controllers/api/releasePetController";
@ -185,6 +186,7 @@ apiRouter.get("/getVendorInfo.php", getVendorInfoController);
apiRouter.get("/hub", hubController); apiRouter.get("/hub", hubController);
apiRouter.get("/hubInstances", hubInstancesController); apiRouter.get("/hubInstances", hubInstancesController);
apiRouter.get("/inbox.php", inboxController); apiRouter.get("/inbox.php", inboxController);
apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
apiRouter.get("/inventory.php", inventoryController); apiRouter.get("/inventory.php", inventoryController);
apiRouter.get("/loginRewards.php", loginRewardsController); apiRouter.get("/loginRewards.php", loginRewardsController);
apiRouter.get("/logout.php", logoutController); apiRouter.get("/logout.php", logoutController);
@ -192,6 +194,7 @@ apiRouter.get("/marketRecommendations.php", marketRecommendationsController);
apiRouter.get("/marketSearchRecommendations.php", marketRecommendationsController); apiRouter.get("/marketSearchRecommendations.php", marketRecommendationsController);
apiRouter.get("/modularWeaponSale.php", modularWeaponSaleController); apiRouter.get("/modularWeaponSale.php", modularWeaponSaleController);
apiRouter.get("/playedParkourTutorial.php", playedParkourTutorialController); apiRouter.get("/playedParkourTutorial.php", playedParkourTutorialController);
apiRouter.get("/questControl.php", questControlController);
apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController); apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController);
apiRouter.get("/removeFromAlliance.php", removeFromAllianceController); apiRouter.get("/removeFromAlliance.php", removeFromAllianceController);
apiRouter.get("/setActiveQuest.php", setActiveQuestController); apiRouter.get("/setActiveQuest.php", setActiveQuestController);
@ -222,6 +225,7 @@ apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController); apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController); apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?)
apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController); apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
apiRouter.post("/confirmGuildInvitation.php", confirmGuildInvitationPostController); apiRouter.post("/confirmGuildInvitation.php", confirmGuildInvitationPostController);
apiRouter.post("/contributeGuildClass.php", contributeGuildClassController); apiRouter.post("/contributeGuildClass.php", contributeGuildClassController);
@ -276,6 +280,7 @@ apiRouter.post("/playerSkills.php", playerSkillsController);
apiRouter.post("/postGuildAdvertisement.php", postGuildAdvertisementController); apiRouter.post("/postGuildAdvertisement.php", postGuildAdvertisementController);
apiRouter.post("/projectionManager.php", projectionManagerController); apiRouter.post("/projectionManager.php", projectionManagerController);
apiRouter.post("/purchase.php", purchaseController); apiRouter.post("/purchase.php", purchaseController);
apiRouter.post("/questControl.php", questControlController); // U17
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController); apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
apiRouter.post("/releasePet.php", releasePetController); apiRouter.post("/releasePet.php", releasePetController);
apiRouter.post("/removeFromGuild.php", removeFromGuildController); apiRouter.post("/removeFromGuild.php", removeFromGuildController);

View File

@ -58,7 +58,7 @@ import conservationAnimals from "@/static/fixed_responses/conservationAnimals.js
import { getInfNodes, getWeaponsForManifest, sendCodaFinishedMessage } from "@/src/helpers/nemesisHelpers"; import { getInfNodes, getWeaponsForManifest, sendCodaFinishedMessage } from "@/src/helpers/nemesisHelpers";
import { Loadout } from "../models/inventoryModels/loadoutModel"; import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService"; import { getLiteSortie, getSortie, getWorldState, idToDay, idToWeek } from "./worldStateService";
import { config } from "./configService"; import { config } from "./configService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
@ -1357,9 +1357,24 @@ function getRandomMissionDrops(
// Invasion assassination has Phorid has the boss who should drop Nyx parts // Invasion assassination has Phorid has the boss who should drop Nyx parts
// TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic // TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic
rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"]; rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"];
} else if (RewardInfo.sortieId && region.missionIndex != 0) { } else if (RewardInfo.sortieId) {
// Sortie mission types differ from the underlying node and hence also don't give rewards from the underlying nodes. Assassinations are an exception to this. // Sortie mission types differ from the underlying node and hence also don't give rewards from the underlying nodes. Assassinations are an exception to this.
if (region.missionIndex == 0) {
const arr = RewardInfo.sortieId.split("_");
let sortieId = arr[1];
if (sortieId == "Lite") {
sortieId = arr[2];
}
const sortie = getSortie(idToDay(sortieId));
const mission = sortie.Variants.find(x => x.node == arr[0])!;
if (mission.missionType == "MT_ASSASSINATION") {
rewardManifests = region.rewardManifests;
} else {
rewardManifests = []; rewardManifests = [];
}
} else {
rewardManifests = [];
}
} else { } else {
rewardManifests = region.rewardManifests; rewardManifests = region.rewardManifests;
} }
@ -1452,7 +1467,7 @@ function getRandomMissionDrops(
if (job.xpAmounts.length > 1) { if (job.xpAmounts.length > 1) {
rotations = [RewardInfo.JobStage! % (job.xpAmounts.length - 1)]; rotations = [RewardInfo.JobStage! % (job.xpAmounts.length - 1)];
} else { } else {
rotations = [RewardInfo.JobStage!]; rotations = [0];
} }
if ( if (
RewardInfo.Q && RewardInfo.Q &&

View File

@ -12,6 +12,7 @@ import {
ICalendarSeason, ICalendarSeason,
ILiteSortie, ILiteSortie,
ISeasonChallenge, ISeasonChallenge,
ISortie,
ISortieMission, ISortieMission,
IWorldState IWorldState
} from "../types/worldStateTypes"; } from "../types/worldStateTypes";
@ -215,16 +216,7 @@ const pushSyndicateMissions = (
}); });
}; };
const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => { export const getSortie = (day: number): ISortie => {
const dayStart = getSortieTime(day);
if (!isBeforeNextExpectedWorldStateRefresh(dayStart)) {
return;
}
const dayEnd = getSortieTime(day + 1);
if (Date.now() >= dayEnd) {
return;
}
const seed = new CRng(day).randomInt(0, 0xffff); const seed = new CRng(day).randomInt(0, 0xffff);
const rng = new CRng(seed); const rng = new CRng(seed);
@ -291,11 +283,9 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
"SORTIE_MODIFIER_VIRAL", "SORTIE_MODIFIER_VIRAL",
"SORTIE_MODIFIER_ELECTRICITY", "SORTIE_MODIFIER_ELECTRICITY",
"SORTIE_MODIFIER_RADIATION", "SORTIE_MODIFIER_RADIATION",
"SORTIE_MODIFIER_GAS",
"SORTIE_MODIFIER_FIRE", "SORTIE_MODIFIER_FIRE",
"SORTIE_MODIFIER_EXPLOSION", "SORTIE_MODIFIER_EXPLOSION",
"SORTIE_MODIFIER_FREEZE", "SORTIE_MODIFIER_FREEZE",
"SORTIE_MODIFIER_TOXIN",
"SORTIE_MODIFIER_POISON", "SORTIE_MODIFIER_POISON",
"SORTIE_MODIFIER_SECONDARY_ONLY", "SORTIE_MODIFIER_SECONDARY_ONLY",
"SORTIE_MODIFIER_SHOTGUN_ONLY", "SORTIE_MODIFIER_SHOTGUN_ONLY",
@ -373,7 +363,9 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
missionTypes.add(missionType); missionTypes.add(missionType);
} }
worldState.Sorties.push({ const dayStart = getSortieTime(day);
const dayEnd = getSortieTime(day + 1);
return {
_id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "d4d932c97c0a3acd" }, _id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "d4d932c97c0a3acd" },
Activation: { $date: { $numberLong: dayStart.toString() } }, Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } }, Expiry: { $date: { $numberLong: dayEnd.toString() } },
@ -381,14 +373,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
Seed: seed, Seed: seed,
Boss: boss, Boss: boss,
Variants: selectedNodes Variants: selectedNodes
}); };
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48049", "ArbitersSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804a", "CephalonSudaSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804e", "NewLokaSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48050", "PerrinSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4805e", "RedVeilSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48061", "SteelMeridianSyndicate");
}; };
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x => const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
@ -742,6 +727,10 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// Omit void fissures for versions prior to Dante Unbound to avoid script errors. // Omit void fissures for versions prior to Dante Unbound to avoid script errors.
if (buildLabel && version_compare(buildLabel, "2024.03.24.20.00") < 0) { if (buildLabel && version_compare(buildLabel, "2024.03.24.20.00") < 0) {
worldState.ActiveMissions = []; worldState.ActiveMissions = [];
if (version_compare(buildLabel, "2017.10.12.17.04") < 0) {
// Old versions seem to really get hung up on not being able to load these.
worldState.PVPChallengeInstances = [];
}
} }
if (config.worldState?.starDays) { if (config.worldState?.starDays) {
@ -904,7 +893,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Jobs: [ Jobs: [
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`,
masteryReq: 0, masteryReq: 0,
minEnemyLevel: 5, minEnemyLevel: 5,
maxEnemyLevel: 15, maxEnemyLevel: 15,
@ -912,7 +901,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`,
masteryReq: 1, masteryReq: 1,
minEnemyLevel: 10, minEnemyLevel: 10,
maxEnemyLevel: 30, maxEnemyLevel: 30,
@ -920,7 +909,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`,
masteryReq: 2, masteryReq: 2,
minEnemyLevel: 20, minEnemyLevel: 20,
maxEnemyLevel: 40, maxEnemyLevel: 40,
@ -928,7 +917,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`,
masteryReq: 3, masteryReq: 3,
minEnemyLevel: 30, minEnemyLevel: 30,
maxEnemyLevel: 50, maxEnemyLevel: 50,
@ -936,7 +925,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
masteryReq: 5, masteryReq: 5,
minEnemyLevel: 40, minEnemyLevel: 40,
maxEnemyLevel: 60, maxEnemyLevel: 60,
@ -944,7 +933,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`, rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
masteryReq: 10, masteryReq: 10,
minEnemyLevel: 100, minEnemyLevel: 100,
maxEnemyLevel: 100, maxEnemyLevel: 100,
@ -1093,8 +1082,25 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST) // Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
pushSortieIfRelevant(worldState, day - 1); {
pushSortieIfRelevant(worldState, day); const rollover = getSortieTime(day);
if (Date.now() < rollover) {
worldState.Sorties.push(getSortie(day - 1));
}
if (isBeforeNextExpectedWorldStateRefresh(rollover)) {
worldState.Sorties.push(getSortie(day));
}
// The client does not seem to respect activation for classic syndicate missions, so only pushing current ones.
const rng = new CRng(Date.now() >= rollover ? day : day - 1);
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48049", "ArbitersSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804a", "CephalonSudaSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804e", "NewLokaSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48050", "PerrinSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4805e", "RedVeilSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48061", "SteelMeridianSyndicate");
}
// Archon Hunt cycling every week // Archon Hunt cycling every week
worldState.LiteSorties.push(getLiteSortie(week)); worldState.LiteSorties.push(getLiteSortie(week));
@ -1172,8 +1178,12 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
return worldState; return worldState;
}; };
export const idToDay = (id: string): number => {
return Math.trunc((parseInt(id.substring(0, 8), 16) * 1000 - EPOCH) / 86400_000);
};
export const idToWeek = (id: string): number => { export const idToWeek = (id: string): number => {
return (parseInt(id.substring(0, 8), 16) * 1000 - EPOCH) / 604800000; return Math.trunc((parseInt(id.substring(0, 8), 16) * 1000 - EPOCH) / 604800_000);
}; };
export const getLiteSortie = (week: number): ILiteSortie => { export const getLiteSortie = (week: number): ILiteSortie => {

View File

@ -11,6 +11,7 @@ import {
IOperatorConfigDatabase IOperatorConfigDatabase
} from "@/src/types/inventoryTypes/commonInventoryTypes"; } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { IFingerprintStat, RivenFingerprint } from "@/src/helpers/rivenHelper"; import { IFingerprintStat, RivenFingerprint } from "@/src/helpers/rivenHelper";
import { IOrbiter } from "../personalRoomsTypes";
export type InventoryDatabaseEquipment = { export type InventoryDatabaseEquipment = {
[_ in TEquipmentKey]: IEquipmentDatabase[]; [_ in TEquipmentKey]: IEquipmentDatabase[];
@ -373,6 +374,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
BrandedSuits?: IOid[]; BrandedSuits?: IOid[];
LockedWeaponGroup?: ILockedWeaponGroupClient; LockedWeaponGroup?: ILockedWeaponGroupClient;
HubNpcCustomizations?: IHubNpcCustomization[]; HubNpcCustomizations?: IHubNpcCustomization[];
Ship?: IOrbiter; // U22 and below, response only
} }
export interface IAffiliation { export interface IAffiliation {

View File

@ -35,11 +35,11 @@ export interface ILoginRequest {
email: string; email: string;
password: string; password: string;
time: number; time: number;
s: string; s?: string;
lang: string; lang?: string;
date: number; date: number;
ClientType: string; ClientType?: string;
PS: string; PS?: string;
kick?: boolean; kick?: boolean;
} }
@ -51,7 +51,7 @@ export interface ILoginResponse extends IAccountAndLoginResponseCommons {
platformCDNs?: string[]; platformCDNs?: string[];
NRS?: string[]; NRS?: string[];
DTLS?: number; DTLS?: number;
IRC: string[]; IRC?: string[];
HUB?: string; HUB?: string;
} }

View File

@ -12,6 +12,7 @@ export interface IWorldState {
GlobalUpgrades: IGlobalUpgrade[]; GlobalUpgrades: IGlobalUpgrade[];
ActiveMissions: IFissure[]; ActiveMissions: IFissure[];
NodeOverrides: INodeOverride[]; NodeOverrides: INodeOverride[];
PVPChallengeInstances: IPVPChallengeInstance[];
EndlessXpChoices: IEndlessXpChoice[]; EndlessXpChoices: IEndlessXpChoice[];
SeasonInfo: { SeasonInfo: {
Activation: IMongoDate; Activation: IMongoDate;
@ -130,6 +131,21 @@ export interface ILiteSortie {
}[]; }[];
} }
export interface IPVPChallengeInstance {
_id: IOid;
challengeTypeRefID: string;
startDate: IMongoDate;
endDate: IMongoDate;
params: {
n: string; // "ScriptParamValue";
v: number;
}[];
isGenerated: boolean;
PVPMode: string;
subChallenges: IOid[];
Category: string; // "PVPChallengeTypeCategory_WEEKLY" | "PVPChallengeTypeCategory_WEEKLY_ROOT" | "PVPChallengeTypeCategory_DAILY";
}
export interface IEndlessXpChoice { export interface IEndlessXpChoice {
Category: string; Category: string;
Choices: string[]; Choices: string[];