Compare commits

..

13 Commits
main ... main

Author SHA1 Message Date
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
355a70d366 fix: login failure on u23 and below (#1972)
Reviewed-on: OpenWF/SpaceNinjaServer#1972
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-03 17:48:01 -07:00
cad82cf7de fix: refuse to add horse if one is already owned (#1973)
Reviewed-on: OpenWF/SpaceNinjaServer#1973
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-03 17:24:47 -07:00
ff3a9b382c chore: handle profile viewing data request from old versions (#1970)
Reviewed-on: OpenWF/SpaceNinjaServer#1970
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-03 17:24:40 -07:00
18fbd51efb fix: omit nightwave challenges for versions before 38.0.8 (#1969)
Reviewed-on: OpenWF/SpaceNinjaServer#1969
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-03 17:24:31 -07:00
f7b4b4f089 fix: dojo time fields for old versions (#1968)
Tested this in U27 & U38.5

Reviewed-on: OpenWF/SpaceNinjaServer#1968
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-03 17:24:20 -07:00
83743831c9 fix: don't divide by 0 (#1966)
Closes #1964

Reviewed-on: OpenWF/SpaceNinjaServer#1966
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-03 17:24:08 -07:00
13 changed files with 198 additions and 80 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

@ -82,9 +82,12 @@ 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;
@ -103,13 +106,16 @@ const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, b
CountryCode: account.CountryCode, CountryCode: account.CountryCode,
AmazonAuthToken: account.AmazonAuthToken, AmazonAuthToken: account.AmazonAuthToken,
AmazonRefreshToken: account.AmazonRefreshToken, AmazonRefreshToken: account.AmazonRefreshToken,
ConsentNeeded: account.ConsentNeeded,
TrackedSettings: account.TrackedSettings,
Nonce: account.Nonce, Nonce: account.Nonce,
IRC: config.myIrcAddresses ?? [myAddress], IRC: config.myIrcAddresses ?? [myAddress],
NRS: config.NRS, NRS: config.NRS,
BuildLabel: buildLabel BuildLabel: buildLabel
}; };
if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
// U24 and up
resp.ConsentNeeded = account.ConsentNeeded;
resp.TrackedSettings = account.TrackedSettings;
}
if (version_compare(buildLabel, "2019.08.29.20.01") >= 0) { if (version_compare(buildLabel, "2019.08.29.20.01") >= 0) {
// U25.7 and up // U25.7 and up
resp.ForceLogoutVersion = account.ForceLogoutVersion; resp.ForceLogoutVersion = account.ForceLogoutVersion;

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

@ -18,16 +18,15 @@ import {
ITypeXPItem ITypeXPItem
} from "@/src/types/inventoryTypes/inventoryTypes"; } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { catBreadHash } from "@/src/helpers/stringHelpers"; import { catBreadHash, getJSONfromString } from "@/src/helpers/stringHelpers";
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus"; import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
import { IStatsClient } from "@/src/types/statTypes"; import { IStatsClient } from "@/src/types/statTypes";
import { toStoreItem } from "@/src/services/itemDataService"; import { toStoreItem } from "@/src/services/itemDataService";
import { FlattenMaps } from "mongoose";
export const getProfileViewingDataController: RequestHandler = async (req, res) => { const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
if (req.query.playerId) { const account = await Account.findById(playerId, "DisplayName");
const account = await Account.findById(req.query.playerId as string, "DisplayName");
if (!account) { if (!account) {
res.status(409).send("Could not find requested account");
return; return;
} }
const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!; const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
@ -67,13 +66,23 @@ export const getProfileViewingDataController: RequestHandler = async (req, res)
delete stats.__v; delete stats.__v;
delete stats.accountOwnerId; delete stats.accountOwnerId;
res.json({ return {
Results: [result], Results: [result],
TechProjects: [], TechProjects: [],
XpComponents: [], XpComponents: [],
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
Stats: stats Stats: stats
}); };
};
export const getProfileViewingDataGetController: RequestHandler = async (req, res) => {
if (req.query.playerId) {
const data = await getProfileViewingDataByPlayerIdImpl(req.query.playerId as string);
if (data) {
res.json(data);
} else {
res.status(409).send("Could not find requested account");
}
} else if (req.query.guildId) { } else if (req.query.guildId) {
const guild = await Guild.findById(req.query.guildId, "Name Tier XP Class Emblem TechProjects ClaimedXP"); const guild = await Guild.findById(req.query.guildId, "Name Tier XP Class Emblem TechProjects ClaimedXP");
if (!guild) { if (!guild) {
@ -170,6 +179,28 @@ export const getProfileViewingDataController: RequestHandler = async (req, res)
} }
}; };
// For old versions, this was an authenticated POST request.
interface IGetProfileViewingDataRequest {
AccountId: string;
}
export const getProfileViewingDataPostController: RequestHandler = async (req, res) => {
const payload = getJSONfromString<IGetProfileViewingDataRequest>(String(req.body));
const data = await getProfileViewingDataByPlayerIdImpl(payload.AccountId);
if (data) {
res.json(data);
} else {
res.status(409).send("Could not find requested account");
}
};
interface IProfileViewingData {
Results: IPlayerProfileViewingDataResult[];
TechProjects: [];
XpComponents: [];
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
Stats: FlattenMaps<Partial<TStatsDatabaseDocument>>;
}
interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> { interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
AccountId: IOid; AccountId: IOid;
DisplayName: string; DisplayName: string;

View File

@ -59,6 +59,7 @@ import { getGuildDojoController } from "@/src/controllers/api/getGuildDojoContro
import { getGuildLogController } from "@/src/controllers/api/getGuildLogController"; import { getGuildLogController } from "@/src/controllers/api/getGuildLogController";
import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController"; import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController";
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController"; import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
import { getProfileViewingDataPostController } from "@/src/controllers/dynamic/getProfileViewingDataController";
import { getShipController } from "@/src/controllers/api/getShipController"; import { getShipController } from "@/src/controllers/api/getShipController";
import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController"; import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController";
import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController"; import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController";
@ -96,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";
@ -184,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);
@ -191,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);
@ -247,6 +251,7 @@ apiRouter.post("/genericUpdate.php", genericUpdateController);
apiRouter.post("/getAlliance.php", getAllianceController); apiRouter.post("/getAlliance.php", getAllianceController);
apiRouter.post("/getFriends.php", getFriendsController); apiRouter.post("/getFriends.php", getFriendsController);
apiRouter.post("/getGuildDojo.php", getGuildDojoController); apiRouter.post("/getGuildDojo.php", getGuildDojoController);
apiRouter.post("/getProfileViewingData.php", getProfileViewingDataPostController);
apiRouter.post("/getVoidProjectionRewards.php", getVoidProjectionRewardsController); apiRouter.post("/getVoidProjectionRewards.php", getVoidProjectionRewardsController);
apiRouter.post("/gifting.php", giftingController); apiRouter.post("/gifting.php", giftingController);
apiRouter.post("/gildWeapon.php", gildWeaponController); apiRouter.post("/gildWeapon.php", gildWeaponController);
@ -274,6 +279,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

@ -1,14 +1,14 @@
import express from "express"; import express from "express";
import { aggregateSessionsController } from "@/src/controllers/dynamic/aggregateSessionsController"; import { aggregateSessionsController } from "@/src/controllers/dynamic/aggregateSessionsController";
import { getGuildAdsController } from "@/src/controllers/dynamic/getGuildAdsController"; import { getGuildAdsController } from "@/src/controllers/dynamic/getGuildAdsController";
import { getProfileViewingDataController } from "@/src/controllers/dynamic/getProfileViewingDataController"; import { getProfileViewingDataGetController } from "@/src/controllers/dynamic/getProfileViewingDataController";
import { worldStateController } from "@/src/controllers/dynamic/worldStateController"; import { worldStateController } from "@/src/controllers/dynamic/worldStateController";
const dynamicController = express.Router(); const dynamicController = express.Router();
dynamicController.get("/aggregateSessions.php", aggregateSessionsController); dynamicController.get("/aggregateSessions.php", aggregateSessionsController);
dynamicController.get("/getGuildAds.php", getGuildAdsController); dynamicController.get("/getGuildAds.php", getGuildAdsController);
dynamicController.get("/getProfileViewingData.php", getProfileViewingDataController); dynamicController.get("/getProfileViewingData.php", getProfileViewingDataGetController);
dynamicController.get("/worldState.php", worldStateController); dynamicController.get("/worldState.php", worldStateController);
export { dynamicController }; export { dynamicController };

View File

@ -175,6 +175,9 @@ export const getDojoClient = async (
} }
if (dojoComponent.CompletionTime) { if (dojoComponent.CompletionTime) {
clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime); clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime);
clientComponent.TimeRemaining = Math.trunc(
(dojoComponent.CompletionTime.getTime() - Date.now()) / 1000
);
if (dojoComponent.CompletionLogPending && Date.now() >= dojoComponent.CompletionTime.getTime()) { if (dojoComponent.CompletionLogPending && Date.now() >= dojoComponent.CompletionTime.getTime()) {
const entry = guild.RoomChanges?.find(x => x.componentId.equals(dojoComponent._id)); const entry = guild.RoomChanges?.find(x => x.componentId.equals(dojoComponent._id));
if (entry) { if (entry) {
@ -210,6 +213,9 @@ export const getDojoClient = async (
continue; continue;
} }
clientComponent.DestructionTime = toMongoDate(dojoComponent.DestructionTime); clientComponent.DestructionTime = toMongoDate(dojoComponent.DestructionTime);
clientComponent.DestructionTimeRemaining = Math.trunc(
(dojoComponent.DestructionTime.getTime() - Date.now()) / 1000
);
} }
} else { } else {
clientComponent.RegularCredits = dojoComponent.RegularCredits; clientComponent.RegularCredits = dojoComponent.RegularCredits;
@ -245,6 +251,7 @@ export const getDojoClient = async (
continue; continue;
} }
clientDeco.CompletionTime = toMongoDate(deco.CompletionTime); clientDeco.CompletionTime = toMongoDate(deco.CompletionTime);
clientDeco.TimeRemaining = Math.trunc((deco.CompletionTime.getTime() - Date.now()) / 1000);
} else { } else {
clientDeco.RegularCredits = deco.RegularCredits; clientDeco.RegularCredits = deco.RegularCredits;
clientDeco.MiscItems = deco.MiscItems; clientDeco.MiscItems = deco.MiscItems;

View File

@ -789,6 +789,10 @@ export const addItem = async (
break; break;
} }
case "NeutralCreatures": { case "NeutralCreatures": {
if (inventory.Horses.length != 0) {
logger.warn("refusing to add Horse because account already has one");
return {};
}
const horseIndex = inventory.Horses.push({ ItemType: typeName }); const horseIndex = inventory.Horses.push({ ItemType: typeName });
return { return {
Horses: [inventory.Horses[horseIndex - 1].toJSON<IEquipmentClient>()] Horses: [inventory.Horses[horseIndex - 1].toJSON<IEquipmentClient>()]

View File

@ -1449,7 +1449,11 @@ function getRandomMissionDrops(
} }
} }
rewardManifests = [job.rewards]; rewardManifests = [job.rewards];
if (job.xpAmounts.length > 1) {
rotations = [RewardInfo.JobStage! % (job.xpAmounts.length - 1)]; rotations = [RewardInfo.JobStage! % (job.xpAmounts.length - 1)];
} else {
rotations = [0];
}
if ( if (
RewardInfo.Q && RewardInfo.Q &&
(RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) && (RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) &&

View File

@ -193,6 +193,12 @@ const pushSyndicateMissions = (
idSuffix: string, idSuffix: string,
syndicateTag: string syndicateTag: string
): void => { ): void => {
const dayStart = getSortieTime(day);
if (Date.now() >= dayStart) {
return; // The client does not seem to respect activation.
}
const dayEnd = getSortieTime(day + 1);
const nodeOptions: string[] = [...syndicateMissions]; const nodeOptions: string[] = [...syndicateMissions];
const rng = new CRng(seed); const rng = new CRng(seed);
@ -203,8 +209,6 @@ const pushSyndicateMissions = (
nodeOptions.splice(index, 1); nodeOptions.splice(index, 1);
} }
const dayStart = getSortieTime(day);
const dayEnd = getSortieTime(day + 1);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + idSuffix }, _id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + idSuffix },
Activation: { $date: { $numberLong: dayStart.toString() } }, Activation: { $date: { $numberLong: dayStart.toString() } },
@ -291,11 +295,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",
@ -742,6 +744,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) {
@ -762,6 +768,8 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
// Nightwave Challenges // Nightwave Challenges
// Current nightwave season was introduced in 38.0.8 so omitting challenges before that to avoid UI bugs and even crashes on really old versions.
if (!buildLabel || version_compare(buildLabel, "2025.02.05.11.19") >= 0) {
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 2)); worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 2));
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 1)); worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 1));
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 0)); worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 0));
@ -772,6 +780,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) { if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
pushWeeklyActs(worldState, week + 1); pushWeeklyActs(worldState, week + 1);
} }
}
// Elite Sanctuary Onslaught cycling every week // Elite Sanctuary Onslaught cycling every week
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = new CRng(week).randomInt(0, 0xffff); worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = new CRng(week).randomInt(0, 0xffff);
@ -901,7 +910,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,
@ -909,7 +918,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,
@ -917,7 +926,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,
@ -925,7 +934,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,
@ -933,7 +942,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,
@ -941,7 +950,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,

View File

@ -173,9 +173,11 @@ export interface IDojoComponentClient {
Message?: string; Message?: string;
RegularCredits?: number; // "Collecting Materials" state: Number of credits that were donated. RegularCredits?: number; // "Collecting Materials" state: Number of credits that were donated.
MiscItems?: IMiscItem[]; // "Collecting Materials" state: Resources that were donated. MiscItems?: IMiscItem[]; // "Collecting Materials" state: Resources that were donated.
CompletionTime?: IMongoDate; CompletionTime?: IMongoDate; // new versions
TimeRemaining?: number; // old versions
RushPlatinum?: number; RushPlatinum?: number;
DestructionTime?: IMongoDate; DestructionTime?: IMongoDate; // new versions
DestructionTimeRemaining?: number; // old versions
Decos?: IDojoDecoClient[]; Decos?: IDojoDecoClient[];
DecoCapacity?: number; DecoCapacity?: number;
PaintBot?: IOid; PaintBot?: IOid;
@ -212,7 +214,8 @@ export interface IDojoDecoClient {
Sockets?: number; Sockets?: number;
RegularCredits?: number; RegularCredits?: number;
MiscItems?: IMiscItem[]; MiscItems?: IMiscItem[];
CompletionTime?: IMongoDate; CompletionTime?: IMongoDate; // new versions
TimeRemaining?: number; // old versions
RushPlatinum?: number; RushPlatinum?: number;
PictureFrameInfo?: IPictureFrameInfo; PictureFrameInfo?: IPictureFrameInfo;
Pending?: boolean; Pending?: boolean;

View File

@ -8,8 +8,8 @@ export interface IAccountAndLoginResponseCommons {
ForceLogoutVersion?: number; ForceLogoutVersion?: number;
AmazonAuthToken?: string; AmazonAuthToken?: string;
AmazonRefreshToken?: string; AmazonRefreshToken?: string;
ConsentNeeded: boolean; ConsentNeeded?: boolean;
TrackedSettings: string[]; TrackedSettings?: string[];
Nonce: number; Nonce: number;
} }

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[];