返回提交历史
Modified
config-vanilla.json
+1
-0
Modified
src/models/leaderboardModel.ts
+1
-1
Modified
src/models/statsModel.ts
+13
-1
Modified
src/routes/stats.ts
+1
-0
Modified
src/services/configService.ts
+1
-0
Modified
src/services/guildService.ts
+15
-0
Modified
src/services/leaderboardService.ts
+40
-11
Modified
src/services/missionInventoryUpdateService.ts
+45
-4
Modified
src/services/statsService.ts
+23
-0
Modified
src/services/worldStateService.ts
+80
-3
Modified
src/types/leaderboardTypes.ts
+1
-1
Modified
src/types/requestTypes.ts
+1
-0
Modified
src/types/statTypes.ts
+13
-0
Modified
src/types/worldStateTypes.ts
+5
-2
Modified
static/webui/index.html
+7
-1
Modified
static/webui/translations/de.js
+1
-0
Modified
static/webui/translations/en.js
+1
-0
Modified
static/webui/translations/es.js
+1
-0
Modified
static/webui/translations/fr.js
+1
-0
Modified
static/webui/translations/ru.js
+1
-0
Modified
static/webui/translations/uk.js
+1
-0
Modified
static/webui/translations/zh.js
+1
-0
XFEstudio/XFESpaceNinjaServer
feat: orphix venom (#2637)
Without rotation on last mission Re #1103 Thanks to https://wiki.warframe.com/w/World_State/Example Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2637 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
d0743654
代码差异
22 个文件
+254
-24
@@ -67,6 +67,7 @@
67
67
"resourceBoost": false,
68
68
"tennoLiveRelay": false,
69
69
"wolfHunt": false,
70
"orphixVenom": false,
70
71
"longShadow": false,
71
72
"hallowedFlame": false,
72
73
"hallowedNightmares": false,
@@ -8,7 +8,7 @@ const leaderboardEntrySchema = new Schema<ILeaderboardEntryDatabase>(
8
8
displayName: { type: String, required: true },
9
9
score: { type: Number, required: true },
10
10
guildId: Schema.Types.ObjectId,
11
expiry: { type: Date, required: true },
11
expiry: Date,
12
12
guildTier: Number
13
13
},
14
14
{ id: false }
@@ -97,7 +97,19 @@ const statsSchema = new Schema<IStatsDatabase>({
97
97
SentinelGameScore: Number,
98
98
CaliberChicksScore: Number,
99
99
OlliesCrashCourseScore: Number,
100
DojoObstacleScore: Number
100
DojoObstacleScore: Number,
101
102
Halloween16: Number,
103
AmalgamEventScoreMax: Number,
104
Halloween19ScoreMax: Number,
105
FlotillaEventScore: Number,
106
FlotillaSpaceBadgesTier1: Number,
107
FlotillaSpaceBadgesTier2: Number,
108
FlotillaSpaceBadgesTier3: Number,
109
FlotillaGroundBadgesTier1: Number,
110
FlotillaGroundBadgesTier2: Number,
111
FlotillaGroundBadgesTier3: Number,
112
MechSurvivalScoreMax: Number
101
113
});
102
114
103
115
statsSchema.set("toJSON", {
@@ -8,5 +8,6 @@ const statsRouter = express.Router();
8
8
statsRouter.get("/view.php", viewController);
9
9
statsRouter.post("/upload.php", uploadController);
10
10
statsRouter.post("/leaderboardWeekly.php", leaderboardController);
11
statsRouter.post("/leaderboardArchived.php", leaderboardController);
11
12
12
13
export { statsRouter };
@@ -79,6 +79,7 @@ export interface IConfig {
79
79
tennoLiveRelay?: boolean;
80
80
baroTennoConRelay?: boolean;
81
81
wolfHunt?: boolean;
82
orphixVenom?: boolean;
82
83
longShadow?: boolean;
83
84
hallowedFlame?: boolean;
84
85
hallowedNightmares?: boolean;
@@ -896,5 +896,20 @@ export const goalGuildRewardByTag: Record<string, { guildGoals: number[][]; rewa
896
896
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventSilverTrophyRecipe",
897
897
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventGoldTrophyRecipe"
898
898
]
899
},
900
MechSurvival: {
901
guildGoals: [
902
[1390, 5860, 13920, 18850],
903
[3510, 22275, 69120, 137250],
904
[11700, 75250, 230400, 457500],
905
[35100, 222750, 691200, 1372500],
906
[117000, 742500, 2304000, 4575000]
907
],
908
rewards: [
909
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophyTerracottaRecipe",
910
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophyBronzeRecipe",
911
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophySilverRecipe",
912
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophyGoldRecipe"
913
]
899
914
}
900
915
};
@@ -1,38 +1,66 @@
1
1
import { Guild } from "@/src/models/guildModel";
2
2
import { Leaderboard, TLeaderboardEntryDocument } from "@/src/models/leaderboardModel";
3
3
import { ILeaderboardEntryClient } from "@/src/types/leaderboardTypes";
4
import { handleGuildGoalProgress } from "@/src/services/guildService";
5
import { getWorldState } from "@/src/services/worldStateService";
6
import { Types } from "mongoose";
4
7
5
8
export const submitLeaderboardScore = async (
6
schedule: "weekly" | "daily",
9
schedule: "weekly" | "daily" | "events",
7
10
leaderboard: string,
8
11
ownerId: string,
9
12
displayName: string,
10
13
score: number,
11
14
guildId: string | undefined
12
15
): Promise<void> => {
13
let expiry: Date;
16
let expiry: Date | undefined;
14
17
if (schedule == "daily") {
15
18
expiry = new Date(Math.trunc(Date.now() / 86400000) * 86400000 + 86400000);
16
} else {
19
} else if (schedule == "weekly") {
17
20
const EPOCH = 1734307200 * 1000; // Monday
18
21
const week = Math.trunc((Date.now() - EPOCH) / 604800000);
19
22
const weekStart = EPOCH + week * 604800000;
20
23
const weekEnd = weekStart + 604800000;
21
24
expiry = new Date(weekEnd);
22
25
}
26
27
if (guildId) {
28
const guild = (await Guild.findById(guildId, "Name Tier GoalProgress VaultDecoRecipes"))!;
29
if (schedule == "events") {
30
const prevAccount = await Leaderboard.findOne(
31
{ leaderboard: `${schedule}.accounts.${leaderboard}`, ownerId },
32
"score"
33
);
34
const delta = score - (prevAccount?.score ?? 0);
35
if (delta > 0) {
36
await Leaderboard.findOneAndUpdate(
37
{ leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId },
38
{ $inc: { score: delta }, $set: { displayName: guild.Name, guildTier: guild.Tier } },
39
{ upsert: true }
40
);
41
const goal = getWorldState().Goals.find(x => x.ScoreMaxTag == leaderboard);
42
if (goal) {
43
await handleGuildGoalProgress(guild, {
44
Count: delta,
45
Tag: goal.Tag,
46
goalId: new Types.ObjectId(goal._id.$oid)
47
});
48
}
49
}
50
} else {
51
await Leaderboard.findOneAndUpdate(
52
{ leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId },
53
{ $max: { score }, $set: { displayName: guild.Name, guildTier: guild.Tier, expiry } },
54
{ upsert: true, new: true }
55
);
56
}
57
}
58
23
59
await Leaderboard.findOneAndUpdate(
24
60
{ leaderboard: `${schedule}.accounts.${leaderboard}`, ownerId },
25
61
{ $max: { score }, $set: { displayName, guildId, expiry } },
26
62
{ upsert: true }
27
63
);
28
if (guildId) {
29
const guild = (await Guild.findById(guildId, "Name Tier"))!;
30
await Leaderboard.findOneAndUpdate(
31
{ leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId },
32
{ $max: { score }, $set: { displayName: guild.Name, guildTier: guild.Tier, expiry } },
33
{ upsert: true }
34
);
35
}
36
64
};
37
65
38
66
export const getLeaderboard = async (
@@ -43,6 +71,7 @@ export const getLeaderboard = async (
43
71
guildId: string | undefined,
44
72
guildTier: number | undefined
45
73
): Promise<ILeaderboardEntryClient[]> => {
74
leaderboard = leaderboard.replace("archived", guildTier || guildId ? "events.guilds" : "events.accounts");
46
75
const filter: { leaderboard: string; guildId?: string; guildTier?: number } = { leaderboard };
47
76
if (guildId) {
48
77
filter.guildId = guildId;
@@ -652,7 +652,12 @@ export const addMissionInventoryUpdates = async (
652
652
}
653
653
}
654
654
if (currentMissionKey && currentMissionKey in goalMessagesByKey) {
655
const totalCount = (goalProgress?.Count ?? 0) + uploadProgress.Count;
655
let countBeforeUpload = goalProgress?.Count ?? 0;
656
let totalCount = countBeforeUpload + uploadProgress.Count;
657
if (goal.Best) {
658
countBeforeUpload = goalProgress?.Best ?? 0;
659
totalCount = uploadProgress.Best;
660
}
656
661
let reward;
657
662
658
663
if (goal.InterimGoals && goal.InterimRewards) {
@@ -660,7 +665,7 @@ export const addMissionInventoryUpdates = async (
660
665
if (
661
666
goal.InterimGoals[i] &&
662
667
goal.InterimGoals[i] <= totalCount &&
663
(!goalProgress || goalProgress.Count < goal.InterimGoals[i]) &&
668
(!goalProgress || countBeforeUpload < goal.InterimGoals[i]) &&
664
669
goal.InterimRewards[i]
665
670
) {
666
671
reward = goal.InterimRewards[i];
@@ -672,7 +677,7 @@ export const addMissionInventoryUpdates = async (
672
677
!reward &&
673
678
goal.Goal &&
674
679
goal.Goal <= totalCount &&
675
(!goalProgress || goalProgress.Count < goal.Goal) &&
680
(!goalProgress || countBeforeUpload < goal.Goal) &&
676
681
goal.Reward
677
682
) {
678
683
reward = goal.Reward;
@@ -681,7 +686,7 @@ export const addMissionInventoryUpdates = async (
681
686
!reward &&
682
687
goal.BonusGoal &&
683
688
goal.BonusGoal <= totalCount &&
684
(!goalProgress || goalProgress.Count < goal.BonusGoal) &&
689
(!goalProgress || countBeforeUpload < goal.BonusGoal) &&
685
690
goal.BonusReward
686
691
) {
687
692
reward = goal.BonusReward;
@@ -1091,6 +1096,12 @@ export const addMissionRewards = async (
1091
1096
}
1092
1097
}
1093
1098
}
1099
if (rewardInfo.GoalProgressAmount && goal.Tag.startsWith("MechSurvival")) {
1100
MissionRewards.push({
1101
StoreItem: "/Lotus/StoreItems/Types/Items/MiscItems/MechSurvivalEventCreds",
1102
ItemCount: Math.trunc(rewardInfo.GoalProgressAmount / 10)
1103
});
1104
}
1094
1105
}
1095
1106
}
1096
1107
@@ -2416,5 +2427,35 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
2416
2427
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
2417
2428
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionFourTitle",
2418
2429
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
2430
},
2431
"/Lotus/Types/Keys/MechSurvivalCorpusShip": {
2432
sndr: "/Lotus/Language/Bosses/DeimosFather",
2433
msg: "/Lotus/Language/Inbox/MechEvent2020Tier1CompleteDesc",
2434
sub: "/Lotus/Language/Inbox/MechEvent2020Tier1CompleteTitle",
2435
icon: "/Lotus/Interface/Icons/Npcs/Entrati/Father.png"
2436
},
2437
"/Lotus/Types/Keys/MechSurvivalGrineerGalleon": {
2438
sndr: "/Lotus/Language/Bosses/DeimosFather",
2439
msg: "/Lotus/Language/Inbox/MechEvent2020Tier2CompleteDesc",
2440
sub: "/Lotus/Language/Inbox/MechEvent2020Tier2CompleteTitle",
2441
icon: "/Lotus/Interface/Icons/Npcs/Entrati/Father.png"
2442
},
2443
"/Lotus/Types/Keys/MechSurvivalGasCity": {
2444
sndr: "/Lotus/Language/Bosses/DeimosFather",
2445
msg: "/Lotus/Language/Inbox/MechEvent2020Tier3CompleteDesc",
2446
sub: "/Lotus/Language/Inbox/MechEvent2020Tier3CompleteTitle",
2447
icon: "/Lotus/Interface/Icons/Npcs/Entrati/Father.png"
2448
},
2449
"/Lotus/Types/Keys/MechSurvivalCorpusShipEndurance": {
2450
sndr: "/Lotus/Language/Bosses/DeimosFather",
2451
msg: "/Lotus/Language/Inbox/MechEvent2020Tier3CompleteDesc",
2452
sub: "/Lotus/Language/Inbox/MechEvent2020Tier3CompleteTitle",
2453
icon: "/Lotus/Interface/Icons/Npcs/Entrati/Father.png"
2454
},
2455
"/Lotus/Types/Keys/MechSurvivalGrineerGalleonEndurance": {
2456
sndr: "/Lotus/Language/Bosses/DeimosFather",
2457
msg: "/Lotus/Language/Inbox/MechEvent2020Tier3CompleteDesc",
2458
sub: "/Lotus/Language/Inbox/MechEvent2020Tier3CompleteTitle",
2459
icon: "/Lotus/Interface/Icons/Npcs/Entrati/Father.png"
2419
2460
}
2420
2461
};
@@ -382,6 +382,29 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate)
382
382
);
383
383
break;
384
384
385
case "Halloween16":
386
case "AmalgamEventScoreMax":
387
case "Halloween19ScoreMax":
388
case "FlotillaEventScore":
389
case "FlotillaSpaceBadgesTier1":
390
case "FlotillaSpaceBadgesTier2":
391
case "FlotillaSpaceBadgesTier3":
392
case "FlotillaGroundBadgesTier1":
393
case "FlotillaGroundBadgesTier2":
394
case "FlotillaGroundBadgesTier3":
395
case "MechSurvivalScoreMax":
396
playerStats[category] ??= 0;
397
if (data > playerStats[category]) playerStats[category] = data as number;
398
await submitLeaderboardScore(
399
"events",
400
category,
401
accountOwnerId,
402
payload.displayName,
403
data as number,
404
payload.guildId
405
);
406
break;
407
385
408
default:
386
409
if (!ignoredCategories.includes(category)) {
387
410
unknownCategories[action] ??= [];
@@ -1538,7 +1538,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
1538
1538
Personal: true,
1539
1539
Bounty: true,
1540
1540
ClampNodeScores: true,
1541
Node: "EventNode28", // Incompatible with Wolf Hunt (2025)
1541
Node: "EventNode28", // Incompatible with Wolf Hunt (2025), Orphix Venom
1542
1542
MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB",
1543
1543
Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
1544
1544
Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
@@ -1964,7 +1964,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
1964
1964
"/Lotus/Types/Keys/WolfTacAlertReduxD"
1965
1965
],
1966
1966
ConcurrentNodeReqs: [1, 2, 3],
1967
ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls
1967
ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls, Orphix Venom
1968
1968
MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertReduxA",
1969
1969
Faction: "FC_GRINEER",
1970
1970
Desc: "/Lotus/Language/Alerts/WolfAlert",
@@ -2216,7 +2216,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
2216
2216
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour"
2217
2217
],
2218
2218
ConcurrentNodeReqs: [1, 2, 3],
2219
ConcurrentNodes: ["EventNode7", "EventNode4", "EventNode17"],
2219
ConcurrentNodes: ["EventNode7", "EventNode4", "EventNode17"], // Incompatible with Orphix venom
2220
2220
MissionKeyName: "/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne",
2221
2221
Faction: "FC_CORPUS",
2222
2222
Desc: "/Lotus/Language/Alerts/TacAlertProxyRebellion",
@@ -2315,6 +2315,83 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
2315
2315
});
2316
2316
}
2317
2317
2318
if (config.worldState?.orphixVenom) {
2319
worldState.Goals.push(
2320
{
2321
_id: { $oid: "5fdcccb875d5ad500dc477d0" },
2322
Activation: { $date: { $numberLong: "1608320400000" } },
2323
Expiry: { $date: { $numberLong: "2000000000000" } },
2324
Count: 0,
2325
Goal: 500,
2326
Success: 0,
2327
Personal: true,
2328
Best: true,
2329
Node: "EventNode17", // Incompatible with Proxy Rebellion
2330
MissionKeyName: "/Lotus/Types/Keys/MechSurvivalCorpusShip",
2331
Faction: "FC_SENTIENT",
2332
Desc: "/Lotus/Language/Events/MechEventMissionTier1",
2333
Icon: "/Lotus/Interface/Icons/Categories/IconMech256.png",
2334
Tag: "MechSurvivalA",
2335
ScoreVar: "MechSurvivalScore",
2336
Reward: {
2337
items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/MechEventEmblemItem"]
2338
}
2339
},
2340
{
2341
_id: { $oid: "5fdcccb875d5ad500dc477d1" },
2342
Activation: { $date: { $numberLong: "1608320400000" } },
2343
Expiry: { $date: { $numberLong: "2000000000000" } },
2344
Count: 0,
2345
Goal: 1000,
2346
Success: 0,
2347
Personal: true,
2348
Best: true,
2349
Node: "EventNode28", // Incompatible with Galleon Of Ghouls, Wolf Hunt (2025)
2350
MissionKeyName: "/Lotus/Types/Keys/MechSurvivalGrineerGalleon",
2351
Faction: "FC_SENTIENT",
2352
Desc: "/Lotus/Language/Events/MechEventMissionTier2",
2353
Icon: "/Lotus/Interface/Icons/Categories/IconMech256.png",
2354
Tag: "MechSurvivalB",
2355
PrereqGoalTags: ["MechSurvivalA"],
2356
ScoreVar: "MechSurvivalScore",
2357
Reward: {
2358
items: ["/Lotus/StoreItems/Types/Items/FusionTreasures/OroFusexJ"]
2359
}
2360
},
2361
{
2362
_id: { $oid: "5fdcccb875d5ad500dc477d2" },
2363
Activation: { $date: { $numberLong: "1608320400000" } },
2364
Expiry: { $date: { $numberLong: "2000000000000" } },
2365
Count: 0,
2366
Goal: 2000,
2367
Success: 0,
2368
Personal: true,
2369
Best: true,
2370
Node: "EventNode32",
2371
MissionKeyName: "/Lotus/Types/Keys/MechSurvivalGasCity",
2372
MissionKeyRotation: [
2373
"/Lotus/Types/Keys/MechSurvivalGasCity",
2374
"/Lotus/Types/Keys/MechSurvivalCorpusShipEndurance",
2375
"/Lotus/Types/Keys/MechSurvivalGrineerGalleonEndurance"
2376
],
2377
MissionKeyRotationInterval: 3600, // 1 hour
2378
Faction: "FC_SENTIENT",
2379
Desc: "/Lotus/Language/Events/MechEventMissionTier3",
2380
Icon: "/Lotus/Interface/Icons/Categories/IconMech256.png",
2381
Tag: "MechSurvival",
2382
PrereqGoalTags: ["MechSurvivalA", "MechSurvivalB"],
2383
ScoreVar: "MechSurvivalScore",
2384
ScoreMaxTag: "MechSurvivalScoreMax",
2385
Reward: {
2386
items: [
2387
"/Lotus/StoreItems/Types/Items/MiscItems/FormaAura",
2388
"/Lotus/StoreItems/Upgrades/Skins/Necramech/MechWeapon/MechEventMausolonSkin"
2389
]
2390
}
2391
}
2392
);
2393
}
2394
2318
2395
// Nightwave Challenges
2319
2396
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
2320
2397
if (nightwaveSyndicateTag) {
@@ -6,7 +6,7 @@ export interface ILeaderboardEntryDatabase {
6
6
displayName: string;
7
7
score: number;
8
8
guildId?: Types.ObjectId;
9
expiry: Date;
9
expiry?: Date;
10
10
guildTier?: number;
11
11
}
12
12
@@ -206,6 +206,7 @@ export interface IRewardInfo {
206
206
Q?: boolean; // likely indicates that the bonus objective for this stage was completed
207
207
CheckpointCounter?: number; // starts at 1, is incremented with each job stage upload, and does not reset when starting a new job
208
208
challengeMissionId?: string;
209
GoalProgressAmount?: number;
209
210
}
210
211
211
212
export type IMissionStatus = "GS_SUCCESS" | "GS_FAILURE" | "GS_DUMPED" | "GS_QUIT" | "GS_INTERRUPTED";