diff --git a/src/models/leaderboardModel.ts b/src/models/leaderboardModel.ts index 591f36e3..a7375e0f 100644 --- a/src/models/leaderboardModel.ts +++ b/src/models/leaderboardModel.ts @@ -8,7 +8,7 @@ const leaderboardEntrySchema = new Schema( displayName: { type: String, required: true }, score: { type: Number, required: true }, guildId: Schema.Types.ObjectId, - expiry: { type: Date, required: true }, + expiry: Date, guildTier: Number }, { id: false } diff --git a/src/models/statsModel.ts b/src/models/statsModel.ts index d3377cc7..af015cc0 100644 --- a/src/models/statsModel.ts +++ b/src/models/statsModel.ts @@ -97,7 +97,19 @@ const statsSchema = new Schema({ SentinelGameScore: Number, CaliberChicksScore: Number, OlliesCrashCourseScore: Number, - DojoObstacleScore: Number + DojoObstacleScore: Number, + + Halloween16: Number, + AmalgamEventScoreMax: Number, + Halloween19ScoreMax: Number, + FlotillaEventScore: Number, + FlotillaSpaceBadgesTier1: Number, + FlotillaSpaceBadgesTier2: Number, + FlotillaSpaceBadgesTier3: Number, + FlotillaGroundBadgesTier1: Number, + FlotillaGroundBadgesTier2: Number, + FlotillaGroundBadgesTier3: Number, + MechSurvivalScoreMax: Number }); statsSchema.set("toJSON", { diff --git a/src/routes/stats.ts b/src/routes/stats.ts index 11705259..d0ecc647 100644 --- a/src/routes/stats.ts +++ b/src/routes/stats.ts @@ -8,5 +8,6 @@ const statsRouter = express.Router(); statsRouter.get("/view.php", viewController); statsRouter.post("/upload.php", uploadController); statsRouter.post("/leaderboardWeekly.php", leaderboardController); +statsRouter.post("/leaderboardArchived.php", leaderboardController); export { statsRouter }; diff --git a/src/services/configService.ts b/src/services/configService.ts index 997276e7..72efe735 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -79,6 +79,7 @@ export interface IConfig { tennoLiveRelay?: boolean; baroTennoConRelay?: boolean; wolfHunt?: boolean; + orphixVenom?: boolean; longShadow?: boolean; hallowedFlame?: boolean; hallowedNightmares?: boolean; diff --git a/src/services/guildService.ts b/src/services/guildService.ts index da80d2aa..cd7ee147 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -896,5 +896,20 @@ export const goalGuildRewardByTag: Record => { - let expiry: Date; + let expiry: Date | undefined; if (schedule == "daily") { expiry = new Date(Math.trunc(Date.now() / 86400000) * 86400000 + 86400000); - } else { + } else if (schedule == "weekly") { const EPOCH = 1734307200 * 1000; // Monday const week = Math.trunc((Date.now() - EPOCH) / 604800000); const weekStart = EPOCH + week * 604800000; const weekEnd = weekStart + 604800000; expiry = new Date(weekEnd); } + + if (guildId) { + const guild = (await Guild.findById(guildId, "Name Tier GoalProgress VaultDecoRecipes"))!; + if (schedule == "events") { + const prevAccount = await Leaderboard.findOne( + { leaderboard: `${schedule}.accounts.${leaderboard}`, ownerId }, + "score" + ); + const delta = score - (prevAccount?.score ?? 0); + if (delta > 0) { + await Leaderboard.findOneAndUpdate( + { leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId }, + { $inc: { score: delta }, $set: { displayName: guild.Name, guildTier: guild.Tier } }, + { upsert: true } + ); + const goal = getWorldState().Goals.find(x => x.ScoreMaxTag == leaderboard); + if (goal) { + await handleGuildGoalProgress(guild, { + Count: delta, + Tag: goal.Tag, + goalId: new Types.ObjectId(goal._id.$oid) + }); + } + } + } else { + await Leaderboard.findOneAndUpdate( + { leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId }, + { $max: { score }, $set: { displayName: guild.Name, guildTier: guild.Tier, expiry } }, + { upsert: true, new: true } + ); + } + } + await Leaderboard.findOneAndUpdate( { leaderboard: `${schedule}.accounts.${leaderboard}`, ownerId }, { $max: { score }, $set: { displayName, guildId, expiry } }, { upsert: true } ); - if (guildId) { - const guild = (await Guild.findById(guildId, "Name Tier"))!; - await Leaderboard.findOneAndUpdate( - { leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId }, - { $max: { score }, $set: { displayName: guild.Name, guildTier: guild.Tier, expiry } }, - { upsert: true } - ); - } }; export const getLeaderboard = async ( @@ -43,6 +71,7 @@ export const getLeaderboard = async ( guildId: string | undefined, guildTier: number | undefined ): Promise => { + leaderboard = leaderboard.replace("archived", guildTier || guildId ? "events.guilds" : "events.accounts"); const filter: { leaderboard: string; guildId?: string; guildTier?: number } = { leaderboard }; if (guildId) { filter.guildId = guildId; diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 8fb17b59..7196b8da 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -652,7 +652,12 @@ export const addMissionInventoryUpdates = async ( } } if (currentMissionKey && currentMissionKey in goalMessagesByKey) { - const totalCount = (goalProgress?.Count ?? 0) + uploadProgress.Count; + let countBeforeUpload = goalProgress?.Count ?? 0; + let totalCount = countBeforeUpload + uploadProgress.Count; + if (goal.Best) { + countBeforeUpload = goalProgress?.Best ?? 0; + totalCount = uploadProgress.Best; + } let reward; if (goal.InterimGoals && goal.InterimRewards) { @@ -660,7 +665,7 @@ export const addMissionInventoryUpdates = async ( if ( goal.InterimGoals[i] && goal.InterimGoals[i] <= totalCount && - (!goalProgress || goalProgress.Count < goal.InterimGoals[i]) && + (!goalProgress || countBeforeUpload < goal.InterimGoals[i]) && goal.InterimRewards[i] ) { reward = goal.InterimRewards[i]; @@ -672,7 +677,7 @@ export const addMissionInventoryUpdates = async ( !reward && goal.Goal && goal.Goal <= totalCount && - (!goalProgress || goalProgress.Count < goal.Goal) && + (!goalProgress || countBeforeUpload < goal.Goal) && goal.Reward ) { reward = goal.Reward; @@ -681,7 +686,7 @@ export const addMissionInventoryUpdates = async ( !reward && goal.BonusGoal && goal.BonusGoal <= totalCount && - (!goalProgress || goalProgress.Count < goal.BonusGoal) && + (!goalProgress || countBeforeUpload < goal.BonusGoal) && goal.BonusReward ) { reward = goal.BonusReward; @@ -1091,6 +1096,12 @@ export const addMissionRewards = async ( } } } + if (rewardInfo.GoalProgressAmount && goal.Tag.startsWith("MechSurvival")) { + MissionRewards.push({ + StoreItem: "/Lotus/StoreItems/Types/Items/MiscItems/MechSurvivalEventCreds", + ItemCount: Math.trunc(rewardInfo.GoalProgressAmount / 10) + }); + } } } @@ -2395,5 +2406,35 @@ const goalMessagesByKey: Record playerStats[category]) playerStats[category] = data as number; + await submitLeaderboardScore( + "events", + category, + accountOwnerId, + payload.displayName, + data as number, + payload.guildId + ); + break; + default: if (!ignoredCategories.includes(category)) { unknownCategories[action] ??= []; diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index b929331d..0ed14058 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1535,7 +1535,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Personal: true, Bounty: true, ClampNodeScores: true, - Node: "EventNode28", // Incompatible with Wolf Hunt (2025) + Node: "EventNode28", // Incompatible with Wolf Hunt (2025), Orphix Venom MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB", Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle", Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png", @@ -1958,7 +1958,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { "/Lotus/Types/Keys/WolfTacAlertReduxD" ], ConcurrentNodeReqs: [1, 2, 3], - ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls + ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls, Orphix Venom MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertReduxA", Faction: "FC_GRINEER", Desc: "/Lotus/Language/Alerts/WolfAlert", @@ -2210,7 +2210,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { "/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour" ], ConcurrentNodeReqs: [1, 2, 3], - ConcurrentNodes: ["EventNode7", "EventNode4", "EventNode17"], + ConcurrentNodes: ["EventNode7", "EventNode4", "EventNode17"], // Incompatible with Orphix venom MissionKeyName: "/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne", Faction: "FC_CORPUS", Desc: "/Lotus/Language/Alerts/TacAlertProxyRebellion", @@ -2309,6 +2309,83 @@ export const getWorldState = (buildLabel?: string): IWorldState => { }); } + if (config.worldState?.orphixVenom) { + worldState.Goals.push( + { + _id: { $oid: "5fdcccb875d5ad500dc477d0" }, + Activation: { $date: { $numberLong: "1608320400000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 500, + Success: 0, + Personal: true, + Best: true, + Node: "EventNode17", // Incompatible with Proxy Rebellion + MissionKeyName: "/Lotus/Types/Keys/MechSurvivalCorpusShip", + Faction: "FC_SENTIENT", + Desc: "/Lotus/Language/Events/MechEventMissionTier1", + Icon: "/Lotus/Interface/Icons/Categories/IconMech256.png", + Tag: "MechSurvivalA", + ScoreVar: "MechSurvivalScore", + Reward: { + items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/MechEventEmblemItem"] + } + }, + { + _id: { $oid: "5fdcccb875d5ad500dc477d1" }, + Activation: { $date: { $numberLong: "1608320400000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 1000, + Success: 0, + Personal: true, + Best: true, + Node: "EventNode28", // Incompatible with Galleon Of Ghouls, Wolf Hunt (2025) + MissionKeyName: "/Lotus/Types/Keys/MechSurvivalGrineerGalleon", + Faction: "FC_SENTIENT", + Desc: "/Lotus/Language/Events/MechEventMissionTier2", + Icon: "/Lotus/Interface/Icons/Categories/IconMech256.png", + Tag: "MechSurvivalB", + PrereqGoalTags: ["MechSurvivalA"], + ScoreVar: "MechSurvivalScore", + Reward: { + items: ["/Lotus/StoreItems/Types/Items/FusionTreasures/OroFusexJ"] + } + }, + { + _id: { $oid: "5fdcccb875d5ad500dc477d2" }, + Activation: { $date: { $numberLong: "1608320400000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 2000, + Success: 0, + Personal: true, + Best: true, + Node: "EventNode32", + MissionKeyName: "/Lotus/Types/Keys/MechSurvivalGasCity", + MissionKeyRotation: [ + "/Lotus/Types/Keys/MechSurvivalGasCity", + "/Lotus/Types/Keys/MechSurvivalCorpusShipEndurance", + "/Lotus/Types/Keys/MechSurvivalGrineerGalleonEndurance" + ], + MissionKeyRotationInterval: 3600, // 1 hour + Faction: "FC_SENTIENT", + Desc: "/Lotus/Language/Events/MechEventMissionTier3", + Icon: "/Lotus/Interface/Icons/Categories/IconMech256.png", + Tag: "MechSurvival", + PrereqGoalTags: ["MechSurvivalA", "MechSurvivalB"], + ScoreVar: "MechSurvivalScore", + ScoreMaxTag: "MechSurvivalScoreMax", + Reward: { + items: [ + "/Lotus/StoreItems/Types/Items/MiscItems/FormaAura", + "/Lotus/StoreItems/Upgrades/Skins/Necramech/MechWeapon/MechEventMausolonSkin" + ] + } + } + ); + } + // Nightwave Challenges const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel); if (nightwaveSyndicateTag) { diff --git a/src/types/leaderboardTypes.ts b/src/types/leaderboardTypes.ts index ed14c94d..69dbc3d9 100644 --- a/src/types/leaderboardTypes.ts +++ b/src/types/leaderboardTypes.ts @@ -6,7 +6,7 @@ export interface ILeaderboardEntryDatabase { displayName: string; score: number; guildId?: Types.ObjectId; - expiry: Date; + expiry?: Date; guildTier?: number; } diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index d272f6cf..2a7c4643 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -205,6 +205,7 @@ export interface IRewardInfo { Q?: boolean; // likely indicates that the bonus objective for this stage was completed CheckpointCounter?: number; // starts at 1, is incremented with each job stage upload, and does not reset when starting a new job challengeMissionId?: string; + GoalProgressAmount?: number; } export type IMissionStatus = "GS_SUCCESS" | "GS_FAILURE" | "GS_DUMPED" | "GS_QUIT" | "GS_INTERRUPTED"; diff --git a/src/types/statTypes.ts b/src/types/statTypes.ts index 970d1be5..08f2bcf9 100644 --- a/src/types/statTypes.ts +++ b/src/types/statTypes.ts @@ -32,6 +32,19 @@ export interface IStatsClient { OlliesCrashCourseScore?: number; DojoObstacleScore?: number; + // event scores + Halloween16?: number; + AmalgamEventScoreMax?: number; + Halloween19ScoreMax?: number; + FlotillaEventScore?: number; + FlotillaSpaceBadgesTier1?: number; + FlotillaSpaceBadgesTier2?: number; + FlotillaSpaceBadgesTier3?: number; + FlotillaGroundBadgesTier1?: number; + FlotillaGroundBadgesTier2?: number; + FlotillaGroundBadgesTier3?: number; + MechSurvivalScoreMax?: number; + // not in schema PVP?: { suitDeaths?: number; diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index 84054bbb..fbb3547f 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -56,7 +56,7 @@ export interface IGoal { Success?: number; Personal?: boolean; Community?: boolean; - Best?: boolean; // Fist one on Event Tab + Best?: boolean; // Use Best instead of Count to check for reward Bounty?: boolean; // Tactical Alert ClampNodeScores?: boolean; @@ -88,9 +88,12 @@ export interface IGoal { JobPreviousVersion?: IOid; ScoreVar?: string; - ScoreMaxTag?: string; + ScoreMaxTag?: string; // Field in leaderboard ScoreLocTag?: string; + MissionKeyRotation?: string[]; + MissionKeyRotationInterval?: number; + NightLevel?: string; } diff --git a/static/webui/index.html b/static/webui/index.html index 48128f57..6971471e 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -940,7 +940,12 @@
- + +
+
+ + +
@@ -972,6 +977,7 @@
+