diff --git a/config.json.example b/config.json.example index e874d450..fe7e4bb2 100644 --- a/config.json.example +++ b/config.json.example @@ -20,5 +20,6 @@ "unlockAllShipDecorations": true, "unlockAllFlavourItems": true, "unlockAllSkins": true, + "useStaticWorldState": true, "spoofMasteryRank": -1 } diff --git a/src/controllers/dynamic/worldStateController.ts b/src/controllers/dynamic/worldStateController.ts index 32cfdfa0..fa42d3bb 100644 --- a/src/controllers/dynamic/worldStateController.ts +++ b/src/controllers/dynamic/worldStateController.ts @@ -1,13 +1,23 @@ import { RequestHandler } from "express"; import worldState from "@/static/fixed_responses/worldState.json"; import buildConfig from "@/static/data/buildConfig.json"; +import { IWorldState } from "@/src/types/worldStateTypes"; +import { config } from "@/src/services/configService"; +import { getWorldState } from "@/src/services/worldStateService"; -const worldStateController: RequestHandler = (_req, res) => { - res.json({ - ...worldState, - BuildLabel: buildConfig.buildLabel, - Time: Math.round(Date.now() / 1000) - }); +const worldStateController: RequestHandler = async (_req, res) => { + let ws: IWorldState = {} + if(config.useStaticWorldState){ + ws = worldState; + ws.BuildLabel = buildConfig.buildLabel; + ws.Time = Math.round(Date.now() / 1000); + } else { + ws = (await getWorldState()).toJSON(); + ws.BuildLabel = buildConfig.buildLabel; + ws.Time = Math.round(Date.now() / 1000); + } + + res.json(ws); }; export { worldStateController }; diff --git a/src/models/worldStateModel.ts b/src/models/worldStateModel.ts new file mode 100644 index 00000000..535d82e7 --- /dev/null +++ b/src/models/worldStateModel.ts @@ -0,0 +1,484 @@ +import { model, Schema } from "mongoose"; +import { IEvent, IFlashSale, IJob, ILink, IMessage, IPVPChallengeInstance, ICategory, IPVPChallengeInstanceParam, IWorldState, IMission, IAlert, ICountedItems, IReward, IBaseWorldStateObject, ISortie, ILiteSortie, ISortieMission, ISyndicateMission, IActiveMission, IGlobalUpgrade, IInGameMarket, ILandingPage, IInvasion, IInvasionMissionInfo, INodeOverride, IVoidTrader, IVoidTraderItem, IVoidTraderScheduleInfo, IVoidStorm, IPrimeAccessAvailability, IDailyDeal, ILibraryInfo, IEndlessXpChoice, IFeaturedGuild, IActiveChallenge, ISeasonInfo } from "@/src/types/worldStateTypes"; + +const messageSchema = new Schema( + { + LanguageCode: String, + Message: String + }, + { _id: false } +); + +const linkSchema = new Schema( + { + LanguageCode: String, + Link: String + }, + { _id: false } +); + +const EventSchema = new Schema( + { + Messages: [messageSchema], + Prop: String, + Links: [linkSchema], + Date: Date, + Icon: String, + EventStartDate: Date, + EventEndDate: Date, + ImageUrl: String, + Priority: Boolean, + MobileOnly: Boolean, + HideEndDateModifier: Boolean, + Community: Boolean + } +); + + +EventSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const CountedItemsSchema = new Schema( + { + ItemType: String, + ItemCount: Number + }, + { _id: false } +); + +const RewardSchema = new Schema( + { + credits: Number, + xp: Number, + items: [String], + countedItems: [CountedItemsSchema], + }, + { _id: false } +) + +const MissionSchema = new Schema( + { + location: String, + missionType: String, + faction: String, + difficulty: Number, + missionReward: RewardSchema, + levelOverride: String, + enemySpec: String, + minEnemyLevel: Number, + maxEnemyLevel: Number, + descText: String, + maxWaveNum: Number, + exclusiveWeapon: String, + nightmare: Boolean, + archwingRequired: Boolean, + isSharkwing: Boolean, + advancedSpawners: [String], + requiredItems: [String], + consumeRequiredItems: Boolean, + vipAgent: Boolean, + leadersAlwaysAllowed: Boolean, + goalTag: String, + levelAuras: [String], + }, + { _id: false } +); + +const AlertSchema = new Schema({ + Activation: Date, + Expiry: Date, + MissionInfo: MissionSchema, + ForceUnlock: Boolean, + Tag: String +}); + +AlertSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const SortieMissionSchema = new Schema({ + missionType: String, + modifierType: String, + node: String, + tileset: String +}); + +SortieMissionSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const LiteSortieSchema = new Schema({ + Activation: Date, + Expiry: Date, + Reward: String, + Seed: String, + Boss: String, + Missions: [SortieMissionSchema] +}); + +LiteSortieSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const SortieSchema = new Schema({ + Activation: Date, + Expiry: Date, + Reward: String, + Seed: String, + Boss: String, + Variants: [SortieMissionSchema], + Twitter: Boolean +}); + +SortieSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const JobSchema = new Schema( + { + jobType: String, + rewards: String, + masteryReq: Number, + minEnemyLevel: Number, + maxEnemyLevel: Number, + xpAmounts: [Number], + endless: Boolean, + bonusXpMultiplier: Number, + locationTag: String, + isVault: Boolean + }, + { _id: false } +); + +const SyndicateMissionSchema = new Schema({ + Activation: Date, + Expiry: Date, + Tag: String, + Seed: String, + Nodes: [String], + Jobs: [JobSchema] +}); + +SyndicateMissionSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + + +const ActiveMissionSchema = new Schema({ + Activation: Date, + Expiry: Date, + Region: String, + Seed: String, + Node: String, + MissionType: String, + Modifier: String, + Hard: Boolean +}); + +ActiveMissionSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const GlobalUpgradeSchema = new Schema({ + Activation: Date, + Expiry: Date, + UpgradeType: String, + OperationType: String, + Value: String +}); + +GlobalUpgradeSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const FlashSaleSchema = new Schema( + { + TypeName: String, + StartDate: Date, + EndDate: Date, + ShowInMarket: Boolean, + HideFromMarket: Boolean, + SupporterPack: Boolean, + Discount: Number, + RegularOverride: Number, + PremiumOverride: Number, + BogoBuy: Number, + BogoGet: Number + }, + { _id: false } +); + +const ShopCategorySchema = new Schema( + { + CategoryName: String, + Name: String, + Icon: String, + AddToMenu: Boolean, + Items: [String] + }, + { _id: false } +); + +const LandingPageSchema = new Schema( + { + Categories: ShopCategorySchema + }, + { _id: false } +); + +const InGameMarketSchema = new Schema( + { + LandingPage: LandingPageSchema + }, + { _id: false } +) + +const InvasionMissionInfoSchema = new Schema( + { + seed: Number, + faction: String + }, + { _id: false } +) + +const InvasionSchema = new Schema({ + Activation: Date, + Faction: String, + DefenderFaction: String, + Node: String, + Count: Number, + Goal: Number, + LocTag: String, + Completed: Boolean, + ChainID: Schema.Types.ObjectId, + AttackerReward: RewardSchema, + AttackerMissionInfo: InvasionMissionInfoSchema, + DefenderReward: RewardSchema, + DefenderMissionInfo: InvasionMissionInfoSchema, +}); + +InvasionSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const NodeOverrideSchema = new Schema({ + Activation: Date, + Expiry: Date, + Node: String, + Faction: String, + CustomNpcEncounters: [String], + LevelOverride: String, +}); + +NodeOverrideSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const VoidTraderItemSchema = new Schema( + { + ItemType: String, + PrimePrice: Number, + RegularPrice: Number + }, + { _id: false } +); + +const VoidTraderScheduleInfoSchema = new Schema( + { + Expiry: Date, + PreviewHiddenUntil: Date, + FeaturedItem: String + }, + { _id: false } +); + +const VoidTraderSchema = new Schema( + { + Activation: Date, + Expiry: Date, + Character: String, + Node: String, + Completed: Boolean, + Manifest: [VoidTraderItemSchema], + EvergreenManifest: [VoidTraderItemSchema], + ScheduleInfo: [VoidTraderScheduleInfoSchema], + } +); + +VoidTraderSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const VoidStormSchema = new Schema({ + Activation: Date, + Expiry: Date, + Node: String, + ActiveMissionTier: String +}); + +VoidStormSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const PrimeAccessAvailabilitySchema = new Schema( + { + State: String + }, + { _id: false } +); + +const DailyDealSchema = new Schema( + { + Activation: Date, + Expiry: Date, + StoreItem: String, + Discount: Number, + OriginalPrice: Number, + SalePrice: Number, + AmountTotal: Number, + AmountSold: Number + }, + { _id: false } +); + +const LibraryInfoSchema = new Schema( + { + LastCompletedTargetType: String + }, + { _id: false } +); + +const PVPChallengeInstanceParam = new Schema( + { + n: String, + v: Number + }, + { _id: false } +); + +const PVPChallengeInstanceSchema = new Schema( + { + challengeTypeRefID: String, + startDate: Date, + endDate: Date, + params: [PVPChallengeInstanceParam], + isGenerated: Boolean, + PVPMode: String, + subChallenges: [Schema.Types.ObjectId], + Category: String + } +); + +PVPChallengeInstanceSchema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const EndlessXpChoiceSchema = new Schema( + { + Category: String, + Choices: [String] + }, + { _id: false } +); + +const FeaturedGuildShema = new Schema({ + Name: String, + Tier: Number, + Emblem: Boolean, + OriginalPlatform: Number, + AllianceId: Schema.Types.ObjectId +}); + +FeaturedGuildShema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const ActiveChallengeSchema = new Schema({ + Activation: Date, + Expiry: Date, + Daily: Boolean, + Challenge: String +}); + +FeaturedGuildShema.set("toJSON", { + transform(_document, returnedObject) { + returnedObject._id = { $oid: returnedObject._id.toString()}; + } +}); + +const SeasonInfoSchema = new Schema( + { + AffiliationTag: String, + Season: Number, + Phase: Number, + Params: String, + ActiveChallenges: [ActiveChallengeSchema] + }, { _id: false } +); + +const WorldStateSchema = new Schema( + { + Events: [EventSchema], + // Goals: [GoalSchema], + Alerts: [AlertSchema], + Sorties: [SortieSchema], + LiteSorties: [LiteSortieSchema], + SyndicateMissions: [SyndicateMissionSchema], + ActiveMissions: [ActiveMissionSchema], + GlobalUpgrades: [GlobalUpgradeSchema], + FlashSales: [FlashSaleSchema], + InGameMarket: InGameMarketSchema, + Invasions: [InvasionSchema], + NodeOverrides: [NodeOverrideSchema], + VoidTraders: [VoidTraderSchema], + PrimeVaultTraders: [VoidTraderSchema], + VoidStorms: [VoidStormSchema], + PrimeAccessAvailability: PrimeAccessAvailabilitySchema, + DailyDeals: [DailyDealSchema], + LibraryInfo: LibraryInfoSchema, + PVPChallengeInstances: [PVPChallengeInstanceSchema], + ProjectPct: [Number], + EndlessXpChoices: [EndlessXpChoiceSchema], + FeaturedGuilds: [FeaturedGuildShema], + SeasonInfo: SeasonInfoSchema, + Tmp: String + } +); + +WorldStateSchema.set("toJSON", { + transform(_document, returnedObject) { + delete returnedObject.__v; + delete returnedObject._id; + } +}); + +export const WorldState = model("WorldState", WorldStateSchema); \ No newline at end of file diff --git a/src/services/configService.ts b/src/services/configService.ts index 283b31bf..f8ea42c8 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -37,6 +37,7 @@ interface IConfig { unlockAllFlavourItems?: boolean; unlockAllSkins?: boolean; spoofMasteryRank?: number; + useStaticWorldState?: boolean; } interface ILoggerConfig { diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts new file mode 100644 index 00000000..dd3cdbca --- /dev/null +++ b/src/services/worldStateService.ts @@ -0,0 +1,17 @@ +import { WorldState } from "@/src/models/worldStateModel"; +import buildConfig from "@/static/data/buildConfig.json"; + +export const createWorldState = async () => { + const worldState = new WorldState() + await worldState.save(); + return worldState; +} + +export const getWorldState = async () => { + let ws = await WorldState.findOne(); + if (!ws) { + ws = await createWorldState(); + } + + return ws; +}; \ No newline at end of file diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts new file mode 100644 index 00000000..b2cb5219 --- /dev/null +++ b/src/types/worldStateTypes.ts @@ -0,0 +1,339 @@ +import { IMongoDate, IOid } from "@/src/types/commonTypes"; + +export interface IMessage { + LanguageCode?: string; + Message: string; +}; + +export interface ILink { + LanguageCode?: string; + Link: string; +}; + +export interface IBaseWorldStateObject { + Activation: IMongoDate; + Expiry: IMongoDate; + _id?: IOid +} + +export interface IReward { + credits?: number; + xp?: number; + items?: string[]; + countedItems?: ICountedItems[]; +} + +export interface ICountedItems { + ItemType: string; + ItemCount: number; +} + +export interface IMission { + location: string; + missionType: string; + faction: string; + difficulty: number; + missionReward: IReward; + levelOverride: string; + enemySpec: string; + minEnemyLevel: number; + maxEnemyLevel: number; + descText: string; + maxWaveNum?: number; + exclusiveWeapon?: string; + nightmare?: boolean; + archwingRequired?: boolean; + isSharkwing?: boolean; + advancedSpawners?: string[]; + requiredItems?: string[]; + consumeRequiredItems?: boolean; + vipAgent?: boolean; + leadersAlwaysAllowed?: boolean; + goalTag?: string; + levelAuras?: string[]; +} + +export interface IEvent { + Messages: IMessage[]; + Prop?: string; + ImageUrl?: string; + Links?: ILink[]; + Icon?: string; + Community?: boolean; + Priority?: boolean; + EventStartDate?: IMongoDate; + EventEndDate?: IMongoDate; + MobileOnly?: boolean; + HideEndDateModifier?: boolean; + Date?: IMongoDate; + _id?: IOid; +}; + +export interface IGoal extends IBaseWorldStateObject { + Node: string; + ScoreVar: string; + ScoreLocTag: string; + Count: number; + HealthPct: number; + Regions: number[]; + Desc: string; + ToolTip: string; + OptionalInMission: boolean; + Tag: string; + UpgradeIds: IOid[]; + Personal: boolean; + Community: boolean; + Goal: number; + Reward: IReward; + InterimGoals: number[]; + InterimRewards: IReward[]; +}; + +export interface IAlert extends IBaseWorldStateObject { + MissionInfo: IMission; + ForceUnlock: boolean; + Tag: string; +} + +export interface ISortieMission { + missionType: string; + modifierType?: string; + node: string; + tileset?: string +} + +export interface ISortie extends Omit { + // ExtraDrops: []; Unknown + Variants: ISortieMission[]; + Twitter: boolean; +} + +export interface ILiteSortie extends IBaseWorldStateObject { + Reward: string; + Seed: number; + Boss: String; + Missions: ISortieMission[] +} + +export interface IJob { + jobType?: string; + rewards: string; + masteryReq: number; + minEnemyLevel: number; + maxEnemyLevel: number; + endless?: boolean; + bonusXpMultiplier?: number; + xpAmounts: number[]; + locationTag?: string; + isVault?: boolean; +} +export interface ISyndicateMission extends IBaseWorldStateObject { + Tag: string; + Seed: number; + Nodes: string[]; + Jobs?: IJob[]; +} + +export interface IActiveMission extends IBaseWorldStateObject { + Region: number; + Seed: number; + Node: string; + MissionType: string; + Modifier?: string; + Hard?: boolean; +} + +export interface IGlobalUpgrade extends IBaseWorldStateObject { + UpgradeType: string; + OperationType: string; + Value: string; +} + +export interface IFlashSale { + StartDate: IMongoDate; + EndDate: IMongoDate; + TypeName: string; + ShowInMarket: boolean; + HideFromMarket: boolean; + SupporterPack: boolean; + Discount: number; + RegularOverride: number; + PremiumOverride: number; + BogoBuy: number; + BogoGet: number; +} + + +export interface IInGameMarket { + LandingPage: ILandingPage; +} + +export interface ILandingPage { + Categories: ICategory[]; +} + +export interface ICategory { + CategoryName: string; + Name: string; + Icon: string; + AddToMenu?: boolean; + Items: string[]; +} + +export interface IInvasion extends Omit { + Faction: string; + DefenderFaction: string; + Node: string; + Count: number; + Goal: number; + LocTag: string; + Completed: boolean; + ChainID: IOid; + AttackerReward: IReward; + AttackerMissionInfo: IInvasionMissionInfo; + DefenderReward: IReward; + DefenderMissionInfo: IInvasionMissionInfo; +} + +export interface IInvasionMissionInfo { + seed: number; + faction: string; +} + +export interface INodeOverride { + Activation?: IMongoDate; + Expiry?: IMongoDate; + Node: string, + Faction?: string, + CustomNpcEncounters?: string[]; + LevelOverride?: string; +} + +export interface IVoidTrader extends IBaseWorldStateObject { + Character?: string; + Node: string; + Completed?: boolean; + Manifest?: IVoidTraderItem[]; + EvergreenManifest?: IVoidTraderItem[]; + ScheduleInfo?: IVoidTraderScheduleInfo[]; +} + +export interface IVoidTraderItem { + ItemType: string; + PrimePrice?: number; + RegularPrice?: number; +} + +export interface IVoidTraderScheduleInfo extends Omit { + PreviewHiddenUntil?: IMongoDate; + FeaturedItem?: string; +} + + +export interface IVoidStorm extends IBaseWorldStateObject { + Node: string; + ActiveMissionTier: string; +} + +export interface IPrimeAccessAvailability { + State: string; +} + +export interface IDailyDeal extends Omit { + StoreItem: string; + Discount: number; + OriginalPrice: number; + SalePrice: number; + AmountTotal: number; + AmountSold: number; +} + +export interface ILibraryInfo { + LastCompletedTargetType: string; +} + +export interface IPVPChallengeInstance { + challengeTypeRefID: string; + startDate: IMongoDate; + endDate: IMongoDate; + params: IPVPChallengeInstanceParam[]; + isGenerated: boolean; + PVPMode: string; + subChallenges: IOid[]; + Category: string; +} + +export interface IPVPChallengeInstanceParam { + n: string; + v: number; +} + + +export interface IEndlessXpChoice { + Category: string; + Choices: string[]; +} + +export interface IFeaturedGuild { + _id: IOid; + Name: string; + Tier: number; + Emblem: boolean; + OriginalPlatform: number; + AllianceId?: IOid; +} + +export interface ISeasonInfo extends Omit { + AffiliationTag: string; + Season: number; + Phase: number; + Params: string; + ActiveChallenges: IActiveChallenge[]; +} + +export interface IActiveChallenge extends IBaseWorldStateObject { + Daily?: boolean; + Challenge: string; +} + +export interface IWorldState { + WorldSeed?: string; + Version?: number; + MobileVersion?: string; + BuildLabel?: string; + Time?: number; + Events?: IEvent[]; + Goals?: IGoal[]; + Alerts?: IAlert[]; + Sorties?: ISortie[]; + LiteSorties?: ILiteSortie[]; + SyndicateMissions?: ISyndicateMission[]; + ActiveMissions?: IActiveMission[]; + GlobalUpgrades?: IGlobalUpgrade[]; + FlashSales?: IFlashSale[]; + InGameMarket?: IInGameMarket; + Invasions?: IInvasion[]; + NodeOverrides?: INodeOverride[]; + VoidTraders?: IVoidTrader[]; + PrimeVaultTraders?: IVoidTrader[]; + VoidStorms?: IVoidStorm[]; + PrimeAccessAvailability?: IPrimeAccessAvailability; + DailyDeals?: IDailyDeal[]; + LibraryInfo?: ILibraryInfo; + PVPChallengeInstances?: IPVPChallengeInstance[]; + ProjectPct?: number[]; + EndlessXpChoices?: IEndlessXpChoice[]; + ForceLogoutVersion?: number; + FeaturedGuilds?: IFeaturedGuild[]; + SeasonInfo?: ISeasonInfo; + Tmp?: string; + + // Unkown + // HubEvent?: []; + // PersistentEnemies?: []; + // PVPAlternativeModes?: []; + // PVPActiveTournaments?: []; + // ConstructionProjects?: []; + // TwitchPromos?: []; + // ExperimentRecommended?: []; +} \ No newline at end of file diff --git a/static/fixed_responses/worldState.json b/static/fixed_responses/worldState.json index aa30c06b..d42de99b 100644 --- a/static/fixed_responses/worldState.json +++ b/static/fixed_responses/worldState.json @@ -889,8 +889,7 @@ "Expiry": { "$date": { "$numberLong": "1717696800000" } }, "PreviewHiddenUntil": { "$date": { "$numberLong": "1714068000000" } }, "FeaturedItem": "/Lotus/Types/StoreItems/Packages/MegaPrimeVault/MPVEquinoxWukongPrimeDualPack" - }, - { "Expiry": { "$date": { "$numberLong": "1720116000000" } }, "PreviewHiddenUntil": { "$date": { "$numberLong": "1716487200000" } } } + } ] } ],